[maglev] Float64 bitwise ops as truncation + Int32

Implement truncating bitwise ops (ops that treat their input as a number
truncated to int32) for Float64 representation, by adding truncation
operations for Float64 and tagged Number.

Bug: v8:7700
Change-Id: I36f423ba8d5332e8eb8c3d6357bbaed7ea4bbb37
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4013685
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84182}
This commit is contained in:
Leszek Swirski 2022-11-10 14:27:57 +01:00 committed by V8 LUCI CQ
parent 246d04486c
commit 2adc620152
7 changed files with 287 additions and 39 deletions

View File

@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "src/codegen/interface-descriptors-inl.h"
#include "src/common/globals.h"
#include "src/maglev/maglev-assembler-inl.h"
#include "src/objects/heap-number.h"
@ -348,6 +349,30 @@ void MaglevAssembler::ToBoolean(Register value, ZoneLabelRef is_true,
}
}
void MaglevAssembler::TruncateDoubleToInt32(Register dst, DoubleRegister src) {
ZoneLabelRef done(this);
Cvttsd2siq(dst, src);
// Check whether the Cvt overflowed.
cmpq(dst, Immediate(1));
JumpToDeferredIf(
overflow,
[](MaglevAssembler* masm, DoubleRegister src, Register dst,
ZoneLabelRef done) {
// Push the double register onto the stack as an input argument.
__ AllocateStackSpace(kDoubleSize);
__ Movsd(MemOperand(rsp, 0), src);
__ CallBuiltin(Builtin::kDoubleToI);
// DoubleToI sets the result on the stack, pop the result off the stack.
// Avoid using `pop` to not mix implicit and explicit rsp updates.
__ movl(dst, MemOperand(rsp, 0));
__ addq(rsp, Immediate(kDoubleSize));
__ jmp(*done);
},
src, dst, done);
bind(*done);
}
} // namespace maglev
} // namespace internal
} // namespace v8

View File

