Skip to content

[clr-interp] Add Swift calling convention support to CoreCLR interpreter on arm64#123142

Merged
kotlarmilos merged 37 commits intodotnet:mainfrom
kotlarmilos:feature/clr-interp-swift-interop
Feb 5, 2026
Merged

[clr-interp] Add Swift calling convention support to CoreCLR interpreter on arm64#123142
kotlarmilos merged 37 commits intodotnet:mainfrom
kotlarmilos:feature/clr-interp-swift-interop

Conversation

@kotlarmilos
Copy link
Member

@kotlarmilos kotlarmilos commented Jan 13, 2026

Description

Swift uses a calling convention that differs from the standard arm64 ABI. This PR extends the CoreCLR interpreter so that it can correctly call Swift functions on arm64. The changes are limited to call stub generation and includes Swift-specific register usage, argument lowering, and return value lowering.

SwiftSelf and SwiftSelf<T>

Swift instance methods pass self in a dedicated register (x20 on arm64) rather than as a regular argument. In ComputeCallStubWorker when SwiftSelf in encountered, it emits routine for loading argument into x20 before the call.

When SwiftSelf<T> is encountered and not lowered according to the Swift value type lowering, it emits routine for loading address of struct into x20 before the call. If struct T can be lowered, it is processed as normal lowered argument as described below.

SwiftError

Swift error is returned via dedicated register (x21 on arm64) when a call returns. It is passed as byref argument where is expected to load error reg into that bytrerf arg when call returns. It is done by:

  • The byref argument address is saved at fp + offset
  • Before the call, both x21 and the byref slot are initialized to null
  • After the call, x21 is copied into scratch register x10
  • If x10 is non-null, it points to a Swift error object
  • The interpreter checks x10 and loads the error into the argument

SwiftIndirectResult

Swift returns non-lowered structs indirectly via a dedicated register (x8 on arm64). In ComputeCallStubWorker when SwiftIndirectResult in encountered, it emits routine for loading argument into x8 before the call. Then the callee writes the return value to the memory provided in the argument.

Lowering of value types in arguments

Swift lowers structs into individual primitive arguments when they fit within four machine words. Consider the following struct:

[StructLayout(LayoutKind.Sequential, Size = 14)]
struct F3_S4
{
    public nuint F0;  // size = 8, offset = 0
    public float F1;  // size = 4, offset = 8
    public ushort F2; // size = 2, offset = 12
}

A Swift function swiftFunc(int a, F3_S4 b, float c) is lowered to swiftFunc(int a, nuint b_0, float b_1, ushort b_2, float c)

The signature is first rewritten into lowered form. This enables correct register and stack allocation, but introduces a problem. When loading lowered arguments into registers or stack, the standard routines assume 8-byte alignment on the interpreter stack, which is not always true for fields within lowered structs.

For example, the loader routine reads F0 into a register and advances the argument pointer by 8 bytes. It then loads F1 and again advances by 8 bytes (including 4 bytes of padding). This is incorrect because F1 is at offset 8, and F2 is at offset 12

To fix that, new load routines are introduced for Swift-lowered arguments:

  • Routines read the field offset from the routine array
  • They load from [x9 + offset] without advancing the interpreter argument pointer
  • The argument pointer is advanced by the struct size only when the last element of the lowered struct is loaded

Lowering of return value types

Swift lowers struct return values into multiple registers (both GP and FP). This is done by collecting return registers after the call and reconstructing the original struct layout. When a lowered struct return is detected in ComputeCallStubWorker, additional post-call routines are emitted to load the return value into the managed return buffer. To implement this, a new return stub InterpreterStubRetSwiftLowered is introduced. This stub stores the return registers (GP0–GP4 and FP0–FP4) into a contiguous temporary buffer immediately after the call returns.

