Skip to content

Commit

Permalink
Reuse x86 GC and unwinding code in NativeAOT (dotnet#99109)
Browse files Browse the repository at this point in the history
* Verbatim code extraction from eetwain.cpp into gc_unwind_x86.inl

* Reuse x86 GC and unwinding code in NativeAOT

Extracts x86 unwinding code and EnumGCRefs from eetwain.h/cpp into
gc_unwind_x86.h/inl files. The bulk of the code remains unchanged
with the methods UnwindStackFrameX86 and EnumGcRefsX86 extracted
from the original EECodeManager methods.

CoffNativeCodeManager includes all the x86 GC decoders and includes
gc_unwind_x86.inl for TARGET_X86. Missing methods are implemented
in terms of UnwindStackFrameX86, EnumGcRefsX86, and DecodeGCHdrInfo.

Funclet calling assembly helpers are fixed up to follow the same
pattern as on other platform.

* Remove assert
  • Loading branch information
filipnavara authored Mar 6, 2024
1 parent 1aa10a7 commit 91e6a2b
Show file tree
Hide file tree
Showing 20 changed files with 5,331 additions and 5,077 deletions.
2 changes: 2 additions & 0 deletions src/coreclr/inc/bitvector.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
#define UNDEF_ASSERTE
#endif

#ifndef FEATURE_NATIVEAOT
#define USE_BITVECTOR 1
#endif
#if USE_BITVECTOR

/* The bitvector class is meant to be a drop in replacement for an integer
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/inc/daccess.h
Original file line number Diff line number Diff line change
Expand Up @@ -2374,6 +2374,7 @@ typedef DPTR(int32_t) PTR_int32_t;
typedef DPTR(uint32_t) PTR_uint32_t;
typedef DPTR(uint64_t) PTR_uint64_t;
typedef DPTR(uintptr_t) PTR_uintptr_t;
typedef DPTR(TADDR) PTR_TADDR;

#ifndef NATIVEAOT
typedef ArrayDPTR(BYTE) PTR_BYTE;
Expand All @@ -2395,7 +2396,6 @@ typedef DPTR(ULONG64) PTR_ULONG64;
typedef DPTR(INT64) PTR_INT64;
typedef DPTR(UINT64) PTR_UINT64;
typedef DPTR(SIZE_T) PTR_SIZE_T;
typedef DPTR(TADDR) PTR_TADDR;
typedef DPTR(int) PTR_int;
typedef DPTR(BOOL) PTR_BOOL;
typedef DPTR(unsigned) PTR_unsigned;
Expand Down
121 changes: 4 additions & 117 deletions src/coreclr/inc/eetwain.h
Original file line number Diff line number Diff line change
Expand Up @@ -629,123 +629,7 @@ HRESULT FixContextForEnC(PCONTEXT pCtx,
};

#ifdef TARGET_X86
bool UnwindStackFrame(PREGDISPLAY pContext,
EECodeInfo *pCodeInfo,
unsigned flags,
CodeManState *pState);

size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken,
unsigned curOffset,
hdrInfo * infoPtr);
#endif

/*****************************************************************************
<TODO>ToDo: Do we want to include JIT/IL/target.h? </TODO>
*/

enum regNum
{
REGI_EAX, REGI_ECX, REGI_EDX, REGI_EBX,
REGI_ESP, REGI_EBP, REGI_ESI, REGI_EDI,
REGI_COUNT,
REGI_NA = REGI_COUNT
};

/*****************************************************************************
Register masks
*/

enum RegMask
{
RM_EAX = 0x01,
RM_ECX = 0x02,
RM_EDX = 0x04,
RM_EBX = 0x08,
RM_ESP = 0x10,
RM_EBP = 0x20,
RM_ESI = 0x40,
RM_EDI = 0x80,

RM_NONE = 0x00,
RM_ALL = (RM_EAX|RM_ECX|RM_EDX|RM_EBX|RM_ESP|RM_EBP|RM_ESI|RM_EDI),
RM_CALLEE_SAVED = (RM_EBP|RM_EBX|RM_ESI|RM_EDI),
RM_CALLEE_TRASHED = (RM_ALL & ~RM_CALLEE_SAVED),
};

/*****************************************************************************
*
* Helper to extract basic info from a method info block.
*/

struct hdrInfo
{
unsigned int methodSize; // native code bytes
unsigned int argSize; // in bytes
unsigned int stackSize; // including callee saved registers
unsigned int rawStkSize; // excluding callee saved registers
ReturnKind returnKind; // The ReturnKind for this method.

unsigned int prologSize;

// Size of the epilogs in the method.
// For methods which use CEE_JMP, some epilogs may end with a "ret" instruction
// and some may end with a "jmp". The epilogSize reported should be for the
// epilog with the smallest size.
unsigned int epilogSize;

unsigned char epilogCnt;
bool epilogEnd; // is the epilog at the end of the method

bool ebpFrame; // locals and arguments addressed relative to EBP
bool doubleAlign; // is the stack double-aligned? locals addressed relative to ESP, and arguments relative to EBP
bool interruptible; // intr. at all times (excluding prolog/epilog), not just call sites

bool handlers; // has callable handlers
bool localloc; // uses localloc
bool editNcontinue; // has been compiled in EnC mode
bool varargs; // is this a varargs routine
bool profCallbacks; // does the method have Enter-Leave callbacks
bool genericsContext;// has a reported generic context parameter
bool genericsContextIsMethodDesc;// reported generic context parameter is methoddesc
bool isSpeculativeStackWalk; // is the stackwalk seeded by an untrusted source (e.g., sampling profiler)?

// These always includes EBP for EBP-frames and double-aligned-frames
RegMask savedRegMask:8; // which callee-saved regs are saved on stack

// Count of the callee-saved registers, excluding the frame pointer.
// This does not include EBP for EBP-frames and double-aligned-frames.
unsigned int savedRegsCountExclFP;

unsigned int untrackedCnt;
unsigned int varPtrTableSize;
unsigned int argTabOffset; // INVALID_ARGTAB_OFFSET if argtab must be reached by stepping through ptr tables
unsigned int gsCookieOffset; // INVALID_GS_COOKIE_OFFSET if there is no GuardStack cookie

unsigned int syncStartOffset; // start/end code offset of the protected region in synchronized methods.
unsigned int syncEndOffset; // INVALID_SYNC_OFFSET if there not synchronized method
unsigned int syncEpilogStart; // The start of the epilog. Synchronized methods are guaranteed to have no more than one epilog.
unsigned int revPInvokeOffset; // INVALID_REV_PINVOKE_OFFSET if there is no Reverse PInvoke frame

enum { NOT_IN_PROLOG = -1, NOT_IN_EPILOG = -1 };

int prologOffs; // NOT_IN_PROLOG if not in prolog
int epilogOffs; // NOT_IN_EPILOG if not in epilog. It is never 0

//
// Results passed back from scanArgRegTable
//
regNum thisPtrResult; // register holding "this"
RegMask regMaskResult; // registers currently holding GC ptrs
RegMask iregMaskResult; // iptr qualifier for regMaskResult
unsigned argHnumResult;
PTR_CBYTE argTabResult; // Table of encoded offsets of pending ptr args
unsigned argTabBytes; // Number of bytes in argTabResult[]

// These next two are now large structs (i.e 132 bytes each)

ptrArgTP argMaskResult; // pending arguments mask
ptrArgTP iargMaskResult; // iptr qualifier for argMaskResult
};
#include "gc_unwind_x86.h"

/*****************************************************************************
How the stackwalkers buffer will be interpreted
Expand All @@ -756,6 +640,9 @@ struct CodeManStateBuf
DWORD hdrInfoSize;
hdrInfo hdrInfoBody;
};

#endif

//*****************************************************************************
#endif // _EETWAIN_H
//*****************************************************************************
138 changes: 138 additions & 0 deletions src/coreclr/inc/gc_unwind_x86.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#ifndef _UNWIND_X86_H
#define _UNWIND_X86_H

// This file is shared between CoreCLR and NativeAOT. Some of the differences are handled
// with the FEATURE_NATIVEAOT and FEATURE_EH_FUNCLETS defines. There are three main methods
// that are used by both runtimes - DecodeGCHdrInfo, UnwindStackFrameX86, and EnumGcRefsX86.
//
// The IN_EH_FUNCLETS and IN_EH_FUNCLETS_COMMA macros are used to specify some parameters
// for the above methods that are specific for a certain runtime or configuration.
#ifdef FEATURE_EH_FUNCLETS
#define IN_EH_FUNCLETS(a) a
#define IN_EH_FUNCLETS_COMMA(a) a,
#else
#define IN_EH_FUNCLETS(a)
#define IN_EH_FUNCLETS_COMMA(a)
#endif

enum regNum
{
REGI_EAX, REGI_ECX, REGI_EDX, REGI_EBX,
REGI_ESP, REGI_EBP, REGI_ESI, REGI_EDI,
REGI_COUNT,
REGI_NA = REGI_COUNT
};

/*****************************************************************************
Register masks
*/

enum RegMask
{
RM_EAX = 0x01,
RM_ECX = 0x02,
RM_EDX = 0x04,
RM_EBX = 0x08,
RM_ESP = 0x10,
RM_EBP = 0x20,
RM_ESI = 0x40,
RM_EDI = 0x80,

RM_NONE = 0x00,
RM_ALL = (RM_EAX|RM_ECX|RM_EDX|RM_EBX|RM_ESP|RM_EBP|RM_ESI|RM_EDI),
RM_CALLEE_SAVED = (RM_EBP|RM_EBX|RM_ESI|RM_EDI),
RM_CALLEE_TRASHED = (RM_ALL & ~RM_CALLEE_SAVED),
};

/*****************************************************************************
*
* Helper to extract basic info from a method info block.
*/

struct hdrInfo
{
unsigned int methodSize; // native code bytes
unsigned int argSize; // in bytes
unsigned int stackSize; // including callee saved registers
unsigned int rawStkSize; // excluding callee saved registers
ReturnKind returnKind; // The ReturnKind for this method.

unsigned int prologSize;

// Size of the epilogs in the method.
// For methods which use CEE_JMP, some epilogs may end with a "ret" instruction
// and some may end with a "jmp". The epilogSize reported should be for the
// epilog with the smallest size.
unsigned int epilogSize;

unsigned char epilogCnt;
bool epilogEnd; // is the epilog at the end of the method

bool ebpFrame; // locals and arguments addressed relative to EBP
bool doubleAlign; // is the stack double-aligned? locals addressed relative to ESP, and arguments relative to EBP
bool interruptible; // intr. at all times (excluding prolog/epilog), not just call sites

bool handlers; // has callable handlers
bool localloc; // uses localloc
bool editNcontinue; // has been compiled in EnC mode
bool varargs; // is this a varargs routine
bool profCallbacks; // does the method have Enter-Leave callbacks
bool genericsContext;// has a reported generic context parameter
bool genericsContextIsMethodDesc;// reported generic context parameter is methoddesc
bool isSpeculativeStackWalk; // is the stackwalk seeded by an untrusted source (e.g., sampling profiler)?

// These always includes EBP for EBP-frames and double-aligned-frames
RegMask savedRegMask:8; // which callee-saved regs are saved on stack

// Count of the callee-saved registers, excluding the frame pointer.
// This does not include EBP for EBP-frames and double-aligned-frames.
unsigned int savedRegsCountExclFP;

unsigned int untrackedCnt;
unsigned int varPtrTableSize;
unsigned int argTabOffset; // INVALID_ARGTAB_OFFSET if argtab must be reached by stepping through ptr tables
unsigned int gsCookieOffset; // INVALID_GS_COOKIE_OFFSET if there is no GuardStack cookie

unsigned int syncStartOffset; // start/end code offset of the protected region in synchronized methods.
unsigned int syncEndOffset; // INVALID_SYNC_OFFSET if there not synchronized method
unsigned int syncEpilogStart; // The start of the epilog. Synchronized methods are guaranteed to have no more than one epilog.
unsigned int revPInvokeOffset; // INVALID_REV_PINVOKE_OFFSET if there is no Reverse PInvoke frame

enum { NOT_IN_PROLOG = -1, NOT_IN_EPILOG = -1 };

int prologOffs; // NOT_IN_PROLOG if not in prolog
int epilogOffs; // NOT_IN_EPILOG if not in epilog. It is never 0

//
// Results passed back from scanArgRegTable
//
regNum thisPtrResult; // register holding "this"
RegMask regMaskResult; // registers currently holding GC ptrs
RegMask iregMaskResult; // iptr qualifier for regMaskResult
unsigned argHnumResult;
PTR_CBYTE argTabResult; // Table of encoded offsets of pending ptr args
unsigned argTabBytes; // Number of bytes in argTabResult[]

// These next two are now large structs (i.e 132 bytes each)

ptrArgTP argMaskResult; // pending arguments mask
ptrArgTP iargMaskResult; // iptr qualifier for argMaskResult
};

bool UnwindStackFrameX86(PREGDISPLAY pContext,
PTR_CBYTE methodStart,
DWORD curOffs,
hdrInfo * info,
PTR_CBYTE table,
IN_EH_FUNCLETS_COMMA(PTR_CBYTE funcletStart)
IN_EH_FUNCLETS_COMMA(bool isFunclet)
bool updateAllRegs);

size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken,
unsigned curOffset,
hdrInfo * infoPtr);

