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

Add anyref feature and type #3109

Merged
merged 14 commits into from
Sep 11, 2020
Merged

Add anyref feature and type #3109

merged 14 commits into from
Sep 11, 2020

Conversation

dcodeIO
Copy link
Contributor

@dcodeIO dcodeIO commented Sep 9, 2020

Adds a custom --enable-anyref feature enabling just the anyref type on top of reference types that allows us to test subtyping relationship (with externref, funcref and exnref) without having to enable the full set of GC features. Re-enables previously disabled tests.

Comment on lines 545 to 547
// FIXME: `anyref` is only valid here if the `anyref` feature is enabled,
// but this information is not available within `Type` alone.
return b.isRef() ? Type::anyref : Type::none;
Copy link
Contributor Author

@dcodeIO dcodeIO Sep 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixing this looks like it requires changing the signature of this function to getLeastUpperBound(Type a, Type b, FeatureSet features), with call sites aware of enabled features passing these in.

Only works this way currently because we are not generating, for example, an (if $condition $funcref $externref) where anyref is not enabled, but an API user can do so yielding an unexpected (otherwise not enabled) anyref here. Do you prefer to tackle this issue in this PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm missing some use of this function, I believe it will only incorrectly return anyref for invalid code. As long as validation will separately catch that invalidity, I'm tempted to not do anything with this issue besides document it. WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, it looks like this PR doesn't make any changes to validation yet. It would be good to fix that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have a feeling that this will resurface eventually, but since the fuzzer isn't complaining I guess it is fine for now. Is the comment ok, or would you prefer a little more detail?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, going to check!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a first glimpse it looks like the validator uses Type::isSubType for its checks. This then leads to a similar issue like this one for getLeastUpperBound, but for isSubType not knowing the enabled features I guess, where the check in isSubType is right == Type::anyref which can only happen in valid code if anyref is enabled.

Can you point me into the right direction what else needs to be updated in the validator?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand, there will be a(nother) validation error anyway if anyref is used but not enabled, via Type::getFeatures().

Copy link
Member

@aheejin aheejin Sep 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this ends up generating anyref in other programs where anyref isn't enabled, eventually it will be caught by the validator, and that basically means one of Binaryen passes have a bug. So I think it's OK to keep this simple and not include types for now, but erroring out here might help Binaryen authors debug their code a little, or something. Anyway I think this should be OK for now.

I think isSubType will be even more OK, because it doesn't create anyref when it isn't enabled.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand, there will be a(nother) validation error anyway if anyref is used but not enabled, via Type::getFeatures().

Aha, I had forgotten that this wouldn't require any changes in the validator itself. I agree with @aheejin that this should be fine, then. We can probably replace the FIXME with a comment pointing out the issue but explaining that it will only happen with invalid code.

Comment on lines 326 to 328
if (wasm.features.hasExceptionHandling()) {
options.push_back(Type::exnref);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found that at most places, similar checks are not nested inside if (hasReferenceTypes). Not an urgent problem, but I'd imagine that this is going to lead to obscure issues where a user specifies --enable-anyref but not --enable-reference-types eventually.

One way to tackle this might be to implicitly enable reference types on setAnyref(true) respectively setExceptionHandling(true), and implicitly disable anyref and exception handling on setReferenceTypes(false)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is a tricky issue. So far we've avoided having any implicit dependencies between features to keep the mental model as simple and predictable as possible. One possibility would be to make it a validation error to have anyref enabled without having reference types enabled. That's a little more annoying for users, but on the other hand it keeps the features simple and there should not be real users of the --enable-anyref feature anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a feature validation step, but naturally this now breaks the fuzzer because it doesn't know of implied features. Any suggestions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up with fb1a789 for now

Copy link
Member

@aheejin aheejin Sep 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the fuzzer will generate types that does not respect dependences. For example, we check for both FeatureSet::ExceptionHandling and FeatureSet::ReferenceTypes whenever we try to add exnref or other EH instructions (By the way I just realized my PR for generating EH instructions for the fuzzer hasn't landed and is pending for months, but anyway). So I guess the fuzzer error you've seen was just complaining about the feature combination itself, right?

So I'm wondering, why do we need to impose the dependences in validator? Unless giving --enable-exception-handling without --enable-reference-types itself in the command line of a VM is an error, I don't think we should treat it as an error.

I think it is a subtle difference; using exnref without enabling --enable-reference-types might be an error. But Wasm spec doesn't say giving --enable-exception-handling without also giving --enable-reference-types is an error, so..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a user's perspective:

  • If making missing required features validation errors: "Oh, I see"
  • If refactoring all code locations: "Why isn't this option picked up, even though it's specified?"
  • If enabling features implicitly: "Why does my binary suddenly utilize reference types, even though it isn't specified?"

From our perspective:

  • If making missing required features validation errors: -
  • If refactoring all code locations: "Look, a new issue"
  • If enabling features implicitly: "Look, a new issue"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial concern was that at most places we have code like

if (hasReferenceTypes) {
  add(funcref)
  add(externref)
}
if (hasExceptionHandling) {
  add(exnref);
}
if (hasAnyref) {
  add(anyref);
}

instead of

if (hasReferenceTypes) {
  add(funcref)
  add(externref)
  if (hasExceptionHandling) {
    add(exnref);
  }
  if (hasAnyref) {
    add(anyref);
  }
}

so it seems possible to yield an exnref or anyref without reference types being enabled, or to otherwise get false positives, and a validation check lets us keep the code as-is.

I wonder where we do something like the first? The only place I can think of that creates exnref or anyref is the fuzzer, and I think it handles them correctly (or you handled them correctly in this PR). Validation or checking wouldn't be as problematic as creating them because they don't bother with anyref of exnref when they don't exist. I could be mistaken; if so please let me know.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking again, there is one place where we are doing the former

if (curr->features.hasReferenceTypes()) {
addImport(curr,
get_funcref,
{Type::i32, Type::i32, Type::funcref},
Type::funcref);
addImport(curr,
set_funcref,
{Type::i32, Type::i32, Type::funcref},
Type::funcref);
addImport(curr,
get_externref,
{Type::i32, Type::i32, Type::externref},
Type::externref);
addImport(curr,
set_externref,
{Type::i32, Type::i32, Type::externref},
Type::externref);
}
if (curr->features.hasExceptionHandling()) {
addImport(
curr, get_exnref, {Type::i32, Type::i32, Type::exnref}, Type::exnref);
addImport(
curr, set_exnref, {Type::i32, Type::i32, Type::exnref}, Type::exnref);
}

and I somehow assumed that this is some sort of convention, even though it probably isn't. Would suggest to move the hasExceptionHandling into hasReferenceTypes there as well so there can't be instrumented get_exnref/set_exnref functions taking an exnref type if reference types is not enabled, but exception handling is. Validating separately still seems fine to me, though, for clarity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Haven't checked other locations where just hasExceptionHandling is checked, but hasReferenceTypes is not)

Copy link
Member

@aheejin aheejin Sep 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for finding this. That is a special pass that generates imports, and I agree that moving hasExceptionHandling condition into hasReferenceTypes is the right call.. Can you add that to this PR if possible?

But I don't think there are many, if at all, passes that generates anyref of exnref out of thin air. (Except for the fuzzer, but it is not a pass) One function that can generate them and can be called from Binaryen passes is getLeastUpperBound, as you pointed out, and if that function creates a type that's not valid that means we have a bug, and that will be caught by the validator. So I don't think there will be mass refactoring necessary.

I just checked what V8 and wabt do for feature dependencies, and actually they are both doing the implication thing, so if you only enable EH that automatically enables reference types too, even if you don't explicitly enable it. Maybe we should consider doing a similar thing in future.

@dcodeIO
Copy link
Contributor Author

dcodeIO commented Sep 9, 2020

Some early fuzzing:

Invocations so far:
   FuzzExec: 5875
   CompareVMs: 1441
   CheckDeterminism: 510
   Wasm2JS: 1351
   Asyncify: 1412

ITERATION: 6816

@tlively
Copy link
Member

tlively commented Sep 9, 2020

This LGTM once those two conversations are resolved 👍

@aheejin
Copy link
Member

aheejin commented Sep 9, 2020

Thanks for doing this. I quickly glanced over a few re-enabled subtype tests and I am a little confused; some tests seem to have their supertype-subtype order reversed compared to the original tests; and it is hard to check that because, disabled tests in #3084 already have their types changed.

The one I discovered was done by comparing my local code (without changes from #3084) and this PR. I just started looking at this code just now, but can you hold off on landing this?

@dcodeIO
Copy link
Contributor Author

dcodeIO commented Sep 9, 2020

Yeah, these changes can be confusing. Previously we had

nullref <: externref | funcref | exnref

with nullref being a common subtype, but now we have

externref | funcref | exnref <: anyref

wit anyref being a common supertype, effectively reversing the order.

@aheejin
Copy link
Member

aheejin commented Sep 9, 2020

Oh sorry. You're right, and I was aware of that. I thought the case I meant was not that and really had a relationship reversed, but I just realized that the line wrapping changed and I was confused by that. Anyway, the case I discovered didn't have any problem. Sorry for the confusion and thanks!

case Type::anyref:
assert(literal.isNull() && "TODO: non-null anyref values");
o << "anyref(null)";
break;
case Type::externref:
assert(literal.isNull() && "TODO: non-null externref values");
o << "externref(null)";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which case do we need non-null anyref and externref literals?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This came up earlier in that these are currently not supported by Literal, which only can represent nulls of any reference type, but Thomas mentioned that we'd want to support these eventually, analogous to ExceptionPackage for exnref, so the interpreter can run code by representing non-null anyref etc. at least abstractly.

Comment on lines 326 to 328
if (wasm.features.hasExceptionHandling()) {
options.push_back(Type::exnref);
}
Copy link
Member

@aheejin aheejin Sep 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the fuzzer will generate types that does not respect dependences. For example, we check for both FeatureSet::ExceptionHandling and FeatureSet::ReferenceTypes whenever we try to add exnref or other EH instructions (By the way I just realized my PR for generating EH instructions for the fuzzer hasn't landed and is pending for months, but anyway). So I guess the fuzzer error you've seen was just complaining about the feature combination itself, right?

So I'm wondering, why do we need to impose the dependences in validator? Unless giving --enable-exception-handling without --enable-reference-types itself in the command line of a VM is an error, I don't think we should treat it as an error.

I think it is a subtle difference; using exnref without enabling --enable-reference-types might be an error. But Wasm spec doesn't say giving --enable-exception-handling without also giving --enable-reference-types is an error, so..

Co-authored-by: Thomas Lively <[email protected]>
Copy link
Member

@aheejin aheejin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, and thanks!

@dcodeIO
Copy link
Contributor Author

dcodeIO commented Sep 10, 2020

Please let me know whether the recent series of commits sufficiently addresses your concerns or if I misunderstood something :)

CHANGELOG.md Outdated
@@ -16,6 +16,9 @@ Current Trunk
-------------
- Remove asm2wasm, which supported Emscripten's fastcomp backend, after fastcomp
was removed.
- Enabling the exception handling feature now requires enabling the reference
types feature as well since `exnref` depends on it. The same applies to the
new anyref feature where `anyref` depends on reference types.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been the case before (that you have to specify dependent features too), so nothing has changed here. If we later add feature implying feature then we need to write something here.

But it'd be worth noting that --enable-anyref is added, and what it does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this because it is a validation error now (as of the current state of this PR), but wasn't before. Previously one would only get a validation error further down the road if --enable-exception-handling was specified, an exnref emitted somehow but --enable-reference-types omitted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split the entry into two parts, with one mentioning the new feature and one mentioning that not enabling implied features is a validation error now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, I misinterpreted the comment. I actually forgot that this PR makes it a validation error if dependent flags aren't specified. I was not in favor of that, but maybe we should do feature implication later.

options.push_back(Type::externref);
}
options.push_back(Type::funcref);
options.push_back(Type::externref);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too opinionated about this, but the fuzzer still does not assume feature dependencies have been completely resolved; it checks that here and there. How about restoring this condition and maybe we can remove it when we add feature implication feature.

This comment was marked as outdated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gone ahead and restored the condition. Hope it's correct now :)

@dcodeIO
Copy link
Contributor Author

dcodeIO commented Sep 11, 2020

Feel free to merge if it looks ok to you now. Not quite confident that your concerns are addressed. Meanwhile looking for something useful to do, now eyeballing the name section, i.e. module and local names.

@tlively tlively merged commit 1927577 into WebAssembly:master Sep 11, 2020
@MaxGraey MaxGraey mentioned this pull request Sep 11, 2020
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.

3 participants