Skip to content

Abolish PSPSym from ABI #114630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 52 commits into from
Apr 21, 2025
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
e69f16f
Update VM and NativeAOT to use PSPSym-less convention for calling fun…
filipnavara Apr 14, 2025
4319268
Do not emit and use PSPSym in JIT
filipnavara Apr 14, 2025
01986aa
Update documentation
filipnavara Apr 14, 2025
b1987e2
Fix build
filipnavara Apr 14, 2025
d86572f
Fix ARM64 build
filipnavara Apr 14, 2025
d248211
Fix RISC-V and LA64 build
filipnavara Apr 14, 2025
ed12de5
Remove unused fiFunction_CallerSP_to_FP_delta
filipnavara Apr 14, 2025
24f60d7
Remove unused fiFunction_InitialSP_to_FP_delta
filipnavara Apr 14, 2025
a06a6a6
Apply JIT diff
filipnavara Apr 14, 2025
f6ff6e1
Fix typo
filipnavara Apr 14, 2025
951b7eb
Remove unused fiFunctionCallerSPtoFPdelta
filipnavara Apr 14, 2025
9c697df
sp -> rsp
filipnavara Apr 14, 2025
fdee150
Save establisher frame in funclet frame, use it in GetExactGenericsToken
filipnavara Apr 15, 2025
7085e5f
Try to restore the former ABI
filipnavara Apr 15, 2025
8365e15
Fix build
filipnavara Apr 15, 2025
d05eeca
WIP
filipnavara Apr 15, 2025
bb43879
Remove mentions of USE_FUNCLET_CALL_HELPER from documentation
filipnavara Apr 15, 2025
43c8dc8
Build fixes
filipnavara Apr 15, 2025
42815c4
Apply JIT format
filipnavara Apr 15, 2025
4ceea64
WASM build fix
filipnavara Apr 15, 2025
21b9077
Fix linux-x64 CallEHFunclet
filipnavara Apr 15, 2025
41e98a4
WASM build fix
filipnavara Apr 15, 2025
78bc386
WIP
filipnavara Apr 15, 2025
4c843d8
Add comment
filipnavara Apr 15, 2025
6db4625
Cleanup
filipnavara Apr 15, 2025
f429aa6
Reencode generics instance context stack slot as SP/FP based
filipnavara Apr 16, 2025
1679464
Remove establisher frame pointer again
filipnavara Apr 16, 2025
e02b5c1
Use symbolic constants
filipnavara Apr 16, 2025
0e93d0d
Cleanup the code in gcencode.cpp
filipnavara Apr 16, 2025
e3b98b6
Fix NativeAOT x64
filipnavara Apr 16, 2025
c315047
Apply JIT format
filipnavara Apr 16, 2025
792d01d
Fix CallEHFilterFunclet on linux-x64
filipnavara Apr 16, 2025
4317b07
Build fix
filipnavara Apr 16, 2025
b7b6b08
Attempt to fix unwinding info for ARM64
filipnavara Apr 16, 2025
f630b13
Reuse the PROLOG_SAVE_REG_PAIR_NO_FP_INDEXED macro from NativeAOT
filipnavara Apr 16, 2025
7be6fff
Fix RV64 and LA64 CallEHFilterFunclet unwind info
filipnavara Apr 16, 2025
c97c7cf
Light version of CallEH[Filter]Funclet for x64 without restoring CONT…
filipnavara Apr 16, 2025
c78ea88
Remove obsolete docs and comments
filipnavara Apr 16, 2025
67ef94a
Quick test for linux-x64 test failure
filipnavara Apr 16, 2025
2c87f23
Fix hex notation
filipnavara Apr 16, 2025
298b850
Reduce the scope of throw-in-filter workaround on linux-x64
filipnavara Apr 17, 2025
10ffb86
Fix stack trashing on linux-x64
filipnavara Apr 17, 2025
414e311
Update documentation
filipnavara Apr 17, 2025
3bee83b
Prevent peephole optimization in funclet prolog/epilog; it results in…
filipnavara Apr 20, 2025
3340a70
Remove support for encoding the PSPSym in GC info
filipnavara Apr 20, 2025
5d7a4c0
Minor doc update
filipnavara Apr 20, 2025
f41cb02
Revert "Prevent peephole optimization in funclet prolog/epilog; it re…
filipnavara Apr 20, 2025
8d17a6a
don't optimize prologs/epilogues in OptimizePostIndexed
EgorBo Apr 20, 2025
fba3040
R2R / GC info versioning
filipnavara Apr 20, 2025
52b3fcf
Bump R2R version on two more places
filipnavara Apr 20, 2025
d78b7e5
Save R11 in CallEHFilterFunclet on ARM
filipnavara Apr 21, 2025
83efb78
Apply doc suggestions
filipnavara Apr 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 4 additions & 30 deletions docs/design/coreclr/botr/clr-abi.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,33 +322,11 @@ Finally1:

