Commit 013e86b8 authored by Karl Schimpf's avatar Karl Schimpf Committed by Commit Bot

Add saturating f32 to i32 conversion to WASM

This CL adds the i32.trunc_s:sat/f32 WASM opcode to the turbofan
compiler and interpreter (more saturating operators will be added in
later CLs).

The operatation has been added under an experimental flag.

Bug: v8:7226
Change-Id: Ia69e981ffddb2da682e53ba25f489fc9d0cd2db5
Reviewed-on: https://chromium-review.googlesource.com/834670
Commit-Queue: Karl Schimpf <kschimpf@chromium.org>
Reviewed-by: 's avatarClemens Hammacher <clemensh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50311}
parent 13485a69
......@@ -585,7 +585,10 @@ Node* WasmGraphBuilder::Unop(wasm::WasmOpcode opcode, Node* input,
op = m->RoundUint32ToFloat32();
break;
case wasm::kExprI32SConvertF32:
return BuildI32SConvertF32(input, position);
return BuildI32SConvertF32(input, position, NumericImplementation::kTrap);
case wasm::kExprI32SConvertSatF32:
return BuildI32SConvertF32(input, position,
NumericImplementation::kSaturate);
case wasm::kExprI32UConvertF32:
return BuildI32UConvertF32(input, position);
case wasm::kExprI32AsmjsSConvertF32:
......@@ -1360,7 +1363,8 @@ Node* WasmGraphBuilder::BuildF64CopySign(Node* left, Node* right) {
}
Node* WasmGraphBuilder::BuildI32SConvertF32(Node* input,
wasm::WasmCodePosition position) {
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);
......@@ -1370,9 +1374,33 @@ Node* WasmGraphBuilder::BuildI32SConvertF32(Node* input,
// 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);
TrapIfTrue(wasm::kTrapFloatUnrepresentable, overflow, position);
return result;
switch (impl) {
case NumericImplementation::kTrap:
TrapIfTrue(wasm::kTrapFloatUnrepresentable, overflow, position);
return result;
case NumericImplementation::kSaturate: {
Diamond tl_d(graph(), jsgraph()->common(), overflow, BranchHint::kFalse);
tl_d.Chain(*control_);
Diamond nan_d(graph(), jsgraph()->common(),
Binop(wasm::kExprF32Ne, 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);
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);
}
}
UNREACHABLE();
}
Node* WasmGraphBuilder::BuildI32SConvertF64(Node* input,
......
......@@ -422,6 +422,7 @@ class WasmGraphBuilder {
bool use_trap_handler() const { return env_ && env_->use_trap_handler; }
private:
enum class NumericImplementation : uint8_t { kTrap, kSaturate };
static const int kDefaultBufferSize = 16;
Zone* zone_;
......@@ -480,7 +481,8 @@ class WasmGraphBuilder {
Node* BuildF32CopySign(Node* left, Node* right);
Node* BuildF64CopySign(Node* left, Node* right);
Node* BuildI32SConvertF32(Node* input, wasm::WasmCodePosition position);
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);
......
......@@ -548,6 +548,8 @@ DEFINE_BOOL(experimental_wasm_mv, false,
"enable prototype multi-value support for wasm")
DEFINE_BOOL(experimental_wasm_threads, false,
"enable prototype threads for wasm")
DEFINE_BOOL(experimental_wasm_sat_f2i_conversions, false,
"enable non-trapping float-to-int conversions for wasm")
DEFINE_BOOL(wasm_opt, false, "enable wasm optimization")
DEFINE_BOOL(wasm_no_bounds_checks, false,
......
......@@ -27,6 +27,8 @@ struct WasmException;
if (FLAG_trace_wasm_decoder) PrintF(__VA_ARGS__); \
} while (false)
#define TRACE_INST_FORMAT " @%-8d #%-20s|"
// Return the evaluation of `condition` if validate==true, DCHECK that it's
// true and always return true otherwise.
#define VALIDATE(condition) \
......@@ -971,6 +973,8 @@ class WasmDecoder : public Decoder {
return 5;
case kExprF64Const:
return 9;
case kNumericPrefix:
return 2;
case kSimdPrefix: {
byte simd_index = decoder->read_u8<validate>(pc + 1, "simd_index");
WasmOpcode opcode =
......@@ -1075,6 +1079,7 @@ class WasmDecoder : public Decoder {
case kExprReturn:
case kExprUnreachable:
return {0, 0};
case kNumericPrefix:
case kAtomicPrefix:
case kSimdPrefix: {
opcode = static_cast<WasmOpcode>(opcode << 8 | *(pc + 1));
......@@ -1333,7 +1338,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
TraceLine trace_msg;
#define TRACE_PART(...) trace_msg.Append(__VA_ARGS__)
if (!WasmOpcodes::IsPrefixOpcode(opcode)) {
TRACE_PART(" @%-8d #%-20s|", startrel(this->pc_),
TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_),
WasmOpcodes::OpcodeName(opcode));
}
#else
......@@ -1506,7 +1511,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
}
last_end_found_ = true;
// The result of the block is the return value.
TRACE_PART("\n @%-8d #%-20s|", startrel(this->pc_),
TRACE_PART("\n" TRACE_INST_FORMAT, startrel(this->pc_),
"(implicit) return");
DoReturn(c, true);
}
......@@ -1780,13 +1785,30 @@ class WasmFullDecoder : public WasmDecoder<validate> {
args_.data(), returns);
break;
}
case kNumericPrefix: {
CHECK_PROTOTYPE_OPCODE(sat_f2i_conversions);
++len;
byte numeric_index = this->template read_u8<validate>(
this->pc_ + 1, "numeric index");
opcode = static_cast<WasmOpcode>(opcode << 8 | numeric_index);
TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_),
WasmOpcodes::OpcodeName(opcode));
sig = WasmOpcodes::Signature(opcode);
if (sig == nullptr) {
this->errorf(this->pc_, "Unrecognized numeric opcode: %x\n",
opcode);
return;
}
BuildSimpleOperator(opcode, sig);
break;
}
case kSimdPrefix: {
CHECK_PROTOTYPE_OPCODE(simd);
len++;
byte simd_index =
this->template read_u8<validate>(this->pc_ + 1, "simd index");
opcode = static_cast<WasmOpcode>(opcode << 8 | simd_index);
TRACE_PART(" @%-4d #%-20s|", startrel(this->pc_),
TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_),
WasmOpcodes::OpcodeName(opcode));
len += DecodeSimdOpcode(opcode);
break;
......@@ -1798,7 +1820,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
byte atomic_index =
this->template read_u8<validate>(this->pc_ + 1, "atomic index");
opcode = static_cast<WasmOpcode>(opcode << 8 | atomic_index);
TRACE_PART(" @%-4d #%-20s|", startrel(this->pc_),
TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_),
WasmOpcodes::OpcodeName(opcode));
len += DecodeAtomicOpcode(opcode);
break;
......@@ -2358,6 +2380,7 @@ class EmptyInterface {
};
#undef TRACE
#undef TRACE_INST_FORMAT
#undef VALIDATE
#undef CHECK_PROTOTYPE_OPCODE
#undef OPCODE_ERROR
......
......@@ -456,6 +456,17 @@ int32_t ExecuteI32SConvertF32(float a, TrapReason* trap) {
return 0;
}
int32_t ExecuteI32SConvertSatF32(float a) {
TrapReason base_trap = kTrapCount;
int32_t val = ExecuteI32SConvertF32(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) {
// The upper bound is (INT32_MAX + 1), which is the lowest double-
// representable number above INT32_MAX which cannot be represented as int32.
......@@ -1559,6 +1570,23 @@ class ThreadImpl {
return true;
}
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));
return true;
}
default:
V8_Fatal(__FILE__, __LINE__, "Unknown or unimplemented opcode #%d:%s",
code->start[pc], OpcodeName(code->start[pc]));
UNREACHABLE();
}
return false;
}
bool ExecuteAtomicOp(WasmOpcode opcode, Decoder* decoder,
InterpreterCode* code, pc_t pc, int& len) {
WasmValue result;
......@@ -2102,6 +2130,11 @@ class ThreadImpl {
Push(WasmValue(ExecuteI64ReinterpretF64(val)));
break;
}
case kNumericPrefix: {
++len;
if (!ExecuteNumericOp(opcode, &decoder, code, pc, len)) return;
break;
}
case kAtomicPrefix: {
if (!ExecuteAtomicOp(opcode, &decoder, code, pc, len)) return;
break;
......
......@@ -98,6 +98,10 @@ 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")
CASE_CONVERT_OP(Convert, I64, I32, "i32", "extend")
CASE_CONVERT_OP(Convert, F32, I32, "i32", "convert")
CASE_CONVERT_OP(Convert, F32, I64, "i64", "convert")
......@@ -391,6 +395,14 @@ struct GetAtomicOpcodeSigIndex {
}
};
struct GetNumericOpcodeSigIndex {
constexpr WasmOpcodeSig operator()(byte opcode) const {
#define CASE(name, opc, sig) opcode == (opc & 0xFF) ? kSigEnum_##sig:
return FOREACH_NUMERIC_OPCODE(CASE) kSigEnum_None;
#undef CASE
}
};
constexpr std::array<WasmOpcodeSig, 256> kSimpleExprSigTable =
base::make_array<256>(GetOpcodeSigIndex{});
constexpr std::array<WasmOpcodeSig, 256> kSimpleAsmjsExprSigTable =
......@@ -399,20 +411,26 @@ constexpr std::array<WasmOpcodeSig, 256> kSimdExprSigTable =
base::make_array<256>(GetSimdOpcodeSigIndex{});
constexpr std::array<WasmOpcodeSig, 256> kAtomicExprSigTable =
base::make_array<256>(GetAtomicOpcodeSigIndex{});
constexpr std::array<WasmOpcodeSig, 256> kNumericExprSigTable =
base::make_array<256>(GetNumericOpcodeSigIndex{});
} // namespace
FunctionSig* WasmOpcodes::Signature(WasmOpcode opcode) {
if (opcode >> 8 == kSimdPrefix) {
return const_cast<FunctionSig*>(
kSimpleExprSigs[kSimdExprSigTable[opcode & 0xFF]]);
} else if (opcode >> 8 == kAtomicPrefix) {
return const_cast<FunctionSig*>(
kSimpleExprSigs[kAtomicExprSigTable[opcode & 0xFF]]);
} else {
DCHECK_GT(kSimpleExprSigTable.size(), opcode);
return const_cast<FunctionSig*>(
kSimpleExprSigs[kSimpleExprSigTable[opcode]]);
switch (opcode >> 8) {
case kSimdPrefix:
return const_cast<FunctionSig*>(
kSimpleExprSigs[kSimdExprSigTable[opcode & 0xFF]]);
case kAtomicPrefix:
return const_cast<FunctionSig*>(
kSimpleExprSigs[kAtomicExprSigTable[opcode & 0xFF]]);
case kNumericPrefix:
return const_cast<FunctionSig*>(
kSimpleExprSigs[kNumericExprSigTable[opcode & 0xFF]]);
default:
DCHECK_GT(kSimpleExprSigTable.size(), opcode);
return const_cast<FunctionSig*>(
kSimpleExprSigs[kSimpleExprSigTable[opcode]]);
}
}
......
......@@ -415,6 +415,9 @@ constexpr WasmCodePosition kNoCodePosition = -1;
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_ATOMIC_OPCODE(V) \
V(I32AtomicLoad, 0xfe10, i_i) \
V(I32AtomicLoad8U, 0xfe12, i_i) \
......@@ -457,7 +460,8 @@ constexpr WasmCodePosition kNoCodePosition = -1;
FOREACH_SIMD_1_OPERAND_OPCODE(V) \
FOREACH_SIMD_MASK_OPERAND_OPCODE(V) \
FOREACH_SIMD_MEM_OPCODE(V) \
FOREACH_ATOMIC_OPCODE(V)
FOREACH_ATOMIC_OPCODE(V) \
FOREACH_NUMERIC_OPCODE(V)
// All signatures.
#define FOREACH_SIGNATURE(V) \
......@@ -504,6 +508,7 @@ constexpr WasmCodePosition kNoCodePosition = -1;
V(s_sss, kWasmS128, kWasmS128, kWasmS128, kWasmS128)
#define FOREACH_PREFIX(V) \
V(Numeric, 0xfc) \
V(Simd, 0xfd) \
V(Atomic, 0xfe)
......
......@@ -2853,15 +2853,17 @@ WASM_EXEC_TEST(I32SConvertF32) {
WasmRunner<int32_t, float> r(execution_mode);
BUILD(r, WASM_I32_SCONVERT_F32(WASM_GET_LOCAL(0)));
// The upper bound is (INT32_MAX + 1), which is the lowest float-representable
// number above INT32_MAX which cannot be represented as int32.
float upper_bound = 2147483648.0f;
// We use INT32_MIN as a lower bound because (INT32_MIN - 1) is not
// representable as float, and no number between (INT32_MIN - 1) and INT32_MIN
// is.
float lower_bound = static_cast<float>(INT32_MIN);
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 < upper_bound && *i >= lower_bound) {
if (*i < kUpperBound && *i >= kLowerBound) {
CHECK_EQ(static_cast<int32_t>(*i), r.Call(*i));
} else {
CHECK_TRAP32(r.Call(*i));
......@@ -2869,6 +2871,33 @@ WASM_EXEC_TEST(I32SConvertF32) {
}
}
WASM_EXEC_TEST(I32SConvertSatF32) {
EXPERIMENTAL_FLAG_SCOPE(sat_f2i_conversions);
WasmRunner<int32_t, float> r(execution_mode);
BUILD(r, WASM_I32_SCONVERT_SAT_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) {
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));
}
}
}
WASM_EXEC_TEST(I32SConvertF64) {
WasmRunner<int32_t, double> r(execution_mode);
BUILD(r, WASM_I32_SCONVERT_F64(WASM_GET_LOCAL(0)));
......
......@@ -556,6 +556,12 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
#define WASM_I32_REINTERPRET_F32(x) x, kExprI32ReinterpretF32
#define WASM_I64_REINTERPRET_F64(x) x, kExprI64ReinterpretF64
//------------------------------------------------------------------------------
// Numeric operations
//------------------------------------------------------------------------------
#define WASM_NUMERIC_OP(op) kNumericPrefix, static_cast<byte>(op)
#define WASM_I32_SCONVERT_SAT_F32(x) x, WASM_NUMERIC_OP(kExprI32SConvertSatF32)
//------------------------------------------------------------------------------
// 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