-
Notifications
You must be signed in to change notification settings - Fork 441
Resolve stack overflow in tree visitation when having a reduced stack size #205
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
2929238
to
0d810ae
Compare
@swift-ci Please test |
Does this have any effect in release build performance? |
0d810ae
to
33b313d
Compare
Ah, forgot to check. I just did and it did have a 50% performance decrease in release builds. I stinn think it is valuable for day-to-day development because dispatch queues are much nicer to work with than @keith: You would need to specify |
I'd recommend to use it unconditionally for |
Yea I think expecting everyone to use that variable for debug builds if we landed my change probably wouldn't be ok |
My idea was that I didn’t want debug builds to start using a completely different implementation than release builds. This might make debugging weird. If you specify the extra flag, you know what you are opting in to. But I’m happy to just make it conditional on |
If this is triggered by a compilation condition defined in SwiftSyntax, there's no way for swift-format to "push that down" into SwiftSyntax when it specifies that dependency in the manifest, right? AFAIK, swift-format can only set the defines in Package.swift on the targets that it defines for itself. If that's the case, if we accept @keith's change, then it would no longer be possible to just clone swift-format and do |
@allevato Because the stack size issue only happens in debug builds |
I think it is fine. If you change it and it fixes the stack overflow in debug and has no effect in release performance, it is good from my end. But I have another question, if we reduce stack usage here are we just pushing the problem further, meaning there is no stack overflow with the swift code we are seeing now, but there can always be some hugely deep-nested piece code that ends up exhausting the stack space again. It's not clear to me, is there code recursion in swift-format that could move to being data recursive and thus being secure against deeply-nested code? In any case, I think we should get this change in and consider code vs data recursion as a separate thing. |
Ok, thanks for confirming! In that case, I still agree with @akyrtzi then that this should just be conditional on Especially since this doesn't just affect swift-format. Requiring every client of SwiftSyntax to define a custom flag for debug builds if they use APIs in a dispatch queue would be a huge usability penalty. |
It's not 100% clear to me, is it the rewriter doing code recursion, and could we switch it to data recursion without affecting the public APIs? |
I believe it's doing code recursion. The default implementation of Because of that, I'm not sure we could unroll the recursion into an explicitly maintained stack/loop while retaining the API contract we have today. |
I see. Later on we could look into adding some rewriting API that is safer to use. Not just to protect against stack overflows with deeply nested code but also protect against messing up the tree invariants (with SyntaxRewriter you may end up with unexpected child nodes if you are not careful with what kind of nodes you return, and get a crash later on). |
We are, but I think in practice Swift code tends to not be nested too deply + I assume that these super-nested edge cases tend to be hit in real-world and not development scenarios that use a release build and are thus less prone to stack overflow (because the optimiser does a good job at reducing stack usage).
It is doing code recursion |
…duced stack size To determine the correct specific visitation function for a syntax node, we need to switch through a huge switch statement that covers all syntax types. In debug builds, the cases of this switch statement do not share stack space (rdar://55929175). Because of this, the switch statement requires allocates about 15KB of stack space. In scenarios with reduced stack size (in particular dispatch queues), this often results in a stack overflow during syntax tree visitation. To circumvent this problem, this commit moves adds a flag that moves the retrieval of the specific visitation function to its own function. This way, the stack frame that determines the correct visitiation function will be popped of the stack before the function is being called, making the switch's stack space transient instead of having it linger in the call stack. The workaround currently has a 50% performance decrease in release builds, so it is only used in debug builds.
33b313d
to
a0fd756
Compare
I have updated the PR to make it conditional on the @swift-ci Please test |
Build failed |
Build failed |
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.
LGTM, assuming it doesn't affect release performance.
Thank you Alex!
How would this work when overridden For example, many early swift-format bugs were caused by omitting calls to I haven't thought this completely through, but it might be possible to keep the same API signatures with a bottom-up rewriting implementation, but I'm having trouble seeing how we could also retain the behavior of existing subclasses. Happy to take this discussion offline though; I'd love to see the recursion be removed, so if there's something I'm missing that makes that possible while also preserving the rest of the contract, please show me the piece I'm missing! |
I agree it is not urgent, but note that you can't really determine how safe nesting is by just examining |
I just double-checked. The change does not have an impact on the
You’re right of course. I didn’t think it through properly. I believe, I was thinking of the syntax visitor, not the syntax visitor, not the syntax rewriter 🤦🏽♂️
Yeah, but maybe we have given ourselves enough headroom now, that the issue simply never comes up. Anyway, I think we all agree that it’s not urgent for now. |
OMG thanks for fixing this💖 |
Remove @testable imports, upgrading symbols to public visibility.
To determine the correct specific visitation function for a syntax node, we need to switch through a huge switch statement that covers all syntax types. In debug builds, the cases of this switch statement do not share stack space (rdar://55929175). Because of this, the switch statement requires allocates about 15KB of stack space. In scenarios with reduced stack size (in particular dispatch queues), this often results in a stack overflow during syntax tree visitation.
To circumvent this problem, this commit moves the retrieval of the specific visitation function to its own function. This way, the stack frame that determines the correct visitiation function will be popped of the stack before the function is being called, making the switch's stack
space transient instead of having it linger in the call stack.
This tackles the stack overflow issue discovered that's blocking swiftlang/swift-format#117
CC: @keith