Skip to content

SE-0139: NSValue bridging #4865

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 2 commits into from
Sep 22, 2016
Merged

Conversation

jckarter
Copy link
Contributor

@jckarter jckarter commented Sep 19, 2016

For every struct type for which the frameworks provides an NSValue category for boxing and unboxing values of that type, provide an _ObjectiveCBridgeable conformance in the Swift overlay that bridges that struct to NSValue, allowing the structs to be used naturally with id-as-Any APIs and Cocoa container classes. This is mostly a matter of gyb-ing out boilerplate using NSValue.init(bytes:objCType:) to construct the instance, NSValue.objCType to check its type when casting, and NSValue.getValue(_:) to extract the unboxed value, though there are a number of special snowflake cases that need special accommodation:

  • To maintain proper layering, CoreGraphics structs need to be bridged in the Foundation overlay.
  • AVFoundation provides the NSValue boxing categories for structs owned by CoreMedia, but it does so using its own internal subclasses of NSValue, and these subclasses do not interop properly with the standard NSValue subclasses instantiated by Foundation. To do the right thing, we therefore have to let AVFoundation provide the bridging implementation for the CoreMedia types, and we have to use its category methods to do so.
  • SceneKit provides NSValue categories to box and unbox SCNVector3, SCNVector4, and SCNMatrix4; however, the methods it provides do so in an unusual way. SCNVector3 and SCNVector4 are packaged into CGRects and then the CGRect is boxed using valueWithCGRect:. SCNMatrix4 is copied into a CATransform3D, which is then boxed using valueWithCATransform3D: from CoreAnimation. To be consistent with what SceneKit does, use its category methods for these types as well, and when casting, check the type against the type encoding SceneKit uses rather than the type encoding of the expected type.

We need the encode string to be able to construct NSValues using the core valueWithBytes:objCType: API. This builtin only works with concrete, @objc-representable types for now, which should be sufficient for a stdlib-internal API.
@jckarter
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test OS X Platform
Git Commit - 3cd9b9b1831b658c135f735f619a270009590ef4
Test requested by - @jckarter

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test Linux Platform
Git Commit - 3cd9b9b1831b658c135f735f619a270009590ef4
Test requested by - @jckarter

@@ -3951,8 +3951,17 @@ bool ASTContext::isStandardLibraryTypeBridgedInFoundation(
nominal == getStringDecl() ||
nominal == getErrorDecl() ||
nominal == getAnyHashableDecl() ||
// Weird one-off case where CGFloat is bridged to NSNumber.
nominal->getName() == Id_CGFloat);
// Foundation is a dependency of CoreGraphics, but
Copy link
Contributor

Choose a reason for hiding this comment

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

Foundation isn't actually a dependency of CoreGraphics; IIRC the two frameworks are unrelated in iOS, and related the other way on macOS.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting. The Swift overlays are set up in CMake such that Foundation's overlay depends on the CoreGraphics one on all platforms. Would it make sense to change it the other way, if that's purely a Swift-build-system layering and not a system one?

Copy link
Contributor

Choose a reason for hiding this comment

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

"Foundation is a dependency of CoreGraphics" means "CoreGraphics depends on Foundation", no?

Copy link
Contributor

Choose a reason for hiding this comment

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

This check also strikes me as incredibly general. Should we just give up and say all imported types might be bridged at some point?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I meant "Foundation depends on CoreGraphics". Treating all imported types as possibly bridged might be prudent for ABI compatibility, if we want to reserve the right to continue adding new bridging relationships after ABI stability is declared.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm less concerned about ABI stability than about us either being too general or not general enough. Either we think it's worth maintaining this list, or we don't.

Copy link
Contributor

Choose a reason for hiding this comment

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

