Skip to content
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
40 changes: 22 additions & 18 deletions lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,9 @@ extension PointerBoundsThunkBuilder {
return try transformType(oldType, generateSpan, isSizedBy, isParameter)
}
}
var countLabel: String {
return isSizedBy && generateSpan ? "byteCount" : "count"
}
}

protocol ParamBoundsThunkBuilder: BoundsThunkBuilder {
Expand Down Expand Up @@ -699,26 +702,28 @@ struct CountedOrSizedReturnPointerThunkBuilder: PointerBoundsThunkBuilder {
"start"
}
var cast = try newType
var expr: ExprSyntax
if nullable {
if let optType = cast.as(OptionalTypeSyntax.self) {
cast = optType.wrappedType
}
return """
{ () in
let _resultValue = \(call)
if unsafe _resultValue == nil {
return nil
} else {
return unsafe \(raw: cast)(\(raw: startLabel): _resultValue!, count: Int(\(countExpr)))
}
}()
"""
expr =
"""
{ () in
let _resultValue = \(call)
if unsafe _resultValue == nil {
return nil
} else {
return unsafe _swiftifyOverrideLifetime(\(raw: cast)(\(raw: startLabel): _resultValue!, \(raw: countLabel): Int(\(countExpr))), copying: ())
}
}()
"""
} else {
expr =
"""
\(raw: cast)(\(raw: startLabel): \(call), \(raw: countLabel): Int(\(countExpr)))
"""
}
var expr = ExprSyntax(
"""
\(raw: cast)(\(raw: startLabel): \(call), count: Int(\(countExpr)))
"""
)
if generateSpan {
expr = "_swiftifyOverrideLifetime(\(expr), copying: ())"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't think result in two calls to _swiftifyOverrideLifetime(), one inside the other? Is that necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is necessary until we change the macro to not use immediately called closures for nullable pointers, because we need it each time a Span is returned from a function (which it technically is in the closure)

}
Expand Down Expand Up @@ -823,11 +828,10 @@ struct CountedOrSizedPointerThunkBuilder: ParamBoundsThunkBuilder, PointerBounds
}

func getCount() -> ExprSyntax {
let countName = isSizedBy && generateSpan ? "byteCount" : "count"
if nullable {
return ExprSyntax("\(name)?.\(raw: countName) ?? 0")
return ExprSyntax("\(name)?.\(raw: countLabel) ?? 0")
}
return ExprSyntax("\(name).\(raw: countName)")
return ExprSyntax("\(name).\(raw: countLabel)")
}

