/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*/
//
// x86Instruction.cpp
//
// Simon Holmes a Court
// Peter DeSantis
#include "prtypes.h"
#include "x86Instruction.h"
#include "InstructionEmitter.h"
#include "x86Formatter.h"
class x86Emitter;
extern char* x86GPRText[];
UT_DEFINE_LOG_MODULE(x86Spill);
//================================================================================
// Debugging Structures
#ifdef DEBUG_LOG
char* conditionalSuffixes[] =
{
"o", // ccJO
"no", // ccJNO
"b", // ccJB
"nb", // ccJNB
"e", // ccJE
"ne", // ccJNE
"be", // ccJBE
"nbe", // ccJNBE
"s", // ccJS
"ns", // ccJNS
"p", // ccJP
"np", // ccJNP
"l", // ccJL
"nl", // ccJNL
"le", // ccJLE
"nle", // ccJNLE
};
#endif // DEBUG_LOG
//================================================================================
// x86ArgListInstruction Methods
// For now this is a member of x86ArgListInstruction, but it really should be available for all
// instructions on the x86 platform
// Method: x86StandardUseDefine
// Caller: Emitter
// Purpose: Due to x86 behaviour of modifying source register, we must make a copy of the source before
// the operation. Unnecessary copies can be removed by the register allocator.
void x86ArgListInstruction::
x86StandardUseDefine(x86Emitter& inEmitter)
{
InstructionUse* instructionUseBegin = getInstructionUseBegin();
InstructionDefine* instructionDefineBegin = getInstructionDefineBegin();
Uint8 curIndex;
// Copy the virtual register which will be overwritten
VirtualRegister& vrToBeOverwritten = inEmitter.emit_CopyOfInput(*this, *mSrcPrimitive, 0);
inEmitter.useProducer(*mSrcPrimitive, *this, 0);
// Set the rest of the uses.
InstructionUse* curUse;
for (curUse = instructionUseBegin + 1, curIndex = 1; curUse < getInstructionUseEnd(); curUse++, curIndex++)
addStandardUse(inEmitter, curIndex);
// Now redefine the copied register.
inEmitter.redefineTemporary(*this, vrToBeOverwritten, 0);
assert(instructionDefineBegin + 1 >= getInstructionDefineEnd());
//// Set the rest of the defines.
//InstructionDefine* curDefine;
//for (curDefine = instructionDefineBegin + 1, curIndex = 1; curDefine < getInstructionDefineEnd(); curDefine++, curIndex++)
// inEmitter.defineProducer(nthOutputProducer(*mSrcPrimitive, curIndex), *this, curIndex);
}
// Method: switchUseToSpill
// Caller: Register Allocator
// Purpose: Folds the spill into the instruction if possible.
// Returns: Returns true if possible, false otherwise
bool x86ArgListInstruction::
switchUseToSpill(Uint8 inWhichUse, VirtualRegister& inVR)
{
#ifdef DEBUG_LOG
UT_LOG(x86Spill, PR_LOG_DEBUG, (" spill use %d of (%p)'", inWhichUse, this));
printOpcode(UT_LOG_MODULE(x86Spill));
#endif
DEBUG_ONLY(checkIntegrity();)
if (!opcodeAcceptsSpill())
goto SpillFail;
// Ask the argument list if it can switch the current argument
if (iArgumentList->alSwitchArgumentTypeToSpill(inWhichUse, *this)) // has side effect!
{
// Tell the Opcode that the argumentlist has been switched to a spill
switchOpcodeToSpill();
// Replace the old virtual register with a new one which contains the colored stack slot.
InstructionUse& use = getInstructionUseBegin()[inWhichUse];
InstructionDefine& define = getInstructionDefineBegin()[0];
assert(use.isVirtualRegister());
if(inWhichUse == 0 && define.isVirtualRegister() && use.getVirtualRegister().getRegisterIndex() == define.getVirtualRegister().getRegisterIndex())
// this virtual register is also redefined by this instruction so we must reinitialize the define also
define.getVirtualRegisterPtr().initialize(inVR);
use.getVirtualRegisterPtr().initialize(inVR);
DEBUG_ONLY(checkIntegrity();)
goto SpillSuccess;
}
// by default we fail
SpillFail:
UT_LOG(x86Spill, PR_LOG_DEBUG, ("': false\n"));
DEBUG_ONLY(checkIntegrity();)
return false;
SpillSuccess:
UT_LOG(x86Spill, PR_LOG_DEBUG, ("': true\n"));
DEBUG_ONLY(checkIntegrity();)
return true;
}
// Method: switchDefineToSpill
// Caller: Register Allocator
// Purpose: Folds the spill into the instruction if possible.
// Returns: Returns true if possible, false otherwise
bool x86ArgListInstruction::
switchDefineToSpill(Uint8 inWhichDefine, VirtualRegister& inVR)
{
#ifdef DEBUG_LOG
UT_LOG(x86Spill, PR_LOG_DEBUG, (" spill def %d of (%p)'", inWhichDefine, this));
printOpcode(UT_LOG_MODULE(x86Spill));
#endif
DEBUG_ONLY(checkIntegrity();)
assert(inWhichDefine == 0); // can only switch the first define
InstructionUse& use = getInstructionUseBegin()[0];
InstructionUse* useEnd = getInstructionUseEnd();
InstructionDefine& define = getInstructionDefineBegin()[inWhichDefine];
assert(define.isVirtualRegister()); // cannot call this routine on anything other than a virtual register
// some instructions cannot spill
if (!opcodeAcceptsSpill())
goto SpillFail;
// If this register is being redefined then it should correspond to the first use
// Make sure that there is a first use
if(&use < useEnd)
{
// If it is indeed the same virtual register
if(use.isVirtualRegister() && use.getVirtualRegister().getRegisterIndex() == define.getVirtualRegister().getRegisterIndex())
{
// define == first use, try to switch the first argument to spill type
if(opcodeAcceptsSpill() && iArgumentList->alSwitchArgumentTypeToSpill(0, *this))
{
switchOpcodeToSpill(); // Tell the Opcode that the argumentlist has been switched to a spill
// Replace the old virtual register with a new one which contains the colored stack slot
// The define is also the same as the use so we need to reinitialize both the use and the define VR.
use.getVirtualRegisterPtr().initialize(inVR);
define.getVirtualRegisterPtr().initialize(inVR);
goto SpillSuccess;
}
}
else
{
// There are no other VRs in the uses, define is the second argument
if(opcodeAcceptsSpill() && iArgumentList->alSwitchArgumentTypeToSpill(1, *this))
{
switchOpcodeToSpill(); // Tell the Opcode that the argumentlist has been switched to a spill
// Replace the old virtual register with a new one which contains the colored stack slot
define.getVirtualRegisterPtr().initialize(inVR);
goto SpillSuccess;
}
}
}
else
{
// There are no VRs in the uses so we need to try to switch the first argument
if(opcodeAcceptsSpill() && iArgumentList->alSwitchArgumentTypeToSpill(0, *this))
{
switchOpcodeToSpill(); // Tell the Opcode that the argumentlist has been switched to a spill
// Replace the old virtual register with a new one which contains the colored stack slot
define.getVirtualRegisterPtr().initialize(inVR);
goto SpillSuccess;
}
}
// by default we fail
SpillFail:
UT_LOG(x86Spill, PR_LOG_DEBUG, ("': false\n"));
DEBUG_ONLY(checkIntegrity();)
return false;
SpillSuccess:
UT_LOG(x86Spill, PR_LOG_DEBUG, ("': true\n"));
DEBUG_ONLY(checkIntegrity();)
return true;
}
//================================================================================
// x86Instruction
void x86Instruction::
formatToMemory(void* inStart, Uint32 inOffset, MdFormatter& inFormatter)
{
assert(iOpcode != NULL && iArgumentList != NULL);
// Format the opcode to memory
iOpcode->opFormatToMemory(inStart, *iArgumentList, *this);
// Find the location of the argumnet list and format it to memory
Uint8* argLocation = (Uint8*)inStart + iOpcode->opSize();
iArgumentList->alFormatToMemory((void*)argLocation , inOffset, *this, inFormatter);
// If the opcode has an opcode extension then or it into the proper place. ( the reg field of the modr/m byte.)
if(iOpcode->opHasRegFieldExtension( *iArgumentList, *this ))
{
Uint8 temp = iOpcode->opGetRegFieldExtension();
*argLocation = *argLocation | temp;
}
}
//================================================================================
// InsnSwitch
InsnSwitch::
InsnSwitch(DataNode* inPrimitive, Pool& inPool) :
InsnUseXDefineYFromPool(inPrimitive, inPool, 1, 0 )
{
mControlNode = inPrimitive->getContainer();
assert(mControlNode);
mNumCases = mControlNode->nSuccessors();
}
void InsnSwitch::
formatToMemory(void* inStartAddress, Uint32 /*inOffset*/, MdFormatter& inFormatter)
{
Uint8* start = (Uint8*)inStartAddress;
// calculate position of jump table
mTableAddress = start + 7;
// get the register
Uint8 reg = useToRegisterNumber(*getInstructionUseBegin());
assert (reg != ESP); // ESP is an invalid index
// calculate the SIB
Uint8 SIB = 0x80 | ( reg << 3) | 0x05;
// write out instruction
*start++ = 0xff; // opcode for jump
*start++ = 0x24; // mod/ext/rm for jmp disp32[reg * 4]
*start++ = SIB;
// write address of jump table
writeLittleWordUnaligned(start, (int)mTableAddress);
// write out table
Uint8* methodBegin = inFormatter.getMethodBegin();
ControlEdge* edgesEnd = mControlNode->getSuccessorsEnd();
for(ControlEdge* edge = mControlNode->getSwitchSuccessors(); edge != edgesEnd; edge++)
{
Uint8* destAddress = methodBegin + edge->getTarget().getNativeOffset();
start += 4;
writeLittleWordUnaligned(start, (Uint32)destAddress);
}
}
size_t InsnSwitch::
getFormattedSize(MdFormatter& /*inFormatter*/)
{
// reserve 7 bytes for the indexed jump, plus 4 bytes per entry
return 7 + (4 * mNumCases);
}
#ifdef DEBUG_LOG
void InsnSwitch::
printPretty(LogModuleObject &f)
{
Uint8 reg = useToRegisterNumber(*getInstructionUseBegin());
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("jmp 0x%x[4 * %s]", Uint32(mTableAddress), x86GPRText[reg]));
// print table
ControlEdge* edgesEnd = mControlNode->getSuccessorsEnd();
for(ControlEdge* edge = mControlNode->getSwitchSuccessors(); edge != edgesEnd; edge++)
{
Uint32 destAddress = edge->getTarget().getNativeOffset();
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("\n Method Start + 0x%x [N%d]",
destAddress, edge->getTarget().dfsNum, edge->getTarget().dfsNum));
}
}
#endif
//================================================================================
// InsnCondBranch
void InsnCondBranch::
formatToMemory(void* inStart, Uint32 inOffset, MdFormatter& /*inFormatter*/)
{
uint8* start = (uint8*) inStart;
*start++ = 0x0f;
*start++ = 0x80 | condType;
// find destination
ControlNode& cnoTarget = cnoSource.getTrueSuccessor().getTarget();
// To compute the relative branch we subtract our current address from out target address. Then we subtract the size
// of our instruction because our current IP is actually there. This is the sum of the size of the opcode and the size of the
// argumentlist. NOTE: If we use 1 byte jumps in the future then this needs to be fixed from a constant 4.
const int opcodeSize = 2;
Int32 jumpOffset = cnoTarget.getNativeOffset() - inOffset - opcodeSize - 4;
writeLittleWordUnaligned(start, jumpOffset);
}
#ifdef DEBUG_LOG
void InsnCondBranch::
printPretty(LogModuleObject &f)
{
ControlNode& cnoTarget = cnoSource.getTrueSuccessor().getTarget();
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("j%-7s start+%0d [N%d]",
conditionalSuffixes[condType], cnoTarget.getNativeOffset(), cnoTarget.dfsNum, cnoTarget.dfsNum));
}
#endif
//================================================================================
// Set
// Can only be EAX, ECX, EDX or EBX
void InsnSet::
formatToMemory(void* inStart, Uint32 /*inOffset*/, MdFormatter& /*inFormatter*/ )
{
uint8* start = (uint8*) inStart;
// format the opcode to memory
*start++ = 0x0f;
*start++ = 0x90 | condType;
// find the register
InstructionDefine* defineBegin = getInstructionDefineBegin();
#ifdef DEBUG
InstructionUse* useBegin = getInstructionUseBegin();
InstructionUse* useEnd = getInstructionUseEnd();
assert(useBegin < useEnd); // condition code always used
InstructionDefine* defineEnd = getInstructionDefineEnd();
assert((defineBegin < defineEnd) && defineBegin->isVirtualRegister());
#endif
Uint8 reg = defineToRegisterNumber(*defineBegin);
assert(/* (reg >= 0) && */ (reg <= 3)); // these are the only legal registers
// format the register
Uint8 modRM = 0xc0 | reg;
*start = modRM;
}
#ifdef DEBUG_LOG
void InsnSet::
printPretty(LogModuleObject &f)
{
InstructionDefine* defineBegin = getInstructionDefineBegin();
Uint8 reg = defineToRegisterNumber(*defineBegin);
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("set%-5s %s", conditionalSuffixes[condType], x86GPRText[reg]));
}
#endif
//================================================================================
// InsnCondSysCallBranch
#ifdef DEBUG_LOG
void InsnSysCallCondBranch::
printPretty(LogModuleObject &f)
{
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("j%-7s (over next)\n call %p", conditionalSuffixes[condType], functionAddress));
}
#endif
//================================================================================
// InsnCondSysCallBranch
struct x86NoArgsInfo
{
uint8 opcode; // generally the opcode, but can have additional info
// eventually will be in DEBUG_LOG build only
char* text; // string for fake disassembly
};
x86NoArgsInfo noArgsInfo[] =
{
{ 0x99, "cdq" }, //opCdq 99
{ 0xcc, "int 3" }, //opBreak cc
{ 0x9e, "sahf" }, //opSahf 9e
};
void InsnNoArgs::
formatToMemory(void* inStart, Uint32 /*inOffset*/, MdFormatter& /*inFormatter*/)
{
*((Uint8*)inStart) = (Uint8) noArgsInfo[code].opcode;
}
#ifdef DEBUG_LOG
void InsnNoArgs::
printPretty(LogModuleObject &f)
{
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("%-8s", noArgsInfo[code].text));
}
#endif
//================================================================================
// InsnDoubleOp Methods
struct x86DoubleOpInfo
{
uint8 opcode; // generally the opcode, but can have additional info
bool hasPrefix; // does the opcode needs to be prefixed with 0x0f
// eventually will be in DEBUG_LOG build only
char* text; // string for fake disassembly
};
x86DoubleOpInfo doubleOpInfo[] =
{
{ 0xaf, true, "imul " }, //opIMul 0f af
{ 0xbe, true, "movsxB "}, //opMovSxB 0f be
{ 0xbf, true, "movsxH "}, //opMovSxH 0f bf
{ 0xb6, true, "movzxB "}, //opMovZxB 0f b6
{ 0xb7, true, "movzxH "} //opMovZxH 0f b7
};
InsnDoubleOp::
InsnDoubleOp( DataNode* inPrimitive, Pool& inPool, x86DoubleOpCode inCodeType,
x86ArgumentType inArgType1, x86ArgumentType inArgType2,
Uint8 uses, Uint8 defines ) :
x86ArgListInstruction (inPrimitive, inPool, uses, defines ),
codeType(inCodeType)
{
iArgumentList = new x86DoubleArgumentList(inArgType1, inArgType2);
DEBUG_ONLY(debugType = kInsnDoubleOp);
}
InsnDoubleOp::
InsnDoubleOp( DataNode* inPrimitive, Pool& inPool, x86DoubleOpCode inCodeType,
Uint32 inDisplacement,
x86ArgumentType inArgType1, x86ArgumentType inArgType2,
Uint8 uses, Uint8 defines) :
x86ArgListInstruction (inPrimitive, inPool, uses, defines ),
codeType(inCodeType)
{
iArgumentList = new x86DoubleArgumentList(inArgType1, inArgType2, inDisplacement);
DEBUG_ONLY(debugType = kInsnDoubleOp);
}
void InsnDoubleOp::
formatToMemory(void* inStart, Uint32 inOffset, MdFormatter& inFormatter)
{
uint8* start = (uint8*) inStart;
assert(iArgumentList);
// Format the opcode to memory
if(doubleOpInfo[codeType].hasPrefix)
*start++ = kPrefix_For_2_Byte;
*start = doubleOpInfo[codeType].opcode;
// Find the location of the argumnet list and format it to memory
Uint8* argLocation = (Uint8*)inStart + opcodeSize();
iArgumentList->alFormatToMemory((void*)argLocation , inOffset, *this, inFormatter);
}
Uint8 InsnDoubleOp::
opcodeSize()
{
return (doubleOpInfo[codeType].hasPrefix) ? 2 : 1;
}
#ifdef DEBUG_LOG
void InsnDoubleOp::
printOpcode(LogModuleObject &f)
{
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("%-8s ", doubleOpInfo[codeType].text));
}
#endif
//================================================================================
// InsnDoubleOpDir Methods
// used for debugging only (for now)
enum raType
{
kArith,
kLoad,
kStore,
kCopy
};
struct x86RAOpcodeInfo
{
uint8 opcode; // generally the opcode, but can have additional info
bool needs16BitPrefix; // does the opcode need the 'force to 16 bit' opcode?
raType type; // is it a move
// eventually will be in DEBUG_LOG build only
char* text; // string for fake disassembly
};
// note: store dest, source
// note: copy
x86RAOpcodeInfo raInfo[] =
{
{ 0x01, false, kArith, "add " }, //raAdd
{ 0x11, false, kArith, "adc " }, //raAdc
{ 0x29, false, kArith, "sub " }, //raSub
{ 0x19, false, kArith, "sbb " }, //raSbb
{ 0x21, false, kArith, "and " }, //raAnd
{ 0x09, false, kArith, "or " }, //raOr
{ 0x31, false, kArith, "xor " }, //raXor
{ 0x39, false, kArith, "cmp " }, //raCmp
{ 0x89, false, kLoad, "mov(ld) " }, //raLoadI
{ 0x89, false, kCopy, "mov(cp) " }, //raCopyI
{ 0x89, false, kStore, "mov(st) " }, //raStoreI
{ 0x89, false, kStore, "mov(sv) " }, //raSaveReg
{ 0x88, false, kStore, "movB "}, //raStoreB
{ 0x89, true, kStore, "movH "}, //raStoreH
};
InsnDoubleOpDir::
InsnDoubleOpDir(DataNode* inPrimitive, Pool& inPool, x86DoubleOpDirCode inCodeType,
x86ArgumentType inArgType1, x86ArgumentType inArgType2,
Uint8 uses, Uint8 defines ) :
x86ArgListInstruction (inPrimitive, inPool, uses, defines ),
codeType(inCodeType)
{
iArgumentList = new x86DoubleArgumentList(inArgType1, inArgType2);
DEBUG_ONLY(debugType = kInsnDoubleOpDir);
// temp for asserts
getDirectionBit();
}
InsnDoubleOpDir::
InsnDoubleOpDir(DataNode* inPrimitive, Pool& inPool, x86DoubleOpDirCode inCodeType,
Uint32 inDisplacement, x86ArgumentType inArgType1, x86ArgumentType inArgType2,
Uint8 uses, Uint8 defines) :
x86ArgListInstruction (inPrimitive, inPool, uses, defines ),
codeType(inCodeType)
{
iArgumentList = new x86DoubleArgumentList(inArgType1, inArgType2, inDisplacement);
DEBUG_ONLY(debugType = kInsnDoubleOpDir);
// temp for asserts
getDirectionBit();
}
// copy flag should only be set if insn is a copy and the use and define are registerDirect -- no other flags can be set
InstructionFlags InsnDoubleOpDir::
getFlags() const
{
return (codeType == raCopyI && iArgumentList->alIsRegisterDirect()) ? ifCopy : ifNone;
}
bool InsnDoubleOpDir::
getDirectionBit()
{
x86DoubleArgumentList* arglist = (x86DoubleArgumentList*)iArgumentList;
bool arg1isDirect = arglist->akIsArg1Direct();
bool arg2isDirect = arglist->akIsArg2Direct();
assert(arg1isDirect || arg2isDirect); // at least one has to be register direct
bool dirBit;
switch(codeType)
{
case raSaveReg:
// Save Instructions
// saves use (first arg) to stack slot (define)
// mov r1 -> M => mov r1 -> M dir = false
assert(arg1isDirect);
assert(!arg2isDirect);
dirBit = false;
break;
case raCopyI:
// Copy Instructions
/* OLD
// copies use (first arg) to define (second arg)
// mov r1 -> r2 => mov r1 -> r2 dir = false
// mov r1 -> M => mov r1 -> M dir = false
// mov M -> r2 => mov r2 <- M dir = true
// ie direction bit is clear iff argument 1 is registerDirect
dirBit = !arg1isDirect;
break;
*/
// copies use (first arg) to define (second arg)
// mov r1 -> r2 => mov r2 <- r1 dir = true
// mov r1 -> M => mov r1 -> M dir = false
// mov M -> r2 => mov r2 <- M dir = true
// ie direction bit is clear iff argument 1 is registerDirect
dirBit = arg2isDirect;
break;
case raStoreB:
case raStoreH:
case raStoreI:
// Store Instruction
// stores second use into first use memory
// mov M <- r2 => mov r2 -> M dir = false
dirBit = false;
assert(!arg1isDirect);
assert(arg2isDirect);
break;
case raLoadI:
// Load Instruction
// loads from memory address (use, first arg) into register (define, second arg)
// mov M -> r1 => mov r1 <- M dir = true
dirBit = true;
assert(!arg1isDirect);
assert(arg2isDirect);
break;
default:
// Arithmetic Instructions
// add r1, r2 => add r1, r2 dir = true
// add r1, M => add r1, M dir = true
// add M, r2 => add r2, M dir = false
// ie direction bit is set iff argument 1 is registerDirect
assert(raInfo[codeType].type == kArith);
dirBit = arg1isDirect;
}
return dirBit;
}
void InsnDoubleOpDir::
formatToMemory(void* inStart, Uint32 inOffset, MdFormatter& inFormatter)
{
uint8* start = (uint8*) inStart;
assert(iArgumentList);
// Format the opcode to memory
if(raInfo[codeType].needs16BitPrefix)
*start++ = kPrefix_For_16_Bits;
// calculate opcode
Uint8 direction = getDirectionBit() ? 2 : 0;
Uint8 opcode = raInfo[codeType].opcode | direction;
*start = opcode;
// Find the location of the argumnet list and format it to memory
Uint8* argLocation = (Uint8*)inStart + opcodeSize();
iArgumentList->alFormatToMemory((void*)argLocation , inOffset, *this, inFormatter);
}
Uint8 InsnDoubleOpDir::
opcodeSize()
{
return (raInfo[codeType].needs16BitPrefix) ? 2 : 1;
}
#ifdef DEBUG_LOG
void InsnDoubleOpDir::
printOpcode(LogModuleObject &f)
{
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("%-8s ", raInfo[codeType].text));
}
#endif
//================================================================================