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
Replace try/catch/rethrow in unwind with unwind_guard #309
Conversation
As you noticed, skipping the However, you do have a point here, all those try/catch-blocks are something to look into and they do provide an optimization opportunity. So, what could be done? I can see some potential by checking if a grammar rule (including all reachable sub-rules) can throw an exception. This could be used to skip |
Yeah, partly this change is predicated on letting the exception out to the caller, in which case you don't need to unwind because the What I'm probably going to do for my own project is copy enough of the |
Maybe it would be better to define an RAII Edit: This would actually apply to the core |
Here you go, how about something like this? |
Codecov Report
@@ Coverage Diff @@
## main #309 +/- ##
=======================================
Coverage 99.68% 99.68%
=======================================
Files 253 254 +1
Lines 5083 5088 +5
=======================================
+ Hits 5067 5072 +5
Misses 16 16
Continue to review full report at Codecov.
|
I like the idea about the Also, I would like to get some idea of how it improves code on all major compilers (GCC, Clang, and MSVC), as we don't want to improve code on one compiler on the expense of others. I ran some small checks with GCC so far and sometimes the binaries became larger, sometimes they became smaller. With Clang, there was a lot more overhead and most binaries grew in size. Minor details: The capture list can be shortened to just |
One more thing that I should probably mention: We don't want to check the code with Debug mode, as the PEGTL is heavily templated and relies on the optimizer to generate reasonable code. Anything compiled without optimizations is best effort, at best. We should probably also put this in the documentation in the "Requirements" section. @ColinH Would you like to formulate something? |
FWIW, the same stack overflow occurs in release builds using MSVC (VS2022) on my machine. I don't think the release optimizations can save it from this particular issue. |
OK, will do.
Would you prefer an
OK. 👍🏼 |
I have a configurable depth limit and a prototype of a custom |
@d-frey Can we merge this as is? |
I think we already talked about this, especially benchmarks. Are we sure this PR would not introduce drawbacks for GCC/Clang? |
I've been playing around with it on Compiler Explorer, and with N.B. A side benefit of #313 is that it'll remove this code in |
Here's one finding after finally playing around with this for a bit, on a Mac with M1 Max running Monterey. With a slightly modified So, at least for Clang and GCC, it doesn't seem to matter, since, at least in this simple case, the optimisers sees right through the supposed "difference". PS: On this platform Clang consistently produced much smaller binaries than GCC 11. |
Thanks Colin for testing. @wravery Can you update this PR so I can merge it? Thanks! |
I merged it manually, I'll do a release of the 3.x branch shortly. |
I noticed while investigating microsoft/cppgraphqlgen#222 that
parse_tree
implementsunwind
on themake_control<>::state_handler<>
struct, and this results in creating a try/catch/rethrow block around every rule match inmatch_control_unwind
. Every try/catch/rethrow seems to be consuming significant stack space, so if youraise
/throw an exception from a relatively shallow (< 20) nested rule, it often exhausts the stack.This change makes it so
parse_tree
will only implementunwind
on theControl
type wrapper if either theControl
or theNode
type implements it. When parsing into a tree that doesn't implement either of them, the exception will be thrown once all the way from the initialraise
to the caller ofparse_tree::parse
.Since the unit tests use the default
node
which inheritsbasic_node
, they still implementunwind
. The unit tests also wrap the rule in atry_catch_type
handler, though, so the exception doesn't bubble all the way up. Before I nailed down theconstexpr bool
definitions, I accidentally suppressedunwind
forbasic_node
types in the unit tests as well, and the lack ofstack.pop_back()
calls resulted in theassert(stack.size() == 1)
firing for the unit test. So that tells me this is incompatible withtry_catch_type
. That's the only thing I think might block it.