Skip to content

Commit 5a209c9

Browse files
committed
[JExtract] Bridge closures with UnsafeRawBufferPointer parameter
First step to bridging closures with conversions.
1 parent c9dc9b6 commit 5a209c9

File tree

8 files changed

+351
-44
lines changed

8 files changed

+351
-44
lines changed

Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int {
5353

5454
public var globalBuffer: UnsafeRawBufferPointer = UnsafeRawBufferPointer(UnsafeMutableRawBufferPointer.allocate(byteCount: 124, alignment: 1))
5555

56+
public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) {
57+
body(globalBuffer)
58+
}
59+
5660
// ==== Internal helpers
5761

5862
func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) {

Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ static void examples() {
4747

4848
SwiftKit.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize());
4949

50+
MySwiftLibrary.withBuffer((buf) -> {
51+
SwiftKit.trace("withBuffer{$0.byteSize()}=" + buf.byteSize());
52+
});
5053
// Example of using an arena; MyClass.deinit is run at end of scope
5154
try (var arena = SwiftArena.ofConfined()) {
5255
MySwiftClass obj = MySwiftClass.init(2222, 7777, arena);

Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -332,14 +332,15 @@ struct CdeclLowering {
332332
var parameters: [SwiftParameter] = []
333333
var parameterConversions: [ConversionStep] = []
334334

335-
for parameter in fn.parameters {
336-
if let _ = try? CType(cdeclType: parameter.type) {
337-
parameters.append(SwiftParameter(convention: .byValue, type: parameter.type))
338-
parameterConversions.append(.placeholder)
339-
} else {
340-
// Non-trivial types are not yet supported.
341-
throw LoweringError.unhandledType(.function(fn))
342-
}
335+
for (i, parameter) in fn.parameters.enumerated() {
336+
let parameterName = parameter.parameterName ?? "_\(i)"
337+
let loweredParam = try lowerClosureParameter(
338+
parameter.type,
339+
convention: parameter.convention,
340+
parameterName: parameterName
341+
)
342+
parameters.append(contentsOf: loweredParam.cdeclParameters)
343+
parameterConversions.append(loweredParam.conversion)
343344
}
344345

345346
let resultType: SwiftType
@@ -352,15 +353,74 @@ struct CdeclLowering {
352353
throw LoweringError.unhandledType(.function(fn))
353354
}
354355

355-
// Ignore the conversions for now, since we don't support non-trivial types yet.
356-
_ = (parameterConversions, resultConversion)
356+
let isCompatibleWithC = parameterConversions.allSatisfy(\.isPlaceholder) && resultConversion.isPlaceholder
357357

358358
return (
359359
type: .function(SwiftFunctionType(convention: .c, parameters: parameters, resultType: resultType)),
360-
conversion: .placeholder
360+
conversion: isCompatibleWithC ? .placeholder : .closureLowering(parameters: parameterConversions, result: resultConversion)
361361
)
362362
}
363363

364+
func lowerClosureParameter(
365+
_ type: SwiftType,
366+
convention: SwiftParameterConvention,
367+
parameterName: String
368+
) throws -> LoweredParameter {
369+
// If there is a 1:1 mapping between this Swift type and a C type, we just
370+
// return it.
371+
if let _ = try? CType(cdeclType: type) {
372+
return LoweredParameter(
373+
cdeclParameters: [
374+
SwiftParameter(
375+
convention: .byValue,
376+
parameterName: parameterName,
377+
type: type
378+
),
379+
],
380+
conversion: .placeholder
381+
)
382+
}
383+
384+
switch type {
385+
case .nominal(let nominal):
386+
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
387+
switch knownType {
388+
case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
389+
// pointer buffers are lowered to (raw-pointer, count) pair.
390+
let isMutable = knownType == .unsafeMutableRawBufferPointer
391+
return LoweredParameter(
392+
cdeclParameters: [
393+
SwiftParameter(
394+
convention: .byValue,
395+
parameterName: "\(parameterName)_pointer",
396+
type: .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer)
397+
),
398+
SwiftParameter(
399+
convention: .byValue,
400+
parameterName: "\(parameterName)_count",
401+
type: knownTypes.int
402+
),
403+
],
404+
conversion: .tuplify([
405+
.member(.placeholder, member: "baseAddress"),
406+
.member(.placeholder, member: "count")
407+
])
408+
)
409+
410+
default:
411+
throw LoweringError.unhandledType(type)
412+
}
413+
}
414+
415+
// Custom types are not supported yet.
416+
throw LoweringError.unhandledType(type)
417+
418+
case .function, .metatype, .optional, .tuple:
419+
// TODO: Implement
420+
throw LoweringError.unhandledType(type)
421+
}
422+
}
423+
364424
/// Lower a Swift result type to cdecl out parameters and return type.
365425
///
366426
/// - Parameters:

Sources/JExtractSwiftLib/FFM/ConversionStep.swift

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ enum ConversionStep: Equatable {
5858
/// Perform multiple conversions using the same input.
5959
case aggregate([ConversionStep], name: String?)
6060

61+
indirect case closureLowering(parameters: [ConversionStep], result: ConversionStep)
62+
6163
indirect case member(ConversionStep, member: String)
6264

6365
/// Count the number of times that the placeholder occurs within this
@@ -73,13 +75,20 @@ enum ConversionStep: Equatable {
7375
inner.placeholderCount
7476
case .initialize(_, arguments: let arguments):
7577
arguments.reduce(0) { $0 + $1.argument.placeholderCount }
76-
case .placeholder, .tupleExplode:
78+
case .placeholder, .tupleExplode, .closureLowering:
7779
1
7880
case .tuplify(let elements), .aggregate(let elements, _):
7981
elements.reduce(0) { $0 + $1.placeholderCount }
8082
}
8183
}
8284

85+
var isPlaceholder: Bool {
86+
if case .placeholder = self {
87+
return true
88+
}
89+
return false
90+
}
91+
8392
/// Convert the conversion step into an expression with the given
8493
/// value as the placeholder value in the expression.
8594
func asExprSyntax(placeholder: String, bodyItems: inout [CodeBlockItemSyntax]) -> ExprSyntax? {
@@ -165,6 +174,48 @@ enum ConversionStep: Equatable {
165174
}
166175
}
167176
return nil
177+
178+
case .closureLowering(let parameterSteps, let resultStep):
179+
var body: [CodeBlockItemSyntax] = []
180+
181+
// Lower parameters.
182+
var params: [String] = []
183+
var args: [ExprSyntax] = []
184+
for (i, parameterStep) in parameterSteps.enumerated() {
185+
let paramName = "_\(i)"
186+
params.append(paramName)
187+
if case .tuplify(let elemSteps) = parameterStep {
188+
for elemStep in elemSteps {
189+
if let elemExpr = elemStep.asExprSyntax(placeholder: paramName, bodyItems: &body) {
190+
args.append(elemExpr)
191+
}
192+
}
193+
} else if let paramExpr = parameterStep.asExprSyntax(placeholder: paramName, bodyItems: &body) {
194+
args.append(paramExpr)
195+
}
196+
}
197+
198+
// Call the lowered closure with lowered parameters.
199+
let loweredResult = "\(placeholder)(\(args.map(\.description).joined(separator: ", ")))"
200+
201+
// Raise the lowered result.
202+
let result = resultStep.asExprSyntax(placeholder: loweredResult.description, bodyItems: &body)
203+
body.append("return \(result)")
204+
205+
// Construct the closure expression.
206+
var closure = ExprSyntax(
207+
"""
208+
{ (\(raw: params.joined(separator: ", "))) in
209+
}
210+
"""
211+
).cast(ClosureExprSyntax.self)
212+
213+
closure.statements = CodeBlockItemListSyntax {
214+
body.map {
215+
$0.with(\.leadingTrivia, [.newlines(1), .spaces(4)])
216+
}
217+
}
218+
return ExprSyntax(closure)
168219
}
169220
}
170221
}

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,6 @@ extension FFMSwift2JavaGenerator {
251251
)
252252
} else {
253253
// Otherwise, the lambda must be wrapped with the lowered function instance.
254-
assertionFailure("should be unreachable at this point")
255254
let apiParams = functionType.parameters.flatMap {
256255
$0.javaParameters.map { param in "\(param.type) \(param.name)" }
257256
}
@@ -262,13 +261,38 @@ extension FFMSwift2JavaGenerator {
262261
public interface \(functionType.name) {
263262
\(functionType.result.javaResultType) apply(\(apiParams.joined(separator: ", ")));
264263
}
265-
private static MemorySegment $toUpcallStub(\(functionType.name) fi, Arena arena) {
266-
return \(cdeclDescriptor).toUpcallStub((<cdecl-params>) -> {
267-
<maybe-return> fi(<converted-args>)
268-
}, arena);
269-
}
270264
"""
271265
)
266+
267+
let cdeclParams = functionType.cdeclType.parameters.map( { "\($0.parameterName!)" })
268+
269+
printer.printBraceBlock(
270+
"""
271+
private static MemorySegment $toUpcallStub(\(functionType.name) fi, Arena arena)
272+
"""
273+
) { printer in
274+
printer.print(
275+
"""
276+
return \(cdeclDescriptor).toUpcallStub((\(cdeclParams.joined(separator: ", "))) -> {
277+
"""
278+
)
279+
printer.indent()
280+
var convertedArgs: [String] = []
281+
for param in functionType.parameters {
282+
let arg = param.conversion.render(&printer, param.javaParameters[0].name)
283+
convertedArgs.append(arg)
284+
}
285+
286+
let call = "fi.apply(\(convertedArgs.joined(separator: ", ")))"
287+
let result = functionType.result.conversion.render(&printer, call)
288+
if functionType.result.javaResultType == .void {
289+
printer.print("\(result);")
290+
} else {
291+
printer.print("return \(result);")
292+
}
293+
printer.outdent()
294+
printer.print("}, arena);")
295+
}
272296
}
273297
}
274298

@@ -419,7 +443,7 @@ extension JavaConversionStep {
419443
/// Whether the conversion uses SwiftArena.
420444
var requiresSwiftArena: Bool {
421445
switch self {
422-
case .placeholder, .constant, .readOutParameter:
446+
case .placeholder, .explodedName, .constant, .readMemorySegment:
423447
return false
424448
case .constructSwiftValue:
425449
return true
@@ -436,9 +460,9 @@ extension JavaConversionStep {
436460
/// Whether the conversion uses temporary Arena.
437461
var requiresTemporaryArena: Bool {
438462
switch self {
439-
case .placeholder, .constant:
463+
case .placeholder, .explodedName, .constant:
440464
return false
441-
case .readOutParameter:
465+
case .readMemorySegment:
442466
return true
443467
case .cast(let inner, _), .construct(let inner, _), .constructSwiftValue(let inner, _), .swiftValueSelfSegment(let inner):
444468
return inner.requiresSwiftArena
@@ -459,6 +483,9 @@ extension JavaConversionStep {
459483
case .placeholder:
460484
return placeholder
461485

486+
case .explodedName(let component):
487+
return "\(placeholder)_\(component)"
488+
462489
case .swiftValueSelfSegment:
463490
return "\(placeholder).$memorySegment()"
464491

@@ -491,8 +518,9 @@ extension JavaConversionStep {
491518
case .constant(let value):
492519
return value
493520

494-
case .readOutParameter(let javaType, let name):
495-
return "\(placeholder)_\(name).get(\(ForeignValueLayout(javaType: javaType)!), 0)"
521+
case .readMemorySegment(let inner, let javaType):
522+
let inner = inner.render(&printer, placeholder)
523+
return "\(inner).get(\(ForeignValueLayout(javaType: javaType)!), 0)"
496524
}
497525
}
498526
}

0 commit comments

Comments
 (0)