Skip to content

Fix mono handling of SwiftError - #131634

Merged
vcsjones merged 3 commits into
dotnet:mainfrom
vcsjones:mono-swift-error-fix
Aug 4, 2026
Merged

vcsjones merged 3 commits into
dotnet:mainfrom
vcsjones:mono-swift-error-fix

Conversation

@vcsjones

@vcsjones vcsjones commented Jul 31, 2026 •

Copy link
Copy Markdown
Member

There appear to be two problems with the Swift calling convention implementation in Mono for how it handles SwiftError.

On x64, structs can spill to the stack while a later scalar argument still has an available register. Consequently, SwiftError can be register passed while carrying a nonzero accumulated stack offset. Mono mistakes that offset for a stack location and reads an invalid pointer.

On ARM64, the first argument placed on the stack naturally has offset zero. Mono mistakes that stack location for a register passed argument and uses the wrong value. offset == 0  is ambiguous: it can mean either "passed in a register" or "passed in the first stack slot." Right now it doesn't look like it can tell the difference.

The fix records whether SwiftError was originally passed in a register before replacing its argument classification with ArgSwiftError, rather than inferring its location from the stack offset. On x64, stack-passed SwiftError arguments also disable frame-pointer omission so their incoming stack slots remain addressable.

The tests that have been added would fail without the fixes, and I confirmed this locally on an x64 macOS 26 machine.

This was originally found in #131630, where changes at the p/invoke boundary caused the SwiftError to be identified as register passed but carried a nonzero accumulated stack offset because preceding two word buffer structs had spilled.

This is a follow up to #103043, where it appears the fix was incomplete.

@vcsjones vcsjones self-assigned this Jul 31, 2026
Copilot AI review requested due to automatic review settings July 31, 2026 10:06
@vcsjones vcsjones added NO-REVIEW Experimental/testing PR, do NOT review it area-Codegen-JIT-mono labels Jul 31, 2026
@vcsjones

Copy link
Copy Markdown
Member Author

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @vitek-karas
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR expands the existing Swift interop test asset to add additional ABI/calling-convention stress cases around SwiftError placement, and wires them up from C# via Swift-callconv P/Invokes with new xUnit tests to exercise those scenarios on Apple platforms.

Changes:

  • Add a new Swift exported throwing function with argument shaping intended to place SwiftError in the first stack slot scenario.
  • Add a new Swift exported throwing function that takes multiple 2-word struct-like arguments (Swift UnsafeBufferPointer) plus a 1-word scalar argument to stress mixed register/stack argument passing.
  • Add corresponding C# interop declarations and new xUnit tests validating both “no throw” behavior and ABI correctness via a shared hash computation.
Show a summary per file
File Description
src/tests/Interop/Swift/SwiftErrorHandling/SwiftErrorHandling.swift Adds new exported Swift entry points and hashing helpers to exercise ABI argument placement while still returning a deterministic value.
src/tests/Interop/Swift/SwiftErrorHandling/SwiftErrorHandling.cs Adds marshaling types, new Swift-callconv P/Invokes, and new xUnit tests to validate the ABI scenarios end-to-end.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread src/tests/Interop/Swift/SwiftErrorHandling/SwiftErrorHandling.swift
Comment thread src/tests/Interop/Swift/SwiftErrorHandling/SwiftErrorHandling.cs
Copilot AI review requested due to automatic review settings July 31, 2026 12:02
@vcsjones

Copy link
Copy Markdown
Member Author

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

Suppressed comments (2)

src/tests/Interop/Swift/SwiftErrorHandling/SwiftErrorHandling.cs:173

  • This new test only covers the non-throwing path. To better stress/validate SwiftError placement and propagation, it should also exercise the throwing path (matching the existing pattern of having both thrown and not-thrown coverage for the other entrypoints). Converting this to a [Theory] over willThrow keeps the file compact while covering both behaviors.
    [Fact]
    public static unsafe void TestSwiftErrorInFirstStackSlotNotThrown()
    {
        SwiftError error = new SwiftError();

        int i = 0;
        int result = (int)conditionallyThrowErrorInFirstStackSlot(0, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7, ref error);

        Assert.True(error.Value == null, "No Swift error was expected to be thrown.");
        Assert.True(result == 42, "The result from Swift does not match the expected value.");
    }

