Skip to content

Commit df27b79

Browse files
author
Gabor Horvath
committed
[cxx-interop] Work around lifetime errors in SwiftifyImport generated code
Unfortunately, this was not discovered earlier as swift-ide-test is not invoking the SIL passes that produce this diagnostic. When creating Swift spans from C++ spans we have no lifetime dependency information to propagate as C++ spans are modeled as escapable types. Hence, this PR introduces a helper function to bypass the lifetime checks triggered by this discepancy. Hopefully, the new utility will go away as the lifetime analysis matures on the Swift side and we get standardized way to deal with unsafe lifetimes.
1 parent a3fac71 commit df27b79

File tree

6 files changed

+42
-10
lines changed

6 files changed

+42
-10
lines changed

lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ struct CxxSpanReturnThunkBuilder: BoundsCheckedThunkBuilder {
437437

438438
func buildFunctionCall(_ pointerArgs: [Int: ExprSyntax]) throws -> ExprSyntax {
439439
let call = try base.buildFunctionCall(pointerArgs)
440-
return "Span(_unsafeCxxSpan: \(call))"
440+
return "_unsafeRemoveLifetime(Span(_unsafeCxxSpan: \(call)))"
441441
}
442442
}
443443

stdlib/public/Cxx/CxxSpan.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ internal func unsafeBitCast<T: ~Escapable, U>(
1818
Builtin.reinterpretCast(x)
1919
}
2020

21+
/// Used by SwiftifyImport to work around a compiler diagnostic. It should be removed once the
22+
/// workaround is no longer needed.
23+
@_unsafeNonescapableResult
24+
@_alwaysEmitIntoClient
25+
@_transparent
26+
public func _unsafeRemoveLifetime<T: ~Copyable & ~Escapable>(_ dependent: consuming T) -> T {
27+
dependent
28+
}
29+
2130
/// Unsafely discard any lifetime dependency on the `dependent` argument. Return
2231
/// a value identical to `dependent` with a lifetime dependency on the caller's
2332
/// borrow scope of the `source` argument.
@@ -81,7 +90,7 @@ extension CxxSpan {
8190
extension Span {
8291
@_alwaysEmitIntoClient
8392
@unsafe
84-
@lifetime(borrow span)
93+
@_unsafeNonescapableResult
8594
public init<T: CxxSpan<Element>>(
8695
_unsafeCxxSpan span: borrowing T,
8796
) {

test/Interop/Cxx/stdlib/Inputs/std-span.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ inline SpanOfInt initSpan(int arr[], size_t size) {
5757
struct DependsOnSelf {
5858
std::vector<int> v;
5959
__attribute__((swift_name("get()")))
60-
ConstSpanOfInt get() [[clang::lifetimebound]] { return ConstSpanOfInt(v.data(), v.size()); }
60+
ConstSpanOfInt get() const [[clang::lifetimebound]] { return ConstSpanOfInt(v.data(), v.size()); }
6161
};
6262

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

test/Interop/Cxx/stdlib/std-span-interface.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import CxxStdlib
1616

1717
// CHECK: struct DependsOnSelf {
1818
// CHECK: @lifetime(borrow self)
19-
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public mutating func get() -> Span<CInt>
20-
// CHECK-NEXT: mutating func get() -> ConstSpanOfInt
19+
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public borrowing func get() -> Span<CInt>
20+
// CHECK-NEXT: borrowing func get() -> ConstSpanOfInt
2121

2222
// CHECK: mutating func set(_ x: borrowing std.{{.*}}vector<CInt, std.{{.*}}allocator<CInt>>)
2323
// CHECK: func funcWithSafeWrapper(_ s: ConstSpanOfInt)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: %target-run-simple-swift(-plugin-path %swift-plugin-dir -I %S/Inputs -Xfrontend -enable-experimental-cxx-interop -swift-version 6 -Xfrontend -disable-availability-checking -Xcc -std=c++20 -enable-experimental-feature LifetimeDependence -enable-experimental-feature Span -enable-experimental-feature SafeInteropWrappers)
2+
3+
// FIXME swift-ci linux tests do not support std::span
4+
// UNSUPPORTED: OS=linux-gnu
5+
6+
// TODO: test failed in Windows PR testing: rdar://144384453
7+
// UNSUPPORTED: OS=windows-msvc
8+
9+
// REQUIRES: swift_feature_SafeInteropWrappers
10+
// REQUIRES: swift_feature_Span
11+
// REQUIRES: swift_feature_LifetimeDependence
12+
13+
// REQUIRES: executable_test
14+
15+
#if !BRIDGING_HEADER
16+
import StdSpan
17+
#endif
18+
import CxxStdlib
19+
20+
func canCallSafeSpanAPIs(_ x: Span<CInt>) {
21+
funcWithSafeWrapper(x)
22+
funcWithSafeWrapper2(x)
23+
}

test/Macros/SwiftifyImport/CxxSpan/LifetimeboundSpan.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,25 @@ struct X {
3636

3737
// CHECK: @_alwaysEmitIntoClient @lifetime(span)
3838
// CHECK-NEXT: func myFunc(_ span: Span<CInt>) -> Span<CInt> {
39-
// CHECK-NEXT: return Span(_unsafeCxxSpan: myFunc(SpanOfInt(span)))
39+
// CHECK-NEXT: return _unsafeRemoveLifetime(Span(_unsafeCxxSpan: myFunc(SpanOfInt(span))))
4040
// CHECK-NEXT: }
4141

4242
// CHECK: @_alwaysEmitIntoClient @lifetime(borrow vec) @_disfavoredOverload
4343
// CHECK-NEXT: func myFunc2(_ vec: borrowing VecOfInt) -> Span<CInt> {
44-
// CHECK-NEXT: return Span(_unsafeCxxSpan: myFunc2(vec))
44+
// CHECK-NEXT: return _unsafeRemoveLifetime(Span(_unsafeCxxSpan: myFunc2(vec)))
4545
// CHECK-NEXT: }
4646

4747
// CHECK: @_alwaysEmitIntoClient @lifetime(span1, span2)
4848
// CHECK-NEXT: func myFunc3(_ span1: Span<CInt>, _ span2: Span<CInt>) -> Span<CInt> {
49-
// CHECK-NEXT: return Span(_unsafeCxxSpan: myFunc3(SpanOfInt(span1), SpanOfInt(span2)))
49+
// CHECK-NEXT: return _unsafeRemoveLifetime(Span(_unsafeCxxSpan: myFunc3(SpanOfInt(span1), SpanOfInt(span2))))
5050
// CHECK-NEXT: }
5151

5252
// CHECK: @_alwaysEmitIntoClient @lifetime(borrow vec, span)
5353
// CHECK-NEXT: func myFunc4(_ vec: borrowing VecOfInt, _ span: Span<CInt>) -> Span<CInt> {
54-
// CHECK-NEXT: return Span(_unsafeCxxSpan: myFunc4(vec, SpanOfInt(span)))
54+
// CHECK-NEXT: return _unsafeRemoveLifetime(Span(_unsafeCxxSpan: myFunc4(vec, SpanOfInt(span))))
5555
// CHECK-NEXT: }
5656

5757
// CHECK: @_alwaysEmitIntoClient @lifetime(borrow self) @_disfavoredOverload
5858
// CHECK-NEXT: func myFunc5() -> Span<CInt> {
59-
// CHECK-NEXT: return Span(_unsafeCxxSpan: myFunc5())
59+
// CHECK-NEXT: return _unsafeRemoveLifetime(Span(_unsafeCxxSpan: myFunc5()))
6060
// CHECK-NEXT: }

0 commit comments

Comments
 (0)