Skip to content

Add ABI note about small return types #94987

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 3 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions docs/design/coreclr/botr/clr-abi.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ ARM64-only: When a method returns a structure that is larger than 16 bytes the c

*Normal PInvoke* - The VM shares IL stubs based on signatures, but wants the right method to show up in call stack and exceptions, so the MethodDesc for the exact PInvoke is passed in the (x86) `EAX` / (AMD64) `R10` / (ARM, ARM64) `R12` (in the JIT: `REG_SECRET_STUB_PARAM`). Then in the IL stub, when the JIT gets `CORJIT_FLG_PUBLISH_SECRET_PARAM`, it must move the register into a compiler temp. The value is returned for the intrinsic `NI_System_StubHelpers_GetStubContext`.

## Small primitive returns

Primitive value types smaller than 32-bits are widened to 32-bits: signed small types are sign extended and unsigned small types are zero extended. This can be different from the standard calling conventions that may leave the state of unused bits in the return register undefined.

# PInvokes

The convention is that any method with an InlinedCallFrame (either an IL stub or a normal method with an inlined PInvoke) saves/restores all non-volatile integer registers in its prolog/epilog respectively. This is done so that the InlinedCallFrame can just contain a return address, a stack pointer and a frame pointer. Then using just those three it can start a full stack walk using the normal RtlVirtualUnwind.
Expand Down
28 changes: 8 additions & 20 deletions src/coreclr/vm/fcall.h
Original file line number Diff line number Diff line change
Expand Up @@ -1289,11 +1289,11 @@ struct FCSigCheck {



// The x86 JIT calling convention expects returned small types (e.g. bool) to be
// widened on return. The C/C++ calling convention does not guarantee returned
// small types to be widened. The small types has to be artificially widened on return
// to fit x86 JIT calling convention. Thus fcalls returning small types has to
// use the FC_XXX_RET types to force C/C++ compiler to do the widening.
// The managed calling convention expects returned small types (e.g. bool) to be
// widened to 32-bit on return. The C/C++ calling convention does not guarantee returned
// small types to be widened on most platforms. The small types have to be artificially
// widened on return to fit the managed calling convention. Thus fcalls returning small
// types have to use the FC_XXX_RET types to force C/C++ compiler to do the widening.
//
// The most common small return type of FCALLs is bool. The widening of bool is
// especially tricky since the value has to be also normalized. FC_BOOL_RET and
Expand All @@ -1306,7 +1306,7 @@ struct FCSigCheck {
// FC_RETURN_BOOL(ret); // return statements should be FC_RETURN_BOOL
// FCIMPLEND

// This rules are verified in binder.cpp if DOTNET_ConsistencyCheck is set.
// This rule is verified in corelib.cpp if DOTNET_ConsistencyCheck is set.

#ifdef _PREFAST_

Expand All @@ -1320,32 +1320,20 @@ typedef LPVOID FC_BOOL_RET;

#else

#if defined(TARGET_X86) || defined(TARGET_AMD64)
// The return value is artificially widened on x86 and amd64
// The return value is artificially widened in managed calling convention
typedef INT32 FC_BOOL_RET;
#else
typedef CLR_BOOL FC_BOOL_RET;
#endif

#define FC_RETURN_BOOL(x) do { return !!(x); } while(0)

#endif


#if defined(TARGET_X86) || defined(TARGET_AMD64)
// The return value is artificially widened on x86 and amd64
// Small primitive return values are artificially widened in managed calling convention
typedef UINT32 FC_CHAR_RET;
typedef INT32 FC_INT8_RET;
typedef UINT32 FC_UINT8_RET;
typedef INT32 FC_INT16_RET;
typedef UINT32 FC_UINT16_RET;
#else
typedef CLR_CHAR FC_CHAR_RET;
typedef INT8 FC_INT8_RET;
typedef UINT8 FC_UINT8_RET;
typedef INT16 FC_INT16_RET;
typedef UINT16 FC_UINT16_RET;
#endif


// FC_TypedByRef should be used for TypedReferences in FCall signatures
Expand Down