[wasm] TrapIf and TrapUnless TurboFan operators implemented on ia32.
Original commit message: [wasm] Introduce the TrapIf and TrapUnless operators to generate trap code. Some instructions in WebAssembly trap for some inputs, which means that the execution is terminated and (at least at the moment) a JavaScript exception is thrown. Examples for traps are out-of-bounds memory accesses, or integer divisions by zero. Without the TrapIf and TrapUnless operators trap check in WebAssembly introduces 5 TurboFan nodes (branch, if_true, if_false, trap-reason constant, trap-position constant), in addition to the trap condition itself. Additionally, each WebAssembly function has four TurboFan nodes (merge, effect_phi, 2 phis) whose number of inputs is linear to the number of trap checks in the function. Especially for functions with high numbers of trap checks we observe a significant slowdown in compilation time, down to 0.22 MiB/s in the sqlite benchmark instead of the average of 3 MiB/s in other benchmarks. By introducing a TrapIf common operator only a single node is necessary per trap check, in addition to the trap condition. Also the nodes which are shared between trap checks (merge, effect_phi, 2 phis) would disappear. First measurements suggest a speedup of 30-50% on average. This CL only implements TrapIf and TrapUnless on x64. The implementation is also hidden behind the --wasm-trap-if flag. Please take a special look at how the source position is transfered from the instruction selector to the code generator, and at the context that is used for the runtime call. R=titzer@chromium.org Review-Url: https://codereview.chromium.org/2571813002 Cr-Commit-Position: refs/heads/master@{#41735}
This commit is contained in:
parent
4f2cb8fe82
commit
f435d6222f
@ -1608,61 +1608,66 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
return kSuccess;
|
||||
} // NOLINT(readability/fn_size)
|
||||
|
||||
static Condition FlagsConditionToCondition(FlagsCondition condition) {
|
||||
switch (condition) {
|
||||
case kUnorderedEqual:
|
||||
case kEqual:
|
||||
return equal;
|
||||
break;
|
||||
case kUnorderedNotEqual:
|
||||
case kNotEqual:
|
||||
return not_equal;
|
||||
break;
|
||||
case kSignedLessThan:
|
||||
return less;
|
||||
break;
|
||||
case kSignedGreaterThanOrEqual:
|
||||
return greater_equal;
|
||||
break;
|
||||
case kSignedLessThanOrEqual:
|
||||
return less_equal;
|
||||
break;
|
||||
case kSignedGreaterThan:
|
||||
return greater;
|
||||
break;
|
||||
case kUnsignedLessThan:
|
||||
return below;
|
||||
break;
|
||||
case kUnsignedGreaterThanOrEqual:
|
||||
return above_equal;
|
||||
break;
|
||||
case kUnsignedLessThanOrEqual:
|
||||
return below_equal;
|
||||
break;
|
||||
case kUnsignedGreaterThan:
|
||||
return above;
|
||||
break;
|
||||
case kOverflow:
|
||||
return overflow;
|
||||
break;
|
||||
case kNotOverflow:
|
||||
return no_overflow;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return no_condition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Assembles a branch after an instruction.
|
||||
void CodeGenerator::AssembleArchBranch(Instruction* instr, BranchInfo* branch) {
|
||||
IA32OperandConverter i(this, instr);
|
||||
Label::Distance flabel_distance =
|
||||
branch->fallthru ? Label::kNear : Label::kFar;
|
||||
Label* tlabel = branch->true_label;
|
||||
Label* flabel = branch->false_label;
|
||||
switch (branch->condition) {
|
||||
case kUnorderedEqual:
|
||||
if (branch->condition == kUnorderedEqual) {
|
||||
__ j(parity_even, flabel, flabel_distance);
|
||||
// Fall through.
|
||||
case kEqual:
|
||||
__ j(equal, tlabel);
|
||||
break;
|
||||
case kUnorderedNotEqual:
|
||||
} else if (branch->condition == kUnorderedNotEqual) {
|
||||
__ j(parity_even, tlabel);
|
||||
// Fall through.
|
||||
case kNotEqual:
|
||||
__ j(not_equal, tlabel);
|
||||
break;
|
||||
case kSignedLessThan:
|
||||
__ j(less, tlabel);
|
||||
break;
|
||||
case kSignedGreaterThanOrEqual:
|
||||
__ j(greater_equal, tlabel);
|
||||
break;
|
||||
case kSignedLessThanOrEqual:
|
||||
__ j(less_equal, tlabel);
|
||||
break;
|
||||
case kSignedGreaterThan:
|
||||
__ j(greater, tlabel);
|
||||
break;
|
||||
case kUnsignedLessThan:
|
||||
__ j(below, tlabel);
|
||||
break;
|
||||
case kUnsignedGreaterThanOrEqual:
|
||||
__ j(above_equal, tlabel);
|
||||
break;
|
||||
case kUnsignedLessThanOrEqual:
|
||||
__ j(below_equal, tlabel);
|
||||
break;
|
||||
case kUnsignedGreaterThan:
|
||||
__ j(above, tlabel);
|
||||
break;
|
||||
case kOverflow:
|
||||
__ j(overflow, tlabel);
|
||||
break;
|
||||
case kNotOverflow:
|
||||
__ j(no_overflow, tlabel);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
__ j(FlagsConditionToCondition(branch->condition), tlabel);
|
||||
|
||||
// Add a jump if not falling through to the next block.
|
||||
if (!branch->fallthru) __ jmp(flabel);
|
||||
}
|
||||
@ -1674,7 +1679,68 @@ void CodeGenerator::AssembleArchJump(RpoNumber target) {
|
||||
|
||||
void CodeGenerator::AssembleArchTrap(Instruction* instr,
|
||||
FlagsCondition condition) {
|
||||
UNREACHABLE();
|
||||
class OutOfLineTrap final : public OutOfLineCode {
|
||||
public:
|
||||
OutOfLineTrap(CodeGenerator* gen, bool frame_elided, Instruction* instr)
|
||||
: OutOfLineCode(gen),
|
||||
frame_elided_(frame_elided),
|
||||
instr_(instr),
|
||||
gen_(gen) {}
|
||||
|
||||
void Generate() final {
|
||||
IA32OperandConverter i(gen_, instr_);
|
||||
|
||||
Runtime::FunctionId trap_id = static_cast<Runtime::FunctionId>(
|
||||
i.InputInt32(instr_->InputCount() - 1));
|
||||
bool old_has_frame = __ has_frame();
|
||||
if (frame_elided_) {
|
||||
__ set_has_frame(true);
|
||||
__ EnterFrame(StackFrame::WASM);
|
||||
}
|
||||
GenerateCallToTrap(trap_id);
|
||||
if (frame_elided_) {
|
||||
ReferenceMap* reference_map =
|
||||
new (gen_->zone()) ReferenceMap(gen_->zone());
|
||||
gen_->RecordSafepoint(reference_map, Safepoint::kSimple, 0,
|
||||
Safepoint::kNoLazyDeopt);
|
||||
__ set_has_frame(old_has_frame);
|
||||
}
|
||||
if (FLAG_debug_code) {
|
||||
__ ud2();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void GenerateCallToTrap(Runtime::FunctionId trap_id) {
|
||||
if (trap_id == Runtime::kNumFunctions) {
|
||||
// We cannot test calls to the runtime in cctest/test-run-wasm.
|
||||
// Therefore we emit a call to C here instead of a call to the runtime.
|
||||
__ PrepareCallCFunction(0, esi);
|
||||
__ CallCFunction(
|
||||
ExternalReference::wasm_call_trap_callback_for_testing(isolate()),
|
||||
0);
|
||||
} else {
|
||||
__ Move(esi, isolate()->native_context());
|
||||
gen_->AssembleSourcePosition(instr_);
|
||||
__ CallRuntime(trap_id);
|
||||
}
|
||||
}
|
||||
|
||||
bool frame_elided_;
|
||||
Instruction* instr_;
|
||||
CodeGenerator* gen_;
|
||||
};
|
||||
bool frame_elided = !frame_access_state()->has_frame();
|
||||
auto ool = new (zone()) OutOfLineTrap(this, frame_elided, instr);
|
||||
Label* tlabel = ool->entry();
|
||||
Label end;
|
||||
if (condition == kUnorderedEqual) {
|
||||
__ j(parity_even, &end);
|
||||
} else if (condition == kUnorderedNotEqual) {
|
||||
__ j(parity_even, tlabel);
|
||||
}
|
||||
__ j(FlagsConditionToCondition(condition), tlabel);
|
||||
__ bind(&end);
|
||||
}
|
||||
|
||||
// Assembles boolean materializations after an instruction.
|
||||
@ -1688,58 +1754,17 @@ void CodeGenerator::AssembleArchBoolean(Instruction* instr,
|
||||
Label check;
|
||||
DCHECK_NE(0u, instr->OutputCount());
|
||||
Register reg = i.OutputRegister(instr->OutputCount() - 1);
|
||||
Condition cc = no_condition;
|
||||
switch (condition) {
|
||||
case kUnorderedEqual:
|
||||
if (condition == kUnorderedEqual) {
|
||||
__ j(parity_odd, &check, Label::kNear);
|
||||
__ Move(reg, Immediate(0));
|
||||
__ jmp(&done, Label::kNear);
|
||||
// Fall through.
|
||||
case kEqual:
|
||||
cc = equal;
|
||||
break;
|
||||
case kUnorderedNotEqual:
|
||||
} else if (condition == kUnorderedNotEqual) {
|
||||
__ j(parity_odd, &check, Label::kNear);
|
||||
__ mov(reg, Immediate(1));
|
||||
__ jmp(&done, Label::kNear);
|
||||
// Fall through.
|
||||
case kNotEqual:
|
||||
cc = not_equal;
|
||||
break;
|
||||
case kSignedLessThan:
|
||||
cc = less;
|
||||
break;
|
||||
case kSignedGreaterThanOrEqual:
|
||||
cc = greater_equal;
|
||||
break;
|
||||
case kSignedLessThanOrEqual:
|
||||
cc = less_equal;
|
||||
break;
|
||||
case kSignedGreaterThan:
|
||||
cc = greater;
|
||||
break;
|
||||
case kUnsignedLessThan:
|
||||
cc = below;
|
||||
break;
|
||||
case kUnsignedGreaterThanOrEqual:
|
||||
cc = above_equal;
|
||||
break;
|
||||
case kUnsignedLessThanOrEqual:
|
||||
cc = below_equal;
|
||||
break;
|
||||
case kUnsignedGreaterThan:
|
||||
cc = above;
|
||||
break;
|
||||
case kOverflow:
|
||||
cc = overflow;
|
||||
break;
|
||||
case kNotOverflow:
|
||||
cc = no_overflow;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
Condition cc = FlagsConditionToCondition(condition);
|
||||
|
||||
__ bind(&check);
|
||||
if (reg.is_byte_register()) {
|
||||
// setcc for byte registers (al, bl, cl, dl).
|
||||
|
@ -1208,10 +1208,13 @@ void VisitCompareWithMemoryOperand(InstructionSelector* selector,
|
||||
} else if (cont->IsDeoptimize()) {
|
||||
selector->EmitDeoptimize(opcode, 0, nullptr, input_count, inputs,
|
||||
cont->reason(), cont->frame_state());
|
||||
} else {
|
||||
DCHECK(cont->IsSet());
|
||||
} else if (cont->IsSet()) {
|
||||
InstructionOperand output = g.DefineAsRegister(cont->result());
|
||||
selector->Emit(opcode, 1, &output, input_count, inputs);
|
||||
} else {
|
||||
DCHECK(cont->IsTrap());
|
||||
inputs[input_count++] = g.UseImmediate(cont->trap_id());
|
||||
selector->Emit(opcode, 0, nullptr, input_count, inputs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1227,9 +1230,12 @@ void VisitCompare(InstructionSelector* selector, InstructionCode opcode,
|
||||
} else if (cont->IsDeoptimize()) {
|
||||
selector->EmitDeoptimize(opcode, g.NoOutput(), left, right, cont->reason(),
|
||||
cont->frame_state());
|
||||
} else {
|
||||
DCHECK(cont->IsSet());
|
||||
} else if (cont->IsSet()) {
|
||||
selector->Emit(opcode, g.DefineAsByteRegister(cont->result()), left, right);
|
||||
} else {
|
||||
DCHECK(cont->IsTrap());
|
||||
selector->Emit(opcode, g.NoOutput(), left, right,
|
||||
g.UseImmediate(cont->trap_id()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1501,9 +1507,18 @@ void InstructionSelector::VisitDeoptimizeUnless(Node* node) {
|
||||
VisitWordCompareZero(this, node, node->InputAt(0), &cont);
|
||||
}
|
||||
|
||||
void InstructionSelector::VisitTrapIf(Node* node) { UNREACHABLE(); }
|
||||
void InstructionSelector::VisitTrapIf(Node* node) {
|
||||
FlagsContinuation cont = FlagsContinuation::ForTrap(
|
||||
kNotEqual, OpParameter<Runtime::FunctionId>(node->op()),
|
||||
node->InputAt(1));
|
||||
VisitWordCompareZero(this, node, node->InputAt(0), &cont);
|
||||
}
|
||||
|
||||
void InstructionSelector::VisitTrapUnless(Node* node) { UNREACHABLE(); }
|
||||
void InstructionSelector::VisitTrapUnless(Node* node) {
|
||||
FlagsContinuation cont = FlagsContinuation::ForTrap(
|
||||
kEqual, OpParameter<Runtime::FunctionId>(node->op()), node->InputAt(1));
|
||||
VisitWordCompareZero(this, node, node->InputAt(0), &cont);
|
||||
}
|
||||
|
||||
void InstructionSelector::VisitSwitch(Node* node, const SwitchInfo& sw) {
|
||||
IA32OperandGenerator g(this);
|
||||
|
@ -168,20 +168,27 @@ class WasmTrapHelper : public ZoneObject {
|
||||
return TrapIfEq64(reason, node, 0, position);
|
||||
}
|
||||
|
||||
int32_t GetFunctionIdForTrap(wasm::TrapReason reason) {
|
||||
int32_t trap_id;
|
||||
Runtime::FunctionId GetFunctionIdForTrap(wasm::TrapReason reason) {
|
||||
if (builder_->module_ && !builder_->module_->instance->context.is_null()) {
|
||||
trap_id = wasm::WasmOpcodes::TrapReasonToFunctionId(reason);
|
||||
switch (reason) {
|
||||
#define TRAPREASON_TO_MESSAGE(name) \
|
||||
case wasm::k##name: \
|
||||
return Runtime::kThrowWasm##name;
|
||||
FOREACH_WASM_TRAPREASON(TRAPREASON_TO_MESSAGE)
|
||||
#undef TRAPREASON_TO_MESSAGE
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return Runtime::kNumFunctions;
|
||||
}
|
||||
} else {
|
||||
// We use Runtime::kNumFunctions as a marker to tell the code generator
|
||||
// to generate a call to a testing c-function instead of a runtime
|
||||
// function. This code should only be called from a cctest.
|
||||
trap_id = Runtime::kNumFunctions;
|
||||
return Runtime::kNumFunctions;
|
||||
}
|
||||
return trap_id;
|
||||
}
|
||||
|
||||
#if V8_TARGET_ARCH_X64
|
||||
#if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32
|
||||
#define WASM_TRAP_IF_SUPPORTED
|
||||
#endif
|
||||
|
||||
@ -197,7 +204,7 @@ class WasmTrapHelper : public ZoneObject {
|
||||
builder_->SetSourcePosition(node, position);
|
||||
return;
|
||||
}
|
||||
#endif // V8_TARGET_ARCH_X64
|
||||
#endif // WASM_TRAP_IF_SUPPORTED
|
||||
BuildTrapIf(reason, cond, true, position);
|
||||
}
|
||||
|
||||
@ -214,7 +221,7 @@ class WasmTrapHelper : public ZoneObject {
|
||||
builder_->SetSourcePosition(node, position);
|
||||
return;
|
||||
}
|
||||
#endif // V8_TARGET_ARCH_X64
|
||||
#endif // WASM_TRAP_IF_SUPPORTED
|
||||
|
||||
BuildTrapIf(reason, cond, false, position);
|
||||
}
|
||||
|
@ -176,19 +176,6 @@ int WasmOpcodes::TrapReasonToMessageId(TrapReason reason) {
|
||||
}
|
||||
}
|
||||
|
||||
int32_t WasmOpcodes::TrapReasonToFunctionId(TrapReason reason) {
|
||||
switch (reason) {
|
||||
#define TRAPREASON_TO_MESSAGE(name) \
|
||||
case k##name: \
|
||||
return static_cast<int32_t>(Runtime::kThrowWasm##name);
|
||||
FOREACH_WASM_TRAPREASON(TRAPREASON_TO_MESSAGE)
|
||||
#undef TRAPREASON_TO_MESSAGE
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
const char* WasmOpcodes::TrapReasonMessage(TrapReason reason) {
|
||||
return MessageTemplate::TemplateString(TrapReasonToMessageId(reason));
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "src/globals.h"
|
||||
#include "src/machine-type.h"
|
||||
#include "src/runtime/runtime.h"
|
||||
#include "src/signature.h"
|
||||
|
||||
namespace v8 {
|
||||
@ -533,7 +534,6 @@ class V8_EXPORT_PRIVATE WasmOpcodes {
|
||||
|
||||
static int TrapReasonToMessageId(TrapReason reason);
|
||||
static const char* TrapReasonMessage(TrapReason reason);
|
||||
static int32_t TrapReasonToFunctionId(TrapReason reason);
|
||||
|
||||
static byte MemSize(MachineType type) {
|
||||
return 1 << ElementSizeLog2Of(type.representation());
|
||||
|
Loading…
Reference in New Issue
Block a user