singrdk/base/Imported/Bartok/runtime/shared/GCs/CallStack.cs

623 lines
27 KiB
C#

//
// 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
}
}