Skip to content
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

[Wasm GC] Ignore unreachable sets for purposes of non-nullable locals #5665

Closed
wants to merge 7 commits into from

Conversation

kripken
Copy link
Member

@kripken kripken commented Apr 13, 2023

As pointed out by @tlively in #5599, we don't emit unreachable code in the binary
format, which means such sets can't help gets validate.

Fixes #5599

@kripken kripken requested a review from tlively April 13, 2023 23:39
@kripken
Copy link
Member Author

kripken commented Apr 14, 2023

It turns out this is tricky. If we land this PR, then any pass that does a refinalize will need to fix up non-nullable locals, beceause refinalization can propagate unreachability - and with this PR, unreachability affects non-nullable local validation.

In practice , that means adding non-nullable fixups to 24 more passes (!) (all those that don't do it now, but do perform a refinalize). That's so many passes that probably we would want to just constantly run non-nullable fixups after every pass and not bother with requiresNonNullableLocalFixups() as a way to opt out, and we'd just suffer the slower compile times. But that seems a high cost for this particular corner case...

@tlively
Copy link
Member

tlively commented Apr 14, 2023

Another workaround would be to treat these local.sets specially in the binary writer and emit them even though we otherwise wouldn't.

Another fix would be in the spec/v8, where we could consider all locals to be initialized in unreachable code.

@tlively
Copy link
Member

tlively commented Apr 14, 2023

Filed WebAssembly/function-references#98 to discuss a spec change.

@kripken
Copy link
Member Author

kripken commented Apr 17, 2023

Another workaround would be to treat these local.sets specially in the binary writer and emit them even though we otherwise wouldn't.

Unfortunately, I found this wouldn't be simple, as atm we stop at the first block etc. child that is unreachable. So to do this, we'd need to continue onward to check for sets, which would add complexity and overhead.

@tlively
Copy link
Member

tlively commented Apr 17, 2023

What about the solution of continuing to skip emitting things even beyond the end of blocks, as long as those blocks are unnamed?

@kripken
Copy link
Member Author

kripken commented Apr 17, 2023

That doesn't seem simple. It would affect multiple functions, as e.g. visitPossibleBlockContents just sees a single block. So we'd need to pass that state around somehow. And we need to preserve the other properties that this code handles, like this:

binaryen/src/wasm-stack.h

Lines 237 to 241 in 5679e3b

// children, since the latter won't be reached. This (together with logic in
// the control flow visitors) also ensures that the final instruction in each
// unreachable block is a source of unreachability, which means we don't need
// to emit an extra `unreachable` before the end of the block to prevent type
// errors.

It might be worth trying though, I'm just saying it's not obvious to me that it will work or work easily.

(OTOH, just running non-nullable local fixups, or just running dce before binary emitting, would definitely work and be very simple - but slow us down.)

@tlively
Copy link
Member

tlively commented Apr 17, 2023

This would all be much easier if we could separate iteration from other logic using generators built on C++20 coroutines :'(

@kripken
Copy link
Member Author

kripken commented Apr 18, 2023

I had the idea to notice in ReFinalize when we turn a set unreachable. That is rare, so doing more work in that case would be fine. And it's a change in one place (ReFinalize) and not in multiple passes. That works ok (with some fallout in LocalRefining, see code), but it doesn't address the more general problem, a set that is after unreachable code but not unreachable itself,

(module
 (func $2
  (local $3 (ref array))
  (drop
   (block (result nullref)
    (unreachable)
    (local.set $3
     (ref.as_non_null
      (ref.null none)
     )
    )
    (ref.null none)
   )
  )
  (drop
   (local.get $3)
  )
 )
)

That won't validate in v8 but it does work in Binaryen. Noticing that in ReFinalize is not simple and would, I think, add overhead (we'd need to track when we turn any child of anything unreachable, and see if there's a set after it).

kripken added a commit that referenced this pull request Apr 19, 2023
DCE at the end avoids issues with non-nullable local operations in unreachable
code, which is still being discussed. This PR avoids fuzzer errors for now, but we
should revert it when we have a proper fix.

See

* #5599
* #5665
* WebAssembly/function-references#98
@kripken
Copy link
Member Author

kripken commented Apr 25, 2023

As of #5677 we have a fuzzer workaround here, and nothing else seems urgent. Closing, and we'll see how the spec and other discussions go around this topic.

@kripken kripken closed this Apr 25, 2023
@kripken kripken deleted the unreachable.set.nn-local branch April 25, 2023 20:22
kripken added a commit that referenced this pull request Feb 22, 2024
See #5665 #5599, this is an existing issue and we have a workaround for it
using --dce, but it does not always work. I seem to be seeing this in higher
frequency since landing recent fuzzer improvements, so ignore it.

There is some risk of us missing real bugs here (that we validate and V8
does not), but this is a validation error which is not as serious as a difference
in behavior. And this is a long-standing issue that hasn't bitten us yet.
radekdoulik pushed a commit to dotnet/binaryen that referenced this pull request Jul 12, 2024
DCE at the end avoids issues with non-nullable local operations in unreachable
code, which is still being discussed. This PR avoids fuzzer errors for now, but we
should revert it when we have a proper fix.

See

* WebAssembly#5599
* WebAssembly#5665
* WebAssembly/function-references#98
radekdoulik pushed a commit to dotnet/binaryen that referenced this pull request Jul 12, 2024
…Assembly#6337)

See WebAssembly#5665 WebAssembly#5599, this is an existing issue and we have a workaround for it
using --dce, but it does not always work. I seem to be seeing this in higher
frequency since landing recent fuzzer improvements, so ignore it.

There is some risk of us missing real bugs here (that we validate and V8
does not), but this is a validation error which is not as serious as a difference
in behavior. And this is a long-standing issue that hasn't bitten us yet.
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.

Invalid non-nullable local binary emitting related to unreachability
2 participants