Skip to content

Commit

Permalink
JIT: Add support for SwiftSelf<T> in Swift calling convention (#103576)
Browse files Browse the repository at this point in the history
This adds support to allow SwiftSelf<T> with a frozen struct as T. Swift
allows enregistration of 'self' in these cases, but the 'self' must
still be passed in the dedicated context register when the frozen struct
is not enregistered, which makes this support necessary to handle as
part of the calling convention.

A few notes:
- If `T` is not a value class we `BADCODE`
- If the signature includes `SwiftSelf<T>`, then it must be the first
  argument of the signature, which matches how 'self' gets enregistered
  on the Swift side, otherwise we `BADCODE`
- There is not support for reverse pinvokes for `SwiftSelf<T>`. That's
  because the context passed to function pointers is always a pointer,
  so I do not see any use case for this in reverse pinvokes.
- Some care must be taken since `SwiftSelf<T>` is a generic struct whose
  layout generally is not going to match `T` without tail padding (until
  we get something like #100896).
  • Loading branch information
jakobbotsch authored Jun 18, 2024
1 parent 504b4fb commit 84874d8
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 6 deletions.
42 changes: 39 additions & 3 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2027,6 +2027,7 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
unsigned short swiftErrorIndex = sig->numArgs;
unsigned short swiftSelfIndex = sig->numArgs;

CORINFO_CLASS_HANDLE selfType = NO_CLASS_HANDLE;
// We are importing an unmanaged Swift call, which might require special parameter handling
bool checkEntireStack = false;

Expand Down Expand Up @@ -2090,6 +2091,34 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
swiftSelfIndex = argIndex;
// Fall through to make sure the struct value becomes a local.
}
else if ((strcmp(className, "SwiftSelf`1") == 0) &&
(strcmp(namespaceName, "System.Runtime.InteropServices.Swift") == 0))
{
// We expect a SwiftSelf struct to be passed, not a pointer/reference
if (argIsByrefOrPtr)
{
BADCODE("Expected SwiftSelf<T> struct, got pointer/reference");
}

if (swiftSelfIndex != sig->numArgs)
{
BADCODE("Duplicate SwiftSelf parameter");
}

if (argIndex != 0)
{
BADCODE("SwiftSelf<T> must be the first argument in the signature");
}

selfType = info.compCompHnd->getTypeInstantiationArgument(argClass, 0);
CorInfoType selfCorType = info.compCompHnd->asCorInfoType(selfType);
if (selfCorType != CORINFO_TYPE_VALUECLASS)
{
BADCODE("SwiftSelf<T> expects T to be a value class");
}

swiftSelfIndex = argIndex;
}
// TODO: Handle SwiftAsync
}

