-
Notifications
You must be signed in to change notification settings - Fork 215
Description
Description
This proposal discusses the projection of Swift generics in C#. The primary goal is to validate the design of projections and integrate CryptoKit APIs for direct invocation from the dotnet/runtime repository. To limit the scope, this discussion specifically focuses on generics use-cases for the CryptoKit dev template. Protocols are considered as constraints within the Swift runtime, which should not affect functionality and they are not covered in this proposal.
Generics in Swift functions
In Swift, when a generic type is used as a parameter, its memory layout might not be known until runtime. To manage this, the generic parameter is represented by an opaque pointer. For object allocation, the runtime utilizes init functions from the value witness table using a metadata pointer. This metadata pointer is included as an implicit argument at the end of the function's signature, enabling the runtime to correctly allocate and manage generic types. When returning a generic type, the runtime utilizes indirect return result mechanism.
Here is a code snippet that illustrates how generics are handled in functions with the corresponding assembly code for arm64:
func returnData<T>(data: T) -> T {
return data
}
As the function returns a generic type, the register x8
is expected to contain the return buffer where the value should be stored. The first parameter x0
is an opaque pointer; the second parameter x1
is the metadata pointer of the type.
00000000000079b0 sub sp, sp, #0x30
00000000000079b4 stp x29, x30, [sp, #0x20]
00000000000079b8 add x29, sp, #0x20
00000000000079bc str x8, [sp]
This block shuffles regs for an object allocation. At the end, the x0
is the return buffer, x1
is the opaque pointer, and x2
is the metadata pointer.
00000000000079c0 mov x2, x0
00000000000079c4 ldr x0, [sp]
00000000000079c8 str x2, [sp, #0x8]
00000000000079cc mov x2, x1
00000000000079d0 ldr x1, [sp, #0x8]
00000000000079d4 str xzr, [sp, #0x10]
00000000000079d8 mov x8, x2
00000000000079dc stur x8, [x29, #-0x8]
00000000000079e0 mov x8, x1
Next, x8
loads a value witness table pointer from -1 offset of the metadata pointer, and then loads the init function initializeBufferWithCopyOfBuffer. This function is invoked with the x0
return buffer , x1
opaque pointer, and x2
metadata pointer.
00000000000079e4 str x8, [sp, #0x10]
00000000000079e8 ldur x8, [x2, #-0x8]
00000000000079ec ldr x8, [x8, #0x10]
00000000000079f0 blr x8
00000000000079f4 ldp x29, x30, [sp, #0x20]
00000000000079f8 add sp, sp, #0x30
00000000000079fc ret
CryptoKit bindings for encryption and decryption
To directly call into CryptoKit APIs, the tooling should surface methods for encryption and decryption with Foundation.Data
type:
static func seal<Plaintext, AuthenticatedData>(
_ message: Plaintext,
using key: SymmetricKey,
nonce: ChaChaPoly.Nonce? = nil,
authenticating authenticatedData: AuthenticatedData
) throws -> ChaChaPoly.SealedBox where Plaintext : DataProtocol, AuthenticatedData : DataProtocol
static func open<AuthenticatedData>(
_ sealedBox: ChaChaPoly.SealedBox,
using key: SymmetricKey,
authenticating authenticatedData: AuthenticatedData
) throws -> Data where AuthenticatedData : DataProtocol
Following the generics implementation described earlier, below are proposed CryptoKit APIs in C#:
[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport("/System/Library/Frameworks/CryptoKit.framework/CryptoKit")]
public unsafe static extern SealedBox PIfunc_seal(void* messagePtr, SymmetricKey key, Nonce nonce, void* authenticatedData, void* messageMetadata, void* aadMetadata);
public unsafe static SealedBox seal<Plaintext, AuthenticatedData>(Plaintext message, SymmetricKey key, Nonce nonce, AuthenticatedData authenticatedData)
{
void* messagePtr = Unsafe.AsPointer(ref message);
void* authenticatedDataPtr = Unsafe.AsPointer(ref authenticatedData);
void* messageMetadata = Swift.Runtime.GetMetadata(message);
void* aadMetadata = Swift.Runtime.GetMetadata(authenticatedData);
return PIfunc_seal(messagePtr, key, nonce, authenticatedDataPtr, messageMetadata, aadMetadata);
}
[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport("/System/Library/Frameworks/CryptoKit.framework/CryptoKit")]
public unsafe static extern Data PIfunc_open(SealedBox sealedBox, SymmetricKey key, void* authenticatedData, void* aadMetadata);
public unsafe static Data open<AuthenticatedData>(SealedBox sealedBox, SymmetricKey key, AuthenticatedData authenticatedData)
{
void* authenticatedDataPtr = Unsafe.AsPointer(ref authenticatedData);
void* aadMetadata = Swift.Runtime.GetMetadata(authenticatedData);
return PIfunc_seal(sealedBox, key, authenticatedDataPtr, aadMetadata);
}
/cc: @jkoritzinsky @AaronRobinsonMSFT @stephen-hawley @rolfbjarne @jkotas