// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more 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 "gcinfotypes.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) */ CLANG_FORMAT_COMMENT_ANCHOR; #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(prologSize)); header->prologSize = static_cast(prologSize); assert(FitsIn(epilogSize)); header->epilogSize = static_cast(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()) { assert(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.rsCalleeRegArgCount * sizeof(void*))) / sizeof(void*); assert(argCount <= MAX_USHORT_SIZE_T); header->argCount = static_cast(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(*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? */ #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 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); // This assert is currently a known issue for X86-RyuJit 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; } } } // A struct will have gcSlots only if it is at least TARGET_POINTER_SIZE. if (varDsc->lvType == TYP_STRUCT && varDsc->lvOnFrame && (varDsc->lvExactSize >= TARGET_POINTER_SIZE)) { 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(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(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(*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(GCINFO_VERSION); gcDump.gcPrintf = gcDump_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(GCINFO_VERSION); gcDump.gcPrintf = gcDump_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(GCINFO_VERSION); gcDump.gcPrintf = gcDump_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; template class SimplerHashTable; #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; public: GcInfoEncoderWithLogging(GcInfoEncoder* gcInfoEncoder, bool verbose) : m_gcInfoEncoder(gcInfoEncoder), m_doLogging(verbose || JitConfig.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 SetReturnKind(ReturnKind returnKind) { m_gcInfoEncoder->SetReturnKind(returnKind); if (m_doLogging) { printf("Set ReturnKind to %s.\n", ReturnKindToString(returnKind)); } } 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); } } }; #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 ReturnKind GCTypeToReturnKind(CorInfoGCType gcType) { switch (gcType) { case TYPE_GC_NONE: return RT_Scalar; case TYPE_GC_REF: return RT_Object; case TYPE_GC_BYREF: return RT_ByRef; default: _ASSERTE(!"TYP_GC_OTHER is unexpected"); return RT_Illegal; } } 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); ReturnKind returnKind = RT_Illegal; switch (compiler->info.compRetType) { case TYP_REF: case TYP_ARRAY: returnKind = RT_Object; break; case TYP_BYREF: returnKind = RT_ByRef; break; case TYP_STRUCT: { CORINFO_CLASS_HANDLE structType = compiler->info.compMethodInfo->args.retTypeClass; var_types retType = compiler->getReturnTypeForStruct(structType); switch (retType) { case TYP_ARRAY: _ASSERTE(false && "TYP_ARRAY unexpected from getReturnTypeForStruct()"); case TYP_REF: returnKind = RT_Object; break; case TYP_BYREF: returnKind = RT_ByRef; break; case TYP_STRUCT: if (compiler->IsHfa(structType)) { returnKind = RT_Scalar; } else { // Multi-reg return BYTE gcPtrs[2] = { TYPE_GC_NONE, TYPE_GC_NONE }; compiler->info.compCompHnd->getClassGClayout(structType, gcPtrs); ReturnKind first = GCTypeToReturnKind((CorInfoGCType)gcPtrs[0]); ReturnKind second = GCTypeToReturnKind((CorInfoGCType)gcPtrs[1]); returnKind = GetStructReturnKind(first, second); } break; default: returnKind = RT_Scalar; break; } break; } default: returnKind = RT_Scalar; } _ASSERTE(returnKind != RT_Illegal); gcInfoEncoderWithLog->SetReturnKind(returnKind); 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? CLANG_FORMAT_COMMENT_ANCHOR; #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. CLANG_FORMAT_COMMENT_ANCHOR; #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 this is a TYP_STRUCT, handle its GC pointers. // Note that the enregisterable struct types cannot have GC pointers in them. if ((varDsc->lvType == TYP_STRUCT) && varDsc->lvOnFrame && (varDsc->lvExactSize >= TARGET_POINTER_SIZE)) { 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 = nullptr; // Walk the list of pointer register/argument entries. for (regPtrDsc* genRegPtrTemp = gcRegPtrList; genRegPtrTemp != nullptr; 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 != nullptr)) { // Record any outgoing arguments as becoming dead gcInfoRecordGCStackArgsDead(gcInfoEncoder, genRegPtrTemp->rpdOffs, regStackArgFirst, genRegPtrTemp); } regStackArgFirst = nullptr; } 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 == nullptr) { 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 != nullptr)) { // Record any outgoing arguments as becoming dead gcInfoRecordGCStackArgsDead(gcInfoEncoder, genRegPtrTemp->rpdOffs, regStackArgFirst, genRegPtrTemp); } regStackArgFirst = nullptr; } } } 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 = nullptr; BYTE* pCallSiteSizes = nullptr; unsigned callSiteNum = 0; if (mode == MAKE_REG_PTR_MODE_DO_WORK) { if (gcCallDescList != nullptr) { for (CallDsc* call = gcCallDescList; call != nullptr; 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 != nullptr; 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, nullptr); // ...and dead after. gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, call->cdOffs, regMask, GC_SLOT_DEAD, byrefRegMask, nullptr); } // 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 = nullptr; BYTE* pCallSiteSizes = nullptr; unsigned callSiteNum = 0; if (mode == MAKE_REG_PTR_MODE_DO_WORK) { for (regPtrDsc* genRegPtrTemp = gcRegPtrList; genRegPtrTemp != nullptr; 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 != nullptr; 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, nullptr); gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, genRegPtrTemp->rpdOffs, regMask, GC_SLOT_DEAD, byrefRegMask, nullptr); // Also remember the call site. if (mode == MAKE_REG_PTR_MODE_DO_WORK) { assert(pCallSites != nullptr && pCallSiteSizes != nullptr); 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. regMaskTP tmpMask = genFindLowestReg(regMask); assert(tmpMask); // Remember the new state of this register. if (pPtrRegs != nullptr) { 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 != nullptr; 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 != nullptr; 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(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 != nullptr; 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. CLANG_FORMAT_COMMENT_ANCHOR; #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. CLANG_FORMAT_COMMENT_ANCHOR; #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 CLANG_FORMAT_COMMENT_ANCHOR; #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. CLANG_FORMAT_COMMENT_ANCHOR; #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 /*****************************************************************************/ /*****************************************************************************/