#endif // _UNWIND_X86_H
13 changes: 10 additions & 3 deletions src/coreclr/inc/gcinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
/*****************************************************************************/

#include "daccess.h"
#include "windef.h" // For BYTE

// Use the lower 2 bits of the offsets stored in the tables
// to encode properties
Expand Down Expand Up @@ -56,9 +55,17 @@ const unsigned this_OFFSET_FLAG = 0x2; // the offset is "this"
struct GCInfoToken
{
PTR_VOID Info;
UINT32 Version;
uint32_t Version;

static UINT32 ReadyToRunVersionToGcInfoVersion(UINT32 readyToRunMajorVersion)
#ifdef FEATURE_NATIVEAOT
GCInfoToken(PTR_VOID info)
{
Info = info;
Version = GCINFO_VERSION;
}
#endif

static uint32_t ReadyToRunVersionToGcInfoVersion(uint32_t readyToRunMajorVersion)
{
// GcInfo version is current from ReadyToRun version 2.0
return GCINFO_VERSION;
Expand Down
25 changes: 13 additions & 12 deletions src/coreclr/inc/gcinfodecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,17 @@

#ifdef FEATURE_NATIVEAOT

#include "gcinfo.h"

typedef ArrayDPTR(const uint8_t) PTR_CBYTE;
#ifdef TARGET_X86
// Bridge few additional pointer types used in x86 unwinding code
typedef DPTR(DWORD) PTR_DWORD;
typedef DPTR(WORD) PTR_WORD;
typedef DPTR(BYTE) PTR_BYTE;
typedef DPTR(signed char) PTR_SBYTE;
typedef DPTR(INT32) PTR_INT32;
#endif

#define LIMITED_METHOD_CONTRACT
#define SUPPORTS_DAC
Expand All @@ -50,22 +60,12 @@ typedef ArrayDPTR(const uint8_t) PTR_CBYTE;
#define SSIZE_T intptr_t
#define LPVOID void*

#define CHECK_APP_DOMAIN 0

typedef void * OBJECTREF;

#define GET_CALLER_SP(pREGDISPLAY) ((TADDR)0)

struct GCInfoToken
{
PTR_VOID Info;
UINT32 Version;

GCInfoToken(PTR_VOID info)
{
Info = info;
Version = 2;
}
};

#else // FEATURE_NATIVEAOT

// Stuff from cgencpu.h:
Expand Down Expand Up @@ -179,6 +179,7 @@ enum ICodeManagerFlags
ExecutionAborted = 0x0002, // execution of this function has been aborted
// (i.e. it will not continue execution at the
// current location)
AbortingCall = 0x0004, // The current call will never return
ParentOfFuncletStackFrame
= 0x0040, // A funclet for this frame was previously reported

Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/inc/regdisp.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ inline LPVOID GetRegdisplayFPAddress(REGDISPLAY *display) {
return (LPVOID)display->GetEbpLocation();
}

inline void SetRegdisplayPCTAddr(REGDISPLAY *display, TADDR addr)
{
display->PCTAddr = addr;
display->ControlPC = *PTR_PCODE(addr);
}


// This function tells us if the given stack pointer is in one of the frames of the functions called by the given frame
inline BOOL IsInCalleesFrames(REGDISPLAY *display, LPVOID stackPointer) {
Expand Down
Loading

0 comments on commit 91e6a2b

Please sign in to comment.