[WASM] Implements catch for the wasm low level exception mechanism.
BUG= Review-Url: https://codereview.chromium.org/2275293002 Cr-Commit-Position: refs/heads/master@{#39881}
This commit is contained in:
parent
6a2169c00d
commit
93e5425c46
@ -1089,6 +1089,7 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
__ Ldr(cp, MemOperand(cp));
|
||||
__ Mov(jssp, Operand(pending_handler_sp_address));
|
||||
__ Ldr(jssp, MemOperand(jssp));
|
||||
__ Mov(csp, jssp);
|
||||
__ Mov(fp, Operand(pending_handler_fp_address));
|
||||
__ Ldr(fp, MemOperand(fp));
|
||||
|
||||
|
@ -1707,13 +1707,17 @@ void InstructionSelector::VisitParameter(Node* node) {
|
||||
Emit(kArchNop, op);
|
||||
}
|
||||
|
||||
namespace {
|
||||
LinkageLocation ExceptionLocation() {
|
||||
return LinkageLocation::ForRegister(kReturnRegister0.code(),
|
||||
MachineType::IntPtr());
|
||||
}
|
||||
}
|
||||
|
||||
void InstructionSelector::VisitIfException(Node* node) {
|
||||
OperandGenerator g(this);
|
||||
Node* call = node->InputAt(1);
|
||||
DCHECK_EQ(IrOpcode::kCall, call->opcode());
|
||||
const CallDescriptor* descriptor = CallDescriptorOf(call->op());
|
||||
Emit(kArchNop, g.DefineAsLocation(node, descriptor->GetReturnLocation(0)));
|
||||
DCHECK_EQ(IrOpcode::kCall, node->InputAt(1)->opcode());
|
||||
Emit(kArchNop, g.DefineAsLocation(node, ExceptionLocation()));
|
||||
}
|
||||
|
||||
|
||||
|
@ -337,6 +337,19 @@ bool WasmGraphBuilder::IsPhiWithMerge(Node* phi, Node* merge) {
|
||||
NodeProperties::GetControlInput(phi) == merge;
|
||||
}
|
||||
|
||||
bool WasmGraphBuilder::ThrowsException(Node* node, Node** if_success,
|
||||
Node** if_exception) {
|
||||
if (node->op()->HasProperty(compiler::Operator::kNoThrow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*if_success = graph()->NewNode(jsgraph()->common()->IfSuccess(), node);
|
||||
*if_exception =
|
||||
graph()->NewNode(jsgraph()->common()->IfException(), node, node);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WasmGraphBuilder::AppendToMerge(Node* merge, Node* from) {
|
||||
DCHECK(IrOpcode::IsMergeOpcode(merge->opcode()));
|
||||
merge->AppendInput(jsgraph()->zone(), from);
|
||||
@ -1724,6 +1737,43 @@ Node* WasmGraphBuilder::Throw(Node* input) {
|
||||
arraysize(parameters), effect_, *control_);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::Catch(Node* input, wasm::WasmCodePosition position) {
|
||||
CommonOperatorBuilder* common = jsgraph()->common();
|
||||
|
||||
Node* parameters[] = {input}; // caught value
|
||||
Node* value =
|
||||
BuildCallToRuntime(Runtime::kWasmGetCaughtExceptionValue, jsgraph(),
|
||||
module_->instance->context, parameters,
|
||||
arraysize(parameters), effect_, *control_);
|
||||
|
||||
Node* is_smi;
|
||||
Node* is_heap;
|
||||
Branch(BuildTestNotSmi(value), &is_heap, &is_smi);
|
||||
|
||||
// is_heap
|
||||
Node* heap_f64 = BuildLoadHeapNumberValue(value, is_heap);
|
||||
// *control_ needs to point to the current control dependency (is_heap) in
|
||||
// case BuildI32SConvertF64 needs to insert nodes that depend on the "current"
|
||||
// control node.
|
||||
*control_ = is_heap;
|
||||
Node* heap_i32 = BuildI32SConvertF64(heap_f64, position);
|
||||
// *control_ contains the control node that should be used when merging the
|
||||
// result for the catch clause. It may be different than *control_ because
|
||||
// BuildI32SConvertF64 may introduce a new control node (used for trapping if
|
||||
// heap_f64 cannot be converted to an i32.
|
||||
is_heap = *control_;
|
||||
|
||||
// is_smi
|
||||
Node* smi_i32 = BuildChangeSmiToInt32(value);
|
||||
|
||||
Node* merge = graph()->NewNode(common->Merge(2), is_heap, is_smi);
|
||||
Node* value_i32 = graph()->NewNode(
|
||||
common->Phi(MachineRepresentation::kWord32, 2), heap_i32, smi_i32, merge);
|
||||
|
||||
*control_ = merge;
|
||||
return value_i32;
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::BuildI32DivS(Node* left, Node* right,
|
||||
wasm::WasmCodePosition position) {
|
||||
MachineOperatorBuilder* m = jsgraph()->machine();
|
||||
@ -1992,8 +2042,9 @@ Node* WasmGraphBuilder::BuildCCall(MachineSignature* sig, Node** args) {
|
||||
return call;
|
||||
}
|
||||
|
||||
Node** WasmGraphBuilder::BuildWasmCall(wasm::FunctionSig* sig, Node** args,
|
||||
wasm::WasmCodePosition position) {
|
||||
Node* WasmGraphBuilder::BuildWasmCall(wasm::FunctionSig* sig, Node** args,
|
||||
Node*** rets,
|
||||
wasm::WasmCodePosition position) {
|
||||
const size_t params = sig->parameter_count();
|
||||
const size_t extra = 2; // effect and control inputs.
|
||||
const size_t count = 1 + params + extra;
|
||||
@ -2013,24 +2064,24 @@ Node** WasmGraphBuilder::BuildWasmCall(wasm::FunctionSig* sig, Node** args,
|
||||
|
||||
*effect_ = call;
|
||||
size_t ret_count = sig->return_count();
|
||||
if (ret_count == 0) return nullptr; // No return value.
|
||||
if (ret_count == 0) return call; // No return value.
|
||||
|
||||
Node** rets = Buffer(ret_count);
|
||||
*rets = Buffer(ret_count);
|
||||
if (ret_count == 1) {
|
||||
// Only a single return value.
|
||||
rets[0] = call;
|
||||
(*rets)[0] = call;
|
||||
} else {
|
||||
// Create projections for all return values.
|
||||
for (size_t i = 0; i < ret_count; i++) {
|
||||
rets[i] = graph()->NewNode(jsgraph()->common()->Projection(i), call,
|
||||
graph()->start());
|
||||
(*rets)[i] = graph()->NewNode(jsgraph()->common()->Projection(i), call,
|
||||
graph()->start());
|
||||
}
|
||||
}
|
||||
return rets;
|
||||
return call;
|
||||
}
|
||||
|
||||
Node** WasmGraphBuilder::CallDirect(uint32_t index, Node** args,
|
||||
wasm::WasmCodePosition position) {
|
||||
Node* WasmGraphBuilder::CallDirect(uint32_t index, Node** args, Node*** rets,
|
||||
wasm::WasmCodePosition position) {
|
||||
DCHECK_NULL(args[0]);
|
||||
|
||||
// Add code object as constant.
|
||||
@ -2039,11 +2090,11 @@ Node** WasmGraphBuilder::CallDirect(uint32_t index, Node** args,
|
||||
args[0] = HeapConstant(code);
|
||||
wasm::FunctionSig* sig = module_->GetFunctionSignature(index);
|
||||
|
||||
return BuildWasmCall(sig, args, position);
|
||||
return BuildWasmCall(sig, args, rets, position);
|
||||
}
|
||||
|
||||
Node** WasmGraphBuilder::CallIndirect(uint32_t index, Node** args,
|
||||
wasm::WasmCodePosition position) {
|
||||
Node* WasmGraphBuilder::CallIndirect(uint32_t index, Node** args, Node*** rets,
|
||||
wasm::WasmCodePosition position) {
|
||||
DCHECK_NOT_NULL(args[0]);
|
||||
DCHECK(module_ && module_->instance);
|
||||
|
||||
@ -2066,11 +2117,11 @@ Node** WasmGraphBuilder::CallIndirect(uint32_t index, Node** args,
|
||||
} else {
|
||||
// No function table. Generate a trap and return a constant.
|
||||
trap_->AddTrapIfFalse(wasm::kTrapFuncInvalid, Int32Constant(0), position);
|
||||
Node** rets = Buffer(sig->return_count());
|
||||
(*rets) = Buffer(sig->return_count());
|
||||
for (size_t i = 0; i < sig->return_count(); i++) {
|
||||
rets[i] = trap_->GetTrapValue(sig->GetReturn(i));
|
||||
(*rets)[i] = trap_->GetTrapValue(sig->GetReturn(i));
|
||||
}
|
||||
return rets;
|
||||
return trap_->GetTrapValue(sig);
|
||||
}
|
||||
Node* table = FunctionTable(0);
|
||||
|
||||
@ -2104,7 +2155,7 @@ Node** WasmGraphBuilder::CallIndirect(uint32_t index, Node** args,
|
||||
*effect_, *control_);
|
||||
|
||||
args[0] = load_code;
|
||||
return BuildWasmCall(sig, args, position);
|
||||
return BuildWasmCall(sig, args, rets, position);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::BuildI32Rol(Node* left, Node* right) {
|
||||
|
@ -135,8 +135,10 @@ class WasmGraphBuilder {
|
||||
wasm::WasmCodePosition position = wasm::kNoCodePosition);
|
||||
Node* GrowMemory(Node* input);
|
||||
Node* Throw(Node* input);
|
||||
Node* Catch(Node* input, wasm::WasmCodePosition position);
|
||||
unsigned InputCount(Node* node);
|
||||
bool IsPhiWithMerge(Node* phi, Node* merge);
|
||||
bool ThrowsException(Node* node, Node** if_success, Node** if_exception);
|
||||
void AppendToMerge(Node* merge, Node* from);
|
||||
void AppendToPhi(Node* phi, Node* from);
|
||||
|
||||
@ -153,10 +155,10 @@ class WasmGraphBuilder {
|
||||
Node* ReturnVoid();
|
||||
Node* Unreachable(wasm::WasmCodePosition position);
|
||||
|
||||
Node** CallDirect(uint32_t index, Node** args,
|
||||
wasm::WasmCodePosition position);
|
||||
Node** CallIndirect(uint32_t index, Node** args,
|
||||
wasm::WasmCodePosition position);
|
||||
Node* CallDirect(uint32_t index, Node** args, Node*** rets,
|
||||
wasm::WasmCodePosition position);
|
||||
Node* CallIndirect(uint32_t index, Node** args, Node*** rets,
|
||||
wasm::WasmCodePosition position);
|
||||
|
||||
void BuildJSToWasmWrapper(Handle<Code> wasm_code, wasm::FunctionSig* sig);
|
||||
void BuildWasmToJSWrapper(Handle<JSReceiver> target, wasm::FunctionSig* sig);
|
||||
@ -240,8 +242,8 @@ class WasmGraphBuilder {
|
||||
Node* MaskShiftCount64(Node* node);
|
||||
|
||||
Node* BuildCCall(MachineSignature* sig, Node** args);
|
||||
Node** BuildWasmCall(wasm::FunctionSig* sig, Node** args,
|
||||
wasm::WasmCodePosition position);
|
||||
Node* BuildWasmCall(wasm::FunctionSig* sig, Node** args, Node*** rets,
|
||||
wasm::WasmCodePosition position);
|
||||
|
||||
Node* BuildF32CopySign(Node* left, Node* right);
|
||||
Node* BuildF64CopySign(Node* left, Node* right);
|
||||
|
@ -1477,6 +1477,15 @@ Script* WasmFrame::script() const {
|
||||
return wasm::WasmDebugInfo::GetFunctionScript(debug_info, function_index());
|
||||
}
|
||||
|
||||
int WasmFrame::LookupExceptionHandlerInTable(int* stack_slots) {
|
||||
DCHECK_NOT_NULL(stack_slots);
|
||||
Code* code = LookupCode();
|
||||
HandlerTable* table = HandlerTable::cast(code->handler_table());
|
||||
int pc_offset = static_cast<int>(pc() - code->entry());
|
||||
*stack_slots = code->stack_slots();
|
||||
return table->LookupReturn(pc_offset);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
|
@ -1056,6 +1056,10 @@ class WasmFrame : public StandardFrame {
|
||||
void Print(StringStream* accumulator, PrintMode mode,
|
||||
int index) const override;
|
||||
|
||||
// Lookup exception handler for current {pc}, returns -1 if none found. Also
|
||||
// returns the stack slot count of the entire frame.
|
||||
int LookupExceptionHandlerInTable(int* data);
|
||||
|
||||
// Determine the code for the frame.
|
||||
Code* unchecked_code() const override;
|
||||
|
||||
|
@ -76,6 +76,11 @@ bool Isolate::is_catchable_by_javascript(Object* exception) {
|
||||
return exception != heap()->termination_exception();
|
||||
}
|
||||
|
||||
bool Isolate::is_catchable_by_wasm(Object* exception) {
|
||||
return is_catchable_by_javascript(exception) &&
|
||||
(exception->IsNumber() || exception->IsSmi());
|
||||
}
|
||||
|
||||
void Isolate::FireBeforeCallEnteredCallback() {
|
||||
for (int i = 0; i < before_call_entered_callbacks_.length(); i++) {
|
||||
before_call_entered_callbacks_.at(i)(reinterpret_cast<v8::Isolate*>(this));
|
||||
|
@ -1149,8 +1149,8 @@ Object* Isolate::UnwindAndFindHandler() {
|
||||
Address handler_sp = nullptr;
|
||||
Address handler_fp = nullptr;
|
||||
|
||||
// Special handling of termination exceptions, uncatchable by JavaScript code,
|
||||
// we unwind the handlers until the top ENTRY handler is found.
|
||||
// Special handling of termination exceptions, uncatchable by JavaScript and
|
||||
// Wasm code, we unwind the handlers until the top ENTRY handler is found.
|
||||
bool catchable_by_js = is_catchable_by_javascript(exception);
|
||||
|
||||
// Compute handler and stack unwinding information by performing a full walk
|
||||
@ -1172,6 +1172,28 @@ Object* Isolate::UnwindAndFindHandler() {
|
||||
break;
|
||||
}
|
||||
|
||||
if (FLAG_wasm_eh_prototype) {
|
||||
if (frame->is_wasm() && is_catchable_by_wasm(exception)) {
|
||||
int stack_slots = 0; // Will contain stack slot count of frame.
|
||||
WasmFrame* wasm_frame = static_cast<WasmFrame*>(frame);
|
||||
offset = wasm_frame->LookupExceptionHandlerInTable(&stack_slots);
|
||||
if (offset >= 0) {
|
||||
// Compute the stack pointer from the frame pointer. This ensures that
|
||||
// argument slots on the stack are dropped as returning would.
|
||||
Address return_sp = frame->fp() +
|
||||
StandardFrameConstants::kFixedFrameSizeAboveFp -
|
||||
stack_slots * kPointerSize;
|
||||
|
||||
// Gather information from the frame.
|
||||
code = frame->LookupCode();
|
||||
|
||||
handler_sp = return_sp;
|
||||
handler_fp = frame->fp();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For optimized frames we perform a lookup in the handler table.
|
||||
if (frame->is_optimized() && catchable_by_js) {
|
||||
OptimizedFrame* js_frame = static_cast<OptimizedFrame*>(frame);
|
||||
|
@ -621,6 +621,7 @@ class Isolate {
|
||||
bool IsExternalHandlerOnTop(Object* exception);
|
||||
|
||||
inline bool is_catchable_by_javascript(Object* exception);
|
||||
inline bool is_catchable_by_wasm(Object* exception);
|
||||
|
||||
// JS execution stack (see frames.h).
|
||||
static Address c_entry_fp(ThreadLocalTop* thread) {
|
||||
|
@ -129,5 +129,16 @@ RUNTIME_FUNCTION(Runtime_WasmThrow) {
|
||||
return isolate->Throw(*isolate->factory()->NewNumberFromInt(thrown_value));
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_WasmGetCaughtExceptionValue) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
Object* exception = args[0];
|
||||
// The unwinder will only deliver exceptions to wasm if the exception is a
|
||||
// Number or a Smi (which we have just converted to a Number.) This logic
|
||||
// lives in Isolate::is_catchable_by_wasm(Object*).
|
||||
CHECK(exception->IsNumber());
|
||||
return exception;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -918,7 +918,8 @@ namespace internal {
|
||||
#define FOR_EACH_INTRINSIC_WASM(F) \
|
||||
F(WasmGrowMemory, 1, 1) \
|
||||
F(WasmThrowTypeError, 0, 1) \
|
||||
F(WasmThrow, 2, 1)
|
||||
F(WasmThrow, 2, 1) \
|
||||
F(WasmGetCaughtExceptionValue, 1, 1)
|
||||
|
||||
#define FOR_EACH_INTRINSIC_RETURN_PAIR(F) \
|
||||
F(LoadLookupSlotForCall, 1, 2)
|
||||
|
@ -70,7 +70,12 @@ struct Value {
|
||||
LocalType type;
|
||||
};
|
||||
|
||||
struct Control;
|
||||
struct TryInfo : public ZoneObject {
|
||||
SsaEnv* catch_env;
|
||||
TFNode* exception;
|
||||
|
||||
explicit TryInfo(SsaEnv* c) : catch_env(c), exception(nullptr) {}
|
||||
};
|
||||
|
||||
struct MergeValues {
|
||||
uint32_t arity;
|
||||
@ -85,13 +90,6 @@ struct MergeValues {
|
||||
}
|
||||
};
|
||||
|
||||
// IncomingBranch is used by exception handling code for managing finally's.
|
||||
struct IncomingBranch {
|
||||
int32_t token_value;
|
||||
Control* target;
|
||||
MergeValues merge;
|
||||
};
|
||||
|
||||
static Value* NO_VALUE = nullptr;
|
||||
|
||||
enum ControlKind { kControlIf, kControlBlock, kControlLoop, kControlTry };
|
||||
@ -103,7 +101,8 @@ struct Control {
|
||||
int stack_depth; // stack height at the beginning of the construct.
|
||||
SsaEnv* end_env; // end environment for the construct.
|
||||
SsaEnv* false_env; // false environment (only for if).
|
||||
SsaEnv* catch_env; // catch environment (only for try).
|
||||
TryInfo* try_info; // Information used for compiling try statements.
|
||||
int32_t previous_catch; // The previous Control (on the stack) with a catch.
|
||||
|
||||
// Values merged into the end of this control construct.
|
||||
MergeValues merge;
|
||||
@ -114,34 +113,39 @@ struct Control {
|
||||
inline bool is_try() const { return kind == kControlTry; }
|
||||
|
||||
// Named constructors.
|
||||
static Control Block(const byte* pc, int stack_depth, SsaEnv* end_env) {
|
||||
static Control Block(const byte* pc, int stack_depth, SsaEnv* end_env,
|
||||
int32_t previous_catch) {
|
||||
return {pc, kControlBlock, stack_depth, end_env,
|
||||
nullptr, nullptr, {0, {NO_VALUE}}};
|
||||
nullptr, nullptr, previous_catch, {0, {NO_VALUE}}};
|
||||
}
|
||||
|
||||
static Control If(const byte* pc, int stack_depth, SsaEnv* end_env,
|
||||
SsaEnv* false_env) {
|
||||
SsaEnv* false_env, int32_t previous_catch) {
|
||||
return {pc, kControlIf, stack_depth, end_env,
|
||||
false_env, nullptr, {0, {NO_VALUE}}};
|
||||
false_env, nullptr, previous_catch, {0, {NO_VALUE}}};
|
||||
}
|
||||
|
||||
static Control Loop(const byte* pc, int stack_depth, SsaEnv* end_env) {
|
||||
static Control Loop(const byte* pc, int stack_depth, SsaEnv* end_env,
|
||||
int32_t previous_catch) {
|
||||
return {pc, kControlLoop, stack_depth, end_env,
|
||||
nullptr, nullptr, {0, {NO_VALUE}}};
|
||||
nullptr, nullptr, previous_catch, {0, {NO_VALUE}}};
|
||||
}
|
||||
|
||||
static Control Try(const byte* pc, int stack_depth, SsaEnv* end_env,
|
||||
SsaEnv* catch_env) {
|
||||
Zone* zone, SsaEnv* catch_env, int32_t previous_catch) {
|
||||
DCHECK_NOT_NULL(catch_env);
|
||||
TryInfo* try_info = new (zone) TryInfo(catch_env);
|
||||
return {pc, kControlTry, stack_depth, end_env,
|
||||
nullptr, catch_env, {0, {NO_VALUE}}};
|
||||
nullptr, try_info, previous_catch, {0, {NO_VALUE}}};
|
||||
}
|
||||
};
|
||||
|
||||
// Macros that build nodes only if there is a graph and the current SSA
|
||||
// environment is reachable from start. This avoids problems with malformed
|
||||
// TF graphs when decoding inputs that have unreachable code.
|
||||
#define BUILD(func, ...) (build() ? builder_->func(__VA_ARGS__) : nullptr)
|
||||
#define BUILD0(func) (build() ? builder_->func() : nullptr)
|
||||
#define BUILD(func, ...) \
|
||||
(build() ? CheckForException(builder_->func(__VA_ARGS__)) : nullptr)
|
||||
#define BUILD0(func) (build() ? CheckForException(builder_->func()) : nullptr)
|
||||
|
||||
// Generic Wasm bytecode decoder with utilities for decoding operands,
|
||||
// lengths, etc.
|
||||
@ -303,6 +307,8 @@ class WasmDecoder : public Decoder {
|
||||
}
|
||||
};
|
||||
|
||||
static const int32_t kNullCatch = -1;
|
||||
|
||||
// The full WASM decoder for bytecode. Both verifies bytecode and generates
|
||||
// a TurboFan IR graph.
|
||||
class WasmFullDecoder : public WasmDecoder {
|
||||
@ -315,7 +321,8 @@ class WasmFullDecoder : public WasmDecoder {
|
||||
local_type_vec_(zone),
|
||||
stack_(zone),
|
||||
control_(zone),
|
||||
last_end_found_(false) {
|
||||
last_end_found_(false),
|
||||
current_catch_(kNullCatch) {
|
||||
local_types_ = &local_type_vec_;
|
||||
}
|
||||
|
||||
@ -441,6 +448,10 @@ class WasmFullDecoder : public WasmDecoder {
|
||||
ZoneVector<Control> control_; // stack of blocks, loops, and ifs.
|
||||
bool last_end_found_;
|
||||
|
||||
int32_t current_catch_;
|
||||
|
||||
TryInfo* current_try_info() { return control_[current_catch_].try_info; }
|
||||
|
||||
inline bool build() { return builder_ && ssa_env_->go(); }
|
||||
|
||||
void InitSsaEnv() {
|
||||
@ -617,7 +628,7 @@ class WasmFullDecoder : public WasmDecoder {
|
||||
BlockTypeOperand operand(this, pc_);
|
||||
SsaEnv* outer_env = ssa_env_;
|
||||
SsaEnv* try_env = Steal(outer_env);
|
||||
SsaEnv* catch_env = Split(try_env);
|
||||
SsaEnv* catch_env = UnreachableEnv();
|
||||
PushTry(outer_env, catch_env);
|
||||
SetEnv("try_catch:start", try_env);
|
||||
SetBlockType(&control_.back(), operand);
|
||||
@ -640,26 +651,30 @@ class WasmFullDecoder : public WasmDecoder {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c->catch_env == nullptr) {
|
||||
error("catch already present for try with catch");
|
||||
if (c->try_info->catch_env == nullptr) {
|
||||
error(pc_, "catch already present for try with catch");
|
||||
break;
|
||||
}
|
||||
|
||||
Goto(ssa_env_, c->end_env);
|
||||
if (ssa_env_->go()) {
|
||||
MergeValuesInto(c);
|
||||
}
|
||||
stack_.resize(c->stack_depth);
|
||||
|
||||
SsaEnv* catch_env = c->catch_env;
|
||||
c->catch_env = nullptr;
|
||||
DCHECK_NOT_NULL(c->try_info);
|
||||
SsaEnv* catch_env = c->try_info->catch_env;
|
||||
c->try_info->catch_env = nullptr;
|
||||
SetEnv("catch:begin", catch_env);
|
||||
current_catch_ = c->previous_catch;
|
||||
|
||||
if (Validate(pc_, operand)) {
|
||||
// TODO(jpp): figure out how thrown value is propagated. It is
|
||||
// unlikely to be a value on the stack.
|
||||
if (ssa_env_->locals) {
|
||||
ssa_env_->locals[operand.index] = nullptr;
|
||||
TFNode* exception_as_i32 =
|
||||
BUILD(Catch, c->try_info->exception, position());
|
||||
ssa_env_->locals[operand.index] = exception_as_i32;
|
||||
}
|
||||
}
|
||||
|
||||
PopUpTo(c->stack_depth);
|
||||
break;
|
||||
}
|
||||
case kExprLoop: {
|
||||
@ -747,8 +762,8 @@ class WasmFullDecoder : public WasmDecoder {
|
||||
name = "try:end";
|
||||
|
||||
// validate that catch was seen.
|
||||
if (c->catch_env != nullptr) {
|
||||
error("missing catch in try");
|
||||
if (c->try_info->catch_env != nullptr) {
|
||||
error(pc_, "missing catch in try");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1057,8 +1072,8 @@ class WasmFullDecoder : public WasmDecoder {
|
||||
CallFunctionOperand operand(this, pc_);
|
||||
if (Validate(pc_, operand)) {
|
||||
TFNode** buffer = PopArgs(operand.sig);
|
||||
TFNode** rets =
|
||||
BUILD(CallDirect, operand.index, buffer, position());
|
||||
TFNode** rets = nullptr;
|
||||
BUILD(CallDirect, operand.index, buffer, &rets, position());
|
||||
PushReturns(operand.sig, rets);
|
||||
}
|
||||
len = 1 + operand.length;
|
||||
@ -1070,8 +1085,8 @@ class WasmFullDecoder : public WasmDecoder {
|
||||
Value index = Pop(0, kAstI32);
|
||||
TFNode** buffer = PopArgs(operand.sig);
|
||||
if (buffer) buffer[0] = index.node;
|
||||
TFNode** rets =
|
||||
BUILD(CallIndirect, operand.index, buffer, position());
|
||||
TFNode** rets = nullptr;
|
||||
BUILD(CallIndirect, operand.index, buffer, &rets, position());
|
||||
PushReturns(operand.sig, rets);
|
||||
}
|
||||
len = 1 + operand.length;
|
||||
@ -1184,22 +1199,27 @@ class WasmFullDecoder : public WasmDecoder {
|
||||
|
||||
void PushBlock(SsaEnv* end_env) {
|
||||
const int stack_depth = static_cast<int>(stack_.size());
|
||||
control_.emplace_back(Control::Block(pc_, stack_depth, end_env));
|
||||
control_.emplace_back(
|
||||
Control::Block(pc_, stack_depth, end_env, current_catch_));
|
||||
}
|
||||
|
||||
void PushLoop(SsaEnv* end_env) {
|
||||
const int stack_depth = static_cast<int>(stack_.size());
|
||||
control_.emplace_back(Control::Loop(pc_, stack_depth, end_env));
|
||||
control_.emplace_back(
|
||||
Control::Loop(pc_, stack_depth, end_env, current_catch_));
|
||||
}
|
||||
|
||||
void PushIf(SsaEnv* end_env, SsaEnv* false_env) {
|
||||
const int stack_depth = static_cast<int>(stack_.size());
|
||||
control_.emplace_back(Control::If(pc_, stack_depth, end_env, false_env));
|
||||
control_.emplace_back(
|
||||
Control::If(pc_, stack_depth, end_env, false_env, current_catch_));
|
||||
}
|
||||
|
||||
void PushTry(SsaEnv* end_env, SsaEnv* catch_env) {
|
||||
const int stack_depth = static_cast<int>(stack_.size());
|
||||
control_.emplace_back(Control::Try(pc_, stack_depth, end_env, catch_env));
|
||||
control_.emplace_back(Control::Try(pc_, stack_depth, end_env, zone_,
|
||||
catch_env, current_catch_));
|
||||
current_catch_ = static_cast<int32_t>(control_.size() - 1);
|
||||
}
|
||||
|
||||
void PopControl() { control_.pop_back(); }
|
||||
@ -1459,6 +1479,45 @@ class WasmFullDecoder : public WasmDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
TFNode* CheckForException(TFNode* node) {
|
||||
if (node == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const bool inside_try_scope = current_catch_ != kNullCatch;
|
||||
|
||||
if (!inside_try_scope) {
|
||||
return node;
|
||||
}
|
||||
|
||||
TFNode* if_success = nullptr;
|
||||
TFNode* if_exception = nullptr;
|
||||
if (!builder_->ThrowsException(node, &if_success, &if_exception)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
SsaEnv* success_env = Steal(ssa_env_);
|
||||
success_env->control = if_success;
|
||||
|
||||
SsaEnv* exception_env = Split(success_env);
|
||||
exception_env->control = if_exception;
|
||||
TryInfo* try_info = current_try_info();
|
||||
Goto(exception_env, try_info->catch_env);
|
||||
TFNode* exception = try_info->exception;
|
||||
if (exception == nullptr) {
|
||||
DCHECK_EQ(SsaEnv::kReached, try_info->catch_env->state);
|
||||
try_info->exception = if_exception;
|
||||
} else {
|
||||
DCHECK_EQ(SsaEnv::kMerged, try_info->catch_env->state);
|
||||
try_info->exception =
|
||||
CreateOrMergeIntoPhi(kAstI32, try_info->catch_env->control,
|
||||
try_info->exception, if_exception);
|
||||
}
|
||||
|
||||
SetEnv("if_success", success_env);
|
||||
return node;
|
||||
}
|
||||
|
||||
void Goto(SsaEnv* from, SsaEnv* to) {
|
||||
DCHECK_NOT_NULL(to);
|
||||
if (!from->go()) return;
|
||||
|
@ -7,7 +7,8 @@
|
||||
load("test/mjsunit/wasm/wasm-constants.js");
|
||||
load("test/mjsunit/wasm/wasm-module-builder.js");
|
||||
|
||||
var module = (function () {
|
||||
// The following methods do not attempt to catch the exception they raise.
|
||||
var test_throw = (function () {
|
||||
var builder = new WasmModuleBuilder();
|
||||
|
||||
builder.addFunction("throw_param_if_not_zero", kSig_i_i)
|
||||
@ -17,7 +18,7 @@ var module = (function () {
|
||||
kExprI32Ne,
|
||||
kExprIf, kAstStmt,
|
||||
kExprGetLocal, 0,
|
||||
kExprThrow, kAstStmt,
|
||||
kExprThrow,
|
||||
kExprEnd,
|
||||
kExprI32Const, 1
|
||||
])
|
||||
@ -26,7 +27,7 @@ var module = (function () {
|
||||
builder.addFunction("throw_20", kSig_v_v)
|
||||
.addBody([
|
||||
kExprI32Const, 20,
|
||||
kExprThrow, kAstStmt
|
||||
kExprThrow,
|
||||
])
|
||||
.exportFunc()
|
||||
|
||||
@ -43,25 +44,340 @@ var module = (function () {
|
||||
kExprI32Mul,
|
||||
kExprI32Const, 20,
|
||||
kExprI32Sub,
|
||||
kExprThrow, kAstStmt
|
||||
kExprThrow,
|
||||
])
|
||||
.exportFunc()
|
||||
|
||||
return builder.instantiate();
|
||||
})();
|
||||
|
||||
// Check the module exists.
|
||||
assertFalse(module === undefined);
|
||||
assertFalse(module === null);
|
||||
assertFalse(module === 0);
|
||||
assertEquals("object", typeof module.exports);
|
||||
assertEquals("function", typeof module.exports.throw_param_if_not_zero);
|
||||
// Check the test_throw exists.
|
||||
assertFalse(test_throw === undefined);
|
||||
assertFalse(test_throw === null);
|
||||
assertFalse(test_throw === 0);
|
||||
assertEquals("object", typeof test_throw.exports);
|
||||
assertEquals("function", typeof test_throw.exports.throw_param_if_not_zero);
|
||||
assertEquals("function", typeof test_throw.exports.throw_20);
|
||||
assertEquals("function", typeof test_throw.exports.throw_expr_with_params);
|
||||
|
||||
assertEquals(1, module.exports.throw_param_if_not_zero(0));
|
||||
assertWasmThrows(10, function() { module.exports.throw_param_if_not_zero(10) });
|
||||
assertWasmThrows(-1, function() { module.exports.throw_param_if_not_zero(-1) });
|
||||
assertWasmThrows(20, module.exports.throw_20);
|
||||
assertEquals(1, test_throw.exports.throw_param_if_not_zero(0));
|
||||
assertWasmThrows(10, function() { test_throw.exports.throw_param_if_not_zero(10) });
|
||||
assertWasmThrows(-1, function() { test_throw.exports.throw_param_if_not_zero(-1) });
|
||||
assertWasmThrows(20, test_throw.exports.throw_20);
|
||||
assertWasmThrows(
|
||||
-8, function() { module.exports.throw_expr_with_params(1.5, 2.5, 4); });
|
||||
-8, function() { test_throw.exports.throw_expr_with_params(1.5, 2.5, 4); });
|
||||
assertWasmThrows(
|
||||
12, function() { module.exports.throw_expr_with_params(5.7, 2.5, 4); });
|
||||
12, function() { test_throw.exports.throw_expr_with_params(5.7, 2.5, 4); });
|
||||
|
||||
// Now that we know throwing works, we test catching the exceptions we raise.
|
||||
var test_catch = (function () {
|
||||
var builder = new WasmModuleBuilder();
|
||||
|
||||
// Helper function for throwing from js. It is imported by the Wasm module
|
||||
// as throw_i.
|
||||
function throw_value(value) {
|
||||
throw value;
|
||||
}
|
||||
var sig_index = builder.addType(kSig_v_i);
|
||||
var kJSThrowI = builder.addImport("throw_i", sig_index);
|
||||
|
||||
// Helper function that throws a string. Wasm should not catch it.
|
||||
function throw_string() {
|
||||
throw "use wasm;";
|
||||
}
|
||||
sig_index = builder.addType(kSig_v_v);
|
||||
var kJSThrowString = builder.addImport("throw_string", sig_index);
|
||||
|
||||
// Helper function that throws undefined. Wasm should not catch it.
|
||||
function throw_undefined() {
|
||||
throw undefined;
|
||||
}
|
||||
var kJSThrowUndefined = builder.addImport("throw_undefined", sig_index);
|
||||
|
||||
// Helper function that throws an fp. Wasm should not catch it.
|
||||
function throw_fp() {
|
||||
throw 10.5;
|
||||
}
|
||||
var kJSThrowFP = builder.addImport("throw_fp", sig_index);
|
||||
|
||||
// Helper function that throws a large number. Wasm should not catch it.
|
||||
function throw_large() {
|
||||
throw 1e+28;
|
||||
}
|
||||
var kJSThrowLarge = builder.addImport("throw_large", sig_index);
|
||||
|
||||
// Helper function for throwing from Wasm.
|
||||
var kWasmThrowFunction =
|
||||
builder.addFunction("throw", kSig_v_i)
|
||||
.addBody([
|
||||
kExprGetLocal, 0,
|
||||
kExprThrow
|
||||
])
|
||||
.index;
|
||||
|
||||
// Scenario 1: Throw and catch appear on the same function. This should
|
||||
// happen in case of inlining, for example.
|
||||
builder.addFunction("same_scope", kSig_i_i)
|
||||
.addBody([
|
||||
kExprTry, kAstI32,
|
||||
kExprGetLocal, 0,
|
||||
kExprI32Const, 0,
|
||||
kExprI32Ne,
|
||||
kExprIf, kAstStmt,
|
||||
kExprGetLocal, 0,
|
||||
kExprThrow,
|
||||
kExprUnreachable,
|
||||
kExprEnd,
|
||||
kExprI32Const, 63,
|
||||
kExprCatch, 1,
|
||||
kExprGetLocal, 1,
|
||||
kExprEnd
|
||||
])
|
||||
.addLocals({i32_count: 1})
|
||||
.exportFunc()
|
||||
.index;
|
||||
|
||||
builder.addFunction("same_scope_ignore", kSig_i_i)
|
||||
.addBody([
|
||||
kExprTry, kAstI32,
|
||||
kExprGetLocal, 0,
|
||||
kExprThrow,
|
||||
kExprUnreachable,
|
||||
kExprCatch, 1,
|
||||
kExprGetLocal, 0,
|
||||
kExprEnd,
|
||||
])
|
||||
.addLocals({i32_count: 1})
|
||||
.exportFunc();
|
||||
|
||||
builder.addFunction("same_scope_multiple", kSig_i_i)
|
||||
// path = 0;
|
||||
//
|
||||
// try {
|
||||
// try {
|
||||
// try {
|
||||
// if (p == 1)
|
||||
// throw 1;
|
||||
// path |= 2
|
||||
// } catch (v) {
|
||||
// path |= v | 4;
|
||||
// throw path;
|
||||
// }
|
||||
// if (p == 2)
|
||||
// throw path|8;
|
||||
// path |= 16;
|
||||
// } catch (v) {
|
||||
// path |= v | 32;
|
||||
// throw path;
|
||||
// }
|
||||
// if (p == 3)
|
||||
// throw path|64;
|
||||
// path |= 128
|
||||
// } catch (v) {
|
||||
// path |= v | 256;
|
||||
// }
|
||||
//
|
||||
// return path;
|
||||
//
|
||||
// p == 1 -> path == 293
|
||||
// p == 2 -> path == 298
|
||||
// p == 3 -> path == 338
|
||||
// else -> path == 146
|
||||
.addBody([
|
||||
kExprTry, kAstI32,
|
||||
kExprTry, kAstI32,
|
||||
kExprTry, kAstI32,
|
||||
kExprGetLocal, 0,
|
||||
kExprI32Const, 1,
|
||||
kExprI32Eq,
|
||||
kExprIf, kAstStmt,
|
||||
kExprI32Const, 1,
|
||||
kExprThrow,
|
||||
kExprUnreachable,
|
||||
kExprEnd,
|
||||
kExprI32Const, 2,
|
||||
kExprCatch, 1,
|
||||
kExprGetLocal, 1,
|
||||
kExprI32Const, 4,
|
||||
kExprI32Ior,
|
||||
kExprThrow,
|
||||
kExprUnreachable,
|
||||
kExprEnd,
|
||||
kExprTeeLocal, 2,
|
||||
kExprGetLocal, 0,
|
||||
kExprI32Const, 2,
|
||||
kExprI32Eq,
|
||||
kExprIf, kAstStmt,
|
||||
kExprGetLocal, 2,
|
||||
kExprI32Const, 8,
|
||||
kExprI32Ior,
|
||||
kExprThrow,
|
||||
kExprUnreachable,
|
||||
kExprEnd,
|
||||
kExprI32Const, 16,
|
||||
kExprI32Ior,
|
||||
kExprCatch, 1,
|
||||
kExprGetLocal, 1,
|
||||
kExprI32Const, 32,
|
||||
kExprI32Ior,
|
||||
kExprThrow,
|
||||
kExprUnreachable,
|
||||
kExprEnd,
|
||||
kExprTeeLocal, 2,
|
||||
kExprGetLocal, 0,
|
||||
kExprI32Const, 3,
|
||||
kExprI32Eq,
|
||||
kExprIf, kAstStmt,
|
||||
kExprGetLocal, 2,
|
||||
kExprI32Const, /*64=*/ 192, 0,
|
||||
kExprI32Ior,
|
||||
kExprThrow,
|
||||
kExprUnreachable,
|
||||
kExprEnd,
|
||||
kExprI32Const, /*128=*/ 128, 1,
|
||||
kExprI32Ior,
|
||||
kExprCatch, 1,
|
||||
kExprGetLocal, 1,
|
||||
kExprI32Const, /*256=*/ 128, 2,
|
||||
kExprI32Ior,
|
||||
kExprEnd,
|
||||
])
|
||||
.addLocals({i32_count: 2})
|
||||
.exportFunc();
|
||||
|
||||
// Scenario 2: Catches an exception raised from the direct callee.
|
||||
var kFromDirectCallee =
|
||||
builder.addFunction("from_direct_callee", kSig_i_i)
|
||||
.addBody([
|
||||
kExprTry, kAstI32,
|
||||
kExprGetLocal, 0,
|
||||
kExprCallFunction, kWasmThrowFunction,
|
||||
kExprI32Const, /*-1=*/ 127,
|
||||
kExprCatch, 1,
|
||||
kExprGetLocal, 1,
|
||||
kExprEnd
|
||||
])
|
||||
.addLocals({i32_count: 1})
|
||||
.exportFunc()
|
||||
.index;
|
||||
|
||||
// Scenario 3: Catches an exception raised from an indirect callee.
|
||||
var kFromIndirectCalleeHelper = kFromDirectCallee + 1;
|
||||
builder.addFunction("from_indirect_callee_helper", kSig_v_ii)
|
||||
.addBody([
|
||||
kExprGetLocal, 0,
|
||||
kExprI32Const, 0,
|
||||
kExprI32GtS,
|
||||
kExprIf, kAstStmt,
|
||||
kExprGetLocal, 0,
|
||||
kExprI32Const, 1,
|
||||
kExprI32Sub,
|
||||
kExprGetLocal, 1,
|
||||
kExprI32Const, 1,
|
||||
kExprI32Sub,
|
||||
kExprCallFunction, kFromIndirectCalleeHelper,
|
||||
kExprEnd,
|
||||
kExprGetLocal, 1,
|
||||
kExprCallFunction, kWasmThrowFunction,
|
||||
]);
|
||||
|
||||
builder.addFunction("from_indirect_callee", kSig_i_i)
|
||||
.addBody([
|
||||
kExprTry, kAstI32,
|
||||
kExprGetLocal, 0,
|
||||
kExprI32Const, 0,
|
||||
kExprCallFunction, kFromIndirectCalleeHelper,
|
||||
kExprI32Const, /*-1=*/ 127,
|
||||
kExprCatch, 1,
|
||||
kExprGetLocal, 1,
|
||||
kExprEnd
|
||||
])
|
||||
.addLocals({i32_count: 1})
|
||||
.exportFunc();
|
||||
|
||||
// Scenario 4: Catches an exception raised in JS.
|
||||
builder.addFunction("from_js", kSig_i_i)
|
||||
.addBody([
|
||||
kExprTry, kAstI32,
|
||||
kExprGetLocal, 0,
|
||||
kExprCallFunction, kJSThrowI,
|
||||
kExprI32Const, /*-1=*/ 127,
|
||||
kExprCatch, 1,
|
||||
kExprGetLocal, 1,
|
||||
kExprEnd,
|
||||
])
|
||||
.addLocals({i32_count: 1})
|
||||
.exportFunc();
|
||||
|
||||
// Scenario 5: Does not catch an exception raised in JS if it is not a
|
||||
// number.
|
||||
builder.addFunction("string_from_js", kSig_v_v)
|
||||
.addBody([
|
||||
kExprCallFunction, kJSThrowString
|
||||
])
|
||||
.exportFunc();
|
||||
|
||||
builder.addFunction("fp_from_js", kSig_v_v)
|
||||
.addBody([
|
||||
kExprCallFunction, kJSThrowFP
|
||||
])
|
||||
.exportFunc();
|
||||
|
||||
builder.addFunction("large_from_js", kSig_v_v)
|
||||
.addBody([
|
||||
kExprCallFunction, kJSThrowLarge
|
||||
])
|
||||
.exportFunc();
|
||||
|
||||
builder.addFunction("undefined_from_js", kSig_v_v)
|
||||
.addBody([
|
||||
kExprCallFunction, kJSThrowUndefined
|
||||
])
|
||||
.exportFunc();
|
||||
|
||||
return builder.instantiate({
|
||||
throw_i: throw_value,
|
||||
throw_string: throw_string,
|
||||
throw_fp: throw_fp,
|
||||
throw_large, throw_large,
|
||||
throw_undefined: throw_undefined
|
||||
});
|
||||
})();
|
||||
|
||||
// Check the test_catch exists.
|
||||
assertFalse(test_catch === undefined);
|
||||
assertFalse(test_catch === null);
|
||||
assertFalse(test_catch === 0);
|
||||
assertEquals("object", typeof test_catch.exports);
|
||||
assertEquals("function", typeof test_catch.exports.same_scope);
|
||||
assertEquals("function", typeof test_catch.exports.same_scope_ignore);
|
||||
assertEquals("function", typeof test_catch.exports.same_scope_multiple);
|
||||
assertEquals("function", typeof test_catch.exports.from_direct_callee);
|
||||
assertEquals("function", typeof test_catch.exports.from_indirect_callee);
|
||||
assertEquals("function", typeof test_catch.exports.from_js);
|
||||
assertEquals("function", typeof test_catch.exports.string_from_js);
|
||||
|
||||
assertEquals(63, test_catch.exports.same_scope(0));
|
||||
assertEquals(1024, test_catch.exports.same_scope(1024));
|
||||
assertEquals(-3, test_catch.exports.same_scope(-3));
|
||||
assertEquals(-1, test_catch.exports.same_scope_ignore(-1));
|
||||
assertEquals(1, test_catch.exports.same_scope_ignore(1));
|
||||
assertEquals(0x7FFFFFFF, test_catch.exports.same_scope_ignore(0x7FFFFFFF));
|
||||
assertEquals(1024, test_catch.exports.same_scope_ignore(1024));
|
||||
assertEquals(-1, test_catch.exports.same_scope_ignore(-1));
|
||||
assertEquals(293, test_catch.exports.same_scope_multiple(1));
|
||||
assertEquals(298, test_catch.exports.same_scope_multiple(2));
|
||||
assertEquals(338, test_catch.exports.same_scope_multiple(3));
|
||||
assertEquals(146, test_catch.exports.same_scope_multiple(0));
|
||||
assertEquals(-10024, test_catch.exports.from_direct_callee(-10024));
|
||||
assertEquals(3334333, test_catch.exports.from_direct_callee(3334333));
|
||||
assertEquals(-1, test_catch.exports.from_direct_callee(0xFFFFFFFF));
|
||||
assertEquals(0x7FFFFFFF, test_catch.exports.from_direct_callee(0x7FFFFFFF));
|
||||
assertEquals(-10, test_catch.exports.from_indirect_callee(10));
|
||||
assertEquals(-77, test_catch.exports.from_indirect_callee(77));
|
||||
assertEquals(10, test_catch.exports.from_js(10));
|
||||
assertEquals(-10, test_catch.exports.from_js(-10));
|
||||
|
||||
assertThrowsEquals(test_catch.exports.string_from_js, "use wasm;");
|
||||
assertThrowsEquals(test_catch.exports.large_from_js, 1e+28);
|
||||
assertThrowsEquals(test_catch.exports.undefined_from_js, undefined);
|
||||
|
@ -144,6 +144,8 @@ var kExprBrIf = 0x07;
|
||||
var kExprBrTable = 0x08;
|
||||
var kExprReturn = 0x09;
|
||||
var kExprThrow = 0xfa;
|
||||
var kExprTry = 0xfb;
|
||||
var kExprCatch = 0xfe;
|
||||
var kExprEnd = 0x0f;
|
||||
var kExprTeeLocal = 0x19;
|
||||
var kExprDrop = 0x0b;
|
||||
|
Loading…
Reference in New Issue
Block a user