Expand Down Expand Up @@ -2157,7 +2186,7 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
// For the self arg, change it from the SwiftSelf struct to a
// TYP_I_IMPL primitive directly. It must also be marked as a well
// known arg because it has a non-standard calling convention.
if (argIndex == swiftSelfIndex)
if ((argIndex == swiftSelfIndex) && (selfType == NO_CLASS_HANDLE))
{
assert(arg->GetNode()->OperIsLocalRead());
GenTree* primitiveSelf = gtNewLclFldNode(structVal->GetLclNum(), TYP_I_IMPL, structVal->GetLclOffs());
Expand All @@ -2166,7 +2195,8 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
}
else
{
const CORINFO_SWIFT_LOWERING* lowering = GetSwiftLowering(arg->GetSignatureClassHandle());
CORINFO_CLASS_HANDLE argClass = argIndex == swiftSelfIndex ? selfType : arg->GetSignatureClassHandle();
const CORINFO_SWIFT_LOWERING* lowering = GetSwiftLowering(argClass);
if (lowering->byReference)
{
JITDUMP(" Argument %d of type %s must be passed by reference\n", argIndex,
Expand All @@ -2188,7 +2218,13 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
GenTree* addrNode = gtNewLclAddrNode(structVal->GetLclNum(), structVal->GetLclOffs());
JITDUMP(" Passing by reference\n");

insertAfter = call->gtArgs.InsertAfter(this, insertAfter, NewCallArg::Primitive(addrNode, TYP_I_IMPL));
NewCallArg newArg = NewCallArg::Primitive(addrNode, TYP_I_IMPL);
if (argIndex == swiftSelfIndex)
{
newArg = newArg.WellKnown(WellKnownArg::SwiftSelf);
}

insertAfter = call->gtArgs.InsertAfter(this, insertAfter, newArg);
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion src/tests/Interop/Swift/SwiftSelfContext/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ if (NOT SWIFT_COMPILER_TARGET AND CLR_CMAKE_TARGET_OSX)
endif()

add_custom_target(${SOURCE} ALL
COMMAND xcrun swiftc -target ${SWIFT_COMPILER_TARGET} -emit-library ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE}.swift -o ${CMAKE_CURRENT_BINARY_DIR}/lib${SOURCE}.dylib
COMMAND xcrun swiftc -target ${SWIFT_COMPILER_TARGET} -enable-library-evolution -emit-library ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE}.swift -o ${CMAKE_CURRENT_BINARY_DIR}/lib${SOURCE}.dylib
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE}.swift
COMMENT "Generating ${SOURCE} library"
)
Expand Down
43 changes: 41 additions & 2 deletions src/tests/Interop/Swift/SwiftSelfContext/SwiftSelfContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ public class SelfContextTests
public unsafe static extern void* getInstance();

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport(SwiftLib, EntryPoint = "$s16SwiftSelfContext0B7LibraryC14getMagicNumberSiyF")]
[DllImport(SwiftLib, EntryPoint = "$s16SwiftSelfContext0B7LibraryC14getMagicNumberSiyFTj")]
public static extern nint getMagicNumber(SwiftSelf self);

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport(SwiftLib, EntryPoint = "$s16SwiftSelfContext0B7LibraryC14getMagicNumberSiyF")]
[DllImport(SwiftLib, EntryPoint = "$s16SwiftSelfContext0B7LibraryC14getMagicNumberSiyFTj")]
public static extern nint getMagicNumberOnStack(int dummy0, int dummy1, int dummy2, int dummy3, int dummy4, int dummy5, int dummy6, int dummy7, int dummy8, int dummy9, SwiftSelf self);

[Fact]
Expand All @@ -44,4 +44,43 @@ public unsafe static void TestSwiftSelfContextOnStack()
int result = (int)getMagicNumberOnStack(i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7, i + 8, i + 9, self);
Assert.True(result == 42, "The result from Swift does not match the expected value.");
}

public struct FrozenEnregisteredStruct
{
public long A;
public long B;
}

public struct FrozenNonEnregisteredStruct
{
public long A;
public long B;
public long C;
public long D;
public long E;
}

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport(SwiftLib, EntryPoint = "$s16SwiftSelfContext24FrozenEnregisteredStructV3Sums5Int64VyF")]
public static extern long SumFrozenEnregisteredStruct(SwiftSelf<FrozenEnregisteredStruct> self);

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport(SwiftLib, EntryPoint = "$s16SwiftSelfContext27FrozenNonEnregisteredStructV3Sums5Int64VyF")]
public static extern long SumFrozenNonEnregisteredStruct(SwiftSelf<FrozenNonEnregisteredStruct> self);

[Fact]
[SkipOnMono("SwiftSelf<T> is not supported on Mono")]
public unsafe static void TestSelfIsFrozenEnregisteredStruct()
{
long sum = SumFrozenEnregisteredStruct(new SwiftSelf<FrozenEnregisteredStruct>(new FrozenEnregisteredStruct { A = 10, B = 20 }));
Assert.Equal(30, sum);
}

[Fact]
[SkipOnMono("SwiftSelf<T> is not supported on Mono")]
public unsafe static void TestSelfIsFrozenNonEnregisteredStruct()
{
long sum = SumFrozenNonEnregisteredStruct(new SwiftSelf<FrozenNonEnregisteredStruct>(new FrozenNonEnregisteredStruct { A = 10, B = 20, C = 30, D = 40, E = 50 }));
Assert.Equal(150, sum);
}
}
24 changes: 24 additions & 0 deletions src/tests/Interop/Swift/SwiftSelfContext/SwiftSelfContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,27 @@ public class SelfLibrary {
return pointer
}
}

@frozen
public struct FrozenEnregisteredStruct
{
let a : Int64;
let b : Int64;

public func Sum() -> Int64 {
return a + b
}
}

@frozen
public struct FrozenNonEnregisteredStruct {
let a : Int64;
let b : Int64;
let c : Int64;
let d : Int64;
let e : Int64;

public func Sum() -> Int64 {
return a + b + c + d + e
}
}

0 comments on commit 84874d8

Please sign in to comment.