Note that JIT64 does not implement this properly. The C# compiler used to always insert all necessary "step" blocks. The Roslyn C# compiler at one point did not, but then was changed to once again insert them.

## The PSPSym and funclet parameters
## Funclet parameters

The *PSPSym* (which stands for Previous Stack Pointer Symbol) is a pointer-sized local variable used to access locals from the main function body.
For filter funclets the VM sets the frame register to be the same as the parent function. For second pass funclets the VM restores all non-volatile registers. The same convention is used across all platforms.

NativeAOT does not use PSPSym. For filter funclets the VM sets the frame register to be the same as the parent function. For second pass funclets the VM restores all non-volatile registers. The same convention is used across all platforms.

CoreCLR uses PSPSym for all platforms except x86: the frame pointer on x86 is always preserved when the handlers are invoked.

First, two definitions.

*Caller-SP* is the value of the stack pointer in a function's caller before the call instruction is executed. That is, when function A calls function B, Caller-SP for B is the value of the stack pointer immediately before the call instruction in A (calling B) was executed. Note that this definition holds for both AMD64, which pushes the return value when a call instruction is executed, and for ARM, which doesn't. For AMD64, Caller-SP is the address above the call return address.

*Initial-SP* is the initial value of the stack pointer after the fixed-size portion of the frame has been allocated. That is, before any "alloca"-type allocations.

The value stored in PSPSym is the value of Initial-SP for AMD64 or Caller-SP for other platforms, for the main function. The stack offset of the PSPSym is reported to the VM in the GC information header. The value reported in the GC information is the offset of the PSPSym from Initial-SP for AMD64 or Caller-SP for other platforms. (Note that both the value stored, and the way the value is reported to the VM, differs between architectures. In particular, note that most things in the GC information header are reported as offsets relative to Caller-SP, but PSPSym on AMD64 is one exception, and maybe the only exception.)

The VM uses the PSPSym to find other locals it cares about (such as the generics context in a funclet frame). The JIT uses it to re-establish the frame pointer register, so that the frame pointer is the same value in a funclet as it is in the main function body.

When a funclet is called, it is passed the *Establisher Frame Pointer*. For AMD64 this is true for all funclets and it is passed as the first argument in RCX, but for ARM and ARM64 this is only true for first pass funclets (currently just filters) and it is passed as the second argument in R1. The Establisher Frame Pointer is a stack pointer of an interesting "parent" frame in the exception processing system. For the CLR, it points either to the main function frame or a dynamically enclosing funclet frame from the same function, for the funclet being invoked. The value of the Establisher Frame Pointer is Initial-SP on AMD64, Caller-SP on x86, ARM, and ARM64.

Using the establisher frame, the funclet wants to load the value of the PSPSym. Since we don't know if the Establisher Frame is from the main function or a funclet, we design the main function and funclet frame layouts to place the PSPSym at an identical, small, constant offset from the Establisher Frame in each case. (This is also required because we only report a single offset to the PSPSym in the GC information, and that offset must be valid for the main function and all of its funclets). Then, the funclet uses this known offset to compute the PSPSym address and read its value. From this, it can compute the value of the frame pointer (which is a constant offset from the PSPSym value) and set the frame register to be the same as the parent function. Also, the funclet writes the value of the PSPSym to its own frame's PSPSym. This "copying" of the PSPSym happens for every funclet invocation, in particular, for every nested funclet invocation.

