Commit 1607614a authored by Karl Schimpf's avatar Karl Schimpf Committed by Commit Bot

[wasm] Add more saturating float to int conversions

Adds I32UConvertF32, I32SConvertF64, and I32UConvertF64 instructions.

Refactors code to use templates where appropriate, and to use
previously committed template function is_inbounds() when appropriate
in tests.

Bug: v8:7226
Change-Id: I2701e5fd0b21cefa1f285677f20616cfde29ab0d
Reviewed-on: https://chromium-review.googlesource.com/862609
Commit-Queue: Karl Schimpf <kschimpf@chromium.org>
Reviewed-by: 's avatarBen Titzer <titzer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50632}
parent 25b21167
This diff is collapsed.
......@@ -252,6 +252,8 @@ typedef ZoneVector<Node*> NodeVector;
class WasmGraphBuilder {
public:
enum EnforceBoundsCheck : bool { kNeedsBoundsCheck, kCanOmitBoundsCheck };
struct IntConvertOps;
struct FloatConvertOps;
WasmGraphBuilder(ModuleEnv* env, Zone* zone, JSGraph* graph,
Handle<Code> centry_stub, wasm::FunctionSig* sig,
......@@ -509,11 +511,25 @@ class WasmGraphBuilder {
Node* BuildF32CopySign(Node* left, Node* right);
Node* BuildF64CopySign(Node* left, Node* right);
Node* BuildI32ConvertOp(Node* input, wasm::WasmCodePosition position,
NumericImplementation impl, const Operator* op,
wasm::WasmOpcode check_op,
const IntConvertOps* int_ops,
const FloatConvertOps* float_ops);
Node* BuildConvertCheck(Node* test, Node* result, Node* input,
wasm::WasmCodePosition position,
NumericImplementation impl,
const IntConvertOps* int_ops,
const FloatConvertOps* float_ops);
Node* BuildI32SConvertF32(Node* input, wasm::WasmCodePosition position,
NumericImplementation impl);
Node* BuildI32SConvertF64(Node* input, wasm::WasmCodePosition position);
Node* BuildI32UConvertF32(Node* input, wasm::WasmCodePosition position);
Node* BuildI32UConvertF64(Node* input, wasm::WasmCodePosition position);
Node* BuildI32SConvertF64(Node* input, wasm::WasmCodePosition position,
NumericImplementation impl);
Node* BuildI32UConvertF32(Node* input, wasm::WasmCodePosition position,
NumericImplementation impl);
Node* BuildI32UConvertF64(Node* input, wasm::WasmCodePosition position,
NumericImplementation impl);
Node* BuildI32Ctz(Node* input);
Node* BuildI32Popcnt(Node* input);
Node* BuildI64Ctz(Node* input);
......
......@@ -126,6 +126,12 @@ namespace wasm {
V(F32CopySign, Float32) \
V(F64CopySign, Float64)
#define FOREACH_I32CONV_FLOATOP(V) \
V(I32SConvertF32, int32_t, float) \
V(I32SConvertF64, int32_t, double) \
V(I32UConvertF32, uint32_t, float) \
V(I32UConvertF64, uint32_t, double)
#define FOREACH_OTHER_UNOP(V) \
V(I32Clz, uint32_t) \
V(I32Ctz, uint32_t) \
......@@ -147,10 +153,6 @@ namespace wasm {
V(F64Floor, double) \
V(F64Trunc, double) \
V(F64NearestInt, double) \
V(I32SConvertF32, float) \
V(I32SConvertF64, double) \
V(I32UConvertF32, float) \
V(I32UConvertF64, double) \
V(I32ConvertI64, int64_t) \
V(I64SConvertF32, float) \
V(I64SConvertF64, double) \
......@@ -441,47 +443,26 @@ inline double ExecuteF64NearestInt(double a, TrapReason* trap) {
inline double ExecuteF64Sqrt(double a, TrapReason* trap) { return sqrt(a); }
int32_t ExecuteI32SConvertF32(float a, TrapReason* trap) {
if (is_inbounds<int32_t>(a)) {
return static_cast<int32_t>(a);
template <typename int_type, typename float_type>
int_type ExecuteConvert(float_type a, TrapReason* trap) {
if (is_inbounds<int_type>(a)) {
return static_cast<int_type>(a);
}
*trap = kTrapFloatUnrepresentable;
return 0;
}
int32_t ExecuteI32SConvertSatF32(float a) {
template <typename int_type, typename float_type>
int_type ExecuteConvertSaturate(float_type a) {
TrapReason base_trap = kTrapCount;
int32_t val = ExecuteI32SConvertF32(a, &base_trap);
int32_t val = ExecuteConvert<int_type>(a, &base_trap);
if (base_trap == kTrapCount) {
return val;
}
return std::isnan(a) ? 0
: (a < 0.0 ? std::numeric_limits<int32_t>::min()
: std::numeric_limits<int32_t>::max());
}
int32_t ExecuteI32SConvertF64(double a, TrapReason* trap) {
if (is_inbounds<int32_t>(a)) {
return static_cast<int32_t>(a);
}
*trap = kTrapFloatUnrepresentable;
return 0;
}
uint32_t ExecuteI32UConvertF32(float a, TrapReason* trap) {
if (is_inbounds<uint32_t>(a)) {
return static_cast<uint32_t>(a);
}
*trap = kTrapFloatUnrepresentable;
return 0;
}
uint32_t ExecuteI32UConvertF64(double a, TrapReason* trap) {
if (is_inbounds<uint32_t>(a)) {
return static_cast<uint32_t>(a);
}
*trap = kTrapFloatUnrepresentable;
return 0;
: (a < static_cast<float_type>(0.0)
? std::numeric_limits<int_type>::min()
: std::numeric_limits<int_type>::max());
}
inline uint32_t ExecuteI32ConvertI64(int64_t a, TrapReason* trap) {
......@@ -1550,12 +1531,18 @@ class ThreadImpl {
bool ExecuteNumericOp(WasmOpcode opcode, Decoder* decoder,
InterpreterCode* code, pc_t pc, int& len) {
switch (opcode) {
case kExprI32SConvertSatF32: {
float val = Pop().to<float>();
auto result = ExecuteI32SConvertSatF32(val);
Push(WasmValue(result));
case kExprI32SConvertSatF32:
Push(WasmValue(ExecuteConvertSaturate<int32_t>(Pop().to<float>())));
return true;
case kExprI32UConvertSatF32:
Push(WasmValue(ExecuteConvertSaturate<uint32_t>(Pop().to<float>())));
return true;
case kExprI32SConvertSatF64:
Push(WasmValue(ExecuteConvertSaturate<int32_t>(Pop().to<double>())));
return true;
case kExprI32UConvertSatF64:
Push(WasmValue(ExecuteConvertSaturate<uint32_t>(Pop().to<double>())));
return true;
}
default:
V8_Fatal(__FILE__, __LINE__, "Unknown or unimplemented opcode #%d:%s",
code->start[pc], OpcodeName(code->start[pc]));
......@@ -2143,19 +2130,27 @@ class ThreadImpl {
FOREACH_OTHER_BINOP(EXECUTE_OTHER_BINOP)
#undef EXECUTE_OTHER_BINOP
#define EXECUTE_OTHER_UNOP(name, ctype) \
#define EXECUTE_UNOP(name, ctype, exec_fn) \
case kExpr##name: { \
TrapReason trap = kTrapCount; \
ctype val = Pop().to<ctype>(); \
auto result = Execute##name(val, &trap); \
auto result = exec_fn(val, &trap); \
possible_nondeterminism_ |= has_nondeterminism(result); \
if (trap != kTrapCount) return DoTrap(trap, pc); \
Push(WasmValue(result)); \
break; \
}
#define EXECUTE_OTHER_UNOP(name, ctype) EXECUTE_UNOP(name, ctype, Execute##name)
FOREACH_OTHER_UNOP(EXECUTE_OTHER_UNOP)
#undef EXECUTE_OTHER_UNOP
#define EXECUTE_I32CONV_FLOATOP(name, out_type, in_type) \
EXECUTE_UNOP(name, in_type, ExecuteConvert<out_type>)
FOREACH_I32CONV_FLOATOP(EXECUTE_I32CONV_FLOATOP)
#undef EXECUTE_I32CONV_FLOATOP
#undef EXECUTE_UNOP
default:
V8_Fatal(__FILE__, __LINE__, "Unknown or unimplemented opcode #%d:%s",
code->start[pc], OpcodeName(code->start[pc]));
......@@ -2960,6 +2955,7 @@ WasmInterpreter::HeapObjectsScope::~HeapObjectsScope() {
#undef WASM_CTYPES
#undef FOREACH_SIMPLE_BINOP
#undef FOREACH_OTHER_BINOP
#undef FOREACH_I32CONV_FLOATOP
#undef FOREACH_OTHER_UNOP
} // namespace wasm
......
......@@ -49,6 +49,9 @@ namespace wasm {
#define CASE_CONVERT_OP(name, RES, SRC, src_suffix, str) \
CASE_##RES##_OP(U##name##SRC, str "_u/" src_suffix) \
CASE_##RES##_OP(S##name##SRC, str "_s/" src_suffix)
#define CASE_CONVERT_SAT_OP(name, RES, SRC, src_suffix, str) \
CASE_##RES##_OP(U##name##Sat##SRC, str "_u:sat/" src_suffix) \
CASE_##RES##_OP(S##name##Sat##SRC, str "_s:sat/" src_suffix)
#define CASE_L32_OP(name, str) \
CASE_SIGN_OP(I32, name##8, str "8") \
CASE_SIGN_OP(I32, name##16, str "16") \
......@@ -98,9 +101,9 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
CASE_I32_OP(ConvertI64, "wrap/i64")
CASE_CONVERT_OP(Convert, INT, F32, "f32", "trunc")
CASE_CONVERT_OP(Convert, INT, F64, "f64", "trunc")
// TODO(kschimpf): Simplify after filling in other saturating
// operations.
CASE_I32_OP(SConvertSatF32, "trunc_s:sat/f32")
// TODO(kschimpf): Add I64 versions of saturating conversions.
CASE_CONVERT_SAT_OP(Convert, I32, F32, "f32", "trunc")
CASE_CONVERT_SAT_OP(Convert, I32, F64, "f64", "trunc")
CASE_CONVERT_OP(Convert, I64, I32, "i32", "extend")
CASE_CONVERT_OP(Convert, F32, I32, "i32", "convert")
......@@ -279,6 +282,7 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
#undef CASE_UNSIGNED_OP
#undef CASE_ALL_SIGN_OP
#undef CASE_CONVERT_OP
#undef CASE_CONVERT_SAT_OP
#undef CASE_L32_OP
#undef CASE_U32_OP
......
......@@ -399,8 +399,12 @@ using WasmName = Vector<const char>;
V(S128LoadMem, 0xfd80, s_i) \
V(S128StoreMem, 0xfd81, v_is)
#define FOREACH_NUMERIC_OPCODE(V) V(I32SConvertSatF32, 0xfc00, i_f)
// TODO(kschimpf): Add remaining numeric opcodes.
#define FOREACH_NUMERIC_OPCODE(V) \
V(I32SConvertSatF32, 0xfc00, i_f) \
V(I32UConvertSatF32, 0xfc01, i_f) \
V(I32SConvertSatF64, 0xfc02, i_d) \
V(I32UConvertSatF64, 0xfc03, i_d)
// TODO(kschimpf): Add remaining i64 numeric opcodes.
#define FOREACH_ATOMIC_OPCODE(V) \
V(I32AtomicLoad, 0xfe10, i_i) \
......
......@@ -2854,17 +2854,8 @@ WASM_EXEC_TEST(I32SConvertF32) {
WasmRunner<int32_t, float> r(execution_mode);
BUILD(r, WASM_I32_SCONVERT_F32(WASM_GET_LOCAL(0)));
constexpr float kLowerBound =
static_cast<float>(std::numeric_limits<int32_t>::min());
constexpr float kUpperBound =
static_cast<float>(std::numeric_limits<int32_t>::max());
assert(static_cast<int64_t>(kUpperBound) >
static_cast<int64_t>(std::numeric_limits<int32_t>::max()));
assert(static_cast<int32_t>(kLowerBound) ==
std::numeric_limits<int32_t>::min());
FOR_FLOAT32_INPUTS(i) {
if (*i < kUpperBound && *i >= kLowerBound) {
if (is_inbounds<int32_t>(*i)) {
CHECK_EQ(static_cast<int32_t>(*i), r.Call(*i));
} else {
CHECK_TRAP32(r.Call(*i));
......@@ -2877,25 +2868,15 @@ WASM_EXEC_TEST(I32SConvertSatF32) {
WasmRunner<int32_t, float> r(execution_mode);
BUILD(r, WASM_I32_SCONVERT_SAT_F32(WASM_GET_LOCAL(0)));
constexpr float kLowerBound = std::numeric_limits<int32_t>::min();
constexpr float kUpperBound = std::numeric_limits<int32_t>::max();
FOR_FLOAT32_INPUTS(i) {
static_assert(static_cast<int64_t>(kUpperBound) >
static_cast<int64_t>(std::numeric_limits<int32_t>::max()),
"kUpperBound invalidates the following bounds check.");
static_assert(static_cast<int32_t>(kLowerBound) ==
std::numeric_limits<int32_t>::min(),
"kLowerBounds invalidates the following bounds check.");
if (*i < kUpperBound && *i >= kLowerBound) {
CHECK_EQ(static_cast<int32_t>(*i), r.Call(*i));
} else if (std::isnan(*i)) {
CHECK_EQ(0, r.Call(*i));
} else if (*i < 0.0) {
CHECK_EQ(std::numeric_limits<int32_t>::min(), r.Call(*i));
} else {
CHECK_EQ(std::numeric_limits<int32_t>::max(), r.Call(*i));
}
int32_t expected =
is_inbounds<int32_t>(*i)
? static_cast<int32_t>(*i)
: std::isnan(*i) ? 0
: *i < 0.0 ? std::numeric_limits<int32_t>::min()
: std::numeric_limits<int32_t>::max();
int32_t found = r.Call(*i);
CHECK_EQ(expected, found);
}
}
......@@ -2903,14 +2884,8 @@ WASM_EXEC_TEST(I32SConvertF64) {
WasmRunner<int32_t, double> r(execution_mode);
BUILD(r, WASM_I32_SCONVERT_F64(WASM_GET_LOCAL(0)));
// The upper bound is (INT32_MAX + 1), which is the lowest double-
// representable number above INT32_MAX which cannot be represented as int32.
double upper_bound = 2147483648.0;
// The lower bound is (INT32_MIN - 1), which is the greatest double-
// representable number below INT32_MIN which cannot be represented as int32.
double lower_bound = -2147483649.0;
FOR_FLOAT64_INPUTS(i) {
if (*i<upper_bound&& * i> lower_bound) {
if (is_inbounds<int32_t>(*i)) {
CHECK_EQ(static_cast<int32_t>(*i), r.Call(*i));
} else {
CHECK_TRAP32(r.Call(*i));
......@@ -2918,16 +2893,27 @@ WASM_EXEC_TEST(I32SConvertF64) {
}
}
WASM_EXEC_TEST(I32SConvertSatF64) {
EXPERIMENTAL_FLAG_SCOPE(sat_f2i_conversions);
WasmRunner<int32_t, double> r(execution_mode);
BUILD(r, WASM_I32_SCONVERT_SAT_F64(WASM_GET_LOCAL(0)));
FOR_FLOAT64_INPUTS(i) {
int32_t expected =
is_inbounds<int32_t>(*i)
? static_cast<int32_t>(*i)
: std::isnan(*i) ? 0
: *i < 0.0 ? std::numeric_limits<int32_t>::min()
: std::numeric_limits<int32_t>::max();
int32_t found = r.Call(*i);
CHECK_EQ(expected, found);
}
}
WASM_EXEC_TEST(I32UConvertF32) {
WasmRunner<uint32_t, float> r(execution_mode);
BUILD(r, WASM_I32_UCONVERT_F32(WASM_GET_LOCAL(0)));
// The upper bound is (UINT32_MAX + 1), which is the lowest
// float-representable number above UINT32_MAX which cannot be represented as
// uint32.
double upper_bound = 4294967296.0f;
double lower_bound = -1.0f;
FOR_FLOAT32_INPUTS(i) {
if (*i<upper_bound&& * i> lower_bound) {
if (is_inbounds<uint32_t>(*i)) {
CHECK_EQ(static_cast<uint32_t>(*i), r.Call(*i));
} else {
CHECK_TRAP32(r.Call(*i));
......@@ -2935,16 +2921,27 @@ WASM_EXEC_TEST(I32UConvertF32) {
}
}
WASM_EXEC_TEST(I32UConvertSatF32) {
EXPERIMENTAL_FLAG_SCOPE(sat_f2i_conversions);
WasmRunner<uint32_t, float> r(execution_mode);
BUILD(r, WASM_I32_UCONVERT_SAT_F32(WASM_GET_LOCAL(0)));
FOR_FLOAT32_INPUTS(i) {
int32_t expected =
is_inbounds<uint32_t>(*i)
? static_cast<uint32_t>(*i)
: std::isnan(*i) ? 0
: *i < 0.0 ? std::numeric_limits<uint32_t>::min()
: std::numeric_limits<uint32_t>::max();
int32_t found = r.Call(*i);
CHECK_EQ(expected, found);
}
}
WASM_EXEC_TEST(I32UConvertF64) {
WasmRunner<uint32_t, double> r(execution_mode);
BUILD(r, WASM_I32_UCONVERT_F64(WASM_GET_LOCAL(0)));
// The upper bound is (UINT32_MAX + 1), which is the lowest
// double-representable number above UINT32_MAX which cannot be represented as
// uint32.
double upper_bound = 4294967296.0;
double lower_bound = -1.0;
FOR_FLOAT64_INPUTS(i) {
if (*i<upper_bound&& * i> lower_bound) {
if (is_inbounds<uint32_t>(*i)) {
CHECK_EQ(static_cast<uint32_t>(*i), r.Call(*i));
} else {
CHECK_TRAP32(r.Call(*i));
......@@ -2952,6 +2949,22 @@ WASM_EXEC_TEST(I32UConvertF64) {
}
}
WASM_EXEC_TEST(I32UConvertSatF64) {
EXPERIMENTAL_FLAG_SCOPE(sat_f2i_conversions);
WasmRunner<uint32_t, double> r(execution_mode);
BUILD(r, WASM_I32_UCONVERT_SAT_F64(WASM_GET_LOCAL(0)));
FOR_FLOAT64_INPUTS(i) {
int32_t expected =
is_inbounds<uint32_t>(*i)
? static_cast<uint32_t>(*i)
: std::isnan(*i) ? 0
: *i < 0.0 ? std::numeric_limits<uint32_t>::min()
: std::numeric_limits<uint32_t>::max();
int32_t found = r.Call(*i);
CHECK_EQ(expected, found);
}
}
WASM_EXEC_TEST(F64CopySign) {
WasmRunner<double, double, double> r(execution_mode);
BUILD(r, WASM_F64_COPYSIGN(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
......
......@@ -561,6 +561,9 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
//------------------------------------------------------------------------------
#define WASM_NUMERIC_OP(op) kNumericPrefix, static_cast<byte>(op)
#define WASM_I32_SCONVERT_SAT_F32(x) x, WASM_NUMERIC_OP(kExprI32SConvertSatF32)
#define WASM_I32_UCONVERT_SAT_F32(x) x, WASM_NUMERIC_OP(kExprI32UConvertSatF32)
#define WASM_I32_SCONVERT_SAT_F64(x) x, WASM_NUMERIC_OP(kExprI32SConvertSatF64)
#define WASM_I32_UCONVERT_SAT_F64(x) x, WASM_NUMERIC_OP(kExprI32UConvertSatF64)
//------------------------------------------------------------------------------
// Memory Operations.
......
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