Skip to content

[Swiftify] Don't use count from Span inside withUnsafeBufferPointer call #81267

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 3 commits into from
May 4, 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
18 changes: 15 additions & 3 deletions lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ struct CxxSpanReturnThunkBuilder: SpanBoundsThunkBuilder {
} else {
"MutableSpan"
}
return "unsafe _cxxOverrideLifetime(\(raw: cast)(_unsafeCxxSpan: \(call)), copying: ())"
return "unsafe _swiftifyOverrideLifetime(\(raw: cast)(_unsafeCxxSpan: \(call)), copying: ())"
}
}

Expand Down Expand Up @@ -701,10 +701,15 @@ struct CountedOrSizedReturnPointerThunkBuilder: PointerBoundsThunkBuilder {
}()
"""
}
return
var expr = ExprSyntax(
"""
unsafe \(raw: cast)(\(raw: startLabel): \(call), count: Int(\(countExpr)))
\(raw: cast)(\(raw: startLabel): \(call), count: Int(\(countExpr)))
"""
)
if generateSpan {
expr = "_swiftifyOverrideLifetime(\(expr), copying: ())"
}
return "unsafe \(expr)"
}
}

Expand Down Expand Up @@ -778,6 +783,13 @@ struct CountedOrSizedPointerThunkBuilder: ParamBoundsThunkBuilder, PointerBounds
let argExpr = ExprSyntax("\(unwrappedName).baseAddress")
assert(args[index] == nil)
args[index] = try castPointerToOpaquePointer(unwrapIfNonnullable(argExpr))
if skipTrivialCount {
if let countVar = countExpr.as(DeclReferenceExprSyntax.self) {
let i = try getParameterIndexForDeclRef(signature.parameterClause.parameters, countVar)
args[i] = castIntToTargetType(
expr: "\(unwrappedName).count", type: getParam(signature, i).type)
}
}
let call = try base.buildFunctionCall(args)
let ptrRef = unwrapIfNullable(ExprSyntax(DeclReferenceExprSyntax(baseName: name)))

Expand Down
40 changes: 40 additions & 0 deletions stdlib/public/core/SwiftifyImport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,43 @@ public enum _SwiftifyInfo {
public macro _SwiftifyImport(_ paramInfo: _SwiftifyInfo..., typeMappings: [String: String] = [:]) =
#externalMacro(module: "SwiftMacros", type: "SwiftifyImportMacro")
#endif

/// Unsafely discard any lifetime dependency on the `dependent` argument. Return
/// a value identical to `dependent` with a lifetime dependency on the caller's
/// borrow scope of the `source` argument.
///
/// This mimics the stdlib definition. It is public for use with import macros.
@unsafe
@_unsafeNonescapableResult
@_alwaysEmitIntoClient
@_transparent
@lifetime(borrow source)
public func _swiftifyOverrideLifetime<
T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable
>(
_ dependent: consuming T, borrowing source: borrowing U
) -> T {
// TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence
// should be expressed by a builtin that is hidden within the function body.
dependent
}

/// Unsafely discard any lifetime dependency on the `dependent` argument. Return
/// a value identical to `dependent` that inherits all lifetime dependencies from
/// the `source` argument.
///
/// This mimics the stdlib definition. It is public for use with import macros.
@unsafe
@_unsafeNonescapableResult
@_alwaysEmitIntoClient
@_transparent
@lifetime(copy source)
public func _swiftifyOverrideLifetime<
T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable
>(
_ dependent: consuming T, copying source: borrowing U
) -> T {
// TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence
// should be expressed by a builtin that is hidden within the function body.
dependent
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ void nullUnspecified(int len, int * __counted_by(len) _Null_unspecified __noesca

void nonnull(int len, int * __counted_by(len) _Nonnull __noescape p);

//void nullable(int len, int * __counted_by(len) _Nullable p);
void nullable(int len, int * __counted_by(len) _Nullable p __noescape);

int * __counted_by(len) __noescape returnPointer(int len);

Expand Down
3 changes: 1 addition & 2 deletions test/Interop/C/swiftify-import/Inputs/sized-by-noescape.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ void nullUnspecified(int len, const void * __sized_by(len) __noescape _Null_unsp

void nonnull(int len, const void * __sized_by(len) __noescape _Nonnull p);

// Nullable ~Escapable types not supported yet
//void nullable(int len, const void * __sized_by(len) __noescape _Nullable p);
void nullable(int len, const void * __sized_by(len) __noescape _Nullable p);

const void * __sized_by(len) __noescape _Nonnull returnPointer(int len);

Expand Down
52 changes: 51 additions & 1 deletion test/Interop/C/swiftify-import/counted-by-noescape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// swift-ide-test doesn't currently typecheck the macro expansions, so run the compiler as well
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/CountedByNoEscape.swiftmodule -I %S/Inputs -enable-experimental-feature SafeInteropWrappers %s
// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/CountedByNoEscape.swiftmodule -I %S/Inputs -enable-experimental-feature SafeInteropWrappers -enable-experimental-feature LifetimeDependence %s

// Check that ClangImporter correctly infers and expands @_SwiftifyImport macros for functions with __counted_by __noescape parameters.

Expand All @@ -17,6 +17,8 @@ import CountedByNoEscapeClang
// CHECK-NEXT: @_alwaysEmitIntoClient public func nonnull(_ p: inout MutableSpan<Int32>)
// CHECK-NEXT: @lifetime(p: copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullUnspecified(_ p: inout MutableSpan<Int32>)
// CHECK-NEXT: @lifetime(p: copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullable(_ p: inout MutableSpan<Int32>?)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @lifetime(p: copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func returnLifetimeBound(_ len1: Int32, _ p: inout MutableSpan<Int32>) -> MutableSpan<Int32>
Expand All @@ -29,9 +31,57 @@ import CountedByNoEscapeClang
// CHECK-NEXT: @lifetime(p: copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func swiftAttr(_ p: inout MutableSpan<Int32>)

@lifetime(p: copy p)
@inlinable
public func callComplexExpr(_ p: inout MutableSpan<CInt>) {
complexExpr(CInt(p.count), 1, &p)
}

@lifetime(p: copy p)
@inlinable
public func callNonnull(_ p: inout MutableSpan<CInt>) {
nonnull(&p)
}

@lifetime(p: copy p)
@inlinable
public func callNullUnspecified(_ p: inout MutableSpan<CInt>) {
nullUnspecified(&p)
}

@lifetime(p: copy p)
@inlinable
public func callNullable(_ p: inout MutableSpan<CInt>?) {
nullable(&p)
}

@lifetime(p: copy p)
@inlinable
public func callReturnLifetimeBound(_ p: inout MutableSpan<CInt>) {
let a: MutableSpan<CInt> = returnLifetimeBound(2, &p)
}

@inlinable
public func callReturnPointer() {
let a: UnsafeMutableBufferPointer<CInt>? = returnPointer(4) // call wrapper
let b: UnsafeMutablePointer<CInt>? = returnPointer(4) // call unsafe interop
}

@lifetime(p: copy p)
@lifetime(p2: copy p2)
@inlinable
public func callShared(_ p: inout MutableSpan<CInt>, _ p2: inout MutableSpan<CInt>) {
shared(CInt(p.count), &p, &p2)
}

@lifetime(p: copy p)
@inlinable
public func callSimple(_ p: inout MutableSpan<CInt>) {
simple(&p)
}

@lifetime(p: copy p)
@inlinable
public func callSwiftAttr(_ p: inout MutableSpan<CInt>) {
swiftAttr(&p)
}
35 changes: 35 additions & 0 deletions test/Interop/C/swiftify-import/counted-by.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,43 @@ import CountedByClang
// CHECK-NEXT: @_alwaysEmitIntoClient public func simple(_ p: UnsafeMutableBufferPointer<Int{{.*}}>)
// CHECK-NEXT: @_alwaysEmitIntoClient public func swiftAttr(_ p: UnsafeMutableBufferPointer<Int{{.*}}>)

@inlinable
public func callComplexExpr(_ p: UnsafeMutableBufferPointer<CInt>) {
complexExpr(CInt(p.count), 1, p)
}

@inlinable
public func callNonnull(_ p: UnsafeMutableBufferPointer<CInt>) {
nonnull(p)
}

@inlinable
public func callNullUnspecified(_ p: UnsafeMutableBufferPointer<CInt>) {
nullUnspecified(p)
}

@inlinable
public func callNullable(_ p: UnsafeMutableBufferPointer<CInt>?) {
nullable(p)
}

@inlinable
public func callReturnPointer() {
let a: UnsafeMutableBufferPointer<CInt>? = returnPointer(4) // call wrapper
let b: UnsafeMutablePointer<CInt>? = returnPointer(4) // call unsafe interop
}

@inlinable
public func callShared(_ p: UnsafeMutableBufferPointer<CInt>, _ p2: UnsafeMutableBufferPointer<CInt>) {
shared(CInt(p.count), p, p2)
}

@inlinable
public func callSimple(_ p: UnsafeMutableBufferPointer<CInt>) {
simple(p)
}

@inlinable
public func callSwiftAttr(_ p: UnsafeMutableBufferPointer<CInt>) {
swiftAttr(p)
}
42 changes: 42 additions & 0 deletions test/Interop/C/swiftify-import/sized-by-noescape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,50 @@ import SizedByNoEscapeClang
// CHECK: @_alwaysEmitIntoClient public func complexExpr(_ len: Int{{.*}}, _ offset: Int{{.*}}, _ p: RawSpan)
// CHECK-NEXT: @_alwaysEmitIntoClient public func nonnull(_ p: RawSpan)
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullUnspecified(_ p: RawSpan)
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullable(_ p: RawSpan?)
// CHECK-NEXT: @_alwaysEmitIntoClient public func opaque(_ p: RawSpan)
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func returnPointer(_ len: Int{{.*}}) -> UnsafeRawBufferPointer
// CHECK-NEXT: @_alwaysEmitIntoClient public func shared(_ len: Int{{.*}}, _ p1: RawSpan, _ p2: RawSpan)
// CHECK-NEXT: @_alwaysEmitIntoClient public func simple(_ p: RawSpan)
// CHECK-NEXT: @_alwaysEmitIntoClient public func swiftAttr(_ p: RawSpan)

@inlinable
public func callComplexExpr(_ p: RawSpan) {
complexExpr(CInt(p.byteCount), 1, p)
}

@inlinable
public func callNonnull(_ p: RawSpan) {
nonnull(p)
}

@inlinable
public func callNullUnspecified(_ p: RawSpan) {
nullUnspecified(p)
}

@inlinable
public func callNullable(_ p: RawSpan?) {
nullable(p)
}

@inlinable
public func callReturnPointer() {
let a: UnsafeRawBufferPointer? = returnPointer(4) // call wrapper
let b: UnsafeRawPointer? = returnPointer(4) // call unsafe interop
}

@inlinable
public func callShared(_ p: RawSpan, _ p2: RawSpan) {
shared(CInt(p.byteCount), p, p2)
}

@inlinable
public func callSimple(_ p: RawSpan) {
simple(p)
}

@inlinable
public func callSwiftAttr(_ p: RawSpan) {
swiftAttr(p)
}
45 changes: 45 additions & 0 deletions test/Interop/C/swiftify-import/sized-by.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,48 @@ import SizedByClang
// CHECK-NEXT: @_alwaysEmitIntoClient public func simple(_ p: UnsafeMutableRawBufferPointer)
// CHECK-NEXT: @_alwaysEmitIntoClient public func swiftAttr(_ p: UnsafeMutableRawBufferPointer)

@inlinable
public func callComplexExpr(_ p: UnsafeMutableRawBufferPointer) {
complexExpr(CInt(p.count), 1, p)
}

@inlinable
public func callNonnull(_ p: UnsafeMutableRawBufferPointer) {
nonnull(p)
}

@inlinable
public func callNullUnspecified(_ p: UnsafeMutableRawBufferPointer) {
nullUnspecified(p)
}

@inlinable
public func callNullable(_ p: UnsafeMutableRawBufferPointer?) {
nullable(p)
}

@inlinable
public func callOpaque(_ p: UnsafeRawBufferPointer) {
opaque(p)
}

@inlinable
public func callReturnPointer() {
let a: UnsafeMutableRawBufferPointer? = returnPointer(4) // call wrapper
let b: UnsafeMutableRawPointer? = returnPointer(4) // call unsafe interop
}

@inlinable
public func callShared(_ p: UnsafeMutableRawBufferPointer, _ p2: UnsafeMutableRawBufferPointer) {
shared(CInt(p.count), p, p2)
}

@inlinable
public func callSimple(_ p: UnsafeMutableRawBufferPointer) {
simple(p)
}

@inlinable
public func callSwiftAttr(_ p: UnsafeMutableRawBufferPointer) {
swiftAttr(p)
}
2 changes: 1 addition & 1 deletion test/Macros/SwiftifyImport/CountedBy/MutableSpan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ func myFunc(_ ptr: UnsafeMutablePointer<CInt>, _ len: CInt) {
// CHECK: @_alwaysEmitIntoClient @lifetime(ptr: copy ptr)
// CHECK-NEXT: func myFunc(_ ptr: inout MutableSpan<CInt>) {
// CHECK-NEXT: return unsafe ptr.withUnsafeMutableBufferPointer { _ptrPtr in
// CHECK-NEXT: return unsafe myFunc(_ptrPtr.baseAddress!, CInt(exactly: ptr.count)!)
// CHECK-NEXT: return unsafe myFunc(_ptrPtr.baseAddress!, CInt(exactly: _ptrPtr.count)!)
// CHECK-NEXT: }
// CHECK-NEXT: }
8 changes: 4 additions & 4 deletions test/Macros/SwiftifyImport/CountedBy/Nullable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func myFunc4(_ ptr: UnsafeMutablePointer<CInt>?, _ len: CInt) -> UnsafeMutablePo
// CHECK-NEXT: unsafe myFunc2(nil, CInt(exactly: ptr?.count ?? 0)!)
// CHECK-NEXT: } else {
// CHECK-NEXT: unsafe ptr!.withUnsafeMutableBufferPointer { _ptrPtr in
// CHECK-NEXT: return unsafe myFunc2(_ptrPtr.baseAddress, CInt(exactly: ptr?.count ?? 0)!)
// CHECK-NEXT: return unsafe myFunc2(_ptrPtr.baseAddress, CInt(exactly: _ptrPtr.count)!)
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: }()
Expand All @@ -45,18 +45,18 @@ func myFunc4(_ ptr: UnsafeMutablePointer<CInt>?, _ len: CInt) -> UnsafeMutablePo
// CHECK-NEXT: unsafe myFunc3(nil, CInt(exactly: ptr?.count ?? 0)!, nil, CInt(exactly: ptr2?.count ?? 0)!)
// CHECK-NEXT: } else {
// CHECK-NEXT: unsafe ptr!.withUnsafeMutableBufferPointer { _ptrPtr in
// CHECK-NEXT: return unsafe myFunc3(_ptrPtr.baseAddress, CInt(exactly: ptr?.count ?? 0)!, nil, CInt(exactly: ptr2?.count ?? 0)!)
// CHECK-NEXT: return unsafe myFunc3(_ptrPtr.baseAddress, CInt(exactly: _ptrPtr.count)!, nil, CInt(exactly: ptr2?.count ?? 0)!)
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: }()
// CHECK-NEXT: } else {
// CHECK-NEXT: unsafe ptr2!.withUnsafeMutableBufferPointer { _ptr2Ptr in
// CHECK-NEXT: return { () in
// CHECK-NEXT: return if ptr == nil {
// CHECK-NEXT: unsafe myFunc3(nil, CInt(exactly: ptr?.count ?? 0)!, _ptr2Ptr.baseAddress, CInt(exactly: ptr2?.count ?? 0)!)
// CHECK-NEXT: unsafe myFunc3(nil, CInt(exactly: ptr?.count ?? 0)!, _ptr2Ptr.baseAddress, CInt(exactly: _ptr2Ptr.count)!)
// CHECK-NEXT: } else {
// CHECK-NEXT: unsafe ptr!.withUnsafeMutableBufferPointer { _ptrPtr in
// CHECK-NEXT: return unsafe myFunc3(_ptrPtr.baseAddress, CInt(exactly: ptr?.count ?? 0)!, _ptr2Ptr.baseAddress, CInt(exactly: ptr2?.count ?? 0)!)
// CHECK-NEXT: return unsafe myFunc3(_ptrPtr.baseAddress, CInt(exactly: _ptrPtr.count)!, _ptr2Ptr.baseAddress, CInt(exactly: _ptr2Ptr.count)!)
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: }()
Expand Down
8 changes: 4 additions & 4 deletions test/Macros/SwiftifyImport/CountedBy/PointerReturn.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ func lifetimeDependentBorrow(_ p: borrowing UnsafePointer<CInt>, _ len1: CInt, _

// CHECK: @_alwaysEmitIntoClient @lifetime(copy p)
// CHECK-NEXT: func lifetimeDependentCopy(_ p: Span<CInt>, _ len2: CInt) -> Span<CInt> {
// CHECK-NEXT: return unsafe Span<CInt> (_unsafeStart: unsafe p.withUnsafeBufferPointer { _pPtr in
// CHECK-NEXT: return unsafe lifetimeDependentCopy(_pPtr.baseAddress!, CInt(exactly: p.count)!, len2)
// CHECK-NEXT: }, count: Int(len2))
// CHECK-NEXT: return unsafe _swiftifyOverrideLifetime(Span<CInt> (_unsafeStart: unsafe p.withUnsafeBufferPointer { _pPtr in
// CHECK-NEXT: return unsafe lifetimeDependentCopy(_pPtr.baseAddress!, CInt(exactly: _pPtr.count)!, len2)
// CHECK-NEXT: }, count: Int(len2)), copying: ())
// CHECK-NEXT: }

// CHECK: @_alwaysEmitIntoClient @lifetime(borrow p)
// CHECK-NEXT: func lifetimeDependentBorrow(_ p: borrowing UnsafeBufferPointer<CInt>, _ len2: CInt) -> Span<CInt> {
// CHECK-NEXT: return unsafe Span<CInt> (_unsafeStart: unsafe lifetimeDependentBorrow(p.baseAddress!, CInt(exactly: p.count)!, len2), count: Int(len2))
// CHECK-NEXT: return unsafe _swiftifyOverrideLifetime(Span<CInt> (_unsafeStart: unsafe lifetimeDependentBorrow(p.baseAddress!, CInt(exactly: p.count)!, len2), count: Int(len2)), copying: ())
// CHECK-NEXT: }

2 changes: 1 addition & 1 deletion test/Macros/SwiftifyImport/CountedBy/SimpleSpan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ func myFunc(_ ptr: UnsafePointer<CInt>, _ len: CInt) {
// CHECK: @_alwaysEmitIntoClient
// CHECK-NEXT: func myFunc(_ ptr: Span<CInt>) {
// CHECK-NEXT: return unsafe ptr.withUnsafeBufferPointer { _ptrPtr in
// CHECK-NEXT: return unsafe myFunc(_ptrPtr.baseAddress!, CInt(exactly: ptr.count)!)
// CHECK-NEXT: return unsafe myFunc(_ptrPtr.baseAddress!, CInt(exactly: _ptrPtr.count)!)
// CHECK-NEXT: }
// CHECK-NEXT: }
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ func myFunc(_ ptr: UnsafePointer<CInt>, _ len: CInt) -> CInt {
// CHECK: @_alwaysEmitIntoClient
// CHECK-NEXT: func myFunc(_ ptr: Span<CInt>) -> CInt {
// CHECK-NEXT: return unsafe ptr.withUnsafeBufferPointer { _ptrPtr in
// CHECK-NEXT: return unsafe myFunc(_ptrPtr.baseAddress!, CInt(exactly: ptr.count)!)
// CHECK-NEXT: return unsafe myFunc(_ptrPtr.baseAddress!, CInt(exactly: _ptrPtr.count)!)
// CHECK-NEXT: }
// CHECK-NEXT: }
Loading