On ARM and ARM64, for all second pass funclets (finally, fault, catch, and filter-handler) the VM restores all non-volatile registers to their values within the parent frame. This includes the frame register (`R11`). Thus, the PSPSym is not used to recompute the frame pointer register in this case, though the PSPSym is copied to the funclet's frame, as for all funclets.

Catch, Filter, and Filter-handlers also get an Exception object (GC ref) as an argument (`REG_EXCEPTION_OBJECT`). On AMD64 it is the second argument and thus passed in RDX. On ARM and ARM64 this is the first argument and passed in R0.

(Note that the JIT64 source code contains a comment that says, "The current CLR doesn't always pass the correct establisher frame to the funclet. Funclet may receive establisher frame of funclet when expecting that of original routine." It indicates this is the reason that a PSPSym is required in all funclets as well as the main function, whereas if the establisher frame was correctly reported, the PSPSym could be omitted in some cases.)
Catch, Filter, and Filter-handlers also get an Exception object (GC ref) as an argument (`REG_EXCEPTION_OBJECT`). On AMD64 it is passed in RCX (Windows ABI) or RSI (Unix ABI). On ARM and ARM64 this is the first argument and passed in R0.

## Funclet Return Values

Expand All @@ -374,11 +352,7 @@ Some definitions:

When an exception occurs, the VM is invoked to do some processing. If the exception is within a "try" region, it eventually calls a corresponding handler (which also includes calling filters). The exception location within a function might be where a "throw" instruction executes, the point of a processor exception like null pointer dereference or divide by zero, or the point of a call where the callee threw an exception but did not catch it.

On AMD64, all register values that existed at the exception point in the corresponding "try" region are trashed on entry to the funclet. That is, the only registers that have known values are those of the funclet parameters.

On ARM and ARM64, all registers are restored to their values at the exception point.

On x86: TBD.
All non-volatile registers are restored to their values at the exception point.

### Registers on return from a funclet

Expand Down
6 changes: 2 additions & 4 deletions docs/design/coreclr/botr/guide-for-porting.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,12 +386,10 @@ Here is an annotated list of the stubs implemented for Unix on Arm64.
application

11. `CallEHFunclet` – Used to call catch, finally and fault funclets. Behavior
is specific to exactly how funclets are implemented. Only used if
USE_FUNCLET_CALL_HELPER is set
is specific to exactly how funclets are implemented.

12. `CallEHFilterFunclet` – Used to call filter funclets. Behavior is specific
to exactly how funclets are implemented. Only used if
USE_FUNCLET_CALL_HELPER is set
to exactly how funclets are implemented.

