Commit 99df710d authored by Nico Hartmann's avatar Nico Hartmann Committed by Commit Bot

[turbofan] Push BigInt truncation over addition and heap constants

This change implements lowering of speculative BigInt addition as well as
BigInt heap constants to corresponding int64 versions, if they are used in
a context where the result is truncated to the least significant 64 bits
(e.g. using asUintN). The JSHeapBroker is extended to provide access to the
BigInt's least significant digit during concurrent compilation. The BigInt
context (required to introduce correct conversions) is recognized in the
RepresentationChanger by either the output type propagated downward or the
TypeCheckKind propagated upward. This is necessary, because the TypeCheckKind
may only be set by nodes that may potentially deopt (and sit in the effect
chain). This is the case for SpeculativeBigIntAdd, but not for BigIntAsUintN.

This CL contains a simple fix to prevent int64-lowered BigInts to flow into
state values as the deoptimizer cannot handle them yet. A more sophisticated
solution to allow the deoptimizer to materialize truncated BigInts will be
added in a following CL.

Bug: v8:9407
Change-Id: I96a293e9077962f53e5f199857644f004e3ae56e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1684183
Commit-Queue: Nico Hartmann <nicohartmann@google.com>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Reviewed-by: 's avatarSigurd Schneider <sigurds@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62665}
parent 2e82ead8
......@@ -72,6 +72,7 @@ enum class OddballType : uint8_t {
V(Symbol) \
/* Subtypes of HeapObject */ \
V(AllocationSite) \
V(BigInt) \
V(CallHandlerInfo) \
V(Cell) \
V(Code) \
......@@ -478,6 +479,14 @@ class AllocationSiteRef : public HeapObjectRef {
bool CanInlineCall() const;
};
class BigIntRef : public HeapObjectRef {
public:
using HeapObjectRef::HeapObjectRef;
Handle<BigInt> object() const;
uint64_t AsUint64() const;
};
class V8_EXPORT_PRIVATE MapRef : public HeapObjectRef {
public:
using HeapObjectRef::HeapObjectRef;
......
......@@ -810,6 +810,18 @@ class AllocationSiteData : public HeapObjectData {
bool serialized_boilerplate_ = false;
};
class BigIntData : public HeapObjectData {
public:
BigIntData(JSHeapBroker* broker, ObjectData** storage, Handle<BigInt> object)
: HeapObjectData(broker, storage, object),
as_uint64_(object->AsUint64(nullptr)) {}
uint64_t AsUint64() const { return as_uint64_; }
private:
const uint64_t as_uint64_;
};
// Only used in JSNativeContextSpecialization.
class ScriptContextTableData : public HeapObjectData {
public:
......@@ -3263,6 +3275,11 @@ double MutableHeapNumberRef::value() const {
return data()->AsMutableHeapNumber()->value();
}
uint64_t BigIntRef::AsUint64() const {
IF_BROKER_DISABLED_ACCESS_HANDLE_C(BigInt, AsUint64);
return data()->AsBigInt()->AsUint64();
}
CellRef SourceTextModuleRef::GetCell(int cell_index) const {
if (broker()->mode() == JSHeapBroker::kDisabled) {
AllowHandleAllocation handle_allocation;
......
......@@ -19,6 +19,7 @@ RedundancyElimination::~RedundancyElimination() = default;
Reduction RedundancyElimination::Reduce(Node* node) {
if (node_checks_.Get(node)) return NoChange();
switch (node->opcode()) {
case IrOpcode::kCheckBigInt:
case IrOpcode::kCheckBounds:
case IrOpcode::kCheckEqualsInternalizedString:
case IrOpcode::kCheckEqualsSymbol:
......
......@@ -8,6 +8,7 @@
#include "src/base/bits.h"
#include "src/codegen/code-factory.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/type-cache.h"
......@@ -137,10 +138,11 @@ bool IsWord(MachineRepresentation rep) {
} // namespace
RepresentationChanger::RepresentationChanger(JSGraph* jsgraph, Isolate* isolate)
RepresentationChanger::RepresentationChanger(JSGraph* jsgraph,
JSHeapBroker* broker)
: cache_(TypeCache::Get()),
jsgraph_(jsgraph),
isolate_(isolate),
broker_(broker),
testing_type_errors_(false),
type_error_(false) {}
......@@ -432,6 +434,8 @@ Node* RepresentationChanger::GetTaggedPointerRepresentationFor(
op = machine()->ChangeInt64ToFloat64();
node = jsgraph()->graph()->NewNode(op, node);
op = simplified()->ChangeFloat64ToTaggedPointer();
} else if (output_type.Is(Type::BigInt())) {
op = simplified()->ChangeUint64ToBigInt();
} else {
return TypeError(node, output_rep, output_type,
MachineRepresentation::kTaggedPointer);
......@@ -461,10 +465,11 @@ Node* RepresentationChanger::GetTaggedPointerRepresentationFor(
// TODO(turbofan): Consider adding a Bailout operator that just deopts
// for TaggedSigned output representation.
op = simplified()->CheckedTaggedToTaggedPointer(use_info.feedback());
} else if ((IsAnyTagged(output_rep) || IsAnyCompressed(output_rep)) &&
use_info.type_check() == TypeCheckKind::kBigInt) {
if (IsAnyCompressed(output_rep)) {
node = InsertChangeCompressedToTagged(node);
} else if (IsAnyTagged(output_rep) &&
(use_info.type_check() == TypeCheckKind::kBigInt ||
output_type.Is(Type::BigInt()))) {
if (output_type.Is(Type::BigInt())) {
return node;
}
op = simplified()->CheckBigInt(use_info.feedback());
} else if (output_rep == MachineRepresentation::kCompressedPointer) {
......@@ -1290,6 +1295,15 @@ Node* RepresentationChanger::GetWord64RepresentationFor(
}
break;
}
case IrOpcode::kHeapConstant: {
HeapObjectMatcher m(node);
if (m.HasValue() && m.Ref(broker_).IsBigInt()) {
auto bigint = m.Ref(broker_).AsBigInt();
return jsgraph()->Int64Constant(
static_cast<int64_t>(bigint.AsUint64()));
}
break;
}
default:
break;
}
......@@ -1367,11 +1381,12 @@ Node* RepresentationChanger::GetWord64RepresentationFor(
return TypeError(node, output_rep, output_type,
MachineRepresentation::kWord64);
}
} else if ((IsAnyTagged(output_rep) || IsAnyCompressed(output_rep)) &&
output_type.Is(Type::BigInt())) {
if (IsAnyCompressed(output_rep)) {
node = InsertChangeCompressedToTagged(node);
}
} else if (IsAnyTagged(output_rep) &&
use_info.truncation().IsUsedAsWord64() &&
(use_info.type_check() == TypeCheckKind::kBigInt ||
output_type.Is(Type::BigInt()))) {
node = GetTaggedPointerRepresentationFor(node, output_rep, output_type,
use_node, use_info);
op = simplified()->TruncateBigIntToUint64();
} else if (CanBeTaggedPointer(output_rep)) {
if (output_type.Is(cache_->kInt64)) {
......@@ -1711,6 +1726,8 @@ Node* RepresentationChanger::InsertChangeCompressedToTagged(Node* node) {
node);
}
Isolate* RepresentationChanger::isolate() const { return broker_->isolate(); }
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -173,9 +173,12 @@ class UseInfo {
static UseInfo TruncatingWord32() {
return UseInfo(MachineRepresentation::kWord32, Truncation::Word32());
}
static UseInfo TruncatedBigIntAsWord64() {
static UseInfo TruncatingWord64() {
return UseInfo(MachineRepresentation::kWord64, Truncation::Word64());
}
static UseInfo CheckedBigIntTruncatingWord64(const VectorSlotPair& feedback) {
return UseInfo(MachineRepresentation::kWord64, Truncation::Word64(),
TypeCheckKind::kBigInt);
TypeCheckKind::kBigInt, feedback);
}
static UseInfo Word64() {
return UseInfo(MachineRepresentation::kWord64, Truncation::Any());
......@@ -308,7 +311,7 @@ class UseInfo {
// Eagerly folds any representation changes for constants.
class V8_EXPORT_PRIVATE RepresentationChanger final {
public:
RepresentationChanger(JSGraph* jsgraph, Isolate* isolate);
RepresentationChanger(JSGraph* jsgraph, JSHeapBroker* broker);
// Changes representation from {output_type} to {use_rep}. The {truncation}
// parameter is only used for sanity checking - if the changer cannot figure
......@@ -338,7 +341,7 @@ class V8_EXPORT_PRIVATE RepresentationChanger final {
private:
TypeCache const* cache_;
JSGraph* jsgraph_;
Isolate* isolate_;
JSHeapBroker* broker_;
friend class RepresentationChangerTester; // accesses the below fields.
......@@ -398,7 +401,7 @@ class V8_EXPORT_PRIVATE RepresentationChanger final {
Node* InsertUnconditionalDeopt(Node* node, DeoptimizeReason reason);
JSGraph* jsgraph() const { return jsgraph_; }
Isolate* isolate() const { return isolate_; }
Isolate* isolate() const;
Factory* factory() const { return isolate()->factory(); }
SimplifiedOperatorBuilder* simplified() { return jsgraph()->simplified(); }
MachineOperatorBuilder* machine() { return jsgraph()->machine(); }
......
......@@ -1117,8 +1117,11 @@ class RepresentationSelector {
if (IsAnyCompressed(rep)) {
return MachineType::AnyCompressed();
}
// Word64 representation is only valid for safe integer values.
if (rep == MachineRepresentation::kWord64) {
if (type.Is(Type::BigInt())) {
return MachineType::AnyTagged();
}
DCHECK(type.Is(TypeCache::Get()->kSafeInteger));
return MachineType(rep, MachineSemantic::kInt64);
}
......@@ -1134,7 +1137,17 @@ class RepresentationSelector {
void VisitStateValues(Node* node) {
if (propagate()) {
for (int i = 0; i < node->InputCount(); i++) {
EnqueueInput(node, i, UseInfo::Any());
// When lowering 64 bit BigInts to Word64 representation, we have to
// make sure they are rematerialized before deoptimization. By
// propagating a AnyTagged use, the RepresentationChanger is going to
// insert the necessary conversions.
// TODO(nicohartmann): Remove, once the deoptimizer can rematerialize
// truncated BigInts.
if (TypeOf(node->InputAt(i)).Is(Type::BigInt())) {
EnqueueInput(node, i, UseInfo::AnyTagged());
} else {
EnqueueInput(node, i, UseInfo::Any());
}
}
} else if (lower()) {
Zone* zone = jsgraph_->zone();
......@@ -1143,6 +1156,12 @@ class RepresentationSelector {
ZoneVector<MachineType>(node->InputCount(), zone);
for (int i = 0; i < node->InputCount(); i++) {
Node* input = node->InputAt(i);
// TODO(nicohartmann): Remove, once the deoptimizer can rematerialize
// truncated BigInts.
if (TypeOf(input).Is(Type::BigInt())) {
ProcessInput(node, i, UseInfo::AnyTagged());
}
(*types)[i] =
DeoptMachineTypeOf(GetInfo(input)->representation(), TypeOf(input));
}
......@@ -2481,7 +2500,7 @@ class RepresentationSelector {
return;
}
case IrOpcode::kBigIntAsUintN: {
ProcessInput(node, 0, UseInfo::TruncatedBigIntAsWord64());
ProcessInput(node, 0, UseInfo::TruncatingWord64());
SetOutput(node, MachineRepresentation::kWord64, Type::BigInt());
return;
}
......@@ -2646,9 +2665,18 @@ class RepresentationSelector {
return;
}
case IrOpcode::kSpeculativeBigIntAdd: {
VisitBinop(node,
UseInfo::CheckedBigIntAsTaggedPointer(VectorSlotPair{}),
MachineRepresentation::kTaggedPointer);
if (truncation.IsUsedAsWord64()) {
VisitBinop(node,
UseInfo::CheckedBigIntTruncatingWord64(VectorSlotPair{}),
MachineRepresentation::kWord64);
if (lower()) {
ChangeToPureOp(node, lowering->machine()->Int64Add());
}
} else {
VisitBinop(node,
UseInfo::CheckedBigIntAsTaggedPointer(VectorSlotPair{}),
MachineRepresentation::kTaggedPointer);
}
return;
}
case IrOpcode::kStringConcat: {
......@@ -3594,7 +3622,7 @@ SimplifiedLowering::SimplifiedLowering(JSGraph* jsgraph, JSHeapBroker* broker,
poisoning_level_(poisoning_level) {}
void SimplifiedLowering::LowerAllNodes() {
RepresentationChanger changer(jsgraph(), jsgraph()->isolate());
RepresentationChanger changer(jsgraph(), broker_);
RepresentationSelector selector(jsgraph(), broker_, zone_, &changer,
source_positions_, node_origins_);
selector.Run(this);
......
......@@ -239,6 +239,15 @@ NEVER_READ_ONLY_SPACE_IMPL(MutableBigInt)
MaybeHandle<MutableBigInt> MutableBigInt::New(Isolate* isolate, int length,
AllocationType allocation) {
if (length > BigInt::kMaxLength) {
// If the result of a BigInt computation is truncated to 64 bit, Turbofan
// can sometimes truncate intermediate results already, which can prevent
// those from exceeding the maximum length, effectively changing the
// semantics of optimized code. As this is a performance optimization, this
// behavior is accepted. To prevent the fuzzer from detecting this
// difference, we crash the program.
if (FLAG_correctness_fuzzer_suppressions) {
FATAL("Aborting on invalid BigInt length");
}
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kBigIntTooBig),
MutableBigInt);
}
......
......@@ -4,6 +4,8 @@
#include <limits>
#include "src/compiler/access-info.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/representation-change.h"
#include "src/compiler/type-cache.h"
......@@ -25,13 +27,15 @@ class RepresentationChangerTester : public HandleAndZoneScope,
javascript_(main_zone()),
jsgraph_(main_isolate(), main_graph_, &main_common_, &javascript_,
&main_simplified_, &main_machine_),
changer_(&jsgraph_, main_isolate()) {
broker_{main_isolate(), main_zone(), FLAG_trace_heap_broker},
changer_(&jsgraph_, &broker_) {
Node* s = graph()->NewNode(common()->Start(num_parameters));
graph()->SetStart(s);
}
JSOperatorBuilder javascript_;
JSGraph jsgraph_;
JSHeapBroker broker_;
RepresentationChanger changer_;
Isolate* isolate() { return main_isolate(); }
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --opt
function TestAsUintN() {
assertEquals(0n, BigInt.asUintN(64, 0n));
assertEquals(0n, BigInt.asUintN(8, 0n));
assertEquals(0n, BigInt.asUintN(1, 0n));
assertEquals(0n, BigInt.asUintN(0, 0n));
assertEquals(0n, BigInt.asUintN(100, 0n));
assertEquals(123n, BigInt.asUintN(64, 123n));
assertEquals(123n, BigInt.asUintN(32, 123n));
assertEquals(123n, BigInt.asUintN(8, 123n));
assertEquals(59n, BigInt.asUintN(6, 123n));
assertEquals(27n, BigInt.asUintN(5, 123n));
assertEquals(11n, BigInt.asUintN(4, 123n));
assertEquals(1n, BigInt.asUintN(1, 123n));
assertEquals(0n, BigInt.asUintN(0, 123n));
assertEquals(123n, BigInt.asUintN(72, 123n));
assertEquals(BigInt("0xFFFFFFFFFFFFFF85"), BigInt.asUintN(64, -123n));
assertEquals(BigInt("0xFFFFFF85"), BigInt.asUintN(32, -123n));
assertEquals(BigInt("0x85"), BigInt.asUintN(8, -123n));
assertEquals(5n, BigInt.asUintN(6, -123n));
assertEquals(5n, BigInt.asUintN(5, -123n));
assertEquals(5n, BigInt.asUintN(4, -123n));
assertEquals(1n, BigInt.asUintN(1, -123n));
assertEquals(0n, BigInt.asUintN(0, -123n));
assertEquals(BigInt("0xFFFFFFFFFFFFFFFF85"), BigInt.asUintN(72, -123n));
}
function TestInt64LoweredOperations() {
assertEquals(0n, BigInt.asUintN(64, -0n));
assertEquals(0n, BigInt.asUintN(64, 15n + -15n));
assertEquals(0n, BigInt.asUintN(64, 0n + 0n));
assertEquals(14n, BigInt.asUintN(32, 8n + 6n));
assertEquals(813n, BigInt.asUintN(10, 1013n + -200n));
assertEquals(15n, BigInt.asUintN(4, -319n + 302n));
for (let i = 0; i < 2; ++i) {
let x = 32n; // x = 32n
if (i === 1) {
x = BigInt.asUintN(64, x + 3n); // x = 35n
const y = x + -8n + x; // x = 35n, y = 62n
x = BigInt.asUintN(6, y + x); // x = 33n, y = 62n
x = -9n + y + -x; // x = 20n
x = BigInt.asUintN(10000 * i, x); // x = 20n
} else {
x = x + 400n; // x = 432n
x = -144n + BigInt.asUintN(8, 500n) + x; // x = 532n
}
assertEquals(20n, BigInt.asUintN(8, x));
}
let x = 7n;
for (let i = 0; i < 10; ++i) {
x = x + 5n;
}
assertEquals(57n, BigInt.asUintN(8, x));
let y = 7n;
for(let i = 0; i < 10; ++i) {
y = BigInt.asUintN(4, y + 16n);
}
assertEquals(7n, y);
}
function OptimizeAndTest(fn) {
%PrepareFunctionForOptimization(fn);
fn();
fn();
%OptimizeFunctionOnNextCall(fn);
fn();
assertOptimized(fn);
fn();
}
OptimizeAndTest(TestAsUintN);
OptimizeAndTest(TestInt64LoweredOperations);
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --opt
function TestNegate() {
assertEquals(0n, -0n);
const x = 15n;
assertEquals(-15n, -x);
assertEquals(15n, - -x);
assertEquals(30n, -(-x + -x));
}
function OptimizeAndTest(fn) {
%PrepareFunctionForOptimization(fn);
fn();
fn();
%OptimizeFunctionOnNextCall(fn);
fn();
assertOptimized(fn);
fn();
}
OptimizeAndTest(TestNegate);
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --opt
{
function test(a, b, c) {
let x = BigInt.asUintN(64, a + b);
console.log(x);
try {
return BigInt.asUintN(64, x + c);
} catch(_) {
return x;
}
}
%PrepareFunctionForOptimization(test);
test(3n, 4n, 5n);
test(6n, 7n, 8n);
test(9n, 2n, 1n);
%OptimizeFunctionOnNextCall(test);
test(1n, 2n, 3n);
test(3n, 2n, 1n);
assertEquals(6n, test(1n, 3n, 2n));
assertEquals(5n, test(2n, 3n, 2));
}
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