A new call stub CallJittedMethodRetSwiftLowered is introduced to allow for execution of routines after the target call. Based on the computed lowering, a sequence of post-call routines is emitted. These routines copy each lowered field from the temporary buffer into the return struct at the correct offsets. A SwiftLoweredReturnTerminator routine is added at the end to branch back to the epilog.

Out of scope

x64 and UnmanagedCallersOnly are currently not supported and will be added in a follow-up.

Contributes to #120049

@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @BrzVlad, @janvorli, @kg
See info in area-owners.md if you want to be subscribed.

@kotlarmilos kotlarmilos changed the title [clr-interp] Add Swift calling convention support to CoreCLR interpreter [clr-interp] Add Swift calling convention support to CoreCLR interpreter on arm64 Jan 16, 2026
@kotlarmilos kotlarmilos marked this pull request as ready for review January 16, 2026 10:39
Copilot AI review requested due to automatic review settings January 16, 2026 10:39
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Copilot AI review requested due to automatic review settings January 30, 2026 15:44
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Copilot AI review requested due to automatic review settings January 30, 2026 16:50
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated no new comments.

@kotlarmilos
Copy link
Member Author

@davidwrighton @janvorli This PR is ready for review, please take a look

Copy link
Member

@janvorli janvorli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM modulo the comments on adding some ifdefs.

davidwrighton added a commit that referenced this pull request Feb 4, 2026
## Description

Converts interpreter call stub logging from compile-time `#if
LOG_COMPUTE_CALL_STUB` blocks to runtime-controllable LOG2 facility.
These logging statements have proven valuable for debugging but required
recompilation to enable.

## Changes

- Added `LF2_INTERPRETER = 0x00000002` to LogFacility2 enum
- Converted 26 logging instances to `LOG2((LF2_INTERPRETER,
LL_INFO10000, ...))`
- Removed `#define LOG_COMPUTE_CALL_STUB 0`
- Updated `INVOKE_FUNCTION_PTR` and `RETURN_TYPE_HANDLER` macros

### Before
```cpp
#define LOG_COMPUTE_CALL_STUB 0

PCODE GetGPRegRangeRoutine(int r1, int r2)
{
#if LOG_COMPUTE_CALL_STUB
    printf("GetGPRegRangeRoutine %d %d\n", r1, r2);
#endif
    // ...
}
```

### After
```cpp
PCODE GetGPRegRangeRoutine(int r1, int r2)
{
    LOG2((LF2_INTERPRETER, LL_INFO10000, "GetGPRegRangeRoutine %d %d\n", r1, r2));
    // ...
}
```

Logging now enabled via: `DOTNET_LogFacility2=0x2 DOTNET_LogLevel=10000`

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>[clr-interp] Convert logging in call stubs to use logging
facility</issue_title>
> <issue_description>I think we should convert it to the LOG mechanism.
When I wrote these logging statements, I had thought they would likely
be very temporary, but I've used them 4-5 times since then, so they have
some ongoing value. However, I'd like to see that work done in a
separate PR. We should convert them to using LOG statements probably
with a new LF enum value. Probably what should be done is add a new log
facility enum in log.h, call it LF2_INTERPRETER. And convert these log
statements to use the LOG2 macro, with info level LL_INFO10000 instead
of wrapping them in #if LOG_COMPUTE_CALL_STUB blocks.
> 
> _Originally posted by @davidwrighton in
#123142 (comment)
>             </issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #123272

<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for
you](https://github.com/dotnet/runtime/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com>
Co-authored-by: David Wrighton <davidwr@microsoft.com>
Copilot AI review requested due to automatic review settings February 4, 2026 09:27
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 3 comments.

@kotlarmilos kotlarmilos enabled auto-merge (squash) February 4, 2026 10:03
@kotlarmilos
Copy link
Member Author

/ba-g ios-arm64 failure is #123796

@kotlarmilos kotlarmilos merged commit 095855b into dotnet:main Feb 5, 2026
105 of 108 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants