diff --git a/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift b/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift new file mode 100644 index 0000000..0099ae5 --- /dev/null +++ b/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift @@ -0,0 +1,195 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +extension ConversionStep { + /// Produce a conversion that takes in a value (or set of values) that + /// would be available in a @_cdecl function to represent the given Swift + /// type, and convert that to an instance of the Swift type. + init(cdeclToSwift swiftType: SwiftType) throws { + // If there is a 1:1 mapping between this Swift type and a C type, then + // there is no translation to do. + if let cType = try? CType(cdeclType: swiftType) { + _ = cType + self = .placeholder + return + } + + switch swiftType { + case .function, .optional: + throw LoweringError.unhandledType(swiftType) + + case .metatype(let instanceType): + self = .unsafeCastPointer( + .placeholder, + swiftType: instanceType + ) + + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + // Typed pointers + if let firstGenericArgument = nominal.genericArguments?.first { + switch knownType { + case .unsafePointer, .unsafeMutablePointer: + self = .typedPointer( + .explodedComponent(.placeholder, component: "pointer"), + swiftType: firstGenericArgument + ) + return + + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + self = .initialize( + swiftType, + arguments: [ + LabeledArgument( + label: "start", + argument: .typedPointer( + .explodedComponent(.placeholder, component: "pointer"), + swiftType: firstGenericArgument) + ), + LabeledArgument( + label: "count", + argument: .explodedComponent(.placeholder, component: "count") + ) + ] + ) + return + + default: + break + } + } + } + + // Arbitrary nominal types. + switch nominal.nominalTypeDecl.kind { + case .actor, .class: + // For actor and class, we pass around the pointer directly. + self = .unsafeCastPointer(.placeholder, swiftType: swiftType) + case .enum, .struct, .protocol: + // For enums, structs, and protocol types, we pass around the + // values indirectly. + self = .passIndirectly( + .pointee(.typedPointer(.placeholder, swiftType: swiftType)) + ) + } + + case .tuple(let elements): + self = .tuplify(try elements.map { try ConversionStep(cdeclToSwift: $0) }) + } + } + + /// Produce a conversion that takes in a value that would be available in a + /// Swift function and convert that to the corresponding cdecl values. + /// + /// This conversion goes in the opposite direction of init(cdeclToSwift:), and + /// is used for (e.g.) returning the Swift value from a cdecl function. When + /// there are multiple cdecl values that correspond to this one Swift value, + /// the result will be a tuple that can be assigned to a tuple of the cdecl + /// values, e.g., (.baseAddress, .count). + init( + swiftToCDecl swiftType: SwiftType, + stdlibTypes: SwiftStandardLibraryTypes + ) throws { + // If there is a 1:1 mapping between this Swift type and a C type, then + // there is no translation to do. + if let cType = try? CType(cdeclType: swiftType) { + _ = cType + self = .placeholder + return + } + + switch swiftType { + case .function, .optional: + throw LoweringError.unhandledType(swiftType) + + case .metatype: + self = .unsafeCastPointer( + .placeholder, + swiftType: .nominal( + SwiftNominalType( + nominalTypeDecl: stdlibTypes[.unsafeRawPointer] + ) + ) + ) + return + + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + // Typed pointers + if nominal.genericArguments?.first != nil { + switch knownType { + case .unsafePointer, .unsafeMutablePointer: + let isMutable = knownType == .unsafeMutablePointer + self = ConversionStep( + initializeRawPointerFromTyped: .placeholder, + isMutable: isMutable, + isPartOfBufferPointer: false, + stdlibTypes: stdlibTypes + ) + return + + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + let isMutable = knownType == .unsafeMutableBufferPointer + self = .tuplify( + [ + ConversionStep( + initializeRawPointerFromTyped: .explodedComponent( + .placeholder, + component: "pointer" + ), + isMutable: isMutable, + isPartOfBufferPointer: true, + stdlibTypes: stdlibTypes + ), + .explodedComponent(.placeholder, component: "count") + ] + ) + return + + default: + break + } + } + } + + // Arbitrary nominal types. + switch nominal.nominalTypeDecl.kind { + case .actor, .class: + // For actor and class, we pass around the pointer directly. Case to + // the unsafe raw pointer type we use to represent it in C. + self = .unsafeCastPointer( + .placeholder, + swiftType: .nominal( + SwiftNominalType( + nominalTypeDecl: stdlibTypes[.unsafeRawPointer] + ) + ) + ) + + case .enum, .struct, .protocol: + // For enums, structs, and protocol types, we leave the value alone. + // The indirection will be handled by the caller. + self = .placeholder + } + + case .tuple(let elements): + // Convert all of the elements. + self = .tuplify( + try elements.map { element in + try ConversionStep(swiftToCDecl: element, stdlibTypes: stdlibTypes) + } + ) + } + } +} diff --git a/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift new file mode 100644 index 0000000..15e4ebb --- /dev/null +++ b/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift @@ -0,0 +1,116 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +extension CType { + /// Lower the given Swift type down to a its corresponding C type. + /// + /// This operation only supports the subset of Swift types that are + /// representable in a Swift `@_cdecl` function, which means that they are + /// also directly representable in C. If lowering an arbitrary Swift + /// function, first go through Swift -> cdecl lowering. This function + /// will throw an error if it encounters a type that is not expressible in + /// C. + init(cdeclType: SwiftType) throws { + switch cdeclType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType, + let primitiveCType = knownType.primitiveCType { + self = primitiveCType + return + } + + throw CDeclToCLoweringError.invalidNominalType(nominalType.nominalTypeDecl) + + case .function(let functionType): + switch functionType.convention { + case .swift: + throw CDeclToCLoweringError.invalidFunctionConvention(functionType) + + case .c: + let resultType = try CType(cdeclType: functionType.resultType) + let parameterTypes = try functionType.parameters.map { param in + try CType(cdeclType: param.type) + } + + self = .function( + resultType: resultType, + parameters: parameterTypes, + variadic: false + ) + } + + case .tuple([]): + self = .void + + case .metatype, .optional, .tuple: + throw CDeclToCLoweringError.invalidCDeclType(cdeclType) + } + } +} + +extension CFunction { + /// Produce a C function that represents the given @_cdecl Swift function. + init(cdeclSignature: SwiftFunctionSignature, cName: String) throws { + assert(cdeclSignature.selfParameter == nil) + + let cResultType = try CType(cdeclType: cdeclSignature.result.type) + let cParameters = try cdeclSignature.parameters.map { parameter in + CParameter( + name: parameter.parameterName, + type: try CType(cdeclType: parameter.type).parameterDecay + ) + } + + self = CFunction( + resultType: cResultType, + name: cName, + parameters: cParameters, + isVariadic: false + ) + } +} + +enum CDeclToCLoweringError: Error { + case invalidCDeclType(SwiftType) + case invalidNominalType(SwiftNominalTypeDeclaration) + case invalidFunctionConvention(SwiftFunctionType) +} + +extension KnownStandardLibraryType { + /// Determine the primitive C type that corresponds to this C standard + /// library type, if there is one. + var primitiveCType: CType? { + switch self { + case .bool: .integral(.bool) + case .int: .integral(.ptrdiff_t) + case .uint: .integral(.size_t) + case .int8: .integral(.signed(bits: 8)) + case .uint8: .integral(.unsigned(bits: 8)) + case .int16: .integral(.signed(bits: 16)) + case .uint16: .integral(.unsigned(bits: 16)) + case .int32: .integral(.signed(bits: 32)) + case .uint32: .integral(.unsigned(bits: 32)) + case .int64: .integral(.signed(bits: 64)) + case .uint64: .integral(.unsigned(bits: 64)) + case .float: .floating(.float) + case .double: .floating(.double) + case .unsafeMutableRawPointer: .pointer(.void) + case .unsafeRawPointer: .pointer( + .qualified(const: true, volatile: false, type: .void) + ) + case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer: + nil + } + } +} diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift new file mode 100644 index 0000000..d65948a --- /dev/null +++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift @@ -0,0 +1,535 @@ +//===----------------------------------------------------------------------===// +// +// 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 lowerFunctionSignature(signature) + } + + /// 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 lowerFunctionSignature(signature) + } + + /// 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 + ) throws -> LoweredFunctionSignature { + // 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. + var loweredResult = try lowerParameter( + signature.result.type, + convention: .byValue, + parameterName: "_result" + ) + + // If the result type doesn't lower to either empty (void) or a single + // result, make it indirect. + let indirectResult: Bool + if loweredResult.cdeclParameters.count == 0 { + // void result type + indirectResult = false + } else if loweredResult.cdeclParameters.count == 1, + loweredResult.cdeclParameters[0].canBeDirectReturn { + // Primitive result type + indirectResult = false + } else { + loweredResult = try lowerParameter( + signature.result.type, + convention: .inout, + parameterName: "_result" + ) + indirectResult = true + } + + // Lower the self parameter. + let loweredSelf = try signature.selfParameter.flatMap { selfParameter in + switch selfParameter { + case .instance(let selfParameter): + try lowerParameter( + selfParameter.type, + convention: selfParameter.convention, + parameterName: selfParameter.parameterName ?? "self" + ) + case .initializer, .staticMethod: + nil + } + } + + // Collect all of the lowered parameters for the @_cdecl function. + var allLoweredParameters: [LoweredParameters] = [] + var cdeclLoweredParameters: [SwiftParameter] = [] + allLoweredParameters.append(contentsOf: loweredParameters) + cdeclLoweredParameters.append( + contentsOf: loweredParameters.flatMap { $0.cdeclParameters } + ) + + // Lower self. + if let loweredSelf { + allLoweredParameters.append(loweredSelf) + cdeclLoweredParameters.append(contentsOf: loweredSelf.cdeclParameters) + } + + // Lower indirect results. + let cdeclResult: SwiftResult + if indirectResult { + cdeclLoweredParameters.append( + contentsOf: loweredResult.cdeclParameters + ) + cdeclResult = .init(convention: .direct, type: .tuple([])) + } else if loweredResult.cdeclParameters.count == 1, + let primitiveResult = loweredResult.cdeclParameters.first { + cdeclResult = .init(convention: .direct, type: primitiveResult.type) + } else if loweredResult.cdeclParameters.count == 0 { + cdeclResult = .init(convention: .direct, type: .tuple([])) + } else { + fatalError("Improper lowering of result for \(signature)") + } + + let cdeclSignature = SwiftFunctionSignature( + selfParameter: nil, + parameters: cdeclLoweredParameters, + result: cdeclResult + ) + + return LoweredFunctionSignature( + original: signature, + cdecl: cdeclSignature, + parameters: allLoweredParameters, + result: loweredResult + ) + } + + func lowerParameter( + _ type: SwiftType, + convention: SwiftParameterConvention, + parameterName: String + ) throws -> LoweredParameters { + // If there is a 1:1 mapping between this Swift type and a C type, we just + // need to add the corresponding C [arameter. + if let cType = try? CType(cdeclType: type), convention != .inout { + _ = cType + return LoweredParameters( + cdeclParameters: [ + SwiftParameter( + convention: convention, + parameterName: parameterName, + type: type, + canBeDirectReturn: true + ) + ] + ) + } + + switch type { + case .function, .optional: + throw LoweringError.unhandledType(type) + + case .metatype: + return LoweredParameters( + cdeclParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: parameterName, + type: .nominal( + SwiftNominalType( + nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer] + ) + ), + canBeDirectReturn: true + ) + ] + ) + + case .nominal(let nominal): + // Types from the Swift standard library that we know about. + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType, + convention != .inout { + // Typed pointers are mapped down to their raw forms in cdecl entry + // points. These can be passed through directly. + if knownType == .unsafePointer || knownType == .unsafeMutablePointer { + let isMutable = knownType == .unsafeMutablePointer + let cdeclPointerType = isMutable + ? swiftStdlibTypes[.unsafeMutableRawPointer] + : swiftStdlibTypes[.unsafeRawPointer] + return LoweredParameters( + cdeclParameters: [ + SwiftParameter( + convention: convention, + parameterName: parameterName + "_pointer", + type: SwiftType.nominal( + SwiftNominalType(nominalTypeDecl: cdeclPointerType) + ), + canBeDirectReturn: true + ) + ] + ) + } + + // Typed buffer pointers are mapped down to a (pointer, count) pair + // so those parts can be passed through directly. + if knownType == .unsafeBufferPointer || knownType == .unsafeMutableBufferPointer { + let isMutable = knownType == .unsafeMutableBufferPointer + let cdeclPointerType = isMutable + ? swiftStdlibTypes[.unsafeMutableRawPointer] + : swiftStdlibTypes[.unsafeRawPointer] + return LoweredParameters( + cdeclParameters: [ + SwiftParameter( + convention: convention, + parameterName: parameterName + "_pointer", + type: SwiftType.nominal( + SwiftNominalType(nominalTypeDecl: cdeclPointerType) + ) + ), + SwiftParameter( + convention: convention, + parameterName: parameterName + "_count", + type: SwiftType.nominal( + SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int]) + ) + ) + ] + ) + } + } + + // Arbitrary types are lowered to raw pointers that either "are" the + // reference (for classes and actors) or will point to it. + let canBeDirectReturn = switch nominal.nominalTypeDecl.kind { + case .actor, .class: true + case .enum, .protocol, .struct: false + } + + let isMutable = (convention == .inout) + return LoweredParameters( + cdeclParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: parameterName, + type: .nominal( + SwiftNominalType( + nominalTypeDecl: isMutable + ? swiftStdlibTypes[.unsafeMutableRawPointer] + : swiftStdlibTypes[.unsafeRawPointer] + ) + ), + canBeDirectReturn: canBeDirectReturn + ) + ] + ) + + case .tuple(let tuple): + let parameterNames = tuple.indices.map { "\(parameterName)_\($0)" } + let loweredElements: [LoweredParameters] = try zip(tuple, parameterNames).map { element, name in + try lowerParameter(element, convention: convention, parameterName: name) + } + return LoweredParameters( + cdeclParameters: loweredElements.flatMap { $0.cdeclParameters } + ) + } + } + + /// 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) + } +} + +struct LabeledArgument { + var label: String? + var argument: Element +} + +extension LabeledArgument: Equatable where Element: Equatable { } + + +struct LoweredParameters: Equatable { + /// The lowering of the parameters at the C level in Swift. + var cdeclParameters: [SwiftParameter] +} + +enum LoweringError: Error { + case inoutNotSupported(SwiftType) + case unhandledType(SwiftType) +} + +@_spi(Testing) +public struct LoweredFunctionSignature: Equatable { + var original: SwiftFunctionSignature + public var cdecl: SwiftFunctionSignature + + var parameters: [LoweredParameters] + var result: LoweredParameters +} + +extension LoweredFunctionSignature { + /// Produce the `@_cdecl` thunk for this lowered function signature that will + /// call into the original function. + @_spi(Testing) + public func cdeclThunk( + cName: String, + swiftFunctionName: String, + stdlibTypes: SwiftStandardLibraryTypes + ) -> FunctionDeclSyntax { + var loweredCDecl = cdecl.createFunctionDecl(cName) + + // Add the @_cdecl attribute. + let cdeclAttribute: AttributeSyntax = "@_cdecl(\(literal: cName))\n" + loweredCDecl.attributes.append(.attribute(cdeclAttribute)) + + // Make it public. + loweredCDecl.modifiers.append( + DeclModifierSyntax(name: .keyword(.public), trailingTrivia: .space) + ) + + // Create the body. + + // Lower "self", if there is one. + let parametersToLower: ArraySlice + let cdeclToOriginalSelf: ExprSyntax? + var initializerType: SwiftType? = nil + if let originalSelf = original.selfParameter { + switch originalSelf { + case .instance(let originalSelfParam): + // The instance was provided to the cdecl thunk, so convert it to + // its Swift representation. + cdeclToOriginalSelf = try! ConversionStep( + cdeclToSwift: originalSelfParam.type + ).asExprSyntax( + isSelf: true, + placeholder: originalSelfParam.parameterName ?? "self" + ) + parametersToLower = parameters.dropLast() + + case .staticMethod(let selfType): + // Static methods use the Swift type as "self", but there is no + // corresponding cdecl parameter. + cdeclToOriginalSelf = "\(raw: selfType.description)" + parametersToLower = parameters[...] + + case .initializer(let selfType): + // Initializers use the Swift type to create the instance. Save it + // for later. There is no corresponding cdecl parameter. + initializerType = selfType + cdeclToOriginalSelf = nil + parametersToLower = parameters[...] + } + } else { + cdeclToOriginalSelf = nil + parametersToLower = parameters[...] + } + + // Lower the remaining arguments. + let cdeclToOriginalArguments = parametersToLower.indices.map { index in + let originalParam = original.parameters[index] + let cdeclToOriginalArg = try! ConversionStep( + cdeclToSwift: originalParam.type + ).asExprSyntax( + isSelf: false, + placeholder: originalParam.parameterName ?? "_\(index)" + ) + + if let argumentLabel = originalParam.argumentLabel { + return "\(argumentLabel): \(cdeclToOriginalArg.description)" + } else { + return cdeclToOriginalArg.description + } + } + + // Form the call expression. + let callArguments: ExprSyntax = "(\(raw: cdeclToOriginalArguments.joined(separator: ", ")))" + let callExpression: ExprSyntax + if let initializerType { + callExpression = "\(raw: initializerType.description)\(callArguments)" + } else if let cdeclToOriginalSelf { + callExpression = "\(cdeclToOriginalSelf).\(raw: swiftFunctionName)\(callArguments)" + } else { + callExpression = "\(raw: swiftFunctionName)\(callArguments)" + } + + // Handle the return. + if cdecl.result.type.isVoid && original.result.type.isVoid { + // Nothing to return. + loweredCDecl.body = """ + { + \(callExpression) + } + """ + } else { + // Determine the necessary conversion of the Swift return value to the + // cdecl return value. + let resultConversion = try! ConversionStep( + swiftToCDecl: original.result.type, + stdlibTypes: stdlibTypes + ) + + var bodyItems: [CodeBlockItemSyntax] = [] + + // If the are multiple places in the result conversion that reference + // the placeholder, capture the result of the call in a local variable. + // This prevents us from calling the function multiple times. + let originalResult: ExprSyntax + if resultConversion.placeholderCount > 1 { + bodyItems.append(""" + let __swift_result = \(callExpression) + """ + ) + originalResult = "__swift_result" + } else { + originalResult = callExpression + } + + if cdecl.result.type.isVoid { + // Indirect return. This is a regular return in Swift that turns + // into an assignment via the indirect parameters. We do a cdeclToSwift + // conversion on the left-hand side of the tuple to gather all of the + // indirect output parameters we need to assign to, and the result + // conversion is the corresponding right-hand side. + let cdeclParamConversion = try! ConversionStep( + cdeclToSwift: original.result.type + ) + + // For each indirect result, initialize the value directly with the + // corresponding element in the converted result. + bodyItems.append( + contentsOf: cdeclParamConversion.initialize( + placeholder: "_result", + from: resultConversion, + otherPlaceholder: originalResult.description + ) + ) + } else { + // Direct return. Just convert the expression. + let convertedResult = resultConversion.asExprSyntax( + isSelf: true, + placeholder: originalResult.description + ) + + bodyItems.append(""" + return \(convertedResult) + """ + ) + } + + loweredCDecl.body = CodeBlockSyntax( + leftBrace: .leftBraceToken(trailingTrivia: .newline), + statements: .init(bodyItems.map { $0.with(\.trailingTrivia, .newline) }) + ) + } + + return loweredCDecl + } +} + +extension ConversionStep { + /// Form a set of statements that initializes the placeholders within + /// the given conversion step from ones in the other step, effectively + /// exploding something like `(a, (b, c)) = (d, (e, f))` into + /// separate initializations for a, b, and c from d, e, and f, respectively. + func initialize( + placeholder: String, + from otherStep: ConversionStep, + otherPlaceholder: String + ) -> [CodeBlockItemSyntax] { + // Create separate assignments for each element in paired tuples. + if case .tuplify(let elements) = self, + case .tuplify(let otherElements) = otherStep { + assert(elements.count == otherElements.count) + + return elements.indices.flatMap { index in + elements[index].initialize( + placeholder: "\(placeholder)_\(index)", + from: otherElements[index], + otherPlaceholder: "\(otherPlaceholder)_\(index)" + ) + } + } + + // Look through "pass indirectly" steps; they do nothing here. + if case .passIndirectly(let conversionStep) = self { + return conversionStep.initialize( + placeholder: placeholder, + from: otherStep, + otherPlaceholder: otherPlaceholder + ) + } + + // The value we're initializing from. + let otherExpr = otherStep.asExprSyntax( + isSelf: false, + placeholder: otherPlaceholder + ) + + // If we have a "pointee" on where we are performing initialization, we + // need to instead produce an initialize(to:) call. + if case .pointee(let innerSelf) = self { + let selfPointerExpr = innerSelf.asExprSyntax( + isSelf: true, + placeholder: placeholder + ) + + return [ " \(selfPointerExpr).initialize(to: \(otherExpr))" ] + } + + let selfExpr = self.asExprSyntax(isSelf: true, placeholder: placeholder) + return [ " \(selfExpr) = \(otherExpr)" ] + } +} diff --git a/Sources/JExtractSwift/CTypes/CEnum.swift b/Sources/JExtractSwift/CTypes/CEnum.swift new file mode 100644 index 0000000..3caddb9 --- /dev/null +++ b/Sources/JExtractSwift/CTypes/CEnum.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Describes a C enum type. +public class CEnum { + public var name: String + public init(name: String) { + self.name = name + } +} diff --git a/Sources/JExtractSwift/CTypes/CFunction.swift b/Sources/JExtractSwift/CTypes/CFunction.swift new file mode 100644 index 0000000..0d01f38 --- /dev/null +++ b/Sources/JExtractSwift/CTypes/CFunction.swift @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Describes a C function. +public struct CFunction { + /// The result type of the function. + public var resultType: CType + + /// The name of the function. + public var name: String + + /// The parameters of the function. + public var parameters: [CParameter] + + /// Whether the function is variadic. + public var isVariadic: Bool + + public init(resultType: CType, name: String, parameters: [CParameter], isVariadic: Bool) { + self.resultType = resultType + self.name = name + self.parameters = parameters + self.isVariadic = isVariadic + } + + /// Produces the type of the function. + public var functionType: CType { + .function( + resultType: resultType, + parameters: parameters.map { $0.type }, + variadic: isVariadic + ) + } +} + +extension CFunction: CustomStringConvertible { + /// Print the declaration of this C function + public var description: String { + var result = "" + + var hasEmptyPlaceholder = false + resultType.printBefore(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result) + + result += name + + // Function parameters. + result += "(" + result += parameters.map { $0.description }.joined(separator: ", ") + CType.printFunctionParametersSuffix( + isVariadic: isVariadic, + hasZeroParameters: parameters.isEmpty, + to: &result + ) + result += ")" + + resultType.printAfter( + hasEmptyPlaceholder: &hasEmptyPlaceholder, + result: &result + ) + + result += "" + return result + } +} diff --git a/Sources/JExtractSwift/CTypes/CParameter.swift b/Sources/JExtractSwift/CTypes/CParameter.swift new file mode 100644 index 0000000..500af4b --- /dev/null +++ b/Sources/JExtractSwift/CTypes/CParameter.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Describes a parameter to a C function. +public struct CParameter { + /// The name of the parameter, if provided. + public var name: String? + + /// The type of the parameter. + public var type: CType + + public init(name: String? = nil, type: CType) { + self.name = name + self.type = type + } +} + +extension CParameter: CustomStringConvertible { + public var description: String { + type.print(placeholder: name ?? "") + } +} diff --git a/Sources/JExtractSwift/CTypes/CStruct.swift b/Sources/JExtractSwift/CTypes/CStruct.swift new file mode 100644 index 0000000..c7e3ab6 --- /dev/null +++ b/Sources/JExtractSwift/CTypes/CStruct.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Describes a C struct type. +public class CStruct { + public var name: String + + public init(name: String) { + self.name = name + } +} diff --git a/Sources/JExtractSwift/CTypes/CTag.swift b/Sources/JExtractSwift/CTypes/CTag.swift new file mode 100644 index 0000000..3e26d2d --- /dev/null +++ b/Sources/JExtractSwift/CTypes/CTag.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Describes a tag type in C, which is either a struct or an enum. +public enum CTag { + case `struct`(CStruct) + case `enum`(CEnum) + case `union`(CUnion) + + public var name: String { + switch self { + case .struct(let cStruct): return cStruct.name + case .enum(let cEnum): return cEnum.name + case .union(let cUnion): return cUnion.name + } + } +} + diff --git a/Sources/JExtractSwift/CTypes/CType.swift b/Sources/JExtractSwift/CTypes/CType.swift new file mode 100644 index 0000000..9b8744d --- /dev/null +++ b/Sources/JExtractSwift/CTypes/CType.swift @@ -0,0 +1,300 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Describes a type in the C type system as it is used for lowering of Swift +/// declarations to C. +/// +/// This description of the C type system only has to account for the types +/// that are used when providing C-compatible thunks from Swift code. It is +/// not a complete representation of the C type system, and leaves some +/// target-specific types (like the actual type that ptrdiff_t and size_t +/// map to) unresolved. +public enum CType { + /// A tag type, such as a struct or enum. + case tag(CTag) + + /// An integral type. + case integral(IntegralType) + + /// A floating-point type. + case floating(FloatingType) + + case void + + /// A qualiied type, such as 'const T'. + indirect case qualified(const: Bool, volatile: Bool, type: CType) + + /// A pointer to the given type. + indirect case pointer(CType) + + /// A function type. + indirect case function(resultType: CType, parameters: [CType], variadic: Bool) + + /// An integral type in C, described mostly in terms of the bit-sized + /// typedefs rather than actual C types (like int or long), because Swift + /// deals in bit-widths. + public enum IntegralType { + case bool + + /// A signed integer type stored with the given number of bits. This + /// corresponds to the intNNN_t types from . + case signed(bits: Int) + + /// An unsigned integer type stored with the given number of bits. This + /// corresponds to the uintNNN_t types from . + case unsigned(bits: Int) + + /// The ptrdiff_t type, which in C is a typedef for the signed integer + /// type that is the same size as a pointer. + case ptrdiff_t + + /// The size_t type, which in C is a typedef for the unsigned integer + /// type that is the same size as a pointer. + case size_t + } + + /// A floating point type in C. + public enum FloatingType { + case float + case double + } +} + +extension CType: CustomStringConvertible { + /// Print the part of this type that comes before the declarator, appending + /// it to the provided `result` string. + func printBefore(hasEmptyPlaceholder: inout Bool, result: inout String) { + // Save the value of hasEmptyPlaceholder and restore it once we're done + // here. + let previousHasEmptyPlaceholder = hasEmptyPlaceholder + defer { + hasEmptyPlaceholder = previousHasEmptyPlaceholder + } + + switch self { + case .floating(let floating): + switch floating { + case .float: result += "float" + case .double: result += "double" + } + + spaceBeforePlaceHolder( + hasEmptyPlaceholder: hasEmptyPlaceholder, + result: &result + ) + + case .function(resultType: let resultType, parameters: _, variadic: _): + let previousHasEmptyPlaceholder = hasEmptyPlaceholder + hasEmptyPlaceholder = false + defer { + hasEmptyPlaceholder = previousHasEmptyPlaceholder + } + resultType.printBefore( + hasEmptyPlaceholder: &hasEmptyPlaceholder, + result: &result + ) + + if !previousHasEmptyPlaceholder { + result += "(" + } + + case .integral(let integral): + switch integral { + case .bool: result += "_Bool" + case .signed(let bits): result += "int\(bits)_t" + case .unsigned(let bits): result += "uint\(bits)_t" + case .ptrdiff_t: result += "ptrdiff_t" + case .size_t: result += "size_t" + } + + spaceBeforePlaceHolder( + hasEmptyPlaceholder: hasEmptyPlaceholder, + result: &result + ) + + case .pointer(let pointee): + var innerHasEmptyPlaceholder = false + pointee.printBefore( + hasEmptyPlaceholder: &innerHasEmptyPlaceholder, + result: &result + ) + result += "*" + + case .qualified(const: let isConst, volatile: let isVolatile, type: let underlying): + func printQualifier(_ qualifier: String, if condition: Bool) { + if condition { + result += qualifier + hasEmptyPlaceholder = false + + spaceBeforePlaceHolder( + hasEmptyPlaceholder: hasEmptyPlaceholder, + result: &result + ) + } + } + + let canPrefixQualifiers = underlying.canPrefixQualifiers + if canPrefixQualifiers { + printQualifier("const", if: isConst) + printQualifier("volatile", if: isVolatile) + } + + if (isConst || isVolatile) && !canPrefixQualifiers { + hasEmptyPlaceholder = false + } + + underlying.printBefore(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result) + + if !canPrefixQualifiers { + printQualifier("const", if: isConst) + printQualifier("volatile", if: isVolatile) + } + + case .tag(let tag): + switch tag { + case .enum(let cEnum): result += "enum \(cEnum.name)" + case .struct(let cStruct): result += "struct \(cStruct.name)" + case .union(let cUnion): result += "union \(cUnion.name)" + } + + spaceBeforePlaceHolder( + hasEmptyPlaceholder: hasEmptyPlaceholder, + result: &result + ) + + case .void: + result += "void" + + spaceBeforePlaceHolder( + hasEmptyPlaceholder: hasEmptyPlaceholder, + result: &result + ) + } + } + + /// Render an appropriate "suffix" to the parameter list of a function, + /// which goes just before the closing ")" of that function, appending + /// it to the string. This includes whether the function is variadic and + /// whether is had zero parameters. + static func printFunctionParametersSuffix( + isVariadic: Bool, + hasZeroParameters: Bool, + to result: inout String + ) { + // Take care of variadic parameters and empty parameter lists together, + // because the formatter of the former depends on the latter. + switch (isVariadic, hasZeroParameters) { + case (true, false): result += ", ..." + case (true, true): result += "..." + case (false, true): result += "void" + case (false, false): break + } + } + + /// Print the part of the type that comes after the declarator, appending + /// it to the provided `result` string. + func printAfter(hasEmptyPlaceholder: inout Bool, result: inout String) { + switch self { + case .floating, .integral, .tag, .void: break + + case .function(resultType: let resultType, parameters: let parameters, variadic: let variadic): + if !hasEmptyPlaceholder { + result += ")" + } + + result += "(" + + // Render the parameter types. + result += parameters.map { $0.description }.joined(separator: ", ") + + CType.printFunctionParametersSuffix( + isVariadic: variadic, + hasZeroParameters: parameters.isEmpty, + to: &result + ) + + result += ")" + + var innerHasEmptyPlaceholder = false + resultType.printAfter( + hasEmptyPlaceholder: &innerHasEmptyPlaceholder, + result: &result + ) + + case .pointer(let pointee): + var innerHasEmptyPlaceholder = false + pointee.printAfter( + hasEmptyPlaceholder: &innerHasEmptyPlaceholder, + result: &result + ) + + case .qualified(const: _, volatile: _, type: let underlying): + underlying.printAfter( + hasEmptyPlaceholder: &hasEmptyPlaceholder, + result: &result + ) + } + } + + /// Print this type into a string, with the given placeholder as the name + /// of the entity being declared. + public func print(placeholder: String?) -> String { + var hasEmptyPlaceholder = (placeholder == nil) + var result = "" + printBefore(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result) + if let placeholder { + result += placeholder + } + printAfter(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result) + return result + } + + /// Render the C type into a string that represents the type in C. + public var description: String { + print(placeholder: nil) + } + + /// Print a space before the placeholder in a declarator. + private func spaceBeforePlaceHolder( + hasEmptyPlaceholder: Bool, + result: inout String + ) { + if !hasEmptyPlaceholder { + result += " " + } + } + + /// Determine whether qualifiers can be printed before the given type + /// (`const int`) vs. having to be afterward to maintain semantics. + var canPrefixQualifiers: Bool { + switch self { + case .floating, .integral, .tag, .void: true + case .function, .pointer: false + case .qualified(const: _, volatile: _, type: let type): type.canPrefixQualifiers + } + } +} + +extension CType { + /// Apply the rules for function parameter decay to produce the resulting + /// decayed type. For example, this will adjust a function type to a + /// pointer-to-function type. + var parameterDecay: CType { + switch self { + case .floating, .integral, .pointer, .qualified, .tag, .void: self + + case .function: .pointer(self) + } + } +} diff --git a/Sources/JExtractSwift/CTypes/CUnion.swift b/Sources/JExtractSwift/CTypes/CUnion.swift new file mode 100644 index 0000000..e8d68c1 --- /dev/null +++ b/Sources/JExtractSwift/CTypes/CUnion.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Describes a C union type. +public class CUnion { + public var name: String + public init(name: String) { + self.name = name + } +} diff --git a/Sources/JExtractSwift/ConversionStep.swift b/Sources/JExtractSwift/ConversionStep.swift new file mode 100644 index 0000000..31b33a8 --- /dev/null +++ b/Sources/JExtractSwift/ConversionStep.swift @@ -0,0 +1,156 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftSyntax + +/// Describes the transformation needed to take the parameters of a thunk +/// and map them to the corresponding parameter (or result value) of the +/// original function. +enum ConversionStep: Equatable { + /// The value being lowered. + case placeholder + + /// A reference to a component in a value that has been exploded, such as + /// a tuple element or part of a buffer pointer. + indirect case explodedComponent(ConversionStep, component: String) + + /// Cast the pointer described by the lowering step to the given + /// Swift type using `unsafeBitCast(_:to:)`. + indirect case unsafeCastPointer(ConversionStep, swiftType: SwiftType) + + /// Assume at the untyped pointer described by the lowering step to the + /// given type, using `assumingMemoryBound(to:).` + indirect case typedPointer(ConversionStep, swiftType: SwiftType) + + /// The thing to which the pointer typed, which is the `pointee` property + /// of the `Unsafe(Mutable)Pointer` types in Swift. + indirect case pointee(ConversionStep) + + /// Pass this value indirectly, via & for explicit `inout` parameters. + indirect case passIndirectly(ConversionStep) + + /// Initialize a value of the given Swift type with the set of labeled + /// arguments. + case initialize(SwiftType, arguments: [LabeledArgument]) + + /// Produce a tuple with the given elements. + /// + /// This is used for exploding Swift tuple arguments into multiple + /// elements, recursively. Note that this always produces unlabeled + /// tuples, which Swift will convert to the labeled tuple form. + case tuplify([ConversionStep]) + + /// Create an initialization step that produces the raw pointer type that + /// corresponds to the typed pointer. + init( + initializeRawPointerFromTyped typedStep: ConversionStep, + isMutable: Bool, + isPartOfBufferPointer: Bool, + stdlibTypes: SwiftStandardLibraryTypes + ) { + // Initialize the corresponding raw pointer type from the typed + // pointer we have on the Swift side. + let rawPointerType = isMutable + ? stdlibTypes[.unsafeMutableRawPointer] + : stdlibTypes[.unsafeRawPointer] + self = .initialize( + .nominal( + SwiftNominalType( + nominalTypeDecl: rawPointerType + ) + ), + arguments: [ + LabeledArgument( + argument: isPartOfBufferPointer + ? .explodedComponent( + typedStep, + component: "pointer" + ) + : typedStep + ), + ] + ) + } + + /// Count the number of times that the placeholder occurs within this + /// conversion step. + var placeholderCount: Int { + switch self { + case .explodedComponent(let inner, component: _), + .passIndirectly(let inner), .pointee(let inner), + .typedPointer(let inner, swiftType: _), + .unsafeCastPointer(let inner, swiftType: _): + inner.placeholderCount + case .initialize(_, arguments: let arguments): + arguments.reduce(0) { $0 + $1.argument.placeholderCount } + case .placeholder: + 1 + case .tuplify(let elements): + elements.reduce(0) { $0 + $1.placeholderCount } + } + } + + /// Convert the conversion step into an expression with the given + /// value as the placeholder value in the expression. + func asExprSyntax(isSelf: Bool, placeholder: String) -> ExprSyntax { + switch self { + case .placeholder: + return "\(raw: placeholder)" + + case .explodedComponent(let step, component: let component): + return step.asExprSyntax(isSelf: false, placeholder: "\(placeholder)_\(component)") + + case .unsafeCastPointer(let step, swiftType: let swiftType): + let untypedExpr = step.asExprSyntax(isSelf: false, placeholder: placeholder) + return "unsafeBitCast(\(untypedExpr), to: \(swiftType.metatypeReferenceExprSyntax))" + + case .typedPointer(let step, swiftType: let type): + let untypedExpr = step.asExprSyntax(isSelf: isSelf, placeholder: placeholder) + return "\(untypedExpr).assumingMemoryBound(to: \(type.metatypeReferenceExprSyntax))" + + case .pointee(let step): + let untypedExpr = step.asExprSyntax(isSelf: isSelf, placeholder: placeholder) + return "\(untypedExpr).pointee" + + case .passIndirectly(let step): + let innerExpr = step.asExprSyntax(isSelf: false, placeholder: placeholder) + return isSelf ? innerExpr : "&\(innerExpr)" + + case .initialize(let type, arguments: let arguments): + let renderedArguments: [String] = arguments.map { labeledArgument in + let renderedArg = labeledArgument.argument.asExprSyntax(isSelf: false, placeholder: placeholder) + if let argmentLabel = labeledArgument.label { + return "\(argmentLabel): \(renderedArg.description)" + } else { + return renderedArg.description + } + } + + // FIXME: Should be able to use structured initializers here instead + // of splatting out text. + let renderedArgumentList = renderedArguments.joined(separator: ", ") + return "\(raw: type.description)(\(raw: renderedArgumentList))" + + case .tuplify(let elements): + let renderedElements: [String] = elements.enumerated().map { (index, element) in + element.asExprSyntax(isSelf: false, placeholder: "\(placeholder)_\(index)").description + } + + // FIXME: Should be able to use structured initializers here instead + // of splatting out text. + let renderedElementList = renderedElements.joined(separator: ", ") + return "(\(raw: renderedElementList))" + } + } +} diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift deleted file mode 100644 index 5e0c5b1..0000000 --- a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift +++ /dev/null @@ -1,557 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 lowerFunctionSignature(signature) - } - - /// 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 - ) throws -> LoweredFunctionSignature { - // Lower all of the parameters. - let loweredSelf = try signature.selfParameter.map { selfParameter in - try lowerParameter( - selfParameter.type, - convention: selfParameter.convention, parameterName: "self" - ) - } - - let loweredParameters = try signature.parameters.enumerated().map { (index, param) in - try lowerParameter( - param.type, - convention: param.convention, - parameterName: param.parameterName ?? "_\(index)" - ) - } - - // Lower the result. - var loweredResult = try lowerParameter( - signature.result.type, - convention: .byValue, - parameterName: "_result" - ) - - // If the result type doesn't lower to either empty (void) or a single - // primitive result, make it indirect. - let indirectResult: Bool - if !(loweredResult.javaFFMParameters.count == 0 || - (loweredResult.javaFFMParameters.count == 1 && - loweredResult.javaFFMParameters[0].isPrimitive)) { - loweredResult = try lowerParameter( - signature.result.type, - convention: .inout, - parameterName: "_result" - ) - indirectResult = true - } else { - indirectResult = false - } - - // Collect all of the lowered parameters for the @_cdecl function. - var allLoweredParameters: [LoweredParameters] = [] - var cdeclLoweredParameters: [SwiftParameter] = [] - if let loweredSelf { - allLoweredParameters.append(loweredSelf) - cdeclLoweredParameters.append(contentsOf: loweredSelf.cdeclParameters) - } - allLoweredParameters.append(contentsOf: loweredParameters) - cdeclLoweredParameters.append( - contentsOf: loweredParameters.flatMap { $0.cdeclParameters } - ) - - let cdeclResult: SwiftResult - if indirectResult { - cdeclLoweredParameters.append( - contentsOf: loweredResult.cdeclParameters - ) - cdeclResult = .init(convention: .direct, type: .tuple([])) - } else if loweredResult.cdeclParameters.count == 1, - let primitiveResult = loweredResult.cdeclParameters.first { - cdeclResult = .init(convention: .direct, type: primitiveResult.type) - } else if loweredResult.cdeclParameters.count == 0 { - cdeclResult = .init(convention: .direct, type: .tuple([])) - } else { - fatalError("Improper lowering of result for \(signature)") - } - - let cdeclSignature = SwiftFunctionSignature( - isStaticOrClass: false, - selfParameter: nil, - parameters: cdeclLoweredParameters, - result: cdeclResult - ) - - return LoweredFunctionSignature( - original: signature, - cdecl: cdeclSignature, - parameters: allLoweredParameters, - result: loweredResult - ) - } - - func lowerParameter( - _ type: SwiftType, - convention: SwiftParameterConvention, - parameterName: String - ) throws -> LoweredParameters { - switch type { - case .function, .optional: - throw LoweringError.unhandledType(type) - - case .metatype(let instanceType): - return LoweredParameters( - cdeclToOriginal: .unsafeCastPointer( - .passDirectly(parameterName), - swiftType: instanceType - ), - cdeclParameters: [ - SwiftParameter( - convention: .byValue, - parameterName: parameterName, - type: .nominal( - SwiftNominalType( - nominalTypeDecl: swiftStdlibTypes.unsafeRawPointerDecl - ) - ) - ) - ], - javaFFMParameters: [.SwiftPointer] - ) - - case .nominal(let nominal): - // Types from the Swift standard library that we know about. - if nominal.nominalTypeDecl.moduleName == "Swift", - nominal.nominalTypeDecl.parent == nil { - // Primitive types - if let loweredPrimitive = try lowerParameterPrimitive(nominal, convention: convention, parameterName: parameterName) { - return loweredPrimitive - } - - // Swift pointer types. - if let loweredPointers = try lowerParameterPointers(nominal, convention: convention, parameterName: parameterName) { - return loweredPointers - } - } - - let mutable = (convention == .inout) - let loweringStep: LoweringStep - switch nominal.nominalTypeDecl.kind { - case .actor, .class: - loweringStep = - .unsafeCastPointer(.passDirectly(parameterName), swiftType: type) - case .enum, .struct, .protocol: - loweringStep = - .passIndirectly(.pointee( .typedPointer(.passDirectly(parameterName), swiftType: type))) - } - - return LoweredParameters( - cdeclToOriginal: loweringStep, - cdeclParameters: [ - SwiftParameter( - convention: .byValue, - parameterName: parameterName, - type: .nominal( - SwiftNominalType( - nominalTypeDecl: mutable - ? swiftStdlibTypes.unsafeMutableRawPointerDecl - : swiftStdlibTypes.unsafeRawPointerDecl - ) - ) - ) - ], - javaFFMParameters: [.SwiftPointer] - ) - - case .tuple(let tuple): - let parameterNames = tuple.indices.map { "\(parameterName)_\($0)" } - let loweredElements: [LoweredParameters] = try zip(tuple, parameterNames).map { element, name in - try lowerParameter(element, convention: convention, parameterName: name) - } - return LoweredParameters( - cdeclToOriginal: .tuplify(loweredElements.map { $0.cdeclToOriginal }), - cdeclParameters: loweredElements.flatMap { $0.cdeclParameters }, - javaFFMParameters: loweredElements.flatMap { $0.javaFFMParameters } - ) - } - } - - func lowerParameterPrimitive( - _ nominal: SwiftNominalType, - convention: SwiftParameterConvention, - parameterName: String - ) throws -> LoweredParameters? { - let nominalName = nominal.nominalTypeDecl.name - let type = SwiftType.nominal(nominal) - - // Swift types that map directly to Java primitive types. - if let primitiveType = JavaType(swiftTypeName: nominalName) { - // We cannot handle inout on primitive types. - if convention == .inout { - throw LoweringError.inoutNotSupported(type) - } - - return LoweredParameters( - cdeclToOriginal: .passDirectly(parameterName), - cdeclParameters: [ - SwiftParameter( - convention: convention, - parameterName: parameterName, - type: type - ) - ], - javaFFMParameters: [ - ForeignValueLayout(javaType: primitiveType)! - ] - ) - } - - // The Swift "Int" type, which maps to whatever the pointer-sized primitive - // integer type is in Java (int for 32-bit, long for 64-bit). - if nominalName == "Int" { - // We cannot handle inout on primitive types. - if convention == .inout { - throw LoweringError.inoutNotSupported(type) - } - - return LoweredParameters( - cdeclToOriginal: .passDirectly(parameterName), - cdeclParameters: [ - SwiftParameter( - convention: convention, - parameterName: parameterName, - type: type - ) - ], - javaFFMParameters: [ - .SwiftInt - ] - ) - } - - return nil - } - - func lowerParameterPointers( - _ nominal: SwiftNominalType, - convention: SwiftParameterConvention, - parameterName: String - ) throws -> LoweredParameters? { - let nominalName = nominal.nominalTypeDecl.name - let type = SwiftType.nominal(nominal) - - guard let (requiresArgument, mutable, hasCount) = nominalName.isNameOfSwiftPointerType else { - return nil - } - - // At the @_cdecl level, make everything a raw pointer. - let cdeclPointerType = mutable - ? swiftStdlibTypes.unsafeMutableRawPointerDecl - : swiftStdlibTypes.unsafeRawPointerDecl - var cdeclToOriginal: LoweringStep - switch (requiresArgument, hasCount) { - case (false, false): - cdeclToOriginal = .passDirectly(parameterName) - - case (true, false): - cdeclToOriginal = .typedPointer( - .passDirectly(parameterName + "_pointer"), - swiftType: nominal.genericArguments![0] - ) - - case (false, true): - cdeclToOriginal = .initialize(type, arguments: [ - LabeledArgument(label: "start", argument: .passDirectly(parameterName + "_pointer")), - LabeledArgument(label: "count", argument: .passDirectly(parameterName + "_count")) - ]) - - case (true, true): - cdeclToOriginal = .initialize( - type, - arguments: [ - LabeledArgument(label: "start", - argument: .typedPointer( - .passDirectly(parameterName + "_pointer"), - swiftType: nominal.genericArguments![0])), - LabeledArgument(label: "count", - argument: .passDirectly(parameterName + "_count")) - ] - ) - } - - let lowered: [(SwiftParameter, ForeignValueLayout)] - if hasCount { - lowered = [ - ( - SwiftParameter( - convention: convention, - parameterName: parameterName + "_pointer", - type: SwiftType.nominal( - SwiftNominalType(nominalTypeDecl: cdeclPointerType) - ) - ), - .SwiftPointer - ), - ( - SwiftParameter( - convention: convention, - parameterName: parameterName + "_count", - type: SwiftType.nominal( - SwiftNominalType(nominalTypeDecl: swiftStdlibTypes.intDecl) - ) - ), - .SwiftInt - ) - ] - } else { - lowered = [ - ( - SwiftParameter( - convention: convention, - parameterName: parameterName + "_pointer", - type: SwiftType.nominal( - SwiftNominalType(nominalTypeDecl: cdeclPointerType) - ) - ), - .SwiftPointer - ), - ] - } - - return LoweredParameters( - cdeclToOriginal: cdeclToOriginal, - cdeclParameters: lowered.map(\.0), - javaFFMParameters: lowered.map(\.1) - ) - } -} - -struct LabeledArgument { - var label: String? - var argument: Element -} - -extension LabeledArgument: Equatable where Element: Equatable { } - -/// Describes the transformation needed to take the parameters of a thunk -/// and map them to the corresponding parameter (or result value) of the -/// original function. -enum LoweringStep: Equatable { - /// A direct reference to a parameter of the thunk. - case passDirectly(String) - - /// Cast the pointer described by the lowering step to the given - /// Swift type using `unsafeBitCast(_:to:)`. - indirect case unsafeCastPointer(LoweringStep, swiftType: SwiftType) - - /// Assume at the untyped pointer described by the lowering step to the - /// given type, using `assumingMemoryBound(to:).` - indirect case typedPointer(LoweringStep, swiftType: SwiftType) - - /// The thing to which the pointer typed, which is the `pointee` property - /// of the `Unsafe(Mutable)Pointer` types in Swift. - indirect case pointee(LoweringStep) - - /// Pass this value indirectly, via & for explicit `inout` parameters. - indirect case passIndirectly(LoweringStep) - - /// Initialize a value of the given Swift type with the set of labeled - /// arguments. - case initialize(SwiftType, arguments: [LabeledArgument]) - - /// Produce a tuple with the given elements. - /// - /// This is used for exploding Swift tuple arguments into multiple - /// elements, recursively. Note that this always produces unlabeled - /// tuples, which Swift will convert to the labeled tuple form. - case tuplify([LoweringStep]) -} - -struct LoweredParameters: Equatable { - /// The steps needed to get from the @_cdecl parameters to the original function - /// parameter. - var cdeclToOriginal: LoweringStep - - /// The lowering of the parameters at the C level in Swift. - var cdeclParameters: [SwiftParameter] - - /// The lowering of the parameters at the C level as expressed for Java's - /// foreign function and memory interface. - /// - /// The elements in this array match up with those of 'cdeclParameters'. - var javaFFMParameters: [ForeignValueLayout] -} - -extension LoweredParameters { - /// Produce an expression that computes the argument for this parameter - /// when calling the original function from the cdecl entrypoint. - func cdeclToOriginalArgumentExpr(isSelf: Bool)-> ExprSyntax { - cdeclToOriginal.asExprSyntax(isSelf: isSelf) - } -} - -extension LoweringStep { - func asExprSyntax(isSelf: Bool) -> ExprSyntax { - switch self { - case .passDirectly(let rawArgument): - return "\(raw: rawArgument)" - - case .unsafeCastPointer(let step, swiftType: let swiftType): - let untypedExpr = step.asExprSyntax(isSelf: false) - return "unsafeBitCast(\(untypedExpr), to: \(swiftType.metatypeReferenceExprSyntax))" - - case .typedPointer(let step, swiftType: let type): - let untypedExpr = step.asExprSyntax(isSelf: isSelf) - return "\(untypedExpr).assumingMemoryBound(to: \(type.metatypeReferenceExprSyntax))" - - case .pointee(let step): - let untypedExpr = step.asExprSyntax(isSelf: isSelf) - return "\(untypedExpr).pointee" - - case .passIndirectly(let step): - let innerExpr = step.asExprSyntax(isSelf: false) - return isSelf ? innerExpr : "&\(innerExpr)" - - case .initialize(let type, arguments: let arguments): - let renderedArguments: [String] = arguments.map { labeledArgument in - let renderedArg = labeledArgument.argument.asExprSyntax(isSelf: false) - if let argmentLabel = labeledArgument.label { - return "\(argmentLabel): \(renderedArg.description)" - } else { - return renderedArg.description - } - } - - // FIXME: Should be able to use structured initializers here instead - // of splatting out text. - let renderedArgumentList = renderedArguments.joined(separator: ", ") - return "\(raw: type.description)(\(raw: renderedArgumentList))" - - case .tuplify(let elements): - let renderedElements: [String] = elements.map { element in - element.asExprSyntax(isSelf: false).description - } - - // FIXME: Should be able to use structured initializers here instead - // of splatting out text. - let renderedElementList = renderedElements.joined(separator: ", ") - return "(\(raw: renderedElementList))" - } - } -} - -enum LoweringError: Error { - case inoutNotSupported(SwiftType) - case unhandledType(SwiftType) -} - -@_spi(Testing) -public struct LoweredFunctionSignature: Equatable { - var original: SwiftFunctionSignature - public var cdecl: SwiftFunctionSignature - - var parameters: [LoweredParameters] - var result: LoweredParameters -} - -extension LoweredFunctionSignature { - /// Produce the `@_cdecl` thunk for this lowered function signature that will - /// call into the original function. - @_spi(Testing) - public func cdeclThunk(cName: String, inputFunction: FunctionDeclSyntax) -> FunctionDeclSyntax { - var loweredCDecl = cdecl.createFunctionDecl(cName) - - // Add the @_cdecl attribute. - let cdeclAttribute: AttributeSyntax = "@_cdecl(\(literal: cName))\n" - loweredCDecl.attributes.append(.attribute(cdeclAttribute)) - - // Create the body. - - // Lower "self", if there is one. - let parametersToLower: ArraySlice - let cdeclToOriginalSelf: ExprSyntax? - if original.selfParameter != nil { - cdeclToOriginalSelf = parameters[0].cdeclToOriginalArgumentExpr(isSelf: true) - parametersToLower = parameters[1...] - } else { - cdeclToOriginalSelf = nil - parametersToLower = parameters[...] - } - - // Lower the remaining arguments. - // FIXME: Should be able to use structured initializers here instead - // of splatting out text. - let cdeclToOriginalArguments = zip(parametersToLower, original.parameters).map { lowering, originalParam in - let cdeclToOriginalArg = lowering.cdeclToOriginalArgumentExpr(isSelf: false) - if let argumentLabel = originalParam.argumentLabel { - return "\(argumentLabel): \(cdeclToOriginalArg.description)" - } else { - return cdeclToOriginalArg.description - } - } - - // Form the call expression. - var callExpression: ExprSyntax = "\(inputFunction.name)(\(raw: cdeclToOriginalArguments.joined(separator: ", ")))" - if let cdeclToOriginalSelf { - callExpression = "\(cdeclToOriginalSelf).\(callExpression)" - } - - // Handle the return. - if cdecl.result.type.isVoid && original.result.type.isVoid { - // Nothing to return. - loweredCDecl.body = """ - { - \(callExpression) - } - """ - } else if cdecl.result.type.isVoid { - // Indirect return. This is a regular return in Swift that turns - // into a - loweredCDecl.body = """ - { - \(result.cdeclToOriginalArgumentExpr(isSelf: true)) = \(callExpression) - } - """ - } else { - // Direct return. - loweredCDecl.body = """ - { - return \(callExpression) - } - """ - } - - return loweredCDecl - } -} diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index ff81ef1..6577a37 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift @@ -39,7 +39,7 @@ public final class Swift2JavaTranslator { /// type representation. package var importedTypes: [String: ImportedNominalType] = [:] - var swiftStdlibTypes: SwiftStandardLibraryTypes + public var swiftStdlibTypes: SwiftStandardLibraryTypes let symbolTable: SwiftSymbolTable let nominalResolution: NominalTypeResolution = NominalTypeResolution() diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift index c2b626f..dc8aadb 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift @@ -19,13 +19,25 @@ import SwiftSyntaxBuilder /// parameters and return type. @_spi(Testing) public struct SwiftFunctionSignature: Equatable { - // FIXME: isStaticOrClass probably shouldn't be here? - var isStaticOrClass: Bool - var selfParameter: SwiftParameter? + var selfParameter: SwiftSelfParameter? var parameters: [SwiftParameter] var result: SwiftResult } +/// Describes the "self" parameter of a Swift function signature. +enum SwiftSelfParameter: Equatable { + /// 'self' is an instance parameter. + case instance(SwiftParameter) + + /// 'self' is a metatype for a static method. We only need the type to + /// form the call. + case staticMethod(SwiftType) + + /// 'self' is the type for a call to an initializer. We only need the type + /// to form the call. + case initializer(SwiftType) +} + extension SwiftFunctionSignature { /// Create a function declaration with the given name that has this /// signature. @@ -49,6 +61,33 @@ extension SwiftFunctionSignature { } extension SwiftFunctionSignature { + init( + _ node: InitializerDeclSyntax, + enclosingType: SwiftType?, + symbolTable: SwiftSymbolTable + ) throws { + guard let enclosingType else { + throw SwiftFunctionTranslationError.missingEnclosingType(node) + } + + // We do not yet support failable initializers. + if node.optionalMark != nil { + throw SwiftFunctionTranslationError.failableInitializer(node) + } + + // Prohibit generics for now. + if let generics = node.genericParameterClause { + throw SwiftFunctionTranslationError.generic(generics) + } + + self.selfParameter = .initializer(enclosingType) + self.result = SwiftResult(convention: .direct, type: enclosingType) + self.parameters = try Self.translateFunctionSignature( + node.signature, + symbolTable: symbolTable + ) + } + init( _ node: FunctionDeclSyntax, enclosingType: SwiftType?, @@ -59,40 +98,36 @@ extension SwiftFunctionSignature { if let enclosingType { var isMutating = false var isConsuming = false - var isStaticOrClass = false + var isStatic = false for modifier in node.modifiers { switch modifier.name.tokenKind { case .keyword(.mutating): isMutating = true - case .keyword(.static), .keyword(.class): isStaticOrClass = true + case .keyword(.static): isStatic = true case .keyword(.consuming): isConsuming = true + case .keyword(.class): throw SwiftFunctionTranslationError.classMethod(modifier.name) default: break } } - if isStaticOrClass { - self.selfParameter = SwiftParameter( - convention: .byValue, - type: .metatype( - enclosingType - ) - ) + if isStatic { + self.selfParameter = .staticMethod(enclosingType) } else { - self.selfParameter = SwiftParameter( - convention: isMutating ? .inout : isConsuming ? .consuming : .byValue, - type: enclosingType + self.selfParameter = .instance( + SwiftParameter( + convention: isMutating ? .inout : isConsuming ? .consuming : .byValue, + type: enclosingType + ) ) } - - self.isStaticOrClass = isStaticOrClass } else { self.selfParameter = nil - self.isStaticOrClass = false } // Translate the parameters. - self.parameters = try node.signature.parameterClause.parameters.map { param in - try SwiftParameter(param, symbolTable: symbolTable) - } + self.parameters = try Self.translateFunctionSignature( + node.signature, + symbolTable: symbolTable + ) // Translate the result type. if let resultType = node.signature.returnClause?.type { @@ -104,17 +139,28 @@ extension SwiftFunctionSignature { self.result = SwiftResult(convention: .direct, type: .tuple([])) } + // Prohibit generics for now. + if let generics = node.genericParameterClause { + throw SwiftFunctionTranslationError.generic(generics) + } + } + + /// Translate the function signature, returning the list of translated + /// parameters. + static func translateFunctionSignature( + _ signature: FunctionSignatureSyntax, + symbolTable: SwiftSymbolTable + ) throws -> [SwiftParameter] { // FIXME: Prohibit effects for now. - if let throwsClause = node.signature.effectSpecifiers?.throwsClause { + if let throwsClause = signature.effectSpecifiers?.throwsClause { throw SwiftFunctionTranslationError.throws(throwsClause) } - if let asyncSpecifier = node.signature.effectSpecifiers?.asyncSpecifier { + if let asyncSpecifier = signature.effectSpecifiers?.asyncSpecifier { throw SwiftFunctionTranslationError.async(asyncSpecifier) } - // Prohibit generics for now. - if let generics = node.genericParameterClause { - throw SwiftFunctionTranslationError.generic(generics) + return try signature.parameterClause.parameters.map { param in + try SwiftParameter(param, symbolTable: symbolTable) } } } @@ -123,4 +169,7 @@ enum SwiftFunctionTranslationError: Error { case `throws`(ThrowsClauseSyntax) case async(TokenSyntax) case generic(GenericParameterClauseSyntax) + case classMethod(TokenSyntax) + case missingEnclosingType(InitializerDeclSyntax) + case failableInitializer(InitializerDeclSyntax) } diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift index 1e5637e..565a24c 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift @@ -27,7 +27,12 @@ struct SwiftFunctionType: Equatable { extension SwiftFunctionType: CustomStringConvertible { var description: String { - return "(\(parameters.map { $0.descriptionInType } )) -> \(resultType.description)" + let parameterString = parameters.map { $0.descriptionInType }.joined(separator: ", ") + let conventionPrefix = switch convention { + case .c: "@convention(c) " + case .swift: "" + } + return "\(conventionPrefix)(\(parameterString)) -> \(resultType.description)" } } diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift index cf017f9..53e103b 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -42,6 +42,12 @@ class SwiftNominalTypeDeclaration { // TODO: Generic parameters. + /// Identify this nominal declaration as one of the known standard library + /// types, like 'Swift.Int[. + lazy var knownStandardLibraryType: KnownStandardLibraryType? = { + self.computeKnownStandardLibraryType() + }() + /// Create a nominal type declaration from the syntax node for a nominal type /// declaration. init( @@ -63,6 +69,16 @@ class SwiftNominalTypeDeclaration { default: fatalError("Not a nominal type declaration") } } + + /// Determine the known standard library type for this nominal type + /// declaration. + private func computeKnownStandardLibraryType() -> KnownStandardLibraryType? { + if parent != nil || moduleName != "Swift" { + return nil + } + + return KnownStandardLibraryType(typeNameInSwiftModule: name) + } } extension SwiftNominalTypeDeclaration: Equatable { diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift index 35b7a0e..24b3d8b 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift @@ -19,6 +19,7 @@ struct SwiftParameter: Equatable { var argumentLabel: String? var parameterName: String? var type: SwiftType + var canBeDirectReturn = false } extension SwiftParameter: CustomStringConvertible { @@ -63,8 +64,10 @@ extension SwiftParameter { var type = node.type var convention = SwiftParameterConvention.byValue if let attributedType = type.as(AttributedTypeSyntax.self) { + var sawUnknownSpecifier = false for specifier in attributedType.specifiers { guard case .simpleTypeSpecifier(let simple) = specifier else { + sawUnknownSpecifier = true continue } @@ -74,13 +77,15 @@ extension SwiftParameter { case .keyword(.inout): convention = .inout default: + sawUnknownSpecifier = true break } } // Ignore anything else in the attributed type. - // FIXME: We might want to check for these and ignore them. - type = attributedType.baseType + if !sawUnknownSpecifier && attributedType.attributes.isEmpty { + type = attributedType.baseType + } } self.convention = convention diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift index 57a5865..fbd7b4f 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift @@ -14,75 +14,101 @@ import SwiftSyntax -/// Captures many types from the Swift standard library in their most basic -/// forms, so that the translator can reason about them in source code. -struct SwiftStandardLibraryTypes { - /// Swift.UnsafeRawPointer - var unsafeRawPointerDecl: SwiftNominalTypeDeclaration +enum KnownStandardLibraryType: String, Hashable, CaseIterable { + case bool = "Bool" + case int = "Int" + case uint = "UInt" + case int8 = "Int8" + case uint8 = "UInt8" + case int16 = "Int16" + case uint16 = "UInt16" + case int32 = "Int32" + case uint32 = "UInt32" + case int64 = "Int64" + case uint64 = "UInt64" + case float = "Float" + case double = "Double" + case unsafeRawPointer = "UnsafeRawPointer" + case unsafeMutableRawPointer = "UnsafeMutableRawPointer" + case unsafePointer = "UnsafePointer" + case unsafeMutablePointer = "UnsafeMutablePointer" + case unsafeBufferPointer = "UnsafeBufferPointer" + case unsafeMutableBufferPointer = "UnsafeMutableBufferPointer" + + var typeName: String { rawValue } + + init?(typeNameInSwiftModule: String) { + self.init(rawValue: typeNameInSwiftModule) + } - /// Swift.UnsafeMutableRawPointer - var unsafeMutableRawPointerDecl: SwiftNominalTypeDeclaration + /// Whether this declaration is generic. + var isGeneric: Bool { + switch self { + case .bool, .double, .float, .int, .int8, .int16, .int32, .int64, + .uint, .uint8, .uint16, .uint32, .uint64, .unsafeRawPointer, + .unsafeMutableRawPointer: + false + + case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, + .unsafeMutableBufferPointer: + true + } + } +} +/// Captures many types from the Swift standard library in their most basic +/// forms, so that the translator can reason about them in source code. +public struct SwiftStandardLibraryTypes { // Swift.UnsafePointer - var unsafePointerDecl: SwiftNominalTypeDeclaration + let unsafePointerDecl: SwiftNominalTypeDeclaration // Swift.UnsafeMutablePointer - var unsafeMutablePointerDecl: SwiftNominalTypeDeclaration + let unsafeMutablePointerDecl: SwiftNominalTypeDeclaration // Swift.UnsafeBufferPointer - var unsafeBufferPointerDecl: SwiftNominalTypeDeclaration + let unsafeBufferPointerDecl: SwiftNominalTypeDeclaration // Swift.UnsafeMutableBufferPointer - var unsafeMutableBufferPointerDecl: SwiftNominalTypeDeclaration - - /// Swift.Bool - var boolDecl: SwiftNominalTypeDeclaration - - /// Swift.Int8 - var int8Decl: SwiftNominalTypeDeclaration - - /// Swift.Int16 - var int16Decl: SwiftNominalTypeDeclaration - - /// Swift.UInt16 - var uint16Decl: SwiftNominalTypeDeclaration - - /// Swift.Int32 - var int32Decl: SwiftNominalTypeDeclaration - - /// Swift.Int64 - var int64Decl: SwiftNominalTypeDeclaration - - /// Swift.Int - var intDecl: SwiftNominalTypeDeclaration - - /// Swift.Float - var floatDecl: SwiftNominalTypeDeclaration - - /// Swift.Double - var doubleDecl: SwiftNominalTypeDeclaration - - /// Swift.String - var stringDecl: SwiftNominalTypeDeclaration - - init(into parsedModule: inout SwiftParsedModuleSymbolTable) { - // Pointer types - self.unsafeRawPointerDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("UnsafeRawPointer"), - memberBlock: .init(members: []) - ), - parent: nil - ) + let unsafeMutableBufferPointerDecl: SwiftNominalTypeDeclaration + + /// Mapping from known standard library types to their nominal type declaration. + let knownTypeToNominal: [KnownStandardLibraryType: SwiftNominalTypeDeclaration] + + /// Mapping from nominal type declarations to known types. + let nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: KnownStandardLibraryType] + + private static func recordKnownType( + _ type: KnownStandardLibraryType, + _ syntax: NominalTypeDeclSyntaxNode, + knownTypeToNominal: inout [KnownStandardLibraryType: SwiftNominalTypeDeclaration], + nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: KnownStandardLibraryType], + parsedModule: inout SwiftParsedModuleSymbolTable + ) { + let nominalDecl = parsedModule.addNominalTypeDeclaration(syntax, parent: nil) + knownTypeToNominal[type] = nominalDecl + nominalTypeDeclToKnownType[nominalDecl] = type + } - self.unsafeMutableRawPointerDecl = parsedModule.addNominalTypeDeclaration( + private static func recordKnownNonGenericStruct( + _ type: KnownStandardLibraryType, + knownTypeToNominal: inout [KnownStandardLibraryType: SwiftNominalTypeDeclaration], + nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: KnownStandardLibraryType], + parsedModule: inout SwiftParsedModuleSymbolTable + ) { + recordKnownType( + type, StructDeclSyntax( - name: .identifier("UnsafeMutableRawPointer"), + name: .identifier(type.typeName), memberBlock: .init(members: []) ), - parent: nil + knownTypeToNominal: &knownTypeToNominal, + nominalTypeDeclToKnownType: &nominalTypeDeclToKnownType, + parsedModule: &parsedModule ) + } + init(into parsedModule: inout SwiftParsedModuleSymbolTable) { + // Pointer types self.unsafePointerDecl = parsedModule.addNominalTypeDeclaration( StructDeclSyntax( name: .identifier("UnsafePointer"), @@ -127,82 +153,32 @@ struct SwiftStandardLibraryTypes { parent: nil ) - self.boolDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("Bool"), - memberBlock: .init(members: []) - ), - parent: nil - ) - self.intDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("Int"), - memberBlock: .init(members: []) - ), - parent: nil - ) - self.int8Decl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("Int8"), - memberBlock: .init(members: []) - ), - parent: nil - ) - self.int16Decl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("Int16"), - memberBlock: .init(members: []) - ), - parent: nil - ) - self.uint16Decl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("UInt16"), - memberBlock: .init(members: []) - ), - parent: nil - ) - self.int32Decl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("Int32"), - memberBlock: .init(members: []) - ), - parent: nil - ) - self.int64Decl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("Int64"), - memberBlock: .init(members: []) - ), - parent: nil - ) - self.floatDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("Float"), - memberBlock: .init(members: []) - ), - parent: nil - ) - self.doubleDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("Double"), - memberBlock: .init(members: []) - ), - parent: nil - ) - self.intDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("Int"), - memberBlock: .init(members: []) - ), - parent: nil - ) - self.stringDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("String"), - memberBlock: .init(members: []) - ), - parent: nil - ) + var knownTypeToNominal: [KnownStandardLibraryType: SwiftNominalTypeDeclaration] = [:] + var nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: KnownStandardLibraryType] = [:] + + // Handle all of the non-generic types at once. + for knownType in KnownStandardLibraryType.allCases { + guard !knownType.isGeneric else { + continue + } + + Self.recordKnownNonGenericStruct( + knownType, + knownTypeToNominal: &knownTypeToNominal, + nominalTypeDeclToKnownType: &nominalTypeDeclToKnownType, + parsedModule: &parsedModule + ) + } + + self.knownTypeToNominal = knownTypeToNominal + self.nominalTypeDeclToKnownType = nominalTypeDeclToKnownType + } + + subscript(knownType: KnownStandardLibraryType) -> SwiftNominalTypeDeclaration { + knownTypeToNominal[knownType]! + } + + subscript(nominalType: SwiftNominalTypeDeclaration) -> KnownStandardLibraryType? { + nominalTypeDeclToKnownType[nominalType] } } diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift index d83d5e6..8ee0b76 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift @@ -129,12 +129,12 @@ extension SwiftType { // Only recognize the "@convention(c)" and "@convention(swift)" attributes, and // then only on function types. // FIXME: This string matching is a horrible hack. - switch attributedType.trimmedDescription { + switch attributedType.attributes.trimmedDescription { case "@convention(c)", "@convention(swift)": let innerType = try SwiftType(attributedType.baseType, symbolTable: symbolTable) switch innerType { case .function(var functionType): - let isConventionC = attributedType.trimmedDescription == "@convention(c)" + let isConventionC = attributedType.attributes.trimmedDescription == "@convention(c)" let convention: SwiftFunctionType.Convention = isConventionC ? .c : .swift functionType.convention = convention self = .function(functionType) diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index b5304b7..6f7a5e8 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -25,6 +25,7 @@ func assertLoweredFunction( sourceFile: SourceFileSyntax? = nil, enclosingType: TypeSyntax? = nil, expectedCDecl: DeclSyntax, + expectedCFunction: String, fileID: String = #fileID, filePath: String = #filePath, line: Int = #line, @@ -41,12 +42,29 @@ func assertLoweredFunction( translator.prepareForTranslation() - let inputFunction = inputDecl.cast(FunctionDeclSyntax.self) - let loweredFunction = try translator.lowerFunctionSignature( - inputFunction, - enclosingType: enclosingType + let swiftFunctionName: String + let loweredFunction: LoweredFunctionSignature + if let inputFunction = inputDecl.as(FunctionDeclSyntax.self) { + loweredFunction = try translator.lowerFunctionSignature( + inputFunction, + enclosingType: enclosingType + ) + swiftFunctionName = inputFunction.name.text + } else if let inputInitializer = inputDecl.as(InitializerDeclSyntax.self) { + loweredFunction = try translator.lowerFunctionSignature( + inputInitializer, + enclosingType: enclosingType + ) + swiftFunctionName = "init" + } else { + fatalError("Unhandling declaration kind for lowering") + } + + let loweredCDecl = loweredFunction.cdeclThunk( + cName: "c_\(swiftFunctionName)", + swiftFunctionName: swiftFunctionName, + stdlibTypes: translator.swiftStdlibTypes ) - let loweredCDecl = loweredFunction.cdeclThunk(cName: "c_\(inputFunction.name.text)", inputFunction: inputFunction) #expect( loweredCDecl.description == expectedCDecl.description, @@ -57,4 +75,18 @@ func assertLoweredFunction( column: column ) ) + + let cFunction = translator.cdeclToCFunctionLowering( + loweredFunction.cdecl, + cName: "c_\(swiftFunctionName)" + ) + #expect( + cFunction.description == expectedCFunction, + sourceLocation: Testing.SourceLocation( + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + ) } diff --git a/Tests/JExtractSwiftTests/CTypeTests.swift b/Tests/JExtractSwiftTests/CTypeTests.swift new file mode 100644 index 0000000..569296d --- /dev/null +++ b/Tests/JExtractSwiftTests/CTypeTests.swift @@ -0,0 +1,106 @@ +//===----------------------------------------------------------------------===// +// +// 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 JExtractSwift +import Testing + +@Suite("C type system tests") +struct CTypeTests { + @Test("C function declaration printing") + func testFunctionDeclarationPrint() { + let malloc = CFunction( + resultType: .pointer(.void), + name: "malloc", + parameters: [ + CParameter(name: "size", type: .integral(.size_t)) + ], + isVariadic: false + ) + #expect(malloc.description == "void *malloc(size_t size)") + + let free = CFunction( + resultType: .void, + name: "free", + parameters: [ + CParameter(name: "ptr", type: .pointer(.void)) + ], + isVariadic: false + ) + #expect(free.description == "void free(void *ptr)") + + let snprintf = CFunction( + resultType: .integral(.signed(bits: 32)), + name: "snprintf", + parameters: [ + CParameter(name: "str", type: .pointer(.integral(.signed(bits: 8)))), + CParameter(name: "size", type: .integral(.size_t)), + CParameter( + name: "format", + type: .pointer( + .qualified( + const: true, + volatile: false, + type: .integral(.signed(bits: 8)) + ) + ) + ) + ], + isVariadic: true + ) + #expect(snprintf.description == "int32_t snprintf(int8_t *str, size_t size, const int8_t *format, ...)") + #expect(snprintf.functionType.description == "int32_t (int8_t *, size_t, const int8_t *, ...)") + + let rand = CFunction( + resultType: .integral(.signed(bits: 32)), + name: "rand", + parameters: [], + isVariadic: false + ) + #expect(rand.description == "int32_t rand(void)") + #expect(rand.functionType.description == "int32_t (void)") + } + + @Test("C pointer declarator printing") + func testPointerDeclaratorPrinting() { + let doit = CFunction( + resultType: .void, + name: "doit", + parameters: [ + .init( + name: "body", + type: .pointer( + .function( + resultType: .void, + parameters: [.integral(.bool)], + variadic: false + ) + ) + ) + ], + isVariadic: false + ) + #expect(doit.description == "void doit(void (*body)(_Bool))") + + let ptrptr = CType.pointer( + .qualified( + const: true, + volatile: false, + type: .pointer( + .qualified(const: false, volatile: true, type: .void) + ) + ) + ) + #expect(ptrptr.description == "volatile void *const *") + } +} diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index 0818791..97fa95f 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -26,10 +26,11 @@ final class FunctionLoweringTests { """, expectedCDecl: """ @_cdecl("c_f") - func c_f(_ x: Int, _ y: Float, _ z_pointer: UnsafeRawPointer, _ z_count: Int) { + public func c_f(_ x: Int, _ y: Float, _ z_pointer: UnsafeRawPointer, _ z_count: Int) { f(x: x, y: y, z: UnsafeBufferPointer(start: z_pointer.assumingMemoryBound(to: Bool.self), count: z_count)) } - """ + """, + expectedCFunction: "void c_f(ptrdiff_t x, float y, const void *z_pointer, ptrdiff_t z_count)" ) } @@ -40,10 +41,11 @@ final class FunctionLoweringTests { """, expectedCDecl: """ @_cdecl("c_f") - func c_f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z_pointer: UnsafeRawPointer) -> Int { + public func c_f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z_pointer: UnsafeRawPointer) -> Int { return f(t: (t_0, (t_1_0, t_1_1)), z: z_pointer.assumingMemoryBound(to: Int.self)) } - """ + """, + expectedCFunction: "ptrdiff_t c_f(ptrdiff_t t_0, float t_1_0, double t_1_1, const void *z_pointer)" ) } @@ -57,12 +59,14 @@ final class FunctionLoweringTests { """, expectedCDecl: """ @_cdecl("c_shift") - func c_shift(_ point: UnsafeMutableRawPointer, _ delta_0: Double, _ delta_1: Double) { + public func c_shift(_ point: UnsafeMutableRawPointer, _ delta_0: Double, _ delta_1: Double) { shift(point: &point.assumingMemoryBound(to: Point.self).pointee, by: (delta_0, delta_1)) } - """ + """, + expectedCFunction: "void c_shift(void *point, double delta_0, double delta_1)" ) } + @Test("Lowering methods") func loweringMethods() throws { try assertLoweredFunction(""" @@ -74,10 +78,11 @@ final class FunctionLoweringTests { enclosingType: "Point", expectedCDecl: """ @_cdecl("c_shifted") - func c_shifted(_ self: UnsafeRawPointer, _ delta_0: Double, _ delta_1: Double, _ _result: UnsafeMutableRawPointer) { - _result.assumingMemoryBound(to: Point.self).pointee = self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1)) + public func c_shifted(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer, _ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Point.self).initialize(to: self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1))) } - """ + """, + expectedCFunction: "void c_shifted(double delta_0, double delta_1, const void *self, void *_result)" ) } @@ -92,10 +97,11 @@ final class FunctionLoweringTests { enclosingType: "Point", expectedCDecl: """ @_cdecl("c_shift") - func c_shift(_ self: UnsafeMutableRawPointer, _ delta_0: Double, _ delta_1: Double) { + public func c_shift(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeMutableRawPointer) { self.assumingMemoryBound(to: Point.self).pointee.shift(by: (delta_0, delta_1)) } - """ + """, + expectedCFunction: "void c_shift(double delta_0, double delta_1, void *self)" ) } @@ -110,10 +116,81 @@ final class FunctionLoweringTests { enclosingType: "Point", expectedCDecl: """ @_cdecl("c_shift") - func c_shift(_ self: UnsafeRawPointer, _ delta_0: Double, _ delta_1: Double) { + public func c_shift(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer) { unsafeBitCast(self, to: Point.self).shift(by: (delta_0, delta_1)) } - """ + """, + expectedCFunction: "void c_shift(double delta_0, double delta_1, const void *self)" + ) + } + + @Test("Lowering static methods") + func loweringStaticMethods() throws { + try assertLoweredFunction(""" + static func scaledUnit(by value: Double) -> Point { } + """, + sourceFile: """ + struct Point { } + """, + enclosingType: "Point", + expectedCDecl: """ + @_cdecl("c_scaledUnit") + public func c_scaledUnit(_ value: Double, _ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Point.self).initialize(to: Point.scaledUnit(by: value)) + } + """, + expectedCFunction: "void c_scaledUnit(double value, void *_result)" + ) + + try assertLoweredFunction(""" + static func randomPerson(seed: Double) -> Person { } + """, + sourceFile: """ + class Person { } + """, + enclosingType: "Person", + expectedCDecl: """ + @_cdecl("c_randomPerson") + public func c_randomPerson(_ seed: Double) -> UnsafeRawPointer { + return unsafeBitCast(Person.randomPerson(seed: seed), to: UnsafeRawPointer.self) + } + """, + expectedCFunction: "const void *c_randomPerson(double seed)" + ) + } + + @Test("Lowering initializers") + func loweringInitializers() throws { + try assertLoweredFunction(""" + init(scaledBy value: Double) { } + """, + sourceFile: """ + struct Point { } + """, + enclosingType: "Point", + expectedCDecl: """ + @_cdecl("c_init") + public func c_init(_ value: Double, _ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Point.self).initialize(to: Point(scaledBy: value)) + } + """, + expectedCFunction: "void c_init(double value, void *_result)" + ) + + try assertLoweredFunction(""" + init(seed: Double) { } + """, + sourceFile: """ + class Person { } + """, + enclosingType: "Person", + expectedCDecl: """ + @_cdecl("c_init") + public func c_init(_ seed: Double) -> UnsafeRawPointer { + return unsafeBitCast(Person(seed: seed), to: UnsafeRawPointer.self) + } + """, + expectedCFunction: "const void *c_init(double seed)" ) } @@ -124,11 +201,116 @@ final class FunctionLoweringTests { """, expectedCDecl: """ @_cdecl("c_f") - func c_f(_ t: UnsafeRawPointer) { + public func c_f(_ t: UnsafeRawPointer) { f(t: unsafeBitCast(t, to: Int.self)) } - """ - ) + """, + expectedCFunction: "void c_f(const void *t)" + ) + + try assertLoweredFunction(""" + func f() -> Int.Type { } + """, + expectedCDecl: """ + @_cdecl("c_f") + public func c_f() -> UnsafeRawPointer { + return unsafeBitCast(f(), to: UnsafeRawPointer.self) + } + """, + expectedCFunction: "const void *c_f(void)" + ) } -} + @Test("Lowering class returns") + func lowerClassReturns() throws { + try assertLoweredFunction(""" + func shifted(by delta: (Double, Double)) -> Point { } + """, + sourceFile: """ + class Point { } + """, + enclosingType: "Point", + expectedCDecl: """ + @_cdecl("c_shifted") + public func c_shifted(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer) -> UnsafeRawPointer { + return unsafeBitCast(unsafeBitCast(self, to: Point.self).shifted(by: (delta_0, delta_1)), to: UnsafeRawPointer.self) + } + """, + expectedCFunction: "const void *c_shifted(double delta_0, double delta_1, const void *self)" + ) + } + + @Test("Lowering pointer returns") + func lowerPointerReturns() throws { + try assertLoweredFunction(""" + func getPointer() -> UnsafePointer { } + """, + sourceFile: """ + struct Point { } + """, + expectedCDecl: """ + @_cdecl("c_getPointer") + public func c_getPointer() -> UnsafeRawPointer { + return UnsafeRawPointer(getPointer()) + } + """, + expectedCFunction: "const void *c_getPointer(void)" + ) + } + + @Test("Lowering tuple returns") + func lowerTupleReturns() throws { + try assertLoweredFunction(""" + func getTuple() -> (Int, (Float, Point)) { } + """, + sourceFile: """ + struct Point { } + """, + expectedCDecl: """ + @_cdecl("c_getTuple") + public func c_getTuple(_ _result_0: UnsafeMutableRawPointer, _ _result_1_0: UnsafeMutableRawPointer, _ _result_1_1: UnsafeMutableRawPointer) { + let __swift_result = getTuple() + _result_0 = __swift_result_0 + _result_1_0 = __swift_result_1_0 + _result_1_1.assumingMemoryBound(to: Point.self).initialize(to: __swift_result_1_1) + } + """, + expectedCFunction: "void c_getTuple(void *_result_0, void *_result_1_0, void *_result_1_1)" + ) + } + + @Test("Lowering buffer pointer returns", .disabled("Doesn't turn into the indirect returns")) + func lowerBufferPointerReturns() throws { + try assertLoweredFunction(""" + func getBufferPointer() -> UnsafeMutableBufferPointer { } + """, + sourceFile: """ + struct Point { } + """, + expectedCDecl: """ + @_cdecl("c_getBufferPointer") + public func c_getBufferPointer(_result_pointer: UnsafeMutableRawPointer, _result_count: UnsafeMutableRawPointer) { + return UnsafeRawPointer(getPointer()) + } + """, + expectedCFunction: "c_getBufferPointer(void* _result_pointer, void* _result_count)" + ) + } + + @Test("Lowering C function types") + func lowerFunctionTypes() throws { + // FIXME: C pretty printing isn't handling parameters of function pointer + // type yet. + try assertLoweredFunction(""" + func doSomething(body: @convention(c) (Int32) -> Double) { } + """, + expectedCDecl: """ + @_cdecl("c_doSomething") + public func c_doSomething(_ body: @convention(c) (Int32) -> Double) { + doSomething(body: body) + } + """, + expectedCFunction: "void c_doSomething(double (*body)(int32_t))" + ) + } +}