13. `ResolveWorkerChainLookupAsmStub`/ `ResolveWorkerAsmStub` Used for virtual
stub dispatch (virtual call support for interface, and some virtual
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/inc/eetwain.h
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,8 @@ PTR_VOID GetExactGenericsToken(PREGDISPLAY pContext,

static
PTR_VOID GetExactGenericsToken(SIZE_T baseStackSlot,
EECodeInfo * pCodeInfo);
EECodeInfo * pCodeInfo,
UINT_PTR returnAddress = 0);


#endif // FEATURE_EH_FUNCLETS && USE_GC_INFO_DECODER
Expand Down
109 changes: 11 additions & 98 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -459,11 +459,8 @@ class CodeGen final : public CodeGenInterface
// same.
struct FuncletFrameInfoDsc
{
regMaskTP fiSaveRegs; // Set of registers saved in the funclet prolog (includes LR)
unsigned fiFunctionCallerSPtoFPdelta; // Delta between caller SP and the frame pointer
unsigned fiSpDelta; // Stack pointer delta
unsigned fiPSP_slot_SP_offset; // PSP slot offset from SP
int fiPSP_slot_CallerSP_offset; // PSP slot offset from Caller SP
regMaskTP fiSaveRegs; // Set of registers saved in the funclet prolog (includes LR)
unsigned fiSpDelta; // Stack pointer delta
};

FuncletFrameInfoDsc genFuncletInfo;
Expand All @@ -475,16 +472,12 @@ class CodeGen final : public CodeGenInterface
// same.
struct FuncletFrameInfoDsc
{
regMaskTP fiSaveRegs; // Set of callee-saved registers saved in the funclet prolog (includes LR)
int fiFunction_CallerSP_to_FP_delta; // Delta between caller SP and the frame pointer in the parent function
// (negative)
int fiSP_to_FPLR_save_delta; // FP/LR register save offset from SP (positive)
int fiSP_to_PSP_slot_delta; // PSP slot offset from SP (positive)
int fiSP_to_CalleeSave_delta; // First callee-saved register slot offset from SP (positive)
int fiCallerSP_to_PSP_slot_delta; // PSP slot offset from Caller SP (negative)
int fiFrameType; // Funclet frame types are numbered. See genFuncletProlog() for details.
int fiSpDelta1; // Stack pointer delta 1 (negative)
int fiSpDelta2; // Stack pointer delta 2 (negative)
regMaskTP fiSaveRegs; // Set of callee-saved registers saved in the funclet prolog (includes LR)
int fiSP_to_FPLR_save_delta; // FP/LR register save offset from SP (positive)
int fiSP_to_CalleeSave_delta; // First callee-saved register slot offset from SP (positive)
int fiFrameType; // Funclet frame types are numbered. See genFuncletProlog() for details.
int fiSpDelta1; // Stack pointer delta 1 (negative)
int fiSpDelta2; // Stack pointer delta 2 (negative)
};

FuncletFrameInfoDsc genFuncletInfo;
Expand All @@ -496,9 +489,7 @@ class CodeGen final : public CodeGenInterface
// same.
struct FuncletFrameInfoDsc
{
unsigned fiFunction_InitialSP_to_FP_delta; // Delta between Initial-SP and the frame pointer
unsigned fiSpDelta; // Stack pointer delta
int fiPSP_slot_InitialSP_offset; // PSP slot offset from Initial-SP
unsigned fiSpDelta; // Stack pointer delta
};

FuncletFrameInfoDsc genFuncletInfo;
Expand All @@ -511,12 +502,8 @@ class CodeGen final : public CodeGenInterface
struct FuncletFrameInfoDsc
{
regMaskTP fiSaveRegs; // Set of callee-saved registers saved in the funclet prolog (includes RA)
int fiFunction_CallerSP_to_FP_delta; // Delta between caller SP and the frame pointer in the parent function
// (negative)
int fiSP_to_CalleeSaved_delta; // CalleeSaved register save offset from SP (positive)
int fiSP_to_PSP_slot_delta; // PSP slot offset from SP (positive)
int fiCallerSP_to_PSP_slot_delta; // PSP slot offset from Caller SP (negative)
int fiSpDelta; // Stack pointer delta (negative)
int fiSP_to_CalleeSaved_delta; // CalleeSaved register save offset from SP (positive)
int fiSpDelta; // Stack pointer delta (negative)
};

FuncletFrameInfoDsc genFuncletInfo;
Expand Down Expand Up @@ -612,80 +599,6 @@ class CodeGen final : public CodeGenInterface
void genFuncletEpilog();
void genCaptureFuncletPrologEpilogInfo();

/*-----------------------------------------------------------------------------
*
* Set the main function PSPSym value in the frame.
* Funclets use different code to load the PSP sym and save it in their frame.
* See the document "CLR ABI.md" for a full description of the PSPSym.
* The PSPSym section of that document is copied here.
*
***********************************
* The name PSPSym stands for Previous Stack Pointer Symbol. It is how a funclet
* accesses locals from the main function body.
*
* First, two definitions.
*
* Caller-SP is the value of the stack pointer in a function's caller before the call
* instruction is executed. That is, when function A calls function B, Caller-SP for B
* is the value of the stack pointer immediately before the call instruction in A
* (calling B) was executed. Note that this definition holds for both AMD64, which
* pushes the return value when a call instruction is executed, and for ARM, which
* doesn't. For AMD64, Caller-SP is the address above the call return address.
*
* Initial-SP is the initial value of the stack pointer after the fixed-size portion of
* the frame has been allocated. That is, before any "alloca"-type allocations.
*
* The PSPSym is a pointer-sized local variable in the frame of the main function and
* of each funclet. The value stored in PSPSym is the value of Initial-SP/Caller-SP
* for the main function. The stack offset of the PSPSym is reported to the VM in the
* GC information header. The value reported in the GC information is the offset of the
* PSPSym from Initial-SP/Caller-SP. (Note that both the value stored, and the way the
* value is reported to the VM, differs between architectures. In particular, note that
* most things in the GC information header are reported as offsets relative to Caller-SP,
* but PSPSym on AMD64 is one (maybe the only) exception.)
*
* The VM uses the PSPSym to find other locals it cares about (such as the generics context
* in a funclet frame). The JIT uses it to re-establish the frame pointer register, so that
* the frame pointer is the same value in a funclet as it is in the main function body.
*
* When a funclet is called, it is passed the Establisher Frame Pointer. For AMD64 this is
* true for all funclets and it is passed as the first argument in RCX, but for ARM this is
* only true for first pass funclets (currently just filters) and it is passed as the second
* argument in R1. The Establisher Frame Pointer is a stack pointer of an interesting "parent"
* frame in the exception processing system. For the CLR, it points either to the main function
* frame or a dynamically enclosing funclet frame from the same function, for the funclet being
* invoked. The value of the Establisher Frame Pointer is Initial-SP on AMD64, Caller-SP on ARM.
*
* Using the establisher frame, the funclet wants to load the value of the PSPSym. Since we
* don't know if the Establisher Frame is from the main function or a funclet, we design the
* main function and funclet frame layouts to place the PSPSym at an identical, small, constant
* offset from the Establisher Frame in each case. (This is also required because we only report
* a single offset to the PSPSym in the GC information, and that offset must be valid for the main
* function and all of its funclets). Then, the funclet uses this known offset to compute the
* PSPSym address and read its value. From this, it can compute the value of the frame pointer
* (which is a constant offset from the PSPSym value) and set the frame register to be the same
* as the parent function. Also, the funclet writes the value of the PSPSym to its own frame's
* PSPSym. This "copying" of the PSPSym happens for every funclet invocation, in particular,
* for every nested funclet invocation.
*
* On ARM, for all second pass funclets (finally, fault, catch, and filter-handler) the VM
* restores all non-volatile registers to their values within the parent frame. This includes
* the frame register (R11). Thus, the PSPSym is not used to recompute the frame pointer register
* in this case, though the PSPSym is copied to the funclet's frame, as for all funclets.
*
* Catch, Filter, and Filter-handlers also get an Exception object (GC ref) as an argument
* (REG_EXCEPTION_OBJECT). On AMD64 it is the second argument and thus passed in RDX. On
* ARM this is the first argument and passed in R0.
*
* (Note that the JIT64 source code contains a comment that says, "The current CLR doesn't always
* pass the correct establisher frame to the funclet. Funclet may receive establisher frame of
* funclet when expecting that of original routine." It indicates this is the reason that a PSPSym
* is required in all funclets as well as the main function, whereas if the establisher frame was
* correctly reported, the PSPSym could be omitted in some cases.)
***********************************
*/
void genSetPSPSym(regNumber initReg, bool* pInitRegZeroed);

void genUpdateCurrentFunclet(BasicBlock* block);

void genGeneratePrologsAndEpilogs();
Expand Down
Loading
Loading