Skip to content

Commit c6066af

Browse files
committed
Leverage the direct Swift cdecl type <-> C type mapping for lowering
When lowering a Swift function to a cdecl thunk, detect when a given Swift type is exactly representable in C. In such cases, just keep the type as written and pass it through without modification. Use this to allow `@convention(c)` functions to go through untouched.
1 parent e11dcea commit c6066af

File tree

7 files changed

+68
-36
lines changed

7 files changed

+68
-36
lines changed

Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ extension ConversionStep {
1717
/// would be available in a @_cdecl function to represent the given Swift
1818
/// type, and convert that to an instance of the Swift type.
1919
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+
2028
switch swiftType {
2129
case .function, .optional:
2230
throw LoweringError.unhandledType(swiftType)
@@ -29,13 +37,6 @@ extension ConversionStep {
2937

3038
case .nominal(let nominal):
3139
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
32-
// Swift types that map to primitive types in C. These can be passed
33-
// through directly.
34-
if knownType.primitiveCType != nil {
35-
self = .placeholder
36-
return
37-
}
38-
3940
// Typed pointers
4041
if let firstGenericArgument = nominal.genericArguments?.first {
4142
switch knownType {
@@ -100,6 +101,14 @@ extension ConversionStep {
100101
swiftToCDecl swiftType: SwiftType,
101102
stdlibTypes: SwiftStandardLibraryTypes
102103
) 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+
103112
switch swiftType {
104113
case .function, .optional:
105114
throw LoweringError.unhandledType(swiftType)
@@ -117,13 +126,6 @@ extension ConversionStep {
117126

118127
case .nominal(let nominal):
119128
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
120-
// Swift types that map to primitive types in C. These can be passed
121-
// through directly.
122-
if knownType.primitiveCType != nil {
123-
self = .placeholder
124-
return
125-
}
126-
127129
// Typed pointers
128130
if nominal.genericArguments?.first != nil {
129131
switch knownType {

Sources/JExtractSwift/CDeclLowering/CRepresentation.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ extension CType {
1616
/// Lower the given Swift type down to a its corresponding C type.
1717
///
1818
/// This operation only supports the subset of Swift types that are
19-
/// representable in a Swift `@_cdecl` function. If lowering an arbitrary
20-
/// Swift function, first go through Swift -> cdecl lowering.
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.
2124
init(cdeclType: SwiftType) throws {
2225
switch cdeclType {
2326
case .nominal(let nominalType):

Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,22 @@ extension Swift2JavaTranslator {
155155
convention: SwiftParameterConvention,
156156
parameterName: String
157157
) throws -> LoweredParameters {
158+
// If there is a 1:1 mapping between this Swift type and a C type, we just
159+
// need to add the corresponding C [arameter.
160+
if let cType = try? CType(cdeclType: type), convention != .inout {
161+
_ = cType
162+
return LoweredParameters(
163+
cdeclParameters: [
164+
SwiftParameter(
165+
convention: convention,
166+
parameterName: parameterName,
167+
type: type,
168+
canBeDirectReturn: true
169+
)
170+
]
171+
)
172+
}
173+
158174
switch type {
159175
case .function, .optional:
160176
throw LoweringError.unhandledType(type)
@@ -179,21 +195,6 @@ extension Swift2JavaTranslator {
179195
// Types from the Swift standard library that we know about.
180196
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType,
181197
convention != .inout {
182-
// Swift types that map to primitive types in C. These can be passed
183-
// through directly.
184-
if knownType.primitiveCType != nil {
185-
return LoweredParameters(
186-
cdeclParameters: [
187-
SwiftParameter(
188-
convention: convention,
189-
parameterName: parameterName,
190-
type: type,
191-
canBeDirectReturn: true
192-
)
193-
]
194-
)
195-
}
196-
197198
// Typed pointers are mapped down to their raw forms in cdecl entry
198199
// points. These can be passed through directly.
199200
if knownType == .unsafePointer || knownType == .unsafeMutablePointer {

Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ struct SwiftFunctionType: Equatable {
2727

2828
extension SwiftFunctionType: CustomStringConvertible {
2929
var description: String {
30-
return "(\(parameters.map { $0.descriptionInType } )) -> \(resultType.description)"
30+
let parameterString = parameters.map { $0.descriptionInType }.joined(separator: ", ")
31+
let conventionPrefix = switch convention {
32+
case .c: "@convention(c) "
33+
case .swift: ""
34+
}
35+
return "\(conventionPrefix)(\(parameterString)) -> \(resultType.description)"
3136
}
3237
}
3338

Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ extension SwiftParameter {
6464
var type = node.type
6565
var convention = SwiftParameterConvention.byValue
6666
if let attributedType = type.as(AttributedTypeSyntax.self) {
67+
var sawUnknownSpecifier = false
6768
for specifier in attributedType.specifiers {
6869
guard case .simpleTypeSpecifier(let simple) = specifier else {
70+
sawUnknownSpecifier = true
6971
continue
7072
}
7173

@@ -75,13 +77,15 @@ extension SwiftParameter {
7577
case .keyword(.inout):
7678
convention = .inout
7779
default:
80+
sawUnknownSpecifier = true
7881
break
7982
}
8083
}
8184

8285
// Ignore anything else in the attributed type.
83-
// FIXME: We might want to check for these and ignore them.
84-
type = attributedType.baseType
86+
if !sawUnknownSpecifier && attributedType.attributes.isEmpty {
87+
type = attributedType.baseType
88+
}
8589
}
8690
self.convention = convention
8791

Sources/JExtractSwift/SwiftTypes/SwiftType.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,12 @@ extension SwiftType {
129129
// Only recognize the "@convention(c)" and "@convention(swift)" attributes, and
130130
// then only on function types.
131131
// FIXME: This string matching is a horrible hack.
132-
switch attributedType.trimmedDescription {
132+
switch attributedType.attributes.trimmedDescription {
133133
case "@convention(c)", "@convention(swift)":
134134
let innerType = try SwiftType(attributedType.baseType, symbolTable: symbolTable)
135135
switch innerType {
136136
case .function(var functionType):
137-
let isConventionC = attributedType.trimmedDescription == "@convention(c)"
137+
let isConventionC = attributedType.attributes.trimmedDescription == "@convention(c)"
138138
let convention: SwiftFunctionType.Convention = isConventionC ? .c : .swift
139139
functionType.convention = convention
140140
self = .function(functionType)

Tests/JExtractSwiftTests/FunctionLoweringTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,21 @@ final class FunctionLoweringTests {
296296
expectedCFunction: "c_getBufferPointer(void* _result_pointer, void* _result_count)"
297297
)
298298
}
299+
300+
@Test("Lowering C function types")
301+
func lowerFunctionTypes() throws {
302+
// FIXME: C pretty printing isn't handling parameters of function pointer
303+
// type yet.
304+
try assertLoweredFunction("""
305+
func doSomething(body: @convention(c) (Int32) -> Double) { }
306+
""",
307+
expectedCDecl: """
308+
@_cdecl("c_doSomething")
309+
public func c_doSomething(_ body: @convention(c) (Int32) -> Double) {
310+
doSomething(body: body)
311+
}
312+
""",
313+
expectedCFunction: "void c_doSomething(double* body(int32_t))"
314+
)
315+
}
299316
}

0 commit comments

Comments
 (0)