@ -119,6 +119,8 @@ class MaglevAssembler : public MacroAssembler {
void ToBoolean(Register value, ZoneLabelRef is_true, ZoneLabelRef is_false,
bool fallthrough_when_true);
void TruncateDoubleToInt32(Register dst, DoubleRegister src);
inline void DefineLazyDeoptPoint(LazyDeoptInfo* info);
inline void DefineExceptionHandlerPoint(NodeBase* node);
inline void DefineExceptionHandlerAndLazyDeoptPoint(NodeBase* node);

View File

@ -230,6 +230,23 @@ template <Operation kOperation>
using GenericNodeForOperation =
typename NodeForOperationHelper<kOperation>::generic_type;
// Bitwise operations truncate their input to an Int32, which means we can be
// less strict on conversions of their inputs.
template <Operation kOperation>
bool BinaryOperationTruncatesInputsToInt32() {
switch (kOperation) {
case Operation::kBitwiseAnd:
case Operation::kBitwiseOr:
case Operation::kBitwiseXor:
case Operation::kShiftLeft:
case Operation::kShiftRight:
case Operation::kShiftRightLogical:
return true;
default:
return false;
}
}
// TODO(victorgomes): Remove this once all operations have fast paths.
template <Operation kOperation>
bool BinaryOperationHasInt32FastPath() {
@ -252,7 +269,7 @@ bool BinaryOperationHasInt32FastPath() {
case Operation::kGreaterThan:
case Operation::kGreaterThanOrEqual:
return true;
default:
case Operation::kExponentiate:
return false;
}
}
@ -414,9 +431,16 @@ ValueNode* MaglevGraphBuilder::TryFoldInt32BinaryOperation(ValueNode* left,
template <Operation kOperation>
void MaglevGraphBuilder::BuildInt32BinaryOperationNode() {
// Truncating Int32 nodes treat their input as a signed int32 regardless
// of whether it's really signed or not, so we allow Uint32 by loading a
// Word32 value.
static const bool inputs_are_truncated =
BinaryOperationTruncatesInputsToInt32<kOperation>();
// TODO(v8:7700): Do constant folding.
ValueNode* left = LoadRegisterInt32(0);
ValueNode* right = GetAccumulatorInt32();
ValueNode* left =
inputs_are_truncated ? LoadRegisterWord32(0) : LoadRegisterInt32(0);
ValueNode* right =
inputs_are_truncated ? GetAccumulatorWord32() : GetAccumulatorInt32();
if (ValueNode* result =
TryFoldInt32BinaryOperation<kOperation>(left, right)) {
@ -428,13 +452,67 @@ void MaglevGraphBuilder::BuildInt32BinaryOperationNode() {
}
template <Operation kOperation>
void MaglevGraphBuilder::BuildInt32BinarySmiOperationNode() {
void MaglevGraphBuilder::BuildTruncatingInt32BinaryOperationNodeForNumber() {
DCHECK(BinaryOperationTruncatesInputsToInt32<kOperation>());
// TODO(v8:7700): Do constant folding.
ValueNode* left = GetAccumulatorInt32();
ValueNode* left;
ValueNode* right;
if (IsRegisterEqualToAccumulator(0)) {
left = right = GetTruncatedWord32FromNumber(
current_interpreter_frame_.get(iterator_.GetRegisterOperand(0)));
} else {
left = GetTruncatedWord32FromNumber(
current_interpreter_frame_.get(iterator_.GetRegisterOperand(0)));
right =
GetTruncatedWord32FromNumber(current_interpreter_frame_.accumulator());
}
if (ValueNode* result =
TryFoldInt32BinaryOperation<kOperation>(left, right)) {
SetAccumulator(result);
return;
}
SetAccumulator(AddNewInt32BinaryOperationNode<kOperation>({left, right}));
}
template <Operation kOperation>
void MaglevGraphBuilder::BuildInt32BinarySmiOperationNode() {
// Truncating Int32 nodes treat their input as a signed int32 regardless
// of whether it's really signed or not, so we allow Uint32 by loading a
// Word32 value.
static const bool inputs_are_truncated =
BinaryOperationTruncatesInputsToInt32<kOperation>();
// TODO(v8:7700): Do constant folding.
ValueNode* left =
inputs_are_truncated ? GetAccumulatorWord32() : GetAccumulatorInt32();
int32_t constant = iterator_.GetImmediateOperand(0);
if (base::Optional<int>(constant) == Int32Identity<kOperation>()) {
// If the constant is the unit of the operation, it already has the right
// value, so we can just return.
// value, so just return.
return;
}
if (ValueNode* result =
TryFoldInt32BinaryOperation<kOperation>(left, constant)) {
SetAccumulator(result);
return;
}
ValueNode* right = GetInt32Constant(constant);
SetAccumulator(AddNewInt32BinaryOperationNode<kOperation>({left, right}));
}
template <Operation kOperation>
void MaglevGraphBuilder::BuildTruncatingInt32BinarySmiOperationNodeForNumber() {
DCHECK(BinaryOperationTruncatesInputsToInt32<kOperation>());
// TODO(v8:7700): Do constant folding.
ValueNode* left =
GetTruncatedWord32FromNumber(current_interpreter_frame_.accumulator());
int32_t constant = iterator_.GetImmediateOperand(0);
if (base::Optional<int>(constant) == Int32Identity<kOperation>()) {
// If the constant is the unit of the operation, it already has the right
// value, so use the truncated value (if not just a conversion) and return.
if (!left->properties().is_conversion()) {
current_interpreter_frame_.set_accumulator(left);
}
return;
}
if (ValueNode* result =
@ -475,26 +553,20 @@ void MaglevGraphBuilder::VisitBinaryOperation() {
FeedbackNexus nexus = FeedbackNexusForOperand(1);
switch (nexus.GetBinaryOperationFeedback()) {
case BinaryOperationHint::kNone:
EmitUnconditionalDeopt(
return EmitUnconditionalDeopt(
DeoptimizeReason::kInsufficientTypeFeedbackForBinaryOperation);
return;
case BinaryOperationHint::kSignedSmall:
if (BinaryOperationHasInt32FastPath<kOperation>()) {
BuildInt32BinaryOperationNode<kOperation>();
return;
return BuildInt32BinaryOperationNode<kOperation>();
}
break;
case BinaryOperationHint::kSignedSmallInputs:
case BinaryOperationHint::kNumber:
if (BinaryOperationHasFloat64FastPath<kOperation>()) {
BuildFloat64BinaryOperationNode<kOperation>();
return;
// } else if (BinaryOperationHasInt32FastPath<kOperation>()) {
// // Fall back to int32 fast path if there is one (this will be the
// case
// // for operations that deal with bits rather than numbers).
// BuildInt32BinaryOperationNode<kOperation>();
// return;
return BuildFloat64BinaryOperationNode<kOperation>();
} else if (BinaryOperationHasInt32FastPath<kOperation>() &&
BinaryOperationTruncatesInputsToInt32<kOperation>()) {
return BuildTruncatingInt32BinaryOperationNodeForNumber<kOperation>();
}
break;
default:
@ -509,26 +581,21 @@ void MaglevGraphBuilder::VisitBinarySmiOperation() {
FeedbackNexus nexus = FeedbackNexusForOperand(1);
switch (nexus.GetBinaryOperationFeedback()) {
case BinaryOperationHint::kNone:
EmitUnconditionalDeopt(
return EmitUnconditionalDeopt(
DeoptimizeReason::kInsufficientTypeFeedbackForBinaryOperation);
return;
case BinaryOperationHint::kSignedSmall:
if (BinaryOperationHasInt32FastPath<kOperation>()) {
BuildInt32BinarySmiOperationNode<kOperation>();
return;
return BuildInt32BinarySmiOperationNode<kOperation>();
}
break;
case BinaryOperationHint::kSignedSmallInputs:
case BinaryOperationHint::kNumber:
if (BinaryOperationHasFloat64FastPath<kOperation>()) {
BuildFloat64BinarySmiOperationNode<kOperation>();
return;
// } else if (BinaryOperationHasInt32FastPath<kOperation>()) {
// // Fall back to int32 fast path if there is one (this will be the
// case
// // for operations that deal with bits rather than numbers).
// BuildInt32BinarySmiOperationNode<kOperation>();
// return;
return BuildFloat64BinarySmiOperationNode<kOperation>();
} else if (BinaryOperationHasInt32FastPath<kOperation>() &&
BinaryOperationTruncatesInputsToInt32<kOperation>()) {
return BuildTruncatingInt32BinarySmiOperationNodeForNumber<
kOperation>();
}
break;
default:

View File

@ -30,6 +30,7 @@
#include "src/maglev/maglev-graph-labeller.h"
#include "src/maglev/maglev-graph-printer.h"
#include "src/maglev/maglev-graph.h"
#include "src/maglev/maglev-interpreter-frame-state.h"
#include "src/maglev/maglev-ir.h"
#include "src/utils/memcopy.h"
@ -706,6 +707,53 @@ class MaglevGraphBuilder {
return node;
}
ValueNode* GetTruncatedWord32FromNumber(ValueNode* value) {
switch (value->properties().value_representation()) {
case ValueRepresentation::kTagged: {
if (SmiConstant* constant = value->TryCast<SmiConstant>()) {
return GetInt32Constant(constant->value().value());
}
NodeInfo* node_info = known_node_aspects().GetOrCreateInfoFor(value);
if (node_info->int32_alternative == nullptr) {
NodeType old_type;
EnsureType(value, NodeType::kNumber, &old_type);
if (NodeTypeIsSmi(old_type)) {
node_info->int32_alternative = AddNewNode<UnsafeSmiUntag>({value});
} else {
// TODO(leszeks): Cache this value somehow.
return AddNewNode<CheckedNumberToWord32>({value});
}
}
return node_info->int32_alternative;
}
case ValueRepresentation::kFloat64:
// TODO(leszeks): Cache this value somehow.
return AddNewNode<TruncateFloat64ToWord32>({value});
case ValueRepresentation::kInt32:
case ValueRepresentation::kUint32:
// Already good.
return value;
}
UNREACHABLE();
}
ValueNode* GetWord32(ValueNode* value) {
switch (value->properties().value_representation()) {
case ValueRepresentation::kTagged:
case ValueRepresentation::kFloat64:
return GetInt32(value);
case ValueRepresentation::kInt32:
case ValueRepresentation::kUint32:
// Both Int32 and Uint32 are valid Word32 values.
return value;
}
UNREACHABLE();
}
ValueNode* GetWord32(interpreter::Register reg) {
return GetWord32(current_interpreter_frame_.get(reg));
}
ValueNode* GetInt32(ValueNode* value) {
switch (value->properties().value_representation()) {
case ValueRepresentation::kTagged: {
@ -788,6 +836,10 @@ class MaglevGraphBuilder {
return GetInt32(interpreter::Register::virtual_accumulator());
}
ValueNode* GetAccumulatorWord32() {
return GetWord32(interpreter::Register::virtual_accumulator());
}
ValueNode* GetAccumulatorFloat64() {
return GetFloat64(interpreter::Register::virtual_accumulator());
}
@ -806,6 +858,10 @@ class MaglevGraphBuilder {
return GetInt32(iterator_.GetRegisterOperand(operand_index));
}
ValueNode* LoadRegisterWord32(int operand_index) {
return GetWord32(iterator_.GetRegisterOperand(operand_index));
}
ValueNode* LoadRegisterFloat64(int operand_index) {
return GetFloat64(iterator_.GetRegisterOperand(operand_index));
}
@ -1299,6 +1355,10 @@ class MaglevGraphBuilder {
template <Operation kOperation>
void BuildInt32BinarySmiOperationNode();
template <Operation kOperation>
void BuildTruncatingInt32BinaryOperationNodeForNumber();
template <Operation kOperation>
void BuildTruncatingInt32BinarySmiOperationNodeForNumber();
template <Operation kOperation>
void BuildFloat64BinaryOperationNode();
template <Operation kOperation>
void BuildFloat64BinarySmiOperationNode();

View File

@ -75,6 +75,22 @@ class MaglevGraphVerifier {
}
}
void CheckValueInputIsWord32(NodeBase* node, int i) {
ValueNode* input = node->input(i).node();
ValueRepresentation got = input->properties().value_representation();
if (got != ValueRepresentation::kInt32 &&
got != ValueRepresentation::kUint32) {
std::ostringstream str;
str << "Type representation error: node ";
if (graph_labeller_) {
str << "#" << graph_labeller_->NodeId(node) << " : ";
}
str << node->opcode() << " (input @" << i << " = " << input->opcode()
<< ") type " << got << " is not Word32 (Int32 or Uint32)";
FATAL("%s", str.str().c_str());
}
}
void Process(NodeBase* node, const ProcessingState& state) {
switch (node->opcode()) {
case Opcode::kAbort:
@ -128,6 +144,7 @@ class MaglevGraphVerifier {
case Opcode::kCheckInstanceType:
case Opcode::kCheckedInternalizedString:
case Opcode::kCheckedObjectToIndex:
case Opcode::kCheckedNumberToWord32:
case Opcode::kConvertReceiver:
case Opcode::kConvertHoleToUndefined:
// TODO(victorgomes): Can we check that the input is Boolean?
@ -173,6 +190,7 @@ class MaglevGraphVerifier {
case Opcode::kFloat64Box:
case Opcode::kHoleyFloat64Box:
case Opcode::kCheckedTruncateFloat64ToInt32:
case Opcode::kTruncateFloat64ToWord32:
DCHECK_EQ(node->input_count(), 1);
CheckValueInputIs(node, 0, ValueRepresentation::kFloat64);
break;
@ -240,14 +258,8 @@ class MaglevGraphVerifier {
case Opcode::kInt32SubtractWithOverflow:
case Opcode::kInt32MultiplyWithOverflow:
case Opcode::kInt32DivideWithOverflow:
// case Opcode::kInt32ExponentiateWithOverflow:
case Opcode::kInt32ModulusWithOverflow:
case Opcode::kInt32BitwiseAnd:
case Opcode::kInt32BitwiseOr:
case Opcode::kInt32BitwiseXor:
case Opcode::kInt32ShiftLeft:
case Opcode::kInt32ShiftRight:
case Opcode::kInt32ShiftRightLogical:
// case Opcode::kInt32ExponentiateWithOverflow:
case Opcode::kInt32Equal:
case Opcode::kInt32StrictEqual:
case Opcode::kInt32LessThan:
@ -260,6 +272,16 @@ class MaglevGraphVerifier {
CheckValueInputIs(node, 0, ValueRepresentation::kInt32);
CheckValueInputIs(node, 1, ValueRepresentation::kInt32);
break;
case Opcode::kInt32BitwiseAnd:
case Opcode::kInt32BitwiseOr:
case Opcode::kInt32BitwiseXor:
case Opcode::kInt32ShiftLeft:
case Opcode::kInt32ShiftRight:
case Opcode::kInt32ShiftRightLogical:
DCHECK_EQ(node->input_count(), 2);
CheckValueInputIsWord32(node, 0);
CheckValueInputIsWord32(node, 1);
break;
case Opcode::kBranchIfReferenceCompare:
DCHECK_EQ(node->input_count(), 2);
CheckValueInputIs(node, 0, ValueRepresentation::kTagged);

View File

@ -3191,7 +3191,7 @@ void CheckedFloat64Unbox::GenerateCode(MaglevAssembler* masm,
Register value = ToRegister(input());
Label is_not_smi, done;
// Check if Smi.
__ JumpIfNotSmi(value, &is_not_smi);
__ JumpIfNotSmi(value, &is_not_smi, Label::kNear);
// If Smi, convert to Float64.
__ SmiToInt32(value);
__ Cvtlsi2sd(ToDoubleRegister(result()), value);
@ -3201,7 +3201,7 @@ void CheckedFloat64Unbox::GenerateCode(MaglevAssembler* masm,
// not the same as the output register or the function does not call a
// builtin. So, we recover the Smi value here.
__ SmiTag(value);
__ jmp(&done);
__ jmp(&done, Label::kNear);
__ bind(&is_not_smi);
// Check if HeapNumber, deopt otherwise.
__ CompareRoot(FieldOperand(value, HeapObject::kMapOffset),
@ -3212,6 +3212,33 @@ void CheckedFloat64Unbox::GenerateCode(MaglevAssembler* masm,
__ bind(&done);
}
void CheckedNumberToWord32::AllocateVreg(
MaglevVregAllocationState* vreg_state) {
UseRegister(input());
DefineSameAsFirst(vreg_state, this);
}
void CheckedNumberToWord32::GenerateCode(MaglevAssembler* masm,
const ProcessingState& state) {
Register value = ToRegister(input());
Register result_reg = ToRegister(result());
DCHECK_EQ(value, result_reg);
Label is_not_smi, done;
// Check if Smi.
__ JumpIfNotSmi(value, &is_not_smi, Label::kNear);
// If Smi, convert to Int32.
__ SmiToInt32(value);
__ jmp(&done, Label::kNear);
__ bind(&is_not_smi);
// Check if HeapNumber, deopt otherwise.
__ CompareRoot(FieldOperand(value, HeapObject::kMapOffset),
RootIndex::kHeapNumberMap);
__ EmitEagerDeoptIf(not_equal, DeoptimizeReason::kNotANumber, this);
auto double_value = kScratchDoubleReg;
__ Movsd(double_value, FieldOperand(value, HeapNumber::kValueOffset));
__ TruncateDoubleToInt32(result_reg, double_value);
__ bind(&done);
}
void LogicalNot::AllocateVreg(MaglevVregAllocationState* vreg_state) {
UseRegister(value());
DefineAsRegister(vreg_state, this);
@ -3642,6 +3669,16 @@ void CheckedTruncateFloat64ToInt32::GenerateCode(MaglevAssembler* masm,
__ bind(&check_done);
}
void TruncateFloat64ToWord32::AllocateVreg(
MaglevVregAllocationState* vreg_state) {
UseRegister(input());
DefineAsRegister(vreg_state, this);
}
void TruncateFloat64ToWord32::GenerateCode(MaglevAssembler* masm,
const ProcessingState& state) {
__ TruncateDoubleToInt32(ToRegister(result()), ToDoubleRegister(input()));
}
void Phi::AllocateVreg(MaglevVregAllocationState* vreg_state) {
// Phi inputs are processed in the post-process, once loop phis' inputs'
// v-regs are allocated.

View File

@ -177,10 +177,12 @@ class CompactInterpreterFrameState;
V(UnsafeSmiUntag) \
V(CheckedInternalizedString) \
V(CheckedObjectToIndex) \
V(CheckedNumberToWord32) \
V(CheckedUint32ToInt32) \
V(ChangeInt32ToFloat64) \
V(ChangeUint32ToFloat64) \
V(CheckedTruncateFloat64ToInt32) \
V(TruncateFloat64ToWord32) \
V(Float64Box) \
V(HoleyFloat64Box) \
V(CheckedFloat64Unbox) \
@ -2107,6 +2109,22 @@ class CheckedTruncateFloat64ToInt32
void PrintParams(std::ostream&, MaglevGraphLabeller*) const {}
};
class TruncateFloat64ToWord32
: public FixedInputValueNodeT<1, TruncateFloat64ToWord32> {
using Base = FixedInputValueNodeT<1, TruncateFloat64ToWord32>;
public:
explicit TruncateFloat64ToWord32(uint64_t bitfield) : Base(bitfield) {}
static constexpr OpProperties kProperties = OpProperties::Int32();
Input& input() { return Node::input(0); }
void AllocateVreg(MaglevVregAllocationState*);
void GenerateCode(MaglevAssembler*, const ProcessingState&);
void PrintParams(std::ostream&, MaglevGraphLabeller*) const {}
};
class CheckedFloat64Unbox
: public FixedInputValueNodeT<1, CheckedFloat64Unbox> {
using Base = FixedInputValueNodeT<1, CheckedFloat64Unbox>;
@ -2125,6 +2143,23 @@ class CheckedFloat64Unbox
void PrintParams(std::ostream&, MaglevGraphLabeller*) const {}
};
class CheckedNumberToWord32
: public FixedInputValueNodeT<1, CheckedNumberToWord32> {
using Base = FixedInputValueNodeT<1, CheckedNumberToWord32>;
public:
explicit CheckedNumberToWord32(uint64_t bitfield) : Base(bitfield) {}
static constexpr OpProperties kProperties =
OpProperties::EagerDeopt() | OpProperties::Int32();
Input& input() { return Node::input(0); }
void AllocateVreg(MaglevVregAllocationState*);
void GenerateCode(MaglevAssembler*, const ProcessingState&);
void PrintParams(std::ostream&, MaglevGraphLabeller*) const {}
};
class LogicalNot : public FixedInputValueNodeT<1, LogicalNot> {
using Base = FixedInputValueNodeT<1, LogicalNot>;