Skip to content

[Typechecker] Fix _modify for properties using a property wrapper #28216

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 7 commits into from
Nov 21, 2019
Merged

[Typechecker] Fix _modify for properties using a property wrapper #28216

merged 7 commits into from
Nov 21, 2019

Conversation

theblixguy
Copy link
Collaborator

@theblixguy theblixguy commented Nov 12, 2019

Follow up to https://forums.swift.org/t/property-wrapper-causing-excessive-array-copying

  1. We weren't correctly synthesising _modify for properties with an attached property wrapper. It was yielding the property itself, instead of the wrappedValue.
  2. The ReadWriteImplKind wasn't Modify so the synthesised _modify was never used (although even if it was then transparent inlining would error out, due to the incorrect synthesis of the coroutine).

Resolves SR-11762

@theblixguy theblixguy changed the title [Typechecker] Enable _modify for properties using a property wrapper [Typechecker] Fix _modify for properties using a property wrapper Nov 12, 2019
@theblixguy
Copy link
Collaborator Author

@swift-ci please test

2 similar comments
@theblixguy
Copy link
Collaborator Author

@swift-ci please test

@theblixguy
Copy link
Collaborator Author

@swift-ci please test

@swiftlang swiftlang deleted a comment from swift-ci Nov 13, 2019
@swiftlang swiftlang deleted a comment from swift-ci Nov 13, 2019
@swiftlang swiftlang deleted a comment from swift-ci Nov 13, 2019
@swiftlang swiftlang deleted a comment from swift-ci Nov 13, 2019
@swiftlang swiftlang deleted a comment from swift-ci Nov 13, 2019
Copy link
Contributor

@rjmccall rjmccall left a comment

Choose a reason for hiding this comment

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

Could you also test the "synthesized _read for a wrapped property" case? It should be something like making a type with a wrapper property conform to a protocol that declares that property as @_borrowed.

@theblixguy
Copy link
Collaborator Author

@rjmccall Sure, I have updated the test! Does it look good to you?

@theblixguy
Copy link
Collaborator Author

theblixguy commented Nov 13, 2019

There's a few crashes due to:

Assertion failed: (address.isLValue() && "physical lvalue decl ref must evaluate to an address"), function emitUsingStorage, file /Users/suyashsrijan/Documents/swift-src/swift/lib/SILGen/SILGenLValue.cpp, line 2641.

I’ll take a look tomorrow.

EDIT: Figured out - basically we weren't correctly synthesising _modify for $foo.

@rjmccall
Copy link
Contributor

@rjmccall Sure, I have updated the test! Does it look good to you?

Yes, the test looks good, thanks!

@theblixguy
Copy link
Collaborator Author

@swift-ci please test

@swiftlang swiftlang deleted a comment from swift-ci Nov 13, 2019
@theblixguy
Copy link
Collaborator Author

@swift-ci please test

@swiftlang swiftlang deleted a comment from swift-ci Nov 13, 2019
@swiftlang swiftlang deleted a comment from swift-ci Nov 14, 2019
@swiftlang swiftlang deleted a comment from swift-ci Nov 14, 2019
@theblixguy
Copy link
Collaborator Author

@swift-ci please test

@swiftlang swiftlang deleted a comment from swift-ci Nov 14, 2019
@theblixguy
Copy link
Collaborator Author

Linux failure is unrelated (“no such module 'Foo'”). Let’s try again:

@swift-ci please test Linux

@swiftlang swiftlang deleted a comment from swift-ci Nov 14, 2019
@theblixguy
Copy link
Collaborator Author

theblixguy commented Nov 14, 2019

The only failure on macOS is Sema/SwiftUI/rdar56710317.swift, although I am not quite sure why (and I don't have access to SwiftUI source code so...). I don't even know why we're emitting SIL for this when the test only verifies a diagnostic? Oh, and the crash doesn’t even happen in the test case, it happens in SwiftUI!

Assertion failed: (address.isLValue() && "physical lvalue decl ref must evaluate to an address"), function emitUsingStorage, file /Users/buildnode/jenkins/workspace/swift-PR-osx/branch-master/swift/lib/SILGen/SILGenLValue.cpp, line 2652.
Stack dump:
0.	While emitting SIL for modify for isOn (at /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/x86_64-apple-macos.swiftinterface:3300:63)
1.	While silgen emitFunction SIL function "@$s7SwiftUI24ToggleStyleConfigurationV4isOnSbvM".
 for modify for isOn (at /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/x86_64-apple-macos.swiftinterface:3300:63)

@rjmccall
Copy link
Contributor

Well, SwiftUI is a relatively heavy user of property wrappers, so if there's a problem that only shows up in some corner case, it's not too surprising that SwiftUI would exercise it.

The crash is just parsing the .swiftinterface for SwiftUI, you shouldn't need the actual source code, just an SDK. If you can't get that, I'm sure someone can try to isolate a test case for you from that.

@theblixguy
Copy link
Collaborator Author

theblixguy commented Nov 14, 2019

Yeah, I was just a bit surprised to see a SIL crash, but I didn't realise that was expected (due to compiling the swiftinterface file). Anyway, this crash is happening because we have a nonmutating setter:

@SwiftUI.Binding @_projectedValueProperty($isOn) public var isOn: Swift.Bool {
    get
    nonmutating set
  }

If I remove nonmutating from it, then the test works as expected. I think this crash is the same as SR-11637 but I haven't figured out why this specific combination crashes yet. I'll investigate shortly.

@rjmccall
Copy link
Contributor

Maybe we're not computing the mutating-ness of modify correctly in that case. If so, it's possible that we might have an ABI issue with that property wrapper that was never caught because of the oversight which caused us to not use it. @DougGregor

@theblixguy
Copy link
Collaborator Author

theblixguy commented Nov 15, 2019

Hmm it seems like not using PropertyWrapperMutability in buildStorageReference for computing isSelfLValue and instead using the storage's mutating-ness fixes the crash. Maybe that was the problem?

@theblixguy
Copy link
Collaborator Author

Let's see if this works now:

@swift-ci please test

@swiftlang swiftlang deleted a comment from swift-ci Nov 15, 2019
@swiftlang swiftlang deleted a comment from swift-ci Nov 15, 2019
@theblixguy
Copy link
Collaborator Author

theblixguy commented Nov 16, 2019

Nope, that didn't work (strange it worked locally, idk what happened there). Let me investigate more - my assumption is that the _modify should have the same mutating-ness as the setter, in case of isOn, it should be non-mutating, which it is in this case so the crash shouldn’t happen I think, but maybe something else is going on.

@theblixguy
Copy link
Collaborator Author

theblixguy commented Nov 16, 2019

In the SwiftUI swiftinterface file, we have @Binding wrapper whose wrappedValue has a getter and nonmutating setter. Now, the crash happens when emitting _modify for the property @Binding var isOn: Bool in ToggleStyleConfiguration, which has a getter and a non-mutating setter (which I believe is synthesized) so there's no difference in mutating-ness. So, I came up with the following test case (mimicking the code in the swiftinterface file):

@frozen @propertyWrapper @dynamicMemberLookup
public struct _Binding<Value> {
  public var wrappedValue: Value {
    get { fatalError() }
    nonmutating set { }
  }

  public var projectedValue: _Binding<Value> {
    get { fatalError() }
  }

  public subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Value, Subject>) -> _Binding<Subject> {
    get { fatalError() }
  }

  public init() {
    fatalError()
  }
}

public struct _ToggleStyleConfiguration {
  @_Binding public var isOn: Bool
}

which compiles. Looking at the generated swiftinterface file for this, I can see that the compiler is inserting a nonmutating _modify for isOn. So, I added it to the swiftinterface for SwiftUI and it worked. So, I think the compiler is correctly computing the mutating-ness. (also, I realised that if the _modify's mutating-ness wasn't in sync with the setter then we would either diagnose it or crash in ASTVerifier, not in SILGen)

@theblixguy
Copy link
Collaborator Author

Yep, this seems to be happening when compiling the swiftinterface file only. I omitted the nonmutating _modify from isOn and invoked ./swiftc -frontend -compile-module-from-interface ~/Desktop/test.swiftinterface -o ~/Desktop/test.swiftmodule -sdk $(xcrun --show-sdk-path), which caused the same crash.

// swift-interface-format-version: 1.0
// swift-compiler-version: Swift version 5.1.1-dev (Swift 1264c561a8)
// swift-module-flags: -enable-library-evolution
import Swift
@frozen @propertyWrapper @dynamicMemberLookup public struct _Binding<Value> {
  public var wrappedValue: Value {
    get
    nonmutating set
  }
  public var projectedValue: test._Binding<Value> {
    get
  }
  public subscript<Subject>(dynamicMember keyPath: Swift.WritableKeyPath<Value, Subject>) -> test._Binding<Subject> {
    get
  }
  public init()
}
public struct _ToggleStyleConfiguration {
  @test._Binding @_projectedValueProperty($isOn) public var isOn: Swift.Bool {
    get
    nonmutating set
  }
  public var $isOn: test._Binding<Swift.Bool> {
    get
  }
}

@rjmccall
Copy link
Contributor

If there's something unusual about building this from a .swiftinterface, that seems like a reasonable test case.

@theblixguy
Copy link
Collaborator Author

theblixguy commented Nov 19, 2019

Yes, I cannot reproduce this with Swift code, but I can with a swiftinterface file and it’s interesting how specific the mutating-ness has to be in order to produce a crash. I don’t know much about swiftinterface files though, so I haven’t managed to figure out why this crashes yet. I think once you regenerate the file then things would work correctly, since it would have the modify accessor, so it only affects files that don’t have one and use this specific mutating-ness. I don’t know if there’s any other way to work around it in the meantime (I mean we could temporarily disable it for swiftinterface files), but perhaps someone who has worked on swiftinterface might be able to help.

@theblixguy
Copy link
Collaborator Author

theblixguy commented Nov 20, 2019

Okay, I think I have narrowed down the problem. It seems like the problem is that we're not computing the property wrapper mutability, because in the PropertyWrapperMutabilityRequest, we're calling var->getParsedAccessor to check if we have a parsed getter and setter and returning None if we do. So, in buildStorageReference, the call to propertyWrapperMutability just fails and we don't correctly compute the values for isMemberLValue & isSelfLValue. Similarly, the IsGetterMutating and IsSetterMutating requests don't work correctly as well.

If I also add a check for a swiftinterface file, then it works correctly. So, I think the fix is to make sure we don't return None if we have a swiftinterface file.

… when parsing getter/setter from swiftinterface file
@theblixguy
Copy link
Collaborator Author

@swift-ci please test

@swiftlang swiftlang deleted a comment from swift-ci Nov 20, 2019
@swiftlang swiftlang deleted a comment from swift-ci Nov 20, 2019
@theblixguy
Copy link
Collaborator Author

Hooray, that worked. Let’s run source compat just to be sure:

@swift-ci please test source compatibility

Copy link
Member

@DougGregor DougGregor left a comment

Choose a reason for hiding this comment

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

This looks great!

@theblixguy theblixguy merged commit 4c85dd6 into swiftlang:master Nov 21, 2019
@theblixguy theblixguy deleted the fix/SR-11762 branch November 21, 2019 11:42
target = TargetImpl::WrapperStorage;
}
}

Copy link
Contributor

@roop roop Dec 2, 2019

Choose a reason for hiding this comment

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

@theblixguy I don't understand why this is required.

Given a property like:

struct Foo {
  @Wrap var bar: Int
}

Before this PR, the synthesized modify in SIL looked like:

Foo.bar.modify:
  var tmp = call Foo.bar.getter(self)
  yield &tmp
  call Foo.bar.setter(tmp, &self)

where the Foo.text.getter calls Wrap.wrappedValue.getter, and Foo.text.setter calls Wrap.wrappvedValue.setter.

After this PR, it looks like:

Foo.bar.modify:
  var tmp = call Wrap.wrappedValue.getter(self._text)
  yield &tmp
  call Wrap.wrappedValue.setter(tmp)

These are equivalent, but I thought it was cleaner that Foo.bar.modify calls into Foo.bar.getter/setter instead of doing something special for property wrappers. Why is it being done the latter way?

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.

4 participants