src/tests/Interop/Swift/SwiftErrorHandling/SwiftErrorHandling.cs:178

  • BufferPointer instances are created with count values up to 66, but the backing stackalloc buffer is only 6 bytes. Even though the current Swift implementation only hashes the pointer value and count, UnsafeBufferPointer semantically represents a range of count elements starting at baseAddress, so passing an out-of-bounds range risks UB/traps if the Swift side changes (or if the optimizer/runtime assumes the range is valid). Allocate enough bytes so all (baseAddress,count) pairs describe valid ranges.
        byte* values = stackalloc byte[6];
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new

Copilot AI review requested due to automatic review settings July 31, 2026 20:16
@vcsjones vcsjones changed the title Add tests to stress Mono's Swift calling conventions Fix mono handling of SwiftError Jul 31, 2026
@vcsjones vcsjones removed the NO-REVIEW Experimental/testing PR, do NOT review it label Jul 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

Suppressed comments (3)

src/tests/Interop/Swift/SwiftErrorHandling/SwiftErrorHandling.cs:177

  • This new scenario is only validated for the non-throwing path. Since the purpose of the test is to stress SwiftError handling/calling convention edge cases, it should also cover the throwing path (non-null SwiftError + message retrieval) to ensure the error propagation logic is exercised.
    [Fact]
    public static unsafe void TestSwiftErrorInFirstStackSlotNotThrown()
    {
        SwiftError error = new SwiftError();

        int i = 0;
        int result = (int)conditionallyThrowErrorInFirstStackSlot(0, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7, ref error);

        Assert.True(error.Value == null, "No Swift error was expected to be thrown.");
        Assert.True(result == 42, "The result from Swift does not match the expected value.");
    }

src/tests/Interop/Swift/SwiftErrorHandling/SwiftErrorHandling.cs:210

  • The test constructs Swift-style buffer pointers whose Count values exceed the actual stackalloced buffer size (e.g. values + 5 with count 66). Even though the current Swift implementation doesn’t dereference the buffers, this violates the contract of UnsafeBufferPointer and can become undefined behavior if the Swift side changes (or if the optimizer introduces unexpected accesses). Allocate enough stack space to cover the largest (base + count) range used by the test data.
        byte* values = stackalloc byte[6];

src/mono/mono/mini/mini-amd64.c:1134

  • The PR description frames this as adding stress tests to reproduce Mono Swift calling-convention issues, but this diff also changes Mono’s argument classification/stack allocation behavior for SwiftError on amd64 (and similarly on arm64). If the intent is tests-only, these runtime changes should be moved to a follow-up PR; otherwise, please update the title/description to reflect that this PR includes a functional fix as well.
				if (sig->pinvoke) {
					ainfo->reg = GINT32_TO_UINT8 (AMD64_R12);
					ainfo->swift_error_in_reg = TRUE;
				} else {
					add_general (&gr, &stack_size, ainfo);
					ainfo->swift_error_in_reg = ainfo->storage == ArgInIReg;
				}
				ainfo->storage = ArgSwiftError;
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new

@vcsjones
vcsjones marked this pull request as ready for review July 31, 2026 20:25
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@vitek-karas

Copy link
Copy Markdown
Member

@BrzVlad @matouskozak can one you please review?

@matouskozak matouskozak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall looking good to me, just one question around the new ArgInfo field.

I've run it locally on arm64 macOS and without this change I'm getting NullReferenceException and passes with this change.

fyi: @dalexsoto since you're now working on this domain

Comment thread src/mono/mono/mini/mini-amd64.h
@vcsjones
vcsjones merged commit ac85c68 into dotnet:main Aug 4, 2026
133 checks passed
@vcsjones
vcsjones deleted the mono-swift-error-fix branch August 4, 2026 15:42
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-rc1 milestone Aug 5, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants