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; ...@@ -252,6 +252,8 @@ typedef ZoneVector<Node*> NodeVector;
class WasmGraphBuilder { class WasmGraphBuilder {
public: public:
enum EnforceBoundsCheck : bool { kNeedsBoundsCheck, kCanOmitBoundsCheck }; enum EnforceBoundsCheck : bool { kNeedsBoundsCheck, kCanOmitBoundsCheck };
struct IntConvertOps;
struct FloatConvertOps;
WasmGraphBuilder(ModuleEnv* env, Zone* zone, JSGraph* graph, WasmGraphBuilder(ModuleEnv* env, Zone* zone, JSGraph* graph,
Handle<Code> centry_stub, wasm::FunctionSig* sig, Handle<Code> centry_stub, wasm::FunctionSig* sig,
...@@ -509,11 +511,25 @@ class WasmGraphBuilder { ...@@ -509,11 +511,25 @@ class WasmGraphBuilder {
Node* BuildF32CopySign(Node* left, Node* right); Node* BuildF32CopySign(Node* left, Node* right);
Node* BuildF64CopySign(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, Node* BuildI32SConvertF32(Node* input, wasm::WasmCodePosition position,
NumericImplementation impl); NumericImplementation impl);
Node* BuildI32SConvertF64(Node* input, wasm::WasmCodePosition position); Node* BuildI32SConvertF64(Node* input, wasm::WasmCodePosition position,
Node* BuildI32UConvertF32(Node* input, wasm::WasmCodePosition position); NumericImplementation impl);
Node* BuildI32UConvertF64(Node* input, wasm::WasmCodePosition position); Node* BuildI32UConvertF32(Node* input, wasm::WasmCodePosition position,
NumericImplementation impl);
Node* BuildI32UConvertF64(Node* input, wasm::WasmCodePosition position,
NumericImplementation impl);
Node* BuildI32Ctz(Node* input); Node* BuildI32Ctz(Node* input);
Node* BuildI32Popcnt(Node* input); Node* BuildI32Popcnt(Node* input);
Node* BuildI64Ctz(Node* input); Node* BuildI64Ctz(Node* input);
......
...@@ -126,6 +126,12 @@ namespace wasm { ...@@ -126,6 +126,12 @@ namespace wasm {
V(F32CopySign, Float32) \ V(F32CopySign, Float32) \
V(F64CopySign, Float64) 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) \ #define FOREACH_OTHER_UNOP(V) \
V(I32Clz, uint32_t) \ V(I32Clz, uint32_t) \
V(I32Ctz, uint32_t) \ V(I32Ctz, uint32_t) \
...@@ -147,10 +153,6 @@ namespace wasm { ...@@ -147,10 +153,6 @@ namespace wasm {
V(F64Floor, double) \ V(F64Floor, double) \
V(F64Trunc, double) \ V(F64Trunc, double) \
V(F64NearestInt, double) \ V(F64NearestInt, double) \
V(I32SConvertF32, float) \
V(I32SConvertF64, double) \
V(I32UConvertF32, float) \
V(I32UConvertF64, double) \
V(I32ConvertI64, int64_t) \ V(I32ConvertI64, int64_t) \
V(I64SConvertF32, float) \ V(I64SConvertF32, float) \
V(I64SConvertF64, double) \ V(I64SConvertF64, double) \
...@@ -441,47 +443,26 @@ inline double ExecuteF64NearestInt(double a, TrapReason* trap) { ...@@ -441,47 +443,26 @@ inline double ExecuteF64NearestInt(double a, TrapReason* trap) {
inline double ExecuteF64Sqrt(double a, TrapReason* trap) { return sqrt(a); } inline double ExecuteF64Sqrt(double a, TrapReason* trap) { return sqrt(a); }
int32_t ExecuteI32SConvertF32(float a, TrapReason* trap) { template <typename int_type, typename float_type>
if (is_inbounds<int32_t>(a)) { int_type ExecuteConvert(float_type a, TrapReason* trap) {
return static_cast<int32_t>(a); if (is_inbounds<int_type>(a)) {
return static_cast<int_type>(a);
} }
*trap = kTrapFloatUnrepresentable; *trap = kTrapFloatUnrepresentable;
return 0; return 0;
} }
int32_t ExecuteI32SConvertSatF32(float a) { template <typename int_type, typename float_type>
int_type ExecuteConvertSaturate(float_type a) {
TrapReason base_trap = kTrapCount; TrapReason base_trap = kTrapCount;
int32_t val = ExecuteI32SConvertF32(a, &base_trap); int32_t val = ExecuteConvert<int_type>(a, &base_trap);
if (base_trap == kTrapCount) { if (base_trap == kTrapCount) {
return val; return val;
} }
return std::isnan(a) ? 0 return std::isnan(a) ? 0
: (a < 0.0 ? std::numeric_limits<int32_t>::min() : (a < static_cast<float_type>(0.0)
: std::numeric_limits<int32_t>::max()); ? std::numeric_limits<int_type>::min()
} : std::numeric_limits<int_type>::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;
} }
inline uint32_t ExecuteI32ConvertI64(int64_t a, TrapReason* trap) { inline uint32_t ExecuteI32ConvertI64(int64_t a, TrapReason* trap) {
...@@ -1550,12 +1531,18 @@ class ThreadImpl { ...@@ -1550,12 +1531,18 @@ class ThreadImpl {
bool ExecuteNumericOp(WasmOpcode opcode, Decoder* decoder, bool ExecuteNumericOp(WasmOpcode opcode, Decoder* decoder,
InterpreterCode* code, pc_t pc, int& len) { InterpreterCode* code, pc_t pc, int& len) {
switch (opcode) { switch (opcode) {
case kExprI32SConvertSatF32: { case kExprI32SConvertSatF32:
float val = Pop().to<float>(); Push(WasmValue(ExecuteConvertSaturate<int32_t>(Pop().to<float>())));
auto result = ExecuteI32SConvertSatF32(val); return true;
Push(WasmValue(result)); 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; return true;
}
default: default:
V8_Fatal(__FILE__, __LINE__, "Unknown or unimplemented opcode #%d:%s", V8_Fatal(__FILE__, __LINE__, "Unknown or unimplemented opcode #%d:%s",
code->start[pc], OpcodeName(code->start[pc])); code->start[pc], OpcodeName(code->start[pc]));
...@@ -2143,19 +2130,27 @@ class ThreadImpl { ...@@ -2143,19 +2130,27 @@ class ThreadImpl {
FOREACH_OTHER_BINOP(EXECUTE_OTHER_BINOP) FOREACH_OTHER_BINOP(EXECUTE_OTHER_BINOP)
#undef EXECUTE_OTHER_BINOP #undef EXECUTE_OTHER_BINOP
#define EXECUTE_OTHER_UNOP(name, ctype) \ #define EXECUTE_UNOP(name, ctype, exec_fn) \
case kExpr##name: { \ case kExpr##name: { \
TrapReason trap = kTrapCount; \ TrapReason trap = kTrapCount; \
ctype val = Pop().to<ctype>(); \ ctype val = Pop().to<ctype>(); \
auto result = Execute##name(val, &trap); \ auto result = exec_fn(val, &trap); \
possible_nondeterminism_ |= has_nondeterminism(result); \ possible_nondeterminism_ |= has_nondeterminism(result); \
if (trap != kTrapCount) return DoTrap(trap, pc); \ if (trap != kTrapCount) return DoTrap(trap, pc); \
Push(WasmValue(result)); \ Push(WasmValue(result)); \
break; \ break; \
} }
#define EXECUTE_OTHER_UNOP(name, ctype) EXECUTE_UNOP(name, ctype, Execute##name)
FOREACH_OTHER_UNOP(EXECUTE_OTHER_UNOP) FOREACH_OTHER_UNOP(EXECUTE_OTHER_UNOP)
#undef 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: default:
V8_Fatal(__FILE__, __LINE__, "Unknown or unimplemented opcode #%d:%s", V8_Fatal(__FILE__, __LINE__, "Unknown or unimplemented opcode #%d:%s",
code->start[pc], OpcodeName(code->start[pc])); code->start[pc], OpcodeName(code->start[pc]));
...@@ -2960,6 +2955,7 @@ WasmInterpreter::HeapObjectsScope::~HeapObjectsScope() { ...@@ -2960,6 +2955,7 @@ WasmInterpreter::HeapObjectsScope::~HeapObjectsScope() {
#undef WASM_CTYPES #undef WASM_CTYPES
#undef FOREACH_SIMPLE_BINOP #undef FOREACH_SIMPLE_BINOP
#undef FOREACH_OTHER_BINOP #undef FOREACH_OTHER_BINOP
#undef FOREACH_I32CONV_FLOATOP
#undef FOREACH_OTHER_UNOP #undef FOREACH_OTHER_UNOP
} // namespace wasm } // namespace wasm
......
...@@ -49,6 +49,9 @@ namespace wasm { ...@@ -49,6 +49,9 @@ namespace wasm {
#define CASE_CONVERT_OP(name, RES, SRC, src_suffix, str) \ #define CASE_CONVERT_OP(name, RES, SRC, src_suffix, str) \
CASE_##RES##_OP(U##name##SRC, str "_u/" src_suffix) \ CASE_##RES##_OP(U##name##SRC, str "_u/" src_suffix) \
CASE_##RES##_OP(S##name##SRC, str "_s/" 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) \ #define CASE_L32_OP(name, str) \
CASE_SIGN_OP(I32, name##8, str "8") \ CASE_SIGN_OP(I32, name##8, str "8") \
CASE_SIGN_OP(I32, name##16, str "16") \ CASE_SIGN_OP(I32, name##16, str "16") \
...@@ -98,9 +101,9 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) { ...@@ -98,9 +101,9 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
CASE_I32_OP(ConvertI64, "wrap/i64") CASE_I32_OP(ConvertI64, "wrap/i64")
CASE_CONVERT_OP(Convert, INT, F32, "f32", "trunc") CASE_CONVERT_OP(Convert, INT, F32, "f32", "trunc")
CASE_CONVERT_OP(Convert, INT, F64, "f64", "trunc") CASE_CONVERT_OP(Convert, INT, F64, "f64", "trunc")
// TODO(kschimpf): Simplify after filling in other saturating // TODO(kschimpf): Add I64 versions of saturating conversions.
// operations. CASE_CONVERT_SAT_OP(Convert, I32, F32, "f32", "trunc")
CASE_I32_OP(SConvertSatF32, "trunc_s:sat/f32") CASE_CONVERT_SAT_OP(Convert, I32, F64, "f64", "trunc")
CASE_CONVERT_OP(Convert, I64, I32, "i32", "extend") CASE_CONVERT_OP(Convert, I64, I32, "i32", "extend")
CASE_CONVERT_OP(Convert, F32, I32, "i32", "convert") CASE_CONVERT_OP(Convert, F32, I32, "i32", "convert")
...@@ -279,6 +282,7 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) { ...@@ -279,6 +282,7 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
#undef CASE_UNSIGNED_OP #undef CASE_UNSIGNED_OP
#undef CASE_ALL_SIGN_OP #undef CASE_ALL_SIGN_OP
#undef CASE_CONVERT_OP #undef CASE_CONVERT_OP
#undef CASE_CONVERT_SAT_OP
#undef CASE_L32_OP #undef CASE_L32_OP
#undef CASE_U32_OP #undef CASE_U32_OP
......
...@@ -399,8 +399,12 @@ using WasmName = Vector<const char>; ...@@ -399,8 +399,12 @@ using WasmName = Vector<const char>;
V(S128LoadMem, 0xfd80, s_i) \ V(S128LoadMem, 0xfd80, s_i) \
V(S128StoreMem, 0xfd81, v_is) V(S128StoreMem, 0xfd81, v_is)
#define FOREACH_NUMERIC_OPCODE(V) V(I32SConvertSatF32, 0xfc00, i_f) #define FOREACH_NUMERIC_OPCODE(V) \
// TODO(kschimpf): Add remaining numeric opcodes. 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) \ #define FOREACH_ATOMIC_OPCODE(V) \
V(I32AtomicLoad, 0xfe10, i_i) \ V(I32AtomicLoad, 0xfe10, i_i) \
......
...@@ -2854,17 +2854,8 @@ WASM_EXEC_TEST(I32SConvertF32) { ...@@ -2854,17 +2854,8 @@ WASM_EXEC_TEST(I32SConvertF32) {
WasmRunner<int32_t, float> r(execution_mode); WasmRunner<int32_t, float> r(execution_mode);
BUILD(r, WASM_I32_SCONVERT_F32(WASM_GET_LOCAL(0))); 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) { 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)); CHECK_EQ(static_cast<int32_t>(*i), r.Call(*i));
} else { } else {
CHECK_TRAP32(r.Call(*i)); CHECK_TRAP32(r.Call(*i));
...@@ -2877,25 +2868,15 @@ WASM_EXEC_TEST(I32SConvertSatF32) { ...@@ -2877,25 +2868,15 @@ WASM_EXEC_TEST(I32SConvertSatF32) {
WasmRunner<int32_t, float> r(execution_mode); WasmRunner<int32_t, float> r(execution_mode);
BUILD(r, WASM_I32_SCONVERT_SAT_F32(WASM_GET_LOCAL(0))); 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) { FOR_FLOAT32_INPUTS(i) {
static_assert(static_cast<int64_t>(kUpperBound) > int32_t expected =
static_cast<int64_t>(std::numeric_limits<int32_t>::max()), is_inbounds<int32_t>(*i)
"kUpperBound invalidates the following bounds check."); ? static_cast<int32_t>(*i)
static_assert(static_cast<int32_t>(kLowerBound) == : std::isnan(*i) ? 0
std::numeric_limits<int32_t>::min(), : *i < 0.0 ? std::numeric_limits<int32_t>::min()
"kLowerBounds invalidates the following bounds check."); : std::numeric_limits<int32_t>::max();
if (*i < kUpperBound && *i >= kLowerBound) { int32_t found = r.Call(*i);
CHECK_EQ(static_cast<int32_t>(*i), r.Call(*i)); CHECK_EQ(expected, found);
} 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));
}
} }
} }
...@@ -2903,14 +2884,8 @@ WASM_EXEC_TEST(I32SConvertF64) { ...@@ -2903,14 +2884,8 @@ WASM_EXEC_TEST(I32SConvertF64) {
WasmRunner<int32_t, double> r(execution_mode); WasmRunner<int32_t, double> r(execution_mode);
BUILD(r, WASM_I32_SCONVERT_F64(WASM_GET_LOCAL(0))); 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) { 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)); CHECK_EQ(static_cast<int32_t>(*i), r.Call(*i));
} else { } else {
CHECK_TRAP32(r.Call(*i)); CHECK_TRAP32(r.Call(*i));
...@@ -2918,16 +2893,27 @@ WASM_EXEC_TEST(I32SConvertF64) { ...@@ -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) { WASM_EXEC_TEST(I32UConvertF32) {
WasmRunner<uint32_t, float> r(execution_mode); WasmRunner<uint32_t, float> r(execution_mode);
BUILD(r, WASM_I32_UCONVERT_F32(WASM_GET_LOCAL(0))); 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) { 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)); CHECK_EQ(static_cast<uint32_t>(*i), r.Call(*i));
} else { } else {
CHECK_TRAP32(r.Call(*i)); CHECK_TRAP32(r.Call(*i));
...@@ -2935,16 +2921,27 @@ WASM_EXEC_TEST(I32UConvertF32) { ...@@ -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) { WASM_EXEC_TEST(I32UConvertF64) {
WasmRunner<uint32_t, double> r(execution_mode); WasmRunner<uint32_t, double> r(execution_mode);
BUILD(r, WASM_I32_UCONVERT_F64(WASM_GET_LOCAL(0))); 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) { 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)); CHECK_EQ(static_cast<uint32_t>(*i), r.Call(*i));
} else { } else {
CHECK_TRAP32(r.Call(*i)); CHECK_TRAP32(r.Call(*i));
...@@ -2952,6 +2949,22 @@ WASM_EXEC_TEST(I32UConvertF64) { ...@@ -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) { WASM_EXEC_TEST(F64CopySign) {
WasmRunner<double, double, double> r(execution_mode); WasmRunner<double, double, double> r(execution_mode);
BUILD(r, WASM_F64_COPYSIGN(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))); BUILD(r, WASM_F64_COPYSIGN(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
......
...@@ -561,6 +561,9 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) { ...@@ -561,6 +561,9 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#define WASM_NUMERIC_OP(op) kNumericPrefix, static_cast<byte>(op) #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_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. // 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