Skip to content

Introduce staged lowering of Swift to @_cdecl Swift to C #214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

extension ConversionStep {
/// Produce a conversion that takes in a value (or set of values) that
/// would be available in a @_cdecl function to represent the given Swift
/// type, and convert that to an instance of the Swift type.
init(cdeclToSwift swiftType: SwiftType) throws {
// If there is a 1:1 mapping between this Swift type and a C type, then
// there is no translation to do.
if let cType = try? CType(cdeclType: swiftType) {
_ = cType
self = .placeholder
return
}

switch swiftType {
case .function, .optional:
throw LoweringError.unhandledType(swiftType)

case .metatype(let instanceType):
self = .unsafeCastPointer(
.placeholder,
swiftType: instanceType
)

case .nominal(let nominal):
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
// Typed pointers
if let firstGenericArgument = nominal.genericArguments?.first {
switch knownType {
case .unsafePointer, .unsafeMutablePointer:
self = .typedPointer(
.explodedComponent(.placeholder, component: "pointer"),
swiftType: firstGenericArgument
)
return

case .unsafeBufferPointer, .unsafeMutableBufferPointer:
self = .initialize(
swiftType,
arguments: [
LabeledArgument(
label: "start",
argument: .typedPointer(
.explodedComponent(.placeholder, component: "pointer"),
swiftType: firstGenericArgument)
),
LabeledArgument(
label: "count",
argument: .explodedComponent(.placeholder, component: "count")
)
]
)
return

default:
break
}
}
}

// Arbitrary nominal types.
switch nominal.nominalTypeDecl.kind {
case .actor, .class:
// For actor and class, we pass around the pointer directly.
self = .unsafeCastPointer(.placeholder, swiftType: swiftType)
case .enum, .struct, .protocol:
// For enums, structs, and protocol types, we pass around the
// values indirectly.
self = .passIndirectly(
.pointee(.typedPointer(.placeholder, swiftType: swiftType))
)
}

case .tuple(let elements):
self = .tuplify(try elements.map { try ConversionStep(cdeclToSwift: $0) })
}
}

/// Produce a conversion that takes in a value that would be available in a
/// Swift function and convert that to the corresponding cdecl values.
///
/// This conversion goes in the opposite direction of init(cdeclToSwift:), and
/// is used for (e.g.) returning the Swift value from a cdecl function. When
/// there are multiple cdecl values that correspond to this one Swift value,
/// the result will be a tuple that can be assigned to a tuple of the cdecl
/// values, e.g., (<placeholder>.baseAddress, <placeholder>.count).
init(
swiftToCDecl swiftType: SwiftType,
stdlibTypes: SwiftStandardLibraryTypes
) throws {
// If there is a 1:1 mapping between this Swift type and a C type, then
// there is no translation to do.
if let cType = try? CType(cdeclType: swiftType) {
_ = cType
self = .placeholder
return
}

switch swiftType {
case .function, .optional:
throw LoweringError.unhandledType(swiftType)

case .metatype:
self = .unsafeCastPointer(
.placeholder,
swiftType: .nominal(
SwiftNominalType(
nominalTypeDecl: stdlibTypes[.unsafeRawPointer]
)
)
)
return

case .nominal(let nominal):
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
// Typed pointers
if nominal.genericArguments?.first != nil {
switch knownType {
case .unsafePointer, .unsafeMutablePointer:
let isMutable = knownType == .unsafeMutablePointer
self = ConversionStep(
initializeRawPointerFromTyped: .placeholder,
isMutable: isMutable,
isPartOfBufferPointer: false,
stdlibTypes: stdlibTypes
)
return

case .unsafeBufferPointer, .unsafeMutableBufferPointer:
let isMutable = knownType == .unsafeMutableBufferPointer
self = .tuplify(
[
ConversionStep(
initializeRawPointerFromTyped: .explodedComponent(
.placeholder,
component: "pointer"
),
isMutable: isMutable,
isPartOfBufferPointer: true,
stdlibTypes: stdlibTypes
),
.explodedComponent(.placeholder, component: "count")
]
)
return

default:
break
}
}
}

// Arbitrary nominal types.
switch nominal.nominalTypeDecl.kind {
case .actor, .class:
// For actor and class, we pass around the pointer directly. Case to
// the unsafe raw pointer type we use to represent it in C.
self = .unsafeCastPointer(
.placeholder,
swiftType: .nominal(
SwiftNominalType(
nominalTypeDecl: stdlibTypes[.unsafeRawPointer]
)
)
)

case .enum, .struct, .protocol:
// For enums, structs, and protocol types, we leave the value alone.
// The indirection will be handled by the caller.
self = .placeholder
}

case .tuple(let elements):
// Convert all of the elements.
self = .tuplify(
try elements.map { element in
try ConversionStep(swiftToCDecl: element, stdlibTypes: stdlibTypes)
}
)
}
}
}
116 changes: 116 additions & 0 deletions Sources/JExtractSwift/CDeclLowering/CRepresentation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

