-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Add @PointerBounds macro #76969
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
Add @PointerBounds macro #76969
Conversation
@swift-ci Please smoke test |
@@ -0,0 +1,11 @@ | |||
add_swift_target_library(swift_PointerBounds ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't create a new target here. The actual macro
declaration and the types it depend on will go into the standard library so it's always available. In the short term, we can put them into the test cases themselves or (better) a module within the test
directory that we build and feed in to the test command.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't placing it in the test
dir rather than conditionally making it part of the stdlib make it harder to create pre-release versions for testing on real codebases?
// CHECK-NEXT: } | ||
// CHECK-NEXT: | ||
// CHECK-NEXT: func myFunc(_ ptr: UnsafeBufferPointer<CInt>, _ size: CInt, _ count: CInt) { | ||
// CHECK-NEXT: let _ptrCount: some BinaryInteger = size * count |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not have _ptrCount
typed to some BinaryInteger
, which is erasing information when we could be more precise. Maybe just leave off the type annotation and let the compiler figure it out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this because the expression could be any type, and in the case that it does have the wrong type, the type error here makes it clearer that the error is because of the user annotation rather than a bug in the macro. Maybe there's a neater way to do something like this?
let _ptrCount = size * count
let _assertPtrCountBinaryIntegerType: some BinaryInteger = _ptrCount // unused, only for asserting type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess if we have a function like this we could form a call to it:
@_alwaysEmitIntoClient
func _assertBinaryInteger<T: BinaryInteger>(_: T) { }
... but the result is basically the same, so what you've done will work. My main concern is that the opaque type we're creating here will have some runtime impact that doesn't get optimized away.
55744d9
to
20ad631
Compare
Managed to get things to build and run properly now. Will start addressing comments tomorrow. |
@swift-ci Please smoke test |
|
||
struct CountedBy: ParamInfo { | ||
var pointerIndex: Int | ||
var count: ExprSyntax |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be normalized to a count index? That would be more consistent with the other parameters that are resolved as indices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you then represent count expressions with e.g. addition or multiplication?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To get there, we'd have to "parse" the C/C++ expression and map it into something. We can represent that here with an expression in terms of Swift if we need to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. So far I've punted that issue, because the basic operations that cover most use cases have the same syntax in Swift. I imagine a macro syntax like this: .mul(.param(2), .param(3))
. This has the upside of only being able to represent expressions we support, and downside of requiring us to add explicit support for every type of expression. It would also let us add an enum case representing the return value of a function, so malloc could be annotated with @PointerBounds(.sizedByOrNull(pointer: .retVal, size: .param(1)))
.
// Stub interfaces for testing until Span lands | ||
public struct Span<T> { | ||
public var count: Int | ||
var ptr: UnsafeBufferPointer<T> | ||
public func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<T>) throws -> R) rethrows -> R { | ||
return try body(ptr) | ||
} | ||
} | ||
public protocol RawSpan { | ||
var byteCount: Int { get } | ||
func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, I expect Span will land before this PR, now that it's been accepted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! I'll keep it for now for my own testing, seeing as Span hasn't landed, but I'll make sure to remove it before merging this.
// CHECK-NEXT: } | ||
// CHECK-NEXT: | ||
// CHECK-NEXT: func myFunc(_ ptr: UnsafeBufferPointer<CInt>, _ size: CInt, _ count: CInt) { | ||
// CHECK-NEXT: let _ptrCount: some BinaryInteger = size * count |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess if we have a function like this we could form a call to it:
@_alwaysEmitIntoClient
func _assertBinaryInteger<T: BinaryInteger>(_: T) { }
... but the result is basically the same, so what you've done will work. My main concern is that the opaque type we're creating here will have some runtime impact that doesn't get optimized away.
depends on swiftlang/swift-syntax#2886 |
I'd like to break this dependency on the swift-syntax change, which I feel needs more discussion independently of this PR. Can you bring in whatever bits from swiftlang/swift-syntax#2886 that you need here so we can land this PR without any changes to swift-syntax? Once we do get to an answer there, we can move over to the corresponding API. |
56ffed7
to
d668cfb
Compare
@swift-ci Please smoke test |
5 similar comments
@swift-ci Please smoke test |
@swift-ci Please smoke test |
@swift-ci Please smoke test |
@swift-ci Please smoke test |
@swift-ci Please smoke test |
@DougGregor this doesn't look related to this patch at all. Is it a failure in main? |
I suspect you need to rebase your swift and update your swift-syntax |
@PointerBounds is a macro intended to be applied by ClangImporter when importing functions with pointer parameters from C headers. By leveraging C attributes we can get insight into bounds, esapability, and (eventually) lifetimes of pointers, allowing us to map them to safe(r) and more ergonomic types than UnsafePointer. This initial macro implementation supports CountedBy and Sizedby, but not yet EndedBy. It can generate function overloads with and without an explicit count parameter, as well as with UnsafeBufferPointer or Span (if marked nonescaping), and any of their combinations. It supports nullable/optional pointers, and both mutable and immutable pointers. It supports arbitrary count expressions. These are passed to the macro as a string literal since any parameters referred to in the count expression will not have been declared yet when parsing the macro. It does not support indirect pointers or inout parameters. It supports functions with return values, but returned pointers can not be bounds checked yet. Bounds checked pointers must be of type Unsafe[Mutable]Pointer[?]<T> or Unsafe[Mutable]RawPointer[?]. Count expressions must conform to the BinaryInteger protocol, and have an initializer with signature "init(exactly: Int) -> T?" (or accept a supertype of Int). rdar://137628612
65898f3
to
fed9049
Compare
@swift-ci Please smoke test |
1 similar comment
@swift-ci Please smoke test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bah, Python lint issue due to the long name
utils/swift_build_support/swift_build_support/products/wasmstdlib.py
Outdated
Show resolved
Hide resolved
@swift-ci please smoke test |
@hnrklssn the
|
@swift-ci Please smoke test |
@swift-ci please smoke test Windows |
} | ||
|
||
// CHECK: @_alwaysEmitIntoClient | ||
// CHECK-NEXT: func myFunc(_ ptr: MutableSpan<CInt>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MutableSpan isn't actually defined anywhere. Not sure why the Windows tests are crashing here, but at this point it isn't expected to really work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XFAIL'ing tests that need more standard library support to work properly
@swift-ci please smoke test |
@@ -1,6 +1,6 @@ | |||
// REQUIRES: swift_swift_parser | |||
// REQUIRES: pointer_bounds | |||
|
|||
// XFAIL: OS=windows-msvc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DougGregor if MutableSpan is unavailable, shouldn't we XFAIL this on all platforms?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect that the macro is being expanded but nothing ever type-checks it, so it's "working" for now but will fail with subsequent changes. We can disable then.
@PointerBounds is a macro intended to be applied by ClangImporter when importing functions with pointer parameters from C headers. By leveraging C attributes we can get insight into bounds, esapability, and (eventually) lifetimes of pointers, allowing us to map them to safe(r) and more ergonomic types than UnsafePointer.
This initial macro implementation supports CountedBy and Sizedby, but not yet EndedBy. It can generate function overloads with and without an explicit count parameter, as well as with UnsafeBufferPointer or Span (if marked nonescaping), and any of their combinations. It supports nullable/optional pointers, and both mutable and immutable pointers. It supports arbitrary count expressions. These are passed to the macro as a string literal since any parameters referred to in the count expression will not have been declared yet when parsing the macro.
It does not support indirect pointers or inout parameters. It supports functions with return values, but returned pointers can not be bounds checked yet.
Bounds checked pointers must be of type Unsafe[Mutable]Pointer[?]. Count expressions must conform to the BinaryInteger protocol, and have an initializer with signature "init(exactly: Int) -> T?" (or accept a supertype of Int).
rdar://137628612