Skip to content

Commit

Permalink
JIT: Add support for SwiftIndirectResult in Swift calling convention (
Browse files Browse the repository at this point in the history
#103570)

Add a test for pinvoke/reverse pinvokes involving `SwiftIndirectResult`, and add
the necessary support to make the tests pass in RyuJIT.
  • Loading branch information
jakobbotsch authored Jun 18, 2024
1 parent aa0a7e9 commit 35437dd
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 34 deletions.
6 changes: 2 additions & 4 deletions src/coreclr/jit/abi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,11 @@ ABIPassingInformation SwiftABIClassifier::Classify(Compiler* comp,
ClassLayout* structLayout,
WellKnownArg wellKnownParam)
{
#ifdef TARGET_AMD64
if (wellKnownParam == WellKnownArg::RetBuffer)
{
return ABIPassingInformation::FromSegment(comp, ABIPassingSegment::InRegister(REG_SWIFT_ARG_RET_BUFF, 0,
TARGET_POINTER_SIZE));
regNumber reg = theFixedRetBuffReg(CorInfoCallConvExtension::Swift);
return ABIPassingInformation::FromSegment(comp, ABIPassingSegment::InRegister(reg, 0, TARGET_POINTER_SIZE));
}
#endif

if (wellKnownParam == WellKnownArg::SwiftSelf)
{
Expand Down
11 changes: 10 additions & 1 deletion src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4356,7 +4356,7 @@ void CodeGen::genHomeSwiftStructParameters(bool handleStack)
{
for (unsigned lclNum = 0; lclNum < compiler->info.compArgsCount; lclNum++)
{
if (lclNum == compiler->lvaSwiftSelfArg)
if ((lclNum == compiler->lvaSwiftSelfArg) || (lclNum == compiler->lvaSwiftIndirectResultArg))
{
continue;
}
Expand Down Expand Up @@ -5696,6 +5696,15 @@ void CodeGen::genFnProlog()
intRegState.rsCalleeRegArgMaskLiveIn &= ~RBM_SWIFT_SELF;
}

if ((compiler->lvaSwiftIndirectResultArg != BAD_VAR_NUM) &&
((intRegState.rsCalleeRegArgMaskLiveIn & theFixedRetBuffMask(CorInfoCallConvExtension::Swift)) != 0))
{
GetEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE,
theFixedRetBuffReg(CorInfoCallConvExtension::Swift),
compiler->lvaSwiftIndirectResultArg, 0);
intRegState.rsCalleeRegArgMaskLiveIn &= ~theFixedRetBuffMask(CorInfoCallConvExtension::Swift);
}

if (compiler->lvaSwiftErrorArg != BAD_VAR_NUM)
{
intRegState.rsCalleeRegArgMaskLiveIn &= ~RBM_SWIFT_ERROR;
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -3928,6 +3928,7 @@ class Compiler

#ifdef SWIFT_SUPPORT
unsigned lvaSwiftSelfArg;
unsigned lvaSwiftIndirectResultArg;
unsigned lvaSwiftErrorArg;
unsigned lvaSwiftErrorLocal;
#endif
Expand Down
80 changes: 56 additions & 24 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2024,12 +2024,13 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
{
JITDUMP("Creating args for Swift call [%06u]\n", dspTreeID(call));

unsigned short swiftErrorIndex = sig->numArgs;
unsigned short swiftSelfIndex = sig->numArgs;
unsigned swiftErrorIndex = UINT_MAX;
unsigned swiftSelfIndex = UINT_MAX;
unsigned swiftIndirectResultIndex = UINT_MAX;

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

// Check the signature of the Swift call for the special types
CORINFO_ARG_LIST_HANDLE sigArg = sig->args;
Expand Down Expand Up @@ -2066,13 +2067,15 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
BADCODE("Expected SwiftError pointer/reference, got struct");
}

if (swiftErrorIndex != sig->numArgs)
if (swiftErrorIndex != UINT_MAX)
{
BADCODE("Duplicate SwiftError* parameter");
}

swiftErrorIndex = argIndex;
checkEntireStack = true;
swiftErrorIndex = argIndex;
// Spill entire stack as we will need to reuse the error
// argument after the call.
spillStack = true;
}
else if ((strcmp(className, "SwiftSelf") == 0) &&
(strcmp(namespaceName, "System.Runtime.InteropServices.Swift") == 0))
Expand All @@ -2083,7 +2086,7 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
BADCODE("Expected SwiftSelf struct, got pointer/reference");
}

if (swiftSelfIndex != sig->numArgs)
if (swiftSelfIndex != UINT_MAX)
{
BADCODE("Duplicate SwiftSelf parameter");
}
Expand All @@ -2100,7 +2103,7 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
BADCODE("Expected SwiftSelf<T> struct, got pointer/reference");
}

if (swiftSelfIndex != sig->numArgs)
if (swiftSelfIndex != UINT_MAX)
{
BADCODE("Duplicate SwiftSelf parameter");
}
Expand All @@ -2119,6 +2122,30 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,

swiftSelfIndex = argIndex;
}
else if ((strcmp(className, "SwiftIndirectResult") == 0) &&
(strcmp(namespaceName, "System.Runtime.InteropServices.Swift") == 0))
{
if (argIsByrefOrPtr)
{
BADCODE("Expected SwiftIndirectResult struct, got pointer/reference");
}

if (sig->retType != CORINFO_TYPE_VOID)
{
BADCODE("Functions with SwiftIndirectResult arguments must return void");
}

if (swiftIndirectResultIndex != UINT_MAX)
{
BADCODE("Duplicate SwiftIndirectResult argument");
}

swiftIndirectResultIndex = argIndex;

// We will move this arg to the beginning of the arg list, so
// we must spill due to this potential reordering of arguments.
spillStack = true;
}
// TODO: Handle SwiftAsync
}

Expand All @@ -2127,12 +2154,6 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
continue;
}

if (argIndex != swiftSelfIndex)
{
// This is a struct type. Check if it needs to be lowered.
// TODO-Bug: SIMD types are not handled correctly by this.
}

// We must spill this struct to a local to be able to expand it into primitives.
GenTree* node = impStackTop(sig->numArgs - 1 - argIndex).val;
if (!node->OperIsLocalRead())
Expand All @@ -2145,9 +2166,7 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
}
}

// If using SwiftError*, spill entire stack as we will need to reuse the
// error argument after the call.
if (checkEntireStack)
if (spillStack)
{
impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("Spill for swift call"));
}
Expand All @@ -2160,7 +2179,7 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,

// Get SwiftError* arg (if it exists) before modifying the arg list
CallArg* const swiftErrorArg =
(swiftErrorIndex != sig->numArgs) ? call->gtArgs.GetArgByIndex(swiftErrorIndex) : nullptr;
(swiftErrorIndex != UINT_MAX) ? call->gtArgs.GetArgByIndex(swiftErrorIndex) : nullptr;

// Now expand struct args that must be lowered into primitives
unsigned argIndex = 0;
Expand All @@ -2183,15 +2202,28 @@ void Compiler::impPopArgsForSwiftCall(GenTreeCall* call, CORINFO_SIG_INFO* sig,
GenTreeLclVarCommon* structVal = arg->GetNode()->AsLclVarCommon();

CallArg* insertAfter = arg;
// 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) && (selfType == NO_CLASS_HANDLE))
// For the self/indirect result args, change them to a TYP_I_IMPL
// primitive directly. They must also be marked as a well known arg
// because they have a non-standard calling convention.
if (((argIndex == swiftSelfIndex) && (selfType == NO_CLASS_HANDLE)) || (argIndex == swiftIndirectResultIndex))
{
assert(arg->GetNode()->OperIsLocalRead());
GenTree* primitiveSelf = gtNewLclFldNode(structVal->GetLclNum(), TYP_I_IMPL, structVal->GetLclOffs());
NewCallArg newArg = NewCallArg::Primitive(primitiveSelf, TYP_I_IMPL).WellKnown(WellKnownArg::SwiftSelf);
insertAfter = call->gtArgs.InsertAfter(this, insertAfter, newArg);
NewCallArg newArg = NewCallArg::Primitive(primitiveSelf, TYP_I_IMPL);
if (argIndex == swiftSelfIndex)
{
newArg = newArg.WellKnown(WellKnownArg::SwiftSelf);
insertAfter = call->gtArgs.InsertAfter(this, insertAfter, newArg);
}
else
{
// We move the retbuf to the beginning of the arg list to make
// the call follow the same invariant as other calls with
// retbufs.
newArg = newArg.WellKnown(WellKnownArg::RetBuffer);
call->gtArgs.PushFront(this, newArg);
call->gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG;
}
}
else
{
Expand Down
37 changes: 35 additions & 2 deletions src/coreclr/jit/lclvars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ void Compiler::lvaInit()
lvaRetAddrVar = BAD_VAR_NUM;

#ifdef SWIFT_SUPPORT
lvaSwiftSelfArg = BAD_VAR_NUM;
lvaSwiftErrorArg = BAD_VAR_NUM;
lvaSwiftSelfArg = BAD_VAR_NUM;
lvaSwiftIndirectResultArg = BAD_VAR_NUM;
lvaSwiftErrorArg = BAD_VAR_NUM;
#endif

lvaInlineeReturnSpillTemp = BAD_VAR_NUM;
Expand Down Expand Up @@ -1443,6 +1444,34 @@ bool Compiler::lvaInitSpecialSwiftParam(CORINFO_ARG_LIST_HANDLE argHnd,
return true;
}

if ((strcmp(className, "SwiftIndirectResult") == 0) &&
(strcmp(namespaceName, "System.Runtime.InteropServices.Swift") == 0))
{
if (argIsByrefOrPtr)
{
BADCODE("Expected SwiftIndirectResult struct, got pointer/reference");
}

if (info.compRetType != TYP_VOID)
{
BADCODE("Functions with SwiftIndirectResult parameters must return void");
}

if (lvaSwiftIndirectResultArg != BAD_VAR_NUM)
{
BADCODE("Duplicate SwiftIndirectResult parameter");
}

LclVarDsc* const varDsc = varDscInfo->varDsc;
varDsc->SetArgReg(theFixedRetBuffReg(CorInfoCallConvExtension::Swift));
varDsc->lvIsRegArg = true;

compArgSize += TARGET_POINTER_SIZE;

lvaSwiftIndirectResultArg = varDscInfo->varNum;
return true;
}

if ((strcmp(className, "SwiftError") == 0) && (strcmp(namespaceName, "System.Runtime.InteropServices.Swift") == 0))
{
if (!argIsByrefOrPtr)
Expand Down Expand Up @@ -1709,6 +1738,10 @@ void Compiler::lvaClassifyParameterABI(Classifier& classifier)
{
wellKnownArg = WellKnownArg::SwiftSelf;
}
else if (i == lvaSwiftIndirectResultArg)
{
wellKnownArg = WellKnownArg::RetBuffer;
}
else if (i == lvaSwiftErrorArg)
{
wellKnownArg = WellKnownArg::SwiftError;
Expand Down
7 changes: 4 additions & 3 deletions src/tests/Interop/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,11 @@ endif(CLR_CMAKE_TARGET_UNIX)
if(CLR_CMAKE_TARGET_APPLE)
add_subdirectory(ObjectiveC/AutoReleaseTest)
add_subdirectory(ObjectiveC/ObjectiveCMarshalAPI)
add_subdirectory(Swift/SwiftAbiStress)
add_subdirectory(Swift/SwiftCallbackAbiStress)
add_subdirectory(Swift/SwiftErrorHandling)
add_subdirectory(Swift/SwiftSelfContext)
add_subdirectory(Swift/SwiftIndirectResult)
add_subdirectory(Swift/SwiftInvalidCallConv)
add_subdirectory(Swift/SwiftAbiStress)
add_subdirectory(Swift/SwiftRetAbiStress)
add_subdirectory(Swift/SwiftCallbackAbiStress)
add_subdirectory(Swift/SwiftSelfContext)
endif()
21 changes: 21 additions & 0 deletions src/tests/Interop/Swift/SwiftIndirectResult/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
project(SwiftIndirectResult)
include ("${CLR_INTEROP_TEST_ROOT}/Interop.cmake")

set(SOURCE SwiftIndirectResult)

if (NOT SWIFT_COMPILER_TARGET AND CLR_CMAKE_TARGET_OSX)
set(SWIFT_PLATFORM "macosx")
set(SWIFT_PLATFORM_SUFFIX "")
set(SWIFT_DEPLOYMENT_TARGET ${CMAKE_OSX_DEPLOYMENT_TARGET})
set(SWIFT_COMPILER_TARGET "${CMAKE_OSX_ARCHITECTURES}-apple-${SWIFT_PLATFORM}${SWIFT_DEPLOYMENT_TARGET}${SWIFT_PLATFORM_SUFFIX}")
endif()

add_custom_target(${SOURCE} ALL
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"
)

install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lib${SOURCE}.dylib
DESTINATION bin
)
53 changes: 53 additions & 0 deletions src/tests/Interop/Swift/SwiftIndirectResult/SwiftIndirectResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Swift;
using Xunit;

public unsafe class SwiftIndirectResultTests
{
private struct NonFrozenStruct
{
public int A;
public int B;
public int C;
}

private const string SwiftLib = "libSwiftIndirectResult.dylib";

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport(SwiftLib, EntryPoint = "$s19SwiftIndirectResult21ReturnNonFrozenStruct1a1b1cAA0efG0Vs5Int32V_A2ItF")]
public static extern void ReturnNonFrozenStruct(SwiftIndirectResult result, int a, int b, int c);

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport(SwiftLib, EntryPoint = "$s19SwiftIndirectResult26SumReturnedNonFrozenStruct1fs5Int32VAA0fgH0VyXE_tF")]
public static extern int SumReturnedNonFrozenStruct(delegate* unmanaged[Swift]<SwiftIndirectResult, SwiftSelf, void> func, void* funcContext);

[Fact]
public static void TestReturnNonFrozenStruct()
{
// In normal circumstances this instance would have unknown/dynamically determined size.
NonFrozenStruct instance;
ReturnNonFrozenStruct(new SwiftIndirectResult(&instance), 10, 20, 30);
Assert.Equal(10, instance.A);
Assert.Equal(20, instance.B);
Assert.Equal(30, instance.C);
}

[UnmanagedCallersOnly(CallConvs = new Type[] { typeof(CallConvSwift) })]
private static void ReversePInvokeReturnNonFrozenStruct(SwiftIndirectResult result, SwiftSelf self)
{
// In normal circumstances this would require using dynamically sized memcpy and members to create the struct.
*(NonFrozenStruct*)result.Value = new NonFrozenStruct { A = 10, B = 20, C = 30 };
}

[Fact]
public static void TestSumReturnedNonFrozenStruct()
{
int result = SumReturnedNonFrozenStruct(&ReversePInvokeReturnNonFrozenStruct, null);
Assert.Equal(60, result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Needed for CLRTestTargetUnsupported, CMakeProjectReference -->
<RequiresProcessIsolation>true</RequiresProcessIsolation>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- Swift interop is supported on Apple platforms only -->
<CLRTestTargetUnsupported Condition="'$(TargetsOSX)' != 'true' and '$(TargetsAppleMobile)' != 'true'">true</CLRTestTargetUnsupported>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(TestLibraryProjectPath)" />
<CMakeProjectReference Include="CMakeLists.txt" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

public struct NonFrozenStruct
{
let a : Int32;
let b : Int32;
let c : Int32;
}

public func ReturnNonFrozenStruct(a: Int32, b: Int32, c: Int32) -> NonFrozenStruct {
return NonFrozenStruct(a: a, b: b, c: c)
}

public func SumReturnedNonFrozenStruct(f: () -> NonFrozenStruct) -> Int32 {
let s = f()
return s.a + s.b + s.c
}
3 changes: 3 additions & 0 deletions src/tests/issues.targets
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,9 @@
<ExcludeList Include="$(XunitTestBinBase)/Interop/Swift/SwiftCallbackAbiStress/**">
<Issue>https://github.com/dotnet/runtime/issues/93631: Swift reverse pinvokes are not implemented on Mono yet</Issue>
</ExcludeList>
<ExcludeList Include="$(XunitTestBinBase)/Interop/Swift/SwiftIndirectResult/**">
<Issue>https://github.com/dotnet/runtime/issues/93631: SwiftIndirectResult is not implemented on Mono yet</Issue>
</ExcludeList>
</ItemGroup>

<!-- Known failures for mono runtime on Windows -->
Expand Down

0 comments on commit 35437dd

Please sign in to comment.