Skip to content

Commit 86b44fa

Browse files
authored
Merge pull request #214 from DougGregor/c-type-lowering
Introduce staged lowering of Swift to `@_cdecl` Swift to C
2 parents 98129ae + e7bd5d0 commit 86b44fa

22 files changed

+2058
-742
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
extension ConversionStep {
16+
/// Produce a conversion that takes in a value (or set of values) that
17+
/// would be available in a @_cdecl function to represent the given Swift
18+
/// type, and convert that to an instance of the Swift type.
19+
init(cdeclToSwift swiftType: SwiftType) throws {
20+
// If there is a 1:1 mapping between this Swift type and a C type, then
21+
// there is no translation to do.
22+
if let cType = try? CType(cdeclType: swiftType) {
23+
_ = cType
24+
self = .placeholder
25+
return
26+
}
27+
28+
switch swiftType {
29+
case .function, .optional:
30+
throw LoweringError.unhandledType(swiftType)
31+
32+
case .metatype(let instanceType):
33+
self = .unsafeCastPointer(
34+
.placeholder,
35+
swiftType: instanceType
36+
)
37+
38+
case .nominal(let nominal):
39+
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
40+
// Typed pointers
41+
if let firstGenericArgument = nominal.genericArguments?.first {
42+
switch knownType {
43+
case .unsafePointer, .unsafeMutablePointer:
44+
self = .typedPointer(
45+
.explodedComponent(.placeholder, component: "pointer"),
46+
swiftType: firstGenericArgument
47+
)
48+
return
49+
50+
case .unsafeBufferPointer, .unsafeMutableBufferPointer:
51+
self = .initialize(
52+
swiftType,
53+
arguments: [
54+
LabeledArgument(
55+
label: "start",
56+
argument: .typedPointer(
57+
.explodedComponent(.placeholder, component: "pointer"),
58+
swiftType: firstGenericArgument)
59+
),
60+
LabeledArgument(
61+
label: "count",
62+
argument: .explodedComponent(.placeholder, component: "count")
63+
)
64+
]
65+
)
66+
return
67+
68+
default:
69+
break
70+
}
71+
}
72+
}
73+
74+
// Arbitrary nominal types.
75+
switch nominal.nominalTypeDecl.kind {
76+
case .actor, .class:
77+
// For actor and class, we pass around the pointer directly.
78+
self = .unsafeCastPointer(.placeholder, swiftType: swiftType)
79+
case .enum, .struct, .protocol:
80+
// For enums, structs, and protocol types, we pass around the
81+
// values indirectly.
82+
self = .passIndirectly(
83+
.pointee(.typedPointer(.placeholder, swiftType: swiftType))
84+
)
85+
}
86+
87+
case .tuple(let elements):
88+
self = .tuplify(try elements.map { try ConversionStep(cdeclToSwift: $0) })
89+
}
90+
}
91+
92+
/// Produce a conversion that takes in a value that would be available in a
93+
/// Swift function and convert that to the corresponding cdecl values.
94+
///
95+
/// This conversion goes in the opposite direction of init(cdeclToSwift:), and
96+
/// is used for (e.g.) returning the Swift value from a cdecl function. When
97+
/// there are multiple cdecl values that correspond to this one Swift value,
98+
/// the result will be a tuple that can be assigned to a tuple of the cdecl
99+
/// values, e.g., (<placeholder>.baseAddress, <placeholder>.count).
100+
init(
101+
swiftToCDecl swiftType: SwiftType,
102+
stdlibTypes: SwiftStandardLibraryTypes
103+
) throws {
104+
// If there is a 1:1 mapping between this Swift type and a C type, then
105+
// there is no translation to do.
106+
if let cType = try? CType(cdeclType: swiftType) {
107+
_ = cType
108+
self = .placeholder
109+
return
110+
}
111+
112+
switch swiftType {
113+
case .function, .optional:
114+
throw LoweringError.unhandledType(swiftType)
115+
116+
case .metatype:
117+
self = .unsafeCastPointer(
118+
.placeholder,
119+
swiftType: .nominal(
120+
SwiftNominalType(
121+
nominalTypeDecl: stdlibTypes[.unsafeRawPointer]
122+
)
123+
)
124+
)
125+
return
126+
127+
case .nominal(let nominal):
128+
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
129+
// Typed pointers
130+
if nominal.genericArguments?.first != nil {
131+
switch knownType {
132+
case .unsafePointer, .unsafeMutablePointer:
133+
let isMutable = knownType == .unsafeMutablePointer
134+
self = ConversionStep(
135+
initializeRawPointerFromTyped: .placeholder,
136+
isMutable: isMutable,
137+
isPartOfBufferPointer: false,
138+
stdlibTypes: stdlibTypes
139+
)
140+
return
141+
142+
case .unsafeBufferPointer, .unsafeMutableBufferPointer:
143+
let isMutable = knownType == .unsafeMutableBufferPointer
144+
self = .tuplify(
145+
[
146+
ConversionStep(
147+
initializeRawPointerFromTyped: .explodedComponent(
148+
.placeholder,
149+
component: "pointer"
150+
),
151+
isMutable: isMutable,
152+
isPartOfBufferPointer: true,
153+
stdlibTypes: stdlibTypes
154+
),
155+
.explodedComponent(.placeholder, component: "count")
156+
]
157+
)
158+
return
159+
160+
default:
161+
break
162+
}
163+
}
164+
}
165+
166+
// Arbitrary nominal types.
167+
switch nominal.nominalTypeDecl.kind {
168+
case .actor, .class:
169+
// For actor and class, we pass around the pointer directly. Case to
170+
// the unsafe raw pointer type we use to represent it in C.
171+
self = .unsafeCastPointer(
172+
.placeholder,
173+
swiftType: .nominal(
174+
SwiftNominalType(
175+
nominalTypeDecl: stdlibTypes[.unsafeRawPointer]
176+
)
177+
)
178+
)
179+
180+
case .enum, .struct, .protocol:
181+
// For enums, structs, and protocol types, we leave the value alone.
182+
// The indirection will be handled by the caller.
183+
self = .placeholder
184+
}
185+
186+
case .tuple(let elements):
187+
// Convert all of the elements.
188+
self = .tuplify(
189+
try elements.map { element in
190+
try ConversionStep(swiftToCDecl: element, stdlibTypes: stdlibTypes)
191+
}
192+
)
193+
}
194+
}
195+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
extension CType {
16+
/// Lower the given Swift type down to a its corresponding C type.
17+
///
18+
/// This operation only supports the subset of Swift types that are
19+
/// representable in a Swift `@_cdecl` function, which means that they are
20+
/// also directly representable in C. If lowering an arbitrary Swift
21+
/// function, first go through Swift -> cdecl lowering. This function
22+
/// will throw an error if it encounters a type that is not expressible in
23+
/// C.
24+
init(cdeclType: SwiftType) throws {
25+
switch cdeclType {
26+
case .nominal(let nominalType):
27+
if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType,
28+
let primitiveCType = knownType.primitiveCType {
29+
self = primitiveCType
30+
return
31+
}
32+
33+
throw CDeclToCLoweringError.invalidNominalType(nominalType.nominalTypeDecl)
34+
35+
case .function(let functionType):
36+
switch functionType.convention {
37+
case .swift:
38+
throw CDeclToCLoweringError.invalidFunctionConvention(functionType)
39+
40+
case .c:
41+
let resultType = try CType(cdeclType: functionType.resultType)
42+
let parameterTypes = try functionType.parameters.map { param in
43+
try CType(cdeclType: param.type)
44+
}
45+
46+
self = .function(
47+
resultType: resultType,
48+
parameters: parameterTypes,
49+
variadic: false
50+
)
51+
}
52+
53+
case .tuple([]):
54+
self = .void
55+
56+
case .metatype, .optional, .tuple:
57+
throw CDeclToCLoweringError.invalidCDeclType(cdeclType)
58+
}
59+
}
60+
}
61+
62+
extension CFunction {
63+
/// Produce a C function that represents the given @_cdecl Swift function.
64+
init(cdeclSignature: SwiftFunctionSignature, cName: String) throws {
65+
assert(cdeclSignature.selfParameter == nil)
66+
67+
let cResultType = try CType(cdeclType: cdeclSignature.result.type)
68+
let cParameters = try cdeclSignature.parameters.map { parameter in
69+
CParameter(
70+
name: parameter.parameterName,
71+
type: try CType(cdeclType: parameter.type).parameterDecay
72+
)
73+
}
74+
75+
self = CFunction(
76+
resultType: cResultType,
77+
name: cName,
78+
parameters: cParameters,
79+
isVariadic: false
80+
)
81+
}
82+
}
83+
84+
enum CDeclToCLoweringError: Error {
85+
case invalidCDeclType(SwiftType)
86+
case invalidNominalType(SwiftNominalTypeDeclaration)
87+
case invalidFunctionConvention(SwiftFunctionType)
88+
}
89+
90+
extension KnownStandardLibraryType {
91+
/// Determine the primitive C type that corresponds to this C standard
92+
/// library type, if there is one.
93+
var primitiveCType: CType? {
94+
switch self {
95+
case .bool: .integral(.bool)
96+
case .int: .integral(.ptrdiff_t)
97+
case .uint: .integral(.size_t)
98+
case .int8: .integral(.signed(bits: 8))
99+
case .uint8: .integral(.unsigned(bits: 8))
100+
case .int16: .integral(.signed(bits: 16))
101+
case .uint16: .integral(.unsigned(bits: 16))
102+
case .int32: .integral(.signed(bits: 32))
103+
case .uint32: .integral(.unsigned(bits: 32))
104+
case .int64: .integral(.signed(bits: 64))
105+
case .uint64: .integral(.unsigned(bits: 64))
106+
case .float: .floating(.float)
107+
case .double: .floating(.double)
108+
case .unsafeMutableRawPointer: .pointer(.void)
109+
case .unsafeRawPointer: .pointer(
110+
.qualified(const: true, volatile: false, type: .void)
111+
)
112+
case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer:
113+
nil
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)