Skip to content

Commit 98129ae

Browse files
authored
Merge pull request #212 from DougGregor/lowered-cdecl-function-bodies
Implement function bodies for lowered @_cdecl thunks
2 parents 831397d + 3d72f9f commit 98129ae

File tree

6 files changed

+327
-41
lines changed

6 files changed

+327
-41
lines changed

Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift

Lines changed: 195 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import JavaTypes
1616
import SwiftSyntax
1717

1818
extension Swift2JavaTranslator {
19+
/// Lower the given function declaration to a C-compatible entrypoint,
20+
/// providing all of the mappings between the parameter and result types
21+
/// of the original function and its `@_cdecl` counterpart.
1922
@_spi(Testing)
2023
public func lowerFunctionSignature(
2124
_ decl: FunctionDeclSyntax,
@@ -124,9 +127,29 @@ extension Swift2JavaTranslator {
124127
parameterName: String
125128
) throws -> LoweredParameters {
126129
switch type {
127-
case .function, .metatype, .optional:
130+
case .function, .optional:
128131
throw LoweringError.unhandledType(type)
129132

133+
case .metatype(let instanceType):
134+
return LoweredParameters(
135+
cdeclToOriginal: .unsafeCastPointer(
136+
.passDirectly(parameterName),
137+
swiftType: instanceType
138+
),
139+
cdeclParameters: [
140+
SwiftParameter(
141+
convention: .byValue,
142+
parameterName: parameterName,
143+
type: .nominal(
144+
SwiftNominalType(
145+
nominalTypeDecl: swiftStdlibTypes.unsafeRawPointerDecl
146+
)
147+
)
148+
)
149+
],
150+
javaFFMParameters: [.SwiftPointer]
151+
)
152+
130153
case .nominal(let nominal):
131154
// Types from the Swift standard library that we know about.
132155
if nominal.nominalTypeDecl.moduleName == "Swift",
@@ -145,8 +168,12 @@ extension Swift2JavaTranslator {
145168
let mutable = (convention == .inout)
146169
let loweringStep: LoweringStep
147170
switch nominal.nominalTypeDecl.kind {
148-
case .actor, .class: loweringStep = .passDirectly(parameterName)
149-
case .enum, .struct, .protocol: loweringStep = .passIndirectly(parameterName)
171+
case .actor, .class:
172+
loweringStep =
173+
.unsafeCastPointer(.passDirectly(parameterName), swiftType: type)
174+
case .enum, .struct, .protocol:
175+
loweringStep =
176+
.passIndirectly(.pointee( .typedPointer(.passDirectly(parameterName), swiftType: type)))
150177
}
151178

152179
return LoweredParameters(
@@ -173,7 +200,7 @@ extension Swift2JavaTranslator {
173200
try lowerParameter(element, convention: convention, parameterName: name)
174201
}
175202
return LoweredParameters(
176-
cdeclToOriginal: .tuplify(parameterNames.map { .passDirectly($0) }),
203+
cdeclToOriginal: .tuplify(loweredElements.map { $0.cdeclToOriginal }),
177204
cdeclParameters: loweredElements.flatMap { $0.cdeclParameters },
178205
javaFFMParameters: loweredElements.flatMap { $0.javaFFMParameters }
179206
)
@@ -258,10 +285,9 @@ extension Swift2JavaTranslator {
258285
cdeclToOriginal = .passDirectly(parameterName)
259286

260287
case (true, false):
261-
// FIXME: Generic arguments, ugh
262-
cdeclToOriginal = .suffixed(
263-
.passDirectly(parameterName),
264-
".assumingMemoryBound(to: \(nominal.genericArguments![0]).self)"
288+
cdeclToOriginal = .typedPointer(
289+
.passDirectly(parameterName + "_pointer"),
290+
swiftType: nominal.genericArguments![0]
265291
)
266292

267293
case (false, true):
@@ -275,9 +301,9 @@ extension Swift2JavaTranslator {
275301
type,
276302
arguments: [
277303
LabeledArgument(label: "start",
278-
argument: .suffixed(
304+
argument: .typedPointer(
279305
.passDirectly(parameterName + "_pointer"),
280-
".assumingMemoryBound(to: \(nominal.genericArguments![0]).self")),
306+
swiftType: nominal.genericArguments![0])),
281307
LabeledArgument(label: "count",
282308
argument: .passDirectly(parameterName + "_count"))
283309
]
@@ -338,30 +364,113 @@ struct LabeledArgument<Element> {
338364

339365
extension LabeledArgument: Equatable where Element: Equatable { }
340366

341-
/// How to lower the Swift parameter
367+
/// Describes the transformation needed to take the parameters of a thunk
368+
/// and map them to the corresponding parameter (or result value) of the
369+
/// original function.
342370
enum LoweringStep: Equatable {
371+
/// A direct reference to a parameter of the thunk.
343372
case passDirectly(String)
344-
case passIndirectly(String)
345-
indirect case suffixed(LoweringStep, String)
373+
374+
/// Cast the pointer described by the lowering step to the given
375+
/// Swift type using `unsafeBitCast(_:to:)`.
376+
indirect case unsafeCastPointer(LoweringStep, swiftType: SwiftType)
377+
378+
/// Assume at the untyped pointer described by the lowering step to the
379+
/// given type, using `assumingMemoryBound(to:).`
380+
indirect case typedPointer(LoweringStep, swiftType: SwiftType)
381+
382+
/// The thing to which the pointer typed, which is the `pointee` property
383+
/// of the `Unsafe(Mutable)Pointer` types in Swift.
384+
indirect case pointee(LoweringStep)
385+
386+
/// Pass this value indirectly, via & for explicit `inout` parameters.
387+
indirect case passIndirectly(LoweringStep)
388+
389+
/// Initialize a value of the given Swift type with the set of labeled
390+
/// arguments.
346391
case initialize(SwiftType, arguments: [LabeledArgument<LoweringStep>])
392+
393+
/// Produce a tuple with the given elements.
394+
///
395+
/// This is used for exploding Swift tuple arguments into multiple
396+
/// elements, recursively. Note that this always produces unlabeled
397+
/// tuples, which Swift will convert to the labeled tuple form.
347398
case tuplify([LoweringStep])
348399
}
349400

350401
struct LoweredParameters: Equatable {
351-
/// The steps needed to get from the @_cdecl parameter to the original function
402+
/// The steps needed to get from the @_cdecl parameters to the original function
352403
/// parameter.
353404
var cdeclToOriginal: LoweringStep
354405

355406
/// The lowering of the parameters at the C level in Swift.
356407
var cdeclParameters: [SwiftParameter]
357408

358-
/// The lowerung of the parmaeters at the C level as expressed for Java's
409+
/// The lowering of the parameters at the C level as expressed for Java's
359410
/// foreign function and memory interface.
360411
///
361412
/// The elements in this array match up with those of 'cdeclParameters'.
362413
var javaFFMParameters: [ForeignValueLayout]
363414
}
364415

416+
extension LoweredParameters {
417+
/// Produce an expression that computes the argument for this parameter
418+
/// when calling the original function from the cdecl entrypoint.
419+
func cdeclToOriginalArgumentExpr(isSelf: Bool)-> ExprSyntax {
420+
cdeclToOriginal.asExprSyntax(isSelf: isSelf)
421+
}
422+
}
423+
424+
extension LoweringStep {
425+
func asExprSyntax(isSelf: Bool) -> ExprSyntax {
426+
switch self {
427+
case .passDirectly(let rawArgument):
428+
return "\(raw: rawArgument)"
429+
430+
case .unsafeCastPointer(let step, swiftType: let swiftType):
431+
let untypedExpr = step.asExprSyntax(isSelf: false)
432+
return "unsafeBitCast(\(untypedExpr), to: \(swiftType.metatypeReferenceExprSyntax))"
433+
434+
case .typedPointer(let step, swiftType: let type):
435+
let untypedExpr = step.asExprSyntax(isSelf: isSelf)
436+
return "\(untypedExpr).assumingMemoryBound(to: \(type.metatypeReferenceExprSyntax))"
437+
438+
case .pointee(let step):
439+
let untypedExpr = step.asExprSyntax(isSelf: isSelf)
440+
return "\(untypedExpr).pointee"
441+
442+
case .passIndirectly(let step):
443+
let innerExpr = step.asExprSyntax(isSelf: false)
444+
return isSelf ? innerExpr : "&\(innerExpr)"
445+
446+
case .initialize(let type, arguments: let arguments):
447+
let renderedArguments: [String] = arguments.map { labeledArgument in
448+
let renderedArg = labeledArgument.argument.asExprSyntax(isSelf: false)
449+
if let argmentLabel = labeledArgument.label {
450+
return "\(argmentLabel): \(renderedArg.description)"
451+
} else {
452+
return renderedArg.description
453+
}
454+
}
455+
456+
// FIXME: Should be able to use structured initializers here instead
457+
// of splatting out text.
458+
let renderedArgumentList = renderedArguments.joined(separator: ", ")
459+
return "\(raw: type.description)(\(raw: renderedArgumentList))"
460+
461+
case .tuplify(let elements):
462+
let renderedElements: [String] = elements.map { element in
463+
element.asExprSyntax(isSelf: false).description
464+
}
465+
466+
// FIXME: Should be able to use structured initializers here instead
467+
// of splatting out text.
468+
let renderedElementList = renderedElements.joined(separator: ", ")
469+
return "(\(raw: renderedElementList))"
470+
}
471+
}
472+
}
473+
365474
enum LoweringError: Error {
366475
case inoutNotSupported(SwiftType)
367476
case unhandledType(SwiftType)
@@ -375,3 +484,74 @@ public struct LoweredFunctionSignature: Equatable {
375484
var parameters: [LoweredParameters]
376485
var result: LoweredParameters
377486
}
487+
488+
extension LoweredFunctionSignature {
489+
/// Produce the `@_cdecl` thunk for this lowered function signature that will
490+
/// call into the original function.
491+
@_spi(Testing)
492+
public func cdeclThunk(cName: String, inputFunction: FunctionDeclSyntax) -> FunctionDeclSyntax {
493+
var loweredCDecl = cdecl.createFunctionDecl(cName)
494+
495+
// Add the @_cdecl attribute.
496+
let cdeclAttribute: AttributeSyntax = "@_cdecl(\(literal: cName))\n"
497+
loweredCDecl.attributes.append(.attribute(cdeclAttribute))
498+
499+
// Create the body.
500+
501+
// Lower "self", if there is one.
502+
let parametersToLower: ArraySlice<LoweredParameters>
503+
let cdeclToOriginalSelf: ExprSyntax?
504+
if original.selfParameter != nil {
505+
cdeclToOriginalSelf = parameters[0].cdeclToOriginalArgumentExpr(isSelf: true)
506+
parametersToLower = parameters[1...]
507+
} else {
508+
cdeclToOriginalSelf = nil
509+
parametersToLower = parameters[...]
510+
}
511+
512+
// Lower the remaining arguments.
513+
// FIXME: Should be able to use structured initializers here instead
514+
// of splatting out text.
515+
let cdeclToOriginalArguments = zip(parametersToLower, original.parameters).map { lowering, originalParam in
516+
let cdeclToOriginalArg = lowering.cdeclToOriginalArgumentExpr(isSelf: false)
517+
if let argumentLabel = originalParam.argumentLabel {
518+
return "\(argumentLabel): \(cdeclToOriginalArg.description)"
519+
} else {
520+
return cdeclToOriginalArg.description
521+
}
522+
}
523+
524+
// Form the call expression.
525+
var callExpression: ExprSyntax = "\(inputFunction.name)(\(raw: cdeclToOriginalArguments.joined(separator: ", ")))"
526+
if let cdeclToOriginalSelf {
527+
callExpression = "\(cdeclToOriginalSelf).\(callExpression)"
528+
}
529+
530+
// Handle the return.
531+
if cdecl.result.type.isVoid && original.result.type.isVoid {
532+
// Nothing to return.
533+
loweredCDecl.body = """
534+
{
535+
\(callExpression)
536+
}
537+
"""
538+
} else if cdecl.result.type.isVoid {
539+
// Indirect return. This is a regular return in Swift that turns
540+
// into a
541+
loweredCDecl.body = """
542+
{
543+
\(result.cdeclToOriginalArgumentExpr(isSelf: true)) = \(callExpression)
544+
}
545+
"""
546+
} else {
547+
// Direct return.
548+
loweredCDecl.body = """
549+
{
550+
return \(callExpression)
551+
}
552+
"""
553+
}
554+
555+
return loweredCDecl
556+
}
557+
}

Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import SwiftSyntaxBuilder
1919
/// parameters and return type.
2020
@_spi(Testing)
2121
public struct SwiftFunctionSignature: Equatable {
22+
// FIXME: isStaticOrClass probably shouldn't be here?
2223
var isStaticOrClass: Bool
2324
var selfParameter: SwiftParameter?
2425
var parameters: [SwiftParameter]
@@ -30,9 +31,16 @@ extension SwiftFunctionSignature {
3031
/// signature.
3132
package func createFunctionDecl(_ name: String) -> FunctionDeclSyntax {
3233
let parametersStr = parameters.map(\.description).joined(separator: ", ")
33-
let resultStr = result.type.description
34+
35+
let resultWithArrow: String
36+
if result.type.isVoid {
37+
resultWithArrow = ""
38+
} else {
39+
resultWithArrow = " -> \(result.type.description)"
40+
}
41+
3442
let decl: DeclSyntax = """
35-
func \(raw: name)(\(raw: parametersStr)) -> \(raw: resultStr) {
43+
func \(raw: name)(\(raw: parametersStr))\(raw: resultWithArrow) {
3644
// implementation
3745
}
3846
"""
@@ -53,7 +61,7 @@ extension SwiftFunctionSignature {
5361
var isConsuming = false
5462
var isStaticOrClass = false
5563
for modifier in node.modifiers {
56-
switch modifier.name {
64+
switch modifier.name.tokenKind {
5765
case .keyword(.mutating): isMutating = true
5866
case .keyword(.static), .keyword(.class): isStaticOrClass = true
5967
case .keyword(.consuming): isConsuming = true

Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,23 +58,34 @@ enum SwiftParameterConvention: Equatable {
5858

5959
extension SwiftParameter {
6060
init(_ node: FunctionParameterSyntax, symbolTable: SwiftSymbolTable) throws {
61-
// Determine the convention. The default is by-value, but modifiers can alter
62-
// this.
61+
// Determine the convention. The default is by-value, but there are
62+
// specifiers on the type for other conventions (like `inout`).
63+
var type = node.type
6364
var convention = SwiftParameterConvention.byValue
64-
for modifier in node.modifiers {
65-
switch modifier.name {
66-
case .keyword(.consuming), .keyword(.__consuming), .keyword(.__owned):
67-
convention = .consuming
68-
case .keyword(.inout):
69-
convention = .inout
70-
default:
71-
break
65+
if let attributedType = type.as(AttributedTypeSyntax.self) {
66+
for specifier in attributedType.specifiers {
67+
guard case .simpleTypeSpecifier(let simple) = specifier else {
68+
continue
69+
}
70+
71+
switch simple.specifier.tokenKind {
72+
case .keyword(.consuming), .keyword(.__consuming), .keyword(.__owned):
73+
convention = .consuming
74+
case .keyword(.inout):
75+
convention = .inout
76+
default:
77+
break
78+
}
7279
}
80+
81+
// Ignore anything else in the attributed type.
82+
// FIXME: We might want to check for these and ignore them.
83+
type = attributedType.baseType
7384
}
7485
self.convention = convention
7586

7687
// Determine the type.
77-
self.type = try SwiftType(node.type, symbolTable: symbolTable)
88+
self.type = try SwiftType(type, symbolTable: symbolTable)
7889

7990
// FIXME: swift-syntax itself should have these utilities based on identifiers.
8091
if let secondName = node.secondName {

0 commit comments

Comments
 (0)