-
Notifications
You must be signed in to change notification settings - Fork 840
Add support for (either ...) in wast #8421
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -319,31 +319,31 @@ struct Shell { | |
| switch (nan.kind) { | ||
| case NaNKind::Canonical: | ||
| if (val.type != nan.type || !val.isCanonicalNaN()) { | ||
| err << "expected canonical " << nan.type << " NaN, got " << val; | ||
| err << "canonical " << nan.type; | ||
| return Err{err.str()}; | ||
| } | ||
| break; | ||
| case NaNKind::Arithmetic: | ||
| if (val.type != nan.type || !val.isArithmeticNaN()) { | ||
| err << "expected arithmetic " << nan.type << " NaN, got " << val; | ||
| err << "arithmetic " << nan.type; | ||
| return Err{err.str()}; | ||
| } | ||
| break; | ||
| } | ||
| return Ok{}; | ||
| } | ||
|
|
||
| Result<> checkLane(Literal val, LaneResult expected, Index index) { | ||
| Result<> checkLane(Literal val, LaneResult expected) { | ||
| std::stringstream err; | ||
| if (auto* e = std::get_if<Literal>(&expected)) { | ||
| if (*e != val) { | ||
| err << "expected " << *e << ", got " << val << " at lane " << index; | ||
| err << *e; | ||
| return Err{err.str()}; | ||
| } | ||
| } else if (auto* nan = std::get_if<NaNResult>(&expected)) { | ||
| auto check = checkNaN(val, *nan); | ||
| if (auto* e = check.getErr()) { | ||
| err << e->msg << " at lane " << index; | ||
| err << e->msg; | ||
| return Err{err.str()}; | ||
| } | ||
| } else { | ||
|
|
@@ -352,6 +352,83 @@ struct Shell { | |
| return Ok{}; | ||
| } | ||
|
|
||
| struct AlternativeErr { | ||
| std::string expected; | ||
| int lane = -1; | ||
| }; | ||
|
|
||
| Result<Ok, AlternativeErr> matchAlternative(const Literal& val, | ||
| const ExpectedResult& expected, | ||
| bool isAlternative) { | ||
| std::stringstream err; | ||
|
|
||
| if (auto* v = std::get_if<Literal>(&expected)) { | ||
| if (val != *v) { | ||
| if (val.type.isVector() && v->type.isVector() && isAlternative) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me why we check |
||
| auto valLanes = val.getLanesI32x4(); | ||
| auto expLanes = v->getLanesI32x4(); | ||
|
Comment on lines
+368
to
+369
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we avoid hard-coding a lane interpretation here? Maybe we should always use |
||
| for (int i = 0; i < 4; ++i) { | ||
| if (valLanes[i] != expLanes[i]) { | ||
| err << "0x" << std::setfill('0') << std::setw(8) << std::hex | ||
| << expLanes[i] << std::dec; | ||
| return AlternativeErr{err.str(), i}; | ||
| } | ||
| } | ||
| } | ||
| err << *v; | ||
| return AlternativeErr{err.str()}; | ||
| } | ||
| } else if (auto* ref = std::get_if<RefResult>(&expected)) { | ||
| if (!val.type.isRef() || | ||
| !HeapType::isSubType(val.type.getHeapType(), ref->type)) { | ||
|
Comment on lines
+382
to
+383
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably check that the value is not null here, too. |
||
| err << ref->type; | ||
| return AlternativeErr{err.str()}; | ||
| } | ||
| } else if ([[maybe_unused]] auto* nullRef = | ||
| std::get_if<NullRefResult>(&expected)) { | ||
| if (!val.isNull()) { | ||
| err << "ref.null"; | ||
| return AlternativeErr{err.str()}; | ||
| } | ||
| } else if (auto* nan = std::get_if<NaNResult>(&expected)) { | ||
| auto check = checkNaN(val, *nan); | ||
| if (auto* e = check.getErr()) { | ||
| err << e->msg; | ||
| return AlternativeErr{err.str()}; | ||
| } | ||
| } else if (auto* lanes = std::get_if<LaneResults>(&expected)) { | ||
| switch (lanes->size()) { | ||
| case 4: { | ||
| auto vals = val.getLanesF32x4(); | ||
| for (int i = 0; i < 4; ++i) { | ||
| auto check = checkLane(vals[i], (*lanes)[i]); | ||
| if (auto* e = check.getErr()) { | ||
| err << e->msg; | ||
| return AlternativeErr{err.str(), i}; | ||
| } | ||
| } | ||
| break; | ||
| } | ||
| case 2: { | ||
| auto vals = val.getLanesF64x2(); | ||
| for (int i = 0; i < 2; ++i) { | ||
| auto check = checkLane(vals[i], (*lanes)[i]); | ||
| if (auto* e = check.getErr()) { | ||
| err << e->msg; | ||
| return AlternativeErr{err.str(), i}; | ||
| } | ||
| } | ||
| break; | ||
| } | ||
| default: | ||
| WASM_UNREACHABLE("unexpected number of lanes"); | ||
| } | ||
| } else { | ||
| WASM_UNREACHABLE("unexpected expectation"); | ||
| } | ||
| return Ok{}; | ||
| } | ||
|
|
||
| Result<> assertReturn(AssertReturn& assn) { | ||
| std::stringstream err; | ||
| auto result = doAction(assn.action); | ||
|
|
@@ -374,63 +451,55 @@ struct Shell { | |
| return ss.str(); | ||
| }; | ||
|
|
||
| Literal val = (*values)[i]; | ||
| auto& expected = assn.expected[i]; | ||
| if (auto* v = std::get_if<Literal>(&expected)) { | ||
| if (val != *v) { | ||
| err << "expected " << *v << ", got " << val << atIndex(); | ||
| return Err{err.str()}; | ||
| } | ||
| } else if (auto* ref = std::get_if<RefResult>(&expected)) { | ||
| if (!val.type.isRef() || | ||
| !HeapType::isSubType(val.type.getHeapType(), ref->type)) { | ||
| err << "expected " << ref->type << " reference, got " << val | ||
| << atIndex(); | ||
| return Err{err.str()}; | ||
| } | ||
| } else if ([[maybe_unused]] auto* nullRef = | ||
| std::get_if<NullRefResult>(&expected)) { | ||
| if (!val.isNull()) { | ||
| err << "expected ref.null, got " << val << atIndex(); | ||
| return Err{err.str()}; | ||
| // non-either case | ||
| if (assn.expected[i].size() == 1) { | ||
| auto result = matchAlternative( | ||
| (*values)[i], assn.expected[i][0], /*isAlternative=*/false); | ||
| if (auto* e = result.getErr()) { | ||
| std::stringstream ss; | ||
| ss << "expected " << e->expected << ", got " << (*values)[i]; | ||
| if (e->lane != -1) { | ||
| ss << " at lane " << e->lane; | ||
| } | ||
| ss << atIndex(); | ||
| return Err{ss.str()}; | ||
| } | ||
| } else if (auto* nan = std::get_if<NaNResult>(&expected)) { | ||
| auto check = checkNaN(val, *nan); | ||
| if (auto* e = check.getErr()) { | ||
| err << e->msg << atIndex(); | ||
| return Err{err.str()}; | ||
| continue; | ||
| } | ||
|
|
||
| // either case | ||
| bool success = false; | ||
| std::vector<std::string> expecteds; | ||
| int failedLane = -1; | ||
| for (const auto& alternative : assn.expected[i]) { | ||
| auto result = | ||
| matchAlternative((*values)[i], alternative, /*isAlternative=*/true); | ||
| if (!result.getErr()) { | ||
| success = true; | ||
| break; | ||
| } | ||
| } else if (auto* lanes = std::get_if<LaneResults>(&expected)) { | ||
| switch (lanes->size()) { | ||
| case 4: { | ||
| auto vals = val.getLanesF32x4(); | ||
| for (Index i = 0; i < 4; ++i) { | ||
| auto check = checkLane(vals[i], (*lanes)[i], i); | ||
| if (auto* e = check.getErr()) { | ||
| err << e->msg << atIndex(); | ||
| return Err{err.str()}; | ||
| } | ||
| } | ||
| break; | ||
| } | ||
| case 2: { | ||
| auto vals = val.getLanesF64x2(); | ||
| for (Index i = 0; i < 2; ++i) { | ||
| auto check = checkLane(vals[i], (*lanes)[i], i); | ||
| if (auto* e = check.getErr()) { | ||
| err << e->msg << atIndex(); | ||
| return Err{err.str()}; | ||
| } | ||
| } | ||
| break; | ||
| } | ||
| default: | ||
| WASM_UNREACHABLE("unexpected number of lanes"); | ||
|
|
||
| auto* e = result.getErr(); | ||
| expecteds.push_back(e->expected); | ||
| if (failedLane == -1 && e->lane != -1) { | ||
| failedLane = e->lane; | ||
| } | ||
| } else { | ||
| WASM_UNREACHABLE("unexpected expectation"); | ||
| } | ||
| if (success) { | ||
| continue; | ||
| } | ||
| std::stringstream ss; | ||
| ss << "Expected one of (" << String::join(expecteds, " | ") << ")"; | ||
| if (failedLane != -1) { | ||
| ss << " at lane " << failedLane; | ||
| } | ||
| ss << " but got " << (*values)[i]; | ||
|
|
||
| ss << atIndex(); | ||
|
|
||
| return Err{ss.str()}; | ||
| } | ||
|
|
||
| return Ok{}; | ||
| } | ||
|
|
||
|
|
||
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.
Why is this better or more correct?
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.
I based this off of Google's style guide for absl::StatusOr which recommends moving from the whole thing instead of just the value inside to indicate that the whole result type is moved-from. Basically it would likely be a mistake to do something with
rafter this and movingrinstead of*rhints that to the reader. I got it from here (ctrl+f "Moving from the value"): https://abseil.io/tips/181There 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.
Interesting! Makes sense, though I find it slightly less intuitive. Seems fine to introduce here.