Skip to content

Commit e252cbb

Browse files
authored
[SwiftifyImport] Add sizedBy support for OpaquePointer (#78315)
This makes it possible to mark a pointer with __sized_by when the pointee type definition is not included. The wrapper function has the same interface as if the parameter were a void pointer, since the stdlib has no `OpaqueBufferPointer` type. * use swift-ide-test for checking interop signatures * add xfail test for Span + Optional combo (Optional requires Escapable)
1 parent 8deac92 commit e252cbb

File tree

8 files changed

+204
-46
lines changed

8 files changed

+204
-46
lines changed

lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,22 @@ func getPointerMutability(text: String) -> Mutability? {
151151
case "UnsafeMutablePointer": return .Mutable
152152
case "UnsafeRawPointer": return .Immutable
153153
case "UnsafeMutableRawPointer": return .Mutable
154+
case "OpaquePointer": return .Immutable
154155
default:
155156
return nil
156157
}
157158
}
158159

160+
func isRawPointerType(text: String) -> Bool {
161+
switch text {
162+
case "UnsafeRawPointer": return true
163+
case "UnsafeMutableRawPointer": return true
164+
case "OpaquePointer": return true
165+
default:
166+
return false
167+
}
168+
}
169+
159170
func getSafePointerName(mut: Mutability, generateSpan: Bool, isRaw: Bool) -> TokenSyntax {
160171
switch (mut, generateSpan, isRaw) {
161172
case (.Immutable, true, true): return "RawSpan"
@@ -180,9 +191,13 @@ func transformType(_ prev: TypeSyntax, _ variant: Variant, _ isSizedBy: Bool) th
180191
}
181192
let name = try getTypeName(prev)
182193
let text = name.text
183-
if !isSizedBy && (text == "UnsafeRawPointer" || text == "UnsafeMutableRawPointer") {
194+
let isRaw = isRawPointerType(text: text)
195+
if isRaw && !isSizedBy {
184196
throw DiagnosticError("raw pointers only supported for SizedBy", node: name)
185197
}
198+
if !isRaw && isSizedBy {
199+
throw DiagnosticError("SizedBy only supported for raw pointers", node: name)
200+
}
186201

187202
guard let kind: Mutability = getPointerMutability(text: text) else {
188203
throw DiagnosticError(
@@ -390,7 +405,7 @@ struct CountedOrSizedPointerThunkBuilder: PointerBoundsThunkBuilder {
390405
var args = argOverrides
391406
let argExpr = ExprSyntax("\(unwrappedName).baseAddress")
392407
assert(args[index] == nil)
393-
args[index] = unwrapIfNonnullable(argExpr)
408+
args[index] = try castPointerToOpaquePointer(unwrapIfNonnullable(argExpr))
394409
let call = try base.buildFunctionCall(args, variant)
395410
let ptrRef = unwrapIfNullable(ExprSyntax(DeclReferenceExprSyntax(baseName: name)))
396411

@@ -412,7 +427,26 @@ struct CountedOrSizedPointerThunkBuilder: PointerBoundsThunkBuilder {
412427
return ExprSyntax("\(name).\(raw: countName)")
413428
}
414429

415-
func getPointerArg() -> ExprSyntax {
430+
func peelOptionalType(_ type: TypeSyntax) -> TypeSyntax {
431+
if let optType = type.as(OptionalTypeSyntax.self) {
432+
return optType.wrappedType
433+
}
434+
if let impOptType = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) {
435+
return impOptType.wrappedType
436+
}
437+
return type
438+
}
439+
440+
func castPointerToOpaquePointer(_ baseAddress: ExprSyntax) throws -> ExprSyntax {
441+
let i = try getParameterIndexForParamName(signature.parameterClause.parameters, name)
442+
let type = peelOptionalType(getParam(signature, i).type)
443+
if type.canRepresentBasicType(type: OpaquePointer.self) {
444+
return ExprSyntax("OpaquePointer(\(baseAddress))")
445+
}
446+
return baseAddress
447+
}
448+
449+
func getPointerArg() throws -> ExprSyntax {
416450
if nullable {
417451
return ExprSyntax("\(name)?.baseAddress")
418452
}
@@ -450,7 +484,7 @@ struct CountedOrSizedPointerThunkBuilder: PointerBoundsThunkBuilder {
450484
return unwrappedCall
451485
}
452486

453-
args[index] = getPointerArg()
487+
args[index] = try castPointerToOpaquePointer(getPointerArg())
454488
return try base.buildFunctionCall(args, variant)
455489
}
456490
}
@@ -499,22 +533,28 @@ func getOptionalArgumentByName(_ argumentList: LabeledExprListSyntax, _ name: St
499533
})?.expression
500534
}
501535

502-
func getParameterIndexForDeclRef(
503-
_ parameterList: FunctionParameterListSyntax, _ ref: DeclReferenceExprSyntax
536+
func getParameterIndexForParamName(
537+
_ parameterList: FunctionParameterListSyntax, _ tok: TokenSyntax
504538
) throws -> Int {
505-
let name = ref.baseName.text
539+
let name = tok.text
506540
guard
507541
let index = parameterList.enumerated().first(where: {
508542
(_: Int, param: FunctionParameterSyntax) in
509543
let paramenterName = param.secondName ?? param.firstName
510544
return paramenterName.trimmed.text == name
511545
})?.offset
512546
else {
513-
throw DiagnosticError("no parameter with name '\(name)' in '\(parameterList)'", node: ref)
547+
throw DiagnosticError("no parameter with name '\(name)' in '\(parameterList)'", node: tok)
514548
}
515549
return index
516550
}
517551

552+
func getParameterIndexForDeclRef(
553+
_ parameterList: FunctionParameterListSyntax, _ ref: DeclReferenceExprSyntax
554+
) throws -> Int {
555+
return try getParameterIndexForParamName((parameterList), ref.baseName)
556+
}
557+
518558
/// A macro that adds safe(r) wrappers for functions with unsafe pointer types.
519559
/// Depends on bounds, escapability and lifetime information for each pointer.
520560
/// Intended to map to C attributes like __counted_by, __ended_by and __no_escape,

test/Interop/C/swiftify-import/Inputs/module.modulemap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@ module CountedByClang {
22
header "counted-by.h"
33
export *
44
}
5+
module SizedByClang {
6+
header "sized-by.h"
7+
export *
8+
}
59

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#define __sized_by(x) __attribute__((__sized_by__(x)))
4+
5+
void simple(int len, void * __sized_by(len) p);
6+
7+
void swiftAttr(int len, void *p) __attribute__((
8+
swift_attr("@_SwiftifyImport(.sizedBy(pointer: 2, size: \"len\"))")));
9+
10+
void shared(int len, void * __sized_by(len) p1, void * __sized_by(len) p2);
11+
12+
void complexExpr(int len, int offset, void * __sized_by(len - offset) p);
13+
14+
void nullUnspecified(int len, void * __sized_by(len) _Null_unspecified p);
15+
16+
void nonnull(int len, void * __sized_by(len) _Nonnull p);
17+
18+
void nullable(int len, void * __sized_by(len) _Nullable p);
19+
20+
typedef struct foo opaque_t;
21+
void opaque(int len, opaque_t * __sized_by(len) p);
Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,19 @@
11
// REQUIRES: swift_feature_SafeInteropWrappers
22

3+
// RUN: %target-swift-ide-test -print-module -module-to-print=CountedByClang -plugin-path %swift-plugin-dir -I %S/Inputs -source-filename=x -enable-experimental-feature SafeInteropWrappers | %FileCheck %s
4+
5+
// swift-ide-test doesn't currently typecheck the macro expansions, so run the compiler as well
36
// RUN: %empty-directory(%t)
4-
// RUN: %target-swift-frontend -emit-module -disable-availability-checking -plugin-path %swift-plugin-dir -o %t/CountedBy.swiftmodule -I %S/Inputs -enable-experimental-feature SafeInteropWrappers %s
7+
// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/CountedBy.swiftmodule -I %S/Inputs -enable-experimental-feature SafeInteropWrappers %s
58

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

811
import CountedByClang
912

10-
@inlinable
11-
public func callSimple(_ p: UnsafeMutableBufferPointer<CInt>) {
12-
simple(p)
13-
}
14-
15-
// Check that macros from swift_attr work the same as inferred macros.
16-
@inlinable
17-
public func callSwiftAttr(_ p: UnsafeMutableBufferPointer<CInt>) {
18-
swiftAttr(p)
19-
}
20-
21-
@inlinable
22-
public func callShared(_ len: CInt, _ p1: UnsafeMutableBufferPointer<CInt>, _ p2: UnsafeMutableBufferPointer<CInt>) {
23-
shared(len, p1, p2)
24-
}
25-
26-
@inlinable
27-
public func callComplexExpr(_ len: CInt, _ offset: CInt, _ p: UnsafeMutableBufferPointer<CInt>) {
28-
complexExpr(len, offset, p)
29-
}
30-
31-
32-
@inlinable
33-
public func callNullUnspecified(_ p: UnsafeMutableBufferPointer<CInt>) {
34-
nullUnspecified(p)
35-
}
36-
37-
@inlinable
38-
public func callNonnull(_ p: UnsafeMutableBufferPointer<CInt>) {
39-
nonnull(p)
40-
}
41-
42-
@inlinable
43-
public func callNullable(_ p: UnsafeMutableBufferPointer<CInt>?) {
44-
nullable(p)
45-
}
46-
13+
// CHECK: @_alwaysEmitIntoClient public func complexExpr(_ len: Int{{.*}}, _ offset: Int{{.*}}, _ p: UnsafeMutableBufferPointer<Int{{.*}}>)
14+
// CHECK-NEXT: @_alwaysEmitIntoClient public func nonnull(_ p: UnsafeMutableBufferPointer<Int{{.*}}>)
15+
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullUnspecified(_ p: UnsafeMutableBufferPointer<Int{{.*}}>)
16+
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullable(_ p: UnsafeMutableBufferPointer<Int{{.*}}>?)
17+
// CHECK-NEXT: @_alwaysEmitIntoClient public func shared(_ len: Int{{.*}}, _ p1: UnsafeMutableBufferPointer<Int{{.*}}>, _ p2: UnsafeMutableBufferPointer<Int{{.*}}>)
18+
// CHECK-NEXT: @_alwaysEmitIntoClient public func simple(_ p: UnsafeMutableBufferPointer<Int{{.*}}>)
19+
// CHECK-NEXT: @_alwaysEmitIntoClient public func swiftAttr(_ p: UnsafeMutableBufferPointer<Int{{.*}}>)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// REQUIRES: swift_feature_SafeInteropWrappers
2+
3+
// RUN: %target-swift-ide-test -print-module -module-to-print=SizedByClang -plugin-path %swift-plugin-dir -I %S/Inputs -source-filename=x -enable-experimental-feature SafeInteropWrappers | %FileCheck %s
4+
5+
// swift-ide-test doesn't currently typecheck the macro expansions, so run the compiler as well
6+
// RUN: %empty-directory(%t)
7+
// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/CountedBy.swiftmodule -I %S/Inputs -enable-experimental-feature SafeInteropWrappers %s
8+
9+
// Check that ClangImporter correctly infers and expands @_SwiftifyImport macros for functions with __sized_by parameters.
10+
import SizedByClang
11+
12+
// CHECK: @_alwaysEmitIntoClient public func complexExpr(_ len: Int{{.*}}, _ offset: Int{{.*}}, _ p: UnsafeMutableRawBufferPointer)
13+
// CHECK-NEXT: @_alwaysEmitIntoClient public func nonnull(_ p: UnsafeMutableRawBufferPointer)
14+
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullUnspecified(_ p: UnsafeMutableRawBufferPointer)
15+
// CHECK-NEXT: @_alwaysEmitIntoClient public func nullable(_ p: UnsafeMutableRawBufferPointer?)
16+
// CHECK-NEXT: @_alwaysEmitIntoClient public func opaque(_ p: UnsafeRawBufferPointer)
17+
// CHECK-NEXT: @_alwaysEmitIntoClient public func shared(_ len: Int{{.*}}, _ p1: UnsafeMutableRawBufferPointer, _ p2: UnsafeMutableRawBufferPointer)
18+
// CHECK-NEXT: @_alwaysEmitIntoClient public func simple(_ p: UnsafeMutableRawBufferPointer)
19+
// CHECK-NEXT: @_alwaysEmitIntoClient public func swiftAttr(_ p: UnsafeMutableRawBufferPointer)
20+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// REQUIRES: swift_swift_parser
2+
// REQUIRES: swift_feature_Span
3+
4+
// RUN: %target-typecheck-verify-swift -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify -enable-experimental-feature Span
5+
6+
// XFAIL: *
7+
// expanded form errors with "type 'Span' does not conform to protocol 'Escapable'" because Optional doesn't support ~Escapable yet
8+
@_SwiftifyImport(.countedBy(pointer: 1, count: "len"), .nonescaping(pointer: 1))
9+
func nullableSpan(_ ptr: UnsafePointer<CInt>?, _ len: CInt) {
10+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// REQUIRES: swift_swift_parser
2+
3+
// RUN: %target-typecheck-verify-swift -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify
4+
5+
// expected-error@+2{{SizedBy only supported for raw pointers}}
6+
@_SwiftifyImport(.sizedBy(pointer: 1, size: "size"))
7+
func myFunc(_ ptr: UnsafePointer<Int>, _ size: CInt) {
8+
}
9+
10+
// expected-error@+2{{raw pointers only supported for SizedBy}}
11+
@_SwiftifyImport(.countedBy(pointer: 1, count: "count"))
12+
func myFunc(_ ptr: UnsafeRawPointer, _ count: CInt) {
13+
}
14+
15+
// expected-error@+2{{raw pointers only supported for SizedBy}}
16+
@_SwiftifyImport(.countedBy(pointer: 1, count: "count"))
17+
func myFunc(_ ptr: OpaquePointer, _ count: CInt) {
18+
}
19+
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// REQUIRES: swift_swift_parser
2+
// REQUIRES: swift_feature_Span
3+
4+
// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions -enable-experimental-feature Span -verify 2>&1 | %FileCheck --match-full-lines %s
5+
6+
@_SwiftifyImport(.sizedBy(pointer: 1, size: "size"))
7+
func nonnullUnsafeRawBufferPointer(_ ptr: OpaquePointer, _ size: CInt) {
8+
}
9+
10+
@_SwiftifyImport(.sizedBy(pointer: 1, size: "size"))
11+
func nullableUnsafeRawBufferPointer(_ ptr: OpaquePointer?, _ size: CInt) {
12+
}
13+
14+
@_SwiftifyImport(.sizedBy(pointer: 1, size: "size"))
15+
func impNullableUnsafeRawBufferPointer(_ ptr: OpaquePointer!, _ size: CInt) {
16+
}
17+
18+
@_SwiftifyImport(.sizedBy(pointer: 1, size: "size"), .nonescaping(pointer: 1))
19+
func nonnullSpan(_ ptr: OpaquePointer, _ size: CInt) {
20+
}
21+
22+
// expected-note@+2{{in expansion of macro '_SwiftifyImport' on global function 'nullableSpan' here}}
23+
// Cannot refer to source location for the error: "type 'RawSpan' does not conform to protocol 'Escapable'" (which is currently necessary for Optional)
24+
@_SwiftifyImport(.sizedBy(pointer: 1, size: "size"), .nonescaping(pointer: 1))
25+
func nullableSpan(_ ptr: OpaquePointer?, _ size: CInt) {
26+
}
27+
28+
@_SwiftifyImport(.sizedBy(pointer: 1, size: "size"), .nonescaping(pointer: 1))
29+
func impNullableSpan(_ ptr: OpaquePointer!, _ size: CInt) {
30+
}
31+
32+
// CHECK: @_alwaysEmitIntoClient
33+
// CHECK-NEXT: func nonnullUnsafeRawBufferPointer(_ ptr: UnsafeRawBufferPointer) {
34+
// CHECK-NEXT: return nonnullUnsafeRawBufferPointer(OpaquePointer(ptr.baseAddress!), CInt(exactly: ptr.count)!)
35+
// CHECK-NEXT: }
36+
37+
// CHECK: @_alwaysEmitIntoClient
38+
// CHECK-NEXT: func nullableUnsafeRawBufferPointer(_ ptr: UnsafeRawBufferPointer?) {
39+
// CHECK-NEXT: return nullableUnsafeRawBufferPointer(OpaquePointer(ptr?.baseAddress), CInt(exactly: ptr?.count ?? 0)!)
40+
// CHECK-NEXT: }
41+
42+
// CHECK: @_alwaysEmitIntoClient
43+
// CHECK-NEXT: func impNullableUnsafeRawBufferPointer(_ ptr: UnsafeRawBufferPointer) {
44+
// CHECK-NEXT: return impNullableUnsafeRawBufferPointer(OpaquePointer(ptr.baseAddress!), CInt(exactly: ptr.count)!)
45+
// CHECK-NEXT: }
46+
47+
// CHECK: @_alwaysEmitIntoClient
48+
// CHECK-NEXT: func nonnullSpan(_ ptr: RawSpan) {
49+
// CHECK-NEXT: return ptr.withUnsafeBytes { _ptrPtr in
50+
// CHECK-NEXT: return nonnullSpan(OpaquePointer(_ptrPtr.baseAddress!), CInt(exactly: ptr.byteCount)!)
51+
// CHECK-NEXT: }
52+
// CHECK-NEXT: }
53+
54+
// CHECK: @_alwaysEmitIntoClient
55+
// CHECK-NEXT: func nullableSpan(_ ptr: RawSpan?) {
56+
// CHECK-NEXT: return if ptr == nil {
57+
// CHECK-NEXT: nullableSpan(nil, CInt(exactly: ptr?.byteCount ?? 0)!)
58+
// CHECK-NEXT: } else {
59+
// CHECK-NEXT: ptr!.withUnsafeBytes { _ptrPtr in
60+
// CHECK-NEXT: return nullableSpan(OpaquePointer(_ptrPtr.baseAddress), CInt(exactly: ptr?.byteCount ?? 0)!)
61+
// CHECK-NEXT: }
62+
// CHECK-NEXT: }
63+
// CHECK-NEXT: }
64+
65+
// CHECK: @_alwaysEmitIntoClient
66+
// CHECK-NEXT: func impNullableSpan(_ ptr: RawSpan) {
67+
// CHECK-NEXT: return ptr.withUnsafeBytes { _ptrPtr in
68+
// CHECK-NEXT: return impNullableSpan(OpaquePointer(_ptrPtr.baseAddress!), CInt(exactly: ptr.byteCount)!)
69+
// CHECK-NEXT: }
70+
// CHECK-NEXT: }
71+

0 commit comments

Comments
 (0)