[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: Ben Titzer <titzer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50632}
This commit is contained in:
Karl Schimpf 2018-01-16 11:32:52 -08:00 committed by Commit Bot
parent 25b2116770
commit 1607614a91
7 changed files with 279 additions and 160 deletions

View File

@ -564,9 +564,15 @@ Node* WasmGraphBuilder::Unop(wasm::WasmOpcode opcode, Node* input,
op = m->Float64Sqrt();
break;
case wasm::kExprI32SConvertF64:
return BuildI32SConvertF64(input, position);
return BuildI32SConvertF64(input, position, NumericImplementation::kTrap);
case wasm::kExprI32SConvertSatF64:
return BuildI32SConvertF64(input, position,
NumericImplementation::kSaturate);
case wasm::kExprI32UConvertF64:
return BuildI32UConvertF64(input, position);
return BuildI32UConvertF64(input, position, NumericImplementation::kTrap);
case wasm::kExprI32UConvertSatF64:
return BuildI32UConvertF64(input, position,
NumericImplementation::kSaturate);
case wasm::kExprI32AsmjsSConvertF64:
return BuildI32AsmjsSConvertF64(input);
case wasm::kExprI32AsmjsUConvertF64:
@ -592,7 +598,10 @@ Node* WasmGraphBuilder::Unop(wasm::WasmOpcode opcode, Node* input,
return BuildI32SConvertF32(input, position,
NumericImplementation::kSaturate);
case wasm::kExprI32UConvertF32:
return BuildI32UConvertF32(input, position);
return BuildI32UConvertF32(input, position, NumericImplementation::kTrap);
case wasm::kExprI32UConvertSatF32:
return BuildI32UConvertF32(input, position,
NumericImplementation::kSaturate);
case wasm::kExprI32AsmjsSConvertF32:
return BuildI32AsmjsSConvertF32(input);
case wasm::kExprI32AsmjsUConvertF32:
@ -1364,93 +1373,167 @@ Node* WasmGraphBuilder::BuildF64CopySign(Node* left, Node* right) {
#endif
}
Node* WasmGraphBuilder::BuildI32SConvertF32(Node* input,
wasm::WasmCodePosition position,
NumericImplementation impl) {
MachineOperatorBuilder* m = jsgraph()->machine();
// Truncation of the input value is needed for the overflow check later.
Node* trunc = Unop(wasm::kExprF32Trunc, input);
Node* result = graph()->NewNode(m->TruncateFloat32ToInt32(), trunc);
// Helper classes for float to int conversions.
struct WasmGraphBuilder::IntConvertOps {
MachineRepresentation word_rep() const {
return MachineRepresentation::kWord32;
}
Node* zero() const { return builder_->Int32Constant(0); }
virtual Node* min() const = 0;
virtual Node* max() const = 0;
virtual ~IntConvertOps() = default;
// Convert the result back to f64. If we end up at a different value than the
// truncated input value, then there has been an overflow and we trap.
Node* check = Unop(wasm::kExprF32SConvertI32, result);
Node* overflow = Binop(wasm::kExprF32Ne, trunc, check);
protected:
explicit IntConvertOps(WasmGraphBuilder* builder) : builder_(builder) {}
WasmGraphBuilder* builder_;
DISALLOW_IMPLICIT_CONSTRUCTORS(IntConvertOps);
};
struct I32SConvertOps final : public WasmGraphBuilder::IntConvertOps {
explicit I32SConvertOps(WasmGraphBuilder* builder)
: WasmGraphBuilder::IntConvertOps(builder) {}
~I32SConvertOps() = default;
Node* min() const {
return builder_->Int32Constant(std::numeric_limits<int32_t>::min());
}
Node* max() const {
return builder_->Int32Constant(std::numeric_limits<int32_t>::max());
}
DISALLOW_IMPLICIT_CONSTRUCTORS(I32SConvertOps);
};
struct I32UConvertOps final : public WasmGraphBuilder::IntConvertOps {
explicit I32UConvertOps(WasmGraphBuilder* builder)
: WasmGraphBuilder::IntConvertOps(builder) {}
~I32UConvertOps() = default;
Node* min() const {
return builder_->Int32Constant(std::numeric_limits<uint32_t>::min());
}
Node* max() const {
return builder_->Int32Constant(std::numeric_limits<uint32_t>::max());
}
DISALLOW_IMPLICIT_CONSTRUCTORS(I32UConvertOps);
};
struct WasmGraphBuilder::FloatConvertOps {
virtual Node* zero() const = 0;
virtual wasm::WasmOpcode trunc_op() const = 0;
virtual wasm::WasmOpcode ne_op() const = 0;
virtual wasm::WasmOpcode lt_op() const = 0;
virtual ~FloatConvertOps() = default;
protected:
explicit FloatConvertOps(WasmGraphBuilder* builder) : builder_(builder) {}
WasmGraphBuilder* builder_;
DISALLOW_IMPLICIT_CONSTRUCTORS(FloatConvertOps);
};
struct F32ConvertOps final : public WasmGraphBuilder::FloatConvertOps {
explicit F32ConvertOps(WasmGraphBuilder* builder)
: WasmGraphBuilder::FloatConvertOps(builder) {}
~F32ConvertOps() = default;
Node* zero() const { return builder_->Float32Constant(0.0); }
wasm::WasmOpcode trunc_op() const { return wasm::kExprF32Trunc; }
wasm::WasmOpcode ne_op() const { return wasm::kExprF32Ne; }
wasm::WasmOpcode lt_op() const { return wasm::kExprF32Lt; }
DISALLOW_IMPLICIT_CONSTRUCTORS(F32ConvertOps);
};
struct F64ConvertOps final : public WasmGraphBuilder::FloatConvertOps {
explicit F64ConvertOps(WasmGraphBuilder* builder)
: WasmGraphBuilder::FloatConvertOps(builder) {}
~F64ConvertOps() = default;
Node* zero() const { return builder_->Float64Constant(0.0); }
wasm::WasmOpcode trunc_op() const { return wasm::kExprF64Trunc; }
wasm::WasmOpcode ne_op() const { return wasm::kExprF64Ne; }
wasm::WasmOpcode lt_op() const { return wasm::kExprF64Lt; }
DISALLOW_IMPLICIT_CONSTRUCTORS(F64ConvertOps);
};
Node* WasmGraphBuilder::BuildConvertCheck(Node* test, Node* result, Node* input,
wasm::WasmCodePosition position,
NumericImplementation impl,
const IntConvertOps* int_ops,
const FloatConvertOps* float_ops) {
switch (impl) {
case NumericImplementation::kTrap:
TrapIfTrue(wasm::kTrapFloatUnrepresentable, overflow, position);
TrapIfTrue(wasm::kTrapFloatUnrepresentable, test, position);
return result;
case NumericImplementation::kSaturate: {
Diamond tl_d(graph(), jsgraph()->common(), overflow, BranchHint::kFalse);
Diamond tl_d(graph(), jsgraph()->common(), test, BranchHint::kFalse);
tl_d.Chain(*control_);
Diamond nan_d(graph(), jsgraph()->common(),
Binop(wasm::kExprF32Ne, input, input), // Checks if NaN.
Binop(float_ops->ne_op(), input, input), // Checks if NaN.
BranchHint::kFalse);
nan_d.Nest(tl_d, true);
Diamond sat_d(
graph(), jsgraph()->common(),
graph()->NewNode(m->Float32LessThan(), input, Float32Constant(0.0)),
BranchHint::kNone);
Diamond sat_d(graph(), jsgraph()->common(),
Binop(float_ops->lt_op(), input, float_ops->zero()),
BranchHint::kNone);
sat_d.Nest(nan_d, false);
Node* sat_val =
sat_d.Phi(MachineRepresentation::kWord32,
Int32Constant(std::numeric_limits<int32_t>::min()),
Int32Constant(std::numeric_limits<int32_t>::max()));
Node* nan_val =
nan_d.Phi(MachineRepresentation::kWord32, Int32Constant(0), sat_val);
return tl_d.Phi(MachineRepresentation::kWord32, nan_val, result);
sat_d.Phi(int_ops->word_rep(), int_ops->min(), int_ops->max());
Node* nan_val = nan_d.Phi(int_ops->word_rep(), int_ops->zero(), sat_val);
return tl_d.Phi(int_ops->word_rep(), nan_val, result);
}
}
UNREACHABLE();
}
Node* WasmGraphBuilder::BuildI32SConvertF64(Node* input,
wasm::WasmCodePosition position) {
MachineOperatorBuilder* m = jsgraph()->machine();
Node* WasmGraphBuilder::BuildI32ConvertOp(
Node* input, wasm::WasmCodePosition position, NumericImplementation impl,
const Operator* op, wasm::WasmOpcode check_op, const IntConvertOps* int_ops,
const FloatConvertOps* float_ops) {
// Truncation of the input value is needed for the overflow check later.
Node* trunc = Unop(wasm::kExprF64Trunc, input);
Node* result = graph()->NewNode(m->ChangeFloat64ToInt32(), trunc);
Node* trunc = Unop(float_ops->trunc_op(), input);
Node* result = graph()->NewNode(op, trunc);
// Convert the result back to f64. If we end up at a different value than the
// truncated input value, then there has been an overflow and we trap.
Node* check = Unop(wasm::kExprF64SConvertI32, result);
Node* overflow = Binop(wasm::kExprF64Ne, trunc, check);
TrapIfTrue(wasm::kTrapFloatUnrepresentable, overflow, position);
// truncated input value, then there has been an overflow and we
// trap/saturate.
Node* check = Unop(check_op, result);
Node* overflow = Binop(float_ops->ne_op(), trunc, check);
return BuildConvertCheck(overflow, result, input, position, impl, int_ops,
float_ops);
}
return result;
Node* WasmGraphBuilder::BuildI32SConvertF32(Node* input,
wasm::WasmCodePosition position,
NumericImplementation impl) {
I32SConvertOps int_ops(this);
F32ConvertOps float_ops(this);
return BuildI32ConvertOp(input, position, impl,
jsgraph()->machine()->TruncateFloat32ToInt32(),
wasm::kExprF32SConvertI32, &int_ops, &float_ops);
}
Node* WasmGraphBuilder::BuildI32SConvertF64(Node* input,
wasm::WasmCodePosition position,
NumericImplementation impl) {
I32SConvertOps int_ops(this);
F64ConvertOps float_ops(this);
return BuildI32ConvertOp(input, position, impl,
jsgraph()->machine()->ChangeFloat64ToInt32(),
wasm::kExprF64SConvertI32, &int_ops, &float_ops);
}
Node* WasmGraphBuilder::BuildI32UConvertF32(Node* input,
wasm::WasmCodePosition position) {
MachineOperatorBuilder* m = jsgraph()->machine();
// Truncation of the input value is needed for the overflow check later.
Node* trunc = Unop(wasm::kExprF32Trunc, input);
Node* result = graph()->NewNode(m->TruncateFloat32ToUint32(), trunc);
// Convert the result back to f32. If we end up at a different value than the
// truncated input value, then there has been an overflow and we trap.
Node* check = Unop(wasm::kExprF32UConvertI32, result);
Node* overflow = Binop(wasm::kExprF32Ne, trunc, check);
TrapIfTrue(wasm::kTrapFloatUnrepresentable, overflow, position);
return result;
wasm::WasmCodePosition position,
NumericImplementation impl) {
I32UConvertOps int_ops(this);
F32ConvertOps float_ops(this);
return BuildI32ConvertOp(input, position, impl,
jsgraph()->machine()->TruncateFloat32ToUint32(),
wasm::kExprF32UConvertI32, &int_ops, &float_ops);
}
Node* WasmGraphBuilder::BuildI32UConvertF64(Node* input,
wasm::WasmCodePosition position) {
MachineOperatorBuilder* m = jsgraph()->machine();
// Truncation of the input value is needed for the overflow check later.
Node* trunc = Unop(wasm::kExprF64Trunc, input);
Node* result = graph()->NewNode(m->TruncateFloat64ToUint32(), trunc);
// Convert the result back to f64. If we end up at a different value than the
// truncated input value, then there has been an overflow and we trap.
Node* check = Unop(wasm::kExprF64UConvertI32, result);
Node* overflow = Binop(wasm::kExprF64Ne, trunc, check);
TrapIfTrue(wasm::kTrapFloatUnrepresentable, overflow, position);
return result;
wasm::WasmCodePosition position,
NumericImplementation impl) {
I32UConvertOps int_ops(this);
F64ConvertOps float_ops(this);
return BuildI32ConvertOp(input, position, impl,
jsgraph()->machine()->TruncateFloat64ToUint32(),
wasm::kExprF64UConvertI32, &int_ops, &float_ops);
}
Node* WasmGraphBuilder::BuildI32AsmjsSConvertF32(Node* input) {

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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) \

View File

@ -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)));

View File

@ -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.