[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/+/2839560 Reviewed-by: Clemens Backes <clemensb@chromium.org> Commit-Queue: Manos Koukoutos <manoskouk@chromium.org> Cr-Commit-Position: refs/heads/master@{#74100}
This commit is contained in:
parent
32281d6247
commit
c4113c4705
@ -2647,34 +2647,28 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
if (!this->Validate(this->pc_ + 1, imm, control_.size())) return 0;
|
||||
Value ref_object = Peek(0, 0);
|
||||
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()) {
|
||||
case kBottom:
|
||||
// We are in a polymorphic stack. Leave the stack as it is.
|
||||
DCHECK(check_result != kReachableBranch);
|
||||
DCHECK(!current_code_reachable_and_ok_);
|
||||
break;
|
||||
case kRef:
|
||||
// For a non-nullable value, we won't take the branch, and can leave
|
||||
// the stack as it is.
|
||||
break;
|
||||
case kOptRef: {
|
||||
if (V8_LIKELY(check_result == kReachableBranch)) {
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnNull, ref_object, imm.depth);
|
||||
Value result = CreateValue(
|
||||
ValueType::Ref(ref_object.type.heap_type(), kNonNullable));
|
||||
// The result of br_on_null has the same value as the argument (but a
|
||||
// non-nullable type).
|
||||
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);
|
||||
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;
|
||||
}
|
||||
default:
|
||||
@ -2753,7 +2747,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
this->DecodeError("else already present for if");
|
||||
return 0;
|
||||
}
|
||||
if (!TypeCheckFallThru()) return 0;
|
||||
if (!VALIDATE(TypeCheckFallThru())) return 0;
|
||||
c->kind = kControlIfElse;
|
||||
CALL_INTERFACE_IF_OK_AND_PARENT_REACHABLE(Else, c);
|
||||
if (c->reachable()) c->end_merge.reached = true;
|
||||
@ -2797,7 +2791,6 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
this->local_types_.begin() + c->locals_count);
|
||||
this->num_locals_ -= c->locals_count;
|
||||
}
|
||||
if (!TypeCheckFallThru()) return 0;
|
||||
|
||||
if (control_.size() == 1) {
|
||||
// If at the last (implicit) control, check we are at end.
|
||||
@ -2808,10 +2801,12 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
// The result of the block is the return value.
|
||||
trace_msg->Append("\n" TRACE_INST_FORMAT, startrel(this->pc_),
|
||||
"(implicit) return");
|
||||
DoReturn();
|
||||
DoReturn<kStrictCounting, kFallthroughMerge>();
|
||||
control_.clear();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!VALIDATE(TypeCheckFallThru())) return 0;
|
||||
PopControl();
|
||||
return 1;
|
||||
}
|
||||
@ -2852,9 +2847,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
BranchDepthImmediate<validate> imm(this, this->pc_ + 1);
|
||||
if (!this->Validate(this->pc_ + 1, imm, control_.size())) return 0;
|
||||
Control* c = control_at(imm.depth);
|
||||
TypeCheckBranchResult check_result = TypeCheckBranch(c, false, 0);
|
||||
if (V8_LIKELY(check_result == kReachableBranch)) {
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOrRet, imm.depth, 0);
|
||||
if (!VALIDATE(TypeCheckBranch<false>(c, 0))) return 0;
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOrRet, imm.depth, 0);
|
||||
if (V8_LIKELY(current_code_reachable_and_ok_)) {
|
||||
c->br_merge()->reached = true;
|
||||
}
|
||||
EndControl();
|
||||
@ -2866,9 +2861,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
if (!this->Validate(this->pc_ + 1, imm, control_.size())) return 0;
|
||||
Value cond = Peek(0, 0, kWasmI32);
|
||||
Control* c = control_at(imm.depth);
|
||||
TypeCheckBranchResult check_result = TypeCheckBranch(c, true, 1);
|
||||
if (V8_LIKELY(check_result == kReachableBranch)) {
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrIf, cond, imm.depth);
|
||||
if (!VALIDATE(TypeCheckBranch<true>(c, 1))) return 0;
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrIf, cond, imm.depth);
|
||||
if (V8_LIKELY(current_code_reachable_and_ok_)) {
|
||||
c->br_merge()->reached = true;
|
||||
}
|
||||
Drop(cond);
|
||||
@ -2909,9 +2904,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
arity);
|
||||
return 0;
|
||||
}
|
||||
TypeCheckBranchResult check_result =
|
||||
TypeCheckBranch(control_at(target), false, 1);
|
||||
if (V8_UNLIKELY(check_result == kInvalidStack)) return 0;
|
||||
if (!VALIDATE(TypeCheckBranch<false>(control_at(target), 1))) return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2928,22 +2921,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
}
|
||||
|
||||
DECODE(Return) {
|
||||
if (V8_LIKELY(current_code_reachable_and_ok_)) {
|
||||
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;
|
||||
return DoReturn<kNonStrictCounting, kReturnMerge>() ? 1 : 0;
|
||||
}
|
||||
|
||||
DECODE(Unreachable) {
|
||||
@ -3657,7 +3635,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
// If the parent block was reachable before, but the popped control does not
|
||||
// return to here, this block becomes "spec only reachable".
|
||||
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) {
|
||||
@ -4338,23 +4316,21 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
? kWasmBottom
|
||||
: ValueType::Ref(rtt.type.ref_index(), kNonNullable));
|
||||
Push(result_on_branch);
|
||||
TypeCheckBranchResult check_result = TypeCheckBranch(c, true, 0);
|
||||
if (V8_LIKELY(check_result == kReachableBranch)) {
|
||||
// This logic ensures that code generation can assume that functions
|
||||
// can only be cast to function types, and data objects to data types.
|
||||
if (V8_LIKELY(ObjectRelatedWithRtt(obj, rtt))) {
|
||||
// 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 {result_on_branch} which was passed-by-value to {Push}.
|
||||
Value* value_on_branch = stack_value(1);
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(
|
||||
BrOnCast, obj, rtt, value_on_branch, branch_depth.depth);
|
||||
if (!VALIDATE(TypeCheckBranch<true>(c, 0))) return 0;
|
||||
// This logic ensures that code generation can assume that functions
|
||||
// can only be cast to function types, and data objects to data types.
|
||||
if (V8_LIKELY(ObjectRelatedWithRtt(obj, rtt))) {
|
||||
// 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 {result_on_branch} which was passed-by-value to {Push}.
|
||||
Value* value_on_branch = stack_value(1);
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(
|
||||
BrOnCast, obj, rtt, value_on_branch, branch_depth.depth);
|
||||
if (V8_LIKELY(current_code_reachable_and_ok_)) {
|
||||
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);
|
||||
Push(obj); // Restore stack state on fallthrough.
|
||||
return opcode_length + branch_depth.length;
|
||||
@ -4422,25 +4398,23 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
Value result_on_branch =
|
||||
CreateValue(ValueType::Ref(heap_type, kNonNullable));
|
||||
Push(result_on_branch);
|
||||
TypeCheckBranchResult check_result = TypeCheckBranch(c, true, 0);
|
||||
if (V8_LIKELY(check_result == kReachableBranch)) {
|
||||
// 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
|
||||
// {result_on_branch} which was passed-by-value to {Push}.
|
||||
Value* value_on_branch = stack_value(1);
|
||||
if (opcode == kExprBrOnFunc) {
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnFunc, obj, value_on_branch,
|
||||
branch_depth.depth);
|
||||
} else if (opcode == kExprBrOnData) {
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnData, obj, value_on_branch,
|
||||
branch_depth.depth);
|
||||
} else {
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnI31, obj, value_on_branch,
|
||||
branch_depth.depth);
|
||||
}
|
||||
if (!VALIDATE(TypeCheckBranch<true>(c, 0))) return 0;
|
||||
// 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
|
||||
// {result_on_branch} which was passed-by-value to {Push}.
|
||||
Value* value_on_branch = stack_value(1);
|
||||
if (opcode == kExprBrOnFunc) {
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnFunc, obj, value_on_branch,
|
||||
branch_depth.depth);
|
||||
} else if (opcode == kExprBrOnData) {
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnData, obj, value_on_branch,
|
||||
branch_depth.depth);
|
||||
} else {
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(BrOnI31, obj, value_on_branch,
|
||||
branch_depth.depth);
|
||||
}
|
||||
if (V8_LIKELY(current_code_reachable_and_ok_)) {
|
||||
c->br_merge()->reached = true;
|
||||
} else if (check_result == kInvalidStack) {
|
||||
return 0;
|
||||
}
|
||||
Drop(result_on_branch);
|
||||
Push(obj); // Restore stack state on fallthrough.
|
||||
@ -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) {
|
||||
if (V8_LIKELY(stack_capacity_end_ - stack_end_ >= slots_needed)) return;
|
||||
GrowStackSpace(slots_needed);
|
||||
@ -4755,7 +4723,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
// TODO(wasm): This check is often redundant.
|
||||
if (V8_UNLIKELY(stack_size() < limit + count)) {
|
||||
// Popping past the current control start in reachable code.
|
||||
if (!VALIDATE(!control_.back().reachable())) {
|
||||
if (!VALIDATE(!current_code_reachable_and_ok_)) {
|
||||
NotEnoughArgumentsError(0);
|
||||
}
|
||||
// Pop what we can.
|
||||
@ -4767,21 +4735,68 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
// For more descriptive call sites:
|
||||
V8_INLINE void Drop(const Value& /* unused */) { Drop(1); }
|
||||
|
||||
// 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 enough
|
||||
// values on the stack).
|
||||
// TODO(manoskouk): We expect this behavior to change, either due to
|
||||
// relaxation of dead code verification, or the introduction of subtyping.
|
||||
// {drop_values} is the number of stack values that will be dropped before the
|
||||
// branch is taken. This is currently 1 for br (condition), br_table (index)
|
||||
// and br_on_null (reference), and 0 for all other branches.
|
||||
bool TypeCheckUnreachableMerge(Merge<Value>& merge, bool push_branch_values,
|
||||
uint32_t drop_values) {
|
||||
int arity = merge.arity;
|
||||
enum StackElementsCountMode : bool {
|
||||
kNonStrictCounting = false,
|
||||
kStrictCounting = true
|
||||
};
|
||||
|
||||
enum MergeType { kBranchMerge, kReturnMerge, kFallthroughMerge };
|
||||
|
||||
// - If the current code is reachable check if the current stack values are
|
||||
// compatible with {merge} based on their number and types. Disregard the
|
||||
// first {drop_values} on the stack. If {strict_count}, check that
|
||||
// #(stack elements) == {merge->arity}, otherwise
|
||||
// #(stack elements) >= {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) {
|
||||
Peek(depth, i, merge[i].type);
|
||||
Peek(depth, i, (*merge)[i].type);
|
||||
}
|
||||
if (push_branch_values) {
|
||||
Drop(drop_values);
|
||||
@ -4790,7 +4805,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
// than requested. So ensuring stack space here is not redundant.
|
||||
EnsureStackSpace(drop_values + arity);
|
||||
// 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
|
||||
// previous types, but we do have to maintain the correct stack height.
|
||||
for (uint32_t i = 0; i < drop_values; i++) {
|
||||
@ -4800,36 +4817,29 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
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_); }
|
||||
|
||||
void FallThrough() {
|
||||
Control* c = &control_.back();
|
||||
DCHECK_NE(c->kind, kControlLoop);
|
||||
if (!TypeCheckFallThru()) return;
|
||||
if (!VALIDATE(TypeCheckFallThru())) return;
|
||||
CALL_INTERFACE_IF_OK_AND_REACHABLE(FallThruTo, c);
|
||||
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) {
|
||||
static_assert(validate, "Call this function only within VALIDATE");
|
||||
DCHECK(c->is_onearmed_if());
|
||||
@ -4852,47 +4862,10 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
|
||||
bool TypeCheckFallThru() {
|
||||
static_assert(validate, "Call this function only within VALIDATE");
|
||||
Control& c = control_.back();
|
||||
if (V8_LIKELY(c.reachable())) {
|
||||
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);
|
||||
return TypeCheckStackAgainstMerge<kStrictCounting, true, kFallthroughMerge>(
|
||||
0, &control_.back().end_merge);
|
||||
}
|
||||
|
||||
enum TypeCheckBranchResult {
|
||||
kReachableBranch,
|
||||
kUnreachableBranch,
|
||||
kInvalidStack,
|
||||
};
|
||||
|
||||
// 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.
|
||||
// Otherwise, we have a polymorphic stack: check if any values that may exist
|
||||
@ -4903,65 +4876,11 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
// {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
|
||||
// (index) and br_on_null (reference), and 0 for all other branches.
|
||||
TypeCheckBranchResult TypeCheckBranch(Control* c, bool push_branch_values,
|
||||
uint32_t drop_values) {
|
||||
if (V8_LIKELY(control_.back().reachable())) {
|
||||
// We only do type-checking here. This is only needed during validation.
|
||||
if (!validate) return kReachableBranch;
|
||||
|
||||
// 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;
|
||||
template <bool push_branch_values>
|
||||
bool TypeCheckBranch(Control* c, uint32_t drop_values) {
|
||||
static_assert(validate, "Call this function only within VALIDATE");
|
||||
return TypeCheckStackAgainstMerge<kNonStrictCounting, push_branch_values,
|
||||
kBranchMerge>(drop_values, c->br_merge());
|
||||
}
|
||||
|
||||
void onFirstError() override {
|
||||
|
@ -95,7 +95,7 @@ class WasmGraphBuildingInterface {
|
||||
};
|
||||
|
||||
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).
|
||||
TryInfo* try_info = nullptr; // information about try statements.
|
||||
int32_t previous_catch = -1; // previous Control with a catch.
|
||||
@ -154,15 +154,15 @@ class WasmGraphBuildingInterface {
|
||||
|
||||
void Block(FullDecoder* decoder, Control* block) {
|
||||
// The branch environment is the outer environment.
|
||||
block->end_env = ssa_env_;
|
||||
block->merge_env = ssa_env_;
|
||||
SetEnv(Steal(decoder->zone(), ssa_env_));
|
||||
}
|
||||
|
||||
void Loop(FullDecoder* decoder, Control* block) {
|
||||
SsaEnv* finish_try_env = Steal(decoder->zone(), ssa_env_);
|
||||
block->end_env = finish_try_env;
|
||||
SetEnv(finish_try_env);
|
||||
// The continue environment is the inner environment.
|
||||
// This is the merge environment at the beginning of the loop.
|
||||
SsaEnv* merge_env = Steal(decoder->zone(), ssa_env_);
|
||||
block->merge_env = merge_env;
|
||||
SetEnv(merge_env);
|
||||
|
||||
ssa_env_->state = SsaEnv::kMerged;
|
||||
|
||||
@ -214,15 +214,15 @@ class WasmGraphBuildingInterface {
|
||||
control());
|
||||
}
|
||||
|
||||
// Now we setup a new environment for the inside of the loop.
|
||||
SetEnv(Split(decoder->zone(), ssa_env_));
|
||||
builder_->StackCheck(decoder->position());
|
||||
|
||||
ssa_env_->SetNotMerged();
|
||||
if (!decoder->ok()) return;
|
||||
|
||||
// Wrap input merge into phis.
|
||||
for (uint32_t i = 0; i < block->start_merge.arity; ++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);
|
||||
}
|
||||
}
|
||||
@ -236,7 +236,7 @@ class WasmGraphBuildingInterface {
|
||||
SsaEnv* try_env = Steal(decoder->zone(), outer_env);
|
||||
SetEnv(try_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;
|
||||
}
|
||||
|
||||
@ -244,12 +244,12 @@ class WasmGraphBuildingInterface {
|
||||
TFNode* if_true = nullptr;
|
||||
TFNode* if_false = nullptr;
|
||||
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_);
|
||||
false_env->control = if_false;
|
||||
SsaEnv* true_env = Steal(decoder->zone(), ssa_env_);
|
||||
true_env->control = if_true;
|
||||
if_block->end_env = end_env;
|
||||
if_block->merge_env = merge_env;
|
||||
if_block->false_env = false_env;
|
||||
SetEnv(true_env);
|
||||
}
|
||||
@ -290,7 +290,7 @@ class WasmGraphBuildingInterface {
|
||||
MergeValuesInto(decoder, block, &block->end_merge, values);
|
||||
}
|
||||
// Now continue with the merged environment.
|
||||
SetEnv(block->end_env);
|
||||
SetEnv(block->merge_env);
|
||||
}
|
||||
|
||||
void UnOp(FullDecoder* decoder, WasmOpcode opcode, const Value& value,
|
||||
@ -1197,7 +1197,7 @@ class WasmGraphBuildingInterface {
|
||||
Value* values) {
|
||||
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().
|
||||
const bool first = target->state == SsaEnv::kUnreachable;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
*%(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);
|
||||
^
|
||||
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
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
*%(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);
|
||||
^
|
||||
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
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
*%(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);
|
||||
^
|
||||
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
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
*%(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);
|
||||
^
|
||||
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
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
*%(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());
|
||||
^
|
||||
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
|
||||
|
||||
|
@ -29,5 +29,5 @@ kExprEnd, // @21
|
||||
assertThrows(
|
||||
() => {builder.toModule()}, WebAssembly.CompileError,
|
||||
'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() {
|
||||
await assertCompileError(
|
||||
buffer,
|
||||
'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() {
|
||||
|
@ -35,7 +35,7 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
|
||||
assertPromiseResult(WebAssembly.compile(bytes)
|
||||
.then(assertUnreachable,
|
||||
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() {
|
||||
|
@ -33,7 +33,7 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
|
||||
assertThrows(() => builder.toModule(),
|
||||
WebAssembly.CompileError,
|
||||
"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() {
|
||||
|
@ -3841,7 +3841,7 @@ TEST_F(FunctionBodyDecoderTest, BrOnNull) {
|
||||
WASM_I32V(0), kExprSelectWithType, 1,
|
||||
WASM_REF_TYPE(reps[0]))},
|
||||
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) {
|
||||
@ -3880,8 +3880,7 @@ TEST_F(FunctionBodyDecoderTest, GCStruct) {
|
||||
&sig_r_v,
|
||||
{WASM_STRUCT_NEW_WITH_RTT(struct_type_index, WASM_I32V(0), WASM_I32V(1),
|
||||
WASM_RTT_CANON(struct_type_index))},
|
||||
kAppendEnd,
|
||||
"expected 1 elements on the stack for fallthru to @1, found 2");
|
||||
kAppendEnd, "expected 1 elements on the stack for fallthru, found 2");
|
||||
// Mistyped arguments.
|
||||
ExpectFailure(&sig_v_r,
|
||||
{WASM_STRUCT_NEW_WITH_RTT(struct_type_index, WASM_LOCAL_GET(0),
|
||||
@ -3927,7 +3926,7 @@ TEST_F(FunctionBodyDecoderTest, GCStruct) {
|
||||
ExpectFailure(
|
||||
&sig_f_r,
|
||||
{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 **/
|
||||
ExpectValidates(&sig_v_r, {WASM_STRUCT_SET(struct_type_index, field_index,
|
||||
@ -3953,7 +3952,7 @@ TEST_F(FunctionBodyDecoderTest, GCStruct) {
|
||||
{WASM_STRUCT_SET(struct_type_index, field_index,
|
||||
WASM_LOCAL_GET(0), WASM_I32V(0))},
|
||||
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.
|
||||
ExpectFailure(
|
||||
sigs.v_v(),
|
||||
@ -4063,7 +4062,7 @@ TEST_F(FunctionBodyDecoderTest, GCArray) {
|
||||
ExpectFailure(
|
||||
&sig_f_r,
|
||||
{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.
|
||||
ExpectFailure(
|
||||
@ -4112,7 +4111,8 @@ TEST_F(FunctionBodyDecoderTest, GCArray) {
|
||||
{WASM_ARRAY_LEN(array_type_index, WASM_LOCAL_GET(0))});
|
||||
// Wrong return type.
|
||||
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.
|
||||
ExpectFailure(&sig_i_r,
|
||||
{WASM_ARRAY_LEN(struct_type_index, WASM_LOCAL_GET(0))},
|
||||
@ -4234,7 +4234,7 @@ TEST_F(FunctionBodyDecoderTest, RttCanon) {
|
||||
ValueType rtt2 = ValueType::Rtt(type_index, 1);
|
||||
FunctionSig sig2(1, 0, &rtt2);
|
||||
ExpectFailure(&sig2, {WASM_RTT_CANON(type_index)}, kAppendEnd,
|
||||
"type error in merge[0]");
|
||||
"type error in fallthru[0]");
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user