extension CType {
/// Lower the given Swift type down to a its corresponding C type.
///
/// This operation only supports the subset of Swift types that are
/// representable in a Swift `@_cdecl` function, which means that they are
/// also directly representable in C. If lowering an arbitrary Swift
/// function, first go through Swift -> cdecl lowering. This function
/// will throw an error if it encounters a type that is not expressible in
/// C.
init(cdeclType: SwiftType) throws {
switch cdeclType {
case .nominal(let nominalType):
if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType,
let primitiveCType = knownType.primitiveCType {
self = primitiveCType
return
}

throw CDeclToCLoweringError.invalidNominalType(nominalType.nominalTypeDecl)

case .function(let functionType):
switch functionType.convention {
case .swift:
throw CDeclToCLoweringError.invalidFunctionConvention(functionType)

case .c:
let resultType = try CType(cdeclType: functionType.resultType)
let parameterTypes = try functionType.parameters.map { param in
try CType(cdeclType: param.type)
}

self = .function(
resultType: resultType,
parameters: parameterTypes,
variadic: false
)
}

case .tuple([]):
self = .void

case .metatype, .optional, .tuple:
throw CDeclToCLoweringError.invalidCDeclType(cdeclType)
}
}
}

extension CFunction {
/// Produce a C function that represents the given @_cdecl Swift function.
init(cdeclSignature: SwiftFunctionSignature, cName: String) throws {
assert(cdeclSignature.selfParameter == nil)

let cResultType = try CType(cdeclType: cdeclSignature.result.type)
let cParameters = try cdeclSignature.parameters.map { parameter in
CParameter(
name: parameter.parameterName,
type: try CType(cdeclType: parameter.type).parameterDecay
)
}

self = CFunction(
resultType: cResultType,
name: cName,
parameters: cParameters,
isVariadic: false
)
}
}

enum CDeclToCLoweringError: Error {
case invalidCDeclType(SwiftType)
case invalidNominalType(SwiftNominalTypeDeclaration)
case invalidFunctionConvention(SwiftFunctionType)
}

extension KnownStandardLibraryType {
/// Determine the primitive C type that corresponds to this C standard
/// library type, if there is one.
var primitiveCType: CType? {
switch self {
case .bool: .integral(.bool)
case .int: .integral(.ptrdiff_t)
case .uint: .integral(.size_t)
case .int8: .integral(.signed(bits: 8))
case .uint8: .integral(.unsigned(bits: 8))
case .int16: .integral(.signed(bits: 16))
case .uint16: .integral(.unsigned(bits: 16))
case .int32: .integral(.signed(bits: 32))
case .uint32: .integral(.unsigned(bits: 32))
case .int64: .integral(.signed(bits: 64))
case .uint64: .integral(.unsigned(bits: 64))
case .float: .floating(.float)
case .double: .floating(.double)
case .unsafeMutableRawPointer: .pointer(.void)
case .unsafeRawPointer: .pointer(
.qualified(const: true, volatile: false, type: .void)
)
case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer:
nil
}
}
}
Loading