in src/coreclr/jit/gcencode.cpp [2106:3537]
size_t GCInfo::gcMakeRegPtrTable(BYTE* dest, int mask, const InfoHdr& header, unsigned codeSize, size_t* pArgTabOffset)
{
unsigned varNum;
LclVarDsc* varDsc;
size_t totalSize = 0;
unsigned lastOffset;
/* The mask should be all 0's or all 1's */
assert(mask == 0 || mask == -1);
/* Start computing the total size of the table */
bool emitArgTabOffset = (header.varPtrTableSize != 0 || header.untrackedCnt > SET_UNTRACKED_MAX);
if (mask != 0 && emitArgTabOffset)
{
assert(*pArgTabOffset <= MAX_UNSIGNED_SIZE_T);
unsigned sz = encodeUnsigned(dest, static_cast<unsigned>(*pArgTabOffset));
dest += sz;
totalSize += sz;
}
#if VERIFY_GC_TABLES
if (mask)
{
*(short*)dest = (short)0xBEEF;
dest += sizeof(short);
}
totalSize += sizeof(short);
#endif
/**************************************************************************
*
* Untracked ptr variables
*
**************************************************************************
*/
#if DEBUG
unsigned untrackedCount = 0;
unsigned varPtrTableSize = 0;
gcCountForHeader(&untrackedCount, &varPtrTableSize);
assert(untrackedCount == header.untrackedCnt);
assert(varPtrTableSize == header.varPtrTableSize);
#endif // DEBUG
if (header.untrackedCnt != 0)
{
// Write the table of untracked pointer variables.
int lastoffset = 0;
for (varNum = 0, varDsc = compiler->lvaTable; varNum < compiler->lvaCount; varNum++, varDsc++)
{
if (compiler->lvaIsFieldOfDependentlyPromotedStruct(varDsc))
{
// Field local of a PROMOTION_TYPE_DEPENDENT struct must have been
// reported through its parent local
continue;
}
if (varTypeIsGC(varDsc->TypeGet()))
{
if (!gcIsUntrackedLocalOrNonEnregisteredArg(varNum))
{
continue;
}
int offset = varDsc->GetStackOffset();
#if DOUBLE_ALIGN
// For genDoubleAlign(), locals are addressed relative to ESP and
// arguments are addressed relative to EBP.
if (compiler->genDoubleAlign() && varDsc->lvIsParam && !varDsc->lvIsRegArg)
offset += compiler->codeGen->genTotalFrameSize();
#endif
// The lower bits of the offset encode properties of the stk ptr
assert(~OFFSET_MASK % sizeof(offset) == 0);
if (varDsc->TypeGet() == TYP_BYREF)
{
// Or in byref_OFFSET_FLAG for 'byref' pointer tracking
offset |= byref_OFFSET_FLAG;
}
if (varDsc->lvPinned)
{
// Or in pinned_OFFSET_FLAG for 'pinned' pointer tracking
offset |= pinned_OFFSET_FLAG;
}
int encodedoffset = lastoffset - offset;
lastoffset = offset;
if (mask == 0)
totalSize += encodeSigned(NULL, encodedoffset);
else
{
unsigned sz = encodeSigned(dest, encodedoffset);
dest += sz;
totalSize += sz;
}
}
else if ((varDsc->TypeGet() == TYP_STRUCT) && varDsc->lvOnFrame && varDsc->HasGCPtr())
{
ClassLayout* layout = varDsc->GetLayout();
unsigned slots = layout->GetSlotCount();
for (unsigned i = 0; i < slots; i++)
{
if (!layout->IsGCPtr(i))
{
continue;
}
unsigned offset = varDsc->GetStackOffset() + i * TARGET_POINTER_SIZE;
#if DOUBLE_ALIGN
// For genDoubleAlign(), locals are addressed relative to ESP and
// arguments are addressed relative to EBP.
if (compiler->genDoubleAlign() && varDsc->lvIsParam && !varDsc->lvIsRegArg)
{
offset += compiler->codeGen->genTotalFrameSize();
}
#endif
if (layout->GetGCPtrType(i) == TYP_BYREF)
{
offset |= byref_OFFSET_FLAG; // indicate it is a byref GC pointer
}
int encodedoffset = lastoffset - offset;
lastoffset = offset;
if (mask == 0)
{
totalSize += encodeSigned(NULL, encodedoffset);
}
else
{
unsigned sz = encodeSigned(dest, encodedoffset);
dest += sz;
totalSize += sz;
}
}
}
}
/* Count&Write spill temps that hold pointers */
assert(compiler->codeGen->regSet.tmpAllFree());
for (TempDsc* tempItem = compiler->codeGen->regSet.tmpListBeg(); tempItem != nullptr;
tempItem = compiler->codeGen->regSet.tmpListNxt(tempItem))
{
if (varTypeIsGC(tempItem->tdTempType()))
{
{
int offset;
offset = tempItem->tdTempOffs();
if (tempItem->tdTempType() == TYP_BYREF)
{
offset |= byref_OFFSET_FLAG;
}
int encodedoffset = lastoffset - offset;
lastoffset = offset;
if (mask == 0)
{
totalSize += encodeSigned(NULL, encodedoffset);
}
else
{
unsigned sz = encodeSigned(dest, encodedoffset);
dest += sz;
totalSize += sz;
}
}
}
}
}
#if VERIFY_GC_TABLES
if (mask)
{
*(short*)dest = (short)0xCAFE;
dest += sizeof(short);
}
totalSize += sizeof(short);
#endif
/**************************************************************************
*
* Generate the table of stack pointer variable lifetimes.
*
**************************************************************************
*/
bool keepThisAlive = false;
if (!compiler->info.compIsStatic)
{
unsigned thisArgNum = compiler->info.compThisArg;
gcIsUntrackedLocalOrNonEnregisteredArg(thisArgNum, &keepThisAlive);
}
// First we check for the most common case - no lifetimes at all.
if (header.varPtrTableSize != 0)
{
#if defined(FEATURE_EH_WINDOWS_X86)
if (!compiler->UsesFunclets() && keepThisAlive)
{
// Encoding of untracked variables does not support reporting
// "this". So report it as a tracked variable with a liveness
// extending over the entire method.
assert(compiler->lvaTable[compiler->info.compThisArg].TypeGet() == TYP_REF);
unsigned varOffs = compiler->lvaTable[compiler->info.compThisArg].GetStackOffset();
/* For negative stack offsets we must reset the low bits,
* take abs and then set them back */
varOffs = abs(static_cast<int>(varOffs));
varOffs |= this_OFFSET_FLAG;
size_t sz = 0;
sz = encodeUnsigned(mask ? (dest + sz) : NULL, varOffs);
sz += encodeUDelta(mask ? (dest + sz) : NULL, 0, 0);
sz += encodeUDelta(mask ? (dest + sz) : NULL, codeSize, 0);
dest += (sz & mask);
totalSize += sz;
}
#endif // FEATURE_EH_WINDOWS_X86
/* We'll use a delta encoding for the lifetime offsets */
lastOffset = 0;
for (varPtrDsc* varTmp = gcVarPtrList; varTmp; varTmp = varTmp->vpdNext)
{
unsigned varOffs;
unsigned lowBits;
unsigned begOffs;
unsigned endOffs;
assert(~OFFSET_MASK % TARGET_POINTER_SIZE == 0);
/* Get hold of the variable's stack offset */
lowBits = varTmp->vpdVarNum & OFFSET_MASK;
/* For negative stack offsets we must reset the low bits,
* take abs and then set them back */
varOffs = abs(static_cast<int>(varTmp->vpdVarNum & ~OFFSET_MASK));
varOffs |= lowBits;
/* Compute the actual lifetime offsets */
begOffs = varTmp->vpdBegOfs;
endOffs = varTmp->vpdEndOfs;
/* Special case: skip any 0-length lifetimes */
if (endOffs == begOffs)
continue;
/* Are we counting or generating? */
size_t sz = 0;
sz = encodeUnsigned(mask ? (dest + sz) : NULL, varOffs);
sz += encodeUDelta(mask ? (dest + sz) : NULL, begOffs, lastOffset);
sz += encodeUDelta(mask ? (dest + sz) : NULL, endOffs, begOffs);
dest += (sz & mask);
totalSize += sz;
/* The next entry will be relative to the one we just processed */
lastOffset = begOffs;
}
}
if (pArgTabOffset != NULL)
*pArgTabOffset = totalSize;
#if VERIFY_GC_TABLES
if (mask)
{
*(short*)dest = (short)0xBABE;
dest += sizeof(short);
}
totalSize += sizeof(short);
#endif
if (!mask && emitArgTabOffset)
{
assert(*pArgTabOffset <= MAX_UNSIGNED_SIZE_T);
totalSize += encodeUnsigned(NULL, static_cast<unsigned>(*pArgTabOffset));
}
/**************************************************************************
*
* Prepare to generate the pointer register/argument map
*
**************************************************************************
*/
lastOffset = 0;
if (compiler->codeGen->GetInterruptible())
{
#ifdef TARGET_X86
assert(compiler->IsFullPtrRegMapRequired());
unsigned ptrRegs = 0;
regPtrDsc* genRegPtrTemp;
/* Walk the list of pointer register/argument entries */
for (genRegPtrTemp = gcRegPtrList; genRegPtrTemp; genRegPtrTemp = genRegPtrTemp->rpdNext)
{
BYTE* base = dest;
unsigned nextOffset;
DWORD codeDelta;
nextOffset = genRegPtrTemp->rpdOffs;
/*
Encoding table for methods that are fully interruptible
The encoding used is as follows:
ptr reg dead 00RRRDDD [RRR != 100]
ptr reg live 01RRRDDD [RRR != 100]
non-ptr arg push 10110DDD [SSS == 110]
ptr arg push 10SSSDDD [SSS != 110] && [SSS != 111]
ptr arg pop 11CCCDDD [CCC != 000] && [CCC != 110] && [CCC != 111]
little skip 11000DDD [CCC == 000]
bigger skip 11110BBB [CCC == 110]
The values used in the above encodings are as follows:
DDD code offset delta from previous entry (0-7)
BBB bigger delta 000=8,001=16,010=24,...,111=64
RRR register number (EAX=000,ECX=001,EDX=010,EBX=011,
EBP=101,ESI=110,EDI=111), ESP=100 is reserved
SSS argument offset from base of stack. This is
redundant for frameless methods as we can
infer it from the previous pushes+pops. However,
for EBP-methods, we only report GC pushes, and
so we need SSS
CCC argument count being popped (includes only ptrs for EBP methods)
The following are the 'large' versions:
large delta skip 10111000 [0xB8] , encodeUnsigned(delta)
large ptr arg push 11111000 [0xF8] , encodeUnsigned(pushCount)
large non-ptr arg push 11111001 [0xF9] , encodeUnsigned(pushCount)
large ptr arg pop 11111100 [0xFC] , encodeUnsigned(popCount)
large arg dead 11111101 [0xFD] , encodeUnsigned(popCount) for caller-pop args.
Any GC args go dead after the call,
but are still sitting on the stack
this pointer prefix 10111100 [0xBC] the next encoding is a ptr live
or a ptr arg push
and contains the this pointer
interior or by-ref 10111111 [0xBF] the next encoding is a ptr live
pointer prefix or a ptr arg push
and contains an interior
or by-ref pointer
The value 11111111 [0xFF] indicates the end of the table.
*/
codeDelta = nextOffset - lastOffset;
assert((int)codeDelta >= 0);
// If the code delta is between 8 and (64+7),
// generate a 'bigger delta' encoding
if ((codeDelta >= 8) && (codeDelta <= (64 + 7)))
{
unsigned biggerDelta = ((codeDelta - 8) & 0x38) + 8;
*dest++ = (BYTE)(0xF0 | ((biggerDelta - 8) >> 3));
lastOffset += biggerDelta;
codeDelta &= 0x07;
}
// If the code delta is still bigger than 7,
// generate a 'large code delta' encoding
if (codeDelta > 7)
{
*dest++ = 0xB8;
dest += encodeUnsigned(dest, codeDelta);
codeDelta = 0;
/* Remember the new 'last' offset */
lastOffset = nextOffset;
}
/* Is this a pointer argument or register entry? */
if (genRegPtrTemp->rpdArg)
{
if (genRegPtrTemp->rpdArgTypeGet() == rpdARG_KILL)
{
if (codeDelta)
{
/*
Use the small encoding:
little delta skip 11000DDD [0xC0]
*/
assert((codeDelta & 0x7) == codeDelta);
*dest++ = 0xC0 | (BYTE)codeDelta;
/* Remember the new 'last' offset */
lastOffset = nextOffset;
}
/* Caller-pop arguments are dead after call but are still
sitting on the stack */
*dest++ = 0xFD;
assert(genRegPtrTemp->rpdPtrArg != 0);
dest += encodeUnsigned(dest, genRegPtrTemp->rpdPtrArg);
}
else if (genRegPtrTemp->rpdPtrArg < 6 && genRegPtrTemp->rpdGCtypeGet())
{
/* Is the argument offset/count smaller than 6 ? */
dest = gceByrefPrefixI(genRegPtrTemp, dest);
if (genRegPtrTemp->rpdArgTypeGet() == rpdARG_PUSH || (genRegPtrTemp->rpdPtrArg != 0))
{
/*
Use the small encoding:
ptr arg push 10SSSDDD [SSS != 110] && [SSS != 111]
ptr arg pop 11CCCDDD [CCC != 110] && [CCC != 111]
*/
bool isPop = genRegPtrTemp->rpdArgTypeGet() == rpdARG_POP;
*dest++ = (BYTE)(0x80 | (BYTE)codeDelta | genRegPtrTemp->rpdPtrArg << 3 | isPop << 6);
/* Remember the new 'last' offset */
lastOffset = nextOffset;
}
else
{
assert(!"Check this");
}
}
else if (genRegPtrTemp->rpdGCtypeGet() == GCT_NONE)
{
/*
Use the small encoding:
` non-ptr arg push 10110DDD [0xB0] (push of sizeof(int))
*/
assert((codeDelta & 0x7) == codeDelta);
*dest++ = 0xB0 | (BYTE)codeDelta;
assert(compiler->UsesFunclets() || !compiler->isFramePointerUsed());
/* Remember the new 'last' offset */
lastOffset = nextOffset;
}
else
{
/* Will have to use large encoding;
* first do the code delta
*/
if (codeDelta)
{
/*
Use the small encoding:
little delta skip 11000DDD [0xC0]
*/
assert((codeDelta & 0x7) == codeDelta);
*dest++ = 0xC0 | (BYTE)codeDelta;
}
/*
Now append a large argument record:
large ptr arg push 11111000 [0xF8]
large ptr arg pop 11111100 [0xFC]
*/
bool isPop = genRegPtrTemp->rpdArgTypeGet() == rpdARG_POP;
dest = gceByrefPrefixI(genRegPtrTemp, dest);
*dest++ = 0xF8 | (isPop << 2);
dest += encodeUnsigned(dest, genRegPtrTemp->rpdPtrArg);
/* Remember the new 'last' offset */
lastOffset = nextOffset;
}
}
else
{
unsigned regMask;
/* Record any registers that are becoming dead */
regMask = genRegPtrTemp->rpdCompiler.rpdDel & ptrRegs;
while (regMask) // EAX,ECX,EDX,EBX,---,EBP,ESI,EDI
{
unsigned tmpMask;
regNumber regNum;
/* Get hold of the next register bit */
tmpMask = genFindLowestBit(regMask);
assert(tmpMask);
/* Remember the new state of this register */
ptrRegs &= ~tmpMask;
/* Figure out which register the next bit corresponds to */
regNum = genRegNumFromMask(tmpMask);
assert(regNum <= 7);
/* Reserve ESP, regNum==4 for future use */
assert(regNum != 4);
/*
Generate a small encoding:
ptr reg dead 00RRRDDD
*/
assert((codeDelta & 0x7) == codeDelta);
*dest++ = (BYTE)(0x00 | regNum << 3 | (BYTE)codeDelta);
/* Turn the bit we've just generated off and continue */
regMask -= tmpMask; // EAX,ECX,EDX,EBX,---,EBP,ESI,EDI
/* Remember the new 'last' offset */
lastOffset = nextOffset;
/* Any entries that follow will be at the same offset */
codeDelta = zeroFunc(); /* DO NOT REMOVE */
}
/* Record any registers that are becoming live */
regMask = genRegPtrTemp->rpdCompiler.rpdAdd & ~ptrRegs;
while (regMask) // EAX,ECX,EDX,EBX,---,EBP,ESI,EDI
{
unsigned tmpMask;
regNumber regNum;
/* Get hold of the next register bit */
tmpMask = genFindLowestBit(regMask);
assert(tmpMask);
/* Remember the new state of this register */
ptrRegs |= tmpMask;
/* Figure out which register the next bit corresponds to */
regNum = genRegNumFromMask(tmpMask);
assert(regNum <= 7);
/*
Generate a small encoding:
ptr reg live 01RRRDDD
*/
dest = gceByrefPrefixI(genRegPtrTemp, dest);
if (!keepThisAlive && genRegPtrTemp->rpdIsThis)
{
// Mark with 'this' pointer prefix
*dest++ = 0xBC;
// Can only have one bit set in regMask
assert(regMask == tmpMask);
}
assert((codeDelta & 0x7) == codeDelta);
*dest++ = (BYTE)(0x40 | (regNum << 3) | (BYTE)codeDelta);
/* Turn the bit we've just generated off and continue */
regMask -= tmpMask; // EAX,ECX,EDX,EBX,---,EBP,ESI,EDI
/* Remember the new 'last' offset */
lastOffset = nextOffset;
/* Any entries that follow will be at the same offset */
codeDelta = zeroFunc(); /* DO NOT REMOVE */
}
}
/* Keep track of the total amount of generated stuff */
totalSize += dest - base;
/* Go back to the buffer start if we're not generating a table */
if (!mask)
dest = base;
}
#endif // TARGET_X86
/* Terminate the table with 0xFF */
*dest = 0xFF;
dest -= mask;
totalSize++;
}
else if (compiler->isFramePointerUsed()) // GetInterruptible() is false
{
#ifdef TARGET_X86
/*
Encoding table for methods with an EBP frame and
that are not fully interruptible
The encoding used is as follows:
this pointer encodings:
01000000 this pointer in EBX
00100000 this pointer in ESI
00010000 this pointer in EDI
tiny encoding:
0bsdDDDD
requires code delta > 0 & delta < 16 (4-bits)
requires pushed argmask == 0
where DDDD is code delta
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
small encoding:
1DDDDDDD bsdAAAAA
requires code delta < 120 (7-bits)
requires pushed argmask < 64 (5-bits)
where DDDDDDD is code delta
AAAAA is the pushed args mask
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
medium encoding
0xFD aaaaaaaa AAAAdddd bseDDDDD
requires code delta < 512 (9-bits)
requires pushed argmask < 2048 (12-bits)
where DDDDD is the upper 5-bits of the code delta
dddd is the low 4-bits of the code delta
AAAA is the upper 4-bits of the pushed arg mask
aaaaaaaa is the low 8-bits of the pushed arg mask
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
e indicates that register EDI is a live pointer
medium encoding with interior pointers
0xF9 DDDDDDDD bsdAAAAAA iiiIIIII
requires code delta < 256 (8-bits)
requires pushed argmask < 64 (5-bits)
where DDDDDDD is the code delta
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
AAAAA is the pushed arg mask
iii indicates that EBX,EDI,ESI are interior pointers
IIIII indicates that bits in the arg mask are interior
pointers
large encoding
0xFE [0BSD0bsd][32-bit code delta][32-bit argMask]
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
B indicates that register EBX is an interior pointer
S indicates that register ESI is an interior pointer
D indicates that register EDI is an interior pointer
requires pushed argmask < 32-bits
large encoding with interior pointers
0xFA [0BSD0bsd][32-bit code delta][32-bit argMask][32-bit interior pointer mask]
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
B indicates that register EBX is an interior pointer
S indicates that register ESI is an interior pointer
D indicates that register EDI is an interior pointer
requires pushed argmask < 32-bits
requires pushed iArgmask < 32-bits
huge encoding This is the only encoding that supports
a pushed argmask which is greater than
32-bits.
0xFB [0BSD0bsd][32-bit code delta]
[32-bit table count][32-bit table size]
[pushed ptr offsets table...]
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
B indicates that register EBX is an interior pointer
S indicates that register ESI is an interior pointer
D indicates that register EDI is an interior pointer
the list count is the number of entries in the list
the list size gives the byte-length of the list
the offsets in the list are variable-length
*/
/* If "this" is enregistered, note it. We do this explicitly here as
IsFullPtrRegMapRequired()==false, and so we don't have any regPtrDsc's. */
if (compiler->lvaKeepAliveAndReportThis() && compiler->lvaTable[compiler->info.compThisArg].lvRegister)
{
unsigned thisRegMask = (unsigned)genRegMask(compiler->lvaTable[compiler->info.compThisArg].GetRegNum());
unsigned thisPtrRegEnc = gceEncodeCalleeSavedRegs(thisRegMask) << 4;
if (thisPtrRegEnc)
{
totalSize += 1;
if (mask)
*dest++ = (BYTE)thisPtrRegEnc;
}
}
CallDsc* call;
assert(compiler->IsFullPtrRegMapRequired() == false);
/* Walk the list of pointer register/argument entries */
for (call = gcCallDescList; call; call = call->cdNext)
{
BYTE* base = dest;
unsigned nextOffset;
/* Figure out the code offset of this entry */
nextOffset = call->cdOffs;
/* Compute the distance from the previous call */
DWORD codeDelta = nextOffset - lastOffset;
assert((int)codeDelta >= 0);
/* Remember the new 'last' offset */
lastOffset = nextOffset;
/* Compute the register mask */
unsigned gcrefRegMask = 0;
unsigned byrefRegMask = 0;
gcrefRegMask |= gceEncodeCalleeSavedRegs(call->cdGCrefRegs);
byrefRegMask |= gceEncodeCalleeSavedRegs(call->cdByrefRegs);
assert((gcrefRegMask & byrefRegMask) == 0);
unsigned regMask = gcrefRegMask | byrefRegMask;
bool byref = (byrefRegMask | call->u1.cdByrefArgMask) != 0;
/* Check for the really large argument offset case */
/* The very rare Huge encodings */
if (call->cdArgCnt)
{
unsigned argNum;
DWORD argCnt = call->cdArgCnt;
DWORD argBytes = 0;
BYTE* pArgBytes = DUMMY_INIT(NULL);
if (mask != 0)
{
*dest++ = 0xFB;
*dest++ = (BYTE)((byrefRegMask << 4) | regMask);
*(DWORD*)dest = codeDelta;
dest += sizeof(DWORD);
*(DWORD*)dest = argCnt;
dest += sizeof(DWORD);
// skip the byte-size for now. Just note where it will go
pArgBytes = dest;
dest += sizeof(DWORD);
}
for (argNum = 0; argNum < argCnt; argNum++)
{
unsigned eltSize;
eltSize = encodeUnsigned(dest, call->cdArgTable[argNum]);
argBytes += eltSize;
if (mask)
dest += eltSize;
}
if (mask == 0)
{
dest = base + 1 + 1 + 3 * sizeof(DWORD) + argBytes;
}
else
{
assert(dest == pArgBytes + sizeof(argBytes) + argBytes);
*(DWORD*)pArgBytes = argBytes;
}
}
/* Check if we can use a tiny encoding */
else if ((codeDelta < 16) && (codeDelta != 0) && (call->u1.cdArgMask == 0) && !byref)
{
*dest++ = (BYTE)((regMask << 4) | (BYTE)codeDelta);
}
/* Check if we can use the small encoding */
else if ((codeDelta < 0x79) && (call->u1.cdArgMask <= 0x1F) && !byref)
{
*dest++ = 0x80 | (BYTE)codeDelta;
*dest++ = (BYTE)(call->u1.cdArgMask | (regMask << 5));
}
/* Check if we can use the medium encoding */
else if (codeDelta <= 0x01FF && call->u1.cdArgMask <= 0x0FFF && !byref)
{
*dest++ = 0xFD;
*dest++ = (BYTE)call->u1.cdArgMask;
*dest++ = ((call->u1.cdArgMask >> 4) & 0xF0) | ((BYTE)codeDelta & 0x0F);
*dest++ = (BYTE)(regMask << 5) | (BYTE)((codeDelta >> 4) & 0x1F);
}
/* Check if we can use the medium encoding with byrefs */
else if (codeDelta <= 0x0FF && call->u1.cdArgMask <= 0x01F)
{
*dest++ = 0xF9;
*dest++ = (BYTE)codeDelta;
*dest++ = (BYTE)((regMask << 5) | call->u1.cdArgMask);
*dest++ = (BYTE)((byrefRegMask << 5) | call->u1.cdByrefArgMask);
}
/* We'll use the large encoding */
else if (!byref)
{
*dest++ = 0xFE;
*dest++ = (BYTE)((byrefRegMask << 4) | regMask);
*(DWORD*)dest = codeDelta;
dest += sizeof(DWORD);
*(DWORD*)dest = call->u1.cdArgMask;
dest += sizeof(DWORD);
}
/* We'll use the large encoding with byrefs */
else
{
*dest++ = 0xFA;
*dest++ = (BYTE)((byrefRegMask << 4) | regMask);
*(DWORD*)dest = codeDelta;
dest += sizeof(DWORD);
*(DWORD*)dest = call->u1.cdArgMask;
dest += sizeof(DWORD);
*(DWORD*)dest = call->u1.cdByrefArgMask;
dest += sizeof(DWORD);
}
/* Keep track of the total amount of generated stuff */
totalSize += dest - base;
/* Go back to the buffer start if we're not generating a table */
if (!mask)
dest = base;
}
#endif // TARGET_X86
/* Terminate the table with 0xFF */
*dest = 0xFF;
dest -= mask;
totalSize++;
}
else // GetInterruptible() is false and we have an EBP-less frame
{
assert(compiler->IsFullPtrRegMapRequired());
#ifdef TARGET_X86
regPtrDsc* genRegPtrTemp;
regNumber thisRegNum = regNumber(0);
PendingArgsStack pasStk(compiler->GetEmitter()->emitMaxStackDepth, compiler);
/* Walk the list of pointer register/argument entries */
for (genRegPtrTemp = gcRegPtrList; genRegPtrTemp; genRegPtrTemp = genRegPtrTemp->rpdNext)
{
/*
* Encoding table for methods without an EBP frame and
* that are not fully interruptible
*
* The encoding used is as follows:
*
* push 000DDDDD ESP push one item with 5-bit delta
* push 00100000 [pushCount] ESP push multiple items
* reserved 0010xxxx xxxx != 0000
* reserved 0011xxxx
* skip 01000000 [Delta] Skip Delta, arbitrary sized delta
* skip 0100DDDD Skip small Delta, for call (DDDD != 0)
* pop 01CCDDDD ESP pop CC items with 4-bit delta (CC != 00)
* call 1PPPPPPP Call Pattern, P=[0..79]
* call 1101pbsd DDCCCMMM Call RegMask=pbsd,ArgCnt=CCC,
* ArgMask=MMM Delta=commonDelta[DD]
* call 1110pbsd [ArgCnt] [ArgMask] Call ArgCnt,RegMask=pbsd,ArgMask
* call 11111000 [PBSDpbsd][32-bit delta][32-bit ArgCnt]
* [32-bit PndCnt][32-bit PndSize][PndOffs...]
* iptr 11110000 [IPtrMask] Arbitrary Interior Pointer Mask
* thisptr 111101RR This pointer is in Register RR
* 00=EDI,01=ESI,10=EBX,11=EBP
* reserved 111100xx xx != 00
* reserved 111110xx xx != 00
* reserved 11111xxx xxx != 000 && xxx != 111(EOT)
*
* The value 11111111 [0xFF] indicates the end of the table. (EOT)
*
* An offset (at which stack-walking is performed) without an explicit encoding
* is assumed to be a trivial call-site (no GC registers, stack empty before and
* after) to avoid having to encode all trivial calls.
*
* Note on the encoding used for interior pointers
*
* The iptr encoding must immediately precede a call encoding. It is used
* to transform a normal GC pointer addresses into an interior pointers for
* GC purposes. The mask supplied to the iptr encoding is read from the
* least signicant bit to the most signicant bit. (i.e the lowest bit is
* read first)
*
* p indicates that register EBP is a live pointer
* b indicates that register EBX is a live pointer
* s indicates that register ESI is a live pointer
* d indicates that register EDI is a live pointer
* P indicates that register EBP is an interior pointer
* B indicates that register EBX is an interior pointer
* S indicates that register ESI is an interior pointer
* D indicates that register EDI is an interior pointer
*
* As an example the following sequence indicates that EDI.ESI and the
* second pushed pointer in ArgMask are really interior pointers. The
* pointer in ESI in a normal pointer:
*
* iptr 11110000 00010011 => read Interior Ptr, Interior Ptr,
* Normal Ptr, Normal Ptr, Interior Ptr
*
* call 11010011 DDCCC011 RRRR=1011 => read EDI is a GC-pointer,
* ESI is a GC-pointer.
* EBP is a GC-pointer
* MMM=0011 => read two GC-pointers arguments
* on the stack (nested call)
*
* Since the call instruction mentions 5 GC-pointers we list them in
* the required order: EDI, ESI, EBP, 1st-pushed pointer, 2nd-pushed pointer
*
* And we apply the Interior Pointer mask mmmm=10011 to the five GC-pointers
* we learn that EDI and ESI are interior GC-pointers and that
* the second push arg is an interior GC-pointer.
*/
BYTE* base = dest;
bool usePopEncoding;
unsigned regMask;
unsigned argMask;
unsigned byrefRegMask;
unsigned byrefArgMask;
DWORD callArgCnt;
unsigned nextOffset;
DWORD codeDelta;
nextOffset = genRegPtrTemp->rpdOffs;
/* Compute the distance from the previous call */
codeDelta = nextOffset - lastOffset;
assert((int)codeDelta >= 0);
#if REGEN_CALLPAT
// Must initialize this flag to true when REGEN_CALLPAT is on
usePopEncoding = true;
unsigned origCodeDelta = codeDelta;
#endif
if (!keepThisAlive && genRegPtrTemp->rpdIsThis)
{
unsigned tmpMask = genRegPtrTemp->rpdCompiler.rpdAdd;
/* tmpMask must have exactly one bit set */
assert(tmpMask && ((tmpMask & (tmpMask - 1)) == 0));
thisRegNum = genRegNumFromMask(tmpMask);
switch (thisRegNum)
{
case 0: // EAX
case 1: // ECX
case 2: // EDX
case 4: // ESP
break;
case 7: // EDI
*dest++ = 0xF4; /* 11110100 This pointer is in EDI */
break;
case 6: // ESI
*dest++ = 0xF5; /* 11110100 This pointer is in ESI */
break;
case 3: // EBX
*dest++ = 0xF6; /* 11110100 This pointer is in EBX */
break;
case 5: // EBP
*dest++ = 0xF7; /* 11110100 This pointer is in EBP */
break;
default:
break;
}
}
/* Is this a stack pointer change or call? */
if (genRegPtrTemp->rpdArg)
{
if (genRegPtrTemp->rpdArgTypeGet() == rpdARG_KILL)
{
// kill 'rpdPtrArg' number of pointer variables in pasStk
pasStk.pasKill(genRegPtrTemp->rpdPtrArg);
}
/* Is this a call site? */
else if (genRegPtrTemp->rpdCall)
{
/* This is a true call site */
/* Remember the new 'last' offset */
lastOffset = nextOffset;
callArgCnt = genRegPtrTemp->rpdPtrArg;
unsigned gcrefRegMask = genRegPtrTemp->rpdCallGCrefRegs;
byrefRegMask = genRegPtrTemp->rpdCallByrefRegs;
assert((gcrefRegMask & byrefRegMask) == 0);
regMask = gcrefRegMask | byrefRegMask;
/* adjust argMask for this call-site */
pasStk.pasPop(callArgCnt);
/* Do we have to use the fat encoding */
if (pasStk.pasCurDepth() > BITS_IN_pasMask && pasStk.pasHasGCptrs())
{
/* use fat encoding:
* 11111000 [PBSDpbsd][32-bit delta][32-bit ArgCnt]
* [32-bit PndCnt][32-bit PndSize][PndOffs...]
*/
DWORD pndCount = pasStk.pasEnumGCoffsCount();
DWORD pndSize = 0;
BYTE* pPndSize = DUMMY_INIT(NULL);
if (mask)
{
*dest++ = 0xF8;
*dest++ = (BYTE)((byrefRegMask << 4) | regMask);
*(DWORD*)dest = codeDelta;
dest += sizeof(DWORD);
*(DWORD*)dest = callArgCnt;
dest += sizeof(DWORD);
*(DWORD*)dest = pndCount;
dest += sizeof(DWORD);
pPndSize = dest;
dest += sizeof(DWORD); // Leave space for pndSize
}
unsigned offs, iter;
for (iter = pasStk.pasEnumGCoffs(pasENUM_START, &offs); pndCount;
iter = pasStk.pasEnumGCoffs(iter, &offs), pndCount--)
{
unsigned eltSize = encodeUnsigned(dest, offs);
pndSize += eltSize;
if (mask)
dest += eltSize;
}
assert(iter == pasENUM_END);
if (mask == 0)
{
dest = base + 2 + 4 * sizeof(DWORD) + pndSize;
}
else
{
assert(pPndSize + sizeof(pndSize) + pndSize == dest);
*(DWORD*)pPndSize = pndSize;
}
goto NEXT_RPD;
}
argMask = byrefArgMask = 0;
if (pasStk.pasHasGCptrs())
{
assert(pasStk.pasCurDepth() <= BITS_IN_pasMask);
argMask = pasStk.pasArgMask();
byrefArgMask = pasStk.pasByrefArgMask();
}
/* Shouldn't be reporting trivial call-sites */
assert(regMask || argMask || callArgCnt || pasStk.pasCurDepth());
// Emit IPtrMask if needed
#define CHK_NON_INTRPT_ESP_IPtrMask \
\
if (byrefRegMask || byrefArgMask) \
{ \
*dest++ = 0xF0; \
unsigned imask = (byrefArgMask << 4) | byrefRegMask; \
dest += encodeUnsigned(dest, imask); \
}
/* When usePopEncoding is true:
* this is not an interesting call site
* because nothing is live here.
*/
usePopEncoding = ((callArgCnt < 4) && (regMask == 0) && (argMask == 0));
if (!usePopEncoding)
{
int pattern = lookupCallPattern(callArgCnt, regMask, argMask, codeDelta);
if (pattern != -1)
{
if (pattern > 0xff)
{
codeDelta = pattern >> 8;
pattern &= 0xff;
if (codeDelta >= 16)
{
/* use encoding: */
/* skip 01000000 [Delta] */
*dest++ = 0x40;
dest += encodeUnsigned(dest, codeDelta);
codeDelta = 0;
}
else
{
/* use encoding: */
/* skip 0100DDDD small delta=DDDD */
*dest++ = 0x40 | (BYTE)codeDelta;
}
}
// Emit IPtrMask if needed
CHK_NON_INTRPT_ESP_IPtrMask;
assert((pattern >= 0) && (pattern < 80));
*dest++ = (BYTE)(0x80 | pattern);
goto NEXT_RPD;
}
/* See if we can use 2nd call encoding
* 1101RRRR DDCCCMMM encoding */
if ((callArgCnt <= 7) && (argMask <= 7))
{
unsigned inx; // callCommonDelta[] index
unsigned maxCommonDelta = callCommonDelta[3];
if (codeDelta > maxCommonDelta)
{
if (codeDelta > maxCommonDelta + 15)
{
/* use encoding: */
/* skip 01000000 [Delta] */
*dest++ = 0x40;
dest += encodeUnsigned(dest, codeDelta - maxCommonDelta);
}
else
{
/* use encoding: */
/* skip 0100DDDD small delta=DDDD */
*dest++ = 0x40 | (BYTE)(codeDelta - maxCommonDelta);
}
codeDelta = maxCommonDelta;
inx = 3;
goto EMIT_2ND_CALL_ENCODING;
}
for (inx = 0; inx < 4; inx++)
{
if (codeDelta == callCommonDelta[inx])
{
EMIT_2ND_CALL_ENCODING:
// Emit IPtrMask if needed
CHK_NON_INTRPT_ESP_IPtrMask;
*dest++ = (BYTE)(0xD0 | regMask);
*dest++ = (BYTE)((inx << 6) | (callArgCnt << 3) | argMask);
goto NEXT_RPD;
}
}
unsigned minCommonDelta = callCommonDelta[0];
if ((codeDelta > minCommonDelta) && (codeDelta < maxCommonDelta))
{
assert((minCommonDelta + 16) > maxCommonDelta);
/* use encoding: */
/* skip 0100DDDD small delta=DDDD */
*dest++ = 0x40 | (BYTE)(codeDelta - minCommonDelta);
codeDelta = minCommonDelta;
inx = 0;
goto EMIT_2ND_CALL_ENCODING;
}
}
}
if (codeDelta >= 16)
{
unsigned i = (usePopEncoding ? 15 : 0);
/* use encoding: */
/* skip 01000000 [Delta] arbitrary sized delta */
*dest++ = 0x40;
dest += encodeUnsigned(dest, codeDelta - i);
codeDelta = i;
}
if ((codeDelta > 0) || usePopEncoding)
{
if (usePopEncoding)
{
/* use encoding: */
/* pop 01CCDDDD ESP pop CC items, 4-bit delta */
if (callArgCnt || codeDelta)
*dest++ = (BYTE)(0x40 | (callArgCnt << 4) | codeDelta);
goto NEXT_RPD;
}
else
{
/* use encoding: */
/* skip 0100DDDD small delta=DDDD */
*dest++ = 0x40 | (BYTE)codeDelta;
}
}
// Emit IPtrMask if needed
CHK_NON_INTRPT_ESP_IPtrMask;
/* use encoding: */
/* call 1110RRRR [ArgCnt] [ArgMask] */
*dest++ = (BYTE)(0xE0 | regMask);
dest += encodeUnsigned(dest, callArgCnt);
dest += encodeUnsigned(dest, argMask);
}
else
{
/* This is a push or a pop site */
/* Remember the new 'last' offset */
lastOffset = nextOffset;
if (genRegPtrTemp->rpdArgTypeGet() == rpdARG_POP)
{
/* This must be a gcArgPopSingle */
assert(genRegPtrTemp->rpdPtrArg == 1);
if (codeDelta >= 16)
{
/* use encoding: */
/* skip 01000000 [Delta] */
*dest++ = 0x40;
dest += encodeUnsigned(dest, codeDelta - 15);
codeDelta = 15;
}
/* use encoding: */
/* pop1 0101DDDD ESP pop one item, 4-bit delta */
*dest++ = 0x50 | (BYTE)codeDelta;
/* adjust argMask for this pop */
pasStk.pasPop(1);
}
else
{
/* This is a push */
if (codeDelta >= 32)
{
/* use encoding: */
/* skip 01000000 [Delta] */
*dest++ = 0x40;
dest += encodeUnsigned(dest, codeDelta - 31);
codeDelta = 31;
}
assert(codeDelta < 32);
/* use encoding: */
/* push 000DDDDD ESP push one item, 5-bit delta */
*dest++ = (BYTE)codeDelta;
/* adjust argMask for this push */
pasStk.pasPush(genRegPtrTemp->rpdGCtypeGet());
}
}
}
/* We ignore the register live/dead information, since the
* rpdCallRegMask contains all the liveness information
* that we need
*/
NEXT_RPD:
totalSize += dest - base;
/* Go back to the buffer start if we're not generating a table */
if (!mask)
dest = base;
#if REGEN_CALLPAT
if ((mask == -1) && (usePopEncoding == false) && ((dest - base) > 0))
regenLog(origCodeDelta, argMask, regMask, callArgCnt, byrefArgMask, byrefRegMask, base, (dest - base));
#endif
}
/* Verify that we pop every arg that was pushed and that argMask is 0 */
assert(pasStk.pasCurDepth() == 0);
#endif // TARGET_X86
/* Terminate the table with 0xFF */
*dest = 0xFF;
dest -= mask;
totalSize++;
}
#if VERIFY_GC_TABLES
if (mask)
{
*(short*)dest = (short)0xBEEB;
dest += sizeof(short);
}
totalSize += sizeof(short);
#endif
#if MEASURE_PTRTAB_SIZE
if (mask)
s_gcTotalPtrTabSize += totalSize;
#endif
return totalSize;
}