-
Notifications
You must be signed in to change notification settings - Fork 786
Pop in catch emitted inside a block ("not enough arguments on the stack" from VM) #4237
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
Comments
Inserting a new block around a pop will have this problem until we support block param types. Perhaps a short-term fix would be to not add such blocks when there is a |
I considered that, but it would mean anywhere in the codebase that creates a block would need to check that - that would be invasive, I'm afraid, in many passes. I hope a "fixup" process that runs during binary emitting might be practical somehow - maybe similar to what happens during |
Yeah this is tricky... I had to fix many points because of this (e.g. #2885). I checked why I'm not sure if we can come up with a general "fixup" pass.. Maybe we can.. But we have to make sure that those inner blocks generated between |
Maybe we should prioritize supporting block parameters to fix this problem "properly," then. I think the best way to do that would be to allow dangling pops at the beginning of arbitrary blocks, not just in |
@tlively That would be nice, but that mean EH works only when multivalue is enabled right? I don't think that will be a problem in practice, because all engines that implement EH have implemented multivalue already.. But multivalue support is flaky in many parts still, so it might entail more than we think. |
Thinking more on this, I think a general fixup would not be that hard. Basically it could scan catch blocks and see if the pop is nested in control flow, and if so, add a local to pop it immediately instead. Block parameters would be more efficient, but also more work I think? |
The fixup would also have to introduce a new local to hold the result of the new pop and replace the old pop with a local.get of that local, but yeah, that should work. |
@kripken I think that will work. Should that be a new pass, or something like |
@aheejin I think the second option sounds better. That way people running a pass don't need to always remember to run a fixup pass after it. If we do that, plus add validation of this, then we should be pretty safe I think. |
This adds `EHUtils::handleBlockNestedPops`, which can be called at the end of passes that has a possibility to put `pop`s inside `block`s. This method assumes there exists a `pop` in a first-descendant line, even though it can be nested within a block. This allows a `pop` to be nested within a `block` or a `try`, but not a `loop`, since that means the `pop` can run multile times. In case of `if`, `pop` can exist only in its condition; if a `pop` is in its true or false body, that's not in the first-descendant line. To test this, this adds `passes/test_passes.cpp`, which is intended to contain multiple test passes that test a single (or more) utility functions separately. Without this kind of pass, it is hard to test various cases in which nested `pop`s can be generated in existing passes. This PR also adds `PassRegistry::registerTestPass`, which registers a pass that's intended only for internal testing and does not show up in `wasm-opt --help`. Fixes WebAssembly#4237.
This adds `EHUtils::handleBlockNestedPops`, which can be called at the end of passes that has a possibility to put `pop`s inside `block`s. This method assumes there exists a `pop` in a first-descendant line, even though it can be nested within a block. This allows a `pop` to be nested within a `block` or a `try`, but not a `loop`, since that means the `pop` can run multile times. In case of `if`, `pop` can exist only in its condition; if a `pop` is in its true or false body, that's not in the first-descendant line. This can be useful when optimization passes create blocks to do transformations. Wrapping expressions wiith a block does not change semantics most of the time, but if pops happen to be inside a block generated by those passes, they can result in invalid binaries. To test this, this adds `passes/test_passes.cpp`, which is intended to contain multiple test passes that test a single (or more) utility functions separately. Without this kind of pass, it is hard to test various cases in which nested `pop`s can be generated in existing passes. This PR also adds `PassRegistry::registerTestPass`, which registers a pass that's intended only for internal testing and does not show up in `wasm-opt --help`. Fixes #4237.
`ref.cast` can be statically removed when the ref's type is a subtype of the intended RTT type and either of `--ignore-implicit-traps` or `--traps-never-happen` is given: https://github.com/WebAssembly/binaryen/blob/083ab9842ec3d4ca278c95e1a33112ae7cd4d9e5/src/passes/OptimizeInstructions.cpp#L1603-L1624 Some more context: WebAssembly#4097 (comment) But this can create a block in which a `pop` is nested, which makes the `catch` invalid. The test in this PR is the same as the example given by @kripken in WebAssembly#4237. This calls the fixup function `EHUtils::handleBlockNestedPops` at the end of the pass to fix this. Also, because this pass creates a lot of blocks in other patterns, I think it is possible there can be other patterns to cause this kind of `pop` nesting.
`ref.cast` can be statically removed when the ref's type is a subtype of the intended RTT type and either of `--ignore-implicit-traps` or `--traps-never-happen` is given: https://github.com/WebAssembly/binaryen/blob/083ab9842ec3d4ca278c95e1a33112ae7cd4d9e5/src/passes/OptimizeInstructions.cpp#L1603-L1624 Some more context: WebAssembly#4097 (comment) But this can create a block in which a `pop` is nested, which makes the `catch` invalid. The test in this PR is the same as the example given by @kripken in WebAssembly#4237. This calls the fixup function `EHUtils::handleBlockNestedPops` at the end of the pass to fix this. Also, because this pass creates a lot of blocks in other patterns, I think it is possible there can be other patterns to cause this kind of `pop` nesting.
`ref.cast` can be statically removed when the ref's type is a subtype of the intended RTT type and either of `--ignore-implicit-traps` or `--traps-never-happen` is given: https://github.com/WebAssembly/binaryen/blob/083ab9842ec3d4ca278c95e1a33112ae7cd4d9e5/src/passes/OptimizeInstructions.cpp#L1603-L1624 Some more context: #4097 (comment) But this can create a block in which a `pop` is nested, which makes the `catch` invalid. The test in this PR is the same as the example given by @kripken in #4237. This calls the fixup function `EHUtils::handleBlockNestedPops` at the end of the pass to fix this. Also, because this pass creates a lot of blocks in other patterns, I think it is possible there can be other patterns to cause this kind of `pop` nesting.
`ref.cast` can be statically removed when the ref's type is a subtype of the intended RTT type and either of `--ignore-implicit-traps` or `--traps-never-happen` is given: https://github.com/WebAssembly/binaryen/blob/083ab9842ec3d4ca278c95e1a33112ae7cd4d9e5/src/passes/OptimizeInstructions.cpp#L1603-L1624 Some more context: WebAssembly#4097 (comment) But this can create a block in which a `pop` is nested, which makes the `catch` invalid. The test in this PR is the same as the example given by @kripken in WebAssembly#4237. This calls the fixup function `EHUtils::handleBlockNestedPops` at the end of the pass to fix this. Also, because this pass creates a lot of blocks in other patterns, I think it is possible there can be other patterns to cause this kind of `pop` nesting.
`ref.cast` can be statically removed when the ref's type is a subtype of the intended RTT type and either of `--ignore-implicit-traps` or `--traps-never-happen` is given: https://github.com/WebAssembly/binaryen/blob/083ab9842ec3d4ca278c95e1a33112ae7cd4d9e5/src/passes/OptimizeInstructions.cpp#L1603-L1624 Some more context: WebAssembly#4097 (comment) But this can create a block in which a `pop` is nested, which makes the `catch` invalid. The test in this PR is the same as the example given by @kripken in WebAssembly#4237. This calls the fixup function `EHUtils::handleBlockNestedPops` at the end of the pass to fix this. Also, because this pass creates a lot of blocks in other patterns, I think it is possible there can be other patterns to cause this kind of `pop` nesting.
In this IR the pop exists in a place that Binaryen accepts but wasm does not: we use a pop to get the catch value, but we are inside a block so the wasm does not validate, and a VM will error on something like this:
That is with just translating that wat into a binary,
bin/wasm-opt a.wat -all -o a.out.wasm
. However, if we add a--roundtrip
in that command, then the IR becomes this:Note how the pop was moved into a proper position. So it seems like we have some logic in the binary reading code that handles this somehow, and so using
--roundtrip
is a workaround, at least, so this is probably not urgent.I noticed this in wasm GC that had this:
That cast can be statically removed, leading to:
And that is how the problem can occur.
This does not seem specific to wasm GC, but perhaps we have more peephole opts that end up emitting small blocks, which increase the risk.
The text was updated successfully, but these errors were encountered: