Commit c4113c47 authored by Manos Koukoutos's avatar Manos Koukoutos Committed by Commit Bot

[wasm][refactor] Simplify/unify parts of the function decoder

Changes:
- Remove TypeCheckBranchResult. Change TypeCheckBranch() to return bool.
  Refactor call sites to reflect this (decouple current code
  reachability check from type check).
- Unify TypeCheckBranch(), TypeCheckFallthrough(), and the type-checking
  part of Return() into TypeCheckStackAgainstMerge().
- Make sure all TypeCheck* functions are only called within VALIDATE.
- In graph-builder-interface, rename end_env -> merge_env to reflect
  its function for loops.
- Change expected error messages in some tests.

Change-Id: I857edc18db9c2454ad12d539ffe7a10e96367710
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2839560Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74100}
parent 32281d62
...@@ -2647,34 +2647,28 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2647,34 +2647,28 @@ class WasmFullDecoder : public WasmDecoder<validate> {
if (!this->Validate(this->pc_ + 1, imm, control_.size())) return 0; if (!this->Validate(this->pc_ + 1, imm, control_.size())) return 0;
Value ref_object = Peek(0, 0); Value ref_object = Peek(0, 0);
Control* c = control_at(imm.depth); Control* c = control_at(imm.depth);
TypeCheckBranchResult check_result = TypeCheckBranch(c, true, 1); if (!VALIDATE(TypeCheckBranch<true>(c, 1))) return 0;
switch (ref_object.type.kind()) { switch (ref_object.type.kind()) {
case kBottom: case kBottom:
// We are in a polymorphic stack. Leave the stack as it is. // We are in a polymorphic stack. Leave the stack as it is.
DCHECK(check_result != kReachableBranch); DCHECK(!current_code_reachable_and_ok_);
break; break;
case kRef: case kRef:
// For a non-nullable value, we won't take the branch, and can leave // For a non-nullable value, we won't take the branch, and can leave
// the stack as it is. // the stack as it is.
break; break;
case kOptRef: { case kOptRef: {
if (V8_LIKELY(check_result == kReachableBranch)) {
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnNull, ref_object, imm.depth); CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnNull, ref_object, imm.depth);
Value result = CreateValue( Value result = CreateValue(
ValueType::Ref(ref_object.type.heap_type(), kNonNullable)); ValueType::Ref(ref_object.type.heap_type(), kNonNullable));
// The result of br_on_null has the same value as the argument (but a // The result of br_on_null has the same value as the argument (but a
// non-nullable type). // non-nullable type).
CALL_INTERFACE_IF_OK_AND_REACHABLE(Forward, ref_object, &result); CALL_INTERFACE_IF_OK_AND_REACHABLE(Forward, ref_object, &result);
c->br_merge()->reached = true; if (V8_LIKELY(current_code_reachable_and_ok_)) {
c->br_merge()->reached = true;
}
Drop(ref_object); Drop(ref_object);
Push(result); Push(result);
} else {
// Even in non-reachable code, we need to push a value of the correct
// type to the stack.
Drop(ref_object);
Push(CreateValue(
ValueType::Ref(ref_object.type.heap_type(), kNonNullable)));
}
break; break;
} }
default: default:
...@@ -2753,7 +2747,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2753,7 +2747,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
this->DecodeError("else already present for if"); this->DecodeError("else already present for if");
return 0; return 0;
} }
if (!TypeCheckFallThru()) return 0; if (!VALIDATE(TypeCheckFallThru())) return 0;
c->kind = kControlIfElse; c->kind = kControlIfElse;
CALL_INTERFACE_IF_OK_AND_PARENT_REACHABLE(Else, c); CALL_INTERFACE_IF_OK_AND_PARENT_REACHABLE(Else, c);
if (c->reachable()) c->end_merge.reached = true; if (c->reachable()) c->end_merge.reached = true;
...@@ -2797,7 +2791,6 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2797,7 +2791,6 @@ class WasmFullDecoder : public WasmDecoder<validate> {
this->local_types_.begin() + c->locals_count); this->local_types_.begin() + c->locals_count);
this->num_locals_ -= c->locals_count; this->num_locals_ -= c->locals_count;
} }
if (!TypeCheckFallThru()) return 0;
if (control_.size() == 1) { if (control_.size() == 1) {
// If at the last (implicit) control, check we are at end. // If at the last (implicit) control, check we are at end.
...@@ -2808,10 +2801,12 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2808,10 +2801,12 @@ class WasmFullDecoder : public WasmDecoder<validate> {
// The result of the block is the return value. // The result of the block is the return value.
trace_msg->Append("\n" TRACE_INST_FORMAT, startrel(this->pc_), trace_msg->Append("\n" TRACE_INST_FORMAT, startrel(this->pc_),
"(implicit) return"); "(implicit) return");
DoReturn(); DoReturn<kStrictCounting, kFallthroughMerge>();
control_.clear(); control_.clear();
return 1; return 1;
} }
if (!VALIDATE(TypeCheckFallThru())) return 0;
PopControl(); PopControl();
return 1; return 1;
} }
...@@ -2852,9 +2847,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2852,9 +2847,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
BranchDepthImmediate<validate> imm(this, this->pc_ + 1); BranchDepthImmediate<validate> imm(this, this->pc_ + 1);
if (!this->Validate(this->pc_ + 1, imm, control_.size())) return 0; if (!this->Validate(this->pc_ + 1, imm, control_.size())) return 0;
Control* c = control_at(imm.depth); Control* c = control_at(imm.depth);
TypeCheckBranchResult check_result = TypeCheckBranch(c, false, 0); if (!VALIDATE(TypeCheckBranch<false>(c, 0))) return 0;
if (V8_LIKELY(check_result == kReachableBranch)) { CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOrRet, imm.depth, 0);
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOrRet, imm.depth, 0); if (V8_LIKELY(current_code_reachable_and_ok_)) {
c->br_merge()->reached = true; c->br_merge()->reached = true;
} }
EndControl(); EndControl();
...@@ -2866,9 +2861,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2866,9 +2861,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
if (!this->Validate(this->pc_ + 1, imm, control_.size())) return 0; if (!this->Validate(this->pc_ + 1, imm, control_.size())) return 0;
Value cond = Peek(0, 0, kWasmI32); Value cond = Peek(0, 0, kWasmI32);
Control* c = control_at(imm.depth); Control* c = control_at(imm.depth);
TypeCheckBranchResult check_result = TypeCheckBranch(c, true, 1); if (!VALIDATE(TypeCheckBranch<true>(c, 1))) return 0;
if (V8_LIKELY(check_result == kReachableBranch)) { CALL_INTERFACE_IF_OK_AND_REACHABLE(BrIf, cond, imm.depth);
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrIf, cond, imm.depth); if (V8_LIKELY(current_code_reachable_and_ok_)) {
c->br_merge()->reached = true; c->br_merge()->reached = true;
} }
Drop(cond); Drop(cond);
...@@ -2909,9 +2904,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2909,9 +2904,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
arity); arity);
return 0; return 0;
} }
TypeCheckBranchResult check_result = if (!VALIDATE(TypeCheckBranch<false>(control_at(target), 1))) return 0;
TypeCheckBranch(control_at(target), false, 1);
if (V8_UNLIKELY(check_result == kInvalidStack)) return 0;
} }
} }
...@@ -2928,22 +2921,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2928,22 +2921,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
} }
DECODE(Return) { DECODE(Return) {
if (V8_LIKELY(current_code_reachable_and_ok_)) { return DoReturn<kNonStrictCounting, kReturnMerge>() ? 1 : 0;
if (!VALIDATE(TypeCheckReturn())) return 0;
DoReturn();
} else {
// We inspect all return values from the stack to check their type.
// Since we deal with unreachable code, we do not have to keep the
// values.
int num_returns = static_cast<int>(this->sig_->return_count());
for (int i = num_returns - 1, depth = 0; i >= 0; --i, ++depth) {
Peek(depth, i, this->sig_->GetReturn(i));
}
Drop(num_returns);
}
EndControl();
return 1;
} }
DECODE(Unreachable) { DECODE(Unreachable) {
...@@ -3657,7 +3635,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -3657,7 +3635,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
// If the parent block was reachable before, but the popped control does not // If the parent block was reachable before, but the popped control does not
// return to here, this block becomes "spec only reachable". // return to here, this block becomes "spec only reachable".
if (!parent_reached) SetSucceedingCodeDynamicallyUnreachable(); if (!parent_reached) SetSucceedingCodeDynamicallyUnreachable();
current_code_reachable_and_ok_ = control_.back().reachable(); current_code_reachable_and_ok_ = this->ok() && control_.back().reachable();
} }
int DecodeLoadMem(LoadType type, int prefix_len = 1) { int DecodeLoadMem(LoadType type, int prefix_len = 1) {
...@@ -4338,23 +4316,21 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4338,23 +4316,21 @@ class WasmFullDecoder : public WasmDecoder<validate> {
? kWasmBottom ? kWasmBottom
: ValueType::Ref(rtt.type.ref_index(), kNonNullable)); : ValueType::Ref(rtt.type.ref_index(), kNonNullable));
Push(result_on_branch); Push(result_on_branch);
TypeCheckBranchResult check_result = TypeCheckBranch(c, true, 0); if (!VALIDATE(TypeCheckBranch<true>(c, 0))) return 0;
if (V8_LIKELY(check_result == kReachableBranch)) { // This logic ensures that code generation can assume that functions
// This logic ensures that code generation can assume that functions // can only be cast to function types, and data objects to data types.
// can only be cast to function types, and data objects to data types. if (V8_LIKELY(ObjectRelatedWithRtt(obj, rtt))) {
if (V8_LIKELY(ObjectRelatedWithRtt(obj, rtt))) { // The {value_on_branch} parameter we pass to the interface must
// The {value_on_branch} parameter we pass to the interface must // be pointer-identical to the object on the stack, so we can't
// be pointer-identical to the object on the stack, so we can't // reuse {result_on_branch} which was passed-by-value to {Push}.
// reuse {result_on_branch} which was passed-by-value to {Push}. Value* value_on_branch = stack_value(1);
Value* value_on_branch = stack_value(1); CALL_INTERFACE_IF_OK_AND_REACHABLE(
CALL_INTERFACE_IF_OK_AND_REACHABLE( BrOnCast, obj, rtt, value_on_branch, branch_depth.depth);
BrOnCast, obj, rtt, value_on_branch, branch_depth.depth); if (V8_LIKELY(current_code_reachable_and_ok_)) {
c->br_merge()->reached = true; c->br_merge()->reached = true;
} }
// Otherwise the types are unrelated. Do not branch.
} else if (check_result == kInvalidStack) {
return 0;
} }
// Otherwise the types are unrelated. Do not branch.
Drop(result_on_branch); Drop(result_on_branch);
Push(obj); // Restore stack state on fallthrough. Push(obj); // Restore stack state on fallthrough.
return opcode_length + branch_depth.length; return opcode_length + branch_depth.length;
...@@ -4422,25 +4398,23 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4422,25 +4398,23 @@ class WasmFullDecoder : public WasmDecoder<validate> {
Value result_on_branch = Value result_on_branch =
CreateValue(ValueType::Ref(heap_type, kNonNullable)); CreateValue(ValueType::Ref(heap_type, kNonNullable));
Push(result_on_branch); Push(result_on_branch);
TypeCheckBranchResult check_result = TypeCheckBranch(c, true, 0); if (!VALIDATE(TypeCheckBranch<true>(c, 0))) return 0;
if (V8_LIKELY(check_result == kReachableBranch)) { // The {value_on_branch} parameter we pass to the interface must be
// The {value_on_branch} parameter we pass to the interface must be // pointer-identical to the object on the stack, so we can't reuse
// pointer-identical to the object on the stack, so we can't reuse // {result_on_branch} which was passed-by-value to {Push}.
// {result_on_branch} which was passed-by-value to {Push}. Value* value_on_branch = stack_value(1);
Value* value_on_branch = stack_value(1); if (opcode == kExprBrOnFunc) {
if (opcode == kExprBrOnFunc) { CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnFunc, obj, value_on_branch,
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnFunc, obj, value_on_branch, branch_depth.depth);
branch_depth.depth); } else if (opcode == kExprBrOnData) {
} else if (opcode == kExprBrOnData) { CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnData, obj, value_on_branch,
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnData, obj, value_on_branch, branch_depth.depth);
branch_depth.depth); } else {
} else { CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnI31, obj, value_on_branch,
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnI31, obj, value_on_branch, branch_depth.depth);
branch_depth.depth); }
} if (V8_LIKELY(current_code_reachable_and_ok_)) {
c->br_merge()->reached = true; c->br_merge()->reached = true;
} else if (check_result == kInvalidStack) {
return 0;
} }
Drop(result_on_branch); Drop(result_on_branch);
Push(obj); // Restore stack state on fallthrough. Push(obj); // Restore stack state on fallthrough.
...@@ -4626,12 +4600,6 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4626,12 +4600,6 @@ class WasmFullDecoder : public WasmDecoder<validate> {
} }
} }
void DoReturn() {
DCHECK_IMPLIES(current_code_reachable_and_ok_,
stack_size() >= this->sig_->return_count());
CALL_INTERFACE_IF_OK_AND_REACHABLE(DoReturn, 0);
}
V8_INLINE void EnsureStackSpace(int slots_needed) { V8_INLINE void EnsureStackSpace(int slots_needed) {
if (V8_LIKELY(stack_capacity_end_ - stack_end_ >= slots_needed)) return; if (V8_LIKELY(stack_capacity_end_ - stack_end_ >= slots_needed)) return;
GrowStackSpace(slots_needed); GrowStackSpace(slots_needed);
...@@ -4755,7 +4723,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4755,7 +4723,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
// TODO(wasm): This check is often redundant. // TODO(wasm): This check is often redundant.
if (V8_UNLIKELY(stack_size() < limit + count)) { if (V8_UNLIKELY(stack_size() < limit + count)) {
// Popping past the current control start in reachable code. // Popping past the current control start in reachable code.
if (!VALIDATE(!control_.back().reachable())) { if (!VALIDATE(!current_code_reachable_and_ok_)) {
NotEnoughArgumentsError(0); NotEnoughArgumentsError(0);
} }
// Pop what we can. // Pop what we can.
...@@ -4767,21 +4735,68 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4767,21 +4735,68 @@ class WasmFullDecoder : public WasmDecoder<validate> {
// For more descriptive call sites: // For more descriptive call sites:
V8_INLINE void Drop(const Value& /* unused */) { Drop(1); } V8_INLINE void Drop(const Value& /* unused */) { Drop(1); }
// Check if any values that may exist on top of the stack are compatible with enum StackElementsCountMode : bool {
// {merge}. If {push_branch_values}, push back to the stack values based on kNonStrictCounting = false,
// the type of {merge} (this is needed for conditional branches due to their kStrictCounting = true
// typing rules, and fallthroughs so that the outer control finds enough };
// values on the stack).
// TODO(manoskouk): We expect this behavior to change, either due to enum MergeType { kBranchMerge, kReturnMerge, kFallthroughMerge };
// relaxation of dead code verification, or the introduction of subtyping.
// {drop_values} is the number of stack values that will be dropped before the // - If the current code is reachable check if the current stack values are
// branch is taken. This is currently 1 for br (condition), br_table (index) // compatible with {merge} based on their number and types. Disregard the
// and br_on_null (reference), and 0 for all other branches. // first {drop_values} on the stack. If {strict_count}, check that
bool TypeCheckUnreachableMerge(Merge<Value>& merge, bool push_branch_values, // #(stack elements) == {merge->arity}, otherwise
uint32_t drop_values) { // #(stack elements) >= {merge->arity}.
int arity = merge.arity; // - If the current code is unreachable, check if any values that may exist on
// top of the stack are compatible with {merge}. If {push_branch_values},
// push back to the stack values based on the type of {merge} (this is
// needed for conditional branches due to their typing rules, and
// fallthroughs so that the outer control finds the expected values on the
// stack). TODO(manoskouk): We expect the unreachable-code behavior to
// change, either due to relaxation of dead code verification, or the
// introduction of subtyping.
template <StackElementsCountMode strict_count, bool push_branch_values,
MergeType merge_type>
bool TypeCheckStackAgainstMerge(uint32_t drop_values, Merge<Value>* merge) {
static_assert(validate, "Call this function only within VALIDATE");
constexpr const char* merge_description =
merge_type == kBranchMerge
? "branch"
: merge_type == kReturnMerge ? "return" : "fallthru";
uint32_t arity = merge->arity;
uint32_t actual = stack_size() - control_.back().stack_depth;
if (V8_LIKELY(current_code_reachable_and_ok_)) {
if (V8_UNLIKELY(strict_count ? actual != drop_values + arity
: actual < drop_values + arity)) {
this->DecodeError("expected %u elements on the stack for %s, found %u",
arity, merge_description,
actual >= drop_values ? actual - drop_values : 0);
return false;
}
// Typecheck the topmost {merge->arity} values on the stack.
Value* stack_values = stack_end_ - (arity + drop_values);
for (uint32_t i = 0; i < arity; ++i) {
Value& val = stack_values[i];
Value& old = (*merge)[i];
if (!IsSubtypeOf(val.type, old.type, this->module_)) {
this->DecodeError("type error in %s[%u] (expected %s, got %s)",
merge_description, i, old.type.name().c_str(),
val.type.name().c_str());
return false;
}
}
return true;
}
// Unreachable code validation starts here.
if (V8_UNLIKELY(strict_count && actual > drop_values + arity)) {
this->DecodeError("expected %u elements on the stack for %s, found %u",
arity, merge_description,
actual >= drop_values ? actual - drop_values : 0);
return false;
}
// TODO(manoskouk): Use similar code as above if we keep unreachable checks.
for (int i = arity - 1, depth = drop_values; i >= 0; --i, ++depth) { for (int i = arity - 1, depth = drop_values; i >= 0; --i, ++depth) {
Peek(depth, i, merge[i].type); Peek(depth, i, (*merge)[i].type);
} }
if (push_branch_values) { if (push_branch_values) {
Drop(drop_values); Drop(drop_values);
...@@ -4790,7 +4805,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4790,7 +4805,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
// than requested. So ensuring stack space here is not redundant. // than requested. So ensuring stack space here is not redundant.
EnsureStackSpace(drop_values + arity); EnsureStackSpace(drop_values + arity);
// Push values of the correct type onto the stack. // Push values of the correct type onto the stack.
for (int i = 0; i < arity; i++) Push(CreateValue(merge[i].type)); for (int i = 0; i < static_cast<int>(arity); i++) {
Push(CreateValue((*merge)[i].type));
}
// {drop_values} are about to be dropped anyway, so we can forget their // {drop_values} are about to be dropped anyway, so we can forget their
// previous types, but we do have to maintain the correct stack height. // previous types, but we do have to maintain the correct stack height.
for (uint32_t i = 0; i < drop_values; i++) { for (uint32_t i = 0; i < drop_values; i++) {
...@@ -4800,36 +4817,29 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4800,36 +4817,29 @@ class WasmFullDecoder : public WasmDecoder<validate> {
return this->ok(); return this->ok();
} }
template <StackElementsCountMode strict_count, MergeType merge_type>
bool DoReturn() {
if (!VALIDATE((TypeCheckStackAgainstMerge<strict_count, false, merge_type>(
0, &control_.front().end_merge)))) {
return false;
}
DCHECK_IMPLIES(current_code_reachable_and_ok_,
stack_size() >= this->sig_->return_count());
CALL_INTERFACE_IF_OK_AND_REACHABLE(DoReturn, 0);
EndControl();
return true;
}
int startrel(const byte* ptr) { return static_cast<int>(ptr - this->start_); } int startrel(const byte* ptr) { return static_cast<int>(ptr - this->start_); }
void FallThrough() { void FallThrough() {
Control* c = &control_.back(); Control* c = &control_.back();
DCHECK_NE(c->kind, kControlLoop); DCHECK_NE(c->kind, kControlLoop);
if (!TypeCheckFallThru()) return; if (!VALIDATE(TypeCheckFallThru())) return;
CALL_INTERFACE_IF_OK_AND_REACHABLE(FallThruTo, c); CALL_INTERFACE_IF_OK_AND_REACHABLE(FallThruTo, c);
if (c->reachable()) c->end_merge.reached = true; if (c->reachable()) c->end_merge.reached = true;
} }
bool TypeCheckMergeValues(Control* c, uint32_t drop_values,
Merge<Value>* merge) {
static_assert(validate, "Call this function only within VALIDATE");
DCHECK(merge == &c->start_merge || merge == &c->end_merge);
DCHECK_GE(stack_size() - drop_values, c->stack_depth + merge->arity);
Value* stack_values = stack_value(merge->arity + drop_values);
// Typecheck the topmost {merge->arity} values on the stack.
for (uint32_t i = 0; i < merge->arity; ++i) {
Value& val = stack_values[i];
Value& old = (*merge)[i];
if (!VALIDATE(IsSubtypeOf(val.type, old.type, this->module_))) {
this->DecodeError("type error in merge[%u] (expected %s, got %s)", i,
old.type.name().c_str(), val.type.name().c_str());
return false;
}
}
return true;
}
bool TypeCheckOneArmedIf(Control* c) { bool TypeCheckOneArmedIf(Control* c) {
static_assert(validate, "Call this function only within VALIDATE"); static_assert(validate, "Call this function only within VALIDATE");
DCHECK(c->is_onearmed_if()); DCHECK(c->is_onearmed_if());
...@@ -4852,47 +4862,10 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4852,47 +4862,10 @@ class WasmFullDecoder : public WasmDecoder<validate> {
bool TypeCheckFallThru() { bool TypeCheckFallThru() {
static_assert(validate, "Call this function only within VALIDATE"); static_assert(validate, "Call this function only within VALIDATE");
Control& c = control_.back(); return TypeCheckStackAgainstMerge<kStrictCounting, true, kFallthroughMerge>(
if (V8_LIKELY(c.reachable())) { 0, &control_.back().end_merge);
uint32_t expected = c.end_merge.arity;
DCHECK_GE(stack_size(), c.stack_depth);
uint32_t actual = stack_size() - c.stack_depth;
// Fallthrus must match the arity of the control exactly.
if (!VALIDATE(actual == expected)) {
this->DecodeError(
"expected %u elements on the stack for fallthru to @%d, found %u",
expected, startrel(c.pc()), actual);
return false;
}
if (expected == 0) return true; // Fast path.
return TypeCheckMergeValues(&c, 0, &c.end_merge);
}
// Type-check an unreachable fallthru. First we do an arity check, then a
// type check. Note that type-checking may require an adjustment of the
// stack, if some stack values are missing to match the block signature.
Merge<Value>& merge = c.end_merge;
int arity = static_cast<int>(merge.arity);
int available = static_cast<int>(stack_size()) - c.stack_depth;
// For fallthrus, not more than the needed values should be available.
if (!VALIDATE(available <= arity)) {
this->DecodeError(
"expected %u elements on the stack for fallthru to @%d, found %u",
arity, startrel(c.pc()), available);
return false;
}
// Pop all values from the stack for type checking of existing stack
// values.
return TypeCheckUnreachableMerge(merge, true, 0);
} }
enum TypeCheckBranchResult {
kReachableBranch,
kUnreachableBranch,
kInvalidStack,
};
// If the current code is reachable, check if the current stack values are // If the current code is reachable, check if the current stack values are
// compatible with a jump to {c}, based on their number and types. // compatible with a jump to {c}, based on their number and types.
// Otherwise, we have a polymorphic stack: check if any values that may exist // Otherwise, we have a polymorphic stack: check if any values that may exist
...@@ -4903,65 +4876,11 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4903,65 +4876,11 @@ class WasmFullDecoder : public WasmDecoder<validate> {
// {drop_values} is the number of stack values that will be dropped before the // {drop_values} is the number of stack values that will be dropped before the
// branch is taken. This is currently 1 for for br (condition), br_table // branch is taken. This is currently 1 for for br (condition), br_table
// (index) and br_on_null (reference), and 0 for all other branches. // (index) and br_on_null (reference), and 0 for all other branches.
TypeCheckBranchResult TypeCheckBranch(Control* c, bool push_branch_values, template <bool push_branch_values>
uint32_t drop_values) { bool TypeCheckBranch(Control* c, uint32_t drop_values) {
if (V8_LIKELY(control_.back().reachable())) { static_assert(validate, "Call this function only within VALIDATE");
// We only do type-checking here. This is only needed during validation. return TypeCheckStackAgainstMerge<kNonStrictCounting, push_branch_values,
if (!validate) return kReachableBranch; kBranchMerge>(drop_values, c->br_merge());
// Branches must have at least the number of values expected; can have
// more.
uint32_t expected = c->br_merge()->arity;
if (expected == 0) return kReachableBranch; // Fast path.
uint32_t limit = control_.back().stack_depth;
if (!VALIDATE(stack_size() >= limit + drop_values + expected)) {
uint32_t actual = stack_size() - limit;
actual -= std::min(actual, drop_values);
this->DecodeError(
"expected %u elements on the stack for br to @%d, found %u",
expected, startrel(c->pc()), actual);
return kInvalidStack;
}
return TypeCheckMergeValues(c, drop_values, c->br_merge())
? kReachableBranch
: kInvalidStack;
}
return TypeCheckUnreachableMerge(*c->br_merge(), push_branch_values,
drop_values)
? kUnreachableBranch
: kInvalidStack;
}
bool TypeCheckReturn() {
int num_returns = static_cast<int>(this->sig_->return_count());
// No type checking is needed if there are no returns.
if (num_returns == 0) return true;
// Returns must have at least the number of values expected; can have more.
int num_available =
static_cast<int>(stack_size()) - control_.back().stack_depth;
if (!VALIDATE(num_available >= num_returns)) {
this->DecodeError(
"expected %u elements on the stack for return, found %u", num_returns,
num_available);
return false;
}
// Typecheck the topmost {num_returns} values on the stack.
// This line requires num_returns > 0.
Value* stack_values = stack_end_ - num_returns;
for (int i = 0; i < num_returns; ++i) {
Value& val = stack_values[i];
ValueType expected_type = this->sig_->GetReturn(i);
if (!VALIDATE(IsSubtypeOf(val.type, expected_type, this->module_))) {
this->DecodeError("type error in return[%u] (expected %s, got %s)", i,
expected_type.name().c_str(),
val.type.name().c_str());
return false;
}
}
return true;
} }
void onFirstError() override { void onFirstError() override {
......
...@@ -95,7 +95,7 @@ class WasmGraphBuildingInterface { ...@@ -95,7 +95,7 @@ class WasmGraphBuildingInterface {
}; };
struct Control : public ControlBase<Value, validate> { struct Control : public ControlBase<Value, validate> {
SsaEnv* end_env = nullptr; // end environment for the construct. SsaEnv* merge_env = nullptr; // merge environment for the construct.
SsaEnv* false_env = nullptr; // false environment (only for if). SsaEnv* false_env = nullptr; // false environment (only for if).
TryInfo* try_info = nullptr; // information about try statements. TryInfo* try_info = nullptr; // information about try statements.
int32_t previous_catch = -1; // previous Control with a catch. int32_t previous_catch = -1; // previous Control with a catch.
...@@ -154,15 +154,15 @@ class WasmGraphBuildingInterface { ...@@ -154,15 +154,15 @@ class WasmGraphBuildingInterface {
void Block(FullDecoder* decoder, Control* block) { void Block(FullDecoder* decoder, Control* block) {
// The branch environment is the outer environment. // The branch environment is the outer environment.
block->end_env = ssa_env_; block->merge_env = ssa_env_;
SetEnv(Steal(decoder->zone(), ssa_env_)); SetEnv(Steal(decoder->zone(), ssa_env_));
} }
void Loop(FullDecoder* decoder, Control* block) { void Loop(FullDecoder* decoder, Control* block) {
SsaEnv* finish_try_env = Steal(decoder->zone(), ssa_env_); // This is the merge environment at the beginning of the loop.
block->end_env = finish_try_env; SsaEnv* merge_env = Steal(decoder->zone(), ssa_env_);
SetEnv(finish_try_env); block->merge_env = merge_env;
// The continue environment is the inner environment. SetEnv(merge_env);
ssa_env_->state = SsaEnv::kMerged; ssa_env_->state = SsaEnv::kMerged;
...@@ -214,15 +214,15 @@ class WasmGraphBuildingInterface { ...@@ -214,15 +214,15 @@ class WasmGraphBuildingInterface {
control()); control());
} }
// Now we setup a new environment for the inside of the loop.
SetEnv(Split(decoder->zone(), ssa_env_)); SetEnv(Split(decoder->zone(), ssa_env_));
builder_->StackCheck(decoder->position()); builder_->StackCheck(decoder->position());
ssa_env_->SetNotMerged(); ssa_env_->SetNotMerged();
if (!decoder->ok()) return;
// Wrap input merge into phis. // Wrap input merge into phis.
for (uint32_t i = 0; i < block->start_merge.arity; ++i) { for (uint32_t i = 0; i < block->start_merge.arity; ++i) {
Value& val = block->start_merge[i]; Value& val = block->start_merge[i];
TFNode* inputs[] = {val.node, block->end_env->control}; TFNode* inputs[] = {val.node, block->merge_env->control};
val.node = builder_->Phi(val.type, 1, inputs); val.node = builder_->Phi(val.type, 1, inputs);
} }
} }
...@@ -236,7 +236,7 @@ class WasmGraphBuildingInterface { ...@@ -236,7 +236,7 @@ class WasmGraphBuildingInterface {
SsaEnv* try_env = Steal(decoder->zone(), outer_env); SsaEnv* try_env = Steal(decoder->zone(), outer_env);
SetEnv(try_env); SetEnv(try_env);
TryInfo* try_info = decoder->zone()->New<TryInfo>(catch_env); TryInfo* try_info = decoder->zone()->New<TryInfo>(catch_env);
block->end_env = outer_env; block->merge_env = outer_env;
block->try_info = try_info; block->try_info = try_info;
} }
...@@ -244,12 +244,12 @@ class WasmGraphBuildingInterface { ...@@ -244,12 +244,12 @@ class WasmGraphBuildingInterface {
TFNode* if_true = nullptr; TFNode* if_true = nullptr;
TFNode* if_false = nullptr; TFNode* if_false = nullptr;
builder_->BranchNoHint(cond.node, &if_true, &if_false); builder_->BranchNoHint(cond.node, &if_true, &if_false);
SsaEnv* end_env = ssa_env_; SsaEnv* merge_env = ssa_env_;
SsaEnv* false_env = Split(decoder->zone(), ssa_env_); SsaEnv* false_env = Split(decoder->zone(), ssa_env_);
false_env->control = if_false; false_env->control = if_false;
SsaEnv* true_env = Steal(decoder->zone(), ssa_env_); SsaEnv* true_env = Steal(decoder->zone(), ssa_env_);
true_env->control = if_true; true_env->control = if_true;
if_block->end_env = end_env; if_block->merge_env = merge_env;
if_block->false_env = false_env; if_block->false_env = false_env;
SetEnv(true_env); SetEnv(true_env);
} }
...@@ -290,7 +290,7 @@ class WasmGraphBuildingInterface { ...@@ -290,7 +290,7 @@ class WasmGraphBuildingInterface {
MergeValuesInto(decoder, block, &block->end_merge, values); MergeValuesInto(decoder, block, &block->end_merge, values);
} }
// Now continue with the merged environment. // Now continue with the merged environment.
SetEnv(block->end_env); SetEnv(block->merge_env);
} }
void UnOp(FullDecoder* decoder, WasmOpcode opcode, const Value& value, void UnOp(FullDecoder* decoder, WasmOpcode opcode, const Value& value,
...@@ -1197,7 +1197,7 @@ class WasmGraphBuildingInterface { ...@@ -1197,7 +1197,7 @@ class WasmGraphBuildingInterface {
Value* values) { Value* values) {
DCHECK(merge == &c->start_merge || merge == &c->end_merge); DCHECK(merge == &c->start_merge || merge == &c->end_merge);
SsaEnv* target = c->end_env; SsaEnv* target = c->merge_env;
// This has to be computed before calling Goto(). // This has to be computed before calling Goto().
const bool first = target->state == SsaEnv::kUnreachable; const bool first = target->state == SsaEnv::kUnreachable;
......
*%(basename)s:9: CompileError: WebAssembly.compile(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru to @1, found 0 @+24 *%(basename)s:9: CompileError: WebAssembly.compile(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru, found 0 @+24
let rethrow = e => setTimeout(_ => {throw e}, 0); let rethrow = e => setTimeout(_ => {throw e}, 0);
^ ^
CompileError: WebAssembly.compile(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru to @1, found 0 @+24 CompileError: WebAssembly.compile(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru, found 0 @+24
*%(basename)s:9: CompileError: WebAssembly.instantiate(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru to @1, found 0 @+24 *%(basename)s:9: CompileError: WebAssembly.instantiate(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru, found 0 @+24
let rethrow = e => setTimeout(_ => {throw e}, 0); let rethrow = e => setTimeout(_ => {throw e}, 0);
^ ^
CompileError: WebAssembly.instantiate(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru to @1, found 0 @+24 CompileError: WebAssembly.instantiate(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru, found 0 @+24
*%(basename)s:11: CompileError: WebAssembly.compileStreaming(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru to @1, found 0 @+24 *%(basename)s:11: CompileError: WebAssembly.compileStreaming(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru, found 0 @+24
let rethrow = e => setTimeout(_ => {throw e}, 0); let rethrow = e => setTimeout(_ => {throw e}, 0);
^ ^
CompileError: WebAssembly.compileStreaming(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru to @1, found 0 @+24 CompileError: WebAssembly.compileStreaming(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru, found 0 @+24
*%(basename)s:11: CompileError: WebAssembly.instantiateStreaming(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru to @1, found 0 @+24 *%(basename)s:11: CompileError: WebAssembly.instantiateStreaming(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru, found 0 @+24
let rethrow = e => setTimeout(_ => {throw e}, 0); let rethrow = e => setTimeout(_ => {throw e}, 0);
^ ^
CompileError: WebAssembly.instantiateStreaming(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru to @1, found 0 @+24 CompileError: WebAssembly.instantiateStreaming(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru, found 0 @+24
*%(basename)s:9: CompileError: WebAssembly.Module(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru to @1, found 0 @+24 *%(basename)s:9: CompileError: WebAssembly.Module(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru, found 0 @+24
new WebAssembly.Module(builder.toBuffer()); new WebAssembly.Module(builder.toBuffer());
^ ^
CompileError: WebAssembly.Module(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru to @1, found 0 @+24 CompileError: WebAssembly.Module(): Compiling function #0:"f" failed: expected 1 elements on the stack for fallthru, found 0 @+24
at *%(basename)s:9:1 at *%(basename)s:9:1
...@@ -29,5 +29,5 @@ kExprEnd, // @21 ...@@ -29,5 +29,5 @@ kExprEnd, // @21
assertThrows( assertThrows(
() => {builder.toModule()}, WebAssembly.CompileError, () => {builder.toModule()}, WebAssembly.CompileError,
'WebAssembly.Module(): Compiling function #0:\"main\" failed: ' + 'WebAssembly.Module(): Compiling function #0:\"main\" failed: ' +
'type error in merge[0] (expected f32, got i32) @+57'); 'type error in branch[0] (expected f32, got i32) @+57');
})(); })();
...@@ -67,7 +67,7 @@ assertPromiseResult(async function badFunctionInTheMiddle() { ...@@ -67,7 +67,7 @@ assertPromiseResult(async function badFunctionInTheMiddle() {
await assertCompileError( await assertCompileError(
buffer, buffer,
'Compiling function #10:\"bad\" failed: ' + 'Compiling function #10:\"bad\" failed: ' +
'expected 1 elements on the stack for fallthru to @1, found 0 @+94'); 'expected 1 elements on the stack for fallthru, found 0 @+94');
}()); }());
assertPromiseResult(async function importWithoutCode() { assertPromiseResult(async function importWithoutCode() {
......
...@@ -35,7 +35,7 @@ load('test/mjsunit/wasm/wasm-module-builder.js'); ...@@ -35,7 +35,7 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
assertPromiseResult(WebAssembly.compile(bytes) assertPromiseResult(WebAssembly.compile(bytes)
.then(assertUnreachable, .then(assertUnreachable,
error => assertEquals("WebAssembly.compile(): type error in " + error => assertEquals("WebAssembly.compile(): type error in " +
"merge[0] (expected i32, got i64) @+56", error.message))); "fallthru[0] (expected i32, got i64) @+56", error.message)));
})(); })();
(function testCompileEmptyModule() { (function testCompileEmptyModule() {
......
...@@ -33,7 +33,7 @@ load('test/mjsunit/wasm/wasm-module-builder.js'); ...@@ -33,7 +33,7 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
assertThrows(() => builder.toModule(), assertThrows(() => builder.toModule(),
WebAssembly.CompileError, WebAssembly.CompileError,
"WebAssembly.Module(): Compiling function #0:\"id\" failed: type error " + "WebAssembly.Module(): Compiling function #0:\"id\" failed: type error " +
"in merge[0] (expected i32, got i64) @+56"); "in fallthru[0] (expected i32, got i64) @+56");
})(); })();
(function testCompileEmptyModule() { (function testCompileEmptyModule() {
......
...@@ -3841,7 +3841,7 @@ TEST_F(FunctionBodyDecoderTest, BrOnNull) { ...@@ -3841,7 +3841,7 @@ TEST_F(FunctionBodyDecoderTest, BrOnNull) {
WASM_I32V(0), kExprSelectWithType, 1, WASM_I32V(0), kExprSelectWithType, 1,
WASM_REF_TYPE(reps[0]))}, WASM_REF_TYPE(reps[0]))},
kAppendEnd, kAppendEnd,
"expected 1 elements on the stack for br to @1, found 0"); "expected 1 elements on the stack for branch, found 0");
} }
TEST_F(FunctionBodyDecoderTest, GCStruct) { TEST_F(FunctionBodyDecoderTest, GCStruct) {
...@@ -3880,8 +3880,7 @@ TEST_F(FunctionBodyDecoderTest, GCStruct) { ...@@ -3880,8 +3880,7 @@ TEST_F(FunctionBodyDecoderTest, GCStruct) {
&sig_r_v, &sig_r_v,
{WASM_STRUCT_NEW_WITH_RTT(struct_type_index, WASM_I32V(0), WASM_I32V(1), {WASM_STRUCT_NEW_WITH_RTT(struct_type_index, WASM_I32V(0), WASM_I32V(1),
WASM_RTT_CANON(struct_type_index))}, WASM_RTT_CANON(struct_type_index))},
kAppendEnd, kAppendEnd, "expected 1 elements on the stack for fallthru, found 2");
"expected 1 elements on the stack for fallthru to @1, found 2");
// Mistyped arguments. // Mistyped arguments.
ExpectFailure(&sig_v_r, ExpectFailure(&sig_v_r,
{WASM_STRUCT_NEW_WITH_RTT(struct_type_index, WASM_LOCAL_GET(0), {WASM_STRUCT_NEW_WITH_RTT(struct_type_index, WASM_LOCAL_GET(0),
...@@ -3927,7 +3926,7 @@ TEST_F(FunctionBodyDecoderTest, GCStruct) { ...@@ -3927,7 +3926,7 @@ TEST_F(FunctionBodyDecoderTest, GCStruct) {
ExpectFailure( ExpectFailure(
&sig_f_r, &sig_f_r,
{WASM_STRUCT_GET(struct_type_index, field_index, WASM_LOCAL_GET(0))}, {WASM_STRUCT_GET(struct_type_index, field_index, WASM_LOCAL_GET(0))},
kAppendEnd, "type error in merge[0] (expected f32, got i32)"); kAppendEnd, "type error in fallthru[0] (expected f32, got i32)");
/** struct.set **/ /** struct.set **/
ExpectValidates(&sig_v_r, {WASM_STRUCT_SET(struct_type_index, field_index, ExpectValidates(&sig_v_r, {WASM_STRUCT_SET(struct_type_index, field_index,
...@@ -3953,7 +3952,7 @@ TEST_F(FunctionBodyDecoderTest, GCStruct) { ...@@ -3953,7 +3952,7 @@ TEST_F(FunctionBodyDecoderTest, GCStruct) {
{WASM_STRUCT_SET(struct_type_index, field_index, {WASM_STRUCT_SET(struct_type_index, field_index,
WASM_LOCAL_GET(0), WASM_I32V(0))}, WASM_LOCAL_GET(0), WASM_I32V(0))},
kAppendEnd, kAppendEnd,
"expected 1 elements on the stack for fallthru to @1, found 0"); "expected 1 elements on the stack for fallthru, found 0");
// Setting immutable field. // Setting immutable field.
ExpectFailure( ExpectFailure(
sigs.v_v(), sigs.v_v(),
...@@ -4063,7 +4062,7 @@ TEST_F(FunctionBodyDecoderTest, GCArray) { ...@@ -4063,7 +4062,7 @@ TEST_F(FunctionBodyDecoderTest, GCArray) {
ExpectFailure( ExpectFailure(
&sig_f_r, &sig_f_r,
{WASM_ARRAY_GET(array_type_index, WASM_LOCAL_GET(0), WASM_I32V(5))}, {WASM_ARRAY_GET(array_type_index, WASM_LOCAL_GET(0), WASM_I32V(5))},
kAppendEnd, "type error in merge[0] (expected f32, got funcref)"); kAppendEnd, "type error in fallthru[0] (expected f32, got funcref)");
// array.get_s/u fail. // array.get_s/u fail.
ExpectFailure( ExpectFailure(
...@@ -4112,7 +4111,8 @@ TEST_F(FunctionBodyDecoderTest, GCArray) { ...@@ -4112,7 +4111,8 @@ TEST_F(FunctionBodyDecoderTest, GCArray) {
{WASM_ARRAY_LEN(array_type_index, WASM_LOCAL_GET(0))}); {WASM_ARRAY_LEN(array_type_index, WASM_LOCAL_GET(0))});
// Wrong return type. // Wrong return type.
ExpectFailure(&sig_f_r, {WASM_ARRAY_LEN(array_type_index, WASM_LOCAL_GET(0))}, ExpectFailure(&sig_f_r, {WASM_ARRAY_LEN(array_type_index, WASM_LOCAL_GET(0))},
kAppendEnd, "type error in merge[0] (expected f32, got i32)"); kAppendEnd,
"type error in fallthru[0] (expected f32, got i32)");
// Non-array type index. // Non-array type index.
ExpectFailure(&sig_i_r, ExpectFailure(&sig_i_r,
{WASM_ARRAY_LEN(struct_type_index, WASM_LOCAL_GET(0))}, {WASM_ARRAY_LEN(struct_type_index, WASM_LOCAL_GET(0))},
...@@ -4234,7 +4234,7 @@ TEST_F(FunctionBodyDecoderTest, RttCanon) { ...@@ -4234,7 +4234,7 @@ TEST_F(FunctionBodyDecoderTest, RttCanon) {
ValueType rtt2 = ValueType::Rtt(type_index, 1); ValueType rtt2 = ValueType::Rtt(type_index, 1);
FunctionSig sig2(1, 0, &rtt2); FunctionSig sig2(1, 0, &rtt2);
ExpectFailure(&sig2, {WASM_RTT_CANON(type_index)}, kAppendEnd, ExpectFailure(&sig2, {WASM_RTT_CANON(type_index)}, kAppendEnd,
"type error in merge[0]"); "type error in fallthru[0]");
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment