Skip to content

[Exclusivity] Enforce exclusive access for class offsets in KeyPaths #15502

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

Conversation

devincoughlin
Copy link
Contributor

Add dynamic enforcement of exclusive access when a KeyPath directly accesses a final
stored property on an instance of a class. For read-only projections, this begins and ends
the access immediately. For mutable projections, this uses the ClassHolder to perform
a long-term access that lasts as long as the lifetime of the ClassHolder.

rdar://problem/31972680

Copy link
Contributor

@atrick atrick left a comment

Choose a reason for hiding this comment

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

Awesome!


// Tail allocate the UnsafeValueBuffer used as the AccessRecord.
// This avoids a second heap allocation since we cannot have a
// stored property of Builtin.UnsafeValueBuffer type.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no Swift-level way to initialize a value of UnsafeValueBuffer type, so the initializer doesn't pass DI. I'll update the comment with more information.

1._builtinWordValue,
AccessRecord.self)

withUnsafeMutablePointer(to: &holder.previous) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this is more a question for @jckarter. Why did we drop down to withUnsafeMutablePointer, instead of just holding on to an Unmanaged reference?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see any reason we couldn't use Unmanaged and manual reference counting here. Are there undesired interactions between the exclusivity calls and the implicit refcounting at the unmanaged-managed boundaries?

Copy link
Contributor

Choose a reason for hiding this comment

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

Nevermind, it's obvious now that I look at this again, the ClassHolder is now allocated with a builtin. There's just no way to get the memory in an initialized state without using an unsafe pointer.

// Key path accesses are performed in the Standard Library. The Standard Library
// is currently compiled in Swift 3 mode and the compiler currently logs conflicts
// (rather than trapping on them) code compiled in Swift 3 mode. For this reason
// conflicts where the second conflicting access is via a kKeyey path will log rather than
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: kKeyey

@devincoughlin devincoughlin force-pushed the exclusivity-keypaths-class-offset branch 2 times, most recently from 6305429 to f062d6a Compare March 31, 2018 17:18
@devincoughlin
Copy link
Contributor Author

Updated to add additional comments about allocation and initialization of ClassHolder instances. Also rebased on top of master.

@devincoughlin
Copy link
Contributor Author

@swift-ci Please smoke test and merge

Add dynamic enforcement of exclusive access when a KeyPath directly accesses a final
stored property on an instance of a class. For read-only projections, this begins and ends
the access immediately. For mutable projections, this uses the ClassHolder to perform
a long-term access that lasts as long as the lifetime of the ClassHolder.

rdar://problem/31972680
@devincoughlin devincoughlin force-pushed the exclusivity-keypaths-class-offset branch from f062d6a to d1757fc Compare March 31, 2018 18:57
@devincoughlin
Copy link
Contributor Author

Rebase on top of @atrick 's recent 'noNestedConflict' changes. In SILGenBuiltin.cpp I've made emitBuiltinBeginUnpairedModifyAccess() have 'noNestedConflict' set to false and emitBuiltinPerformInstantaneousReadAccess() have it set to true. @atrick does this seem right to you?

@devincoughlin
Copy link
Contributor Author

@swift-ci please smoke test

@atrick
Copy link
Contributor

atrick commented Mar 31, 2018

@devincoughlin that looks good to me.

@atrick
Copy link
Contributor

atrick commented Mar 31, 2018

@swift-ci smoke test OS X Platform.

@atrick
Copy link
Contributor

atrick commented Mar 31, 2018

@devincoughlin you can either set noNestedAccess to false and let the future optimization handle it, or wait until this PR is merged and retest: #15659

@atrick
Copy link
Contributor

atrick commented Apr 1, 2018

@swift-ci smoke test.

@devincoughlin
Copy link
Contributor Author

@swift-ci Please smoke test

@atrick
Copy link
Contributor

atrick commented Apr 1, 2018

Interpreter/enforce_exclusive_access.swift

