diff options
Diffstat (limited to 'src/jit/gcencode.cpp')
-rw-r--r-- | src/jit/gcencode.cpp | 4600 |
1 files changed, 4600 insertions, 0 deletions
diff --git a/src/jit/gcencode.cpp b/src/jit/gcencode.cpp new file mode 100644 index 0000000000..2c9e643fc1 --- /dev/null +++ b/src/jit/gcencode.cpp @@ -0,0 +1,4600 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XX XX +XX GCEncode XX +XX XX +XX Logic to encode the JIT method header and GC pointer tables XX +XX XX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +*/ + +#include "jitpch.h" +#ifdef _MSC_VER +#pragma hdrstop + +#pragma warning(disable:4244) // loss of data int -> char .. + +#endif + +#include "gcinfo.h" + +#ifdef JIT32_GCENCODER + +#include "emit.h" + +/*****************************************************************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +// (see jit.h) #define REGEN_SHORTCUTS 0 +// To Regenerate the compressed info header shortcuts, define REGEN_SHORTCUTS +// and use the following command line pipe/filter to give you the 128 +// most useful encodings. +// +// find . -name regen.txt | xargs cat | grep InfoHdr | sort | uniq -c | sort -r | head -128 + +// (see jit.h) #define REGEN_CALLPAT 0 +// To Regenerate the compressed info header shortcuts, define REGEN_CALLPAT +// and use the following command line pipe/filter to give you the 80 +// most useful encodings. +// +// find . -name regen.txt | xargs cat | grep CallSite | sort | uniq -c | sort -r | head -80 + + +#if REGEN_SHORTCUTS || REGEN_CALLPAT +static FILE* logFile = NULL; +CRITICAL_SECTION logFileLock; +#endif + +#if REGEN_CALLPAT +static void regenLog(unsigned codeDelta, unsigned argMask, + unsigned regMask, unsigned argCnt, + unsigned byrefArgMask, unsigned byrefRegMask, + BYTE* base, unsigned enSize) +{ + CallPattern pat; + + pat.fld.argCnt = (argCnt < 0xff) ? argCnt : 0xff; + pat.fld.regMask = (regMask < 0xff) ? regMask : 0xff; + pat.fld.argMask = (argMask < 0xff) ? argMask : 0xff; + pat.fld.codeDelta = (codeDelta < 0xff) ? codeDelta : 0xff; + + if (logFile == NULL) + { + logFile = fopen("regen.txt", "a"); + InitializeCriticalSection(&logFileLock); + } + + assert(((enSize>0) && (enSize<256)) && ((pat.val & 0xffffff) != 0xffffff)); + + EnterCriticalSection(&logFileLock); + + fprintf(logFile, "CallSite( 0x%08x, 0x%02x%02x, 0x", + pat.val, byrefArgMask, byrefRegMask); + + while (enSize > 0) + { + fprintf(logFile, "%02x", *base++); + enSize--; + } + fprintf(logFile, "),\n"); + fflush(logFile); + + LeaveCriticalSection(&logFileLock); +} +#endif + +#if REGEN_SHORTCUTS +static void regenLog(unsigned encoding, InfoHdr* header, InfoHdr* state) +{ + if (logFile == NULL) + { + logFile = fopen("regen.txt", "a"); + InitializeCriticalSection(&logFileLock); + } + + EnterCriticalSection(&logFileLock); + + fprintf(logFile, "InfoHdr( %2d, %2d, %1d, %1d, %1d," + " %1d, %1d, %1d, %1d, %1d," + " %1d, %1d, %1d, %1d, %1d," + " %1d, %2d, %2d, %2d, %2d," + " %2d, %2d), \n", + state->prologSize, + state->epilogSize, + state->epilogCount, + state->epilogAtEnd, + state->ediSaved, + state->esiSaved, + state->ebxSaved, + state->ebpSaved, + state->ebpFrame, + state->interruptible, + state->doubleAlign, + state->security, + state->handlers, + state->localloc, + state->editNcontinue, + state->varargs, + state->profCallbacks, + state->argCount, + state->frameSize, + (state->untrackedCnt <= SET_UNTRACKED_MAX) ? state->untrackedCnt : HAS_UNTRACKED, + (state->varPtrTableSize == 0) ? 0 : HAS_VARPTR, + (state->gsCookieOffset == INVALID_GS_COOKIE_OFFSET) ? 0 : HAS_GS_COOKIE_OFFSET, + (state->syncStartOffset == INVALID_SYNC_OFFSET) ? 0 : HAS_SYNC_OFFSET, + (state->syncStartOffset == INVALID_SYNC_OFFSET) ? 0 : HAS_SYNC_OFFSET + ); + + fflush(logFile); + + LeaveCriticalSection(&logFileLock); +} +#endif + +/***************************************************************************** + * + * Given the four parameters return the index into the callPatternTable[] + * that is used to encoding these four items. If an exact match cannot + * found then ignore the codeDelta and search the table again for a near + * match. + * Returns 0..79 for an exact match or + * (delta<<8) | (0..79) for a near match. + * A near match will be encoded using two bytes, the first byte will + * skip the adjustment delta that prevented an exact match and the + * rest of the delta plus the other three items are encoded in the + * second byte. + */ +int FASTCALL lookupCallPattern(unsigned argCnt, unsigned regMask, + unsigned argMask, unsigned codeDelta) +{ + if ((argCnt <= CP_MAX_ARG_CNT) && (argMask <= CP_MAX_ARG_MASK)) + { + CallPattern pat; + + pat.fld.argCnt = argCnt; + pat.fld.regMask = regMask; // EBP,EBX,ESI,EDI + pat.fld.argMask = argMask; + pat.fld.codeDelta = codeDelta; + + bool codeDeltaOK = (pat.fld.codeDelta == codeDelta); + unsigned bestDelta2 = 0xff; + unsigned bestPattern = 0xff; + unsigned patval = pat.val; + assert(sizeof(CallPattern) == sizeof(unsigned)); + + const unsigned * curp = &callPatternTable[0]; + for (unsigned inx = 0; + inx < 80; + inx++,curp++) + { + unsigned curval = *curp; + if ((patval == curval) && codeDeltaOK) + return inx; + + if (((patval ^ curval) & 0xffffff) == 0) + { + unsigned delta2 = codeDelta - (curval >> 24); + if (delta2 < bestDelta2) + { + bestDelta2 = delta2; + bestPattern = inx; + } + } + } + + if (bestPattern != 0xff) + { + return (bestDelta2 << 8) | bestPattern; + } + } + return -1; +} + + + +static bool initNeeded3(unsigned cur, unsigned tgt, + unsigned max, unsigned* hint) +{ + assert(cur != tgt); + + unsigned tmp = tgt; + unsigned nib = 0; + unsigned cnt = 0; + + while (tmp > max) + { + nib = tmp & 0x07; + tmp >>= 3; + if (tmp == cur) + { + *hint = nib; + return false; + } + cnt++; + } + + *hint = tmp; + return true; +} + +static bool initNeeded4(unsigned cur, unsigned tgt, + unsigned max, unsigned* hint) +{ + assert(cur != tgt); + + unsigned tmp = tgt; + unsigned nib = 0; + unsigned cnt = 0; + + while (tmp > max) + { + nib = tmp & 0x0f; + tmp >>= 4; + if (tmp == cur) + { + *hint = nib; + return false; + } + cnt++; + } + + *hint = tmp; + return true; +} + +static int bigEncoding3(unsigned cur, unsigned tgt, unsigned max) +{ + assert(cur != tgt); + + unsigned tmp = tgt; + unsigned nib = 0; + unsigned cnt = 0; + + while (tmp > max) + { + nib = tmp & 0x07; + tmp >>= 3; + if (tmp == cur) + break; + cnt++; + } + return cnt; +} + +static int bigEncoding4(unsigned cur, unsigned tgt, unsigned max) +{ + assert(cur != tgt); + + unsigned tmp = tgt; + unsigned nib = 0; + unsigned cnt = 0; + + while (tmp > max) + { + nib = tmp & 0x0f; + tmp >>= 4; + if (tmp == cur) + break; + cnt++; + } + return cnt; +} + +BYTE FASTCALL encodeHeaderNext(const InfoHdr& header, InfoHdr* state) +{ + BYTE encoding = 0xff; + + if (state->argCount != header.argCount) + { + // We have one-byte encodings for 0..8 + if (header.argCount <= SET_ARGCOUNT_MAX) + { + state->argCount = header.argCount; + encoding = SET_ARGCOUNT + header.argCount; + goto DO_RETURN; + } + else + { + unsigned hint; + if (initNeeded4(state->argCount, header.argCount, + SET_ARGCOUNT_MAX, &hint)) + { + assert(hint <= SET_ARGCOUNT_MAX); + state->argCount = hint; + encoding = SET_ARGCOUNT + hint; + goto DO_RETURN; + } + else + { + assert(hint <= 0xf); + state->argCount <<= 4; + state->argCount += hint; + encoding = NEXT_FOUR_ARGCOUNT + hint; + goto DO_RETURN; + } + } + } + + if (state->frameSize != header.frameSize) + { + // We have one-byte encodings for 0..7 + if (header.frameSize <= SET_FRAMESIZE_MAX) + { + state->frameSize = header.frameSize; + encoding = SET_FRAMESIZE + header.frameSize; + goto DO_RETURN; + } + else + { + unsigned hint; + if (initNeeded4(state->frameSize, header.frameSize, + SET_FRAMESIZE_MAX, &hint)) + { + assert(hint <= SET_FRAMESIZE_MAX); + state->frameSize = hint; + encoding = SET_FRAMESIZE + hint; + goto DO_RETURN; + } + else + { + assert(hint <= 0xf); + state->frameSize <<= 4; + state->frameSize += hint; + encoding = NEXT_FOUR_FRAMESIZE + hint; + goto DO_RETURN; + } + } + } + + if ((state->epilogCount != header.epilogCount) || + (state->epilogAtEnd != header.epilogAtEnd)) + { + if (header.epilogCount > SET_EPILOGCNT_MAX) + IMPL_LIMITATION("More than SET_EPILOGCNT_MAX epilogs"); + + state->epilogCount = header.epilogCount; + state->epilogAtEnd = header.epilogAtEnd; + encoding = SET_EPILOGCNT + header.epilogCount*2; + if (header.epilogAtEnd) + encoding++; + goto DO_RETURN; + } + + if (state->varPtrTableSize != header.varPtrTableSize) + { + assert(state->varPtrTableSize == 0 || state->varPtrTableSize == HAS_VARPTR); + + if (state->varPtrTableSize == 0) + { + state->varPtrTableSize = HAS_VARPTR; + encoding = FLIP_VAR_PTR_TABLE_SZ; + goto DO_RETURN; + } + else if (header.varPtrTableSize == 0) + { + state->varPtrTableSize = 0; + encoding = FLIP_VAR_PTR_TABLE_SZ; + goto DO_RETURN; + } + } + + if (state->untrackedCnt != header.untrackedCnt) + { + assert(state->untrackedCnt <= SET_UNTRACKED_MAX || + state->untrackedCnt == HAS_UNTRACKED); + + // We have one-byte encodings for 0..3 + if (header.untrackedCnt <= SET_UNTRACKED_MAX) + { + state->untrackedCnt = header.untrackedCnt; + encoding = SET_UNTRACKED + header.untrackedCnt; + goto DO_RETURN; + } + else if (state->untrackedCnt != HAS_UNTRACKED) + { + state->untrackedCnt = HAS_UNTRACKED; + encoding = FFFF_UNTRACKED_CNT; + goto DO_RETURN; + } + } + + if (state->epilogSize != header.epilogSize) + { + // We have one-byte encodings for 0..10 + if (header.epilogSize <= SET_EPILOGSIZE_MAX) + { + state->epilogSize = header.epilogSize; + encoding = SET_EPILOGSIZE + header.epilogSize; + goto DO_RETURN; + } + else + { + unsigned hint; + if (initNeeded3(state->epilogSize, header.epilogSize, + SET_EPILOGSIZE_MAX, &hint)) + { + assert(hint <= SET_EPILOGSIZE_MAX); + state->epilogSize = hint; + encoding = SET_EPILOGSIZE + hint; + goto DO_RETURN; + } + else + { + assert(hint <= 0x7); + state->epilogSize <<= 3; + state->epilogSize += hint; + encoding = NEXT_THREE_EPILOGSIZE + hint; + goto DO_RETURN; + } + } + } + + if (state->prologSize != header.prologSize) + { + // We have one-byte encodings for 0..16 + if (header.prologSize <= SET_PROLOGSIZE_MAX) + { + state->prologSize = header.prologSize; + encoding = SET_PROLOGSIZE + header.prologSize; + goto DO_RETURN; + } + else + { + unsigned hint; + assert(SET_PROLOGSIZE_MAX > 15); + if (initNeeded3(state->prologSize, header.prologSize, 15, &hint)) + { + assert(hint <= 15); + state->prologSize = hint; + encoding = SET_PROLOGSIZE + hint; + goto DO_RETURN; + } + else + { + assert(hint <= 0x7); + state->prologSize <<= 3; + state->prologSize += hint; + encoding = NEXT_THREE_PROLOGSIZE + hint; + goto DO_RETURN; + } + } + } + + if (state->ediSaved != header.ediSaved) + { + state->ediSaved = header.ediSaved; + encoding = FLIP_EDI_SAVED; + goto DO_RETURN; + } + + if (state->esiSaved != header.esiSaved) + { + state->esiSaved = header.esiSaved; + encoding = FLIP_ESI_SAVED; + goto DO_RETURN; + } + + if (state->ebxSaved != header.ebxSaved) + { + state->ebxSaved = header.ebxSaved; + encoding = FLIP_EBX_SAVED; + goto DO_RETURN; + } + + if (state->ebpSaved != header.ebpSaved) + { + state->ebpSaved = header.ebpSaved; + encoding = FLIP_EBP_SAVED; + goto DO_RETURN; + } + + if (state->ebpFrame != header.ebpFrame) + { + state->ebpFrame = header.ebpFrame; + encoding = FLIP_EBP_FRAME; + goto DO_RETURN; + } + + if (state->interruptible != header.interruptible) + { + state->interruptible = header.interruptible; + encoding = FLIP_INTERRUPTIBLE; + goto DO_RETURN; + } + +#if DOUBLE_ALIGN + if (state->doubleAlign != header.doubleAlign) + { + state->doubleAlign = header.doubleAlign; + encoding = FLIP_DOUBLE_ALIGN; + goto DO_RETURN; + } +#endif + + if (state->security != header.security) + { + state->security = header.security; + encoding = FLIP_SECURITY; + goto DO_RETURN; + } + + if (state->handlers != header.handlers) + { + state->handlers = header.handlers; + encoding = FLIP_HANDLERS; + goto DO_RETURN; + } + + if (state->localloc != header.localloc) + { + state->localloc = header.localloc; + encoding = FLIP_LOCALLOC; + goto DO_RETURN; + } + + if (state->editNcontinue != header.editNcontinue) + { + state->editNcontinue = header.editNcontinue; + encoding = FLIP_EDITnCONTINUE; + goto DO_RETURN; + } + + if (state->varargs != header.varargs) + { + state->varargs = header.varargs; + encoding = FLIP_VARARGS; + goto DO_RETURN; + } + + if (state->profCallbacks != header.profCallbacks) + { + state->profCallbacks = header.profCallbacks; + encoding = FLIP_PROF_CALLBACKS; + goto DO_RETURN; + } + + if (state->genericsContext != header.genericsContext) + { + state->genericsContext = header.genericsContext; + encoding = FLIP_HAS_GENERICS_CONTEXT; + goto DO_RETURN; + } + + if (state->genericsContextIsMethodDesc != header.genericsContextIsMethodDesc) + { + state->genericsContextIsMethodDesc = header.genericsContextIsMethodDesc; + encoding = FLIP_GENERICS_CONTEXT_IS_METHODDESC; + goto DO_RETURN; + } + + if (state->gsCookieOffset != header.gsCookieOffset) + { + assert(state->gsCookieOffset == INVALID_GS_COOKIE_OFFSET || + state->gsCookieOffset == HAS_GS_COOKIE_OFFSET); + + if (state->gsCookieOffset == INVALID_GS_COOKIE_OFFSET) + { + // header.gsCookieOffset is non-zero. We can set it + // to zero using FLIP_HAS_GS_COOKIE + state->gsCookieOffset = HAS_GS_COOKIE_OFFSET; + encoding = FLIP_HAS_GS_COOKIE; + goto DO_RETURN; + } + else if (header.gsCookieOffset == INVALID_GS_COOKIE_OFFSET) + { + state->gsCookieOffset = INVALID_GS_COOKIE_OFFSET; + encoding = FLIP_HAS_GS_COOKIE; + goto DO_RETURN; + } + } + + if (state->syncStartOffset != header.syncStartOffset) + { + assert(state->syncStartOffset == INVALID_SYNC_OFFSET || + state->syncStartOffset == HAS_SYNC_OFFSET); + + if (state->syncStartOffset == INVALID_SYNC_OFFSET) + { + // header.syncStartOffset is non-zero. We can set it + // to zero using FLIP_SYNC + state->syncStartOffset = HAS_SYNC_OFFSET; + encoding = FLIP_SYNC; + goto DO_RETURN; + } + else if (header.syncStartOffset == INVALID_SYNC_OFFSET) + { + state->syncStartOffset = INVALID_SYNC_OFFSET; + encoding = FLIP_SYNC; + goto DO_RETURN; + } + } + +DO_RETURN: + assert(encoding < 0x80); + if (!state->isHeaderMatch(header)) + encoding |= 0x80; + return encoding; +} + +static int measureDistance(const InfoHdr& header, const InfoHdrSmall * p, int closeness) +{ + int distance = 0; + + if (p->untrackedCnt != header.untrackedCnt) + { + if (header.untrackedCnt > 3) + { + if (p->untrackedCnt != HAS_UNTRACKED) + distance += 1; + } + else + { + distance += 1; + } + if (distance >= closeness) return distance; + } + + if (p->varPtrTableSize != header.varPtrTableSize) + { + if (header.varPtrTableSize != 0) + { + if (p->varPtrTableSize != HAS_VARPTR) + distance += 1; + } + else + { + assert(p->varPtrTableSize == HAS_VARPTR); + distance += 1; + } + if (distance >= closeness) return distance; + } + + if (p->frameSize != header.frameSize) + { + distance += 1; + if (distance >= closeness) return distance; + + // We have one-byte encodings for 0..7 + if (header.frameSize > SET_FRAMESIZE_MAX) + { + distance += bigEncoding4(p->frameSize, header.frameSize, + SET_FRAMESIZE_MAX); + if (distance >= closeness) return distance; + } + } + + if (p->argCount != header.argCount) + { + distance += 1; + if (distance >= closeness) return distance; + + // We have one-byte encodings for 0..8 + if (header.argCount > SET_ARGCOUNT_MAX) + { + distance += bigEncoding4(p->argCount, header.argCount, + SET_ARGCOUNT_MAX); + if (distance >= closeness) return distance; + } + } + + if (p->prologSize != header.prologSize) + { + distance += 1; + if (distance >= closeness) return distance; + + // We have one-byte encodings for 0..16 + if (header.prologSize > SET_PROLOGSIZE_MAX) + { + assert(SET_PROLOGSIZE_MAX > 15); + distance += bigEncoding3(p->prologSize, header.prologSize, 15); + if (distance >= closeness) return distance; + } + } + + if (p->epilogSize != header.epilogSize) + { + distance += 1; + if (distance >= closeness) return distance; + // We have one-byte encodings for 0..10 + if (header.epilogSize > SET_EPILOGSIZE_MAX) + { + distance += bigEncoding3(p->epilogSize, header.epilogSize, + SET_EPILOGSIZE_MAX); + if (distance >= closeness) return distance; + } + } + + if ((p->epilogCount != header.epilogCount) || + (p->epilogAtEnd != header.epilogAtEnd)) + { + distance += 1; + if (distance >= closeness) return distance; + + if (header.epilogCount > SET_EPILOGCNT_MAX) + IMPL_LIMITATION("More than SET_EPILOGCNT_MAX epilogs"); + } + + if (p->ediSaved != header.ediSaved) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->esiSaved != header.esiSaved) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->ebxSaved != header.ebxSaved) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->ebpSaved != header.ebpSaved) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->ebpFrame != header.ebpFrame) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->interruptible != header.interruptible) + { + distance += 1; + if (distance >= closeness) return distance; + } + +#if DOUBLE_ALIGN + if (p->doubleAlign != header.doubleAlign) + { + distance += 1; + if (distance >= closeness) return distance; + } +#endif + + if (p->security != header.security) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->handlers != header.handlers) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->localloc != header.localloc) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->editNcontinue != header.editNcontinue) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->varargs != header.varargs) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->profCallbacks != header.profCallbacks) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->genericsContext != header.genericsContext) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (p->genericsContextIsMethodDesc != header.genericsContextIsMethodDesc) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (header.gsCookieOffset != INVALID_GS_COOKIE_OFFSET) + { + distance += 1; + if (distance >= closeness) return distance; + } + + if (header.syncStartOffset != INVALID_SYNC_OFFSET) + { + distance += 1; + if (distance >= closeness) return distance; + } + + return distance; +} + +// DllMain calls gcInitEncoderLookupTable to fill in this table +/* extern */ int infoHdrLookup[IH_MAX_PROLOG_SIZE+2]; + +/* static */ void GCInfo::gcInitEncoderLookupTable() +{ + const InfoHdrSmall * p = &infoHdrShortcut[0]; + int lo = -1; + int hi = 0; + int n; + + for (n = 0; n < 128; n++, p++) + { + if (p->prologSize != lo) + { + if (p->prologSize < lo) + { + assert(p->prologSize == 0); + hi = IH_MAX_PROLOG_SIZE; + } + else + hi = p->prologSize; + + assert(hi <= IH_MAX_PROLOG_SIZE); + + while (lo < hi) + infoHdrLookup[++lo] = n; + + if (lo == IH_MAX_PROLOG_SIZE) + break; + } + } + + assert(lo == IH_MAX_PROLOG_SIZE); + assert(infoHdrLookup[IH_MAX_PROLOG_SIZE] < 128); + + while (p->prologSize == lo) + { + n++; + if (n >= 128) + break; + p++; + } + + infoHdrLookup[++lo] = n; + +#ifdef DEBUG + // + // We do some other DEBUG only validity checks here + // + assert(callCommonDelta[0] < callCommonDelta[1]); + assert(callCommonDelta[1] < callCommonDelta[2]); + assert(callCommonDelta[2] < callCommonDelta[3]); + assert(sizeof(CallPattern) == sizeof(unsigned)); + unsigned maxMarks = 0; + for (unsigned inx=0; inx < 80; inx++) + { + CallPattern pat; + pat.val = callPatternTable[inx]; + + assert(pat.fld.codeDelta <= CP_MAX_CODE_DELTA); + if (pat.fld.codeDelta == CP_MAX_CODE_DELTA) maxMarks |= 0x01; + + assert(pat.fld.argCnt <= CP_MAX_ARG_CNT); + if (pat.fld.argCnt == CP_MAX_ARG_CNT) maxMarks |= 0x02; + + assert(pat.fld.argMask <= CP_MAX_ARG_MASK); + if (pat.fld.argMask == CP_MAX_ARG_MASK) maxMarks |= 0x04; + } + assert(maxMarks == 0x07); +#endif +} + +const int NO_CACHED_HEADER = -1; + +BYTE FASTCALL encodeHeaderFirst(const InfoHdr& header, InfoHdr* state, int* more, int* pCached) +{ + // First try the cached value for an exact match, if there is one + // + int n = *pCached; + const InfoHdrSmall * p; + + if (n != NO_CACHED_HEADER) + { + p = &infoHdrShortcut[n]; + if (p->isHeaderMatch(header)) + { + // exact match found + GetInfoHdr(n, state); + *more = 0; + return n; + } + } + + // Next search the table for an exact match + // Only search entries that have a matching prolog size + // Note: lo and hi are saved here as they specify the + // range of entries that have the correct prolog size + // + unsigned psz = header.prologSize; + int lo = 0; + int hi = 0; + + if (psz <= IH_MAX_PROLOG_SIZE) + { + lo = infoHdrLookup[psz]; + hi = infoHdrLookup[psz+1]; + p = &infoHdrShortcut[lo]; + for (n=lo; n<hi; n++,p++) + { + assert(psz == p->prologSize); + if (p->isHeaderMatch(header)) + { + // exact match found + GetInfoHdr(n, state); + *pCached = n; // cache the value + *more = 0; + return n; + } + } + } + + // + // no exact match in infoHdrShortcut[] + // + // find the nearest entry in the table + // + int nearest = -1; + int closeness = 255; // (i.e. not very close) + + // + // Calculate the minimum acceptable distance + // if we find an entry that is at least this close + // we will stop the search and use that value + // + int min_acceptable_distance = 1; + + if (header.frameSize > SET_FRAMESIZE_MAX) + { + ++min_acceptable_distance; + if (header.frameSize > 32) + ++min_acceptable_distance; + } + if (header.argCount > SET_ARGCOUNT_MAX) + { + ++min_acceptable_distance; + if (header.argCount > 32) + ++min_acceptable_distance; + } + + // First try the cached value + // and see if it meets the minimum acceptable distance + // + if (*pCached != NO_CACHED_HEADER) + { + p = &infoHdrShortcut[*pCached]; + int distance = measureDistance(header, p, closeness); + assert(distance > 0); + if (distance <= min_acceptable_distance) + { + GetInfoHdr(*pCached, state); + *more = distance; + return 0x80 | *pCached; + } + else + { + closeness = distance; + nearest = *pCached; + } + } + + // Then try the ones pointed to by [lo..hi), + // (i.e. the ones that have the correct prolog size) + // + p = &infoHdrShortcut[lo]; + for (n=lo; n<hi; n++,p++) + { + if (n == *pCached) + continue; // already tried this one + int distance = measureDistance(header, p, closeness); + assert(distance > 0); + if (distance <= min_acceptable_distance) + { + GetInfoHdr(n, state); + *pCached = n; // Cache this value + *more = distance; + return 0x80 | n; + } + else if (distance < closeness) + { + closeness = distance; + nearest = n; + } + } + + int last = infoHdrLookup[IH_MAX_PROLOG_SIZE+1]; + assert(last <= 128); + + // Then try all the rest [0..last-1] + p = &infoHdrShortcut[0]; + for (n=0; n<last; n++,p++) + { + if (n == *pCached) + continue; // already tried this one + if ((n>=lo) && (n<hi)) + continue; // already tried these + int distance = measureDistance(header, p, closeness); + assert(distance > 0); + if (distance <= min_acceptable_distance) + { + GetInfoHdr(n, state); + *pCached = n; // Cache this value + *more = distance; + return 0x80 | n; + } + else if (distance < closeness) + { + closeness = distance; + nearest = n; + } + } + + // + // If we reach here then there was no adjacent neighbor + // in infoHdrShortcut[], closeness indicate how many extra + // bytes we will need to encode this item. + // + assert((nearest >= 0) && (nearest <= 127)); + GetInfoHdr(nearest, state); + *pCached = nearest; // Cache this value + *more = closeness; + return 0x80 | nearest; +} + +/***************************************************************************** + * + * Write the initial part of the method info block. This is called twice; + * first to compute the size needed for the info (mask=0), the second time + * to actually generate the contents of the table (mask=-1,dest!=NULL). + */ + +size_t GCInfo::gcInfoBlockHdrSave(BYTE* dest, + int mask, + unsigned methodSize, + unsigned prologSize, + unsigned epilogSize, + InfoHdr* header, + int* pCached) +{ +#ifdef DEBUG + if (compiler->verbose) + printf("*************** In gcInfoBlockHdrSave()\n"); +#endif + size_t size = 0; + +#if VERIFY_GC_TABLES + *castto(dest, unsigned short *)++ = 0xFEEF; size += sizeof(short); +#endif + + /* Write the method size first (using between 1 and 5 bytes) */ + +#ifdef DEBUG + if (compiler->verbose) + { + if (mask) printf("GCINFO: methodSize = %04X\n", methodSize); + if (mask) printf("GCINFO: prologSize = %04X\n", prologSize); + if (mask) printf("GCINFO: epilogSize = %04X\n", epilogSize); + } +#endif + + size_t methSz = encodeUnsigned(dest, methodSize); + size += methSz; + dest += methSz & mask; + + // + // New style InfoBlk Header + // + // Typically only uses one-byte to store everything. + // + + if (mask==0) + { + memset(header, 0, sizeof(InfoHdr)); + *pCached = NO_CACHED_HEADER; + } + + assert(FitsIn<unsigned char>(prologSize)); + header->prologSize = static_cast<unsigned char>(prologSize); + assert(FitsIn<unsigned char>(epilogSize)); + header->epilogSize = static_cast<unsigned char>(epilogSize); + header->epilogCount = compiler->getEmitter()->emitGetEpilogCnt(); + if (header->epilogCount != compiler->getEmitter()->emitGetEpilogCnt()) + IMPL_LIMITATION("emitGetEpilogCnt() does not fit in InfoHdr::epilogCount"); + header->epilogAtEnd = compiler->getEmitter()->emitHasEpilogEnd(); + + + if (compiler->codeGen->regSet.rsRegsModified(RBM_EDI)) + header->ediSaved = 1; + if (compiler->codeGen->regSet.rsRegsModified(RBM_ESI)) + header->esiSaved = 1; + if (compiler->codeGen->regSet.rsRegsModified(RBM_EBX)) + header->ebxSaved = 1; + + + header->interruptible = compiler->codeGen->genInterruptible; + + + if (!compiler->isFramePointerUsed()) + { +#if DOUBLE_ALIGN + if (compiler->genDoubleAlign()) + { + header->ebpSaved = true; + assert(!compiler->codeGen->regSet.rsRegsModified(RBM_EBP)); + } +#endif + if (compiler->codeGen->regSet.rsRegsModified(RBM_EBP)) + { + header->ebpSaved = true; + } + } + else + { + header->ebpSaved = true; + header->ebpFrame = true; + } + + +#if DOUBLE_ALIGN + header->doubleAlign = compiler->genDoubleAlign(); +#endif + + header->security = compiler->opts.compNeedSecurityCheck; + + header->handlers = compiler->ehHasCallableHandlers(); + header->localloc = compiler->compLocallocUsed; + + header->varargs = compiler->info.compIsVarArgs; + header->profCallbacks = compiler->info.compProfilerCallback; + header->editNcontinue = compiler->opts.compDbgEnC; + header->genericsContext = compiler->lvaReportParamTypeArg(); + header->genericsContextIsMethodDesc = header->genericsContext && (compiler->info.compMethodInfo->options & (CORINFO_GENERICS_CTXT_FROM_METHODDESC)); + header->gsCookieOffset = INVALID_GS_COOKIE_OFFSET; + if (compiler->getNeedsGSSecurityCookie()) + { + _ASSERTE(compiler->lvaGSSecurityCookie != BAD_VAR_NUM); + int stkOffs = compiler->lvaTable[compiler->lvaGSSecurityCookie].lvStkOffs; + header->gsCookieOffset = compiler->isFramePointerUsed() ? -stkOffs : stkOffs; + assert(header->gsCookieOffset != INVALID_GS_COOKIE_OFFSET); + } + + header->syncStartOffset = INVALID_SYNC_OFFSET; + header->syncEndOffset = INVALID_SYNC_OFFSET; + if (compiler->info.compFlags & CORINFO_FLG_SYNCH) + { + assert(compiler->syncStartEmitCookie != NULL); + header->syncStartOffset = compiler->getEmitter()->emitCodeOffset(compiler->syncStartEmitCookie, 0); + assert(header->syncStartOffset != INVALID_SYNC_OFFSET); + + assert(compiler->syncEndEmitCookie != NULL); + header->syncEndOffset = compiler->getEmitter()->emitCodeOffset(compiler->syncEndEmitCookie, 0); + assert(header->syncEndOffset != INVALID_SYNC_OFFSET); + + assert(header->syncStartOffset < header->syncEndOffset); + // synchronized methods can't have more than 1 epilog + assert(header->epilogCount <= 1); + } + + assert((compiler->compArgSize & 0x3) == 0); + + size_t argCount = (compiler->compArgSize - (compiler->codeGen->intRegState.rsCalleeRegArgNum * sizeof(void *))) / sizeof(void*); + assert(argCount <= MAX_USHORT_SIZE_T); + header->argCount = static_cast<unsigned short>(argCount); + + header->frameSize = compiler->compLclFrameSize / sizeof(int); + if (header->frameSize != (compiler->compLclFrameSize / sizeof(int))) + IMPL_LIMITATION("compLclFrameSize does not fit in InfoHdr::frameSize"); + + if (mask==0) + { + gcCountForHeader((UNALIGNED unsigned int*)&header->untrackedCnt, (UNALIGNED unsigned int*)&header->varPtrTableSize); + } + + // + // If the high-order bit of headerEncoding is set + // then additional bytes will update the InfoHdr state + // until the fully state is encoded + // + InfoHdr state; + int more = 0; + BYTE headerEncoding = encodeHeaderFirst(*header, &state, &more, pCached); + ++size; + if (mask) + { +#if REGEN_SHORTCUTS + regenLog(headerEncoding, header, &state); +#endif + *dest++ = headerEncoding; + + BYTE encoding = headerEncoding; + while (encoding & 0x80) + { + encoding = encodeHeaderNext(*header, &state); +#if REGEN_SHORTCUTS + regenLog(headerEncoding, header, &state); +#endif + *dest++ = encoding; + ++size; + } + } + else + { + size += more; + } + + if (header->untrackedCnt > SET_UNTRACKED_MAX) + { + unsigned count = header->untrackedCnt; + unsigned sz = encodeUnsigned(mask ? dest : NULL, count); + size += sz; + dest += (sz & mask); + } + + if (header->varPtrTableSize != 0) + { + unsigned count = header->varPtrTableSize; + unsigned sz = encodeUnsigned(mask ? dest : NULL, count); + size += sz; + dest += (sz & mask); + } + + if (header->gsCookieOffset != INVALID_GS_COOKIE_OFFSET) + { + assert(mask == 0 || state.gsCookieOffset == HAS_GS_COOKIE_OFFSET); + unsigned offset = header->gsCookieOffset; + unsigned sz = encodeUnsigned(mask ? dest : NULL, offset); + size += sz; + dest += (sz & mask); + } + + if (header->syncStartOffset != INVALID_SYNC_OFFSET) + { + assert(mask == 0 || state.syncStartOffset == HAS_SYNC_OFFSET); + + { + unsigned offset = header->syncStartOffset; + unsigned sz = encodeUnsigned(mask ? dest : NULL, offset); + size += sz; + dest += (sz & mask); + } + + { + unsigned offset = header->syncEndOffset; + unsigned sz = encodeUnsigned(mask ? dest : NULL, offset); + size += sz; + dest += (sz & mask); + } + } + + + if (header->epilogCount) + { + /* Generate table unless one epilog at the end of the method */ + + if (header->epilogAtEnd == 0 || + header->epilogCount != 1) + { +#if VERIFY_GC_TABLES + *castto(dest, unsigned short *)++ = 0xFACE; size += sizeof(short); +#endif + + /* Simply write a sorted array of offsets using encodeUDelta */ + + gcEpilogTable = mask ? dest : NULL; + gcEpilogPrevOffset = 0; + + size_t sz = compiler->getEmitter()->emitGenEpilogLst(gcRecordEpilog, this); + + /* Add the size of the epilog table to the total size */ + + size += sz; + dest += (sz & mask); + } + } + +#if DISPLAY_SIZES + + if (mask) + { + if (compiler->codeGen->genInterruptible) + { + genMethodICnt++; + } + else + { + genMethodNCnt++; + } + } + +#endif // DISPLAY_SIZES + + return size; +} + +/***************************************************************************** + * + * Return the size of the pointer tracking tables. + */ + +size_t GCInfo::gcPtrTableSize(const InfoHdr& header, unsigned codeSize, size_t* pArgTabOffset) +{ + BYTE temp[16+1]; +#ifdef DEBUG + temp[16] = 0xAB; // Set some marker +#endif + + /* Compute the total size of the tables */ + + size_t size = gcMakeRegPtrTable(temp, 0, header, codeSize, pArgTabOffset); + + assert(temp[16] == 0xAB); // Check that marker didnt get overwritten + + return size; +} + +/***************************************************************************** + * Encode the callee-saved registers into 3 bits. + */ + +unsigned gceEncodeCalleeSavedRegs(unsigned regs) +{ + unsigned encodedRegs = 0; + + if (regs & RBM_EBX) encodedRegs |= 0x04; + if (regs & RBM_ESI) encodedRegs |= 0x02; + if (regs & RBM_EDI) encodedRegs |= 0x01; + + return encodedRegs; +} + +/***************************************************************************** + * Is the next entry for a byref pointer. If so, emit the prefix for the + * interruptible encoding. Check only for pushes and registers + */ + +inline +BYTE * gceByrefPrefixI(GCInfo::regPtrDsc * rpd, BYTE * dest) +{ + // For registers, we don't need a prefix if it is going dead. + assert(rpd->rpdArg || rpd->rpdCompiler.rpdDel==0); + + if (!rpd->rpdArg || rpd->rpdArgType == GCInfo::rpdARG_PUSH) + if (rpd->rpdGCtypeGet() == GCT_BYREF) + *dest++ = 0xBF; + + return dest; +} + +/*****************************************************************************/ + +/* These functions are needed to work around a VC5.0 compiler bug */ +/* DO NOT REMOVE, unless you are sure that the free build works */ +static int zeroFN() { return 0; } +static int (*zeroFunc)() = zeroFN; + +/***************************************************************************** + * Modelling of the GC ptrs pushed on the stack + */ + +typedef unsigned pasMaskType; +#define BITS_IN_pasMask (BITS_IN_BYTE * sizeof(pasMaskType)) +#define HIGHEST_pasMask_BIT (((pasMaskType)0x1) << (BITS_IN_pasMask-1)) + +//----------------------------------------------------------------------------- + +class PendingArgsStack +{ +public: + + PendingArgsStack (unsigned maxDepth, Compiler * pComp); + + void pasPush (GCtype gcType); + void pasPop (unsigned count); + void pasKill (unsigned gcCount); + + unsigned pasCurDepth () { return pasDepth; } + pasMaskType pasArgMask () { assert(pasDepth <= BITS_IN_pasMask); return pasBottomMask; } + pasMaskType pasByrefArgMask () { assert(pasDepth <= BITS_IN_pasMask); return pasByrefBottomMask; } + bool pasHasGCptrs (); + + // Use these in the case where there actually are more ptrs than pasArgMask + unsigned pasEnumGCoffsCount(); + #define pasENUM_START ((unsigned)-1) + #define pasENUM_LAST ((unsigned)-2) + #define pasENUM_END ((unsigned)-3) + unsigned pasEnumGCoffs (unsigned iter, unsigned * offs); + +protected: + + unsigned pasMaxDepth; + + unsigned pasDepth; + + pasMaskType pasBottomMask; // The first 32 args + pasMaskType pasByrefBottomMask; // byref qualifier for pasBottomMask + + BYTE * pasTopArray; // More than 32 args are represented here + unsigned pasPtrsInTopArray; // How many GCptrs here +}; + + +//----------------------------------------------------------------------------- + +PendingArgsStack::PendingArgsStack(unsigned maxDepth, Compiler * pComp) : + pasMaxDepth(maxDepth), pasDepth(0), + pasBottomMask(0), pasByrefBottomMask(0), + pasTopArray(NULL), pasPtrsInTopArray(0) +{ + /* Do we need an array as well as the mask ? */ + + if (pasMaxDepth > BITS_IN_pasMask) + pasTopArray = (BYTE *)pComp->compGetMemA(pasMaxDepth - BITS_IN_pasMask); +} + +//----------------------------------------------------------------------------- + +void PendingArgsStack::pasPush(GCtype gcType) +{ + assert(pasDepth < pasMaxDepth); + + if (pasDepth < BITS_IN_pasMask) + { + /* Shift the mask */ + + pasBottomMask <<= 1; + pasByrefBottomMask <<= 1; + + if (needsGC(gcType)) + { + pasBottomMask |= 1; + + if (gcType == GCT_BYREF) + pasByrefBottomMask |= 1; + } + } + else + { + /* Push on array */ + + pasTopArray[pasDepth - BITS_IN_pasMask] = (BYTE)gcType; + + if (gcType) + pasPtrsInTopArray++; + } + + pasDepth++; +} + +//----------------------------------------------------------------------------- + +void PendingArgsStack::pasPop(unsigned count) +{ + assert(pasDepth >= count); + + /* First pop from array (if applicable) */ + + for (/**/; (pasDepth > BITS_IN_pasMask) && count; pasDepth--,count--) + { + unsigned topIndex = pasDepth - BITS_IN_pasMask - 1; + + GCtype topArg = (GCtype)pasTopArray[topIndex]; + + if (needsGC(topArg)) + pasPtrsInTopArray--; + } + if (count == 0) return; + + /* Now un-shift the mask */ + + assert(pasPtrsInTopArray == 0); + assert(count <= BITS_IN_pasMask); + + if (count == BITS_IN_pasMask) // (x>>32) is a nop on x86. So special-case it + { + pasBottomMask = + pasByrefBottomMask = 0; + pasDepth = 0; + } + else + { + pasBottomMask >>= count; + pasByrefBottomMask >>= count; + pasDepth -= count; + } +} + +//----------------------------------------------------------------------------- +// Kill (but don't pop) the top 'gcCount' args + +void PendingArgsStack::pasKill(unsigned gcCount) +{ + assert(gcCount != 0); + + /* First kill args in array (if any) */ + + for (unsigned curPos = pasDepth; (curPos > BITS_IN_pasMask) && gcCount; curPos--) + { + unsigned curIndex = curPos - BITS_IN_pasMask - 1; + + GCtype curArg = (GCtype)pasTopArray[curIndex]; + + if (needsGC(curArg)) + { + pasTopArray[curIndex] = GCT_NONE; + pasPtrsInTopArray--; + gcCount--; + } + } + + /* Now kill bits from the mask */ + + assert(pasPtrsInTopArray == 0); + assert(gcCount <= BITS_IN_pasMask); + + for (unsigned bitPos = 1; gcCount; bitPos<<=1) + { + assert(pasBottomMask != 0); + + if (pasBottomMask & bitPos) + { + pasBottomMask &= ~bitPos; + pasByrefBottomMask &= ~bitPos; + --gcCount; + } + else + { + assert(bitPos != HIGHEST_pasMask_BIT); + } + } +} + +//----------------------------------------------------------------------------- +// Used for the case where there are more than BITS_IN_pasMask args on stack, +// but none are any pointers. May avoid reporting anything to GCinfo + +bool PendingArgsStack::pasHasGCptrs() +{ + if (pasDepth <= BITS_IN_pasMask) + return pasBottomMask != 0; + else + return pasBottomMask != 0 || pasPtrsInTopArray != 0; +} + +//----------------------------------------------------------------------------- +// Iterates over mask and array to return total count. +// Use only when you are going to emit a table of the offsets + +unsigned PendingArgsStack::pasEnumGCoffsCount() +{ + /* Should only be used in the worst case, when just the mask can't be used */ + + assert(pasDepth > BITS_IN_pasMask && pasHasGCptrs()); + + /* Count number of set bits in mask */ + + unsigned count = 0; + + for (pasMaskType mask = 0x1, i = 0; i < BITS_IN_pasMask; mask<<=1, i++) + { + if (mask & pasBottomMask) + count++; + } + + return count + pasPtrsInTopArray; +} + +//----------------------------------------------------------------------------- +// Initalize enumeration by passing in iter=pasENUM_START. +// Continue by passing in the return value as the new value of iter +// End of enumeration when pasENUM_END is returned +// If return value != pasENUM_END, *offs is set to the offset for GCinfo + +unsigned PendingArgsStack::pasEnumGCoffs(unsigned iter, unsigned * offs) +{ + if (iter == pasENUM_LAST) return pasENUM_END; + + unsigned i = (iter == pasENUM_START) ? pasDepth : iter; + + for (/**/; i > BITS_IN_pasMask; i--) + { + GCtype curArg = (GCtype)pasTopArray[i-BITS_IN_pasMask-1]; + if (needsGC(curArg)) + { + unsigned offset; + + offset = (pasDepth - i) * sizeof(void*); + if (curArg==GCT_BYREF) + offset |= byref_OFFSET_FLAG; + + *offs = offset; + return i - 1; + } + } + + if (!pasBottomMask) return pasENUM_END; + + // Have we already processed some of the bits in pasBottomMask ? + + i = (iter == pasENUM_START || iter >= BITS_IN_pasMask) ? 0 // no + : iter; // yes + + for (pasMaskType mask = 0x1 << i; mask; i++, mask<<=1) + { + if (mask & pasBottomMask) + { + unsigned lvl = (pasDepth>BITS_IN_pasMask) ? (pasDepth-BITS_IN_pasMask) : 0; // How many in pasTopArray[] + lvl += i; + + unsigned offset; + offset = lvl * sizeof(void*); + if (mask & pasByrefBottomMask) + offset |= byref_OFFSET_FLAG; + + *offs = offset; + + unsigned remMask = -int(mask<<1); + return ((pasBottomMask & remMask) ? (i + 1) : pasENUM_LAST); + } + } + + assert(!"Shouldnt reach here"); + return pasENUM_END; +} + +/***************************************************************************** + * + * Generate the register pointer map, and return its total size in bytes. If + * 'mask' is 0, we don't actually store any data in 'dest' (except for one + * entry, which is never more than 10 bytes), so this can be used to merely + * compute the size of the table. + */ + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +size_t GCInfo::gcMakeRegPtrTable(BYTE* dest, + int mask, + const InfoHdr& header, + unsigned codeSize, + size_t* pArgTabOffset) +{ + unsigned count; + + unsigned varNum; + LclVarDsc* varDsc; + + unsigned pass; + + size_t totalSize = 0; + unsigned lastOffset; + + bool thisKeptAliveIsInUntracked = false; + + /* 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 + * + ************************************************************************** + */ + + count = 0; + for (pass = 0; pass < 2; pass++) + { + /* If pass==0, generate the count + * If pass==1, write the table of untracked pointer variables. + */ + + int lastoffset = 0; + if (pass==1) + { + assert(count == header.untrackedCnt); + if (header.untrackedCnt==0) + break; // No entries, break exits the loop since pass==1 + } + + /* Count&Write untracked locals and non-enregistered args */ + + 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())) + { + /* Do we have an argument or local variable? */ + if (!varDsc->lvIsParam) + { + // If is is pinned, it must be an untracked local + assert(!varDsc->lvPinned || !varDsc->lvTracked); + + if (varDsc->lvTracked || !varDsc->lvOnFrame) + continue; + } + else + { + /* Stack-passed arguments which are not enregistered + * are always reported in this "untracked stack + * pointers" section of the GC info even if lvTracked==true + */ + + /* Has this argument been enregistered? */ + if (varDsc->lvRegister) + { + /* if a CEE_JMP has been used, then we need to report all the arguments + even if they are enregistered, since we will be using this value + in JMP call. Note that this is subtle as we require that + argument offsets are always fixed up properly even if lvRegister + is set */ + if (!compiler->compJmpOpUsed) + continue; + } + else + { + if (!varDsc->lvOnFrame) + { + /* If this non-enregistered pointer arg is never + * used, we don't need to report it + */ + assert(varDsc->lvRefCnt == 0); + continue; + } + else if (varDsc->lvIsRegArg && varDsc->lvTracked) + { + /* If this register-passed arg is tracked, then + * it has been allocated space near the other + * pointer variables and we have accurate life- + * time info. It will be reported with + * gcVarPtrList in the "tracked-pointer" section + */ + + continue; + } + } + } + + if (compiler->lvaIsOriginalThisArg(varNum) && compiler->lvaKeepAliveAndReportThis()) + { + // Encoding of untracked variables does not support reporting + // "this". So report it as a tracked variable with a liveness + // extending over the entire method. + + thisKeptAliveIsInUntracked = true; + continue; + } + + if (pass==0) + count++; + else + { + int offset; + assert(pass==1); + + offset = varDsc->lvStkOffs; +#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; + } + } + } + + if (varDsc->lvType == TYP_STRUCT && varDsc->lvOnFrame) + { + unsigned slots = compiler->lvaLclSize(varNum) / sizeof(void*); + BYTE * gcPtrs = compiler->lvaGetGcLayout(varNum); + + // walk each member of the array + for (unsigned i = 0; i < slots; i++) + { + if (gcPtrs[i] == TYPE_GC_NONE) // skip non-gc slots + continue; + + if (pass==0) + count++; + else + { + assert(pass==1); + + unsigned offset = varDsc->lvStkOffs + i * sizeof(void*); +#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 (gcPtrs[i] == TYPE_GC_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->tmpAllFree()); + for (TempDsc* tempItem = compiler->tmpListBeg(); + tempItem != nullptr; + tempItem = compiler->tmpListNxt(tempItem)) + { + if (varTypeIsGC(tempItem->tdTempType())) + { + if (pass==0) + count++; + else + { + int offset; + assert(pass==1); + + 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. + * + * In the first pass we'll count the lifetime entries and note + * whether there are any that don't fit in a small encoding. In + * the second pass we actually generate the table contents. + * + ************************************************************************** + */ + + // First we check for the most common case - no lifetimes at all. + + if (header.varPtrTableSize == 0) + goto DONE_VLT; + + varPtrDsc * varTmp; + count = 0; + + if (thisKeptAliveIsInUntracked) + { + count = 1; + + // 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].lvStkOffs; + + /* 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; + } + + for (pass = 0; pass < 2; pass++) + { + /* If second pass, generate the count */ + + if (pass) + { + assert(header.varPtrTableSize > 0); + assert(header.varPtrTableSize == count); + } + + /* We'll use a delta encoding for the lifetime offsets */ + + lastOffset = 0; + + for (varTmp = gcVarPtrList; varTmp; varTmp = varTmp->vpdNext) + { + unsigned varOffs; + unsigned lowBits; + + unsigned begOffs; + unsigned endOffs; + + assert(~OFFSET_MASK % sizeof(void*) == 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? */ + + if (!pass) + { + count++; + } + else + { + 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; + } + } + +DONE_VLT: + + 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->genInterruptible) + { +#ifdef _TARGET_X86_ + assert(compiler->genFullPtrRegMap); + + 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++ = 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++ = 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->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 = genFindLowestReg(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++ = 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 = genFindLowestReg(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 (!thisKeptAliveIsInUntracked && 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++ = 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()) // genInterruptible 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 + genFullPtrRegMap==false, and so we don't have any regPtrDsc's. */ + + if (compiler->lvaKeepAliveAndReportThis() && compiler->lvaTable[compiler->info.compThisArg].lvRegister) + { + unsigned thisRegMask = genRegMask(compiler->lvaTable[compiler->info.compThisArg].lvRegNum); + unsigned thisPtrRegEnc = gceEncodeCalleeSavedRegs(thisRegMask) << 4; + + if (thisPtrRegEnc) + { + totalSize += 1; + if (mask) + *dest++ = thisPtrRegEnc; + } + } + + CallDsc * call; + + assert(compiler->genFullPtrRegMap == 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++ = (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++ = (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++ = 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++ = call->u1.cdArgMask; + *dest++ = ((call->u1.cdArgMask >> 4) & 0xF0) | ((BYTE)codeDelta & 0x0F); + *dest++ = (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++ = ( regMask << 5) | call->u1.cdArgMask; + *dest++ = (byrefRegMask << 5) | call->u1.cdByrefArgMask; + } + + /* We'll use the large encoding */ + else if (!byref) + { + *dest++ = 0xFE; + *dest++ = (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++ = (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 // genInterruptible is false and we have an EBP-less frame + { + assert(compiler->genFullPtrRegMap); + +#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 (!thisKeptAliveIsInUntracked && 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++ = (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++ = 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++ = 0xD0 | regMask; + *dest++ = (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++ = 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; +} +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +/*****************************************************************************/ +#if DUMP_GC_TABLES +/***************************************************************************** + * + * Dump the contents of a GC pointer table. + */ + +#include "gcdump.h" + +#if VERIFY_GC_TABLES +const bool verifyGCTables = true; +#else +const bool verifyGCTables = false; +#endif + + +/***************************************************************************** + * + * Dump the info block header. + */ + +unsigned GCInfo::gcInfoBlockHdrDump(const BYTE* table, + InfoHdr* header, + unsigned* methodSize) +{ + GCDump gcDump; + + gcDump.gcPrintf = logf; // use my printf (which logs to VM) + printf("Method info block:\n"); + + return gcDump.DumpInfoHdr(table, header, methodSize, verifyGCTables); +} + +/*****************************************************************************/ + +unsigned GCInfo::gcDumpPtrTable(const BYTE* table, + const InfoHdr& header, + unsigned methodSize) +{ + printf("Pointer table:\n"); + + GCDump gcDump; + gcDump.gcPrintf = logf; // use my printf (which logs to VM) + + return gcDump.DumpGCTable(table, header, methodSize, verifyGCTables); +} + + +/***************************************************************************** + * + * Find all the live pointers in a stack frame. + */ + +void GCInfo::gcFindPtrsInFrame(const void* infoBlock, + const void* codeBlock, + unsigned offs) +{ + GCDump gcDump; + gcDump.gcPrintf = logf; // use my printf (which logs to VM) + + gcDump.DumpPtrsInFrame((const BYTE*)infoBlock, (const BYTE*)codeBlock, offs, verifyGCTables); +} + +#endif // DUMP_GC_TABLES + +#else // !JIT32_GCENCODER + +#include "gcinfoencoder.h" +#include "simplerhash.h" + +// Do explicit instantiation. +template class SimplerHashTable<RegSlotIdKey, RegSlotIdKey, GcSlotId, DefaultSimplerHashBehavior>; +template class SimplerHashTable<StackSlotIdKey, StackSlotIdKey, GcSlotId, DefaultSimplerHashBehavior>; + +#ifdef DEBUG + +void GCInfo::gcDumpVarPtrDsc(varPtrDsc * desc) +{ + const int offs = (desc->vpdVarNum & ~OFFSET_MASK); + const GCtype gcType = (desc->vpdVarNum & byref_OFFSET_FLAG) ? GCT_BYREF : GCT_GCREF; + const bool isPin = (desc->vpdVarNum & pinned_OFFSET_FLAG) != 0; + + printf("[%08X] %s%s var at [%s", + dspPtr(desc), + GCtypeStr(gcType), + isPin ? "pinned-ptr" : "", + compiler->isFramePointerUsed() ? STR_FPBASE : STR_SPBASE); + + if (offs < 0) + printf("-%02XH", -offs); + else if (offs > 0) + printf("+%02XH", +offs); + + printf("] live from %04X to %04X\n", + desc->vpdBegOfs, + desc->vpdEndOfs); +} + +static const char * const GcSlotFlagsNames[] = { + "", + "(byref) ", + "(pinned) ", + "(byref, pinned) ", + "(untracked) ", + "(byref, untracked) ", + "(pinned, untracked) ", + "(byref, pinned, untracked) " +}; + +// I'm making a local wrapper class for GcInfoEncoder so that can add logging of my own (DLD). +class GcInfoEncoderWithLogging +{ + GcInfoEncoder* m_gcInfoEncoder; + bool m_doLogging; + static ConfigDWORD s_fJitGCInfoLogging; + +public: + + GcInfoEncoderWithLogging(GcInfoEncoder* gcInfoEncoder, bool verbose) : + m_gcInfoEncoder(gcInfoEncoder), + m_doLogging(verbose || s_fJitGCInfoLogging.val(CLRConfig::INTERNAL_JitGCInfoLogging) != 0) + {} + + GcSlotId GetStackSlotId( INT32 spOffset, GcSlotFlags flags, GcStackSlotBase spBase = GC_CALLER_SP_REL ) + { + GcSlotId newSlotId = m_gcInfoEncoder->GetStackSlotId(spOffset, flags, spBase); + if (m_doLogging) + { + printf("Stack slot id for offset %d (0x%x) (%s) %s= %d.\n", + spOffset, spOffset, GcStackSlotBaseNames[spBase], + GcSlotFlagsNames[flags & 7], newSlotId); + } + return newSlotId; + } + + GcSlotId GetRegisterSlotId( UINT32 regNum, GcSlotFlags flags ) + { + GcSlotId newSlotId = m_gcInfoEncoder->GetRegisterSlotId(regNum, flags); + if (m_doLogging) + { + printf("Register slot id for reg %s %s= %d.\n", + getRegName(regNum), GcSlotFlagsNames[flags & 7], newSlotId); + } + return newSlotId; + } + + void SetSlotState(UINT32 instructionOffset, GcSlotId slotId, GcSlotState slotState) + { + m_gcInfoEncoder->SetSlotState(instructionOffset, slotId, slotState); + if (m_doLogging) + { + printf("Set state of slot %d at instr offset 0x%x to %s.\n", + slotId, instructionOffset, (slotState == GC_SLOT_LIVE ? "Live" : "Dead")); + } + } + + void DefineCallSites(UINT32* pCallSites, BYTE* pCallSiteSizes, UINT32 numCallSites) + { + m_gcInfoEncoder->DefineCallSites(pCallSites, pCallSiteSizes, numCallSites); + if (m_doLogging) + { + printf("Defining %d call sites:\n", numCallSites); + for (UINT32 k = 0; k < numCallSites; k++) + { + printf(" Offset 0x%x, size %d.\n", pCallSites[k], pCallSiteSizes[k]); + } + } + } + + void DefineInterruptibleRange(UINT32 startInstructionOffset, UINT32 length) + { + m_gcInfoEncoder->DefineInterruptibleRange(startInstructionOffset, length); + if (m_doLogging) + { + printf("Defining interruptible range: [0x%x, 0x%x).\n", startInstructionOffset, startInstructionOffset + length); + } + } + + void SetCodeLength(UINT32 length) + { + m_gcInfoEncoder->SetCodeLength(length); + if (m_doLogging) + { + printf("Set code length to %d.\n", length); + } + } + + void SetStackBaseRegister(UINT32 registerNumber) + { + m_gcInfoEncoder->SetStackBaseRegister(registerNumber); + if (m_doLogging) + { + printf("Set stack base register to %s.\n", getRegName(registerNumber)); + } + } + + void SetPrologSize( UINT32 prologSize ) + { + m_gcInfoEncoder->SetPrologSize( prologSize ); + if (m_doLogging) + { + printf("Set prolog size 0x%x.\n", prologSize); + } + } + + void SetGSCookieStackSlot( INT32 spOffsetGSCookie, UINT32 validRangeStart, UINT32 validRangeEnd ) + { + m_gcInfoEncoder->SetGSCookieStackSlot(spOffsetGSCookie, validRangeStart, validRangeEnd); + if (m_doLogging) + { + printf("Set GS Cookie stack slot to %d, valid from 0x%x to 0x%x.\n", spOffsetGSCookie, validRangeStart, validRangeEnd); + } + } + + void SetPSPSymStackSlot( INT32 spOffsetPSPSym ) + { + m_gcInfoEncoder->SetPSPSymStackSlot(spOffsetPSPSym); + if (m_doLogging) + { + printf("Set PSPSym stack slot to %d.\n", spOffsetPSPSym); + } + } + + void SetGenericsInstContextStackSlot( INT32 spOffsetGenericsContext, GENERIC_CONTEXTPARAM_TYPE type ) + { + m_gcInfoEncoder->SetGenericsInstContextStackSlot(spOffsetGenericsContext, type); + if (m_doLogging) + { + printf("Set generic instantiation context stack slot to %d, type is %s.\n", spOffsetGenericsContext, + (type == GENERIC_CONTEXTPARAM_THIS ? "THIS" : + (type == GENERIC_CONTEXTPARAM_MT ? "MT" : + (type == GENERIC_CONTEXTPARAM_MD ? "MD" : "UNKNOWN!")))); + } + } + + void SetSecurityObjectStackSlot( INT32 spOffset ) + { + m_gcInfoEncoder->SetSecurityObjectStackSlot(spOffset); + if (m_doLogging) + { + printf("Set security object stack slot to %d.\n", spOffset); + } + } + + void SetIsVarArg() + { + m_gcInfoEncoder->SetIsVarArg(); + if (m_doLogging) + { + printf("SetIsVarArg.\n"); + } + } + + void SetWantsReportOnlyLeaf() + { + m_gcInfoEncoder->SetWantsReportOnlyLeaf(); + if (m_doLogging) + { + printf("Set WantsReportOnlyLeaf.\n"); + } + } + + void SetSizeOfStackOutgoingAndScratchArea(UINT32 size) + { + m_gcInfoEncoder->SetSizeOfStackOutgoingAndScratchArea(size); + if (m_doLogging) + { + printf("Set Outgoing stack arg area size to %d.\n", size); + } + } + +}; + +ConfigDWORD GcInfoEncoderWithLogging::s_fJitGCInfoLogging; + +#define GCENCODER_WITH_LOGGING(withLog, realEncoder) \ + GcInfoEncoderWithLogging withLog ## Var(realEncoder, compiler->verbose || compiler->opts.dspGCtbls); \ + GcInfoEncoderWithLogging * withLog = &withLog ## Var; + +#else // DEBUG + +#define GCENCODER_WITH_LOGGING(withLog, realEncoder) \ + GcInfoEncoder * withLog = realEncoder; + +#endif // DEBUG + + +void GCInfo::gcInfoBlockHdrSave(GcInfoEncoder* gcInfoEncoder, + unsigned methodSize, + unsigned prologSize) +{ +#ifdef DEBUG + if (compiler->verbose) + printf("*************** In gcInfoBlockHdrSave()\n"); +#endif + + GCENCODER_WITH_LOGGING(gcInfoEncoderWithLog, gcInfoEncoder); + + // Can't create tables if we've not saved code. + + gcInfoEncoderWithLog->SetCodeLength(methodSize); + + if (compiler->isFramePointerUsed()) + { + gcInfoEncoderWithLog->SetStackBaseRegister(REG_FPBASE); + } + + + if (compiler->info.compIsVarArgs) + { + gcInfoEncoderWithLog->SetIsVarArg(); + } + // No equivalents. + // header->profCallbacks = compiler->info.compProfilerCallback; + // header->editNcontinue = compiler->opts.compDbgEnC; + // + if (compiler->lvaReportParamTypeArg()) + { + // The predicate above is true only if there is an extra generic context parameter, not for + // the case where the generic context is provided by "this." + assert(compiler->info.compTypeCtxtArg != BAD_VAR_NUM); + GENERIC_CONTEXTPARAM_TYPE ctxtParamType = GENERIC_CONTEXTPARAM_NONE; + switch (compiler->info.compMethodInfo->options & CORINFO_GENERICS_CTXT_MASK) + { + case CORINFO_GENERICS_CTXT_FROM_METHODDESC: + ctxtParamType = GENERIC_CONTEXTPARAM_MD; break; + case CORINFO_GENERICS_CTXT_FROM_METHODTABLE: + ctxtParamType = GENERIC_CONTEXTPARAM_MT; break; + + case CORINFO_GENERICS_CTXT_FROM_THIS: // See comment above. + default: + // If we have a generic context parameter, then we should have + // one of the two options flags handled above. + assert(false); + } + + gcInfoEncoderWithLog->SetGenericsInstContextStackSlot( + compiler->lvaToCallerSPRelativeOffset(compiler->lvaCachedGenericContextArgOffset(), + compiler->isFramePointerUsed()), + ctxtParamType); + } + // As discussed above, handle the case where the generics context is obtained via + // the method table of "this". + else if (compiler->lvaKeepAliveAndReportThis()) + { + assert(compiler->info.compThisArg != BAD_VAR_NUM); + gcInfoEncoderWithLog->SetGenericsInstContextStackSlot( + compiler->lvaToCallerSPRelativeOffset(compiler->lvaCachedGenericContextArgOffset(), + compiler->isFramePointerUsed()), + GENERIC_CONTEXTPARAM_THIS); + } + + if (compiler->getNeedsGSSecurityCookie()) + { + assert(compiler->lvaGSSecurityCookie != BAD_VAR_NUM); + + // The lv offset is FP-relative, and the using code expects caller-sp relative, so translate. + // The code offset ranges assume that the GS Cookie slot is initialized in the prolog, and is valid + // through the remainder of the method. We will not query for the GS Cookie while we're in an epilog, + // so the question of where in the epilog it becomes invalid is moot. + gcInfoEncoderWithLog->SetGSCookieStackSlot(compiler->lvaGetCallerSPRelativeOffset(compiler->lvaGSSecurityCookie), prologSize, methodSize); + } + else if (compiler->opts.compNeedSecurityCheck || compiler->lvaReportParamTypeArg() || compiler->lvaKeepAliveAndReportThis()) + { + gcInfoEncoderWithLog->SetPrologSize(prologSize); + } + + if (compiler->opts.compNeedSecurityCheck) + { + assert(compiler->lvaSecurityObject != BAD_VAR_NUM); + + // A VM requirement due to how the decoder works (it ignores partially interruptible frames when + // an exception has escaped, but the VM requires the security object to live on). + assert(compiler->codeGen->genInterruptible); + + // The lv offset is FP-relative, and the using code expects caller-sp relative, so translate. + // The normal GC lifetime reporting mechanisms will report a proper lifetime to the GC. + // The security subsystem can safely assume that anywhere it might walk the stack, it will be + // valid (null or a live GC ref). + gcInfoEncoderWithLog->SetSecurityObjectStackSlot(compiler->lvaGetCallerSPRelativeOffset(compiler->lvaSecurityObject)); + } + +#if FEATURE_EH_FUNCLETS + if (compiler->ehNeedsPSPSym()) + { + assert(compiler->lvaPSPSym != BAD_VAR_NUM); + +#ifdef _TARGET_AMD64_ + // The PSPSym is relative to InitialSP on X64 and CallerSP on other platforms. + gcInfoEncoderWithLog->SetPSPSymStackSlot(compiler->lvaGetInitialSPRelativeOffset(compiler->lvaPSPSym)); +#else // !_TARGET_AMD64_ + gcInfoEncoderWithLog->SetPSPSymStackSlot(compiler->lvaGetCallerSPRelativeOffset(compiler->lvaPSPSym)); +#endif // !_TARGET_AMD64_ + } + + if (compiler->ehAnyFunclets()) + { + // Set this to avoid double-reporting the parent frame (unlike JIT64) + gcInfoEncoderWithLog->SetWantsReportOnlyLeaf(); + } +#endif // FEATURE_EH_FUNCLETS + + //outgoing stack area size + gcInfoEncoderWithLog->SetSizeOfStackOutgoingAndScratchArea(compiler->lvaOutgoingArgSpaceSize); + +#if DISPLAY_SIZES + + if (compiler->codeGen->genInterruptible) + { + genMethodICnt++; + } + else + { + genMethodNCnt++; + } + +#endif // DISPLAY_SIZES + +} + +#ifdef DEBUG +#define Encoder GcInfoEncoderWithLogging +#else +#define Encoder GcInfoEncoder +#endif + +// Small helper class to handle the No-GC-Interrupt callbacks +// when reporting interruptible ranges. +// +// Encoder should be either GcInfoEncoder or GcInfoEncoderWithLogging +// +struct InterruptibleRangeReporter +{ + unsigned prevStart; + Encoder * gcInfoEncoderWithLog; + + InterruptibleRangeReporter(unsigned _prevStart, + Encoder * _gcInfo) : + prevStart(_prevStart), + gcInfoEncoderWithLog(_gcInfo) + { + } + + // This callback is called for each insGroup marked with + // IGF_NOGCINTERRUPT (currently just prologs and epilogs). + // Report everything between the previous region and the current + // region as interruptible. + + bool operator()(unsigned igFuncIdx, unsigned igOffs, unsigned igSize) + { + if (igOffs < prevStart) + { + // We're still in the main method prolog, which has already + // had it's interruptible range reported. + assert(igFuncIdx == 0); + assert(igOffs + igSize <= prevStart); + return true; + } + + assert(igOffs >= prevStart); + if (igOffs > prevStart) + { + gcInfoEncoderWithLog->DefineInterruptibleRange(prevStart, igOffs - prevStart); + } + prevStart = igOffs + igSize; + return true; + } +}; + + +void GCInfo::gcMakeRegPtrTable(GcInfoEncoder* gcInfoEncoder, + unsigned codeSize, + unsigned prologSize, + MakeRegPtrMode mode) +{ + GCENCODER_WITH_LOGGING(gcInfoEncoderWithLog, gcInfoEncoder); + + if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS) + { + m_regSlotMap = new (compiler->getAllocator()) RegSlotMap(compiler->getAllocator()); + m_stackSlotMap = new (compiler->getAllocator()) StackSlotMap(compiler->getAllocator()); + } + + /************************************************************************** + * + * Untracked ptr variables + * + ************************************************************************** + */ + + unsigned count = 0; + + int lastoffset = 0; + + /* Count&Write untracked locals and non-enregistered args */ + + unsigned varNum; + LclVarDsc* varDsc; + 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())) + { + // Do we have an argument or local variable? + if (!varDsc->lvIsParam) + { + // If is is pinned, it must be an untracked local. + assert(!varDsc->lvPinned || !varDsc->lvTracked); + + if (varDsc->lvTracked || !varDsc->lvOnFrame) + continue; + } + else + { + // Stack-passed arguments which are not enregistered + // are always reported in this "untracked stack + // pointers" section of the GC info even if lvTracked==true + + // Has this argument been fully enregistered? +#ifndef LEGACY_BACKEND + if (!varDsc->lvOnFrame) +#else // LEGACY_BACKEND + if (varDsc->lvRegister) +#endif // LEGACY_BACKEND + { + // If a CEE_JMP has been used, then we need to report all the arguments + // even if they are enregistered, since we will be using this value + // in a JMP call. Note that this is subtle as we require that + // argument offsets are always fixed up properly even if lvRegister + // is set. + if (!compiler->compJmpOpUsed) + continue; + } + else + { + if (!varDsc->lvOnFrame) + { + // If this non-enregistered pointer arg is never + // used, we don't need to report it. + assert(varDsc->lvRefCnt == 0); + continue; + } + else if (varDsc->lvIsRegArg && varDsc->lvTracked) + { + // If this register-passed arg is tracked, then + // it has been allocated space near the other + // pointer variables and we have accurate life- + // time info. It will be reported with + // gcVarPtrList in the "tracked-pointer" section. + continue; + } + } + } + + // If we haven't continued to the next variable, we should report this as an untracked local. + +#if DOUBLE_ALIGN + // For genDoubleAlign(), locals are addressed relative to ESP and + // arguments are addressed relative to EBP. + + if (genDoubleAlign() && varDsc->lvIsParam && !varDsc->lvIsRegArg) + offset += compiler->codeGen->genTotalFrameSize(); +#endif + GcSlotFlags flags = GC_SLOT_UNTRACKED; + + if (varDsc->TypeGet() == TYP_BYREF) + { + // Or in byref_OFFSET_FLAG for 'byref' pointer tracking + flags = (GcSlotFlags)(flags | GC_SLOT_INTERIOR); + } + + if (varDsc->lvPinned) + { + // Or in pinned_OFFSET_FLAG for 'pinned' pointer tracking + flags = (GcSlotFlags)(flags | GC_SLOT_PINNED); + } + GcStackSlotBase stackSlotBase = GC_SP_REL; + if (varDsc->lvFramePointerBased) stackSlotBase = GC_FRAMEREG_REL; + StackSlotIdKey sskey(varDsc->lvStkOffs, (stackSlotBase == GC_FRAMEREG_REL), flags); + GcSlotId varSlotId; + if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS) + { + if (!m_stackSlotMap->Lookup(sskey, &varSlotId)) + { + varSlotId = gcInfoEncoderWithLog->GetStackSlotId(varDsc->lvStkOffs, flags, stackSlotBase); + m_stackSlotMap->Set(sskey, varSlotId); + } + } + } + + if (varDsc->lvType == TYP_STRUCT && varDsc->lvOnFrame) + { + unsigned slots = compiler->lvaLclSize(varNum) / sizeof(void*); + BYTE * gcPtrs = compiler->lvaGetGcLayout(varNum); + + // walk each member of the array + for (unsigned i = 0; i < slots; i++) + { + if (gcPtrs[i] == TYPE_GC_NONE) // skip non-gc slots + continue; + + int offset = varDsc->lvStkOffs + i * sizeof(void*); +#if DOUBLE_ALIGN + // For genDoubleAlign(), locals are addressed relative to ESP and + // arguments are addressed relative to EBP. + + if (genDoubleAlign() && varDsc->lvIsParam && !varDsc->lvIsRegArg) + offset += compiler->codeGen->genTotalFrameSize(); +#endif + GcSlotFlags flags = GC_SLOT_UNTRACKED; + if (gcPtrs[i] == TYPE_GC_BYREF) + { + flags = (GcSlotFlags)(flags | GC_SLOT_INTERIOR); + } + + GcStackSlotBase stackSlotBase = GC_SP_REL; + if (varDsc->lvFramePointerBased) stackSlotBase = GC_FRAMEREG_REL; + StackSlotIdKey sskey(offset, (stackSlotBase == GC_FRAMEREG_REL), flags); + GcSlotId varSlotId; + if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS) + { + if (!m_stackSlotMap->Lookup(sskey, &varSlotId)) + { + varSlotId = gcInfoEncoderWithLog->GetStackSlotId(offset, flags, stackSlotBase); + m_stackSlotMap->Set(sskey, varSlotId); + } + } + } + } + } + + if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS) + { + // Count&Write spill temps that hold pointers. + + assert(compiler->tmpAllFree()); + for (TempDsc* tempItem = compiler->tmpListBeg(); + tempItem != nullptr; + tempItem = compiler->tmpListNxt(tempItem)) + { + if (varTypeIsGC(tempItem->tdTempType())) + { + int offset = tempItem->tdTempOffs(); + + GcSlotFlags flags = GC_SLOT_UNTRACKED; + if (tempItem->tdTempType() == TYP_BYREF) + { + flags = (GcSlotFlags)(flags | GC_SLOT_INTERIOR); + } + + GcStackSlotBase stackSlotBase = GC_SP_REL; + if (compiler->isFramePointerUsed()) stackSlotBase = GC_FRAMEREG_REL; + StackSlotIdKey sskey(offset, (stackSlotBase == GC_FRAMEREG_REL), flags); + GcSlotId varSlotId; + if (!m_stackSlotMap->Lookup(sskey, &varSlotId)) + { + varSlotId = gcInfoEncoderWithLog->GetStackSlotId(offset, flags, stackSlotBase); + m_stackSlotMap->Set(sskey, varSlotId); + } + } + } + + if (compiler->lvaKeepAliveAndReportThis()) + { + // We need to report the cached copy as an untracked pointer + assert(compiler->info.compThisArg != BAD_VAR_NUM); + assert(!compiler->lvaReportParamTypeArg()); + GcSlotFlags flags = GC_SLOT_UNTRACKED; + + if (compiler->lvaTable[compiler->info.compThisArg].TypeGet() == TYP_BYREF) + { + // Or in GC_SLOT_INTERIOR for 'byref' pointer tracking + flags = (GcSlotFlags)(flags | GC_SLOT_INTERIOR); + } + + GcStackSlotBase stackSlotBase = compiler->isFramePointerUsed() ? GC_FRAMEREG_REL : GC_SP_REL; + + gcInfoEncoderWithLog->GetStackSlotId(compiler->lvaCachedGenericContextArgOffset(), flags, stackSlotBase); + } + + } + + // Generate the table of tracked stack pointer variable lifetimes. + gcMakeVarPtrTable(gcInfoEncoder, mode); + + + /************************************************************************** + * + * Prepare to generate the pointer register/argument map + * + ************************************************************************** + */ + + if (compiler->codeGen->genInterruptible) + { + assert(compiler->genFullPtrRegMap); + + regMaskSmall ptrRegs = 0; + regPtrDsc* regStackArgFirst = NULL; + + // Walk the list of pointer register/argument entries. + + for (regPtrDsc* genRegPtrTemp = gcRegPtrList; + genRegPtrTemp != NULL; + genRegPtrTemp = genRegPtrTemp->rpdNext) + { + int nextOffset = genRegPtrTemp->rpdOffs; + + if (genRegPtrTemp->rpdArg) + { + if (genRegPtrTemp->rpdArgTypeGet() == rpdARG_KILL) + { + // Kill all arguments for a call + if ((mode == MAKE_REG_PTR_MODE_DO_WORK) && (regStackArgFirst != NULL)) + { + // Record any outgoing arguments as becoming dead + gcInfoRecordGCStackArgsDead(gcInfoEncoder, genRegPtrTemp->rpdOffs, regStackArgFirst, genRegPtrTemp); + } + regStackArgFirst = NULL; + } + else if (genRegPtrTemp->rpdGCtypeGet() != GCT_NONE) + { + if ( genRegPtrTemp->rpdArgTypeGet() == rpdARG_PUSH || + (genRegPtrTemp->rpdPtrArg!=0)) + { + bool isPop = genRegPtrTemp->rpdArgTypeGet() == rpdARG_POP; + assert(!isPop); + gcInfoRecordGCStackArgLive(gcInfoEncoder, mode, genRegPtrTemp); + if (regStackArgFirst == NULL) + regStackArgFirst = genRegPtrTemp; + } + else + { + // We know it's a POP. Sometimes we'll record a POP for a call, just to make sure + // the call site is recorded. + // This is just the negation of the condition: + assert(genRegPtrTemp->rpdArgTypeGet() == rpdARG_POP && genRegPtrTemp->rpdPtrArg == 0); + // This asserts that we only get here when we're recording a call site. + assert(genRegPtrTemp->rpdArg && genRegPtrTemp->rpdIsCallInstr()); + + // Kill all arguments for a call + if ((mode == MAKE_REG_PTR_MODE_DO_WORK) && (regStackArgFirst != NULL)) + { + // Record any outgoing arguments as becoming dead + gcInfoRecordGCStackArgsDead(gcInfoEncoder, genRegPtrTemp->rpdOffs, regStackArgFirst, genRegPtrTemp); + } + regStackArgFirst = NULL; + } + + } + } + else + { + // Record any registers that are becoming dead. + + regMaskSmall regMask = genRegPtrTemp->rpdCompiler.rpdDel & ptrRegs; + regMaskSmall byRefMask = 0; + if (genRegPtrTemp->rpdGCtypeGet() == GCT_BYREF) byRefMask = regMask; + gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, genRegPtrTemp->rpdOffs, regMask, GC_SLOT_DEAD, + byRefMask, &ptrRegs); + + // Record any registers that are becoming live. + regMask = genRegPtrTemp->rpdCompiler.rpdAdd & ~ptrRegs; + byRefMask = 0; + // As far as I (DLD, 2010) can tell, there's one GCtype for the entire genRegPtrTemp, so if + // it says byref then all the registers in "regMask" contain byrefs. + if (genRegPtrTemp->rpdGCtypeGet() == GCT_BYREF) byRefMask = regMask; + gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, genRegPtrTemp->rpdOffs, regMask, GC_SLOT_LIVE, + byRefMask, &ptrRegs); + } + } + + // Now we can declare the entire method body fully interruptible. + if (mode == MAKE_REG_PTR_MODE_DO_WORK) + { + assert(prologSize <= codeSize); + + // Now exempt any other region marked as IGF_NOGCINTERRUPT + // Currently just prologs and epilogs. + + InterruptibleRangeReporter reporter(prologSize, gcInfoEncoderWithLog); + compiler->getEmitter()->emitGenNoGCLst(reporter); + prologSize = reporter.prevStart; + + // Report any remainder + if (prologSize < codeSize) + { + gcInfoEncoderWithLog->DefineInterruptibleRange(prologSize, codeSize - prologSize); + } + + } + } + else if (compiler->isFramePointerUsed()) // genInterruptible is false, and we're using EBP as a frame pointer. + { + assert(compiler->genFullPtrRegMap == false); + + // Walk the list of pointer register/argument entries. + + // First count them. + unsigned numCallSites = 0; + + // Now we can allocate the information. + unsigned* pCallSites = NULL; + BYTE* pCallSiteSizes = NULL; + unsigned callSiteNum = 0; + + if (mode == MAKE_REG_PTR_MODE_DO_WORK) + { + if (gcCallDescList != NULL) + { + for (CallDsc* call = gcCallDescList; call != NULL; call = call->cdNext) numCallSites++; + pCallSites = new (compiler, CMK_GC) unsigned[numCallSites]; + pCallSiteSizes = new (compiler, CMK_GC) BYTE[numCallSites]; + } + } + + // Now consider every call. + for (CallDsc* call = gcCallDescList; call != NULL; call = call->cdNext) + { + if (mode == MAKE_REG_PTR_MODE_DO_WORK) + { + pCallSites[callSiteNum] = call->cdOffs - call->cdCallInstrSize; + pCallSiteSizes[callSiteNum] = call->cdCallInstrSize; + callSiteNum++; + } + + unsigned nextOffset; + + // Figure out the code offset of this entry. + nextOffset = call->cdOffs; + + // As far as I (DLD, 2010) can determine by asking around, the "call->u1.cdArgMask" + // and "cdArgCnt" cases are to handle x86 situations in which a call expression is nested as an + // argument to an outer call. The "natural" (evaluation-order-preserving) thing to do is to + // evaluate the outer call's arguments, pushing those that are not enregistered, until you + // encounter the nested call. These parts of the call description, then, describe the "pending" + // pushed arguments. This situation does not exist outside of x86, where we're going to use a + // fixed-size stack frame: in situations like this nested call, we would evaluate the pending + // arguments to temporaries, and only "push" them (really, write them to the outgoing argument section + // of the stack frame) when it's the outer call's "turn." So we can assert that these + // situations never occur. + assert(call->u1.cdArgMask == 0 && call->cdArgCnt == 0); + + // Other than that, we just have to deal with the regmasks. + regMaskSmall gcrefRegMask = call->cdGCrefRegs & RBM_CALLEE_SAVED; + regMaskSmall byrefRegMask = call->cdByrefRegs & RBM_CALLEE_SAVED; + + assert((gcrefRegMask & byrefRegMask) == 0); + + regMaskSmall regMask = gcrefRegMask | byrefRegMask; + + assert(call->cdOffs >= call->cdCallInstrSize); + // call->cdOffs is actually the offset of the instruction *following* the call, so subtract + // the call instruction size to get the offset of the actual call instruction... + unsigned callOffset = call->cdOffs - call->cdCallInstrSize; + // Record that these registers are live before the call... + gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, callOffset, regMask, GC_SLOT_LIVE, byrefRegMask, NULL); + // ...and dead after. + gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, call->cdOffs, regMask, GC_SLOT_DEAD, byrefRegMask, NULL); + } + // OK, define the call sites. + if (mode == MAKE_REG_PTR_MODE_DO_WORK) + { + gcInfoEncoderWithLog->DefineCallSites(pCallSites, pCallSiteSizes, numCallSites); + } + } + else // genInterruptible is false and we have an EBP-less frame + { + assert(compiler->genFullPtrRegMap); + + // Walk the list of pointer register/argument entries */ + // First count them. + unsigned numCallSites = 0; + + // Now we can allocate the information (if we're in the "DO_WORK" pass...) + unsigned* pCallSites = NULL; + BYTE* pCallSiteSizes = NULL; + unsigned callSiteNum = 0; + + if (mode == MAKE_REG_PTR_MODE_DO_WORK) + { + for (regPtrDsc* genRegPtrTemp = gcRegPtrList; + genRegPtrTemp != NULL; + genRegPtrTemp = genRegPtrTemp->rpdNext) + { + if (genRegPtrTemp->rpdArg && genRegPtrTemp->rpdIsCallInstr()) numCallSites++; + } + + if (numCallSites > 0) + { + pCallSites = new (compiler, CMK_GC) unsigned[numCallSites]; + pCallSiteSizes = new (compiler, CMK_GC) BYTE[numCallSites]; + } + } + + for (regPtrDsc* genRegPtrTemp = gcRegPtrList; + genRegPtrTemp != NULL; + genRegPtrTemp = genRegPtrTemp->rpdNext) + { + if (genRegPtrTemp->rpdArg) + { + // Is this a call site? + if (genRegPtrTemp->rpdIsCallInstr()) + { + // This is a true call site. + + regMaskSmall gcrefRegMask = genRegMaskFromCalleeSavedMask(genRegPtrTemp->rpdCallGCrefRegs); + + regMaskSmall byrefRegMask = genRegMaskFromCalleeSavedMask(genRegPtrTemp->rpdCallByrefRegs); + + assert((gcrefRegMask & byrefRegMask) == 0); + + regMaskSmall regMask = gcrefRegMask | byrefRegMask; + + // The "rpdOffs" is (apparently) the offset of the following instruction already. + // GcInfoEncoder wants the call instruction, so subtract the width of the call instruction. + assert(genRegPtrTemp->rpdOffs >= genRegPtrTemp->rpdCallInstrSize); + unsigned callOffset = genRegPtrTemp->rpdOffs - genRegPtrTemp->rpdCallInstrSize; + + // Tell the GCInfo encoder about these registers. We say that the registers become live + // before the call instruction, and dead after. + gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, callOffset, regMask, GC_SLOT_LIVE, byrefRegMask, NULL); + gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, genRegPtrTemp->rpdOffs, regMask, GC_SLOT_DEAD, byrefRegMask, NULL); + + // Also remember the call site. + if (mode == MAKE_REG_PTR_MODE_DO_WORK) + { + assert(pCallSites != NULL && pCallSiteSizes != NULL); + pCallSites[callSiteNum] = callOffset; + pCallSiteSizes[callSiteNum] = genRegPtrTemp->rpdCallInstrSize; + callSiteNum++; + } + } + else + { + // These are reporting outgoing stack arguments, but we don't need to report anything + // for partially interruptible + assert(genRegPtrTemp->rpdGCtypeGet() != GCT_NONE); + assert(genRegPtrTemp->rpdArgTypeGet() == rpdARG_PUSH); + } + } + } + // The routine is fully interruptible. + if (mode == MAKE_REG_PTR_MODE_DO_WORK) + { + gcInfoEncoderWithLog->DefineCallSites(pCallSites, pCallSiteSizes, numCallSites); + } + } +} + +void GCInfo::gcInfoRecordGCRegStateChange(GcInfoEncoder* gcInfoEncoder, + MakeRegPtrMode mode, + unsigned instrOffset, + regMaskSmall regMask, + GcSlotState newState, + regMaskSmall byRefMask, + regMaskSmall* pPtrRegs) +{ + // Precondition: byRefMask is a subset of regMask. + assert((byRefMask & ~regMask) == 0); + + GCENCODER_WITH_LOGGING(gcInfoEncoderWithLog, gcInfoEncoder); + + while (regMask) + { + // Get hold of the next register bit. + unsigned tmpMask = genFindLowestReg(regMask); assert(tmpMask); + + // Remember the new state of this register. + if (pPtrRegs != NULL) + { + if (newState == GC_SLOT_DEAD) + *pPtrRegs &= ~tmpMask; + else + *pPtrRegs |= tmpMask; + } + + // Figure out which register the next bit corresponds to. + regNumber regNum = genRegNumFromMask(tmpMask); + + /* Reserve SP future use */ + assert(regNum != REG_SPBASE); + + GcSlotFlags regFlags = GC_SLOT_BASE; + if ((tmpMask & byRefMask) != 0) + { + regFlags = (GcSlotFlags) (regFlags | GC_SLOT_INTERIOR); + } + + RegSlotIdKey rskey(regNum, regFlags); + GcSlotId regSlotId; + if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS) + { + if (!m_regSlotMap->Lookup(rskey, ®SlotId)) + { + regSlotId = gcInfoEncoderWithLog->GetRegisterSlotId(regNum, regFlags); + m_regSlotMap->Set(rskey, regSlotId); + } + } + else + { + BOOL b = m_regSlotMap->Lookup(rskey, ®SlotId); + assert(b); // Should have been added in the first pass. + gcInfoEncoderWithLog->SetSlotState(instrOffset, regSlotId, newState); + } + + // Turn the bit we've just generated off and continue. + regMask -= tmpMask; // EAX,ECX,EDX,EBX,---,EBP,ESI,EDI + } +} + +/************************************************************************** + * + * gcMakeVarPtrTable - Generate the table of tracked stack pointer + * variable lifetimes. + * + * In the first pass we'll allocate slot Ids + * In the second pass we actually generate the lifetimes. + * + ************************************************************************** + */ + +void GCInfo::gcMakeVarPtrTable(GcInfoEncoder* gcInfoEncoder, + MakeRegPtrMode mode) +{ + GCENCODER_WITH_LOGGING(gcInfoEncoderWithLog, gcInfoEncoder); + + // Make sure any flags we hide in the offset are in the bits guaranteed + // unused by alignment + C_ASSERT((OFFSET_MASK+1) <= sizeof(int)); + +#ifdef DEBUG + if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS) + { + // Tracked variables can't be pinned, and the encoding takes + // advantage of that by using the same bit for 'pinned' and 'this' + // Since we don't track 'this', we should never see either flag here. + // Check it now before we potentially add some pinned flags. + for (varPtrDsc* varTmp = gcVarPtrList; varTmp != NULL; varTmp = varTmp->vpdNext) + { + const unsigned flags = varTmp->vpdVarNum & OFFSET_MASK; + assert((flags & pinned_OFFSET_FLAG) == 0); + assert((flags & this_OFFSET_FLAG) == 0); + } + } +#endif // DEBUG + + // Only need to do this once, and only if we have EH. + if ((mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS) && compiler->ehAnyFunclets()) + { + gcMarkFilterVarsPinned(); + } + + for (varPtrDsc* varTmp = gcVarPtrList; varTmp != NULL; varTmp = varTmp->vpdNext) + { + C_ASSERT((OFFSET_MASK+1) <= sizeof(int)); + + // Get hold of the variable's stack offset. + + unsigned lowBits = varTmp->vpdVarNum & OFFSET_MASK; + + // For negative stack offsets we must reset the low bits + int varOffs = static_cast<int>(varTmp->vpdVarNum & ~OFFSET_MASK); + + // Compute the actual lifetime offsets. + unsigned begOffs = varTmp->vpdBegOfs; + unsigned endOffs = varTmp->vpdEndOfs; + + // Special case: skip any 0-length lifetimes. + if (endOffs == begOffs) + continue; + + GcSlotFlags flags = GC_SLOT_BASE; + if ((lowBits & byref_OFFSET_FLAG) != 0) + { + flags = (GcSlotFlags)(flags | GC_SLOT_INTERIOR); + } + if ((lowBits & pinned_OFFSET_FLAG) != 0) + { + flags = (GcSlotFlags)(flags | GC_SLOT_PINNED); + } + + GcStackSlotBase stackSlotBase = GC_SP_REL; + if (compiler->isFramePointerUsed()) + { + stackSlotBase = GC_FRAMEREG_REL; + } + StackSlotIdKey sskey(varOffs, (stackSlotBase == GC_FRAMEREG_REL), flags); + GcSlotId varSlotId; + if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS) + { + if (!m_stackSlotMap->Lookup(sskey, &varSlotId)) + { + varSlotId = gcInfoEncoderWithLog->GetStackSlotId(varOffs, flags, stackSlotBase); + m_stackSlotMap->Set(sskey, varSlotId); + } + } + else + { + BOOL b = m_stackSlotMap->Lookup(sskey, &varSlotId); + assert(b); // Should have been added in the first pass. + // Live from the beginning to the end. + gcInfoEncoderWithLog->SetSlotState(begOffs, varSlotId, GC_SLOT_LIVE); + gcInfoEncoderWithLog->SetSlotState(endOffs, varSlotId, GC_SLOT_DEAD); + } + } +} + + +// gcMarkFilterVarsPinned - Walk all lifetimes and make it so that anything +// live in a filter is marked as pinned (often by splitting the lifetime +// so that *only* the filter region is pinned). This should only be +// called once (after generating all lifetimes, but before slot ids are +// finalized. +// +// DevDiv 376329 - The VM has to double report filters and their parent frame +// because they occur during the 1st pass and the parent frame doesn't go dead +// until we start unwinding in the 2nd pass. +// +// Untracked locals will only be reported in non-filter funclets and the +// parent. +// Registers can't be double reported by 2 frames since they're different. +// That just leaves stack variables which might be double reported. +// +// Technically double reporting is only a problem when the GC has to relocate a +// reference. So we avoid that problem by marking all live tracked stack +// variables as pinned inside the filter. Thus if they are double reported, it +// won't be a problem since they won't be double relocated. +// +void GCInfo::gcMarkFilterVarsPinned() +{ + assert(compiler->ehAnyFunclets()); + const EHblkDsc * endHBtab = &(compiler->compHndBBtab[compiler->compHndBBtabCount]); + + for (EHblkDsc * HBtab = compiler->compHndBBtab; HBtab < endHBtab; HBtab++) + { + if (HBtab->HasFilter()) + { + const UNATIVE_OFFSET filterBeg = compiler->ehCodeOffset(HBtab->ebdFilter); + const UNATIVE_OFFSET filterEnd = compiler->ehCodeOffset(HBtab->ebdHndBeg); + + for (varPtrDsc* varTmp = gcVarPtrList; varTmp != NULL; varTmp = varTmp->vpdNext) + { + // Get hold of the variable's flags. + const unsigned lowBits = varTmp->vpdVarNum & OFFSET_MASK; + + // Compute the actual lifetime offsets. + const unsigned begOffs = varTmp->vpdBegOfs; + const unsigned endOffs = varTmp->vpdEndOfs; + + // Special case: skip any 0-length lifetimes. + if (endOffs == begOffs) + continue; + + // Skip lifetimes with no overlap with the filter + if ((endOffs <= filterBeg) || (begOffs >= filterEnd)) + continue; + + // Because there is no nesting within filters, nothing + // should be already pinned. + assert((lowBits & pinned_OFFSET_FLAG) == 0); + + if (begOffs < filterBeg) + { + if (endOffs > filterEnd) + { + // The variable lifetime is starts before AND ends after + // the filter, so we need to create 2 new lifetimes: + // (1) a pinned one for the filter + // (2) a regular one for after the filter + // and then adjust the original lifetime to end before + // the filter. + +#ifdef DEBUG + if (compiler->verbose) + { + printf("Splitting lifetime for filter: [%04X, %04X).\nOld: ", filterBeg, filterEnd); + gcDumpVarPtrDsc(varTmp); + } +#endif // DEBUG + + varPtrDsc * desc1 = new (compiler, CMK_GC) varPtrDsc; + desc1->vpdNext = gcVarPtrList; + desc1->vpdVarNum = varTmp->vpdVarNum | pinned_OFFSET_FLAG; + desc1->vpdBegOfs = filterBeg; + desc1->vpdEndOfs = filterEnd; + + varPtrDsc *desc2 = new (compiler, CMK_GC) varPtrDsc; + desc2->vpdNext = desc1; + desc2->vpdVarNum = varTmp->vpdVarNum; + desc2->vpdBegOfs = filterEnd; + desc2->vpdEndOfs = endOffs; + gcVarPtrList = desc2; + + varTmp->vpdEndOfs = filterBeg; +#ifdef DEBUG + if (compiler->verbose) + { + printf("New (1 of 3): "); + gcDumpVarPtrDsc(varTmp); + printf("New (2 of 3): "); + gcDumpVarPtrDsc(desc1); + printf("New (3 of 3): "); + gcDumpVarPtrDsc(desc2); + } +#endif // DEBUG + } + else + { + // The variable lifetime started before the filter and ends + // somewhere inside it, so we only create 1 new lifetime, + // and then adjust the original lifetime to end before + // the filter. + +#ifdef DEBUG + if (compiler->verbose) + { + printf("Splitting lifetime for filter.\nOld: "); + gcDumpVarPtrDsc(varTmp); + } +#endif // DEBUG + + varPtrDsc * desc = new (compiler, CMK_GC) varPtrDsc; + desc->vpdNext = gcVarPtrList; + desc->vpdVarNum = varTmp->vpdVarNum | pinned_OFFSET_FLAG; + desc->vpdBegOfs = filterBeg; + desc->vpdEndOfs = endOffs; + gcVarPtrList = desc; + + varTmp->vpdEndOfs = filterBeg; + +#ifdef DEBUG + if (compiler->verbose) + { + printf("New (1 of 2): "); + gcDumpVarPtrDsc(varTmp); + printf("New (2 of 2): "); + gcDumpVarPtrDsc(desc); + } +#endif // DEBUG + } + } + else + { + if (endOffs > filterEnd) + { + // The variable lifetime starts inside the filter and + // ends somewhere after it, so we create 1 new + // lifetime for the part inside the filter and adjust + // the start of the original lifetime to be the end + // of the filter + +#ifdef DEBUG + if (compiler->verbose) + { + printf("Splitting lifetime for filter.\nOld: "); + gcDumpVarPtrDsc(varTmp); + } +#endif // DEBUG + + varPtrDsc * desc = new (compiler, CMK_GC) varPtrDsc; + desc->vpdNext = gcVarPtrList; + desc->vpdVarNum = varTmp->vpdVarNum | pinned_OFFSET_FLAG; + desc->vpdBegOfs = begOffs; + desc->vpdEndOfs = filterEnd; + gcVarPtrList = desc; + + varTmp->vpdBegOfs = filterEnd; + +#ifdef DEBUG + if (compiler->verbose) + { + printf("New (1 of 2): "); + gcDumpVarPtrDsc(desc); + printf("New (2 of 2): "); + gcDumpVarPtrDsc(varTmp); + } +#endif // DEBUG + } + else + { + // The variable lifetime is completely within the filter, + // so just add the pinned flag. +#ifdef DEBUG + if (compiler->verbose) + { + printf("Pinning lifetime for filter.\nOld: "); + gcDumpVarPtrDsc(varTmp); + } +#endif // DEBUG + varTmp->vpdVarNum |= pinned_OFFSET_FLAG; +#ifdef DEBUG + if (compiler->verbose) + { + printf("New : "); + gcDumpVarPtrDsc(varTmp); + } +#endif // DEBUG + } + } + } + } // HasFilter + } // Foreach EH +} + +void GCInfo::gcInfoRecordGCStackArgLive(GcInfoEncoder* gcInfoEncoder, + MakeRegPtrMode mode, + regPtrDsc* genStackPtr) +{ + // On non-x86 platforms, don't have pointer argument push/pop/kill declarations. + // But we use the same mechanism to record writes into the outgoing argument space... + assert(genStackPtr->rpdGCtypeGet() != GCT_NONE); + assert(genStackPtr->rpdArg); + assert(genStackPtr->rpdArgTypeGet() == rpdARG_PUSH); + + // We only need to report these when we're doing fuly-interruptible + assert(compiler->codeGen->genInterruptible); + + GCENCODER_WITH_LOGGING(gcInfoEncoderWithLog, gcInfoEncoder); + + StackSlotIdKey sskey(genStackPtr->rpdPtrArg, FALSE, GcSlotFlags(genStackPtr->rpdGCtypeGet() == GCT_BYREF ? GC_SLOT_INTERIOR : GC_SLOT_BASE)); + GcSlotId varSlotId; + if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS) + { + if (!m_stackSlotMap->Lookup(sskey, &varSlotId)) + { + varSlotId = gcInfoEncoderWithLog->GetStackSlotId(sskey.m_offset, (GcSlotFlags)sskey.m_flags, GC_SP_REL); + m_stackSlotMap->Set(sskey, varSlotId); + } + } + else + { + BOOL b = m_stackSlotMap->Lookup(sskey, &varSlotId); + assert(b); // Should have been added in the first pass. + // Live until the call. + gcInfoEncoderWithLog->SetSlotState(genStackPtr->rpdOffs, varSlotId, GC_SLOT_LIVE); + } +} + +void GCInfo::gcInfoRecordGCStackArgsDead(GcInfoEncoder* gcInfoEncoder, + unsigned instrOffset, + regPtrDsc* genStackPtrFirst, + regPtrDsc* genStackPtrLast) +{ + // After a call all of the outgoing arguments are marked as dead. + // The calling loop keeps track of the first argument pushed for this call + // and passes it in as genStackPtrFirst. + // genStackPtrLast is the call. + // Re-walk that list and mark all outgoing arguments that we're marked as live + // earlier, as going dead after the call. + + // We only need to report these when we're doing fuly-interruptible + assert(compiler->codeGen->genInterruptible); + + GCENCODER_WITH_LOGGING(gcInfoEncoderWithLog, gcInfoEncoder); + + for (regPtrDsc* genRegPtrTemp = genStackPtrFirst; + genRegPtrTemp != genStackPtrLast; + genRegPtrTemp = genRegPtrTemp->rpdNext) + { + if (!genRegPtrTemp->rpdArg) + continue; + + assert(genRegPtrTemp->rpdGCtypeGet() != GCT_NONE); + assert(genRegPtrTemp->rpdArgTypeGet() == rpdARG_PUSH); + + StackSlotIdKey sskey(genRegPtrTemp->rpdPtrArg, FALSE, genRegPtrTemp->rpdGCtypeGet() == GCT_BYREF ? GC_SLOT_INTERIOR : GC_SLOT_BASE); + GcSlotId varSlotId; + BOOL b = m_stackSlotMap->Lookup(sskey, &varSlotId); + assert(b); // Should have been added in the first pass. + // Live until the call. + gcInfoEncoderWithLog->SetSlotState(instrOffset, varSlotId, GC_SLOT_DEAD); + } +} + +#undef GCENCODER_WITH_LOGGING + +#endif // !JIT32_GCENCODER + +/*****************************************************************************/ +/*****************************************************************************/ |