(You're right that it won't be possible to add cross-module bridging relationships post-stability, but that seems reasonable.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@atrick or @aschwaighofer, how big of a perf impact do you think it'd be if we assumed all imported C value types might have an _ObjectiveCBridgeable conformance?

Copy link
Contributor

Choose a reason for hiding this comment

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

What kind of an impact do you have in mind? Generating a bunch of conformances bloats the binary and metadata. I guess that could be measured. I don't think the optimizer makes use of knowledge of the absence of a conformance. I don't think we could make that assumption for a public type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@atrick It looks like we do try to make optimization assumptions for the lack of _ObjectiveCBridgeable conformances by disallowing conformances to that protocol outside of the type's defining module, except for this hardcoded list of exceptions. I'm not sure how valuable that is anymore in the world of id-as-Any, since there's at least one way to bridge everything to and from a class type now.



func getObjCTypeEncoding<T>(_: T) {
// CHECK: call void @use({{.* i8]\*}} [[INT32:@[0-9]+]],
Copy link
Contributor

Choose a reason for hiding this comment

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

You should be able to check the actual encode strings too, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could, but I wasn't sure that the churn of tracking down and conditionalizing the exact encode strings for each type on each supported platform really gave meaningful coverage over just verifying that the builtin works at all. Clang ought to exercise the underlying type encoding code fairly well already.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure, but nothing is checking that the builtin works at all.

SWIFT_MODULE_DEPENDS_WATCHOS Foundation
SWIFT_MODULE_DEPENDS simd
SWIFT_MODULE_DEPENDS_WATCHOS
SWIFT_MODULE_DEPENDS Foundation simd
Copy link
Contributor

Choose a reason for hiding this comment

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

You can probably just remove the previous line, but it wasn't incorrect the old way—the non-watchOS platforms already indirectly depend on Foundation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch.

@jckarter
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test Linux Platform
Git Commit - 853afe50b09dcc1e16aa59e0205731647b0c0ee0
Test requested by - @jckarter

@jckarter
Copy link
Contributor Author

@swift-ci Please test

@jckarter
Copy link
Contributor Author

@jrose-apple You've already glanced over this, but do you mind reviewing for inclusion in 3.0.1 as well?

@jrose-apple
Copy link
Contributor

I'll do another pass tonight; the first pass wasn't too thorough.

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test OS X Platform
Git Commit - edf0aec59a52824aedb3564e0320471744d966a8
Test requested by - @jckarter

@jrose-apple
Copy link
Contributor

Seems good for now. Still a few open questions but nothing that would affect the 3.0 branch.

@jckarter
Copy link
Contributor Author

@swift-ci Please test

@jckarter
Copy link
Contributor Author

@jrose-apple I updated the comment in ASTContext.cpp and added some tests for specific type encodings that ought to be stable across platforms. How's this look for 3.0 now?

@jrose-apple
Copy link
Contributor

Ship it!

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test OS X Platform
Git Commit - c58492cc32720577c8c0adbdf1c06e7202fa8916
Test requested by - @jckarter

@jckarter
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test Linux Platform
Git Commit - c58492cc32720577c8c0adbdf1c06e7202fa8916
Test requested by - @jckarter

@jckarter
Copy link
Contributor Author

@phausler Is this Foundation test failure expected, or transient?

TestFoundation/TestNSURLSession.swift:40: error: TestURLSession.test_dataTaskWithURL : Asynchronous wait failed - Exceeded timeout of 12.0 seconds, with unfulfilled expectations: data task
11:05:05 TestFoundation/TestNSURLSession.swift:42: error: TestURLSession.test_dataTaskWithURL : XCTAssertEqual failed: ("unknown") is not equal to ("Kathmandu") - test_dataTaskWithURLRequest returned an unexpected result
11:05:05 Test Case 'TestURLSession.test_dataTaskWithURL' failed (12.006 seconds).

@jckarter
Copy link
Contributor Author

@swift-ci Please test

@phausler
Copy link
Contributor

yea that failure is transient, we have seen it flutter a bit and it really needs to be replaced with loopback testing

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test Linux Platform
Git Commit - c69d35d67b7530a3dcc0cbd49a98e1b45698a779
Test requested by - @jckarter

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test OS X Platform
Git Commit - c69d35d67b7530a3dcc0cbd49a98e1b45698a779
Test requested by - @jckarter

For every struct type for which the frameworks provides an NSValue category for boxing and unboxing values of that type, provide an _ObjectiveCBridgeable conformance in the Swift overlay that bridges that struct to NSValue, allowing the structs to be used naturally with id-as-Any APIs and Cocoa container classes. This is mostly a matter of gyb-ing out boilerplate using `NSValue.init(bytes:objCType:)` to construct the instance, `NSValue.objCType` to check its type when casting, and `NSValue.getValue(_:)` to extract the unboxed value, though there are a number of special snowflake cases that need special accommodation:

- To maintain proper layering, CoreGraphics structs need to be bridged in the Foundation overlay.
- AVFoundation provides the NSValue boxing categories for structs owned by CoreMedia, but it does so using its own internal subclasses of NSValue, and these subclasses do not interop properly with the standard `NSValue` subclasses instantiated by Foundation. To do the right thing, we therefore have to let AVFoundation provide the bridging implementation for the CoreMedia types, and we have to use its category methods to do so.
- SceneKit provides NSValue categories to box and unbox SCNVector3, SCNVector4, and SCNMatrix4; however, the methods it provides do so in an unusual way. SCNVector3 and SCNVector4 are packaged into `CGRect`s and then the CGRect is boxed using `valueWithCGRect:`. SCNMatrix4 is copied into a CATransform3D, which is then boxed using `valueWithCATransform3D:` from CoreAnimation. To be consistent with what SceneKit does, use its category methods for these types as well, and when casting, check the type against the type encoding SceneKit uses rather than the type encoding of the expected type.
@jckarter
Copy link
Contributor Author

@swift-ci Please test

@jckarter jckarter merged commit b0e3c0b into swiftlang:master Sep 22, 2016
@jckarter jckarter deleted the nsvalue-bridging branch September 22, 2016 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants