Skip to content

[cxx-interop] Support borrowing from self in SwiftifyImport #78947

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8796,7 +8796,8 @@ class SwiftifyInfoPrinter {

void printLifetimeboundReturn(int idx, bool borrow) {
printSeparator();
out << ".lifetimeDependence(dependsOn: " << (idx + 1);
out << ".lifetimeDependence(dependsOn: ";
printParamOrReturn(idx);
out << ", pointer: .return, type: ";
out << (borrow ? ".borrow" : ".copy");
out << ")";
Expand Down Expand Up @@ -8825,7 +8826,9 @@ class SwiftifyInfoPrinter {
}

void printParamOrReturn(ssize_t pointerIndex) {
if (pointerIndex == -1)
if (pointerIndex == -2)
out << ".self";
else if (pointerIndex == -1)
out << ".return";
else
out << ".param(" << pointerIndex + 1 << ")";
Expand Down Expand Up @@ -8866,15 +8869,21 @@ void ClangImporter::Implementation::importSpanAttributes(FuncDecl *MappedDecl) {
->getDesugaredType()
->getString()));
}
bool lifetimeDependenceOn = MappedDecl->getASTContext().LangOpts.hasFeature(
Feature::LifetimeDependence);
if (SwiftDeclConverter::getImplicitObjectParamAnnotation<
clang::LifetimeBoundAttr>(ClangDecl) &&
lifetimeDependenceOn && returnIsSpan) {
printer.printLifetimeboundReturn(-2, true);
attachMacro = true;
}
for (auto [index, param] : llvm::enumerate(ClangDecl->parameters())) {
auto paramTy = param->getType();
const auto *decl = paramTy->getAsTagDecl();
auto swiftParam = MappedDecl->getParameters()->get(index);
bool isSpan =
decl && decl->isInStdNamespace() && decl->getName() == "span";
if (param->hasAttr<clang::LifetimeBoundAttr>() &&
MappedDecl->getASTContext().LangOpts.hasFeature(
Feature::LifetimeDependence) &&
if (param->hasAttr<clang::LifetimeBoundAttr>() && lifetimeDependenceOn &&
(isSpan || returnIsSpan)) {
printer.printLifetimeboundReturn(
index, !isSpan && swiftParam->getInterfaceType()->isEscapable());
Expand Down
36 changes: 28 additions & 8 deletions lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import SwiftSyntaxMacros
enum SwiftifyExpr: Hashable {
case param(_ index: Int)
case `return`
case `self`
}

extension SwiftifyExpr: CustomStringConvertible {
var description: String {
switch self {
case .param(let index): return ".param(\(index))"
case .return: return ".return"
case .self: return ".self"
}
}
}
Expand All @@ -26,7 +28,7 @@ enum DependenceType {
}

struct LifetimeDependence {
let dependsOn: Int
let dependsOn: SwiftifyExpr
let type: DependenceType
}

Expand All @@ -48,6 +50,8 @@ func tryGetParamName(_ funcDecl: FunctionDeclSyntax, _ expr: SwiftifyExpr) -> To
case .param(let i):
let funcParam = getParam(funcDecl, i - 1)
return funcParam.secondName ?? funcParam.firstName
case .`self`:
return .keyword(.self)
default: return nil
}
}
Expand All @@ -59,6 +63,8 @@ func getSwiftifyExprType(_ funcDecl: FunctionDeclSyntax, _ expr: SwiftifyExpr) -
return funcParam.type
case .return:
return funcDecl.signature.returnClause!.type
case .self:
return TypeSyntax(IdentifierTypeSyntax(name: TokenSyntax("Self")))
}
}

Expand All @@ -84,6 +90,8 @@ struct CxxSpan: ParamInfo {
case .return:
return CxxSpanReturnThunkBuilder(base: base, signature: funcDecl.signature,
typeMappings: typeMappings, node: original)
case .self:
return base
}
}
}
Expand Down Expand Up @@ -118,6 +126,8 @@ struct CountedBy: ParamInfo {
base: base, countExpr: count,
signature: funcDecl.signature,
nonescaping: nonescaping, isSizedBy: sizedBy)
case .self:
return base
}
}
}
Expand Down Expand Up @@ -315,8 +325,9 @@ struct FunctionCallBuilder: BoundsCheckedThunkBuilder {
}.map { (i: Int, e: FunctionParameterSyntax) in
e.with(\.type, (argTypes[i] ?? e.type)!)
}
let last = newParams.popLast()!
newParams.append(last.with(\.trailingComma, nil))
if let last = newParams.popLast() {
newParams.append(last.with(\.trailingComma, nil))
}

