Commit fafb4769 authored by Richard Stotz's avatar Richard Stotz Committed by Commit Bot

[turbofan][wasm][arm64] Improved saturated conversions float32 to int32.

The design of this change was discussed here:
https://docs.google.com/document/d/12otOj6SyXMXj0Dnnx9B6MGLMRwHPhg6RIZRazVw3tFA/

Bug: v8:10720
Change-Id: I8292dcf7272bdf4526a2d630b49fc374cdb01bdc
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2304570
Commit-Queue: Richard Stotz <rstz@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68994}
parent f91231f1
...@@ -1518,24 +1518,32 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction( ...@@ -1518,24 +1518,32 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
case kArm64Float64ToFloat32: case kArm64Float64ToFloat32:
__ Fcvt(i.OutputDoubleRegister().S(), i.InputDoubleRegister(0)); __ Fcvt(i.OutputDoubleRegister().S(), i.InputDoubleRegister(0));
break; break;
case kArm64Float32ToInt32: case kArm64Float32ToInt32: {
__ Fcvtzs(i.OutputRegister32(), i.InputFloat32Register(0)); __ Fcvtzs(i.OutputRegister32(), i.InputFloat32Register(0));
// Avoid INT32_MAX as an overflow indicator and use INT32_MIN instead, bool set_overflow_to_min_i32 = MiscField::decode(instr->opcode());
// because INT32_MIN allows easier out-of-bounds detection. if (set_overflow_to_min_i32) {
__ Cmn(i.OutputRegister32(), 1); // Avoid INT32_MAX as an overflow indicator and use INT32_MIN instead,
__ Csinc(i.OutputRegister32(), i.OutputRegister32(), i.OutputRegister32(), // because INT32_MIN allows easier out-of-bounds detection.
vc); __ Cmn(i.OutputRegister32(), 1);
__ Csinc(i.OutputRegister32(), i.OutputRegister32(),
i.OutputRegister32(), vc);
}
break; break;
}
case kArm64Float64ToInt32: case kArm64Float64ToInt32:
__ Fcvtzs(i.OutputRegister32(), i.InputDoubleRegister(0)); __ Fcvtzs(i.OutputRegister32(), i.InputDoubleRegister(0));
break; break;
case kArm64Float32ToUint32: case kArm64Float32ToUint32: {
__ Fcvtzu(i.OutputRegister32(), i.InputFloat32Register(0)); __ Fcvtzu(i.OutputRegister32(), i.InputFloat32Register(0));
// Avoid UINT32_MAX as an overflow indicator and use 0 instead, bool set_overflow_to_min_u32 = MiscField::decode(instr->opcode());
// because 0 allows easier out-of-bounds detection. if (set_overflow_to_min_u32) {
__ Cmn(i.OutputRegister32(), 1); // Avoid UINT32_MAX as an overflow indicator and use 0 instead,
__ Adc(i.OutputRegister32(), i.OutputRegister32(), Operand(0)); // because 0 allows easier out-of-bounds detection.
__ Cmn(i.OutputRegister32(), 1);
__ Adc(i.OutputRegister32(), i.OutputRegister32(), Operand(0));
}
break; break;
}
case kArm64Float64ToUint32: case kArm64Float64ToUint32:
__ Fcvtzu(i.OutputRegister32(), i.InputDoubleRegister(0)); __ Fcvtzu(i.OutputRegister32(), i.InputDoubleRegister(0));
break; break;
......
...@@ -1336,10 +1336,8 @@ void InstructionSelector::VisitWord64Ror(Node* node) { ...@@ -1336,10 +1336,8 @@ void InstructionSelector::VisitWord64Ror(Node* node) {
V(ChangeInt32ToFloat64, kArm64Int32ToFloat64) \ V(ChangeInt32ToFloat64, kArm64Int32ToFloat64) \
V(ChangeInt64ToFloat64, kArm64Int64ToFloat64) \ V(ChangeInt64ToFloat64, kArm64Int64ToFloat64) \
V(ChangeUint32ToFloat64, kArm64Uint32ToFloat64) \ V(ChangeUint32ToFloat64, kArm64Uint32ToFloat64) \
V(TruncateFloat32ToInt32, kArm64Float32ToInt32) \
V(ChangeFloat64ToInt32, kArm64Float64ToInt32) \ V(ChangeFloat64ToInt32, kArm64Float64ToInt32) \
V(ChangeFloat64ToInt64, kArm64Float64ToInt64) \ V(ChangeFloat64ToInt64, kArm64Float64ToInt64) \
V(TruncateFloat32ToUint32, kArm64Float32ToUint32) \
V(ChangeFloat64ToUint32, kArm64Float64ToUint32) \ V(ChangeFloat64ToUint32, kArm64Float64ToUint32) \
V(ChangeFloat64ToUint64, kArm64Float64ToUint64) \ V(ChangeFloat64ToUint64, kArm64Float64ToUint64) \
V(TruncateFloat64ToInt64, kArm64Float64ToInt64) \ V(TruncateFloat64ToInt64, kArm64Float64ToInt64) \
...@@ -1640,6 +1638,28 @@ void InstructionSelector::VisitUint32MulHigh(Node* node) { ...@@ -1640,6 +1638,28 @@ void InstructionSelector::VisitUint32MulHigh(Node* node) {
Emit(kArm64Lsr, g.DefineAsRegister(node), smull_operand, g.TempImmediate(32)); Emit(kArm64Lsr, g.DefineAsRegister(node), smull_operand, g.TempImmediate(32));
} }
void InstructionSelector::VisitTruncateFloat32ToInt32(Node* node) {
Arm64OperandGenerator g(this);
InstructionCode opcode = kArm64Float32ToInt32;
TruncateKind kind = OpParameter<TruncateKind>(node->op());
opcode |= MiscField::encode(kind == TruncateKind::kSetOverflowToMin);
Emit(opcode, g.DefineAsRegister(node), g.UseRegister(node->InputAt(0)));
}
void InstructionSelector::VisitTruncateFloat32ToUint32(Node* node) {
Arm64OperandGenerator g(this);
InstructionCode opcode = kArm64Float32ToUint32;
TruncateKind kind = OpParameter<TruncateKind>(node->op());
if (kind == TruncateKind::kSetOverflowToMin) {
opcode |= MiscField::encode(true);
}
Emit(opcode, g.DefineAsRegister(node), g.UseRegister(node->InputAt(0)));
}
void InstructionSelector::VisitTryTruncateFloat32ToInt64(Node* node) { void InstructionSelector::VisitTryTruncateFloat32ToInt64(Node* node) {
Arm64OperandGenerator g(this); Arm64OperandGenerator g(this);
...@@ -3694,7 +3714,8 @@ InstructionSelector::SupportedMachineOperatorFlags() { ...@@ -3694,7 +3714,8 @@ InstructionSelector::SupportedMachineOperatorFlags() {
MachineOperatorBuilder::kInt32DivIsSafe | MachineOperatorBuilder::kInt32DivIsSafe |
MachineOperatorBuilder::kUint32DivIsSafe | MachineOperatorBuilder::kUint32DivIsSafe |
MachineOperatorBuilder::kWord32ReverseBits | MachineOperatorBuilder::kWord32ReverseBits |
MachineOperatorBuilder::kWord64ReverseBits; MachineOperatorBuilder::kWord64ReverseBits |
MachineOperatorBuilder::kSatConversionIsSafe;
} }
// static // static
......
...@@ -619,6 +619,11 @@ TNode<Float64T> CodeAssembler::RoundIntPtrToFloat64(Node* value) { ...@@ -619,6 +619,11 @@ TNode<Float64T> CodeAssembler::RoundIntPtrToFloat64(Node* value) {
return UncheckedCast<Float64T>(raw_assembler()->ChangeInt32ToFloat64(value)); return UncheckedCast<Float64T>(raw_assembler()->ChangeInt32ToFloat64(value));
} }
TNode<Int32T> CodeAssembler::TruncateFloat32ToInt32(
SloppyTNode<Float32T> value) {
return UncheckedCast<Int32T>(raw_assembler()->TruncateFloat32ToInt32(
value, TruncateKind::kSetOverflowToMin));
}
#define DEFINE_CODE_ASSEMBLER_UNARY_OP(name, ResType, ArgType) \ #define DEFINE_CODE_ASSEMBLER_UNARY_OP(name, ResType, ArgType) \
TNode<ResType> CodeAssembler::name(SloppyTNode<ArgType> a) { \ TNode<ResType> CodeAssembler::name(SloppyTNode<ArgType> a) { \
return UncheckedCast<ResType>(raw_assembler()->name(a)); \ return UncheckedCast<ResType>(raw_assembler()->name(a)); \
......
...@@ -321,7 +321,6 @@ TNode<Float64T> Float64Add(TNode<Float64T> a, TNode<Float64T> b); ...@@ -321,7 +321,6 @@ TNode<Float64T> Float64Add(TNode<Float64T> a, TNode<Float64T> b);
V(BitcastMaybeObjectToWord, IntPtrT, MaybeObject) \ V(BitcastMaybeObjectToWord, IntPtrT, MaybeObject) \
V(BitcastWordToTagged, Object, WordT) \ V(BitcastWordToTagged, Object, WordT) \
V(BitcastWordToTaggedSigned, Smi, WordT) \ V(BitcastWordToTaggedSigned, Smi, WordT) \
V(TruncateFloat32ToInt32, Int32T, Float32T) \
V(TruncateFloat64ToFloat32, Float32T, Float64T) \ V(TruncateFloat64ToFloat32, Float32T, Float64T) \
V(TruncateFloat64ToWord32, Uint32T, Float64T) \ V(TruncateFloat64ToWord32, Uint32T, Float64T) \
V(TruncateInt64ToInt32, Int32T, Int64T) \ V(TruncateInt64ToInt32, Int32T, Int64T) \
...@@ -936,6 +935,12 @@ class V8_EXPORT_PRIVATE CodeAssembler { ...@@ -936,6 +935,12 @@ class V8_EXPORT_PRIVATE CodeAssembler {
// No-op on 32-bit, otherwise sign extend. // No-op on 32-bit, otherwise sign extend.
TNode<IntPtrT> ChangeInt32ToIntPtr(TNode<Word32T> value); TNode<IntPtrT> ChangeInt32ToIntPtr(TNode<Word32T> value);
// Truncates a float to a 32-bit integer. If the float is outside of 32-bit
// range, make sure that overflow detection is easy. In particular, return
// int_min instead of int_max on arm platforms by using parameter
// kSetOverflowToMin.
TNode<Int32T> TruncateFloat32ToInt32(SloppyTNode<Float32T> value);
// No-op that guarantees that the value is kept alive till this point even // No-op that guarantees that the value is kept alive till this point even
// if GC happens. // if GC happens.
Node* Retain(Node* value); Node* Retain(Node* value);
......
...@@ -235,8 +235,6 @@ ShiftKind ShiftKindOf(Operator const* op) { ...@@ -235,8 +235,6 @@ ShiftKind ShiftKindOf(Operator const* op) {
V(ChangeFloat64ToUint64, Operator::kNoProperties, 1, 0, 1) \ V(ChangeFloat64ToUint64, Operator::kNoProperties, 1, 0, 1) \
V(TruncateFloat64ToInt64, Operator::kNoProperties, 1, 0, 1) \ V(TruncateFloat64ToInt64, Operator::kNoProperties, 1, 0, 1) \
V(TruncateFloat64ToUint32, Operator::kNoProperties, 1, 0, 1) \ V(TruncateFloat64ToUint32, Operator::kNoProperties, 1, 0, 1) \
V(TruncateFloat32ToInt32, Operator::kNoProperties, 1, 0, 1) \
V(TruncateFloat32ToUint32, Operator::kNoProperties, 1, 0, 1) \
V(TryTruncateFloat32ToInt64, Operator::kNoProperties, 1, 0, 2) \ V(TryTruncateFloat32ToInt64, Operator::kNoProperties, 1, 0, 2) \
V(TryTruncateFloat64ToInt64, Operator::kNoProperties, 1, 0, 2) \ V(TryTruncateFloat64ToInt64, Operator::kNoProperties, 1, 0, 2) \
V(TryTruncateFloat32ToUint64, Operator::kNoProperties, 1, 0, 2) \ V(TryTruncateFloat32ToUint64, Operator::kNoProperties, 1, 0, 2) \
...@@ -1014,6 +1012,55 @@ const Operator* MachineOperatorBuilder::UnalignedStore( ...@@ -1014,6 +1012,55 @@ const Operator* MachineOperatorBuilder::UnalignedStore(
UNREACHABLE(); UNREACHABLE();
} }
template <TruncateKind kind>
struct TruncateFloat32ToUint32Operator : Operator1<TruncateKind> {
TruncateFloat32ToUint32Operator()
: Operator1(IrOpcode::kTruncateFloat32ToUint32, Operator::kPure,
"TruncateFloat32ToUint32", 1, 0, 0, 1, 0, 0, kind) {}
};
const Operator* MachineOperatorBuilder::TruncateFloat32ToUint32(
TruncateKind kind) {
switch (kind) {
case TruncateKind::kArchitectureDefault:
return GetCachedOperator<TruncateFloat32ToUint32Operator<
TruncateKind::kArchitectureDefault>>();
case TruncateKind::kSetOverflowToMin:
return GetCachedOperator<
TruncateFloat32ToUint32Operator<TruncateKind::kSetOverflowToMin>>();
}
}
template <TruncateKind kind>
struct TruncateFloat32ToInt32Operator : Operator1<TruncateKind> {
TruncateFloat32ToInt32Operator()
: Operator1(IrOpcode::kTruncateFloat32ToInt32, Operator::kPure,
"TruncateFloat32ToInt32", 1, 0, 0, 1, 0, 0, kind) {}
};
const Operator* MachineOperatorBuilder::TruncateFloat32ToInt32(
TruncateKind kind) {
switch (kind) {
case TruncateKind::kArchitectureDefault:
return GetCachedOperator<
TruncateFloat32ToInt32Operator<TruncateKind::kArchitectureDefault>>();
case TruncateKind::kSetOverflowToMin:
return GetCachedOperator<
TruncateFloat32ToInt32Operator<TruncateKind::kSetOverflowToMin>>();
}
}
size_t hash_value(TruncateKind kind) { return static_cast<size_t>(kind); }
std::ostream& operator<<(std::ostream& os, TruncateKind kind) {
switch (kind) {
case TruncateKind::kArchitectureDefault:
return os << "kArchitectureDefault";
case TruncateKind::kSetOverflowToMin:
return os << "kSetOverflowToMin";
}
}
#define PURE(Name, properties, value_input_count, control_input_count, \ #define PURE(Name, properties, value_input_count, control_input_count, \
output_count) \ output_count) \
const Operator* MachineOperatorBuilder::Name() { \ const Operator* MachineOperatorBuilder::Name() { \
......
...@@ -192,6 +192,12 @@ size_t hash_value(ShiftKind); ...@@ -192,6 +192,12 @@ size_t hash_value(ShiftKind);
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream&, ShiftKind); V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream&, ShiftKind);
ShiftKind ShiftKindOf(Operator const*) V8_WARN_UNUSED_RESULT; ShiftKind ShiftKindOf(Operator const*) V8_WARN_UNUSED_RESULT;
// TruncateKind::kSetOverflowToMin sets the result of a saturating float-to-int
// conversion to INT_MIN if the conversion returns INT_MAX due to overflow. This
// makes it easier to detect an overflow. This parameter is ignored on platforms
// like x64 and ia32 where a range overflow does not result in INT_MAX.
enum class TruncateKind { kArchitectureDefault, kSetOverflowToMin };
// Interface for building machine-level operators. These operators are // Interface for building machine-level operators. These operators are
// machine-level but machine-independent and thus define a language suitable // machine-level but machine-independent and thus define a language suitable
// for generating code to run on architectures such as ia32, x64, arm, etc. // for generating code to run on architectures such as ia32, x64, arm, etc.
...@@ -224,13 +230,14 @@ class V8_EXPORT_PRIVATE MachineOperatorBuilder final ...@@ -224,13 +230,14 @@ class V8_EXPORT_PRIVATE MachineOperatorBuilder final
kInt64AbsWithOverflow = 1u << 21, kInt64AbsWithOverflow = 1u << 21,
kWord32Rol = 1u << 22, kWord32Rol = 1u << 22,
kWord64Rol = 1u << 23, kWord64Rol = 1u << 23,
kSatConversionIsSafe = 1u << 24,
kAllOptionalOps = kAllOptionalOps =
kFloat32RoundDown | kFloat64RoundDown | kFloat32RoundUp | kFloat32RoundDown | kFloat64RoundDown | kFloat32RoundUp |
kFloat64RoundUp | kFloat32RoundTruncate | kFloat64RoundTruncate | kFloat64RoundUp | kFloat32RoundTruncate | kFloat64RoundTruncate |
kFloat64RoundTiesAway | kFloat32RoundTiesEven | kFloat64RoundTiesEven | kFloat64RoundTiesAway | kFloat32RoundTiesEven | kFloat64RoundTiesEven |
kWord32Ctz | kWord64Ctz | kWord32Popcnt | kWord64Popcnt | kWord32Ctz | kWord64Ctz | kWord32Popcnt | kWord64Popcnt |
kWord32ReverseBits | kWord64ReverseBits | kInt32AbsWithOverflow | kWord32ReverseBits | kWord64ReverseBits | kInt32AbsWithOverflow |
kInt64AbsWithOverflow | kWord32Rol | kWord64Rol kInt64AbsWithOverflow | kWord32Rol | kWord64Rol | kSatConversionIsSafe
}; };
using Flags = base::Flags<Flag, unsigned>; using Flags = base::Flags<Flag, unsigned>;
...@@ -332,6 +339,11 @@ class V8_EXPORT_PRIVATE MachineOperatorBuilder final ...@@ -332,6 +339,11 @@ class V8_EXPORT_PRIVATE MachineOperatorBuilder final
// generate a mask with 0x1f on the amount ahead of generating the shift. // generate a mask with 0x1f on the amount ahead of generating the shift.
bool Word32ShiftIsSafe() const { return flags_ & kWord32ShiftIsSafe; } bool Word32ShiftIsSafe() const { return flags_ & kWord32ShiftIsSafe; }
// Return true if the target's implementation of float-to-int-conversions is a
// saturating conversion rounding towards 0. Otherwise, we have to manually
// generate the correct value if a saturating conversion is requested.
bool SatConversionIsSafe() const { return flags_ & kSatConversionIsSafe; }
const Operator* Word64And(); const Operator* Word64And();
const Operator* Word64Or(); const Operator* Word64Or();
const Operator* Word64Xor(); const Operator* Word64Xor();
...@@ -417,6 +429,10 @@ class V8_EXPORT_PRIVATE MachineOperatorBuilder final ...@@ -417,6 +429,10 @@ class V8_EXPORT_PRIVATE MachineOperatorBuilder final
// in the target type and are *not* defined for other inputs. // in the target type and are *not* defined for other inputs.
// Use narrowing change operators only when there is a static guarantee that // Use narrowing change operators only when there is a static guarantee that
// the input value is representable in the target value. // the input value is representable in the target value.
//
// Some operators can have the behaviour on overflow change through specifying
// TruncateKind. The exact semantics are documented in the tests in
// test/cctest/compiler/test-run-machops.cc .
const Operator* ChangeFloat32ToFloat64(); const Operator* ChangeFloat32ToFloat64();
const Operator* ChangeFloat64ToInt32(); // narrowing const Operator* ChangeFloat64ToInt32(); // narrowing
const Operator* ChangeFloat64ToInt64(); const Operator* ChangeFloat64ToInt64();
...@@ -424,8 +440,8 @@ class V8_EXPORT_PRIVATE MachineOperatorBuilder final ...@@ -424,8 +440,8 @@ class V8_EXPORT_PRIVATE MachineOperatorBuilder final
const Operator* ChangeFloat64ToUint64(); const Operator* ChangeFloat64ToUint64();
const Operator* TruncateFloat64ToInt64(); const Operator* TruncateFloat64ToInt64();
const Operator* TruncateFloat64ToUint32(); const Operator* TruncateFloat64ToUint32();
const Operator* TruncateFloat32ToInt32(); const Operator* TruncateFloat32ToInt32(TruncateKind kind);
const Operator* TruncateFloat32ToUint32(); const Operator* TruncateFloat32ToUint32(TruncateKind kind);
const Operator* TryTruncateFloat32ToInt64(); const Operator* TryTruncateFloat32ToInt64();
const Operator* TryTruncateFloat64ToInt64(); const Operator* TryTruncateFloat64ToInt64();
const Operator* TryTruncateFloat32ToUint64(); const Operator* TryTruncateFloat32ToUint64();
......
...@@ -721,11 +721,11 @@ class V8_EXPORT_PRIVATE RawMachineAssembler { ...@@ -721,11 +721,11 @@ class V8_EXPORT_PRIVATE RawMachineAssembler {
Node* TruncateFloat64ToUint32(Node* a) { Node* TruncateFloat64ToUint32(Node* a) {
return AddNode(machine()->TruncateFloat64ToUint32(), a); return AddNode(machine()->TruncateFloat64ToUint32(), a);
} }
Node* TruncateFloat32ToInt32(Node* a) { Node* TruncateFloat32ToInt32(Node* a, TruncateKind kind) {
return AddNode(machine()->TruncateFloat32ToInt32(), a); return AddNode(machine()->TruncateFloat32ToInt32(kind), a);
} }
Node* TruncateFloat32ToUint32(Node* a) { Node* TruncateFloat32ToUint32(Node* a, TruncateKind kind) {
return AddNode(machine()->TruncateFloat32ToUint32(), a); return AddNode(machine()->TruncateFloat32ToUint32(kind), a);
} }
Node* TryTruncateFloat32ToInt64(Node* a) { Node* TryTruncateFloat32ToInt64(Node* a) {
return AddNode(machine()->TryTruncateFloat32ToInt64(), a); return AddNode(machine()->TryTruncateFloat32ToInt64(), a);
......
...@@ -1550,11 +1550,17 @@ MachineType FloatConvertType(wasm::WasmOpcode opcode) { ...@@ -1550,11 +1550,17 @@ MachineType FloatConvertType(wasm::WasmOpcode opcode) {
const Operator* ConvertOp(WasmGraphBuilder* builder, wasm::WasmOpcode opcode) { const Operator* ConvertOp(WasmGraphBuilder* builder, wasm::WasmOpcode opcode) {
switch (opcode) { switch (opcode) {
case wasm::kExprI32SConvertF32: case wasm::kExprI32SConvertF32:
return builder->mcgraph()->machine()->TruncateFloat32ToInt32(
TruncateKind::kSetOverflowToMin);
case wasm::kExprI32SConvertSatF32: case wasm::kExprI32SConvertSatF32:
return builder->mcgraph()->machine()->TruncateFloat32ToInt32(); return builder->mcgraph()->machine()->TruncateFloat32ToInt32(
TruncateKind::kArchitectureDefault);
case wasm::kExprI32UConvertF32: case wasm::kExprI32UConvertF32:
return builder->mcgraph()->machine()->TruncateFloat32ToUint32(
TruncateKind::kSetOverflowToMin);
case wasm::kExprI32UConvertSatF32: case wasm::kExprI32UConvertSatF32:
return builder->mcgraph()->machine()->TruncateFloat32ToUint32(); return builder->mcgraph()->machine()->TruncateFloat32ToUint32(
TruncateKind::kArchitectureDefault);
case wasm::kExprI32SConvertF64: case wasm::kExprI32SConvertF64:
case wasm::kExprI32SConvertSatF64: case wasm::kExprI32SConvertSatF64:
return builder->mcgraph()->machine()->ChangeFloat64ToInt32(); return builder->mcgraph()->machine()->ChangeFloat64ToInt32();
...@@ -1753,6 +1759,9 @@ Node* WasmGraphBuilder::BuildIntConvertFloat(Node* input, ...@@ -1753,6 +1759,9 @@ Node* WasmGraphBuilder::BuildIntConvertFloat(Node* input,
} }
return converted_value; return converted_value;
} }
if (mcgraph()->machine()->SatConversionIsSafe()) {
return converted_value;
}
Node* test = ConvertSaturateTest(this, opcode, int_ty, float_ty, trunc, Node* test = ConvertSaturateTest(this, opcode, int_ty, float_ty, trunc,
converted_value); converted_value);
Diamond tl_d(graph(), mcgraph()->common(), test, BranchHint::kFalse); Diamond tl_d(graph(), mcgraph()->common(), test, BranchHint::kFalse);
......
...@@ -110,7 +110,7 @@ Node* ToInt32(RawMachineAssembler* m, MachineType type, Node* a) { ...@@ -110,7 +110,7 @@ Node* ToInt32(RawMachineAssembler* m, MachineType type, Node* a) {
case MachineRepresentation::kWord64: case MachineRepresentation::kWord64:
return m->TruncateInt64ToInt32(a); return m->TruncateInt64ToInt32(a);
case MachineRepresentation::kFloat32: case MachineRepresentation::kFloat32:
return m->TruncateFloat32ToInt32(a); return m->TruncateFloat32ToInt32(a, TruncateKind::kArchitectureDefault);
case MachineRepresentation::kFloat64: case MachineRepresentation::kFloat64:
return m->RoundFloat64ToInt32(a); return m->RoundFloat64ToInt32(a);
default: default:
......
...@@ -4173,8 +4173,6 @@ TEST(RunChangeUint32ToFloat64) { ...@@ -4173,8 +4173,6 @@ TEST(RunChangeUint32ToFloat64) {
TEST(RunTruncateFloat32ToInt32) { TEST(RunTruncateFloat32ToInt32) {
BufferedRawMachineAssemblerTester<int32_t> m(MachineType::Float32());
m.Return(m.TruncateFloat32ToInt32(m.Parameter(0)));
// The upper bound is (INT32_MAX + 1), which is the lowest float-representable // The upper bound is (INT32_MAX + 1), which is the lowest float-representable
// number above INT32_MAX which cannot be represented as int32. // number above INT32_MAX which cannot be represented as int32.
float upper_bound = 2147483648.0f; float upper_bound = 2147483648.0f;
...@@ -4182,31 +4180,98 @@ TEST(RunTruncateFloat32ToInt32) { ...@@ -4182,31 +4180,98 @@ TEST(RunTruncateFloat32ToInt32) {
// representable as float, and no number between (INT32_MIN - 1) and INT32_MIN // representable as float, and no number between (INT32_MIN - 1) and INT32_MIN
// is. // is.
float lower_bound = static_cast<float>(INT32_MIN); float lower_bound = static_cast<float>(INT32_MIN);
FOR_FLOAT32_INPUTS(i) { {
if (i < upper_bound && i >= lower_bound) { BufferedRawMachineAssemblerTester<int32_t> m(MachineType::Float32());
CHECK_FLOAT_EQ(static_cast<int32_t>(i), m.Call(i)); m.Return(m.TruncateFloat32ToInt32(m.Parameter(0),
TruncateKind::kArchitectureDefault));
FOR_FLOAT32_INPUTS(i) {
if (i < upper_bound && i >= lower_bound) {
CHECK_FLOAT_EQ(static_cast<int32_t>(i), m.Call(i));
} else if (i < lower_bound) {
CHECK_FLOAT_EQ(std::numeric_limits<int32_t>::min(), m.Call(i));
} else if (i >= upper_bound) {
#if V8_TARGET_ARCH_IA32 || V8_TARGET_ARCH_X64
CHECK_FLOAT_EQ(std::numeric_limits<int32_t>::min(), m.Call(i));
#elif V8_TARGET_ARCH_ARM64
CHECK_FLOAT_EQ(std::numeric_limits<int32_t>::max(), m.Call(i));
#endif
} else {
DCHECK(std::isnan(i));
#if V8_TARGET_ARCH_IA32 || V8_TARGET_ARCH_X64
CHECK_FLOAT_EQ(std::numeric_limits<int32_t>::min(), m.Call(i));
#elif V8_TARGET_ARCH_ARM64
CHECK_FLOAT_EQ(0, m.Call(i));
#endif
}
}
}
{
BufferedRawMachineAssemblerTester<int32_t> m(MachineType::Float32());
m.Return(m.TruncateFloat32ToInt32(m.Parameter(0),
TruncateKind::kSetOverflowToMin));
FOR_FLOAT32_INPUTS(i) {
if (i < upper_bound && i >= lower_bound) {
CHECK_FLOAT_EQ(static_cast<int32_t>(i), m.Call(i));
} else if (!std::isnan(i)) {
CHECK_FLOAT_EQ(std::numeric_limits<int32_t>::min(), m.Call(i));
} else {
DCHECK(std::isnan(i));
#if V8_TARGET_ARCH_IA32 || V8_TARGET_ARCH_X64
CHECK_FLOAT_EQ(std::numeric_limits<int32_t>::min(), m.Call(i));
#elif V8_TARGET_ARCH_ARM64
CHECK_FLOAT_EQ(0, m.Call(i));
#endif
}
} }
} }
} }
TEST(RunTruncateFloat32ToUint32) { TEST(RunTruncateFloat32ToUint32) {
BufferedRawMachineAssemblerTester<uint32_t> m(MachineType::Float32());
m.Return(m.TruncateFloat32ToUint32(m.Parameter(0)));
// The upper bound is (UINT32_MAX + 1), which is the lowest // The upper bound is (UINT32_MAX + 1), which is the lowest
// float-representable number above UINT32_MAX which cannot be represented as // float-representable number above UINT32_MAX which cannot be represented as
// uint32. // uint32.
double upper_bound = 4294967296.0f; double upper_bound = 4294967296.0f;
double lower_bound = -1.0f; double lower_bound = -1.0f;
FOR_UINT32_INPUTS(i) {
volatile float input = static_cast<float>(i); // No tests outside the range of UINT32 are performed, as the semantics are
if (input < upper_bound) { // tricky on x64. On this architecture, the assembler transforms float32 into
CHECK_EQ(static_cast<uint32_t>(input), m.Call(input)); // a signed int64 instead of an unsigned int32. Overflow can then be detected
// by converting back to float and testing for equality as done in
// wasm-compiler.cc .
//
// On arm architectures, TruncateKind::kArchitectureDefault rounds towards 0
// upon overflow and returns 0 if the input is NaN.
// TruncateKind::kSetOverflowToMin returns 0 on overflow and NaN.
{
BufferedRawMachineAssemblerTester<uint32_t> m(MachineType::Float32());
m.Return(m.TruncateFloat32ToUint32(m.Parameter(0),
TruncateKind::kArchitectureDefault));
FOR_UINT32_INPUTS(i) {
volatile float input = static_cast<float>(i);
if (input < upper_bound) {
CHECK_EQ(static_cast<uint32_t>(input), m.Call(input));
}
}
FOR_FLOAT32_INPUTS(j) {
if ((j < upper_bound) && (j > lower_bound)) {
CHECK_FLOAT_EQ(static_cast<uint32_t>(j), m.Call(j));
}
} }
} }
FOR_FLOAT32_INPUTS(j) { {
if ((j < upper_bound) && (j > lower_bound)) { BufferedRawMachineAssemblerTester<uint32_t> m(MachineType::Float32());
CHECK_FLOAT_EQ(static_cast<uint32_t>(j), m.Call(j)); m.Return(m.TruncateFloat32ToUint32(m.Parameter(0),
TruncateKind::kSetOverflowToMin));
FOR_UINT32_INPUTS(i) {
volatile float input = static_cast<float>(i);
if (input < upper_bound) {
CHECK_EQ(static_cast<uint32_t>(input), m.Call(input));
}
}
FOR_FLOAT32_INPUTS(j) {
if ((j < upper_bound) && (j > lower_bound)) {
CHECK_FLOAT_EQ(static_cast<uint32_t>(j), m.Call(j));
}
} }
} }
} }
......
...@@ -107,7 +107,7 @@ Node* ToInt32(RawMachineAssembler* m, MachineType type, Node* a) { ...@@ -107,7 +107,7 @@ Node* ToInt32(RawMachineAssembler* m, MachineType type, Node* a) {
case MachineRepresentation::kWord64: case MachineRepresentation::kWord64:
return m->TruncateInt64ToInt32(a); return m->TruncateInt64ToInt32(a);
case MachineRepresentation::kFloat32: case MachineRepresentation::kFloat32:
return m->TruncateFloat32ToInt32(a); return m->TruncateFloat32ToInt32(a, TruncateKind::kArchitectureDefault);
case MachineRepresentation::kFloat64: case MachineRepresentation::kFloat64:
return m->RoundFloat64ToInt32(a); return m->RoundFloat64ToInt32(a);
default: default:
......
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