func peelOptionalType(_ type: TypeSyntax) -> TypeSyntax {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#define __counted_by(x) __attribute__((__counted_by__(x)))
#define __lifetimebound __attribute__((lifetimebound))

int * __counted_by(len) simple(int len, int len2, int * __counted_by(len2) __lifetimebound p);

int * __counted_by(len) shared(int len, int * __counted_by(len) __lifetimebound p);

int * __counted_by(len - offset) complexExpr(int len, int offset, int len2, int * __counted_by(len2) __lifetimebound p);

int * __counted_by(len) _Null_unspecified nullUnspecified(int len, int len2, int * __counted_by(len2) __lifetimebound _Null_unspecified p);

int * __counted_by(len) _Nonnull nonnull(int len, int len2, int * __counted_by(len2) __lifetimebound _Nonnull p);

int * __counted_by(len) _Nullable nullable(int len, int len2, int * __counted_by(len2) __lifetimebound _Nullable p);

typedef struct foo opaque_t;
opaque_t * __counted_by(len) opaque(int len, int len2, opaque_t * __counted_by(len2) __lifetimebound p);

int * __counted_by(len) noncountedLifetime(int len, int * __lifetimebound p);
8 changes: 8 additions & 0 deletions test/Interop/C/swiftify-import/Inputs/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ module SizedByNoEscapeClang {
header "sized-by-noescape.h"
export *
}
module SizedByLifetimeboundClang {
header "sized-by-lifetimebound.h"
export *
}
module CountedByLifetimeboundClang {
header "counted-by-lifetimebound.h"
export *
}
module CommentsClang {
header "comments.h"
export *
Expand Down
21 changes: 21 additions & 0 deletions test/Interop/C/swiftify-import/Inputs/sized-by-lifetimebound.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#define __sized_by(x) __attribute__((__sized_by__(x)))
#define __lifetimebound __attribute__((lifetimebound))

const void * __sized_by(len) simple(int len, int len2, const void * __sized_by(len2) __lifetimebound p);

const void * __sized_by(len) shared(int len, const void * __sized_by(len) __lifetimebound p);

const void * __sized_by(len - offset) complexExpr(int len, int offset, int len2, const void * __sized_by(len2) __lifetimebound p);

const void * __sized_by(len) _Null_unspecified nullUnspecified(int len, int len2, const void * __sized_by(len2) __lifetimebound _Null_unspecified p);

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

const void * __sized_by(len) _Nullable nullable(int len, int len2, const void * __sized_by(len2) __lifetimebound _Nullable p);

typedef struct foo opaque_t;
opaque_t * __sized_by(len) opaque(int len, int len2, opaque_t * __sized_by(len2) __lifetimebound p);

const void * __sized_by(len) nonsizedLifetime(int len, const void * __lifetimebound p);
96 changes: 96 additions & 0 deletions test/Interop/C/swiftify-import/counted-by-lifetimebound.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// REQUIRES: swift_feature_SafeInteropWrappers
// REQUIRES: swift_feature_LifetimeDependence

// RUN: %target-swift-ide-test -print-module -module-to-print=CountedByLifetimeboundClang -plugin-path %swift-plugin-dir -I %S/Inputs -source-filename=x -enable-experimental-feature SafeInteropWrappers | %FileCheck %s

// 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/CountedByLifetimebound.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 __sized_by __lifetimebound parameters and return values.

import CountedByLifetimeboundClang

// CHECK: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @lifetime(p: copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func complexExpr(_ len: Int32, _ offset: Int32, _ len2: Int32, _ p: inout MutableSpan<Int32>) -> MutableSpan<Int32>

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(borrow p)
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func noncountedLifetime(_ len: Int32, _ p: UnsafeMutablePointer<Int32>!) -> MutableSpan<Int32>

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @lifetime(p: copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func nonnull(_ len: Int32, _ p: inout MutableSpan<Int32>) -> MutableSpan<Int32>

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @lifetime(p: copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullUnspecified(_ len: Int32, _ p: inout MutableSpan<Int32>) -> MutableSpan<Int32>

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @lifetime(p: copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullable(_ len: Int32, _ p: inout MutableSpan<Int32>?) -> MutableSpan<Int32>?

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @lifetime(p: copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func shared(_ len: Int32, _ p: inout MutableSpan<Int32>) -> MutableSpan<Int32>

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @lifetime(p: copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func simple(_ len: Int32, _ p: inout MutableSpan<Int32>) -> MutableSpan<Int32>


@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callComplexExpr(_ p: inout MutableSpan<CInt>) {
let _: MutableSpan<CInt> = complexExpr(73, 37, 42, &p)
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callNonnull(_ p: inout MutableSpan<CInt>) {
let _: MutableSpan<CInt> = nonnull(73, &p)
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callNullUnspecified(_ p: inout MutableSpan<CInt>) {
let _: MutableSpan<CInt> = nullUnspecified(73, &p)
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callNullable(_ p: inout MutableSpan<CInt>?) {
let _: MutableSpan<CInt> = nullable(73, &p)!
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callShared(_ p: inout MutableSpan<CInt>) {
let _: MutableSpan<CInt> = shared(CInt(p.count), &p)
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callSimple(_ p: inout MutableSpan<CInt>) {
let _: MutableSpan<CInt> = simple(73, &p)
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callNoncountedLifetime(_ p: UnsafeMutablePointer<CInt>) {
let _: MutableSpan<CInt> = noncountedLifetime(73, p)
}
95 changes: 95 additions & 0 deletions test/Interop/C/swiftify-import/sized-by-lifetimebound.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// REQUIRES: swift_feature_SafeInteropWrappers
// REQUIRES: swift_feature_LifetimeDependence

// RUN: %target-swift-ide-test -print-module -module-to-print=SizedByLifetimeboundClang -plugin-path %swift-plugin-dir -I %S/Inputs -source-filename=x -enable-experimental-feature SafeInteropWrappers | %FileCheck %s

// 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/SizedByLifetimebound.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 __sized_by __lifetimebound parameters and return values.

import SizedByLifetimeboundClang

// CHECK: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func complexExpr(_ len: Int32, _ offset: Int32, _ len2: Int32, _ p: RawSpan) -> RawSpan

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func nonnull(_ len: Int32, _ p: RawSpan) -> RawSpan

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(borrow p)
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func nonsizedLifetime(_ len: Int32, _ p: UnsafeRawPointer!) -> RawSpan

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullUnspecified(_ len: Int32, _ p: RawSpan) -> RawSpan

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullable(_ len: Int32, _ p: RawSpan?) -> RawSpan?

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func opaque(_ len: Int32, _ p: RawSpan) -> RawSpan

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func shared(_ len: Int32, _ p: RawSpan) -> RawSpan

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func simple(_ len: Int32, _ p: RawSpan) -> RawSpan


@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callComplexExpr(_ p: RawSpan) {
let _: RawSpan = complexExpr(73, 37, 42, p)
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callNonnull(_ p: RawSpan) {
let _: RawSpan = nonnull(73, p)
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callNullUnspecified(_ p: RawSpan) {
let _: RawSpan = nullUnspecified(73, p)
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callNullable(_ p: RawSpan?) {
let _: RawSpan = nullable(73, p)!
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callShared(_ p: RawSpan) {
let _: RawSpan = shared(CInt(p.byteCount), p)
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callSimple(_ p: RawSpan) {
let _: RawSpan = simple(73, p)
}

@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
@inlinable
public func callNonsizedLifetime(_ p: UnsafeRawPointer) {
let _: RawSpan = nonsizedLifetime(73, p)
}
8 changes: 4 additions & 4 deletions test/Macros/SwiftifyImport/CountedBy/Nullable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func myFunc4(_ ptr: UnsafeMutablePointer<CInt>?, _ len: CInt) -> UnsafeMutablePo
// CHECK-NEXT: if ptr?.count ?? 0 < _ptrCount || _ptrCount < 0 {
// CHECK-NEXT: fatalError("bounds check failure when calling unsafe function")
// CHECK-NEXT: }
// CHECK-NEXT: return { () in
// CHECK-NEXT: return unsafe _swiftifyOverrideLifetime({ () in
// CHECK-NEXT: let _resultValue = { () in
// CHECK-NEXT: return if ptr == nil {
// CHECK-NEXT: unsafe myFunc4(nil, len)
Expand All @@ -84,7 +84,7 @@ func myFunc4(_ ptr: UnsafeMutablePointer<CInt>?, _ len: CInt) -> UnsafeMutablePo
// CHECK-NEXT: if unsafe _resultValue == nil {
// CHECK-NEXT: return nil
// CHECK-NEXT: } else {
// CHECK-NEXT: return unsafe MutableSpan<CInt>(_unsafeStart: _resultValue!, count: Int(len))
// CHECK-NEXT: return unsafe _swiftifyOverrideLifetime(MutableSpan<CInt>(_unsafeStart: _resultValue!, count: Int(len)), copying: ())
// CHECK-NEXT: }
// CHECK-NEXT: }()
// CHECK-NEXT: }
// CHECK-NEXT: }(), copying: ())
// CHECK-NEXT: }
Loading