var sig = base.signature.with(\.parameterClause.parameters, FunctionParameterListSyntax(newParams))
if returnType != nil {
Expand Down Expand Up @@ -755,9 +766,10 @@ public struct SwiftifyImportMacro: PeerMacro {
let pointerParamIndex: Int = try getIntLiteralValue(pointerParamIndexArg.expression)
return .param(pointerParamIndex)
case "return": return .return
case "self": return .`self`
default:
throw DiagnosticError(
"expected 'param' or 'return', got '\(enumName)'",
"expected 'param', 'return', or 'self', got '\(enumName)'",
node: expr)
}
}
Expand Down Expand Up @@ -823,7 +835,11 @@ public struct SwiftifyImportMacro: PeerMacro {
static func parseLifetimeDependence(_ enumConstructorExpr: FunctionCallExprSyntax) throws -> (SwiftifyExpr, LifetimeDependence) {
let argumentList = enumConstructorExpr.arguments
let pointer: SwiftifyExpr = try parseSwiftifyExpr(try getArgumentByName(argumentList, "pointer"))
let dependsOn: Int = try getIntLiteralValue(try getArgumentByName(argumentList, "dependsOn"))
let dependsOnArg = try getArgumentByName(argumentList, "dependsOn")
let dependsOn: SwiftifyExpr = try parseSwiftifyExpr(dependsOnArg)
if dependsOn == .`return` {
throw DiagnosticError("lifetime cannot depend on the return value", node: dependsOnArg)
}
let type = try getArgumentByName(argumentList, "type")
let depType: DependenceType
switch try parseEnumName(type) {
Expand Down Expand Up @@ -917,8 +933,9 @@ public struct SwiftifyImportMacro: PeerMacro {
let (expr, dependence) = try parseLifetimeDependence(enumConstructorExpr)
lifetimeDependencies[expr, default: []].append(dependence)
// We assume pointers annotated with lifetimebound do not escape.
if dependence.type == DependenceType.copy {
nonescapingPointers.insert(dependence.dependsOn)
let fromIdx = paramOrReturnIndex(dependence.dependsOn)
if dependence.type == DependenceType.copy && fromIdx != 0 {
nonescapingPointers.insert(fromIdx)
}
// The escaping is controlled when a parameter is the target of a lifetimebound.
// So we want to do the transformation to Swift's Span.
Expand Down Expand Up @@ -987,13 +1004,16 @@ public struct SwiftifyImportMacro: PeerMacro {
"multiple _SwiftifyInfos referring to return value: \(pointerInfo) and \(ret!)", node: pointerInfo.original)
}
ret = pointerInfo
case .self:
throw DiagnosticError("do not annotate self", node: pointerInfo.original)
}
}
}

static func paramOrReturnIndex(_ expr: SwiftifyExpr) -> Int {
switch expr {
case .param(let i): return i
case .`self`: return 0
case .return: return -1
}
}
Expand Down Expand Up @@ -1029,7 +1049,7 @@ public struct SwiftifyImportMacro: PeerMacro {
DeclReferenceExprSyntax(baseName: TokenSyntax("borrow"))))
}
args.append(LabeledExprSyntax(expression:
DeclReferenceExprSyntax(baseName: TokenSyntax(tryGetParamName(funcDecl, .param(dependence.dependsOn)))!),
DeclReferenceExprSyntax(baseName: TokenSyntax(tryGetParamName(funcDecl, dependence.dependsOn))!),
trailingComma: .commaToken()))
}
args[args.count - 1] = args[args.count - 1].with(\.trailingComma, nil)
Expand Down
3 changes: 2 additions & 1 deletion stdlib/public/core/SwiftifyImport.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
public enum _SwiftifyExpr {
case param(_ index: Int)
case `return`
case `self`
}

