// // Copyright (c) Microsoft Corporation. All rights reserved. // /*******************************************************************/ /* WARNING */ /* This file should be identical in the Bartok and Singularity */ /* depots. Master copy resides in Bartok Depot. Changes should be */ /* made to Bartok Depot and propagated to Singularity Depot. */ /*******************************************************************/ namespace System.GCs { using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using Microsoft.Bartok.Options; using Microsoft.Bartok.Runtime; #if SINGULARITY_KERNEL #elif SINGULARITY_PROCESS using Microsoft.Singularity.V1.Services; #endif [NoCCtor] [AccessedByRuntime("referenced from halexn.cpp")] internal unsafe class CallStack { #if SINGULARITY // Static fields to be initialized to the addresses of labels // surrounding the linked-stack assembly code that we must skip. private static UIntPtr LinkStackFunctionsBegin; private static UIntPtr LinkStackFunctionsLimit; private static UIntPtr LinkStackBegin; private static UIntPtr LinkStackLimit; private static UIntPtr UnlinkStackBegin; private static UIntPtr UnlinkStackLimit; private static UIntPtr LinkStackStubsBegin; private static UIntPtr LinkStackStubsLimit; #endif internal static void Initialize() { #if SINGULARITY // Linked stacks pose a special problem for stack scanning. // The stack will contain "stuff" from the assembly code // implementations of linked stacks, and the stack walking // code needs to simply skip that "stuff". Since the code // is defined in a single .asm file, we use labels around // the code to help us identify the PC values that will need // special processing. Initialize the above static fields // to hold the addresses of the labels surrounding the code. fixed (byte *regionBegin = &Microsoft.Singularity.Memory.Stacks.LinkStackFunctionsBegin) { LinkStackFunctionsBegin = (UIntPtr) regionBegin; } fixed (byte *regionLimit = &Microsoft.Singularity.Memory.Stacks.LinkStackFunctionsLimit) { LinkStackFunctionsLimit = (UIntPtr) regionLimit; } fixed (byte *linkStackBegin = &Microsoft.Singularity.Memory.Stacks.LinkStackBegin) { LinkStackBegin = (UIntPtr) linkStackBegin; } fixed (byte *linkStackLimit = &Microsoft.Singularity.Memory.Stacks.LinkStackLimit) { LinkStackLimit = (UIntPtr) linkStackLimit; } fixed (byte *unlinkStackBegin = &Microsoft.Singularity.Memory.Stacks.UnlinkStackBegin) { UnlinkStackBegin = (UIntPtr) unlinkStackBegin; } fixed (byte *unlinkStackLimit = &Microsoft.Singularity.Memory.Stacks.UnlinkStackLimit) { UnlinkStackLimit = (UIntPtr) unlinkStackLimit; } fixed (byte *linkStackStubsBegin = &Microsoft.Singularity.Memory.Stacks.LinkStackStubsBegin) { LinkStackStubsBegin = (UIntPtr) linkStackStubsBegin; } fixed (byte *linkStackStubsLimit = &Microsoft.Singularity.Memory.Stacks.LinkStackStubsLimit) { LinkStackStubsLimit = (UIntPtr)linkStackStubsLimit; } #endif } [Mixin(typeof(Thread))] // Should be private, but mixin implementation will warn if visibility // does not match that of Thread. public sealed class CallStackThread : Object { [AccessedByRuntime("referenced in brtstack.asm")] internal UIntPtr asmStackBase; // Limit of thread's stack [AccessedByRuntime("referenced in brtstack.asm")] internal UIntPtr asmStackLimit; [AccessedByRuntime("referenced in brtforgc.asm")] internal unsafe TransitionRecord *asmStackMarker; } private static CallStackThread MixinThread(Thread t) { return (CallStackThread) (Object) t; } internal static UIntPtr StackBase(Thread t) { return MixinThread(t).asmStackBase; } internal static void SetStackBase(Thread t, UIntPtr value) { MixinThread(t).asmStackBase = value; } internal static UIntPtr StackLimit(Thread t) { return MixinThread(t).asmStackLimit; } internal static void SetStackLimit(Thread t, UIntPtr value) { MixinThread(t).asmStackLimit = value; } internal static TransitionRecord* StackMarker(Thread t) { return MixinThread(t).asmStackMarker; } [AccessedByRuntime("referenced from halforgc.asm")] [RequiredByBartok] internal unsafe struct TransitionRecord { [AccessedByRuntime("referenced from halforgc.asm")] internal TransitionRecord *oldTransitionRecord; [AccessedByRuntime("referenced from halforgc.asm")] internal UIntPtr callAddr; [AccessedByRuntime("referenced from halforgc.asm")] internal UIntPtr stackBottom; [AccessedByRuntime("referenced from halforgc.asm")] internal CalleeSaveRegisters calleeSaveRegisters; } internal static Thread threadBeingProcessed; [RequiredByBartok] private static int callSiteTableCount; [AccessedByRuntime("referenced from halexn.cpp")] private static UIntPtr *codeBaseStartTable; [RequiredByBartok] private static UIntPtr **returnAddressToCallSiteSetNumbers; [RequiredByBartok] private static int **callSiteSetCount; private static int CallSiteTableNumber(UIntPtr returnAddr) { UIntPtr address = returnAddr; for (int i = 0; i < callSiteTableCount; i++) { UIntPtr baseAddress = codeBaseStartTable[i]; if (address < baseAddress) { continue; } UIntPtr relativeAddress = address - baseAddress; UIntPtr *ptr = returnAddressToCallSiteSetNumbers[i]; int callSiteCount = *(callSiteSetCount[i]); if (relativeAddress >= ptr[0] && relativeAddress <= ptr[callSiteCount]) { return i; } } return -1; } private static int CallSiteSetNumber(UIntPtr returnAddr, int index) { UIntPtr codeBaseAddr = codeBaseStartTable[index]; UIntPtr relativeAddr = returnAddr - codeBaseAddr; UIntPtr *callSiteTable = returnAddressToCallSiteSetNumbers[index]; int callSiteCount = *(callSiteSetCount[index]); int left = 0; int right = callSiteCount; // Loop invariant: // callSiteTable[left] <= returnAddress < callSiteTable[right] while (left < right-1) { int mid = (left + right)/2; if (callSiteTable[mid] <= relativeAddr) { left = mid; } else { right = mid; } } return left; } [StructLayout(LayoutKind.Sequential)] private struct FullDescriptor { internal UIntPtr mask; internal int variableData; } private const uint ESCAPE32_TAG = 0x0; private const uint ESCAPE16_TAG = 0x1; private const uint ESCAPE8_TAG = 0x2; // Check whether the specified stack frame contains the // transition record: if so then we're done scanning this // segment of the stack. NB: the caller must ensure that the // framePointer has been recomputed in a // framePointerOmitted-method. private static bool FrameContainsTransitionRecord(UIntPtr *framePointer, UIntPtr *stackPointer, TransitionRecord *stopMarker) { bool result = false; if ((framePointer >= stopMarker) && (stackPointer < stopMarker)) { result = true; } return result; } private static bool WalkStackFrame(ref UIntPtr *framePointer, ref UIntPtr *stackPointer, ref CalleeSaveLocations calleeSaves, ref UIntPtr returnAddr, NonNullReferenceVisitor threadReferenceVisitor, NonNullReferenceVisitor pinnedReferenceVisitor, CompressedFrameDescriptor smallFrameDescriptor, TransitionRecord *stopMarker, Thread thread) // BUGBUG: Remove thread argument when ThreadContext is on stack! { FrameDescriptor frameDescriptor = new FrameDescriptor(smallFrameDescriptor); if (frameDescriptor.isFramePointerOmitted) { framePointer = stackPointer + frameDescriptor.frameSize; } if (FrameContainsTransitionRecord(framePointer, stackPointer, stopMarker)) { return true; // true: done } switch (frameDescriptor.argumentTag) { case FrameDescriptor.ESCAPE32_TAG: { int *table = (int *) frameDescriptor.argumentMaskOrTable; int count = table[0]; int pinnedCount = table[1]; int *offsets = &table[2]; if (threadReferenceVisitor != null) { for (int i = 0; i < count; i++) { UIntPtr *loc = framePointer + offsets[i]; if (*loc != UIntPtr.Zero) { // BUGBUG: threadReferenceVisitor.Visit(loc); VisitIfNotContext(thread, threadReferenceVisitor, loc); } } } if (frameDescriptor.hasPinnedVariables && pinnedReferenceVisitor != null) { offsets = &offsets[count]; for (int i = 0; i < pinnedCount; i++) { UIntPtr *loc = framePointer + offsets[i]; if (*loc != UIntPtr.Zero) { pinnedReferenceVisitor.Visit(loc); } } } break; } case FrameDescriptor.ESCAPE16_TAG: { short *table = (short *) frameDescriptor.argumentMaskOrTable; int count = table[0]; int pinnedCount = table[1]; short *offsets = &table[2]; if (threadReferenceVisitor != null) { for (int i = 0; i < count; i++) { UIntPtr *loc = framePointer + offsets[i]; if (*loc != UIntPtr.Zero) { // BUGBUG: threadReferenceVisitor.Visit(loc); VisitIfNotContext(thread, threadReferenceVisitor, loc); } } } if (frameDescriptor.hasPinnedVariables && pinnedReferenceVisitor != null) { offsets = &offsets[count]; for (int i = 0; i < pinnedCount; i++) { UIntPtr *loc = framePointer + offsets[i]; if (*loc != UIntPtr.Zero) { pinnedReferenceVisitor.Visit(loc); } } } break; } case FrameDescriptor.ESCAPE8_TAG: { sbyte *table = (sbyte *) frameDescriptor.argumentMaskOrTable; int count = table[0]; int pinnedCount = table[1]; sbyte *offsets = &table[2]; if (threadReferenceVisitor != null) { for (int i = 0; i < count; i++) { UIntPtr *loc = framePointer + offsets[i]; if (*loc != UIntPtr.Zero) { // BUGBUG: threadReferenceVisitor.Visit(loc); VisitIfNotContext(thread, threadReferenceVisitor, loc); } } } if (frameDescriptor.hasPinnedVariables && pinnedReferenceVisitor != null) { offsets = &offsets[count]; for (int i = 0; i < pinnedCount; i++) { UIntPtr *loc = framePointer + offsets[i]; if (*loc != UIntPtr.Zero) { pinnedReferenceVisitor.Visit(loc); } } } break; } case FrameDescriptor.COMPRESSED_MASK_TAG: { // Process the arguments int inBetweenSlotsAbove = frameDescriptor.inBetweenSlotsAbove; uint stackArgSize = frameDescriptor.argumentCount; UIntPtr mask = frameDescriptor.argumentMaskOrTable; if (threadReferenceVisitor != null && stackArgSize > 0) { UIntPtr *argumentPointer = framePointer + stackArgSize - 1 + inBetweenSlotsAbove; for (int i = 0; mask != 0 && i < stackArgSize; i++) { if ((mask & 0x1) != 0 && *argumentPointer != UIntPtr.Zero) { // BUGBUG: threadReferenceVisitor.Visit(p); VisitIfNotContext(thread, threadReferenceVisitor, argumentPointer); } mask >>= 1; argumentPointer--; } } else { for (int i = 0; mask != 0 && i < stackArgSize; i++) { mask >>= 1; } } // Process the local variables if (threadReferenceVisitor != null) { int transitionRecordSize = frameDescriptor.hasTransitionRecord ? sizeof(TransitionRecord) / sizeof (UIntPtr) : 0; int registerCount = CountBits(frameDescriptor.calleeSaveMask); int inBetweenSlotsBelow = frameDescriptor.inBetweenSlotsBelow; UIntPtr *variablePtr = framePointer - inBetweenSlotsBelow - registerCount - transitionRecordSize - 1; // -1 because FP is pointing in "above" region. while (mask != UIntPtr.Zero) { if ((mask & 0x1) != 0 && *variablePtr != UIntPtr.Zero) { // BUGBUG: threadReferenceVisitor.Visit(p); VisitIfNotContext(thread, threadReferenceVisitor, variablePtr); } mask >>= 1; variablePtr--; } } break; } default: { VTable.NotReached("FrameDescriptor mask switch failed"); break; } } if (frameDescriptor.calleeSaveValueMask != 0 && threadReferenceVisitor != null) { calleeSaves.ScanLiveRegs(frameDescriptor.calleeSaveValueMask, threadReferenceVisitor); } #if X86 || AMD64 || ISA_IX86 || ISA_IX64 UIntPtr nextReturnAddr; if (frameDescriptor.isFramePointerOmitted) { nextReturnAddr = *framePointer; stackPointer = framePointer + 1; } else { nextReturnAddr = *(framePointer + 1); stackPointer = framePointer + 2; } #elif ARM || ISA_ARM // CompressedFrameDescriptor is the target specific portion. // This will be refactored to FrameDescriptor via partial classes. UIntPtr nextReturnAddr = CompressedFrameDescriptor.NextCallSite(frameDescriptor.isFramePointerOmitted, framePointer, out stackPointer); #else #error Unknown Architecture #endif // In Singularity, the final return address of a thread is zero if (nextReturnAddr != UIntPtr.Zero) { calleeSaves.PopFrame(framePointer, frameDescriptor.calleeSaveMask, frameDescriptor.isFramePointerOmitted, frameDescriptor.hasTransitionRecord); } else { calleeSaves.ClearFrame(frameDescriptor.calleeSaveMask, frameDescriptor.isFramePointerOmitted); } UIntPtr *calcedfp = (UIntPtr *) calleeSaves.GetFramePointer(); if (calcedfp != null) { framePointer = calcedfp; } returnAddr = nextReturnAddr; return false; // false: not done scanning: proceed to next frame } private static int CountBits(UIntPtr bitMask) { int result = 0; while (bitMask != UIntPtr.Zero) { if ((bitMask & (UIntPtr) 0x1) != UIntPtr.Zero) { result++; } bitMask >>= 1; } return result; } #if SINGULARITY_PROCESS // TODO: BUGBUG: Get rid of this when ThreadContext is on stack! [Inline] private static void VisitIfNotContext(Thread thread, NonNullReferenceVisitor threadReferenceVisitor, UIntPtr *p) { if (*p != (UIntPtr) thread.context && (int *) *p != &thread.context->gcStates) { threadReferenceVisitor.Visit(p); } } #else [Inline] private static void VisitIfNotContext(Thread thread, NonNullReferenceVisitor threadReferenceVisitor, UIntPtr *p) { threadReferenceVisitor.Visit(p); } #endif [RequiredByBartok] private static ushort **callSetSiteNumberToIndex; [RequiredByBartok] private static CompressedFrameDescriptor **activationDescriptorTable; [PreInitRefCounts] internal static int ScanStacks(NonNullReferenceVisitor VisitThreadReference, NonNullReferenceVisitor VisitPinnedReference) { int limit = Thread.threadTable.Length; int countThreads = 0; for (int i = 0; i < limit; i++) { Thread t = Thread.threadTable[i]; if (t != null) { CollectorStatistics.Event(GCEvent.StackScanStart, i); ScanStack(t, VisitThreadReference, VisitPinnedReference); CollectorStatistics.Event(GCEvent.StackScanComplete, i); countThreads++; } } return countThreads; } [PreInitRefCounts] internal static uint ScanStack(Thread thread, NonNullReferenceVisitor VisitThreadReference, NonNullReferenceVisitor VisitPinnedReference) { Trace.Log(Trace.Area.Stack, "Scanning stack for thread {0:x}", __arglist(thread.threadIndex)); uint numStackFrames = 0; threadBeingProcessed = thread; CalleeSaveLocations calleeSaves = new CalleeSaveLocations(); #if SINGULARITY_KERNEL TransitionRecord *marker = (TransitionRecord *) thread.context.stackMarkers; #elif SINGULARITY_PROCESS TransitionRecord *marker = null; if (thread.context != null) { marker = (TransitionRecord *) thread.context->stackMarkers; } #else TransitionRecord *marker = (TransitionRecord *) MixinThread(thread).asmStackMarker; #endif while (marker != null) { Trace.Log(Trace.Area.Stack, "Transition record: old={0:x}, callAddr={1:x}, stackBottom={2:x}", __arglist(marker->oldTransitionRecord, marker->callAddr, marker->stackBottom)); marker->calleeSaveRegisters.DebugTrace(); TransitionRecord *stopMarker = marker->oldTransitionRecord; UIntPtr returnAddr = marker->callAddr; UIntPtr *fp = (UIntPtr *) marker->calleeSaveRegisters.GetFramePointer(); UIntPtr *sp = (UIntPtr *) marker->stackBottom; calleeSaves.SetCalleeSaves(&marker->calleeSaveRegisters); numStackFrames += ScanStackSegment(ref calleeSaves, returnAddr, fp, sp, stopMarker, VisitThreadReference, VisitPinnedReference, thread); marker = marker->oldTransitionRecord; } threadBeingProcessed = null; return numStackFrames; } [PreInitRefCounts] private static uint ScanStackSegment(ref CalleeSaveLocations calleeSaves, UIntPtr returnAddr, UIntPtr *fp, UIntPtr *sp, TransitionRecord *stopMarker, NonNullReferenceVisitor VisitThreadReference, NonNullReferenceVisitor VisitPinnedReference, Thread thread) // BUGBUG: Remove thread argument when ThreadContext is on stack! { uint numStackFrames = 0; while (true) { #if SINGULARITY if (returnAddr >= LinkStackFunctionsBegin && returnAddr <= LinkStackFunctionsLimit) { if (returnAddr >= LinkStackBegin && returnAddr <= LinkStackLimit) { returnAddr = SkipLinkStackFrame(ref fp, ref sp); } else if (returnAddr >= UnlinkStackBegin && returnAddr <= UnlinkStackLimit) { returnAddr = SkipUnlinkStackFrame(ref fp, ref sp); } else if (returnAddr >= LinkStackStubsBegin && returnAddr <= LinkStackStubsLimit) { returnAddr = SkipLinkStackStubFrame(ref fp, ref sp); } else { VTable.NotReached("Unexpected link stack function"); } } #endif // Exit loop if we have reached the of the stack segment if (fp >= stopMarker && sp < stopMarker) { break; } int tableIndex = CallSiteTableNumber(returnAddr); if (tableIndex < 0) { break; } int callSiteSet = CallSiteSetNumber(returnAddr, tableIndex); if (callSiteSet < 0) { break; } ushort *callSiteToIndexTable = callSetSiteNumberToIndex[tableIndex]; int activationIndex = (int) callSiteToIndexTable[callSiteSet]; VTable.Assert(activationIndex >= 0); CompressedFrameDescriptor *descriptorTable = activationDescriptorTable[tableIndex]; CompressedFrameDescriptor frameDescriptor = descriptorTable[activationIndex]; bool done = WalkStackFrame(ref fp, ref sp, ref calleeSaves, ref returnAddr, VisitThreadReference, VisitPinnedReference, frameDescriptor, stopMarker, thread); if (done) { break; } numStackFrames++; } calleeSaves.ClearCalleeSaves(); return numStackFrames; } #if SINGULARITY private static UIntPtr SkipLinkStackFrame(ref UIntPtr *framePointer, ref UIntPtr *stackPointer) { // The stack pointer and the frame pointer are in the same // stack segment. We still need to skip over the frame // set up for the function that called LinkStack hasn't been // executed yet (the GC interaction occurred during allocation // of the new stack segment). stackPointer = framePointer + 2; framePointer = (UIntPtr*) *framePointer; return *(stackPointer - 1); } private static UIntPtr SkipUnlinkStackFrame(ref UIntPtr *framePointer, ref UIntPtr *stackPointer) { // The frame pointer is already set up. The stack pointer // needs to be adjusted for the two PC values (LinkStackXX and // UnlinkStack) and the two spilled values pushed onto the stack. stackPointer = stackPointer + 4; return *(stackPointer - 1); } private static UIntPtr SkipLinkStackStubFrame(ref UIntPtr *framePointer, ref UIntPtr *stackPointer) { // 'framePointer' points to the ebp in the old frame // since at the end of WalkStackFrame, it sets return addr // and pops up a frame. However, since we are using linked // stacks, "pop up a frame" didn't actually set the frame // to the caller's frame, it just goes from the new frame to the // old frame, therefore, we need to get return addr and pop // a frame again. stackPointer = framePointer + 2; framePointer = (UIntPtr*) *framePointer; return *(stackPointer - 1); } #endif } }