Consistently handle possible traps in all cases (#1062)
* consistently handle possible traps in asm.js ffi results
* consistently handle possible traps in i64/float conversions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h
index f6d0443..90e258c 100644
--- a/src/asm2wasm.h
+++ b/src/asm2wasm.h
@@ -34,7 +34,6 @@
#include "ast_utils.h"
#include "wasm-builder.h"
#include "wasm-emscripten.h"
-#include "wasm-printing.h"
#include "wasm-module-building.h"
namespace wasm {
@@ -788,7 +787,7 @@
}
// Some conversions might trap, so emit them safely if necessary
- Expression* makeTrappingFloatToInt(bool signed_, Expression* value) {
+ Expression* makeTrappingFloatToInt32(bool signed_, Expression* value) {
if (trapMode == TrapMode::Allow) {
auto ret = allocator.alloc<Unary>();
ret->value = value;
@@ -879,6 +878,76 @@
return ret;
}
+ Expression* makeTrappingFloatToInt64(bool signed_, Expression* value) {
+ if (trapMode == TrapMode::Allow) {
+ auto ret = allocator.alloc<Unary>();
+ ret->value = value;
+ bool isF64 = ret->value->type == f64;
+ if (signed_) {
+ ret->op = isF64 ? TruncSFloat64ToInt64 : TruncSFloat32ToInt64;
+ } else {
+ ret->op = isF64 ? TruncUFloat64ToInt64 : TruncUFloat32ToInt64;
+ }
+ ret->type = WasmType::i64;
+ return ret;
+ }
+ // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must do something
+ // First, normalize input to f64
+ auto input = value;
+ if (input->type == f32) {
+ auto conv = allocator.alloc<Unary>();
+ conv->op = PromoteFloat32;
+ conv->value = input;
+ conv->type = WasmType::f64;
+ input = conv;
+ }
+ // There is no "JS" way to handle this, as no i64s in JS, so always clamp if we don't allow traps
+ Call *ret = allocator.alloc<Call>();
+ ret->target = F64_TO_INT64;
+ ret->operands.push_back(input);
+ ret->type = i64;
+ static bool added = false;
+ if (!added) {
+ added = true;
+ auto func = new Function;
+ func->name = ret->target;
+ func->params.push_back(f64);
+ func->result = i64;
+ func->body = builder.makeUnary(TruncSFloat64ToInt64,
+ builder.makeGetLocal(0, f64)
+ );
+ // too small
+ func->body = builder.makeIf(
+ builder.makeBinary(LeFloat64,
+ builder.makeGetLocal(0, f64),
+ builder.makeConst(Literal(double(std::numeric_limits<int64_t>::min()) - 1))
+ ),
+ builder.makeConst(Literal(int64_t(std::numeric_limits<int64_t>::min()))),
+ func->body
+ );
+ // too big
+ func->body = builder.makeIf(
+ builder.makeBinary(GeFloat64,
+ builder.makeGetLocal(0, f64),
+ builder.makeConst(Literal(double(std::numeric_limits<int64_t>::max()) + 1))
+ ),
+ builder.makeConst(Literal(int64_t(std::numeric_limits<int64_t>::min()))), // NB: min here as well. anything out of range => to the min
+ func->body
+ );
+ // nan
+ func->body = builder.makeIf(
+ builder.makeBinary(NeFloat64,
+ builder.makeGetLocal(0, f64),
+ builder.makeGetLocal(0, f64)
+ ),
+ builder.makeConst(Literal(int64_t(std::numeric_limits<int64_t>::min()))), // NB: min here as well. anything invalid => to the min
+ func->body
+ );
+ wasm.addFunction(func);
+ }
+ return ret;
+ }
+
Expression* truncateToInt32(Expression* value) {
if (value->type == i64) return builder.makeUnary(UnaryOp::WrapInt64, value);
// either i32, or a call_import whose type we don't know yet (but would be legalized to i32 anyhow)
@@ -1302,10 +1371,12 @@
}
auto importResult = getModule()->getFunctionType(getModule()->getImport(curr->target)->functionType)->result;
if (curr->type != importResult) {
+ auto old = curr->type;
+ curr->type = importResult;
if (importResult == f64) {
// we use a JS f64 value which is the most general, and convert to it
- switch (curr->type) {
- case i32: replaceCurrent(parent->builder.makeUnary(TruncSFloat64ToInt32, curr)); break;
+ switch (old) {
+ case i32: replaceCurrent(parent->makeTrappingFloatToInt32(true /* signed, asm.js ffi */, curr)); break;
case f32: replaceCurrent(parent->builder.makeUnary(DemoteFloat64, curr)); break;
case none: {
// this function returns a value, but we are not using it, so it must be dropped.
@@ -1315,11 +1386,10 @@
default: WASM_UNREACHABLE();
}
} else {
- assert(curr->type == none);
+ assert(old == none);
// we don't want a return value here, but the import does provide one
// autodrop will do that for us.
}
- curr->type = importResult;
}
}
@@ -1836,7 +1906,7 @@
// ~, might be ~~ as a coercion or just a not
if (ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == B_NOT) {
// if we have an unsigned coercion on us, it is an unsigned op
- return makeTrappingFloatToInt(!isParentUnsignedCoercion(astStackHelper.getParent()), process(ast[2][2]));
+ return makeTrappingFloatToInt32(!isParentUnsignedCoercion(astStackHelper.getParent()), process(ast[2][2]));
}
// no bitwise unary not, so do xor with -1
auto ret = allocator.alloc<Binary>();
@@ -2038,10 +2108,8 @@
if (name == I64_S2D) return builder.makeUnary(UnaryOp::ConvertSInt64ToFloat64, value);
if (name == I64_U2F) return builder.makeUnary(UnaryOp::ConvertUInt64ToFloat32, value);
if (name == I64_U2D) return builder.makeUnary(UnaryOp::ConvertUInt64ToFloat64, value);
- if (name == I64_F2S) return builder.makeUnary(UnaryOp::TruncSFloat32ToInt64, value);
- if (name == I64_D2S) return builder.makeUnary(UnaryOp::TruncSFloat64ToInt64, value);
- if (name == I64_F2U) return builder.makeUnary(UnaryOp::TruncUFloat32ToInt64, value);
- if (name == I64_D2U) return builder.makeUnary(UnaryOp::TruncUFloat64ToInt64, value);
+ if (name == I64_F2S || name == I64_D2S) return makeTrappingFloatToInt64(true /* signed */, value);
+ if (name == I64_F2U || name == I64_D2U) return makeTrappingFloatToInt64(false /* unsigned */, value);
if (name == I64_BC2D) return builder.makeUnary(UnaryOp::ReinterpretInt64, value);
if (name == I64_BC2I) return builder.makeUnary(UnaryOp::ReinterpretFloat64, value);
if (name == I64_CTTZ) return builder.makeUnary(UnaryOp::CtzInt64, value);
diff --git a/src/asmjs/shared-constants.cpp b/src/asmjs/shared-constants.cpp
index 02e80d3..f1c5ac5 100644
--- a/src/asmjs/shared-constants.cpp
+++ b/src/asmjs/shared-constants.cpp
@@ -40,6 +40,7 @@
ASM2WASM("asm2wasm"),
F64_REM("f64-rem"),
F64_TO_INT("f64-to-int"),
+ F64_TO_INT64("f64-to-int64"),
I32S_DIV("i32s-div"),
I32U_DIV("i32u-div"),
I32S_REM("i32s-rem"),
diff --git a/src/asmjs/shared-constants.h b/src/asmjs/shared-constants.h
index f130237..e3108c8 100644
--- a/src/asmjs/shared-constants.h
+++ b/src/asmjs/shared-constants.h
@@ -43,6 +43,7 @@
ASM2WASM,
F64_REM,
F64_TO_INT,
+ F64_TO_INT64,
I32S_DIV,
I32U_DIV,
I32S_REM,
diff --git a/test/unit.fromasm b/test/unit.fromasm
index e5ff236..c869e23 100644
--- a/test/unit.fromasm
+++ b/test/unit.fromasm
@@ -613,7 +613,7 @@
(drop
(if (result i32)
(call $return_int)
- (i32.trunc_s/f64
+ (call $f64-to-int
(call $abort
(f64.convert_s/i32
(i32.const 5)
diff --git a/test/unit.fromasm.clamp b/test/unit.fromasm.clamp
index e21d0f2..370e66c 100644
--- a/test/unit.fromasm.clamp
+++ b/test/unit.fromasm.clamp
@@ -637,7 +637,7 @@
(drop
(if (result i32)
(call $return_int)
- (i32.trunc_s/f64
+ (call $f64-to-int
(call $abort
(f64.convert_s/i32
(i32.const 5)
diff --git a/test/unit.fromasm.clamp.no-opts b/test/unit.fromasm.clamp.no-opts
index 18f16d3..8ff2656 100644
--- a/test/unit.fromasm.clamp.no-opts
+++ b/test/unit.fromasm.clamp.no-opts
@@ -1097,7 +1097,7 @@
(set_local $x
(if (result i32)
(call $return_int)
- (i32.trunc_s/f64
+ (call $f64-to-int
(call $abort
(f64.convert_s/i32
(i32.const 5)
diff --git a/test/unit.fromasm.no-opts b/test/unit.fromasm.no-opts
index 841a4d3..dba91cf 100644
--- a/test/unit.fromasm.no-opts
+++ b/test/unit.fromasm.no-opts
@@ -1073,7 +1073,7 @@
(set_local $x
(if (result i32)
(call $return_int)
- (i32.trunc_s/f64
+ (call $f64-to-int
(call $abort
(f64.convert_s/i32
(i32.const 5)
diff --git a/test/wasm-only.fromasm b/test/wasm-only.fromasm
index b8bda7f..f12226b 100644
--- a/test/wasm-only.fromasm
+++ b/test/wasm-only.fromasm
@@ -191,10 +191,38 @@
)
)
)
+ (func $f64-to-int64 (param $0 f64) (result i64)
+ (if (result i64)
+ (f64.ne
+ (get_local $0)
+ (get_local $0)
+ )
+ (i64.const -9223372036854775808)
+ (if (result i64)
+ (f64.ge
+ (get_local $0)
+ (f64.const 9223372036854775808)
+ )
+ (i64.const -9223372036854775808)
+ (if (result i64)
+ (f64.le
+ (get_local $0)
+ (f64.const -9223372036854775808)
+ )
+ (i64.const -9223372036854775808)
+ (i64.trunc_s/f64
+ (get_local $0)
+ )
+ )
+ )
+ )
+ )
(func $test64
(local $0 i64)
(local $1 i64)
(local $2 i32)
+ (local $3 f32)
+ (local $4 f64)
(drop
(call $i64s-rem
(call $i64u-rem
@@ -203,27 +231,27 @@
(i64.mul
(i64.sub
(i64.add
- (tee_local $0
+ (tee_local $1
(i64.const 128849018897)
)
(i64.const 100)
)
- (get_local $0)
+ (get_local $1)
)
- (get_local $0)
+ (get_local $1)
)
- (get_local $0)
+ (get_local $1)
)
- (get_local $0)
+ (get_local $1)
)
- (get_local $0)
+ (get_local $1)
)
- (get_local $0)
+ (get_local $1)
)
)
(i64.store
(i32.const 120)
- (tee_local $1
+ (tee_local $0
(i64.load
(i32.const 120)
)
@@ -231,30 +259,62 @@
)
(i64.store
(i32.const 120)
- (get_local $1)
+ (get_local $0)
)
(i64.store align=2
(i32.const 120)
- (get_local $1)
+ (get_local $0)
)
(i64.store align=4
(i32.const 120)
- (get_local $1)
+ (get_local $0)
)
(i64.store
(i32.const 120)
- (get_local $1)
+ (get_local $0)
)
(set_local $2
(i32.wrap/i64
- (get_local $1)
+ (get_local $0)
)
)
- (set_local $1
+ (set_local $0
(i64.extend_u/i32
(get_local $2)
)
)
+ (drop
+ (call $f64-to-int64
+ (f64.promote/f32
+ (tee_local $3
+ (f32.convert_u/i64
+ (get_local $0)
+ )
+ )
+ )
+ )
+ )
+ (drop
+ (call $f64-to-int64
+ (tee_local $4
+ (f64.convert_u/i64
+ (get_local $0)
+ )
+ )
+ )
+ )
+ (drop
+ (call $f64-to-int64
+ (f64.promote/f32
+ (get_local $3)
+ )
+ )
+ )
+ (drop
+ (call $f64-to-int64
+ (get_local $4)
+ )
+ )
)
(func $imports (result i64)
(call $legalfunc$illegalImport
diff --git a/test/wasm-only.fromasm.clamp b/test/wasm-only.fromasm.clamp
index b8bda7f..f12226b 100644
--- a/test/wasm-only.fromasm.clamp
+++ b/test/wasm-only.fromasm.clamp
@@ -191,10 +191,38 @@
)
)
)
+ (func $f64-to-int64 (param $0 f64) (result i64)
+ (if (result i64)
+ (f64.ne
+ (get_local $0)
+ (get_local $0)
+ )
+ (i64.const -9223372036854775808)
+ (if (result i64)
+ (f64.ge
+ (get_local $0)
+ (f64.const 9223372036854775808)
+ )
+ (i64.const -9223372036854775808)
+ (if (result i64)
+ (f64.le
+ (get_local $0)
+ (f64.const -9223372036854775808)
+ )
+ (i64.const -9223372036854775808)
+ (i64.trunc_s/f64
+ (get_local $0)
+ )
+ )
+ )
+ )
+ )
(func $test64
(local $0 i64)
(local $1 i64)
(local $2 i32)
+ (local $3 f32)
+ (local $4 f64)
(drop
(call $i64s-rem
(call $i64u-rem
@@ -203,27 +231,27 @@
(i64.mul
(i64.sub
(i64.add
- (tee_local $0
+ (tee_local $1
(i64.const 128849018897)
)
(i64.const 100)
)
- (get_local $0)
+ (get_local $1)
)
- (get_local $0)
+ (get_local $1)
)
- (get_local $0)
+ (get_local $1)
)
- (get_local $0)
+ (get_local $1)
)
- (get_local $0)
+ (get_local $1)
)
- (get_local $0)
+ (get_local $1)
)
)
(i64.store
(i32.const 120)
- (tee_local $1
+ (tee_local $0
(i64.load
(i32.const 120)
)
@@ -231,30 +259,62 @@
)
(i64.store
(i32.const 120)
- (get_local $1)
+ (get_local $0)
)
(i64.store align=2
(i32.const 120)
- (get_local $1)
+ (get_local $0)
)
(i64.store align=4
(i32.const 120)
- (get_local $1)
+ (get_local $0)
)
(i64.store
(i32.const 120)
- (get_local $1)
+ (get_local $0)
)
(set_local $2
(i32.wrap/i64
- (get_local $1)
+ (get_local $0)
)
)
- (set_local $1
+ (set_local $0
(i64.extend_u/i32
(get_local $2)
)
)
+ (drop
+ (call $f64-to-int64
+ (f64.promote/f32
+ (tee_local $3
+ (f32.convert_u/i64
+ (get_local $0)
+ )
+ )
+ )
+ )
+ )
+ (drop
+ (call $f64-to-int64
+ (tee_local $4
+ (f64.convert_u/i64
+ (get_local $0)
+ )
+ )
+ )
+ )
+ (drop
+ (call $f64-to-int64
+ (f64.promote/f32
+ (get_local $3)
+ )
+ )
+ )
+ (drop
+ (call $f64-to-int64
+ (get_local $4)
+ )
+ )
)
(func $imports (result i64)
(call $legalfunc$illegalImport
diff --git a/test/wasm-only.fromasm.clamp.no-opts b/test/wasm-only.fromasm.clamp.no-opts
index 97533d7..baac47f 100644
--- a/test/wasm-only.fromasm.clamp.no-opts
+++ b/test/wasm-only.fromasm.clamp.no-opts
@@ -341,6 +341,32 @@
)
)
)
+ (func $f64-to-int64 (param $0 f64) (result i64)
+ (if (result i64)
+ (f64.ne
+ (get_local $0)
+ (get_local $0)
+ )
+ (i64.const -9223372036854775808)
+ (if (result i64)
+ (f64.ge
+ (get_local $0)
+ (f64.const 9223372036854775808)
+ )
+ (i64.const -9223372036854775808)
+ (if (result i64)
+ (f64.le
+ (get_local $0)
+ (f64.const -9223372036854775808)
+ )
+ (i64.const -9223372036854775808)
+ (i64.trunc_s/f64
+ (get_local $0)
+ )
+ )
+ )
+ )
+ )
(func $test64
(local $x i64)
(local $y i64)
@@ -573,22 +599,26 @@
)
)
(set_local $x
- (i64.trunc_s/f32
- (get_local $float32)
+ (call $f64-to-int64
+ (f64.promote/f32
+ (get_local $float32)
+ )
)
)
(set_local $x
- (i64.trunc_s/f64
+ (call $f64-to-int64
(get_local $float64)
)
)
(set_local $x
- (i64.trunc_u/f32
- (get_local $float32)
+ (call $f64-to-int64
+ (f64.promote/f32
+ (get_local $float32)
+ )
)
)
(set_local $x
- (i64.trunc_u/f64
+ (call $f64-to-int64
(get_local $float64)
)
)
diff --git a/test/wasm-only.fromasm.no-opts b/test/wasm-only.fromasm.no-opts
index 97533d7..baac47f 100644
--- a/test/wasm-only.fromasm.no-opts
+++ b/test/wasm-only.fromasm.no-opts
@@ -341,6 +341,32 @@
)
)
)
+ (func $f64-to-int64 (param $0 f64) (result i64)
+ (if (result i64)
+ (f64.ne
+ (get_local $0)
+ (get_local $0)
+ )
+ (i64.const -9223372036854775808)
+ (if (result i64)
+ (f64.ge
+ (get_local $0)
+ (f64.const 9223372036854775808)
+ )
+ (i64.const -9223372036854775808)
+ (if (result i64)
+ (f64.le
+ (get_local $0)
+ (f64.const -9223372036854775808)
+ )
+ (i64.const -9223372036854775808)
+ (i64.trunc_s/f64
+ (get_local $0)
+ )
+ )
+ )
+ )
+ )
(func $test64
(local $x i64)
(local $y i64)
@@ -573,22 +599,26 @@
)
)
(set_local $x
- (i64.trunc_s/f32
- (get_local $float32)
+ (call $f64-to-int64
+ (f64.promote/f32
+ (get_local $float32)
+ )
)
)
(set_local $x
- (i64.trunc_s/f64
+ (call $f64-to-int64
(get_local $float64)
)
)
(set_local $x
- (i64.trunc_u/f32
- (get_local $float32)
+ (call $f64-to-int64
+ (f64.promote/f32
+ (get_local $float32)
+ )
)
)
(set_local $x
- (i64.trunc_u/f64
+ (call $f64-to-int64
(get_local $float64)
)
)