public enum _DependenceType {
Expand Down Expand Up @@ -41,7 +42,7 @@ public enum _SwiftifyInfo {
case nonescaping(pointer: _SwiftifyExpr)
/// Can express lifetime dependencies between inputs and outputs of a function.
/// 'dependsOn' is the input on which the output 'pointer' depends.
case lifetimeDependence(dependsOn: Int, pointer: _SwiftifyExpr, type: _DependenceType)
case lifetimeDependence(dependsOn: _SwiftifyExpr, pointer: _SwiftifyExpr, type: _DependenceType)
}

/// Generates a safe wrapper for function with Unsafe[Mutable][Raw]Pointer[?] or std::span arguments.
Expand Down
6 changes: 6 additions & 0 deletions test/Interop/Cxx/stdlib/Inputs/std-span.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ inline SpanOfInt initSpan(int arr[], size_t size) {
return SpanOfInt(arr, size);
}

struct DependsOnSelf {
std::vector<int> v;
__attribute__((swift_name("get()")))
ConstSpanOfInt get() [[clang::lifetimebound]] { return ConstSpanOfInt(v.data(), v.size()); }
};

inline struct SpanBox getStructSpanBox() { return {iarray, iarray, sarray, sarray}; }

inline void funcWithSafeWrapper(ConstSpanOfInt s [[clang::noescape]]) {}
Expand Down
5 changes: 5 additions & 0 deletions test/Interop/Cxx/stdlib/std-span-interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import StdSpan
#endif
import CxxStdlib

// CHECK: struct DependsOnSelf {
// CHECK: @lifetime(borrow self)
// CHECK-NEXT: @_alwaysEmitIntoClient public mutating func get() -> Span<CInt>
// CHECK-NEXT: mutating func get() -> ConstSpanOfInt

// CHECK: func funcWithSafeWrapper(_ s: ConstSpanOfInt)
// CHECK-NEXT: func funcWithSafeWrapper2(_ s: borrowing ConstSpanOfInt) -> ConstSpanOfInt
// CHECK-NEXT: func funcWithSafeWrapper3(_ v: borrowing VecOfInt) -> ConstSpanOfInt
Expand Down
18 changes: 14 additions & 4 deletions test/Macros/SwiftifyImport/CxxSpan/LifetimeboundSpan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,27 @@ import StdSpan

public struct VecOfInt {}

@_SwiftifyImport(.lifetimeDependence(dependsOn: 1, pointer: .return, type: .copy), typeMappings: ["SpanOfInt" : "std.span<CInt>"])
@_SwiftifyImport(.lifetimeDependence(dependsOn: .param(1), pointer: .return, type: .copy), typeMappings: ["SpanOfInt" : "std.span<CInt>"])
func myFunc(_ span: SpanOfInt) -> SpanOfInt {
}

@_SwiftifyImport(.lifetimeDependence(dependsOn: 1, pointer: .return, type: .borrow), typeMappings: ["SpanOfInt" : "std.span<CInt>"])
@_SwiftifyImport(.lifetimeDependence(dependsOn: .param(1), pointer: .return, type: .borrow), typeMappings: ["SpanOfInt" : "std.span<CInt>"])
func myFunc2(_ vec: borrowing VecOfInt) -> SpanOfInt {
}

@_SwiftifyImport(.lifetimeDependence(dependsOn: 1, pointer: .return, type: .copy), .lifetimeDependence(dependsOn: 2, pointer: .return, type: .copy), typeMappings: ["SpanOfInt" : "std.span<CInt>"])
@_SwiftifyImport(.lifetimeDependence(dependsOn: .param(1), pointer: .return, type: .copy), .lifetimeDependence(dependsOn: .param(2), pointer: .return, type: .copy), typeMappings: ["SpanOfInt" : "std.span<CInt>"])
func myFunc3(_ span1: SpanOfInt, _ span2: SpanOfInt) -> SpanOfInt {
}

@_SwiftifyImport(.lifetimeDependence(dependsOn: 1, pointer: .return, type: .borrow), .lifetimeDependence(dependsOn: 2, pointer: .return, type: .copy), typeMappings: ["SpanOfInt" : "std.span<CInt>"])
@_SwiftifyImport(.lifetimeDependence(dependsOn: .param(1), pointer: .return, type: .borrow), .lifetimeDependence(dependsOn: .param(2), pointer: .return, type: .copy), typeMappings: ["SpanOfInt" : "std.span<CInt>"])
func myFunc4(_ vec: borrowing VecOfInt, _ span: SpanOfInt) -> SpanOfInt {
}

struct X {
@_SwiftifyImport(.lifetimeDependence(dependsOn: .self, pointer: .return, type: .borrow), typeMappings: ["SpanOfInt" : "std.span<CInt>"])
func myFunc5() -> SpanOfInt {}
}

// CHECK: @_alwaysEmitIntoClient @lifetime(span)
// CHECK-NEXT: func myFunc(_ span: Span<CInt>) -> Span<CInt> {
// CHECK-NEXT: return Span(_unsafeCxxSpan: myFunc(SpanOfInt(span)))
Expand All @@ -48,3 +53,8 @@ func myFunc4(_ vec: borrowing VecOfInt, _ span: SpanOfInt) -> SpanOfInt {
// CHECK-NEXT: func myFunc4(_ vec: borrowing VecOfInt, _ span: Span<CInt>) -> Span<CInt> {
// CHECK-NEXT: return Span(_unsafeCxxSpan: myFunc4(vec, SpanOfInt(span)))
// CHECK-NEXT: }

// CHECK: @_alwaysEmitIntoClient @lifetime(borrow self)
// CHECK-NEXT: func myFunc5() -> Span<CInt> {
// CHECK-NEXT: return Span(_unsafeCxxSpan: myFunc5())
// CHECK-NEXT: }
3 changes: 2 additions & 1 deletion test/abi/macOS/arm64/stdlib.swift
Original file line number Diff line number Diff line change
Expand Up @@ -802,14 +802,15 @@ Added: _$ss7RawSpanVN
// _SwiftifyInfo enum for _SwiftifyImports macro
Added: _$ss13_SwiftifyExprO5paramyABSicABmFWC
Added: _$ss13_SwiftifyExprO6returnyA2BmFWC
Added: _$ss13_SwiftifyExprO4selfyA2BmFWC
Added: _$ss13_SwiftifyExprOMa
Added: _$ss13_SwiftifyExprOMn
Added: _$ss13_SwiftifyExprON
Added: _$ss13_SwiftifyInfoO11nonescapingyABs01_A4ExprO_tcABmFWC
Added: _$ss13_SwiftifyInfoO7endedByyABs01_A4ExprO_SitcABmFWC
Added: _$ss13_SwiftifyInfoO7sizedByyABs01_A4ExprO_SStcABmFWC
Added: _$ss13_SwiftifyInfoO9countedByyABs01_A4ExprO_SStcABmFWC
Added: _$ss13_SwiftifyInfoO18lifetimeDependenceyABSi_s01_A4ExprOs01_D4TypeOtcABmFWC
Added: _$ss13_SwiftifyInfoO18lifetimeDependenceyABs01_A4ExprO_AEs01_D4TypeOtcABmFWC
Added: _$ss13_SwiftifyInfoOMa
Added: _$ss13_SwiftifyInfoOMn
Added: _$ss13_SwiftifyInfoON
Expand Down
3 changes: 2 additions & 1 deletion test/abi/macOS/x86_64/stdlib.swift
Original file line number Diff line number Diff line change
Expand Up @@ -803,14 +803,15 @@ Added: _$ss7RawSpanVN
// _SwiftifyInfo enum for _SwiftifyImports macro
Added: _$ss13_SwiftifyExprO5paramyABSicABmFWC
Added: _$ss13_SwiftifyExprO6returnyA2BmFWC
Added: _$ss13_SwiftifyExprO4selfyA2BmFWC
Added: _$ss13_SwiftifyExprOMa
Added: _$ss13_SwiftifyExprOMn
Added: _$ss13_SwiftifyExprON
Added: _$ss13_SwiftifyInfoO11nonescapingyABs01_A4ExprO_tcABmFWC
Added: _$ss13_SwiftifyInfoO7endedByyABs01_A4ExprO_SitcABmFWC
Added: _$ss13_SwiftifyInfoO7sizedByyABs01_A4ExprO_SStcABmFWC
Added: _$ss13_SwiftifyInfoO9countedByyABs01_A4ExprO_SStcABmFWC
Added: _$ss13_SwiftifyInfoO18lifetimeDependenceyABSi_s01_A4ExprOs01_D4TypeOtcABmFWC
Added: _$ss13_SwiftifyInfoO18lifetimeDependenceyABs01_A4ExprO_AEs01_D4TypeOtcABmFWC
Added: _$ss13_SwiftifyInfoOMa
Added: _$ss13_SwiftifyInfoOMn
Added: _$ss13_SwiftifyInfoON
Expand Down