diff --git a/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift b/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift deleted file mode 100644 index 0099ae5c..00000000 --- a/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift +++ /dev/null @@ -1,195 +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 -// -//===----------------------------------------------------------------------===// - -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 index 15e4ebb8..e1a69d12 100644 --- a/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift @@ -24,10 +24,22 @@ extension CType { init(cdeclType: SwiftType) throws { switch cdeclType { case .nominal(let nominalType): - if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType, - let primitiveCType = knownType.primitiveCType { - self = primitiveCType - return + if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { + if let primitiveCType = knownType.primitiveCType { + self = primitiveCType + return + } + + switch knownType { + case .unsafePointer where nominalType.genericArguments?.count == 1: + self = .pointer(.qualified(const: true, volatile: false, type: try CType(cdeclType: nominalType.genericArguments![0]))) + return + case .unsafeMutablePointer where nominalType.genericArguments?.count == 1: + self = .pointer(try CType(cdeclType: nominalType.genericArguments![0])) + return + default: + break + } } throw CDeclToCLoweringError.invalidNominalType(nominalType.nominalTypeDecl) @@ -109,7 +121,8 @@ extension KnownStandardLibraryType { case .unsafeRawPointer: .pointer( .qualified(const: true, volatile: false, type: .void) ) - case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer: + case .void: .void + case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string: nil } } diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift index d65948ac..9dbcb510 100644 --- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift +++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift @@ -29,8 +29,7 @@ extension Swift2JavaTranslator { enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, symbolTable: symbolTable ) - - return try lowerFunctionSignature(signature) + return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: .function) } /// Lower the given initializer to a C-compatible entrypoint, @@ -47,16 +46,57 @@ extension Swift2JavaTranslator { symbolTable: symbolTable ) - return try lowerFunctionSignature(signature) + return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: .initializer) + } + + /// Lower the given variable decl 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: VariableDeclSyntax, + isSet: Bool, + enclosingType: TypeSyntax? = nil + ) throws -> LoweredFunctionSignature? { + let supportedAccessors = decl.supportedAccessorKinds(binding: decl.bindings.first!) + guard supportedAccessors.contains(isSet ? .set : .get) else { + return nil + } + + let signature = try SwiftFunctionSignature( + decl, + isSet: isSet, + enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, + symbolTable: symbolTable + ) + return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: isSet ? .setter : .getter) } +} + +/// Responsible for lowering Swift API to C API. +struct CdeclLowering { + var swiftStdlibTypes: SwiftStandardLibraryTypes /// Lower the given Swift function signature to a Swift @_cdecl function signature, /// which is C compatible, and the corresponding Java method signature. /// /// Throws an error if this function cannot be lowered for any reason. func lowerFunctionSignature( - _ signature: SwiftFunctionSignature + _ signature: SwiftFunctionSignature, + apiKind: SwiftAPIKind ) throws -> LoweredFunctionSignature { + // Lower the self parameter. + let loweredSelf: LoweredParameter? = switch signature.selfParameter { + case .instance(let selfParameter): + try lowerParameter( + selfParameter.type, + convention: selfParameter.convention, + parameterName: selfParameter.parameterName ?? "self" + ) + case nil, .initializer(_), .staticMethod(_): + nil + } + // Lower all of the parameters. let loweredParameters = try signature.parameters.enumerated().map { (index, param) in try lowerParameter( @@ -67,116 +107,46 @@ extension Swift2JavaTranslator { } // 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 - ) + let loweredResult = try lowerResult(signature.result.type) return LoweredFunctionSignature( original: signature, - cdecl: cdeclSignature, - parameters: allLoweredParameters, + apiKind: apiKind, + selfParameter: loweredSelf, + parameters: loweredParameters, result: loweredResult ) } + /// Lower a Swift function parameter type to cdecl parameters. + /// + /// For example, Swift parameter `arg value: inout Int` can be lowered with + /// `lowerParameter(intTy, .inout, "value")`. + /// + /// - Parameters: + /// - type: The parameter type. + /// - convention: the parameter convention, e.g. `inout`. + /// - parameterName: The name of the parameter. + /// func lowerParameter( _ type: SwiftType, convention: SwiftParameterConvention, parameterName: String - ) throws -> LoweredParameters { + ) throws -> LoweredParameter { // 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 - ) - ] - ) + // return it. + if let _ = try? CType(cdeclType: type) { + if convention != .inout { + return LoweredParameter( + cdeclParameters: [SwiftParameter(convention: .byValue, parameterName: parameterName, type: type)], + conversion: .placeholder + ) + } } switch type { - case .function, .optional: - throw LoweringError.unhandledType(type) - - case .metatype: - return LoweredParameters( + case .metatype(let instanceType): + return LoweredParameter( cdeclParameters: [ SwiftParameter( convention: .byValue, @@ -185,98 +155,271 @@ extension Swift2JavaTranslator { SwiftNominalType( nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer] ) - ), - canBeDirectReturn: true + ) ) - ] + ], + conversion: .unsafeCastPointer(.placeholder, swiftType: instanceType) ) 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 { + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if convention == .inout { + // FIXME: Support non-trivial 'inout' for builtin types. + throw LoweringError.inoutNotSupported(type) + } + switch knownType { + case .unsafePointer, .unsafeMutablePointer: + guard let genericArgs = type.asNominalType?.genericArguments, genericArgs.count == 1 else { + throw LoweringError.unhandledType(type) + } + // Typed pointers are mapped down to their raw forms in cdecl entry + // points. These can be passed through directly. let isMutable = knownType == .unsafeMutablePointer - let cdeclPointerType = isMutable - ? swiftStdlibTypes[.unsafeMutableRawPointer] - : swiftStdlibTypes[.unsafeRawPointer] - return LoweredParameters( - cdeclParameters: [ - SwiftParameter( - convention: convention, - parameterName: parameterName + "_pointer", - type: SwiftType.nominal( - SwiftNominalType(nominalTypeDecl: cdeclPointerType) - ), - canBeDirectReturn: true - ) - ] + let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer] + let paramType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal)) + return LoweredParameter( + cdeclParameters: [SwiftParameter(convention: .byValue, parameterName: parameterName, type: paramType)], + conversion: .typedPointer(.placeholder, swiftType: genericArgs[0]) ) - } - // Typed buffer pointers are mapped down to a (pointer, count) pair - // so those parts can be passed through directly. - if knownType == .unsafeBufferPointer || knownType == .unsafeMutableBufferPointer { + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + guard let genericArgs = type.asNominalType?.genericArguments, genericArgs.count == 1 else { + throw LoweringError.unhandledType(type) + } + // Typed pointers are lowered to (raw-pointer, count) pair. let isMutable = knownType == .unsafeMutableBufferPointer - let cdeclPointerType = isMutable - ? swiftStdlibTypes[.unsafeMutableRawPointer] - : swiftStdlibTypes[.unsafeRawPointer] - return LoweredParameters( + let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer] + + return LoweredParameter( cdeclParameters: [ SwiftParameter( - convention: convention, - parameterName: parameterName + "_pointer", - type: SwiftType.nominal( - SwiftNominalType(nominalTypeDecl: cdeclPointerType) - ) + convention: .byValue, parameterName: "\(parameterName)_pointer", + type: .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal)) ), SwiftParameter( - convention: convention, - parameterName: parameterName + "_count", - type: SwiftType.nominal( - SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int]) + convention: .byValue, parameterName: "\(parameterName)_count", + type: .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int])) + ), + ], conversion: .initialize( + type, + arguments: [ + LabeledArgument( + label: "start", + argument: .typedPointer(.explodedComponent(.placeholder, component: "pointer"), swiftType: genericArgs[0]) + ), + LabeledArgument( + label: "count", + argument: .explodedComponent(.placeholder, component: "count") ) - ) - ] + ] + ) ) + + case .string: + // 'String' is passed in by C string. i.e. 'UnsafePointer' ('const uint8_t *') + if knownType == .string { + return LoweredParameter( + cdeclParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: parameterName, + type: .nominal(SwiftNominalType( + nominalTypeDecl: swiftStdlibTypes.unsafePointerDecl, + genericArguments: [ + .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int8])) + ] + )) + ) + ], + conversion: .initialize(type, arguments: [ + LabeledArgument(label: "cString", argument: .placeholder) + ]) + ) + } + + default: + // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. + throw LoweringError.unhandledType(type) } } - // Arbitrary 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 + // Arbitrary nominal types are passed-in as an pointer. + let isMutable = (convention == .inout) + let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer] + let parameterType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal)) + + return LoweredParameter( + cdeclParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: parameterName, + type: parameterType + ), + ], + conversion: .pointee(.typedPointer(.placeholder, swiftType: type)) + ) + + case .tuple(let tuple): + if tuple.count == 1 { + return try lowerParameter(tuple[0], convention: convention, parameterName: parameterName) + } + if convention == .inout { + throw LoweringError.inoutNotSupported(type) + } + var parameters: [SwiftParameter] = [] + var conversions: [ConversionStep] = [] + for (idx, element) in tuple.enumerated() { + // FIXME: Use tuple element label. + let cdeclName = "\(parameterName)_\(idx)" + let lowered = try lowerParameter(element, convention: convention, parameterName: cdeclName) + + parameters.append(contentsOf: lowered.cdeclParameters) + conversions.append(lowered.conversion) } + return LoweredParameter(cdeclParameters: parameters, conversion: .tuplify(conversions)) - let isMutable = (convention == .inout) - return LoweredParameters( + case .function(let fn) where fn.parameters.isEmpty && fn.resultType.isVoid: + return LoweredParameter( cdeclParameters: [ SwiftParameter( convention: .byValue, parameterName: parameterName, - type: .nominal( - SwiftNominalType( - nominalTypeDecl: isMutable - ? swiftStdlibTypes[.unsafeMutableRawPointer] - : swiftStdlibTypes[.unsafeRawPointer] - ) - ), - canBeDirectReturn: canBeDirectReturn + type: .function(SwiftFunctionType(convention: .c, parameters: [], resultType: fn.resultType)) ) - ] + ], + // '@convention(c) () -> ()' is compatible with '() -> Void'. + conversion: .placeholder + ) + + case .function, .optional: + // FIXME: Support other function types than '() -> Void'. + throw LoweringError.unhandledType(type) + } + } + + /// Lower a Swift result type to cdecl parameters and return type. + /// + /// - Parameters: + /// - type: The return type. + /// - outParameterName: If the type is lowered to a indirect return, this parameter name should be used. + func lowerResult( + _ type: SwiftType, + outParameterName: String = "_result" + ) throws -> LoweredResult { + // If there is a 1:1 mapping between this Swift type and a C type, we just + // return it. + if let cType = try? CType(cdeclType: type) { + _ = cType + return LoweredResult(cdeclResultType: type, cdeclOutParameters: [], conversion: .placeholder); + } + + switch type { + case .metatype: + // 'unsafeBitcast(, to: UnsafeRawPointer.self)' as 'UnsafeRawPointer' + + let resultType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer])) + return LoweredResult( + cdeclResultType: resultType, + cdeclOutParameters: [], + conversion: .unsafeCastPointer(.placeholder, swiftType: resultType) + ) + + case .nominal(let nominal): + // Types from the Swift standard library that we know about. + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + switch knownType { + case .unsafePointer, .unsafeMutablePointer: + // Typed pointers are lowered to corresponding raw forms. + let isMutable = knownType == .unsafeMutablePointer + let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer] + let resultType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal)) + return LoweredResult( + cdeclResultType: resultType, + cdeclOutParameters: [], + conversion: .initialize(resultType, arguments: [LabeledArgument(argument: .placeholder)]) + ) + + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + // Typed pointers are lowered to (raw-pointer, count) pair. + let isMutable = knownType == .unsafeMutableBufferPointer + let rawPointerType = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer] + return try lowerResult( + .tuple([ + .nominal(SwiftNominalType(nominalTypeDecl: rawPointerType)), + .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int])) + ]), + outParameterName: outParameterName + ) + + case .void: + return LoweredResult(cdeclResultType: .void, cdeclOutParameters: [], conversion: .placeholder) + + case .string: + // Returning string is not supported at this point. + throw LoweringError.unhandledType(type) + + default: + // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. + throw LoweringError.unhandledType(type) + } + } + + // Arbitrary nominal types are indirectly returned. + return LoweredResult( + cdeclResultType: .void, + cdeclOutParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: outParameterName, + type: .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.unsafeMutableRawPointer])) + ) + ], + conversion: .populatePointer(name: outParameterName, assumingType: type, to: .placeholder) ) case .tuple(let tuple): - 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) + if tuple.count == 1 { + return try lowerResult(tuple[0], outParameterName: outParameterName) + } + + var parameters: [SwiftParameter] = [] + var conversions: [ConversionStep] = [] + for (idx, element) in tuple.enumerated() { + let outName = "\(outParameterName)_\(idx)" + let lowered = try lowerResult(element, outParameterName: outName) + + // Convert direct return values to typed mutable pointers. + // E.g. (Int8, Int8) is lowered to '_ result_0: UnsafePointer, _ result_1: UnsafePointer' + if !lowered.cdeclResultType.isVoid { + let parameterName = lowered.cdeclOutParameters.isEmpty ? outName : "\(outName)_return" + let parameter = SwiftParameter( + convention: .byValue, + parameterName: parameterName, + type: .nominal(SwiftNominalType( + nominalTypeDecl: swiftStdlibTypes.unsafeMutablePointerDecl, + genericArguments: [lowered.cdeclResultType] + )) + ) + parameters.append(parameter) + conversions.append(.populatePointer( + name: parameterName, + to: lowered.conversion + )) + } else { + // If the element returns void, it should already be a no-result conversion. + parameters.append(contentsOf: lowered.cdeclOutParameters) + conversions.append(lowered.conversion) + } } - return LoweredParameters( - cdeclParameters: loweredElements.flatMap { $0.cdeclParameters } + + return LoweredResult( + cdeclResultType: .void, + cdeclOutParameters: parameters, + conversion: .tupleExplode(conversions, name: outParameterName) ) + + case .function(_), .optional(_): + throw LoweringError.unhandledType(type) } } @@ -293,243 +436,201 @@ extension Swift2JavaTranslator { } } -struct LabeledArgument { - var label: String? - var argument: Element +package enum SwiftAPIKind { + case function + case initializer + case getter + case setter } -extension LabeledArgument: Equatable where Element: Equatable { } +/// Represent a Swift parameter in the cdecl thunk. +struct LoweredParameter: Equatable { + /// Lowered parameters in cdecl thunk. + /// One Swift parameter can be lowered to multiple parameters. + /// E.g. 'Data' as (baseAddress, length) pair. + var cdeclParameters: [SwiftParameter] + /// Conversion to convert the cdecl thunk parameters to the original Swift argument. + var conversion: ConversionStep -struct LoweredParameters: Equatable { - /// The lowering of the parameters at the C level in Swift. - var cdeclParameters: [SwiftParameter] + init(cdeclParameters: [SwiftParameter], conversion: ConversionStep) { + self.cdeclParameters = cdeclParameters + self.conversion = conversion + + assert(cdeclParameters.count == conversion.placeholderCount) + } } -enum LoweringError: Error { - case inoutNotSupported(SwiftType) - case unhandledType(SwiftType) +struct LoweredResult: Equatable { + /// The return type of the cdecl thunk. + var cdeclResultType: SwiftType + + /// Out parameters for populating the returning values. + /// + /// Currently, if this is not empty, `cdeclResultType` is `Void`. But the thunk + /// may utilize both in the future, for example returning the status while + /// populating the out parameter. + var cdeclOutParameters: [SwiftParameter] + + /// The conversion from the Swift result to cdecl result. + var conversion: ConversionStep +} + +extension LoweredResult { + /// Whether the result is returned by populating the passed-in pointer parameters. + var hasIndirectResult: Bool { + !cdeclOutParameters.isEmpty + } } @_spi(Testing) public struct LoweredFunctionSignature: Equatable { var original: SwiftFunctionSignature - public var cdecl: SwiftFunctionSignature - var parameters: [LoweredParameters] - var result: LoweredParameters + var apiKind: SwiftAPIKind + + var selfParameter: LoweredParameter? + var parameters: [LoweredParameter] + var result: LoweredResult + + var allLoweredParameters: [SwiftParameter] { + var all: [SwiftParameter] = [] + // Original parameters. + for loweredParam in parameters { + all += loweredParam.cdeclParameters + } + // Self. + if let selfParameter = self.selfParameter { + all += selfParameter.cdeclParameters + } + // Out parameters. + all += result.cdeclOutParameters + return all + } + + var cdeclSignature: SwiftFunctionSignature { + SwiftFunctionSignature( + selfParameter: nil, + parameters: allLoweredParameters, + result: SwiftResult(convention: .direct, type: result.cdeclResultType) + ) + } } extension LoweredFunctionSignature { /// Produce the `@_cdecl` thunk for this lowered function signature that will /// call into the original function. - @_spi(Testing) public func cdeclThunk( cName: String, - swiftFunctionName: String, + swiftAPIName: 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)) + let cdeclParams = allLoweredParameters.map(\.description).joined(separator: ", ") + let returnClause = !result.cdeclResultType.isVoid ? " -> \(result.cdeclResultType.description)" : "" - // Make it public. - loweredCDecl.modifiers.append( - DeclModifierSyntax(name: .keyword(.public), trailingTrivia: .space) + var loweredCDecl = try! FunctionDeclSyntax( + """ + @_cdecl(\(literal: cName)) + public func \(raw: cName)(\(raw: cdeclParams))\(raw: returnClause) { + } + """ ) - // 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[...] - } + var bodyItems: [CodeBlockItemSyntax] = [] - // 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)" + let selfExpr: ExprSyntax? + switch original.selfParameter { + case .instance(let swiftSelf): + // Raise the 'self' from cdecl parameters. + selfExpr = self.selfParameter!.conversion.asExprSyntax( + placeholder: swiftSelf.parameterName ?? "self", + bodyItems: &bodyItems ) + case .staticMethod(let selfType), .initializer(let selfType): + selfExpr = "\(raw: selfType.description)" + case .none: + selfExpr = nil + } - if let argumentLabel = originalParam.argumentLabel { - return "\(argumentLabel): \(cdeclToOriginalArg.description)" - } else { - return cdeclToOriginalArg.description - } + /// Raise the other parameters. + let paramExprs = parameters.enumerated().map { idx, param in + param.conversion.asExprSyntax( + placeholder: original.parameters[idx].parameterName ?? "_\(idx)", + bodyItems: &bodyItems + )! } - // 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)" + // Build callee expression. + let callee: ExprSyntax = if let selfExpr { + if case .initializer = self.apiKind { + // Don't bother to create explicit ${Self}.init expression. + selfExpr + } else { + ExprSyntax(MemberAccessExprSyntax(base: selfExpr, name: .identifier(swiftAPIName))) + } } else { - callExpression = "\(raw: swiftFunctionName)\(callArguments)" + ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(swiftAPIName))) } - // Handle the return. - if cdecl.result.type.isVoid && original.result.type.isVoid { - // Nothing to return. - loweredCDecl.body = """ - { - \(callExpression) + // Build the result. + let resultExpr: ExprSyntax + switch apiKind { + case .function, .initializer: + let arguments = paramExprs.enumerated() + .map { (i, argument) -> String in + let argExpr = original.parameters[i].convention == .inout ? "&\(argument)" : argument + return LabeledExprSyntax(label: original.parameters[i].argumentLabel, expression: argExpr).description } - """ - } 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] = [] + .joined(separator: ", ") + resultExpr = "\(callee)(\(raw: arguments))" - // 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 - } + case .getter: + assert(paramExprs.isEmpty) + resultExpr = callee - 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 - ) + case .setter: + assert(paramExprs.count == 1) + resultExpr = "\(callee) = \(paramExprs[0])" + } - // 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 - ) + // Lower the result. + if !original.result.type.isVoid { + let loweredResult: ExprSyntax? = result.conversion.asExprSyntax( + placeholder: resultExpr.description, + bodyItems: &bodyItems + ) - bodyItems.append(""" - return \(convertedResult) - """ - ) + if let loweredResult { + bodyItems.append(!result.cdeclResultType.isVoid ? "return \(loweredResult)" : "\(loweredResult)") } - - loweredCDecl.body = CodeBlockSyntax( - leftBrace: .leftBraceToken(trailingTrivia: .newline), - statements: .init(bodyItems.map { $0.with(\.trailingTrivia, .newline) }) - ) + } else { + bodyItems.append("\(resultExpr)") } - 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)" - ) + loweredCDecl.body!.statements = CodeBlockItemListSyntax { + bodyItems.map { + $0.with(\.leadingTrivia, [.newlines(1), .spaces(2)]) } } - // Look through "pass indirectly" steps; they do nothing here. - if case .passIndirectly(let conversionStep) = self { - return conversionStep.initialize( - placeholder: placeholder, - from: otherStep, - otherPlaceholder: otherPlaceholder - ) - } + return loweredCDecl + } - // The value we're initializing from. - let otherExpr = otherStep.asExprSyntax( - isSelf: false, - placeholder: otherPlaceholder + @_spi(Testing) + public func cFunctionDecl(cName: String) throws -> CFunction { + return CFunction( + resultType: try CType(cdeclType: self.result.cdeclResultType), + name: cName, + parameters: try self.allLoweredParameters.map { + try CParameter(name: $0.parameterName, type: CType(cdeclType: $0.type).parameterDecay) + }, + isVariadic: false ) - - // 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)" ] } } + +enum LoweringError: Error { + case inoutNotSupported(SwiftType) + case unhandledType(SwiftType) +} diff --git a/Sources/JExtractSwift/ConversionStep.swift b/Sources/JExtractSwift/ConversionStep.swift index 31b33a86..31d731bc 100644 --- a/Sources/JExtractSwift/ConversionStep.swift +++ b/Sources/JExtractSwift/ConversionStep.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import SwiftSyntax +import SwiftSyntaxBuilder /// Describes the transformation needed to take the parameters of a thunk /// and map them to the corresponding parameter (or result value) of the @@ -37,9 +38,6 @@ enum ConversionStep: Equatable { /// 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]) @@ -51,50 +49,25 @@ enum ConversionStep: Equatable { /// 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 - ), - ] - ) - } + /// Initialize mutable raw pointer with a typed value. + indirect case populatePointer(name: String, assumingType: SwiftType? = nil, to: ConversionStep) + + /// Perform multiple conversions, but discard the result. + case tupleExplode([ConversionStep], name: String?) /// 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), + .pointee(let inner), .typedPointer(let inner, swiftType: _), - .unsafeCastPointer(let inner, swiftType: _): + .unsafeCastPointer(let inner, swiftType: _), + .populatePointer(name: _, assumingType: _, to: let inner): inner.placeholderCount case .initialize(_, arguments: let arguments): arguments.reduce(0) { $0 + $1.argument.placeholderCount } - case .placeholder: + case .placeholder, .tupleExplode: 1 case .tuplify(let elements): elements.reduce(0) { $0 + $1.placeholderCount } @@ -103,38 +76,30 @@ enum ConversionStep: Equatable { /// 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 { + func asExprSyntax(placeholder: String, bodyItems: inout [CodeBlockItemSyntax]) -> ExprSyntax? { switch self { case .placeholder: return "\(raw: placeholder)" case .explodedComponent(let step, component: let component): - return step.asExprSyntax(isSelf: false, placeholder: "\(placeholder)_\(component)") + return step.asExprSyntax(placeholder: "\(placeholder)_\(component)", bodyItems: &bodyItems) case .unsafeCastPointer(let step, swiftType: let swiftType): - let untypedExpr = step.asExprSyntax(isSelf: false, placeholder: placeholder) + let untypedExpr = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) return "unsafeBitCast(\(untypedExpr), to: \(swiftType.metatypeReferenceExprSyntax))" case .typedPointer(let step, swiftType: let type): - let untypedExpr = step.asExprSyntax(isSelf: isSelf, placeholder: placeholder) + let untypedExpr = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) return "\(untypedExpr).assumingMemoryBound(to: \(type.metatypeReferenceExprSyntax))" case .pointee(let step): - let untypedExpr = step.asExprSyntax(isSelf: isSelf, placeholder: placeholder) + let untypedExpr = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) 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 - } + let argExpr = labeledArgument.argument.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) + return LabeledExprSyntax(label: labeledArgument.label, expression: argExpr!).description } // FIXME: Should be able to use structured initializers here instead @@ -144,13 +109,44 @@ enum ConversionStep: Equatable { case .tuplify(let elements): let renderedElements: [String] = elements.enumerated().map { (index, element) in - element.asExprSyntax(isSelf: false, placeholder: "\(placeholder)_\(index)").description + element.asExprSyntax(placeholder: "\(placeholder)_\(index)", bodyItems: &bodyItems)!.description } // FIXME: Should be able to use structured initializers here instead // of splatting out text. let renderedElementList = renderedElements.joined(separator: ", ") return "(\(raw: renderedElementList))" + + case .populatePointer(name: let pointer, assumingType: let type, to: let step): + let inner = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) + let casting = if let type { + ".assumingMemoryBound(to: \(type.metatypeReferenceExprSyntax))" + } else { + "" + } + return "\(raw: pointer)\(raw: casting).initialize(to: \(inner))" + + case .tupleExplode(let steps, let name): + let toExplode: String + if let name { + bodyItems.append("let \(raw: name) = \(raw: placeholder)") + toExplode = name + } else { + toExplode = placeholder + } + for (i, step) in steps.enumerated() { + if let result = step.asExprSyntax(placeholder: "\(toExplode).\(i)", bodyItems: &bodyItems) { + bodyItems.append(CodeBlockItemSyntax(item: .expr(result))) + } + } + return nil } } } + +struct LabeledArgument { + var label: String? + var argument: Element +} + +extension LabeledArgument: Equatable where Element: Equatable { } diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift index dc8aadb1..db427767 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift @@ -17,11 +17,16 @@ import SwiftSyntaxBuilder /// Provides a complete signature for a Swift function, which includes its /// parameters and return type. -@_spi(Testing) public struct SwiftFunctionSignature: Equatable { var selfParameter: SwiftSelfParameter? var parameters: [SwiftParameter] var result: SwiftResult + + init(selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], result: SwiftResult) { + self.selfParameter = selfParameter + self.parameters = parameters + self.result = result + } } /// Describes the "self" parameter of a Swift function signature. @@ -38,34 +43,17 @@ enum SwiftSelfParameter: Equatable { case initializer(SwiftType) } -extension SwiftFunctionSignature { - /// Create a function declaration with the given name that has this - /// signature. - package func createFunctionDecl(_ name: String) -> FunctionDeclSyntax { - let parametersStr = parameters.map(\.description).joined(separator: ", ") - - let resultWithArrow: String - if result.type.isVoid { - resultWithArrow = "" - } else { - resultWithArrow = " -> \(result.type.description)" - } - - let decl: DeclSyntax = """ - func \(raw: name)(\(raw: parametersStr))\(raw: resultWithArrow) { - // implementation - } - """ - return decl.cast(FunctionDeclSyntax.self) - } -} - extension SwiftFunctionSignature { init( _ node: InitializerDeclSyntax, enclosingType: SwiftType?, symbolTable: SwiftSymbolTable ) throws { + // Prohibit generics for now. + if let generics = node.genericParameterClause { + throw SwiftFunctionTranslationError.generic(generics) + } + guard let enclosingType else { throw SwiftFunctionTranslationError.missingEnclosingType(node) } @@ -80,11 +68,13 @@ extension SwiftFunctionSignature { throw SwiftFunctionTranslationError.generic(generics) } - self.selfParameter = .initializer(enclosingType) - self.result = SwiftResult(convention: .direct, type: enclosingType) - self.parameters = try Self.translateFunctionSignature( - node.signature, - symbolTable: symbolTable + self.init( + selfParameter: .initializer(enclosingType), + parameters: try Self.translateFunctionSignature( + node.signature, + symbolTable: symbolTable + ), + result: SwiftResult(convention: .direct, type: enclosingType) ) } @@ -93,8 +83,14 @@ extension SwiftFunctionSignature { enclosingType: SwiftType?, symbolTable: SwiftSymbolTable ) throws { + // Prohibit generics for now. + if let generics = node.genericParameterClause { + throw SwiftFunctionTranslationError.generic(generics) + } + // If this is a member of a type, so we will have a self parameter. Figure out the // type and convention for the self parameter. + let selfParameter: SwiftSelfParameter? if let enclosingType { var isMutating = false var isConsuming = false @@ -110,9 +106,9 @@ extension SwiftFunctionSignature { } if isStatic { - self.selfParameter = .staticMethod(enclosingType) + selfParameter = .staticMethod(enclosingType) } else { - self.selfParameter = .instance( + selfParameter = .instance( SwiftParameter( convention: isMutating ? .inout : isConsuming ? .consuming : .byValue, type: enclosingType @@ -120,29 +116,27 @@ extension SwiftFunctionSignature { ) } } else { - self.selfParameter = nil + selfParameter = nil } // Translate the parameters. - self.parameters = try Self.translateFunctionSignature( + let parameters = try Self.translateFunctionSignature( node.signature, symbolTable: symbolTable ) // Translate the result type. + let result: SwiftResult if let resultType = node.signature.returnClause?.type { - self.result = try SwiftResult( + result = try SwiftResult( convention: .direct, type: SwiftType(resultType, symbolTable: symbolTable) ) } else { - self.result = SwiftResult(convention: .direct, type: .tuple([])) + result = .void } - // Prohibit generics for now. - if let generics = node.genericParameterClause { - throw SwiftFunctionTranslationError.generic(generics) - } + self.init(selfParameter: selfParameter, parameters: parameters, result: result) } /// Translate the function signature, returning the list of translated @@ -163,6 +157,101 @@ extension SwiftFunctionSignature { try SwiftParameter(param, symbolTable: symbolTable) } } + + init(_ varNode: VariableDeclSyntax, isSet: Bool, enclosingType: SwiftType?, symbolTable: SwiftSymbolTable) throws { + + // If this is a member of a type, so we will have a self parameter. Figure out the + // type and convention for the self parameter. + if let enclosingType { + var isStatic = false + for modifier in varNode.modifiers { + switch modifier.name.tokenKind { + case .keyword(.static): isStatic = true + case .keyword(.class): throw SwiftFunctionTranslationError.classMethod(modifier.name) + default: break + } + } + + if isStatic { + self.selfParameter = .staticMethod(enclosingType) + } else { + self.selfParameter = .instance( + SwiftParameter( + convention: isSet && !enclosingType.isReferenceType ? .inout : .byValue, + type: enclosingType + ) + ) + } + } else { + self.selfParameter = nil + } + + guard let binding = varNode.bindings.first, varNode.bindings.count == 1 else { + throw SwiftFunctionTranslationError.multipleBindings(varNode) + } + + guard let varTypeNode = binding.typeAnnotation?.type else { + throw SwiftFunctionTranslationError.missingTypeAnnotation(varNode) + } + let valueType = try SwiftType(varTypeNode, symbolTable: symbolTable) + + if isSet { + self.parameters = [SwiftParameter(convention: .byValue, parameterName: "newValue", type: valueType)] + self.result = .void + } else { + self.parameters = [] + self.result = .init(convention: .direct, type: valueType) + } + } +} + +extension VariableDeclSyntax { + struct SupportedAccessorKinds: OptionSet { + var rawValue: UInt8 + + static var get: Self = .init(rawValue: 1 << 0) + static var set: Self = .init(rawValue: 1 << 1) + } + + /// Determine what operations (i.e. get and/or set) supported in this `VariableDeclSyntax` + /// + /// - Parameters: + /// - binding the pattern binding in this declaration. + func supportedAccessorKinds(binding: PatternBindingSyntax) -> SupportedAccessorKinds { + if self.bindingSpecifier == .keyword(.let) { + return [.get] + } + + if let accessorBlock = binding.accessorBlock { + switch accessorBlock.accessors { + case .getter: + return [.get] + case .accessors(let accessors): + var hasGetter = false + var hasSetter = false + + for accessor in accessors { + switch accessor.accessorSpecifier { + case .keyword(.get), .keyword(._read), .keyword(.unsafeAddress): + hasGetter = true + case .keyword(.set), .keyword(._modify), .keyword(.unsafeMutableAddress): + hasSetter = true + default: // Ignore willSet/didSet and unknown accessors. + break + } + } + + switch (hasGetter, hasSetter) { + case (true, true): return [.get, .set] + case (true, false): return [.get] + case (false, true): return [.set] + case (false, false): break + } + } + } + + return [.get, .set] + } } enum SwiftFunctionTranslationError: Error { @@ -172,4 +261,7 @@ enum SwiftFunctionTranslationError: Error { case classMethod(TokenSyntax) case missingEnclosingType(InitializerDeclSyntax) case failableInitializer(InitializerDeclSyntax) + case multipleBindings(VariableDeclSyntax) + case missingTypeAnnotation(VariableDeclSyntax) + case unsupportedAccessor(AccessorDeclSyntax) } diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift index 5e1c0b18..c8330126 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -29,6 +29,10 @@ package class SwiftNominalTypeDeclaration { case `struct` } + /// The syntax node this declaration is derived from. + /// Can be `nil` if this is loaded from a .swiftmodule. + var syntax: NominalTypeDeclSyntaxNode? + /// The kind of nominal type. var kind: Kind @@ -91,6 +95,15 @@ package class SwiftNominalTypeDeclaration { return name } } + + var isReferenceType: Bool { + switch kind { + case .actor, .class: + return true + case .enum, .struct, .protocol: + return false + } + } } extension SwiftNominalTypeDeclaration: Equatable { diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift b/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift index 4ca14815..d4a19f6e 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift @@ -15,7 +15,7 @@ import SwiftSyntax struct SwiftResult: Equatable { - var convention: SwiftResultConvention + var convention: SwiftResultConvention // currently not used. var type: SwiftType } @@ -23,3 +23,9 @@ enum SwiftResultConvention: Equatable { case direct case indirect } + +extension SwiftResult { + static var void: Self { + return Self(convention: .direct, type: .void) + } +} diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift index fbd7b4f0..4b07c575 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift @@ -34,6 +34,8 @@ enum KnownStandardLibraryType: String, Hashable, CaseIterable { case unsafeMutablePointer = "UnsafeMutablePointer" case unsafeBufferPointer = "UnsafeBufferPointer" case unsafeMutableBufferPointer = "UnsafeMutableBufferPointer" + case string = "String" + case void = "Void" var typeName: String { rawValue } @@ -46,7 +48,7 @@ enum KnownStandardLibraryType: String, Hashable, CaseIterable { switch self { case .bool, .double, .float, .int, .int8, .int16, .int32, .int64, .uint, .uint8, .uint16, .uint32, .uint64, .unsafeRawPointer, - .unsafeMutableRawPointer: + .unsafeMutableRawPointer, .string, .void: false case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift index 8ee0b767..4e17e32d 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift @@ -22,6 +22,10 @@ enum SwiftType: Equatable { indirect case optional(SwiftType) case tuple([SwiftType]) + static var void: Self { + return .tuple([]) + } + var asNominalType: SwiftNominalType? { switch self { case .nominal(let nominal): nominal @@ -37,7 +41,29 @@ enum SwiftType: Equatable { /// Whether this is the "Void" type, which is actually an empty /// tuple. var isVoid: Bool { - return self == .tuple([]) + switch self { + case .tuple([]): + return true + case .nominal(let nominal): + return nominal.parent == nil && nominal.nominalTypeDecl.moduleName == "Swift" && nominal.nominalTypeDecl.name == "Void" + default: + return false + } + } + + /// Reference type + /// + /// * Mutations don't require 'inout' convention. + /// * The value is a pointer of the instance data, + var isReferenceType: Bool { + switch self { + case .nominal(let nominal): + return nominal.nominalTypeDecl.isReferenceType + case .metatype, .function: + return true + case .optional, .tuple: + return false + } } } @@ -83,7 +109,7 @@ struct SwiftNominalType: Equatable { nominalTypeDecl: SwiftNominalTypeDeclaration, genericArguments: [SwiftType]? = nil ) { - self.storedParent = parent.map { .nominal($0) } + self.storedParent = parent.map { .nominal($0) } ?? nominalTypeDecl.parent.map { .nominal(SwiftNominalType(nominalTypeDecl: $0)) } self.nominalTypeDecl = nominalTypeDecl self.genericArguments = genericArguments } @@ -233,6 +259,27 @@ extension SwiftType { ) } + init?( + nominalDecl: NamedDeclSyntax & DeclGroupSyntax, + parent: SwiftType?, + symbolTable: SwiftSymbolTable + ) { + guard let nominalTypeDecl = symbolTable.lookupType( + nominalDecl.name.text, + parent: parent?.asNominalTypeDeclaration + ) else { + return nil + } + + self = .nominal( + SwiftNominalType( + parent: parent?.asNominalType, + nominalTypeDecl: nominalTypeDecl, + genericArguments: nil + ) + ) + } + /// Produce an expression that creates the metatype for this type in /// Swift source code. var metatypeReferenceExprSyntax: ExprSyntax { diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index c37325cb..f0d3ce2d 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -62,7 +62,7 @@ func assertLoweredFunction( let loweredCDecl = loweredFunction.cdeclThunk( cName: "c_\(swiftFunctionName)", - swiftFunctionName: swiftFunctionName, + swiftAPIName: swiftFunctionName, stdlibTypes: translator.swiftStdlibTypes ) @@ -76,10 +76,10 @@ func assertLoweredFunction( ) ) - let cFunction = translator.cdeclToCFunctionLowering( - loweredFunction.cdecl, + let cFunction = try loweredFunction.cFunctionDecl( cName: "c_\(swiftFunctionName)" ) + #expect( cFunction.description == expectedCFunction, sourceLocation: Testing.SourceLocation( @@ -90,3 +90,64 @@ func assertLoweredFunction( ) ) } + +/// Assert that the lowering of the function function declaration to a @_cdecl +/// entrypoint matches the expected form. +func assertLoweredVariableAccessor( + _ inputDecl: VariableDeclSyntax, + isSet: Bool, + javaPackage: String = "org.swift.mypackage", + swiftModuleName: String = "MyModule", + sourceFile: String? = nil, + enclosingType: TypeSyntax? = nil, + expectedCDecl: DeclSyntax?, + expectedCFunction: String?, + fileID: String = #fileID, + filePath: String = #filePath, + line: Int = #line, + column: Int = #column +) throws { + let translator = Swift2JavaTranslator( + javaPackage: javaPackage, + swiftModuleName: swiftModuleName + ) + + if let sourceFile { + translator.add(filePath: "Fake.swift", text: sourceFile) + } + + translator.prepareForTranslation() + + let swiftVariableName = inputDecl.bindings.first!.pattern.description + let loweredFunction = try translator.lowerFunctionSignature(inputDecl, isSet: isSet, enclosingType: enclosingType) + + let loweredCDecl = loweredFunction?.cdeclThunk( + cName: "c_\(swiftVariableName)", + swiftAPIName: swiftVariableName, + stdlibTypes: translator.swiftStdlibTypes + ) + + #expect( + loweredCDecl?.description == expectedCDecl?.description, + sourceLocation: Testing.SourceLocation( + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + ) + + let cFunction = try loweredFunction?.cFunctionDecl( + cName: "c_\(swiftVariableName)" + ) + + #expect( + cFunction?.description == expectedCFunction, + sourceLocation: Testing.SourceLocation( + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + ) +} diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index 97fa95fc..dce594ae 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -14,7 +14,6 @@ import JExtractSwift import SwiftSyntax -import SwiftSyntaxBuilder import Testing @Suite("Swift function lowering tests") @@ -41,14 +40,30 @@ final class FunctionLoweringTests { """, expectedCDecl: """ @_cdecl("c_f") - 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)) + public func c_f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z: UnsafePointer) -> Int { + return f(t: (t_0, (t_1_0, t_1_1)), z: z) } """, - expectedCFunction: "ptrdiff_t c_f(ptrdiff_t t_0, float t_1_0, double t_1_1, const void *z_pointer)" + expectedCFunction: "ptrdiff_t c_f(ptrdiff_t t_0, float t_1_0, double t_1_1, const ptrdiff_t *z)" ) } + @Test("Lowering String") func loweringString() throws { + try assertLoweredFunction( + """ + func takeString(str: String) {} + """, + expectedCDecl: """ + @_cdecl("c_takeString") + public func c_takeString(_ str: UnsafePointer) { + takeString(str: String(cString: str)) + } + """, + expectedCFunction: """ + void c_takeString(const int8_t *str) + """) + } + @Test("Lowering functions involving inout") func loweringInoutParameters() throws { try assertLoweredFunction(""" @@ -117,7 +132,7 @@ final class FunctionLoweringTests { expectedCDecl: """ @_cdecl("c_shift") public func c_shift(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer) { - unsafeBitCast(self, to: Point.self).shift(by: (delta_0, delta_1)) + self.assumingMemoryBound(to: Point.self).pointee.shift(by: (delta_0, delta_1)) } """, expectedCFunction: "void c_shift(double delta_0, double delta_1, const void *self)" @@ -151,11 +166,11 @@ final class FunctionLoweringTests { enclosingType: "Person", expectedCDecl: """ @_cdecl("c_randomPerson") - public func c_randomPerson(_ seed: Double) -> UnsafeRawPointer { - return unsafeBitCast(Person.randomPerson(seed: seed), to: UnsafeRawPointer.self) + public func c_randomPerson(_ seed: Double, _ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Person.self).initialize(to: Person.randomPerson(seed: seed)) } """, - expectedCFunction: "const void *c_randomPerson(double seed)" + expectedCFunction: "void c_randomPerson(double seed, void *_result)" ) } @@ -186,11 +201,11 @@ final class FunctionLoweringTests { enclosingType: "Person", expectedCDecl: """ @_cdecl("c_init") - public func c_init(_ seed: Double) -> UnsafeRawPointer { - return unsafeBitCast(Person(seed: seed), to: UnsafeRawPointer.self) + public func c_init(_ seed: Double, _ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Person.self).initialize(to: Person(seed: seed)) } """, - expectedCFunction: "const void *c_init(double seed)" + expectedCFunction: "void c_init(double seed, void *_result)" ) } @@ -232,11 +247,11 @@ final class FunctionLoweringTests { 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) + 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: "const void *c_shifted(double delta_0, double delta_1, const void *self)" + expectedCFunction: "void c_shifted(double delta_0, double delta_1, const void *self, void *_result)" ) } @@ -268,18 +283,19 @@ final class FunctionLoweringTests { """, 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) + public func c_getTuple(_ _result_0: UnsafeMutablePointer, _ _result_1_0: UnsafeMutablePointer, _ _result_1_1: UnsafeMutableRawPointer) { + let _result = getTuple() + _result_0.initialize(to: _result.0) + let _result_1 = _result.1 + _result_1_0.initialize(to: _result_1.0) + _result_1_1.assumingMemoryBound(to: Point.self).initialize(to: _result_1.1) } """, - expectedCFunction: "void c_getTuple(void *_result_0, void *_result_1_0, void *_result_1_1)" + expectedCFunction: "void c_getTuple(ptrdiff_t *_result_0, float *_result_1_0, void *_result_1_1)" ) } - @Test("Lowering buffer pointer returns", .disabled("Doesn't turn into the indirect returns")) + @Test("Lowering buffer pointer returns") func lowerBufferPointerReturns() throws { try assertLoweredFunction(""" func getBufferPointer() -> UnsafeMutableBufferPointer { } @@ -289,11 +305,28 @@ final class FunctionLoweringTests { """, expectedCDecl: """ @_cdecl("c_getBufferPointer") - public func c_getBufferPointer(_result_pointer: UnsafeMutableRawPointer, _result_count: UnsafeMutableRawPointer) { - return UnsafeRawPointer(getPointer()) + public func c_getBufferPointer(_ _result_0: UnsafeMutablePointer, _ _result_1: UnsafeMutablePointer) { + let _result = getBufferPointer() + _result_0.initialize(to: _result.0) + _result_1.initialize(to: _result.1) + } + """, + expectedCFunction: "void c_getBufferPointer(void **_result_0, ptrdiff_t *_result_1)" + ) + } + + @Test("Lowering () -> Void type") + func lowerSimpleClosureTypes() throws { + try assertLoweredFunction(""" + func doSomething(body: () -> Void) { } + """, + expectedCDecl: """ + @_cdecl("c_doSomething") + public func c_doSomething(_ body: @convention(c) () -> Void) { + doSomething(body: body) } """, - expectedCFunction: "c_getBufferPointer(void* _result_pointer, void* _result_count)" + expectedCFunction: "void c_doSomething(void (*body)(void))" ) } @@ -313,4 +346,86 @@ final class FunctionLoweringTests { expectedCFunction: "void c_doSomething(double (*body)(int32_t))" ) } + + @Test("Lowering read accessor") + func lowerGlobalReadAccessor() throws { + try assertLoweredVariableAccessor( + DeclSyntax(""" + var value: Point = Point() + """).cast(VariableDeclSyntax.self), + isSet: false, + sourceFile: """ + struct Point { } + """, + expectedCDecl: """ + @_cdecl("c_value") + public func c_value(_ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Point.self).initialize(to: value) + } + """, + expectedCFunction: "void c_value(void *_result)" + ) + } + + @Test("Lowering set accessor") + func lowerGlobalSetAccessor() throws { + try assertLoweredVariableAccessor( + DeclSyntax(""" + var value: Point { get { Point() } set {} } + """).cast(VariableDeclSyntax.self), + isSet: true, + sourceFile: """ + struct Point { } + """, + expectedCDecl: """ + @_cdecl("c_value") + public func c_value(_ newValue: UnsafeRawPointer) { + value = newValue.assumingMemoryBound(to: Point.self).pointee + } + """, + expectedCFunction: "void c_value(const void *newValue)" + ) + } + + @Test("Lowering member read accessor") + func lowerMemberReadAccessor() throws { + try assertLoweredVariableAccessor( + DeclSyntax(""" + var value: Int + """).cast(VariableDeclSyntax.self), + isSet: false, + sourceFile: """ + struct Point { } + """, + enclosingType: "Point", + expectedCDecl: """ + @_cdecl("c_value") + public func c_value(_ self: UnsafeRawPointer) -> Int { + return self.assumingMemoryBound(to: Point.self).pointee.value + } + """, + expectedCFunction: "ptrdiff_t c_value(const void *self)" + ) + } + + @Test("Lowering member set accessor") + func lowerMemberSetAccessor() throws { + try assertLoweredVariableAccessor( + DeclSyntax(""" + var value: Point + """).cast(VariableDeclSyntax.self), + isSet: true, + sourceFile: """ + class Point { } + """, + enclosingType: "Point", + expectedCDecl: """ + @_cdecl("c_value") + public func c_value(_ newValue: UnsafeRawPointer, _ self: UnsafeRawPointer) { + self.assumingMemoryBound(to: Point.self).pointee.value = newValue.assumingMemoryBound(to: Point.self).pointee + } + """, + expectedCFunction: "void c_value(const void *newValue, const void *self)" + ) + } }