Commit 257bdfe5 authored by titzer's avatar titzer Committed by Commit bot

[wasm] Fix asm.js semantics for divide by zero in WASM translation.

R=ahaas@chromium.org,bradnelson@chromium.org
BUG=

Review URL: https://codereview.chromium.org/1839333002

Cr-Commit-Position: refs/heads/master@{#35134}
parent baa34fae
...@@ -393,43 +393,14 @@ Node* WasmGraphBuilder::Binop(wasm::WasmOpcode opcode, Node* left, ...@@ -393,43 +393,14 @@ Node* WasmGraphBuilder::Binop(wasm::WasmOpcode opcode, Node* left,
case wasm::kExprI32Mul: case wasm::kExprI32Mul:
op = m->Int32Mul(); op = m->Int32Mul();
break; break;
case wasm::kExprI32DivS: { case wasm::kExprI32DivS:
trap_->ZeroCheck32(kTrapDivByZero, right); return BuildI32DivS(left, right);
Node* before = *control_;
Node* denom_is_m1;
Node* denom_is_not_m1;
Branch(graph()->NewNode(jsgraph()->machine()->Word32Equal(), right,
jsgraph()->Int32Constant(-1)),
&denom_is_m1, &denom_is_not_m1);
*control_ = denom_is_m1;
trap_->TrapIfEq32(kTrapDivUnrepresentable, left, kMinInt);
if (*control_ != denom_is_m1) {
*control_ = graph()->NewNode(jsgraph()->common()->Merge(2),
denom_is_not_m1, *control_);
} else {
*control_ = before;
}
return graph()->NewNode(m->Int32Div(), left, right, *control_);
}
case wasm::kExprI32DivU: case wasm::kExprI32DivU:
op = m->Uint32Div(); return BuildI32DivU(left, right);
return graph()->NewNode(op, left, right, case wasm::kExprI32RemS:
trap_->ZeroCheck32(kTrapDivByZero, right)); return BuildI32RemS(left, right);
case wasm::kExprI32RemS: {
trap_->ZeroCheck32(kTrapRemByZero, right);
Diamond d(graph(), jsgraph()->common(),
graph()->NewNode(jsgraph()->machine()->Word32Equal(), right,
jsgraph()->Int32Constant(-1)));
Node* rem = graph()->NewNode(m->Int32Mod(), left, right, d.if_false);
return d.Phi(MachineRepresentation::kWord32, jsgraph()->Int32Constant(0),
rem);
}
case wasm::kExprI32RemU: case wasm::kExprI32RemU:
op = m->Uint32Mod(); return BuildI32RemU(left, right);
return graph()->NewNode(op, left, right,
trap_->ZeroCheck32(kTrapRemByZero, right));
case wasm::kExprI32And: case wasm::kExprI32And:
op = m->Word32And(); op = m->Word32And();
break; break;
...@@ -1743,6 +1714,132 @@ Node* WasmGraphBuilder::BuildFloatToIntConversionInstruction( ...@@ -1743,6 +1714,132 @@ Node* WasmGraphBuilder::BuildFloatToIntConversionInstruction(
return load; return load;
} }
Node* WasmGraphBuilder::BuildI32DivS(Node* left, Node* right) {
MachineOperatorBuilder* m = jsgraph()->machine();
if (module_ && module_->asm_js()) {
// asm.js semantics return 0 on divide or mod by zero.
if (m->Int32DivIsSafe()) {
// The hardware instruction does the right thing (e.g. arm).
return graph()->NewNode(m->Int32Div(), left, right, graph()->start());
}
// Check denominator for zero.
Diamond z(
graph(), jsgraph()->common(),
graph()->NewNode(m->Word32Equal(), right, jsgraph()->Int32Constant(0)),
BranchHint::kFalse);
// Check numerator for -1. (avoid minint / -1 case).
Diamond n(
graph(), jsgraph()->common(),
graph()->NewNode(m->Word32Equal(), right, jsgraph()->Int32Constant(-1)),
BranchHint::kFalse);
Node* div = graph()->NewNode(m->Int32Div(), left, right, z.if_false);
Node* neg =
graph()->NewNode(m->Int32Sub(), jsgraph()->Int32Constant(0), left);
return n.Phi(MachineRepresentation::kWord32, neg,
z.Phi(MachineRepresentation::kWord32,
jsgraph()->Int32Constant(0), div));
}
trap_->ZeroCheck32(kTrapDivByZero, right);
Node* before = *control_;
Node* denom_is_m1;
Node* denom_is_not_m1;
Branch(
graph()->NewNode(m->Word32Equal(), right, jsgraph()->Int32Constant(-1)),
&denom_is_m1, &denom_is_not_m1);
*control_ = denom_is_m1;
trap_->TrapIfEq32(kTrapDivUnrepresentable, left, kMinInt);
if (*control_ != denom_is_m1) {
*control_ = graph()->NewNode(jsgraph()->common()->Merge(2), denom_is_not_m1,
*control_);
} else {
*control_ = before;
}
return graph()->NewNode(m->Int32Div(), left, right, *control_);
}
Node* WasmGraphBuilder::BuildI32RemS(Node* left, Node* right) {
MachineOperatorBuilder* m = jsgraph()->machine();
if (module_ && module_->asm_js()) {
// asm.js semantics return 0 on divide or mod by zero.
// Explicit check for x % 0.
Diamond z(
graph(), jsgraph()->common(),
graph()->NewNode(m->Word32Equal(), right, jsgraph()->Int32Constant(0)),
BranchHint::kFalse);
// Explicit check for x % -1.
Diamond d(
graph(), jsgraph()->common(),
graph()->NewNode(m->Word32Equal(), right, jsgraph()->Int32Constant(-1)),
BranchHint::kFalse);
d.Chain(z.if_false);
return z.Phi(
MachineRepresentation::kWord32, jsgraph()->Int32Constant(0),
d.Phi(MachineRepresentation::kWord32, jsgraph()->Int32Constant(0),
graph()->NewNode(m->Int32Mod(), left, right, d.if_false)));
}
trap_->ZeroCheck32(kTrapRemByZero, right);
Diamond d(
graph(), jsgraph()->common(),
graph()->NewNode(m->Word32Equal(), right, jsgraph()->Int32Constant(-1)),
BranchHint::kFalse);
d.Chain(*control_);
return d.Phi(MachineRepresentation::kWord32, jsgraph()->Int32Constant(0),
graph()->NewNode(m->Int32Mod(), left, right, d.if_false));
}
Node* WasmGraphBuilder::BuildI32DivU(Node* left, Node* right) {
MachineOperatorBuilder* m = jsgraph()->machine();
if (module_ && module_->asm_js()) {
// asm.js semantics return 0 on divide or mod by zero.
if (m->Uint32DivIsSafe()) {
// The hardware instruction does the right thing (e.g. arm).
return graph()->NewNode(m->Uint32Div(), left, right, graph()->start());
}
// Explicit check for x % 0.
Diamond z(
graph(), jsgraph()->common(),
graph()->NewNode(m->Word32Equal(), right, jsgraph()->Int32Constant(0)),
BranchHint::kFalse);
return z.Phi(MachineRepresentation::kWord32, jsgraph()->Int32Constant(0),
graph()->NewNode(jsgraph()->machine()->Uint32Div(), left,
right, z.if_false));
}
return graph()->NewNode(m->Uint32Div(), left, right,
trap_->ZeroCheck32(kTrapDivByZero, right));
}
Node* WasmGraphBuilder::BuildI32RemU(Node* left, Node* right) {
MachineOperatorBuilder* m = jsgraph()->machine();
if (module_ && module_->asm_js()) {
// asm.js semantics return 0 on divide or mod by zero.
// Explicit check for x % 0.
Diamond z(
graph(), jsgraph()->common(),
graph()->NewNode(m->Word32Equal(), right, jsgraph()->Int32Constant(0)),
BranchHint::kFalse);
Node* rem = graph()->NewNode(jsgraph()->machine()->Uint32Mod(), left, right,
z.if_false);
return z.Phi(MachineRepresentation::kWord32, jsgraph()->Int32Constant(0),
rem);
}
return graph()->NewNode(m->Uint32Mod(), left, right,
trap_->ZeroCheck32(kTrapRemByZero, right));
}
Node* WasmGraphBuilder::BuildI64DivS(Node* left, Node* right) { Node* WasmGraphBuilder::BuildI64DivS(Node* left, Node* right) {
if (jsgraph()->machine()->Is32()) { if (jsgraph()->machine()->Is32()) {
return BuildDiv64Call( return BuildDiv64Call(
......
...@@ -166,6 +166,7 @@ class WasmGraphBuilder { ...@@ -166,6 +166,7 @@ class WasmGraphBuilder {
Node* BuildCCall(MachineSignature* sig, Node** args); Node* BuildCCall(MachineSignature* sig, Node** args);
Node* BuildWasmCall(wasm::FunctionSig* sig, Node** args); Node* BuildWasmCall(wasm::FunctionSig* sig, Node** args);
Node* BuildF32Neg(Node* input); Node* BuildF32Neg(Node* input);
Node* BuildF64Neg(Node* input); Node* BuildF64Neg(Node* input);
Node* BuildF32CopySign(Node* left, Node* right); Node* BuildF32CopySign(Node* left, Node* right);
...@@ -225,6 +226,11 @@ class WasmGraphBuilder { ...@@ -225,6 +226,11 @@ class WasmGraphBuilder {
Node* BuildI64SConvertF64(Node* input); Node* BuildI64SConvertF64(Node* input);
Node* BuildI64UConvertF64(Node* input); Node* BuildI64UConvertF64(Node* input);
Node* BuildI32DivS(Node* left, Node* right);
Node* BuildI32RemS(Node* left, Node* right);
Node* BuildI32DivU(Node* left, Node* right);
Node* BuildI32RemU(Node* left, Node* right);
Node* BuildI64DivS(Node* left, Node* right); Node* BuildI64DivS(Node* left, Node* right);
Node* BuildI64RemS(Node* left, Node* right); Node* BuildI64RemS(Node* left, Node* right);
Node* BuildI64DivU(Node* left, Node* right); Node* BuildI64DivU(Node* left, Node* right);
......
...@@ -260,6 +260,28 @@ struct WasmName { ...@@ -260,6 +260,28 @@ struct WasmName {
V(F64Pow, 0xc9, d_dd) \ V(F64Pow, 0xc9, d_dd) \
V(F64Mod, 0xca, d_dd) V(F64Mod, 0xca, d_dd)
// TODO(titzer): sketch of asm-js compatibility bytecodes
/* V(I32AsmjsDivS, 0xd0, i_ii) \ */
/* V(I32AsmjsDivU, 0xd1, i_ii) \ */
/* V(I32AsmjsRemS, 0xd2, i_ii) \ */
/* V(I32AsmjsRemU, 0xd3, i_ii) \ */
/* V(I32AsmjsLoad8S, 0xd4, i_i) \ */
/* V(I32AsmjsLoad8U, 0xd5, i_i) \ */
/* V(I32AsmjsLoad16S, 0xd6, i_i) \ */
/* V(I32AsmjsLoad16U, 0xd7, i_i) \ */
/* V(I32AsmjsLoad, 0xd8, i_i) \ */
/* V(F32AsmjsLoad, 0xd9, f_i) \ */
/* V(F64AsmjsLoad, 0xda, d_i) \ */
/* V(I32AsmjsStore8, 0xdb, i_i) \ */
/* V(I32AsmjsStore16, 0xdc, i_i) \ */
/* V(I32AsmjsStore, 0xdd, i_ii) \ */
/* V(F32AsmjsStore, 0xde, i_if) \ */
/* V(F64AsmjsStore, 0xdf, i_id) \ */
/* V(I32SAsmjsConvertF32, 0xe0, i_f) \ */
/* V(I32UAsmjsConvertF32, 0xe1, i_f) \ */
/* V(I32SAsmjsConvertF64, 0xe2, i_d) \ */
/* V(I32SAsmjsConvertF64, 0xe3, i_d) */
// All opcodes. // All opcodes.
#define FOREACH_OPCODE(V) \ #define FOREACH_OPCODE(V) \
FOREACH_CONTROL_OPCODE(V) \ FOREACH_CONTROL_OPCODE(V) \
......
...@@ -327,33 +327,36 @@ TEST(Run_WasmI32Eqz) { ...@@ -327,33 +327,36 @@ TEST(Run_WasmI32Eqz) {
TEST(Run_WASM_Int32DivS_trap) { TEST(Run_WASM_Int32DivS_trap) {
WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32()); WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32());
BUILD(r, WASM_I32_DIVS(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))); BUILD(r, WASM_I32_DIVS(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(0, r.Call(0, 100)); CHECK_EQ(0, r.Call(0, 100));
CHECK_TRAP(r.Call(100, 0)); CHECK_TRAP(r.Call(100, 0));
CHECK_TRAP(r.Call(-1001, 0)); CHECK_TRAP(r.Call(-1001, 0));
CHECK_TRAP(r.Call(std::numeric_limits<int32_t>::min(), -1)); CHECK_TRAP(r.Call(kMin, -1));
CHECK_TRAP(r.Call(std::numeric_limits<int32_t>::min(), 0)); CHECK_TRAP(r.Call(kMin, 0));
} }
TEST(Run_WASM_Int32RemS_trap) { TEST(Run_WASM_Int32RemS_trap) {
WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32()); WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32());
BUILD(r, WASM_I32_REMS(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))); BUILD(r, WASM_I32_REMS(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(33, r.Call(133, 100)); CHECK_EQ(33, r.Call(133, 100));
CHECK_EQ(0, r.Call(std::numeric_limits<int32_t>::min(), -1)); CHECK_EQ(0, r.Call(kMin, -1));
CHECK_TRAP(r.Call(100, 0)); CHECK_TRAP(r.Call(100, 0));
CHECK_TRAP(r.Call(-1001, 0)); CHECK_TRAP(r.Call(-1001, 0));
CHECK_TRAP(r.Call(std::numeric_limits<int32_t>::min(), 0)); CHECK_TRAP(r.Call(kMin, 0));
} }
TEST(Run_WASM_Int32DivU_trap) { TEST(Run_WASM_Int32DivU_trap) {
WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32()); WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32());
BUILD(r, WASM_I32_DIVU(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))); BUILD(r, WASM_I32_DIVU(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(0, r.Call(0, 100)); CHECK_EQ(0, r.Call(0, 100));
CHECK_EQ(0, r.Call(std::numeric_limits<int32_t>::min(), -1)); CHECK_EQ(0, r.Call(kMin, -1));
CHECK_TRAP(r.Call(100, 0)); CHECK_TRAP(r.Call(100, 0));
CHECK_TRAP(r.Call(-1001, 0)); CHECK_TRAP(r.Call(-1001, 0));
CHECK_TRAP(r.Call(std::numeric_limits<int32_t>::min(), 0)); CHECK_TRAP(r.Call(kMin, 0));
} }
...@@ -361,11 +364,63 @@ TEST(Run_WASM_Int32RemU_trap) { ...@@ -361,11 +364,63 @@ TEST(Run_WASM_Int32RemU_trap) {
WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32()); WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32());
BUILD(r, WASM_I32_REMU(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))); BUILD(r, WASM_I32_REMU(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
CHECK_EQ(17, r.Call(217, 100)); CHECK_EQ(17, r.Call(217, 100));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_TRAP(r.Call(100, 0)); CHECK_TRAP(r.Call(100, 0));
CHECK_TRAP(r.Call(-1001, 0)); CHECK_TRAP(r.Call(-1001, 0));
CHECK_TRAP(r.Call(std::numeric_limits<int32_t>::min(), 0)); CHECK_TRAP(r.Call(kMin, 0));
CHECK_EQ(std::numeric_limits<int32_t>::min(), CHECK_EQ(kMin, r.Call(kMin, -1));
r.Call(std::numeric_limits<int32_t>::min(), -1)); }
TEST(Run_WASM_Int32DivS_asmjs) {
TestingModule module;
module.origin = kAsmJsOrigin;
WasmRunner<int32_t> r(&module, MachineType::Int32(), MachineType::Int32());
BUILD(r, WASM_I32_DIVS(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(0, r.Call(0, 100));
CHECK_EQ(0, r.Call(100, 0));
CHECK_EQ(0, r.Call(-1001, 0));
CHECK_EQ(kMin, r.Call(kMin, -1));
CHECK_EQ(0, r.Call(kMin, 0));
}
TEST(Run_WASM_Int32RemS_asmjs) {
TestingModule module;
module.origin = kAsmJsOrigin;
WasmRunner<int32_t> r(&module, MachineType::Int32(), MachineType::Int32());
BUILD(r, WASM_I32_REMS(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(33, r.Call(133, 100));
CHECK_EQ(0, r.Call(kMin, -1));
CHECK_EQ(0, r.Call(100, 0));
CHECK_EQ(0, r.Call(-1001, 0));
CHECK_EQ(0, r.Call(kMin, 0));
}
TEST(Run_WASM_Int32DivU_asmjs) {
TestingModule module;
module.origin = kAsmJsOrigin;
WasmRunner<int32_t> r(&module, MachineType::Int32(), MachineType::Int32());
BUILD(r, WASM_I32_DIVU(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(0, r.Call(0, 100));
CHECK_EQ(0, r.Call(kMin, -1));
CHECK_EQ(0, r.Call(100, 0));
CHECK_EQ(0, r.Call(-1001, 0));
CHECK_EQ(0, r.Call(kMin, 0));
}
TEST(Run_WASM_Int32RemU_asmjs) {
TestingModule module;
module.origin = kAsmJsOrigin;
WasmRunner<int32_t> r(&module, MachineType::Int32(), MachineType::Int32());
BUILD(r, WASM_I32_REMU(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(17, r.Call(217, 100));
CHECK_EQ(0, r.Call(100, 0));
CHECK_EQ(0, r.Call(-1001, 0));
CHECK_EQ(0, r.Call(kMin, 0));
CHECK_EQ(kMin, r.Call(kMin, -1));
} }
......
...@@ -213,9 +213,8 @@ var funcs = [ ...@@ -213,9 +213,8 @@ var funcs = [
i32_add, i32_add,
i32_sub, i32_sub,
i32_mul, i32_mul,
// TODO(titzer): i32_mul requires Math.imul i32_div,
// TODO(titzer): i32_div divide by zero is incorrect i32_mod,
// TODO(titzer): i32_mod by zero is incorrect
i32_and, i32_and,
i32_or, i32_or,
i32_xor, i32_xor,
......
...@@ -43,13 +43,13 @@ const imul = Math.imul; ...@@ -43,13 +43,13 @@ const imul = Math.imul;
function u32_add(a, b) { function u32_add(a, b) {
a = a | 0; a = a | 0;
b = b | 0; b = b | 0;
return +((a >>> 0) + (b >>> 0)); return +(((a >>> 0) + (b >>> 0)) >>> 0);
} }
function u32_sub(a, b) { function u32_sub(a, b) {
a = a | 0; a = a | 0;
b = b | 0; b = b | 0;
return +((a >>> 0) - (b >>> 0)); return +(((a >>> 0) - (b >>> 0)) >>> 0);
} }
function u32_mul(a, b) { function u32_mul(a, b) {
...@@ -61,13 +61,13 @@ function u32_mul(a, b) { ...@@ -61,13 +61,13 @@ function u32_mul(a, b) {
function u32_div(a, b) { function u32_div(a, b) {
a = a | 0; a = a | 0;
b = b | 0; b = b | 0;
return ((a >>> 0) / (b >>> 0)) | 0; return +(((a >>> 0) / (b >>> 0)) >>> 0);
} }
function u32_mod(a, b) { function u32_mod(a, b) {
a = a | 0; a = a | 0;
b = b | 0; b = b | 0;
return ((a >>> 0) % (b >>> 0)) | 0; return +(((a >>> 0) % (b >>> 0)) >>> 0);
} }
function u32_and(a, b) { function u32_and(a, b) {
...@@ -188,11 +188,10 @@ var inputs = [ ...@@ -188,11 +188,10 @@ var inputs = [
]; ];
var funcs = [ var funcs = [
// TODO(bradnelson): u32_add, u32_add,
// TODO(bradnelson): u32_sub, u32_sub,
// TODO(titzer): u32_mul requires Math.imul u32_div,
// TODO(titzer): u32_div by zero is incorrect u32_mod,
// TODO(titzer): u32_mod by zero is incorrect
// TODO(titzer): u32_mul crashes turbofan in asm.js mode // TODO(titzer): u32_mul crashes turbofan in asm.js mode
u32_and, u32_and,
u32_or, u32_or,
......
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