forked from swiftlang/swift-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSwift2JavaTranslator+FunctionLowering.swift
More file actions
629 lines (560 loc) · 22.1 KB
/
Swift2JavaTranslator+FunctionLowering.swift
File metadata and controls
629 lines (560 loc) · 22.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
import JavaTypes
import SwiftSyntax
extension Swift2JavaTranslator {
/// Lower the given function declaration to a C-compatible entrypoint,
/// providing all of the mappings between the parameter and result types
/// of the original function and its `@_cdecl` counterpart.
@_spi(Testing)
public func lowerFunctionSignature(
_ decl: FunctionDeclSyntax,
enclosingType: TypeSyntax? = nil
) throws -> LoweredFunctionSignature {
let signature = try SwiftFunctionSignature(
decl,
enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) },
symbolTable: symbolTable
)
return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: .function)
}
/// Lower the given initializer to a C-compatible entrypoint,
/// providing all of the mappings between the parameter and result types
/// of the original function and its `@_cdecl` counterpart.
@_spi(Testing)
public func lowerFunctionSignature(
_ decl: InitializerDeclSyntax,
enclosingType: TypeSyntax? = nil
) throws -> LoweredFunctionSignature {
let signature = try SwiftFunctionSignature(
decl,
enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) },
symbolTable: symbolTable
)
return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: .initializer)
}
/// Lower the given variable decl to a C-compatible entrypoint,
/// providing the mappings between the `self` and value type of the variable
/// and its `@_cdecl` counterpart.
@_spi(Testing)
public func lowerFunctionSignature(
_ decl: VariableDeclSyntax,
isSet: Bool,
enclosingType: TypeSyntax? = nil
) throws -> LoweredFunctionSignature? {
let supportedAccessors = decl.supportedAccessorKinds(binding: decl.bindings.first!)
guard supportedAccessors.contains(isSet ? .set : .get) else {
return nil
}
let signature = try SwiftFunctionSignature(
decl,
isSet: isSet,
enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) },
symbolTable: symbolTable
)
return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: isSet ? .setter : .getter)
}
}
/// Responsible for lowering Swift API to C API.
struct CdeclLowering {
var swiftStdlibTypes: SwiftStandardLibraryTypes
/// Lower the given Swift function signature to a Swift @_cdecl function signature,
/// which is C compatible, and the corresponding Java method signature.
///
/// Throws an error if this function cannot be lowered for any reason.
func lowerFunctionSignature(
_ signature: SwiftFunctionSignature,
apiKind: SwiftAPIKind
) throws -> LoweredFunctionSignature {
// Lower the self parameter.
let loweredSelf: LoweredParameter? = switch signature.selfParameter {
case .instance(let selfParameter):
try lowerParameter(
selfParameter.type,
convention: selfParameter.convention,
parameterName: selfParameter.parameterName ?? "self"
)
case nil, .initializer(_), .staticMethod(_):
nil
}
// Lower all of the parameters.
let loweredParameters = try signature.parameters.enumerated().map { (index, param) in
try lowerParameter(
param.type,
convention: param.convention,
parameterName: param.parameterName ?? "_\(index)"
)
}
// Lower the result.
let loweredResult = try lowerResult(signature.result.type)
return LoweredFunctionSignature(
original: signature,
apiKind: apiKind,
selfParameter: loweredSelf,
parameters: loweredParameters,
result: loweredResult
)
}
/// Lower a Swift function parameter type to cdecl parameters.
///
/// For example, Swift parameter `arg value: inout Int` can be lowered with
/// `lowerParameter(intTy, .inout, "value")`.
///
/// - Parameters:
/// - type: The parameter type.
/// - convention: the parameter convention, e.g. `inout`.
/// - parameterName: The name of the parameter.
///
func lowerParameter(
_ type: SwiftType,
convention: SwiftParameterConvention,
parameterName: String
) throws -> LoweredParameter {
// If there is a 1:1 mapping between this Swift type and a C type, we just
// return it.
if let _ = try? CType(cdeclType: type) {
if convention != .inout {
return LoweredParameter(
cdeclParameters: [SwiftParameter(convention: .byValue, parameterName: parameterName, type: type)],
conversion: .placeholder
)
}
}
switch type {
case .metatype(let instanceType):
return LoweredParameter(
cdeclParameters: [
SwiftParameter(
convention: .byValue,
parameterName: parameterName,
type: .nominal(
SwiftNominalType(
nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer]
)
)
)
],
conversion: .unsafeCastPointer(.placeholder, swiftType: instanceType)
)
case .nominal(let nominal):
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
if convention == .inout {
// FIXME: Support non-trivial 'inout' for builtin types.
throw LoweringError.inoutNotSupported(type)
}
switch knownType {
case .unsafePointer, .unsafeMutablePointer:
guard let genericArgs = type.asNominalType?.genericArguments, genericArgs.count == 1 else {
throw LoweringError.unhandledType(type)
}
// Typed pointers are mapped down to their raw forms in cdecl entry
// points. These can be passed through directly.
let isMutable = knownType == .unsafeMutablePointer
let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
let paramType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
return LoweredParameter(
cdeclParameters: [SwiftParameter(convention: .byValue, parameterName: parameterName, type: paramType)],
conversion: .typedPointer(.placeholder, swiftType: genericArgs[0])
)
case .unsafeBufferPointer, .unsafeMutableBufferPointer:
guard let genericArgs = type.asNominalType?.genericArguments, genericArgs.count == 1 else {
throw LoweringError.unhandledType(type)
}
// Typed pointers are lowered to (raw-pointer, count) pair.
let isMutable = knownType == .unsafeMutableBufferPointer
let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
return LoweredParameter(
cdeclParameters: [
SwiftParameter(
convention: .byValue, parameterName: "\(parameterName)_pointer",
type: .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
),
SwiftParameter(
convention: .byValue, parameterName: "\(parameterName)_count",
type: .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int]))
),
], conversion: .initialize(
type,
arguments: [
LabeledArgument(
label: "start",
argument: .typedPointer(.explodedComponent(.placeholder, component: "pointer"), swiftType: genericArgs[0])
),
LabeledArgument(
label: "count",
argument: .explodedComponent(.placeholder, component: "count")
)
]
)
)
case .string:
// 'String' is passed in by C string. i.e. 'UnsafePointer<Int8>' ('const uint8_t *')
if knownType == .string {
return LoweredParameter(
cdeclParameters: [
SwiftParameter(
convention: .byValue,
parameterName: parameterName,
type: .nominal(SwiftNominalType(
nominalTypeDecl: swiftStdlibTypes.unsafePointerDecl,
genericArguments: [
.nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int8]))
]
))
)
],
conversion: .initialize(type, arguments: [
LabeledArgument(label: "cString", argument: .placeholder)
])
)
}
default:
// Unreachable? Should be handled by `CType(cdeclType:)` lowering above.
throw LoweringError.unhandledType(type)
}
}
// Arbitrary nominal types are passed-in as an pointer.
let isMutable = (convention == .inout)
let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
let parameterType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
return LoweredParameter(
cdeclParameters: [
SwiftParameter(
convention: .byValue,
parameterName: parameterName,
type: parameterType
),
],
conversion: .pointee(.typedPointer(.placeholder, swiftType: type))
)
case .tuple(let tuple):
if tuple.count == 1 {
return try lowerParameter(tuple[0], convention: convention, parameterName: parameterName)
}
if convention == .inout {
throw LoweringError.inoutNotSupported(type)
}
var parameters: [SwiftParameter] = []
var conversions: [ConversionStep] = []
for (idx, element) in tuple.enumerated() {
// FIXME: Use tuple element label.
let cdeclName = "\(parameterName)_\(idx)"
let lowered = try lowerParameter(element, convention: convention, parameterName: cdeclName)
parameters.append(contentsOf: lowered.cdeclParameters)
conversions.append(lowered.conversion)
}
return LoweredParameter(cdeclParameters: parameters, conversion: .tuplify(conversions))
case .function(let fn) where fn.parameters.isEmpty && fn.resultType.isVoid:
return LoweredParameter(
cdeclParameters: [
SwiftParameter(
convention: .byValue,
parameterName: parameterName,
type: .function(SwiftFunctionType(convention: .c, parameters: [], resultType: fn.resultType))
)
],
// '@convention(c) () -> ()' is compatible with '() -> Void'.
conversion: .placeholder
)
case .function, .optional:
// FIXME: Support other function types than '() -> Void'.
throw LoweringError.unhandledType(type)
}
}
/// Lower a Swift result type to cdecl out parameters and return type.
///
/// - Parameters:
/// - type: The return type.
/// - outParameterName: If the type is lowered to a indirect return, this parameter name should be used.
func lowerResult(
_ type: SwiftType,
outParameterName: String = "_result"
) throws -> LoweredResult {
// If there is a 1:1 mapping between this Swift type and a C type, we just
// return it.
if let cType = try? CType(cdeclType: type) {
_ = cType
return LoweredResult(cdeclResultType: type, cdeclOutParameters: [], conversion: .placeholder);
}
switch type {
case .metatype:
// 'unsafeBitcast(<result>, to: UnsafeRawPointer.self)' as 'UnsafeRawPointer'
let resultType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer]))
return LoweredResult(
cdeclResultType: resultType,
cdeclOutParameters: [],
conversion: .unsafeCastPointer(.placeholder, swiftType: resultType)
)
case .nominal(let nominal):
// Types from the Swift standard library that we know about.
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
switch knownType {
case .unsafePointer, .unsafeMutablePointer:
// Typed pointers are lowered to corresponding raw forms.
let isMutable = knownType == .unsafeMutablePointer
let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
let resultType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
return LoweredResult(
cdeclResultType: resultType,
cdeclOutParameters: [],
conversion: .initialize(resultType, arguments: [LabeledArgument(argument: .placeholder)])
)
case .unsafeBufferPointer, .unsafeMutableBufferPointer:
// Typed pointers are lowered to (raw-pointer, count) pair.
let isMutable = knownType == .unsafeMutableBufferPointer
let rawPointerType = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
return try lowerResult(
.tuple([
.nominal(SwiftNominalType(nominalTypeDecl: rawPointerType)),
.nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int]))
]),
outParameterName: outParameterName
)
case .void:
return LoweredResult(cdeclResultType: .void, cdeclOutParameters: [], conversion: .placeholder)
case .string:
// Returning string is not supported at this point.
throw LoweringError.unhandledType(type)
default:
// Unreachable? Should be handled by `CType(cdeclType:)` lowering above.
throw LoweringError.unhandledType(type)
}
}
// Arbitrary nominal types are indirectly returned.
return LoweredResult(
cdeclResultType: .void,
cdeclOutParameters: [
SwiftParameter(
convention: .byValue,
parameterName: outParameterName,
type: .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.unsafeMutableRawPointer]))
)
],
conversion: .populatePointer(name: outParameterName, assumingType: type, to: .placeholder)
)
case .tuple(let tuple):
if tuple.count == 1 {
return try lowerResult(tuple[0], outParameterName: outParameterName)
}
var parameters: [SwiftParameter] = []
var conversions: [ConversionStep] = []
for (idx, element) in tuple.enumerated() {
let outName = "\(outParameterName)_\(idx)"
let lowered = try lowerResult(element, outParameterName: outName)
// Convert direct return values to typed mutable pointers.
// E.g. (Int8, Int8) is lowered to '_ result_0: UnsafePointer<Int8>, _ result_1: UnsafePointer<Int8>'
if !lowered.cdeclResultType.isVoid {
let parameterName = lowered.cdeclOutParameters.isEmpty ? outName : "\(outName)_return"
let parameter = SwiftParameter(
convention: .byValue,
parameterName: parameterName,
type: .nominal(SwiftNominalType(
nominalTypeDecl: swiftStdlibTypes.unsafeMutablePointerDecl,
genericArguments: [lowered.cdeclResultType]
))
)
parameters.append(parameter)
conversions.append(.populatePointer(
name: parameterName,
to: lowered.conversion
))
} else {
// If the element returns void, it should already be a no-result conversion.
parameters.append(contentsOf: lowered.cdeclOutParameters)
conversions.append(lowered.conversion)
}
}
return LoweredResult(
cdeclResultType: .void,
cdeclOutParameters: parameters,
conversion: .tupleExplode(conversions, name: outParameterName)
)
case .function(_), .optional(_):
throw LoweringError.unhandledType(type)
}
}
/// Given a Swift function signature that represents a @_cdecl function,
/// produce the equivalent C function with the given name.
///
/// Lowering to a @_cdecl function should never produce a
@_spi(Testing)
public func cdeclToCFunctionLowering(
_ cdeclSignature: SwiftFunctionSignature,
cName: String
) -> CFunction {
return try! CFunction(cdeclSignature: cdeclSignature, cName: cName)
}
}
package enum SwiftAPIKind {
case function
case initializer
case getter
case setter
}
/// Represent a Swift parameter in the cdecl thunk.
struct LoweredParameter: Equatable {
/// Lowered parameters in cdecl thunk.
/// One Swift parameter can be lowered to multiple parameters.
/// E.g. 'Data' as (baseAddress, length) pair.
var cdeclParameters: [SwiftParameter]
/// Conversion to convert the cdecl thunk parameters to the original Swift argument.
var conversion: ConversionStep
init(cdeclParameters: [SwiftParameter], conversion: ConversionStep) {
self.cdeclParameters = cdeclParameters
self.conversion = conversion
assert(cdeclParameters.count == conversion.placeholderCount)
}
}
struct LoweredResult: Equatable {
/// The return type of the cdecl thunk.
var cdeclResultType: SwiftType
/// Out parameters for populating the returning values.
///
/// Currently, if this is not empty, `cdeclResultType` is `Void`. But the thunk
/// may utilize both in the future, for example returning the status while
/// populating the out parameter.
var cdeclOutParameters: [SwiftParameter]
/// The conversion from the Swift result to cdecl result.
var conversion: ConversionStep
}
extension LoweredResult {
/// Whether the result is returned by populating the passed-in pointer parameters.
var hasIndirectResult: Bool {
!cdeclOutParameters.isEmpty
}
}
@_spi(Testing)
public struct LoweredFunctionSignature: Equatable {
var original: SwiftFunctionSignature
var apiKind: SwiftAPIKind
var selfParameter: LoweredParameter?
var parameters: [LoweredParameter]
var result: LoweredResult
var allLoweredParameters: [SwiftParameter] {
var all: [SwiftParameter] = []
// Original parameters.
for loweredParam in parameters {
all += loweredParam.cdeclParameters
}
// Self.
if let selfParameter = self.selfParameter {
all += selfParameter.cdeclParameters
}
// Out parameters.
all += result.cdeclOutParameters
return all
}
var cdeclSignature: SwiftFunctionSignature {
SwiftFunctionSignature(
selfParameter: nil,
parameters: allLoweredParameters,
result: SwiftResult(convention: .direct, type: result.cdeclResultType)
)
}
}
extension LoweredFunctionSignature {
/// Produce the `@_cdecl` thunk for this lowered function signature that will
/// call into the original function.
public func cdeclThunk(
cName: String,
swiftAPIName: String,
stdlibTypes: SwiftStandardLibraryTypes
) -> FunctionDeclSyntax {
let cdeclParams = allLoweredParameters.map(\.description).joined(separator: ", ")
let returnClause = !result.cdeclResultType.isVoid ? " -> \(result.cdeclResultType.description)" : ""
var loweredCDecl = try! FunctionDeclSyntax(
"""
@_cdecl(\(literal: cName))
public func \(raw: cName)(\(raw: cdeclParams))\(raw: returnClause) {
}
"""
)
var bodyItems: [CodeBlockItemSyntax] = []
let selfExpr: ExprSyntax?
switch original.selfParameter {
case .instance(let swiftSelf):
// Raise the 'self' from cdecl parameters.
selfExpr = self.selfParameter!.conversion.asExprSyntax(
placeholder: swiftSelf.parameterName ?? "self",
bodyItems: &bodyItems
)
case .staticMethod(let selfType), .initializer(let selfType):
selfExpr = "\(raw: selfType.description)"
case .none:
selfExpr = nil
}
/// Raise the other parameters.
let paramExprs = parameters.enumerated().map { idx, param in
param.conversion.asExprSyntax(
placeholder: original.parameters[idx].parameterName ?? "_\(idx)",
bodyItems: &bodyItems
)!
}
// Build callee expression.
let callee: ExprSyntax = if let selfExpr {
if case .initializer = self.apiKind {
// Don't bother to create explicit ${Self}.init expression.
selfExpr
} else {
ExprSyntax(MemberAccessExprSyntax(base: selfExpr, name: .identifier(swiftAPIName)))
}
} else {
ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(swiftAPIName)))
}
// Build the result.
let resultExpr: ExprSyntax
switch apiKind {
case .function, .initializer:
let arguments = paramExprs.enumerated()
.map { (i, argument) -> String in
let argExpr = original.parameters[i].convention == .inout ? "&\(argument)" : argument
return LabeledExprSyntax(label: original.parameters[i].argumentLabel, expression: argExpr).description
}
.joined(separator: ", ")
resultExpr = "\(callee)(\(raw: arguments))"
case .getter:
assert(paramExprs.isEmpty)
resultExpr = callee
case .setter:
assert(paramExprs.count == 1)
resultExpr = "\(callee) = \(paramExprs[0])"
}
// Lower the result.
if !original.result.type.isVoid {
let loweredResult: ExprSyntax? = result.conversion.asExprSyntax(
placeholder: resultExpr.description,
bodyItems: &bodyItems
)
if let loweredResult {
bodyItems.append(!result.cdeclResultType.isVoid ? "return \(loweredResult)" : "\(loweredResult)")
}
} else {
bodyItems.append("\(resultExpr)")
}
loweredCDecl.body!.statements = CodeBlockItemListSyntax {
bodyItems.map {
$0.with(\.leadingTrivia, [.newlines(1), .spaces(2)])
}
}
return loweredCDecl
}
@_spi(Testing)
public func cFunctionDecl(cName: String) throws -> CFunction {
try CFunction(cdeclSignature: self.cdeclSignature, cName: cName)
}
}
enum LoweringError: Error {
case inoutNotSupported(SwiftType)
case unhandledType(SwiftType)
}