Skip to content

Commit 4005306

Browse files
committed
[SwiftifyImport] Add sizedBy support for OpaquePointer
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.
1 parent ef9d2b7 commit 4005306

File tree

7 files changed

+225
-8
lines changed

7 files changed

+225
-8
lines changed

lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,22 @@ func getPointerMutability(text: String) -> Mutability? {
131131
case "UnsafeMutablePointer": return .Mutable
132132
case "UnsafeRawPointer": return .Immutable
133133
case "UnsafeMutableRawPointer": return .Mutable
134+
case "OpaquePointer": return .Immutable
134135
default:
135136
return nil
136137
}
137138
}
138139

140+
func isRawPointerType(text: String) -> Bool {
141+
switch text {
142+
case "UnsafeRawPointer": return true
143+
case "UnsafeMutableRawPointer": return true
144+
case "OpaquePointer": return true
145+
default:
146+
return false
147+
}
148+
}
149+
139150
func getSafePointerName(mut: Mutability, generateSpan: Bool, isRaw: Bool) -> TokenSyntax {
140151
switch (mut, generateSpan, isRaw) {
141152
case (.Immutable, true, true): return "RawSpan"
@@ -160,9 +171,13 @@ func transformType(_ prev: TypeSyntax, _ variant: Variant, _ isSizedBy: Bool) th
160171
}
161172
let name = try getTypeName(prev)
162173
let text = name.text
163-
if !isSizedBy && (text == "UnsafeRawPointer" || text == "UnsafeMutableRawPointer") {
174+
let isRaw = isRawPointerType(text: text)
175+
if isRaw && !isSizedBy {
164176
throw DiagnosticError("raw pointers only supported for SizedBy", node: name)
165177
}
178+
if !isRaw && isSizedBy {
179+
throw DiagnosticError("SizedBy only supported for raw pointers", node: name)
180+
}
166181

167182
guard let kind: Mutability = getPointerMutability(text: text) else {
168183
throw DiagnosticError(
@@ -331,7 +346,7 @@ struct CountedOrSizedPointerThunkBuilder: PointerBoundsThunkBuilder {
331346
var args = argOverrides
332347
let argExpr = ExprSyntax("\(unwrappedName).baseAddress")
333348
assert(args[index] == nil)
334-
args[index] = unwrapIfNonnullable(argExpr)
349+
args[index] = try castPointerToTargetType(unwrapIfNonnullable(argExpr))
335350
let call = try base.buildFunctionCall(args, variant)
336351
let ptrRef = unwrapIfNullable(ExprSyntax(DeclReferenceExprSyntax(baseName: name)))
337352

@@ -353,7 +368,26 @@ struct CountedOrSizedPointerThunkBuilder: PointerBoundsThunkBuilder {
353368
return ExprSyntax("\(name).\(raw: countName)")
354369
}
355370

356-
func getPointerArg() -> ExprSyntax {
371+
func peelOptionalType(_ type: TypeSyntax) -> TypeSyntax {
372+
if let optType = type.as(OptionalTypeSyntax.self) {
373+
return optType.wrappedType
374+
}
375+
if let impOptType = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) {
376+
return impOptType.wrappedType
377+
}
378+
return type
379+
}
380+
381+
func castPointerToTargetType(_ baseAddress: ExprSyntax) throws -> ExprSyntax {
382+
let i = try getParameterIndexForParamName(signature.parameterClause.parameters, name)
383+
let type = peelOptionalType(getParam(signature, i).type)
384+
if type.canRepresentBasicType(type: OpaquePointer.self) {
385+
return ExprSyntax("OpaquePointer(\(baseAddress))")
386+
}
387+
return baseAddress
388+
}
389+
390+
func getPointerArg() throws -> ExprSyntax {
357391
if nullable {
358392
return ExprSyntax("\(name)?.baseAddress")
359393
}
@@ -391,7 +425,7 @@ struct CountedOrSizedPointerThunkBuilder: PointerBoundsThunkBuilder {
391425
return unwrappedCall
392426
}
393427

394-
args[index] = getPointerArg()
428+
args[index] = try castPointerToTargetType(getPointerArg())
395429
return try base.buildFunctionCall(args, variant)
396430
}
397431
}
@@ -440,22 +474,28 @@ func getOptionalArgumentByName(_ argumentList: LabeledExprListSyntax, _ name: St
440474
})?.expression
441475
}
442476

443-
func getParameterIndexForDeclRef(
444-
_ parameterList: FunctionParameterListSyntax, _ ref: DeclReferenceExprSyntax
477+
func getParameterIndexForParamName(
478+
_ parameterList: FunctionParameterListSyntax, _ tok: TokenSyntax
445479
) throws -> Int {
446-
let name = ref.baseName.text
480+
let name = tok.text
447481
guard
448482
let index = parameterList.enumerated().first(where: {
449483
(_: Int, param: FunctionParameterSyntax) in
450484
let paramenterName = param.secondName ?? param.firstName
451485
return paramenterName.trimmed.text == name
452486
})?.offset
453487
else {
454-
throw DiagnosticError("no parameter with name '\(name)' in '\(parameterList)'", node: ref)
488+
throw DiagnosticError("no parameter with name '\(name)' in '\(parameterList)'", node: tok)
455489
}
456490
return index
457491
}
458492

493+
func getParameterIndexForDeclRef(
494+
_ parameterList: FunctionParameterListSyntax, _ ref: DeclReferenceExprSyntax
495+
) throws -> Int {
496+
return try getParameterIndexForParamName((parameterList), ref.baseName)
497+
}
498+
459499
/// A macro that adds safe(r) wrappers for functions with unsafe pointer types.
460500
/// Depends on bounds, escapability and lifetime information for each pointer.
461501
/// 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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// REQUIRES: swift_feature_SafeInteropWrappers
2+
3+
// 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
5+
6+
// Check that ClangImporter correctly infers and expands @_SwiftifyImport macros for functions with __sized_by parameters.
7+
8+
import SizedByClang
9+
10+
@inlinable
11+
public func callSimple(_ p: UnsafeMutableRawBufferPointer) {
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: UnsafeMutableRawBufferPointer) {
18+
swiftAttr(p)
19+
}
20+
21+
@inlinable
22+
public func callShared(_ len: CInt, _ p1: UnsafeMutableRawBufferPointer, _ p2: UnsafeMutableRawBufferPointer) {
23+
shared(len, p1, p2)
24+
}
25+
26+
@inlinable
27+
public func callComplexExpr(_ len: CInt, _ offset: CInt, _ p: UnsafeMutableRawBufferPointer) {
28+
complexExpr(len, offset, p)
29+
}
30+
31+
32+
@inlinable
33+
public func callNullUnspecified(_ p: UnsafeMutableRawBufferPointer) {
34+
nullUnspecified(p)
35+
}
36+
37+
@inlinable
38+
public func callNonnull(_ p: UnsafeMutableRawBufferPointer) {
39+
nonnull(p)
40+
}
41+
42+
@inlinable
43+
public func callNullable(_ p: UnsafeMutableRawBufferPointer?) {
44+
nullable(p)
45+
}
46+
47+
@inlinable
48+
public func callOpaque(_ p: UnsafeRawBufferPointer) {
49+
opaque(p)
50+
}
51+
52+
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
5+
6+
// XFAIL: *
7+
// expanded form errors with "type 'RawSpan' 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)