Skip to content

Commit

Permalink
Merge pull request #214 from DougGregor/c-type-lowering
Browse files Browse the repository at this point in the history
Introduce staged lowering of Swift to `@_cdecl` Swift to C
  • Loading branch information
DougGregor authored Feb 5, 2025
2 parents 98129ae + e7bd5d0 commit 86b44fa
Show file tree
Hide file tree
Showing 22 changed files with 2,058 additions and 742 deletions.
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

0 comments on commit 86b44fa

Please sign in to comment.