19:58:09 [ RUN      ] ExclusiveAccess.KeyPathInoutDirectWriteClassStoredProp
19:58:09 expecting a crash, but the test did not crash
19:58:09 [     FAIL ] ExclusiveAccess.KeyPathInoutDirectWriteClassStoredProp
19:58:09 [ RUN      ] ExclusiveAccess.KeyPathInoutDirectReadClassStoredProp
19:58:09 expecting a crash, but the test did not crash
19:58:09 [     FAIL ] ExclusiveAccess.KeyPathInoutDirectReadClassStoredProp

So, the IRGen "fix" I mentioned above,
#15659, should not have changed any functionality. Skipping the endAccess runtime call is purely an optimization (which is why I originally lumped it in with my
optimization pass). If the beginAccess isn't tracked, then the scratch buffer will be invalid and endAccess will do nothing.

What's more, I don't think the no_nested_conflict changes should have any effect on the KeyPathInoutDirectWriteClassStoredProp test that is failing (there's no instantaneous access in that test).

So, was the test ever passing? I looked into it and noticed that it requires the standard library to emit the unpaired access markers. We had never intended to enable access markers in the standard library because, once it's been tested, there's no point shipping it with extra checks. This is generally the right strategy, but doesn't work for the Builtin checks, which actually form an outer access scope surrounding user code.

I'm tempted to suggest handling the Builtins specially, and unconditionally emitting markers. But first we should consider whether there are other instances of this issue. Maybe the solution needs to
be generalized. As long as the stdlib doesn't instantiate internal classes with materializeForSet properties, I don't think this problem will show up elsewhere. @rjmccall can you think of any other places that we need to emit stdlib checks to pick up on user violations?

@devincoughlin
Copy link
Contributor Author

Oof. That explains why the test passes locally. I'm using a debug version of the standard library.

@rjmccall
Copy link
Contributor

rjmccall commented Apr 2, 2018

I would hope that the standard library generally doesn't need dynamic checks because it can just use static enforcement. So the approach I would suggest would be to look at all the dynamic checks that we do emit and either (1) decide they're statically okay (and disable them with an attribute?) or (2) leave them in as dynamic checks. A base policy of disabling all the checks feels like going the wrong way about it.

@atrick
Copy link
Contributor

atrick commented Apr 12, 2018

@gottesmm plans to add a function-level @semantics attribute to disable access marker elimination (i.e. force them to be emitted). That should unblock this fix.
rdar://problem/39335800 Add a function attribute to KeyPath to force exclusivity enforcement.

gottesmm added a commit to gottesmm/swift that referenced this pull request Apr 17, 2018
…ename it to IRGenPrepare.

I am going to be adding logic here to enable swiftlang#1550 to be completed.
The rename makes sense due to precedent from LLVM's codegen prepare and also
since I am going to be expanding what the pass is doing beyond just "cleaning
up". It is really a grab bag pass for performing simple transformations that we
do not want to pollute IRGen's logic with.

swiftlang#15502

rdar://39335800
@gottesmm
Copy link
Contributor

@devincoughlin I am going to chop off a part of this commit since I need it to test my semantics thing. Specifically I am just going to cut off the builtin emission part.

@atrick
Copy link
Contributor

atrick commented Apr 17, 2018

@devincoughlin, Since @gottesmm is testing this in conjunction with his changes, he'll probably end of staging it back in as two parts it in new PRs.

davidungar pushed a commit to davidungar/swift that referenced this pull request Apr 17, 2018
…ename it to IRGenPrepare.

I am going to be adding logic here to enable swiftlang#1550 to be completed.
The rename makes sense due to precedent from LLVM's codegen prepare and also
since I am going to be expanding what the pass is doing beyond just "cleaning
up". It is really a grab bag pass for performing simple transformations that we
do not want to pollute IRGen's logic with.

swiftlang#15502

rdar://39335800
@atrick
Copy link
Contributor

atrick commented Apr 23, 2018

See #16051..

@atrick atrick closed this Apr 23, 2018
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