-
Couldn't load subscription status.
- Fork 10.6k
[SILGen]: ban forward captures of let temporary allocations #84951
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
base: main
Are you sure you want to change the base?
Conversation
|
@swift-ci please smoke test macos |
|
hmm... got this when building some stuff in
current changes cause a spurious DI error on this pattern: protocol P {}
func fail(_ a: Any) {
let p = a as! P
}i think the issue now is that DI is treating the |
|
That regression ought to be fixed of course, but independently of that we might want to ask them to move away from |
|
@slavapestov do you think updating SILGen & DI is the best (or least effort...) way to go about addressing this? i was wondering if alternatively there's some way it could be detected/banned in Sema... |
I would expect that DI needs to treat this instruction as initializing the |
|
@jamieQ I think catching this in DI is your best bet, since it already does the right thing for loadable types AFAIK. |
|
@swift-ci please smoke test macos |
|
okay i've looked into some of the relevant code here and had a go at solving this in a few ways. some thoughts: 1. update SILGenDecl & DIchanges:
this strategy seems to fix the patterns discovered, but i ran into the following issues:
2. update SILGenFunctionmost existing detection of this problem seems like it doesn't actually come from DI, but from SILGenFunction – there is logic Slava added a while ago to ban 'forward captures'. said logic is what currently catches things like this where the type is not address-only: let fwdInt: Int = { () -> Int in fwdInt }()these cases don't actually make it to DI b/c SILGen errors out when attempting to emit the closure captures. i'm not yet certain why this handling doesn't work the same way for the address-only cases though, and haven't determined if there's some minor adjustment that could be made to the existing logic here to cover those additional cases. 3. update TypeCheckCapturesDI already handles 'two-phase' initialization of this pattern, but 'direct' initialization seems like a simpler case to detect & analyze, and i think could be identified during typechecking. i have another rough draft PR here to test out this concept. the current implementation approach adds a check into the perks of this approach IMO is that it could be pretty self-contained and is less 'invasive' – it won't (i think) mess with any existing tests, nor change existing codegen. currently i'm leaning toward approach edits:
|
|
@swift-ci please smoke test macos |
1 similar comment
|
@swift-ci please smoke test macos |
|
Looks like the tests passed, good job! Are you more confident in this approach now? |
@slavapestov i changed tack somewhat – i found this old PR of yours that i think basically handled this problem for edit: seems the crashes in the optimizer have mysteriously disappeared for me locally, so maybe i was in a weird state or had left some code enabled that i should have removed. anyway, will see how CI likes this approach soon. |
This addresses the same problem as swiftlang#37935 but handles address-only lets rather than vars.
a728b52 to
a246e37
Compare
|
@swift-ci please smoke test |
|
@swift-ci please smoke test linux |
|
@kavon @slavapestov – this approach seems to work and made it through the CI gauntlet (so far). lmk what you think |
|
|
||
| func forward_declared_let_captures() { | ||
| do { | ||
| let bad: Any = { bad }() |
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.
FYI for this particular case I'm hoping we can just ban it in Sema (de3dc77)
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.
@hamishknight thanks for the heads-up! i can't say i totally follow the implications of that change yet 😅, but to try and clarify – are the cases you'd expect to be possible to catch in Sema something like: variable bindings with (immediate) initializers containing closure expressions that reference the binding itself? would you expect it to cover all the test cases i've added here, or just certain ones?
also, more tangentially, do you have a sense if your proposed changes could potentially alleviate the need for what is currently done in the var case in SILGen too (i see you moved some existing tests out of this file)? i.e. with the approach you're considering do you think the SILGen approach where the var is 'hidden' until the initializer emission is completed could be removed in favor of:
- your proposed detection in Sema (to catch immediate inits with forward references)
- existing logic in DI (to catch flow-sensitive violations)
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.
are the cases you'd expect to be possible to catch in Sema something like: variable bindings with (immediate) initializers containing closure expressions that reference the binding itself? would you expect it to cover all the test cases i've added here, or just certain ones?
So my change takes the existing rule we have in Sema that prevents you from doing this:
let a = a // error: use of local variable 'a' before its declaration
_ = b // error: use of local variable 'b' before its declaration
let b: IntAnd extends it such that it also considers any use nested in a closure, which is already illegal today in most cases, and is necessary for correctness since we can't support forward references in closure bodies in the constraint system as the bodies are solved in order. Unfortunately it breaks one project in the source compat suite (since you can do such forward references today with lazy), so I think there will need to be more discussion on that change, so it doesn't need to block this PR.
I don't expect that change to cover all of the cases you're handling here since it purposefully doesn't handle nested func declarations, which are currently allowed to forward reference things. Although I think we probably ought to revisit that since the current behavior can be pretty confusing e.g:
let x = 0
do {
func fn() {
print(x) // prints 1
}
defer {
fn()
print(x) // prints 0
}
let x = 1
}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.
gotcha, thanks for the explanation. and... wow is that last example confusing 🙃
This addresses the same problem as #37935
but handles address-only lets rather than vars.
fixes: #85028