Reland "[turbofan] Optimize rab/gsab-backed TypedArrays and DataViews"

This reverts commit 4b28d53011.

Original change's description:
> [turbofan] Optimize rab/gsab-backed TypedArrays and DataViews
>
> This CL adds TurboFan optimizations for length and element access
> of TypedArrays and DataViews that are rab/gsab-backed.
>
> To enable this optimization, this CL builds the necessary machinery
> required to allow machine operators at the front of the pipeline
> (before simplified lowering). Some key changes to allow this are:
>  - Introduce Type::Machine() to allow the typer and the verifier to
>    provide a type to those machine operators in parts of the pipeline
>    that require nodes to be typed.
>  - Add EnterMachineGraph and ExitMachineGraph operators that define
>    the boundary between early machine graphs and the normal graph with
>    JS semantics.
>  - Give Branch operators a BranchSemantics parameter to distinguish
>    between machine branches (condition is a machine level value) and
>    JS branches (condition is a JS boolean value) and have phases that
>    handle branches decide on the branch's semantics based on this
>    parameter instead of the position in the pipeline.
>  - Extend SimplifiedLowering and SimplifiedLoweringVerifier to handle
>    machine graphs. In particular, constants required special handling,
>    because they are cached in the graph but they may have uses in both
>    a machine and the JS graph, which prevents consistent typing of
>    them.
>  - Moved lots of logic from JSCallReducerAssembler into
>    [JS]GraphAssembler such that functionality can be shared between
>    different phases (e.g. JSNativeContextSpecialization and
>    JSCallReducer need to generate logic to compute a TypedArray's
>    byte length). Extended assembler interface in general with
>    additional TNode<> overloads.
>
>
> Bug: v8:11111, chromium:1358505
> Change-Id: Ife006b8c38a83045cd3b8558acbfdcb66408891f
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3898690
> Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
> Commit-Queue: Nico Hartmann <nicohartmann@chromium.org>
> Reviewed-by: Clemens Backes <clemensb@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#83881}

Bug: v8:11111, chromium:1358505, v8:13412
Change-Id: I61664e18a9dba1741bcb70ec22ba6342521f500a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3976512
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Commit-Queue: Nico Hartmann <nicohartmann@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83904}
This commit is contained in:
Nico Hartmann 2022-10-25 13:27:39 +02:00 committed by V8 LUCI CQ
parent f44d43de74
commit 05bd7d9cd6
45 changed files with 3046 additions and 675 deletions

View File

@ -7,6 +7,7 @@
#include <algorithm>
#include <optional>
#include <vector>
namespace v8::base {
@ -77,6 +78,18 @@ inline size_t count_if(const C& container, const P& predicate) {
return std::count_if(begin(container), end(container), predicate);
}
// Helper for std::all_of.
template <typename C, typename P>
inline bool all_of(const C& container, const P& predicate) {
return std::all_of(begin(container), end(container), predicate);
}
// Helper for std::none_of.
template <typename C, typename P>
inline bool none_of(const C& container, const P& predicate) {
return std::none_of(begin(container), end(container), predicate);
}
// Returns true iff all elements of {container} compare equal using operator==.
template <typename C>
inline bool all_equal(const C& container) {
@ -87,6 +100,21 @@ inline bool all_equal(const C& container) {
[&](const auto& v) { return v == value; });
}
// Returns true iff all elements of {container} compare equal to {value} using
// operator==.
template <typename C, typename T>
inline bool all_equal(const C& container, const T& value) {
return std::all_of(begin(container), end(container),
[&](const auto& v) { return v == value; });
}
// Appends to vector {v} all the elements in the range {begin(container)} and
// {end(container)}.
template <typename T, typename A, typename C>
inline void vector_append(std::vector<T, A>& v, const C& container) {
v.insert(end(v), begin(container), end(container));
}
} // namespace v8::base
#endif // V8_BASE_CONTAINER_UTILS_H_

View File

@ -25,6 +25,7 @@
#include "src/logging/log.h"
#include "src/numbers/hash-seed-inl.h"
#include "src/numbers/math-random.h"
#include "src/objects/elements-kind.h"
#include "src/objects/elements.h"
#include "src/objects/object-type.h"
#include "src/objects/objects-inl.h"
@ -950,6 +951,20 @@ ExternalReference ExternalReference::search_string_raw_two_two() {
return search_string_raw<const base::uc16, const base::uc16>();
}
ExternalReference
ExternalReference::typed_array_and_rab_gsab_typed_array_elements_kind_shifts() {
uint8_t* ptr =
const_cast<uint8_t*>(TypedArrayAndRabGsabTypedArrayElementsKindShifts());
return ExternalReference(reinterpret_cast<Address>(ptr));
}
ExternalReference
ExternalReference::typed_array_and_rab_gsab_typed_array_elements_kind_sizes() {
uint8_t* ptr =
const_cast<uint8_t*>(TypedArrayAndRabGsabTypedArrayElementsKindSizes());
return ExternalReference(reinterpret_cast<Address>(ptr));
}
namespace {
void StringWriteToFlatOneByte(Address source, uint8_t* sink, int32_t start,

View File

@ -338,6 +338,10 @@ class StatsCounter;
V(re_match_for_call_from_js, "IrregexpInterpreter::MatchForCallFromJs") \
V(re_experimental_match_for_call_from_js, \
"ExperimentalRegExp::MatchForCallFromJs") \
V(typed_array_and_rab_gsab_typed_array_elements_kind_shifts, \
"TypedArrayAndRabGsabTypedArrayElementsKindShifts") \
V(typed_array_and_rab_gsab_typed_array_elements_kind_sizes, \
"TypedArrayAndRabGsabTypedArrayElementsKindSizes") \
EXTERNAL_REFERENCE_LIST_INTL(V) \
EXTERNAL_REFERENCE_LIST_SANDBOX(V)
#ifdef V8_INTL_SUPPORT

View File

@ -359,10 +359,10 @@ class TNode {
public:
template <class U,
typename std::enable_if<is_subtype<U, T>::value, int>::type = 0>
TNode(const TNode<U>& other) : node_(other) {
TNode(const TNode<U>& other) V8_NOEXCEPT : node_(other) {
LazyTemplateChecks();
}
TNode(const TNode& other) : node_(other) { LazyTemplateChecks(); }
TNode(const TNode& other) V8_NOEXCEPT : node_(other) { LazyTemplateChecks(); }
TNode() : TNode(nullptr) {}
TNode operator=(TNode other) {
@ -375,7 +375,7 @@ class TNode {
static TNode UncheckedCast(compiler::Node* node) { return TNode(node); }
private:
protected:
explicit TNode(compiler::Node* node) : node_(node) { LazyTemplateChecks(); }
// These checks shouldn't be checked before TNode is actually used.
void LazyTemplateChecks() {
@ -385,6 +385,21 @@ class TNode {
compiler::Node* node_;
};
// SloppyTNode<T> is a variant of TNode<T> and allows implicit casts from
// Node*. It is intended for function arguments as long as some call sites
// still use untyped Node* arguments.
// TODO(turbofan): Delete this class once transition is finished.
template <class T>
class SloppyTNode : public TNode<T> {
public:
SloppyTNode(compiler::Node* node) // NOLINT(runtime/explicit)
: TNode<T>(node) {}
template <class U, typename std::enable_if<is_subtype<U, T>::value,
int>::type = 0>
SloppyTNode(const TNode<U>& other) V8_NOEXCEPT // NOLINT(runtime/explicit)
: TNode<T>(other) {}
};
} // namespace internal
} // namespace v8

View File

@ -364,6 +364,22 @@ FieldAccess AccessBuilder::ForJSArrayBufferBitField() {
return access;
}
// static
FieldAccess AccessBuilder::ForJSArrayBufferByteLength() {
FieldAccess access = {kTaggedBase,
JSArrayBuffer::kRawByteLengthOffset,
MaybeHandle<Name>(),
MaybeHandle<Map>(),
TypeCache::Get()->kJSArrayBufferByteLengthType,
MachineType::UintPtr(),
kNoWriteBarrier,
"JSArrayBufferByteLength"};
#ifdef V8_ENABLE_SANDBOX
access.is_bounded_size_access = true;
#endif
return access;
}
// static
FieldAccess AccessBuilder::ForJSArrayBufferViewBuffer() {
FieldAccess access = {kTaggedBase, JSArrayBufferView::kBufferOffset,
@ -405,6 +421,19 @@ FieldAccess AccessBuilder::ForJSArrayBufferViewByteOffset() {
return access;
}
// static
FieldAccess AccessBuilder::ForJSArrayBufferViewBitField() {
FieldAccess access = {kTaggedBase,
JSArrayBufferView::kBitFieldOffset,
MaybeHandle<Name>(),
MaybeHandle<Map>(),
TypeCache::Get()->kUint32,
MachineType::Uint32(),
kNoWriteBarrier,
"JSArrayBufferViewBitField"};
return access;
}
// static
FieldAccess AccessBuilder::ForJSTypedArrayLength() {
FieldAccess access = {kTaggedBase,

View File

@ -134,6 +134,9 @@ class V8_EXPORT_PRIVATE AccessBuilder final
// Provides access to JSArrayBuffer::bit_field() field.
static FieldAccess ForJSArrayBufferBitField();
// Provides access to JSArrayBuffer::byteLength() field.
static FieldAccess ForJSArrayBufferByteLength();
// Provides access to JSArrayBufferView::buffer() field.
static FieldAccess ForJSArrayBufferViewBuffer();
@ -143,6 +146,9 @@ class V8_EXPORT_PRIVATE AccessBuilder final
// Provides access to JSArrayBufferView::byteOffset() field.
static FieldAccess ForJSArrayBufferViewByteOffset();
// Provides access to JSArrayBufferView::bitfield() field
static FieldAccess ForJSArrayBufferViewBitField();
// Provides access to JSTypedArray::length() field.
static FieldAccess ForJSTypedArrayLength();

View File

@ -12,6 +12,7 @@
#include "src/codegen/tick-counter.h"
#include "src/common/globals.h"
#include "src/compiler/backend/instruction-selector-impl.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/compiler-source-position-table.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/node-properties.h"
@ -1288,6 +1289,10 @@ void InstructionSelector::VisitControl(BasicBlock* block) {
}
case BasicBlock::kBranch: {
DCHECK_EQ(IrOpcode::kBranch, input->opcode());
// TODO(nicohartmann@): Once all branches have explicitly specified
// semantics, we should allow only BranchSemantics::kMachine here.
DCHECK_NE(BranchSemantics::kJS,
BranchParametersOf(input->op()).semantics());
BasicBlock* tbranch = block->SuccessorAt(0);
BasicBlock* fbranch = block->SuccessorAt(1);
if (tbranch == fbranch) {

View File

@ -5,8 +5,10 @@
#include "src/compiler/branch-elimination.h"
#include "src/base/small-vector.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/opcodes.h"
namespace v8 {
namespace internal {
@ -81,11 +83,24 @@ void BranchElimination::SimplifyBranchCondition(Node* branch) {
// second_true second_false
//
auto SemanticsOf = [phase = this->phase_](Node* branch) {
BranchSemantics semantics = BranchSemantics::kUnspecified;
if (branch->opcode() == IrOpcode::kBranch) {
semantics = BranchParametersOf(branch->op()).semantics();
}
if (semantics == BranchSemantics::kUnspecified) {
semantics =
(phase == kEARLY ? BranchSemantics::kJS : BranchSemantics::kMachine);
}
return semantics;
};
DCHECK_EQ(IrOpcode::kBranch, branch->opcode());
Node* merge = NodeProperties::GetControlInput(branch);
if (merge->opcode() != IrOpcode::kMerge) return;
Node* condition = branch->InputAt(0);
BranchSemantics semantics = SemanticsOf(branch);
Graph* graph = jsgraph()->graph();
base::SmallVector<Node*, 2> phi_inputs;
@ -97,12 +112,14 @@ void BranchElimination::SimplifyBranchCondition(Node* branch) {
BranchCondition branch_condition = from_input.LookupState(condition);
if (!branch_condition.IsSet()) return;
if (SemanticsOf(branch_condition.branch) != semantics) return;
bool condition_value = branch_condition.is_true;
if (phase_ == kEARLY) {
if (semantics == BranchSemantics::kJS) {
phi_inputs.emplace_back(condition_value ? jsgraph()->TrueConstant()
: jsgraph()->FalseConstant());
} else {
DCHECK_EQ(semantics, BranchSemantics::kMachine);
phi_inputs.emplace_back(
condition_value
? graph->NewNode(jsgraph()->common()->Int32Constant(1))
@ -110,8 +127,9 @@ void BranchElimination::SimplifyBranchCondition(Node* branch) {
}
}
phi_inputs.emplace_back(merge);
Node* new_phi = graph->NewNode(
common()->Phi(phase_ == kEARLY ? MachineRepresentation::kTagged
Node* new_phi =
graph->NewNode(common()->Phi(semantics == BranchSemantics::kJS
? MachineRepresentation::kTagged
: MachineRepresentation::kWord32,
input_count),
input_count + 1, &phi_inputs.at(0));

View File

@ -43,6 +43,8 @@ class V8_EXPORT_PRIVATE BranchElimination final
: public NON_EXPORTED_BASE(AdvancedReducerWithControlPathState)<
BranchCondition, kUniqueInstance> {
public:
// TODO(nicohartmann@): Remove {Phase} once all Branch operators have
// specified semantics.
enum Phase {
kEARLY,
kLATE,

View File

@ -29,6 +29,18 @@ std::ostream& operator<<(std::ostream& os, BranchHint hint) {
namespace compiler {
std::ostream& operator<<(std::ostream& os, BranchSemantics semantics) {
switch (semantics) {
case BranchSemantics::kJS:
return os << "JS";
case BranchSemantics::kMachine:
return os << "Machine";
case BranchSemantics::kUnspecified:
return os << "Unspecified";
}
UNREACHABLE();
}
std::ostream& operator<<(std::ostream& os, TrapId trap_id) {
switch (trap_id) {
#define TRAP_CASE(Name) \
@ -48,13 +60,33 @@ TrapId TrapIdOf(const Operator* const op) {
return OpParameter<TrapId>(op);
}
bool operator==(const BranchParameters& lhs, const BranchParameters& rhs) {
return lhs.semantics() == rhs.semantics() && lhs.hint() == rhs.hint();
}
size_t hash_value(const BranchParameters& p) {
return base::hash_combine(p.semantics(), p.hint());
}
std::ostream& operator<<(std::ostream& os, const BranchParameters& p) {
return os << p.semantics() << ", " << p.hint();
}
const BranchParameters& BranchParametersOf(const Operator* const op) {
DCHECK_EQ(op->opcode(), IrOpcode::kBranch);
return OpParameter<BranchParameters>(op);
}
BranchHint BranchHintOf(const Operator* const op) {
switch (op->opcode()) {
case IrOpcode::kIfValue:
return IfValueParametersOf(op).hint();
case IrOpcode::kIfDefault:
case IrOpcode::kBranch:
return OpParameter<BranchHint>(op);
// TODO(nicohartmann@): Should remove all uses of BranchHintOf for branches
// and replace with BranchParametersOf.
case IrOpcode::kBranch:
return BranchParametersOf(op).hint();
default:
UNREACHABLE();
}
@ -434,6 +466,27 @@ const SLVerifierHintParameters& SLVerifierHintParametersOf(const Operator* op) {
return OpParameter<SLVerifierHintParameters>(op);
}
V8_EXPORT_PRIVATE bool operator==(const ExitMachineGraphParameters& lhs,
const ExitMachineGraphParameters& rhs) {
return lhs.output_representation() == rhs.output_representation() &&
lhs.output_type().Equals(rhs.output_type());
}
size_t hash_value(const ExitMachineGraphParameters& p) {
return base::hash_combine(p.output_representation(), p.output_type());
}
V8_EXPORT_PRIVATE std::ostream& operator<<(
std::ostream& os, const ExitMachineGraphParameters& p) {
return os << p.output_representation() << ", " << p.output_type();
}
const ExitMachineGraphParameters& ExitMachineGraphParametersOf(
const Operator* op) {
DCHECK_EQ(op->opcode(), IrOpcode::kExitMachineGraph);
return OpParameter<ExitMachineGraphParameters>(op);
}
#define COMMON_CACHED_OP_LIST(V) \
V(Plug, Operator::kNoProperties, 0, 0, 0, 1, 0, 0) \
V(Dead, Operator::kFoldable, 0, 0, 0, 1, 1, 1) \
@ -453,9 +506,15 @@ const SLVerifierHintParameters& SLVerifierHintParametersOf(const Operator* op) {
#define CACHED_LOOP_EXIT_VALUE_LIST(V) V(kTagged)
#define CACHED_BRANCH_LIST(V) \
V(None) \
V(True) \
V(False)
V(JS, None) \
V(JS, True) \
V(JS, False) \
V(Machine, None) \
V(Machine, True) \
V(Machine, False) \
V(Unspecified, None) \
V(Unspecified, True) \
V(Unspecified, False)
#define CACHED_RETURN_LIST(V) \
V(1) \
@ -626,17 +685,18 @@ struct CommonOperatorGlobalCache final {
CACHED_RETURN_LIST(CACHED_RETURN)
#undef CACHED_RETURN
template <BranchHint hint>
struct BranchOperator final : public Operator1<BranchHint> {
template <BranchSemantics semantics, BranchHint hint>
struct BranchOperator final : public Operator1<BranchParameters> {
BranchOperator()
: Operator1<BranchHint>( // --
: Operator1<BranchParameters>( // --
IrOpcode::kBranch, Operator::kKontrol, // opcode
"Branch", // name
1, 0, 1, 0, 0, 2, // counts
hint) {} // parameter
{semantics, hint}) {} // parameter
};
#define CACHED_BRANCH(Hint) \
BranchOperator<BranchHint::k##Hint> kBranch##Hint##Operator;
#define CACHED_BRANCH(Semantics, Hint) \
BranchOperator<BranchSemantics::k##Semantics, BranchHint::k##Hint> \
kBranch##Semantics##Hint##Operator;
CACHED_BRANCH_LIST(CACHED_BRANCH)
#undef CACHED_BRANCH
@ -924,10 +984,12 @@ const Operator* CommonOperatorBuilder::SLVerifierHint(
0, 0, 1, 0, 0, SLVerifierHintParameters(semantics, override_output_type));
}
const Operator* CommonOperatorBuilder::Branch(BranchHint hint) {
#define CACHED_BRANCH(Hint) \
if (hint == BranchHint::k##Hint) { \
return &cache_.kBranch##Hint##Operator; \
const Operator* CommonOperatorBuilder::Branch(BranchHint hint,
BranchSemantics semantics) {
#define CACHED_BRANCH(Semantics, Hint) \
if (semantics == BranchSemantics::k##Semantics && \
hint == BranchHint::k##Hint) { \
return &cache_.kBranch##Semantics##Hint##Operator; \
}
CACHED_BRANCH_LIST(CACHED_BRANCH)
#undef CACHED_BRANCH
@ -1309,6 +1371,19 @@ const Operator* CommonOperatorBuilder::FoldConstant() {
2, 0, 0, 1, 0, 0); // counts
}
const Operator* CommonOperatorBuilder::EnterMachineGraph(UseInfo use_info) {
return zone()->New<Operator1<UseInfo>>(IrOpcode::kEnterMachineGraph,
Operator::kPure, "EnterMachineGraph",
1, 0, 0, 1, 0, 0, use_info);
}
const Operator* CommonOperatorBuilder::ExitMachineGraph(
MachineRepresentation output_representation, Type output_type) {
return zone()->New<Operator1<ExitMachineGraphParameters>>(
IrOpcode::kExitMachineGraph, Operator::kPure, "ExitMachineGraph", 1, 0, 0,
1, 0, 0, ExitMachineGraphParameters{output_representation, output_type});
}
const Operator* CommonOperatorBuilder::EffectPhi(int effect_input_count) {
DCHECK_LT(0, effect_input_count); // Disallow empty effect phis.
switch (effect_input_count) {

View File

@ -13,6 +13,7 @@
#include "src/compiler/frame-states.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/use-info.h"
#include "src/deoptimizer/deoptimize-reason.h"
#include "src/zone/zone-containers.h"
@ -35,7 +36,11 @@ class Node;
// (machine branch semantics). Some passes are applied both before and after
// SimplifiedLowering, and use the BranchSemantics enum to know how branches
// should be treated.
enum class BranchSemantics { kJS, kMachine };
// TODO(nicohartmann@): Need to remove BranchSemantics::kUnspecified once all
// branch uses have been updated.
enum class BranchSemantics { kJS, kMachine, kUnspecified };
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream&, BranchSemantics);
inline BranchHint NegateBranchHint(BranchHint hint) {
switch (hint) {
@ -62,6 +67,32 @@ std::ostream& operator<<(std::ostream&, TrapId trap_id);
TrapId TrapIdOf(const Operator* const op);
class BranchParameters final {
public:
BranchParameters(BranchSemantics semantics, BranchHint hint)
: semantics_(semantics), hint_(hint) {}
BranchSemantics semantics() const { return semantics_; }
BranchHint hint() const { return hint_; }
private:
const BranchSemantics semantics_;
const BranchHint hint_;
};
bool operator==(const BranchParameters& lhs, const BranchParameters& rhs);
inline bool operator!=(const BranchParameters& lhs,
const BranchParameters& rhs) {
return !(lhs == rhs);
}
size_t hash_value(const BranchParameters& p);
std::ostream& operator<<(std::ostream&, const BranchParameters& p);
V8_EXPORT_PRIVATE const BranchParameters& BranchParametersOf(
const Operator* const) V8_WARN_UNUSED_RESULT;
V8_EXPORT_PRIVATE BranchHint BranchHintOf(const Operator* const)
V8_WARN_UNUSED_RESULT;
@ -439,6 +470,35 @@ V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
V8_EXPORT_PRIVATE const SLVerifierHintParameters& SLVerifierHintParametersOf(
const Operator* op) V8_WARN_UNUSED_RESULT;
class ExitMachineGraphParameters final {
public:
ExitMachineGraphParameters(MachineRepresentation output_representation,
Type output_type)
: output_representation_(output_representation),
output_type_(output_type) {}
MachineRepresentation output_representation() const {
return output_representation_;
}
const Type& output_type() const { return output_type_; }
private:
const MachineRepresentation output_representation_;
const Type output_type_;
};
V8_EXPORT_PRIVATE bool operator==(const ExitMachineGraphParameters& lhs,
const ExitMachineGraphParameters& rhs);
size_t hash_value(const ExitMachineGraphParameters& p);
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
const ExitMachineGraphParameters& p);
V8_EXPORT_PRIVATE const ExitMachineGraphParameters&
ExitMachineGraphParametersOf(const Operator* op) V8_WARN_UNUSED_RESULT;
// Interface for building common operators that can be used at any level of IR,
// including JavaScript, mid-level, and low-level.
class V8_EXPORT_PRIVATE CommonOperatorBuilder final
@ -464,7 +524,11 @@ class V8_EXPORT_PRIVATE CommonOperatorBuilder final
const Operator* semantics,
const base::Optional<Type>& override_output_type);
const Operator* End(size_t control_input_count);
const Operator* Branch(BranchHint = BranchHint::kNone);
// TODO(nicohartmann@): Remove the default argument for {semantics} once all
// uses are updated.
const Operator* Branch(
BranchHint = BranchHint::kNone,
BranchSemantics semantics = BranchSemantics::kUnspecified);
const Operator* IfTrue();
const Operator* IfFalse();
const Operator* IfSuccess();
@ -537,6 +601,9 @@ class V8_EXPORT_PRIVATE CommonOperatorBuilder final
const Operator* Retain();
const Operator* TypeGuard(Type type);
const Operator* FoldConstant();
const Operator* EnterMachineGraph(UseInfo use_info);
const Operator* ExitMachineGraph(MachineRepresentation output_representation,
Type output_type);
// Constructs a new merge or phi operator with the same opcode as {op}, but
// with {size} inputs.

View File

@ -23,10 +23,12 @@ struct Diamond {
Node* merge;
Diamond(Graph* g, CommonOperatorBuilder* b, Node* cond,
BranchHint hint = BranchHint::kNone) {
BranchHint hint = BranchHint::kNone,
BranchSemantics semantics = BranchSemantics::kUnspecified) {
graph = g;
common = b;
branch = graph->NewNode(common->Branch(hint), cond, graph->start());
branch =
graph->NewNode(common->Branch(hint, semantics), cond, graph->start());
if_true = graph->NewNode(common->IfTrue(), branch);
if_false = graph->NewNode(common->IfFalse(), branch);
merge = graph->NewNode(common->Merge(2), if_true, if_false);

View File

@ -5113,14 +5113,14 @@ Node* EffectControlLinearizer::AdaptFastCallTypedArrayArgument(
Node* buffer_is_not_detached = __ Word32Equal(
__ Word32And(buffer_bit_field,
__ Int32Constant(JSArrayBuffer::WasDetachedBit::kMask)),
__ ZeroConstant());
__ Int32Constant(0));
__ GotoIfNot(buffer_is_not_detached, bailout);
// Go to the slow path if the {buffer} is shared.
Node* buffer_is_not_shared = __ Word32Equal(
__ Word32And(buffer_bit_field,
__ Int32Constant(JSArrayBuffer::IsSharedBit::kMask)),
__ ZeroConstant());
__ Int32Constant(0));
__ GotoIfNot(buffer_is_not_shared, bailout);
// Unpack the store and length, and store them to a struct
@ -6941,7 +6941,8 @@ void LinearizeEffectControl(JSGraph* graph, Schedule* schedule, Zone* temp_zone,
SourcePositionTable* source_positions,
NodeOriginTable* node_origins,
JSHeapBroker* broker) {
JSGraphAssembler graph_assembler_(graph, temp_zone);
JSGraphAssembler graph_assembler_(graph, temp_zone,
BranchSemantics::kMachine);
EffectControlLinearizer linearizer(graph, schedule, &graph_assembler_,
temp_zone, source_positions, node_origins,
MaintainSchedule::kDiscard, broker);

View File

@ -4,12 +4,21 @@
#include "src/compiler/graph-assembler.h"
#include "src/base/container-utils.h"
#include "src/codegen/callable.h"
#include "src/codegen/machine-type.h"
#include "src/codegen/tnode.h"
#include "src/common/globals.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/graph-reducer.h"
#include "src/compiler/linkage.h"
#include "src/compiler/type-cache.h"
// For TNode types.
#include "src/objects/elements-kind.h"
#include "src/objects/heap-number.h"
#include "src/objects/instance-type.h"
#include "src/objects/js-array-buffer.h"
#include "src/objects/oddball.h"
#include "src/objects/string.h"
@ -33,18 +42,21 @@ class V8_NODISCARD GraphAssembler::BlockInlineReduction {
};
GraphAssembler::GraphAssembler(
MachineGraph* mcgraph, Zone* zone,
MachineGraph* mcgraph, Zone* zone, BranchSemantics default_branch_semantics,
base::Optional<NodeChangedCallback> node_changed_callback,
bool mark_loop_exits)
: temp_zone_(zone),
mcgraph_(mcgraph),
default_branch_semantics_(default_branch_semantics),
effect_(nullptr),
control_(nullptr),
node_changed_callback_(node_changed_callback),
inline_reducers_(zone),
inline_reductions_blocked_(false),
loop_headers_(zone),
mark_loop_exits_(mark_loop_exits) {}
mark_loop_exits_(mark_loop_exits) {
DCHECK_NE(default_branch_semantics_, BranchSemantics::kUnspecified);
}
GraphAssembler::~GraphAssembler() { DCHECK_EQ(loop_nesting_level_, 0); }
@ -52,16 +64,16 @@ Node* GraphAssembler::IntPtrConstant(intptr_t value) {
return AddClonedNode(mcgraph()->IntPtrConstant(value));
}
Node* GraphAssembler::UintPtrConstant(uintptr_t value) {
return AddClonedNode(mcgraph()->UintPtrConstant(value));
TNode<UintPtrT> GraphAssembler::UintPtrConstant(uintptr_t value) {
return TNode<UintPtrT>::UncheckedCast(mcgraph()->UintPtrConstant(value));
}
Node* GraphAssembler::Int32Constant(int32_t value) {
return AddClonedNode(mcgraph()->Int32Constant(value));
}
Node* GraphAssembler::Uint32Constant(uint32_t value) {
return AddClonedNode(mcgraph()->Uint32Constant(value));
TNode<Uint32T> GraphAssembler::Uint32Constant(uint32_t value) {
return TNode<Uint32T>::UncheckedCast(mcgraph()->Uint32Constant(value));
}
Node* GraphAssembler::Int64Constant(int64_t value) {
@ -150,8 +162,43 @@ PURE_ASSEMBLER_MACH_UNOP_LIST(PURE_UNOP_DEF)
Node* GraphAssembler::Name(Node* left, Node* right) { \
return AddNode(graph()->NewNode(machine()->Name(), left, right)); \
}
PURE_ASSEMBLER_MACH_BINOP_LIST(PURE_BINOP_DEF)
#define PURE_BINOP_DEF_TNODE(Name, Result, Left, Right) \
TNode<Result> GraphAssembler::Name(SloppyTNode<Left> left, \
SloppyTNode<Right> right) { \
return AddNode<Result>(graph()->NewNode(machine()->Name(), left, right)); \
}
PURE_ASSEMBLER_MACH_BINOP_LIST(PURE_BINOP_DEF, PURE_BINOP_DEF_TNODE)
#undef PURE_BINOP_DEF
#undef PURE_BINOP_DEF_TNODE
TNode<BoolT> GraphAssembler::UintPtrLessThanOrEqual(TNode<UintPtrT> left,
TNode<UintPtrT> right) {
return kSystemPointerSize == 8
? Uint64LessThanOrEqual(TNode<Uint64T>::UncheckedCast(left),
TNode<Uint64T>::UncheckedCast(right))
: Uint32LessThanOrEqual(TNode<Uint32T>::UncheckedCast(left),
TNode<Uint32T>::UncheckedCast(right));
}
TNode<UintPtrT> GraphAssembler::UintPtrAdd(TNode<UintPtrT> left,
TNode<UintPtrT> right) {
return kSystemPointerSize == 8
? TNode<UintPtrT>::UncheckedCast(Int64Add(left, right))
: TNode<UintPtrT>::UncheckedCast(Int32Add(left, right));
}
TNode<UintPtrT> GraphAssembler::UintPtrSub(TNode<UintPtrT> left,
TNode<UintPtrT> right) {
return kSystemPointerSize == 8
? TNode<UintPtrT>::UncheckedCast(Int64Sub(left, right))
: TNode<UintPtrT>::UncheckedCast(Int32Sub(left, right));
}
TNode<UintPtrT> GraphAssembler::UintPtrDiv(TNode<UintPtrT> left,
TNode<UintPtrT> right) {
return kSystemPointerSize == 8
? TNode<UintPtrT>::UncheckedCast(Uint64Div(left, right))
: TNode<UintPtrT>::UncheckedCast(Uint32Div(left, right));
}
#define CHECKED_BINOP_DEF(Name) \
Node* GraphAssembler::Name(Node* left, Node* right) { \
@ -226,6 +273,15 @@ Node* JSGraphAssembler::LoadField(FieldAccess const& access, Node* object) {
return value;
}
TNode<Uint32T> JSGraphAssembler::LoadElementsKind(TNode<Map> map) {
TNode<Uint8T> bit_field2 = EnterMachineGraph<Uint8T>(
LoadField<Uint8T>(AccessBuilder::ForMapBitField2(), map),
UseInfo::TruncatingWord32());
return TNode<Uint32T>::UncheckedCast(
Word32Shr(TNode<Word32T>::UncheckedCast(bit_field2),
Uint32Constant(Map::Bits2::ElementsKindBits::kShift)));
}
Node* JSGraphAssembler::LoadElement(ElementAccess const& access, Node* object,
Node* index) {
Node* value = AddNode(graph()->NewNode(simplified()->LoadElement(access),
@ -411,6 +467,363 @@ Node* JSGraphAssembler::StringCharCodeAt(TNode<String> string,
position, effect(), control()));
}
class ArrayBufferViewAccessBuilder {
public:
explicit ArrayBufferViewAccessBuilder(JSGraphAssembler* assembler,
std::set<ElementsKind> candidates)
: assembler_(assembler), candidates_(std::move(candidates)) {
DCHECK_NOT_NULL(assembler_);
}
bool maybe_rab_gsab() const {
if (candidates_.empty()) return true;
return !base::all_of(candidates_, [](auto e) {
return !IsRabGsabTypedArrayElementsKind(e);
});
}
base::Optional<int> TryComputeStaticElementShift() {
if (candidates_.empty()) return base::nullopt;
int shift = ElementsKindToShiftSize(*candidates_.begin());
if (!base::all_of(candidates_, [shift](auto e) {
return ElementsKindToShiftSize(e) == shift;
})) {
return base::nullopt;
}
return shift;
}
base::Optional<int> TryComputeStaticElementSize() {
if (candidates_.empty()) return base::nullopt;
int size = ElementsKindToByteSize(*candidates_.begin());
if (!base::all_of(candidates_, [size](auto e) {
return ElementsKindToByteSize(e) == size;
})) {
return base::nullopt;
}
return size;
}
TNode<UintPtrT> BuildLength(TNode<JSArrayBufferView> view,
TNode<Context> context) {
auto& a = *assembler_;
// Case 1: Normal (backed by AB/SAB) or non-length tracking backed by GSAB
// (can't go oob once constructed)
auto GsabFixedOrNormal = [&]() {
return MachineLoadField<UintPtrT>(AccessBuilder::ForJSTypedArrayLength(),
view, UseInfo::Word());
};
// If we statically know we cannot have rab/gsab backed, we can simply
// load from the view.
if (!maybe_rab_gsab()) {
return GsabFixedOrNormal();
}
// Otherwise, we need to generate the checks for the view's bitfield.
TNode<Word32T> bitfield = a.EnterMachineGraph<Word32T>(
a.LoadField<Word32T>(AccessBuilder::ForJSArrayBufferViewBitField(),
view),
UseInfo::TruncatingWord32());
TNode<Word32T> length_tracking_bit = a.Word32And(
bitfield, a.Uint32Constant(JSArrayBufferView::kIsLengthTracking));
TNode<Word32T> backed_by_rab_bit = a.Word32And(
bitfield, a.Uint32Constant(JSArrayBufferView::kIsBackedByRab));
// Load the underlying buffer.
TNode<HeapObject> buffer = a.LoadField<HeapObject>(
AccessBuilder::ForJSArrayBufferViewBuffer(), view);
// Compute the element size.
TNode<Uint32T> element_size;
if (auto size_opt = TryComputeStaticElementSize()) {
element_size = a.Uint32Constant(*size_opt);
} else {
TNode<Map> typed_array_map = a.LoadField<Map>(
AccessBuilder::ForMap(WriteBarrierKind::kNoWriteBarrier), view);
TNode<Uint32T> elements_kind = a.LoadElementsKind(typed_array_map);
element_size = a.LookupByteSizeForElementsKind(elements_kind);
}
// 2) Fixed length backed by RAB (can go oob once constructed)
auto RabFixed = [&]() {
TNode<UintPtrT> unchecked_byte_length = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferViewByteLength(), view,
UseInfo::Word());
TNode<UintPtrT> underlying_byte_length = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferByteLength(), buffer, UseInfo::Word());
TNode<UintPtrT> byte_offset = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferViewByteOffset(), view,
UseInfo::Word());
TNode<UintPtrT> byte_length =
a
.MachineSelectIf<UintPtrT>(a.UintPtrLessThanOrEqual(
a.UintPtrAdd(byte_offset, unchecked_byte_length),
underlying_byte_length))
.Then([&]() { return unchecked_byte_length; })
.Else([&]() { return a.UintPtrConstant(0); })
.Value();
return a.UintPtrDiv(byte_length,
TNode<UintPtrT>::UncheckedCast(element_size));
};
// 3) Length-tracking backed by RAB (JSArrayBuffer stores the length)
auto RabTracking = [&]() {
TNode<UintPtrT> byte_length = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferByteLength(), buffer, UseInfo::Word());
TNode<UintPtrT> byte_offset = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferViewByteOffset(), view,
UseInfo::Word());
return a
.MachineSelectIf<UintPtrT>(
a.UintPtrLessThanOrEqual(byte_offset, byte_length))
.Then([&]() {
// length = floor((byte_length - byte_offset) / element_size)
return a.UintPtrDiv(a.UintPtrSub(byte_length, byte_offset),
TNode<UintPtrT>::UncheckedCast(element_size));
})
.Else([&]() { return a.UintPtrConstant(0); })
.ExpectTrue()
.Value();
};
// 4) Length-tracking backed by GSAB (BackingStore stores the length)
auto GsabTracking = [&]() {
TNode<Number> temp = TNode<Number>::UncheckedCast(a.TypeGuard(
TypeCache::Get()->kJSArrayBufferViewByteLengthType,
a.JSCallRuntime1(Runtime::kGrowableSharedArrayBufferByteLength,
buffer, context, base::nullopt,
Operator::kNoWrite)));
TNode<UintPtrT> byte_length =
a.EnterMachineGraph<UintPtrT>(temp, UseInfo::Word());
TNode<UintPtrT> byte_offset = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferViewByteOffset(), view,
UseInfo::Word());
return a.UintPtrDiv(a.UintPtrSub(byte_length, byte_offset),
TNode<UintPtrT>::UncheckedCast(element_size));
};
return a.MachineSelectIf<UintPtrT>(length_tracking_bit)
.Then([&]() {
return a.MachineSelectIf<UintPtrT>(backed_by_rab_bit)
.Then(RabTracking)
.Else(GsabTracking)
.Value();
})
.Else([&]() {
return a.MachineSelectIf<UintPtrT>(backed_by_rab_bit)
.Then(RabFixed)
.Else(GsabFixedOrNormal)
.Value();
})
.Value();
}
TNode<UintPtrT> BuildByteLength(TNode<JSArrayBufferView> view,
TNode<Context> context) {
auto& a = *assembler_;
// Case 1: Normal (backed by AB/SAB) or non-length tracking backed by GSAB
// (can't go oob once constructed)
auto GsabFixedOrNormal = [&]() {
return MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferViewByteLength(), view,
UseInfo::Word());
};
// If we statically know we cannot have rab/gsab backed, we can simply
// use load from the view.
if (!maybe_rab_gsab()) {
return GsabFixedOrNormal();
}
// Otherwise, we need to generate the checks for the view's bitfield.
TNode<Word32T> bitfield = a.EnterMachineGraph<Word32T>(
a.LoadField<Word32T>(AccessBuilder::ForJSArrayBufferViewBitField(),
view),
UseInfo::TruncatingWord32());
TNode<Word32T> length_tracking_bit = a.Word32And(
bitfield, a.Uint32Constant(JSArrayBufferView::kIsLengthTracking));
TNode<Word32T> backed_by_rab_bit = a.Word32And(
bitfield, a.Uint32Constant(JSArrayBufferView::kIsBackedByRab));
// Load the underlying buffer.
TNode<HeapObject> buffer = a.LoadField<HeapObject>(
AccessBuilder::ForJSArrayBufferViewBuffer(), view);
// Case 2: Fixed length backed by RAB (can go oob once constructed)
auto RabFixed = [&]() {
TNode<UintPtrT> unchecked_byte_length = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferViewByteLength(), view,
UseInfo::Word());
TNode<UintPtrT> underlying_byte_length = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferByteLength(), buffer, UseInfo::Word());
TNode<UintPtrT> byte_offset = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferViewByteOffset(), view,
UseInfo::Word());
return a
.MachineSelectIf<UintPtrT>(a.UintPtrLessThanOrEqual(
a.UintPtrAdd(byte_offset, unchecked_byte_length),
underlying_byte_length))
.Then([&]() { return unchecked_byte_length; })
.Else([&]() { return a.UintPtrConstant(0); })
.Value();
};
auto RoundDownToElementSize = [&](TNode<UintPtrT> byte_size) {
if (auto shift_opt = TryComputeStaticElementShift()) {
constexpr uintptr_t all_bits = static_cast<uintptr_t>(-1);
if (*shift_opt == 0) return byte_size;
return TNode<UintPtrT>::UncheckedCast(
a.WordAnd(byte_size, a.UintPtrConstant(all_bits << (*shift_opt))));
}
TNode<Map> typed_array_map = a.LoadField<Map>(
AccessBuilder::ForMap(WriteBarrierKind::kNoWriteBarrier), view);
TNode<Uint32T> elements_kind = a.LoadElementsKind(typed_array_map);
TNode<Uint32T> element_shift =
a.LookupByteShiftForElementsKind(elements_kind);
return TNode<UintPtrT>::UncheckedCast(
a.WordShl(a.WordShr(byte_size, element_shift), element_shift));
};
// Case 3: Length-tracking backed by RAB (JSArrayBuffer stores the length)
auto RabTracking = [&]() {
TNode<UintPtrT> byte_length = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferByteLength(), buffer, UseInfo::Word());
TNode<UintPtrT> byte_offset = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferViewByteOffset(), view,
UseInfo::Word());
return a
.MachineSelectIf<UintPtrT>(
a.UintPtrLessThanOrEqual(byte_offset, byte_length))
.Then([&]() {
return RoundDownToElementSize(
a.UintPtrSub(byte_length, byte_offset));
})
.Else([&]() { return a.UintPtrConstant(0); })
.ExpectTrue()
.Value();
};
// Case 4: Length-tracking backed by GSAB (BackingStore stores the length)
auto GsabTracking = [&]() {
TNode<Number> temp = TNode<Number>::UncheckedCast(a.TypeGuard(
TypeCache::Get()->kJSArrayBufferViewByteLengthType,
a.JSCallRuntime1(Runtime::kGrowableSharedArrayBufferByteLength,
buffer, context, base::nullopt,
Operator::kNoWrite)));
TNode<UintPtrT> byte_length =
a.EnterMachineGraph<UintPtrT>(temp, UseInfo::Word());
TNode<UintPtrT> byte_offset = MachineLoadField<UintPtrT>(
AccessBuilder::ForJSArrayBufferViewByteOffset(), view,
UseInfo::Word());
return RoundDownToElementSize(a.UintPtrSub(byte_length, byte_offset));
};
return a.MachineSelectIf<UintPtrT>(length_tracking_bit)
.Then([&]() {
return a.MachineSelectIf<UintPtrT>(backed_by_rab_bit)
.Then(RabTracking)
.Else(GsabTracking)
.Value();
})
.Else([&]() {
return a.MachineSelectIf<UintPtrT>(backed_by_rab_bit)
.Then(RabFixed)
.Else(GsabFixedOrNormal)
.Value();
})
.Value();
}
private:
template <typename T>
TNode<T> MachineLoadField(FieldAccess const& access, TNode<HeapObject> object,
const UseInfo& use_info) {
return assembler_->EnterMachineGraph<T>(
assembler_->LoadField<T>(access, object), use_info);
}
JSGraphAssembler* assembler_;
std::set<ElementsKind> candidates_;
};
TNode<Number> JSGraphAssembler::ArrayBufferViewByteLength(
TNode<JSArrayBufferView> array_buffer_view,
std::set<ElementsKind> elements_kinds_candidates, TNode<Context> context) {
ArrayBufferViewAccessBuilder builder(this,
std::move(elements_kinds_candidates));
return ExitMachineGraph<Number>(
builder.BuildByteLength(array_buffer_view, context),
MachineType::PointerRepresentation(),
TypeCache::Get()->kJSArrayBufferByteLengthType);
}
TNode<Number> JSGraphAssembler::TypedArrayLength(
TNode<JSTypedArray> typed_array,
std::set<ElementsKind> elements_kinds_candidates, TNode<Context> context) {
ArrayBufferViewAccessBuilder builder(this, elements_kinds_candidates);
return ExitMachineGraph<Number>(builder.BuildLength(typed_array, context),
MachineType::PointerRepresentation(),
TypeCache::Get()->kJSTypedArrayLengthType);
}
TNode<Uint32T> JSGraphAssembler::LookupByteShiftForElementsKind(
TNode<Uint32T> elements_kind) {
TNode<Uint32T> index = TNode<Uint32T>::UncheckedCast(Int32Sub(
elements_kind, Uint32Constant(FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND)));
TNode<RawPtrT> shift_table = TNode<RawPtrT>::UncheckedCast(ExternalConstant(
ExternalReference::
typed_array_and_rab_gsab_typed_array_elements_kind_shifts()));
return TNode<Uint8T>::UncheckedCast(
Load(MachineType::Uint8(), shift_table, index));
}
TNode<Uint32T> JSGraphAssembler::LookupByteSizeForElementsKind(
TNode<Uint32T> elements_kind) {
TNode<Uint32T> index = TNode<Uint32T>::UncheckedCast(Int32Sub(
elements_kind, Uint32Constant(FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND)));
TNode<RawPtrT> size_table = TNode<RawPtrT>::UncheckedCast(ExternalConstant(
ExternalReference::
typed_array_and_rab_gsab_typed_array_elements_kind_sizes()));
return TNode<Uint8T>::UncheckedCast(
Load(MachineType::Uint8(), size_table, index));
}
TNode<Object> JSGraphAssembler::JSCallRuntime1(
Runtime::FunctionId function_id, TNode<Object> arg0, TNode<Context> context,
base::Optional<FrameState> frame_state, Operator::Properties properties) {
return MayThrow([&]() {
if (frame_state.has_value()) {
return AddNode<Object>(graph()->NewNode(
javascript()->CallRuntime(function_id, 1, properties), arg0, context,
static_cast<Node*>(*frame_state), effect(), control()));
} else {
return AddNode<Object>(graph()->NewNode(
javascript()->CallRuntime(function_id, 1, properties), arg0, context,
effect(), control()));
}
});
}
TNode<Object> JSGraphAssembler::JSCallRuntime2(Runtime::FunctionId function_id,
TNode<Object> arg0,
TNode<Object> arg1,
TNode<Context> context,
FrameState frame_state) {
return MayThrow([&]() {
return AddNode<Object>(
graph()->NewNode(javascript()->CallRuntime(function_id, 2), arg0, arg1,
context, frame_state, effect(), control()));
});
}
Node* GraphAssembler::TypeGuard(Type type, Node* value) {
return AddNode(
graph()->NewNode(common()->TypeGuard(type), value, effect(), control()));
@ -590,7 +1003,7 @@ void GraphAssembler::BranchWithCriticalSafetyCheck(
hint = if_false->IsDeferred() ? BranchHint::kTrue : BranchHint::kFalse;
}
BranchImpl(condition, if_true, if_false, hint);
BranchImpl(default_branch_semantics_, condition, if_true, if_false, hint);
}
void GraphAssembler::ConnectUnreachableToEnd() {

View File

@ -5,14 +5,17 @@
#ifndef V8_COMPILER_GRAPH_ASSEMBLER_H_
#define V8_COMPILER_GRAPH_ASSEMBLER_H_
#include <optional>
#include <type_traits>
#include "src/base/small-vector.h"
#include "src/codegen/tnode.h"
#include "src/common/globals.h"
#include "src/compiler/feedback-source.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node.h"
#include "src/compiler/simplified-operator.h"
#include "src/objects/oddball.h"
namespace v8 {
namespace internal {
@ -62,7 +65,7 @@ class Reducer;
V(Word32ReverseBytes) \
V(Word64ReverseBytes)
#define PURE_ASSEMBLER_MACH_BINOP_LIST(V) \
#define PURE_ASSEMBLER_MACH_BINOP_LIST(V, T) \
V(Float64Add) \
V(Float64Div) \
V(Float64Equal) \
@ -79,23 +82,24 @@ class Reducer;
V(Int32LessThanOrEqual) \
V(Int32Mul) \
V(Int32Sub) \
V(Int64Add) \
V(Int64Sub) \
V(IntAdd) \
V(IntLessThan) \
V(IntMul) \
V(IntSub) \
V(Uint32LessThan) \
V(Uint32LessThanOrEqual) \
T(Uint32LessThanOrEqual, BoolT, Uint32T, Uint32T) \
V(Uint64LessThan) \
V(Uint64LessThanOrEqual) \
T(Uint64LessThanOrEqual, BoolT, Uint64T, Uint64T) \
V(UintLessThan) \
V(Word32And) \
V(Word32Equal) \
T(Word32And, Word32T, Word32T, Word32T) \
T(Word32Equal, BoolT, Word32T, Word32T) \
V(Word32Or) \
V(Word32Sar) \
V(Word32SarShiftOutZeros) \
V(Word32Shl) \
V(Word32Shr) \
T(Word32Shr, Word32T, Word32T, Word32T) \
V(Word32Xor) \
V(Word64And) \
V(Word64Equal) \
@ -280,9 +284,11 @@ class V8_EXPORT_PRIVATE GraphAssembler {
// will maintain the schedule as it updates blocks.
GraphAssembler(
MachineGraph* jsgraph, Zone* zone,
BranchSemantics default_branch_semantics,
base::Optional<NodeChangedCallback> node_changed_callback = base::nullopt,
bool mark_loop_exits = false);
virtual ~GraphAssembler();
virtual SimplifiedOperatorBuilder* simplified() { UNREACHABLE(); }
void Reset();
void InitializeEffectControl(Node* effect, Node* control);
@ -322,9 +328,9 @@ class V8_EXPORT_PRIVATE GraphAssembler {
// Value creation.
Node* IntPtrConstant(intptr_t value);
Node* UintPtrConstant(uintptr_t value);
TNode<UintPtrT> UintPtrConstant(uintptr_t value);
Node* Int32Constant(int32_t value);
Node* Uint32Constant(uint32_t value);
TNode<Uint32T> Uint32Constant(uint32_t value);
Node* Int64Constant(int64_t value);
Node* Uint64Constant(uint64_t value);
Node* UniqueIntPtrConstant(intptr_t value);
@ -343,9 +349,17 @@ class V8_EXPORT_PRIVATE GraphAssembler {
#undef PURE_UNOP_DECL
#define BINOP_DECL(Name) Node* Name(Node* left, Node* right);
PURE_ASSEMBLER_MACH_BINOP_LIST(BINOP_DECL)
#define BINOP_DECL_TNODE(Name, Result, Left, Right) \
TNode<Result> Name(SloppyTNode<Left> left, SloppyTNode<Right> right);
PURE_ASSEMBLER_MACH_BINOP_LIST(BINOP_DECL, BINOP_DECL_TNODE)
CHECKED_ASSEMBLER_MACH_BINOP_LIST(BINOP_DECL)
#undef BINOP_DECL
#undef BINOP_DECL_TNODE
TNode<BoolT> UintPtrLessThanOrEqual(TNode<UintPtrT> left,
TNode<UintPtrT> right);
TNode<UintPtrT> UintPtrAdd(TNode<UintPtrT> left, TNode<UintPtrT> right);
TNode<UintPtrT> UintPtrSub(TNode<UintPtrT> left, TNode<UintPtrT> right);
TNode<UintPtrT> UintPtrDiv(TNode<UintPtrT> left, TNode<UintPtrT> right);
#ifdef V8_MAP_PACKING
Node* PackMapWord(TNode<Map> map);
@ -385,6 +399,10 @@ class V8_EXPORT_PRIVATE GraphAssembler {
Node* BitcastMaybeObjectToWord(Node* value);
Node* TypeGuard(Type type, Node* value);
template <typename T>
TNode<T> TypeGuard(Type type, TNode<T> value) {
return TNode<T>::UncheckedCast(TypeGuard(type, static_cast<Node*>(value)));
}
Node* Checkpoint(FrameState frame_state);
TNode<RawPtrT> StackSlot(int size, int alignment);
@ -443,6 +461,16 @@ class V8_EXPORT_PRIVATE GraphAssembler {
detail::GraphAssemblerLabelForVars<Vars...>* if_true,
detail::GraphAssemblerLabelForVars<Vars...>* if_false,
BranchHint hint, Vars...);
template <typename... Vars>
void MachineBranch(TNode<Word32T> condition,
GraphAssemblerLabel<sizeof...(Vars)>* if_true,
GraphAssemblerLabel<sizeof...(Vars)>* if_false,
BranchHint hint, Vars...);
template <typename... Vars>
void JSBranch(TNode<Boolean> condition,
GraphAssemblerLabel<sizeof...(Vars)>* if_true,
GraphAssemblerLabel<sizeof...(Vars)>* if_false, BranchHint hint,
Vars...);
// Control helpers.
@ -515,6 +543,8 @@ class V8_EXPORT_PRIVATE GraphAssembler {
Effect effect() const { return Effect(effect_); }
protected:
constexpr bool Is64() const { return kSystemPointerSize == 8; }
template <typename... Vars>
void MergeState(detail::GraphAssemblerLabelForVars<Vars...>* label,
Vars... vars);
@ -604,13 +634,14 @@ class V8_EXPORT_PRIVATE GraphAssembler {
class BlockInlineReduction;
template <typename... Vars>
void BranchImpl(Node* condition,
detail::GraphAssemblerLabelForVars<Vars...>* if_true,
detail::GraphAssemblerLabelForVars<Vars...>* if_false,
void BranchImpl(BranchSemantics semantics, Node* condition,
GraphAssemblerLabel<sizeof...(Vars)>* if_true,
GraphAssemblerLabel<sizeof...(Vars)>* if_false,
BranchHint hint, Vars...);
Zone* temp_zone_;
MachineGraph* mcgraph_;
BranchSemantics default_branch_semantics_;
Node* effect_;
Node* control_;
// {node_changed_callback_} should be called when a node outside the
@ -788,7 +819,8 @@ void GraphAssembler::Branch(
hint = if_false->IsDeferred() ? BranchHint::kTrue : BranchHint::kFalse;
}
BranchImpl(condition, if_true, if_false, hint, vars...);
BranchImpl(default_branch_semantics_, condition, if_true, if_false, hint,
vars...);
}
template <typename... Vars>
@ -796,17 +828,36 @@ void GraphAssembler::BranchWithHint(
Node* condition, detail::GraphAssemblerLabelForVars<Vars...>* if_true,
detail::GraphAssemblerLabelForVars<Vars...>* if_false, BranchHint hint,
Vars... vars) {
BranchImpl(condition, if_true, if_false, hint, vars...);
BranchImpl(default_branch_semantics_, condition, if_true, if_false, hint,
vars...);
}
template <typename... Vars>
void GraphAssembler::BranchImpl(
Node* condition, detail::GraphAssemblerLabelForVars<Vars...>* if_true,
detail::GraphAssemblerLabelForVars<Vars...>* if_false, BranchHint hint,
void GraphAssembler::MachineBranch(
TNode<Word32T> condition, GraphAssemblerLabel<sizeof...(Vars)>* if_true,
GraphAssemblerLabel<sizeof...(Vars)>* if_false, BranchHint hint,
Vars... vars) {
BranchImpl(BranchSemantics::kMachine, condition, if_true, if_false, hint,
vars...);
}
template <typename... Vars>
void GraphAssembler::JSBranch(TNode<Boolean> condition,
GraphAssemblerLabel<sizeof...(Vars)>* if_true,
GraphAssemblerLabel<sizeof...(Vars)>* if_false,
BranchHint hint, Vars... vars) {
BranchImpl(BranchSemantics::kJS, condition, if_true, if_false, hint, vars...);
}
template <typename... Vars>
void GraphAssembler::BranchImpl(BranchSemantics semantics, Node* condition,
GraphAssemblerLabel<sizeof...(Vars)>* if_true,
GraphAssemblerLabel<sizeof...(Vars)>* if_false,
BranchHint hint, Vars... vars) {
DCHECK_NOT_NULL(control());
Node* branch = graph()->NewNode(common()->Branch(hint), condition, control());
Node* branch =
graph()->NewNode(common()->Branch(hint, semantics), condition, control());
control_ = graph()->NewNode(common()->IfTrue(), branch);
MergeState(if_true, vars...);
@ -833,7 +884,8 @@ template <typename... Vars>
void GraphAssembler::GotoIf(Node* condition,
detail::GraphAssemblerLabelForVars<Vars...>* label,
BranchHint hint, Vars... vars) {
Node* branch = graph()->NewNode(common()->Branch(hint), condition, control());
Node* branch = graph()->NewNode(
common()->Branch(hint, default_branch_semantics_), condition, control());
control_ = graph()->NewNode(common()->IfTrue(), branch);
MergeState(label, vars...);
@ -845,7 +897,8 @@ template <typename... Vars>
void GraphAssembler::GotoIfNot(
Node* condition, detail::GraphAssemblerLabelForVars<Vars...>* label,
BranchHint hint, Vars... vars) {
Node* branch = graph()->NewNode(common()->Branch(hint), condition, control());
Node* branch = graph()->NewNode(
common()->Branch(hint, default_branch_semantics_), condition, control());
control_ = graph()->NewNode(common()->IfFalse(), branch);
MergeState(label, vars...);
@ -891,11 +944,16 @@ class V8_EXPORT_PRIVATE JSGraphAssembler : public GraphAssembler {
// Constructs a JSGraphAssembler. If {schedule} is not null, the graph
// assembler will maintain the schedule as it updates blocks.
JSGraphAssembler(
JSGraph* jsgraph, Zone* zone,
JSGraph* jsgraph, Zone* zone, BranchSemantics branch_semantics,
base::Optional<NodeChangedCallback> node_changed_callback = base::nullopt,
bool mark_loop_exits = false)
: GraphAssembler(jsgraph, zone, node_changed_callback, mark_loop_exits),
jsgraph_(jsgraph) {}
: GraphAssembler(jsgraph, zone, branch_semantics, node_changed_callback,
mark_loop_exits),
jsgraph_(jsgraph),
outermost_catch_scope_(CatchScope::Outermost(zone)),
catch_scope_(&outermost_catch_scope_) {
outermost_catch_scope_.set_gasm(this);
}
Node* SmiConstant(int32_t value);
TNode<HeapObject> HeapConstant(Handle<HeapObject> object);
@ -922,6 +980,7 @@ class V8_EXPORT_PRIVATE JSGraphAssembler : public GraphAssembler {
// access.machine_type.representation()));
return TNode<T>::UncheckedCast(LoadField(access, object));
}
TNode<Uint32T> LoadElementsKind(TNode<Map> map);
Node* LoadElement(ElementAccess const&, Node* object, Node* index);
template <typename T>
TNode<T> LoadElement(ElementAccess const& access, TNode<HeapObject> object,
@ -949,6 +1008,8 @@ class V8_EXPORT_PRIVATE JSGraphAssembler : public GraphAssembler {
TNode<Number> NumberSubtract(TNode<Number> lhs, TNode<Number> rhs);
TNode<Number> NumberShiftRightLogical(TNode<Number> lhs, TNode<Number> rhs);
TNode<Number> NumberBitwiseAnd(TNode<Number> lhs, TNode<Number> rhs);
TNode<Number> NumberDivide(TNode<Number> lhs, TNode<Number> rhs);
TNode<Number> NumberFloor(TNode<Number> value);
TNode<String> StringSubstring(TNode<String> string, TNode<Number> from,
TNode<Number> to);
TNode<Boolean> ObjectIsCallable(TNode<Object> value);
@ -967,12 +1028,349 @@ class V8_EXPORT_PRIVATE JSGraphAssembler : public GraphAssembler {
Node* StringCharCodeAt(TNode<String> string, TNode<Number> position);
TNode<Object> DoubleArrayMax(TNode<JSArray> array);
TNode<Object> DoubleArrayMin(TNode<JSArray> array);
// Computes the byte length for a given {array_buffer_view}. If the set of
// possible ElementsKinds is known statically pass as
// {elements_kinds_candidates} to allow the assembler to generate more
// efficient code. Pass an empty {elements_kinds_candidates} to generate code
// that is generic enough to handle all ElementsKinds.
TNode<Number> ArrayBufferViewByteLength(
TNode<JSArrayBufferView> array_buffer_view,
std::set<ElementsKind> elements_kinds_candidates, TNode<Context> context);
// Computes the length for a given {typed_array}. If the set of possible
// ElementsKinds is known statically pass as {elements_kinds_candidates} to
// allow the assembler to generate more efficient code. Pass an empty
// {elements_kinds_candidates} to generate code that is generic enough to
// handle all ElementsKinds.
TNode<Number> TypedArrayLength(
TNode<JSTypedArray> typed_array,
std::set<ElementsKind> elements_kinds_candidates, TNode<Context> context);
TNode<Uint32T> LookupByteShiftForElementsKind(TNode<Uint32T> elements_kind);
TNode<Uint32T> LookupByteSizeForElementsKind(TNode<Uint32T> elements_kind);
TNode<Object> JSCallRuntime1(
Runtime::FunctionId function_id, TNode<Object> arg0,
TNode<Context> context, base::Optional<FrameState> frame_state,
Operator::Properties properties = Operator::kNoProperties);
TNode<Object> JSCallRuntime2(Runtime::FunctionId function_id,
TNode<Object> arg0, TNode<Object> arg1,
TNode<Context> context, FrameState frame_state);
JSGraph* jsgraph() const { return jsgraph_; }
Isolate* isolate() const { return jsgraph()->isolate(); }
SimplifiedOperatorBuilder* simplified() const {
SimplifiedOperatorBuilder* simplified() override {
return jsgraph()->simplified();
}
JSOperatorBuilder* javascript() const { return jsgraph()->javascript(); }
template <typename T, typename U>
TNode<T> EnterMachineGraph(TNode<U> input, UseInfo use_info) {
DCHECK_EQ(use_info.type_check(), TypeCheckKind::kNone);
return AddNode<T>(
graph()->NewNode(common()->EnterMachineGraph(use_info), input));
}
template <typename T, typename U>
TNode<T> ExitMachineGraph(TNode<U> input,
MachineRepresentation output_representation,
Type output_type) {
return AddNode<T>(graph()->NewNode(
common()->ExitMachineGraph(output_representation, output_type), input));
}
// A catch scope represents a single catch handler. The handler can be
// custom catch logic within the reduction itself; or a catch handler in the
// outside graph into which the reduction will be integrated (in this case
// the scope is called 'outermost').
class V8_NODISCARD CatchScope {
private:
// Only used to partially construct the outermost scope.
explicit CatchScope(Zone* zone) : if_exception_nodes_(zone) {}
// For all inner scopes.
CatchScope(Zone* zone, JSGraphAssembler* gasm)
: gasm_(gasm),
parent_(gasm->catch_scope_),
has_handler_(true),
if_exception_nodes_(zone) {
DCHECK_NOT_NULL(gasm_);
gasm_->catch_scope_ = this;
}
public:
~CatchScope() { gasm_->catch_scope_ = parent_; }
static CatchScope Outermost(Zone* zone) { return CatchScope{zone}; }
static CatchScope Inner(Zone* zone, JSGraphAssembler* gasm) {
return {zone, gasm};
}
bool has_handler() const { return has_handler_; }
bool is_outermost() const { return parent_ == nullptr; }
CatchScope* parent() const { return parent_; }
// Should only be used to initialize the outermost scope (inner scopes
// always have a handler and are passed the gasm pointer at construction).
void set_has_handler(bool v) {
DCHECK(is_outermost());
has_handler_ = v;
}
void set_gasm(JSGraphAssembler* v) {
DCHECK(is_outermost());
DCHECK_NOT_NULL(v);
gasm_ = v;
}
bool has_exceptional_control_flow() const {
return !if_exception_nodes_.empty();
}
void RegisterIfExceptionNode(Node* if_exception) {
DCHECK(has_handler());
if_exception_nodes_.push_back(if_exception);
}
void MergeExceptionalPaths(TNode<Object>* exception_out, Effect* effect_out,
Control* control_out) {
DCHECK(has_handler());
DCHECK(has_exceptional_control_flow());
const int size = static_cast<int>(if_exception_nodes_.size());
if (size == 1) {
// No merge needed.
Node* e = if_exception_nodes_.at(0);
*exception_out = TNode<Object>::UncheckedCast(e);
*effect_out = Effect(e);
*control_out = Control(e);
} else {
DCHECK_GT(size, 1);
Node* merge = gasm_->graph()->NewNode(gasm_->common()->Merge(size),
size, if_exception_nodes_.data());
// These phis additionally take {merge} as an input. Temporarily add
// it to the list.
if_exception_nodes_.push_back(merge);
const int size_with_merge =
static_cast<int>(if_exception_nodes_.size());
Node* ephi = gasm_->graph()->NewNode(gasm_->common()->EffectPhi(size),
size_with_merge,
if_exception_nodes_.data());
Node* phi = gasm_->graph()->NewNode(
gasm_->common()->Phi(MachineRepresentation::kTagged, size),
size_with_merge, if_exception_nodes_.data());
if_exception_nodes_.pop_back();
*exception_out = TNode<Object>::UncheckedCast(phi);
*effect_out = Effect(ephi);
*control_out = Control(merge);
}
}
private:
JSGraphAssembler* gasm_ = nullptr;
CatchScope* const parent_ = nullptr;
bool has_handler_ = false;
NodeVector if_exception_nodes_;
};
CatchScope* catch_scope() const { return catch_scope_; }
Node* outermost_handler() const { return outermost_handler_; }
using NodeGenerator0 = std::function<TNode<Object>()>;
// TODO(jgruber): Currently, it's the responsibility of the developer to note
// which operations may throw and appropriately wrap these in a call to
// MayThrow (see e.g. JSCall3 and CallRuntime2). A more methodical approach
// would be good.
TNode<Object> MayThrow(const NodeGenerator0& body) {
TNode<Object> result = body();
if (catch_scope()->has_handler()) {
// The IfException node is later merged into the outer graph.
// Note: AddNode is intentionally not called since effect and control
// should not be updated.
Node* if_exception =
graph()->NewNode(common()->IfException(), effect(), control());
catch_scope()->RegisterIfExceptionNode(if_exception);
// Control resumes here.
AddNode(graph()->NewNode(common()->IfSuccess(), control()));
}
return result;
}
using VoidGenerator0 = std::function<void()>;
// TODO(jgruber): Currently IfBuilder0 and IfBuilder1 are implemented as
// separate classes. If, in the future, we encounter additional use cases that
// return more than 1 value, we should merge these back into a single variadic
// implementation.
class IfBuilder0 final {
public:
IfBuilder0(JSGraphAssembler* gasm, TNode<Boolean> cond, bool negate_cond)
: gasm_(gasm),
cond_(cond),
negate_cond_(negate_cond),
initial_effect_(gasm->effect()),
initial_control_(gasm->control()) {}
IfBuilder0& ExpectTrue() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kTrue;
return *this;
}
IfBuilder0& ExpectFalse() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kFalse;
return *this;
}
IfBuilder0& Then(const VoidGenerator0& body) {
then_body_ = body;
return *this;
}
IfBuilder0& Else(const VoidGenerator0& body) {
else_body_ = body;
return *this;
}
~IfBuilder0() {
// Ensure correct usage: effect/control must not have been modified while
// the IfBuilder0 instance is alive.
DCHECK_EQ(gasm_->effect(), initial_effect_);
DCHECK_EQ(gasm_->control(), initial_control_);
// Unlike IfBuilder1, this supports an empty then or else body. This is
// possible since the merge does not take any value inputs.
DCHECK(then_body_ || else_body_);
if (negate_cond_) std::swap(then_body_, else_body_);
auto if_true = (hint_ == BranchHint::kFalse) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto if_false = (hint_ == BranchHint::kTrue) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto merge = gasm_->MakeLabel();
gasm_->Branch(cond_, &if_true, &if_false);
gasm_->Bind(&if_true);
if (then_body_) then_body_();
if (gasm_->HasActiveBlock()) gasm_->Goto(&merge);
gasm_->Bind(&if_false);
if (else_body_) else_body_();
if (gasm_->HasActiveBlock()) gasm_->Goto(&merge);
gasm_->Bind(&merge);
}
IfBuilder0(const IfBuilder0&) = delete;
IfBuilder0& operator=(const IfBuilder0&) = delete;
private:
JSGraphAssembler* const gasm_;
const TNode<Boolean> cond_;
const bool negate_cond_;
const Effect initial_effect_;
const Control initial_control_;
BranchHint hint_ = BranchHint::kNone;
VoidGenerator0 then_body_;
VoidGenerator0 else_body_;
};
IfBuilder0 If(TNode<Boolean> cond) { return {this, cond, false}; }
IfBuilder0 IfNot(TNode<Boolean> cond) { return {this, cond, true}; }
template <typename T, typename Cond>
class IfBuilder1 {
using If1BodyFunction = std::function<TNode<T>()>;
public:
IfBuilder1(JSGraphAssembler* gasm, TNode<Cond> cond, bool negate_cond)
: gasm_(gasm), cond_(cond), negate_cond_(negate_cond) {}
V8_WARN_UNUSED_RESULT IfBuilder1& ExpectTrue() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kTrue;
return *this;
}
V8_WARN_UNUSED_RESULT IfBuilder1& ExpectFalse() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kFalse;
return *this;
}
V8_WARN_UNUSED_RESULT IfBuilder1& Then(const If1BodyFunction& body) {
then_body_ = body;
return *this;
}
V8_WARN_UNUSED_RESULT IfBuilder1& Else(const If1BodyFunction& body) {
else_body_ = body;
return *this;
}
V8_WARN_UNUSED_RESULT TNode<T> Value() {
DCHECK(then_body_);
DCHECK(else_body_);
if (negate_cond_) std::swap(then_body_, else_body_);
auto if_true = (hint_ == BranchHint::kFalse) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto if_false = (hint_ == BranchHint::kTrue) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto merge = gasm_->MakeLabel(PhiMachineRepresentationOf<T>);
if constexpr (std::is_same_v<Cond, Word32T>) {
gasm_->MachineBranch(cond_, &if_true, &if_false, hint_);
} else {
static_assert(std::is_same_v<Cond, Boolean>);
if (hint_ != BranchHint::kNone) {
gasm_->BranchWithHint(cond_, &if_true, &if_false, hint_);
} else {
gasm_->Branch(cond_, &if_true, &if_false);
}
}
gasm_->Bind(&if_true);
TNode<T> then_result = then_body_();
if (gasm_->HasActiveBlock()) gasm_->Goto(&merge, then_result);
gasm_->Bind(&if_false);
TNode<T> else_result = else_body_();
if (gasm_->HasActiveBlock()) {
gasm_->Goto(&merge, else_result);
}
gasm_->Bind(&merge);
return merge.template PhiAt<T>(0);
}
private:
static constexpr MachineRepresentation kPhiRepresentation =
MachineRepresentation::kTagged;
JSGraphAssembler* const gasm_;
const TNode<Cond> cond_;
const bool negate_cond_;
BranchHint hint_ = BranchHint::kNone;
If1BodyFunction then_body_;
If1BodyFunction else_body_;
};
template <typename T>
IfBuilder1<T, Boolean> SelectIf(TNode<Boolean> cond) {
return {this, cond, false};
}
template <typename T>
IfBuilder1<T, Boolean> SelectIfNot(TNode<Boolean> cond) {
return {this, cond, true};
}
template <typename T>
IfBuilder1<T, Word32T> MachineSelectIf(TNode<Word32T> cond) {
return {this, cond, false};
}
protected:
Operator const* PlainPrimitiveToNumberOperator();
@ -980,6 +1378,12 @@ class V8_EXPORT_PRIVATE JSGraphAssembler : public GraphAssembler {
private:
JSGraph* jsgraph_;
SetOncePointer<Operator const> to_number_operator_;
protected:
CatchScope outermost_catch_scope_;
Node* outermost_handler_;
CatchScope* catch_scope_;
friend class CatchScope;
};
} // namespace compiler

View File

@ -4,6 +4,8 @@
#include "src/compiler/heap-refs.h"
#include "src/objects/elements-kind.h"
#ifdef ENABLE_SLOW_DCHECKS
#include <algorithm>
#endif
@ -1079,6 +1081,11 @@ bool MapRef::CanInlineElementAccess() const {
kind != BIGINT64_ELEMENTS) {
return true;
}
if (v8_flags.turbo_rab_gsab && IsRabGsabTypedArrayElementsKind(kind) &&
kind != RAB_GSAB_BIGUINT64_ELEMENTS &&
kind != RAB_GSAB_BIGINT64_ELEMENTS) {
return true;
}
return false;
}

View File

@ -6,6 +6,7 @@
#include <functional>
#include "src/base/container-utils.h"
#include "src/base/small-vector.h"
#include "src/builtins/builtins-promise.h"
#include "src/builtins/builtins-utils.h"
@ -28,8 +29,11 @@
#include "src/compiler/simplified-operator.h"
#include "src/compiler/state-values-utils.h"
#include "src/compiler/type-cache.h"
#include "src/compiler/use-info.h"
#include "src/flags/flags.h"
#include "src/ic/call-optimization.h"
#include "src/objects/elements-kind.h"
#include "src/objects/instance-type.h"
#include "src/objects/js-function.h"
#include "src/objects/objects-inl.h"
#include "src/objects/ordered-hash-table.h"
@ -46,32 +50,26 @@ namespace compiler {
#define _ [&]()
class JSCallReducerAssembler : public JSGraphAssembler {
protected:
class CatchScope;
private:
static constexpr bool kMarkLoopExits = true;
public:
JSCallReducerAssembler(JSCallReducer* reducer, Node* node)
JSCallReducerAssembler(JSCallReducer* reducer, Node* node,
Node* effect = nullptr, Node* control = nullptr)
: JSGraphAssembler(
reducer->JSGraphForGraphAssembler(),
reducer->ZoneForGraphAssembler(),
reducer->ZoneForGraphAssembler(), BranchSemantics::kJS,
[reducer](Node* n) { reducer->RevisitForGraphAssembler(n); },
kMarkLoopExits),
dependencies_(reducer->dependencies()),
node_(node),
outermost_catch_scope_(
CatchScope::Outermost(reducer->ZoneForGraphAssembler())),
catch_scope_(&outermost_catch_scope_) {
InitializeEffectControl(NodeProperties::GetEffectInput(node),
NodeProperties::GetControlInput(node));
node_(node) {
InitializeEffectControl(
effect ? effect : NodeProperties::GetEffectInput(node),
control ? control : NodeProperties::GetControlInput(node));
// Finish initializing the outermost catch scope.
bool has_handler =
NodeProperties::IsExceptionalCall(node, &outermost_handler_);
outermost_catch_scope_.set_has_handler(has_handler);
outermost_catch_scope_.set_gasm(this);
}
TNode<Object> ReduceJSCallWithArrayLikeOrSpreadOfEmpty(
@ -94,164 +92,8 @@ class JSCallReducerAssembler : public JSGraphAssembler {
TNode<Object> ReceiverInput() const { return ReceiverInputAs<Object>(); }
CatchScope* catch_scope() const { return catch_scope_; }
Node* outermost_handler() const { return outermost_handler_; }
Node* node_ptr() const { return node_; }
protected:
using NodeGenerator0 = std::function<TNode<Object>()>;
using VoidGenerator0 = std::function<void()>;
// TODO(jgruber): Currently IfBuilder0 and IfBuilder1 are implemented as
// separate classes. If, in the future, we encounter additional use cases that
// return more than 1 value, we should merge these back into a single variadic
// implementation.
class IfBuilder0 final {
public:
IfBuilder0(JSGraphAssembler* gasm, TNode<Boolean> cond, bool negate_cond)
: gasm_(gasm),
cond_(cond),
negate_cond_(negate_cond),
initial_effect_(gasm->effect()),
initial_control_(gasm->control()) {}
IfBuilder0& ExpectTrue() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kTrue;
return *this;
}
IfBuilder0& ExpectFalse() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kFalse;
return *this;
}
IfBuilder0& Then(const VoidGenerator0& body) {
then_body_ = body;
return *this;
}
IfBuilder0& Else(const VoidGenerator0& body) {
else_body_ = body;
return *this;
}
~IfBuilder0() {
// Ensure correct usage: effect/control must not have been modified while
// the IfBuilder0 instance is alive.
DCHECK_EQ(gasm_->effect(), initial_effect_);
DCHECK_EQ(gasm_->control(), initial_control_);
// Unlike IfBuilder1, this supports an empty then or else body. This is
// possible since the merge does not take any value inputs.
DCHECK(then_body_ || else_body_);
if (negate_cond_) std::swap(then_body_, else_body_);
auto if_true = (hint_ == BranchHint::kFalse) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto if_false = (hint_ == BranchHint::kTrue) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto merge = gasm_->MakeLabel();
gasm_->Branch(cond_, &if_true, &if_false);
gasm_->Bind(&if_true);
if (then_body_) then_body_();
if (gasm_->HasActiveBlock()) gasm_->Goto(&merge);
gasm_->Bind(&if_false);
if (else_body_) else_body_();
if (gasm_->HasActiveBlock()) gasm_->Goto(&merge);
gasm_->Bind(&merge);
}
IfBuilder0(const IfBuilder0&) = delete;
IfBuilder0& operator=(const IfBuilder0&) = delete;
private:
JSGraphAssembler* const gasm_;
const TNode<Boolean> cond_;
const bool negate_cond_;
const Effect initial_effect_;
const Control initial_control_;
BranchHint hint_ = BranchHint::kNone;
VoidGenerator0 then_body_;
VoidGenerator0 else_body_;
};
IfBuilder0 If(TNode<Boolean> cond) { return {this, cond, false}; }
IfBuilder0 IfNot(TNode<Boolean> cond) { return {this, cond, true}; }
template <typename T>
class IfBuilder1 {
using If1BodyFunction = std::function<TNode<T>()>;
public:
IfBuilder1(JSGraphAssembler* gasm, TNode<Boolean> cond)
: gasm_(gasm), cond_(cond) {}
V8_WARN_UNUSED_RESULT IfBuilder1& ExpectTrue() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kTrue;
return *this;
}
V8_WARN_UNUSED_RESULT IfBuilder1& ExpectFalse() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kFalse;
return *this;
}
V8_WARN_UNUSED_RESULT IfBuilder1& Then(const If1BodyFunction& body) {
then_body_ = body;
return *this;
}
V8_WARN_UNUSED_RESULT IfBuilder1& Else(const If1BodyFunction& body) {
else_body_ = body;
return *this;
}
V8_WARN_UNUSED_RESULT TNode<T> Value() {
DCHECK(then_body_);
DCHECK(else_body_);
auto if_true = (hint_ == BranchHint::kFalse) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto if_false = (hint_ == BranchHint::kTrue) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto merge = gasm_->MakeLabel(kPhiRepresentation);
gasm_->Branch(cond_, &if_true, &if_false);
gasm_->Bind(&if_true);
TNode<T> then_result = then_body_();
if (gasm_->HasActiveBlock()) gasm_->Goto(&merge, then_result);
gasm_->Bind(&if_false);
TNode<T> else_result = else_body_();
if (gasm_->HasActiveBlock()) {
gasm_->Goto(&merge, else_result);
}
gasm_->Bind(&merge);
return merge.PhiAt<T>(0);
}
private:
static constexpr MachineRepresentation kPhiRepresentation =
MachineRepresentation::kTagged;
JSGraphAssembler* const gasm_;
const TNode<Boolean> cond_;
BranchHint hint_ = BranchHint::kNone;
If1BodyFunction then_body_;
If1BodyFunction else_body_;
};
template <typename T>
IfBuilder1<T> SelectIf(TNode<Boolean> cond) {
return {this, cond};
}
// Simplified operators.
TNode<Number> SpeculativeToNumber(
TNode<Object> value,
@ -277,9 +119,6 @@ class JSCallReducerAssembler : public JSGraphAssembler {
TNode<Object> arg0, TNode<Object> arg1,
TNode<Object> arg2, TNode<Object> arg3,
FrameState frame_state);
TNode<Object> JSCallRuntime2(Runtime::FunctionId function_id,
TNode<Object> arg0, TNode<Object> arg1,
FrameState frame_state);
// Emplace a copy of the call node into the graph at current effect/control.
TNode<Object> CopyNode();
@ -297,6 +136,20 @@ class JSCallReducerAssembler : public JSGraphAssembler {
TNode<Number> LoadMapElementsKind(TNode<Map> map);
template <typename T, typename U>
TNode<T> EnterMachineGraph(TNode<U> input, UseInfo use_info) {
return AddNode<T>(
graph()->NewNode(common()->EnterMachineGraph(use_info), input));
}
template <typename T, typename U>
TNode<T> ExitMachineGraph(TNode<U> input,
MachineRepresentation output_representation,
Type output_type) {
return AddNode<T>(graph()->NewNode(
common()->ExitMachineGraph(output_representation, output_type), input));
}
void MaybeInsertMapChecks(MapInference* inference,
bool has_stability_dependency) {
// TODO(jgruber): Implement MapInference::InsertMapChecks in graph
@ -308,124 +161,6 @@ class JSCallReducerAssembler : public JSGraphAssembler {
}
}
// TODO(jgruber): Currently, it's the responsibility of the developer to note
// which operations may throw and appropriately wrap these in a call to
// MayThrow (see e.g. JSCall3 and CallRuntime2). A more methodical approach
// would be good.
TNode<Object> MayThrow(const NodeGenerator0& body) {
TNode<Object> result = body();
if (catch_scope()->has_handler()) {
// The IfException node is later merged into the outer graph.
// Note: AddNode is intentionally not called since effect and control
// should not be updated.
Node* if_exception =
graph()->NewNode(common()->IfException(), effect(), control());
catch_scope()->RegisterIfExceptionNode(if_exception);
// Control resumes here.
AddNode(graph()->NewNode(common()->IfSuccess(), control()));
}
return result;
}
// A catch scope represents a single catch handler. The handler can be
// custom catch logic within the reduction itself; or a catch handler in the
// outside graph into which the reduction will be integrated (in this case
// the scope is called 'outermost').
class V8_NODISCARD CatchScope {
private:
// Only used to partially construct the outermost scope.
explicit CatchScope(Zone* zone) : if_exception_nodes_(zone) {}
// For all inner scopes.
CatchScope(Zone* zone, JSCallReducerAssembler* gasm)
: gasm_(gasm),
parent_(gasm->catch_scope_),
has_handler_(true),
if_exception_nodes_(zone) {
gasm_->catch_scope_ = this;
}
public:
~CatchScope() { gasm_->catch_scope_ = parent_; }
static CatchScope Outermost(Zone* zone) { return CatchScope{zone}; }
static CatchScope Inner(Zone* zone, JSCallReducerAssembler* gasm) {
return {zone, gasm};
}
bool has_handler() const { return has_handler_; }
bool is_outermost() const { return parent_ == nullptr; }
CatchScope* parent() const { return parent_; }
// Should only be used to initialize the outermost scope (inner scopes
// always have a handler and are passed the gasm pointer at construction).
void set_has_handler(bool v) {
DCHECK(is_outermost());
has_handler_ = v;
}
void set_gasm(JSCallReducerAssembler* v) {
DCHECK(is_outermost());
gasm_ = v;
}
bool has_exceptional_control_flow() const {
return !if_exception_nodes_.empty();
}
void RegisterIfExceptionNode(Node* if_exception) {
DCHECK(has_handler());
if_exception_nodes_.push_back(if_exception);
}
void MergeExceptionalPaths(TNode<Object>* exception_out, Effect* effect_out,
Control* control_out) {
DCHECK(has_handler());
DCHECK(has_exceptional_control_flow());
const int size = static_cast<int>(if_exception_nodes_.size());
if (size == 1) {
// No merge needed.
Node* e = if_exception_nodes_.at(0);
*exception_out = TNode<Object>::UncheckedCast(e);
*effect_out = Effect(e);
*control_out = Control(e);
} else {
DCHECK_GT(size, 1);
Node* merge = gasm_->graph()->NewNode(gasm_->common()->Merge(size),
size, if_exception_nodes_.data());
// These phis additionally take {merge} as an input. Temporarily add
// it to the list.
if_exception_nodes_.push_back(merge);
const int size_with_merge =
static_cast<int>(if_exception_nodes_.size());
Node* ephi = gasm_->graph()->NewNode(gasm_->common()->EffectPhi(size),
size_with_merge,
if_exception_nodes_.data());
Node* phi = gasm_->graph()->NewNode(
gasm_->common()->Phi(MachineRepresentation::kTagged, size),
size_with_merge, if_exception_nodes_.data());
if_exception_nodes_.pop_back();
*exception_out = TNode<Object>::UncheckedCast(phi);
*effect_out = Effect(ephi);
*control_out = Control(merge);
}
}
private:
JSCallReducerAssembler* gasm_ = nullptr;
CatchScope* const parent_ = nullptr;
bool has_handler_ = false;
NodeVector if_exception_nodes_;
};
class TryCatchBuilder0 {
public:
using TryFunction = VoidGenerator0;
@ -616,7 +351,7 @@ class JSCallReducerAssembler : public JSGraphAssembler {
JSCallRuntime2(Runtime::kThrowTypeError,
NumberConstant(static_cast<double>(
MessageTemplate::kCalledNonCallable)),
maybe_callable, frame_state);
maybe_callable, ContextInput(), frame_state);
Unreachable(); // The runtime call throws unconditionally.
})
.ExpectTrue();
@ -662,17 +397,11 @@ class JSCallReducerAssembler : public JSGraphAssembler {
return FrameState(NodeProperties::GetFrameStateInput(node_));
}
JSOperatorBuilder* javascript() const { return jsgraph()->javascript(); }
CompilationDependencies* dependencies() const { return dependencies_; }
private:
CompilationDependencies* const dependencies_;
Node* const node_;
CatchScope outermost_catch_scope_;
Node* outermost_handler_;
CatchScope* catch_scope_;
friend class CatchScope;
};
enum class ArrayReduceDirection { kLeft, kRight };
@ -1113,16 +842,6 @@ TNode<Object> JSCallReducerAssembler::JSCall4(
});
}
TNode<Object> JSCallReducerAssembler::JSCallRuntime2(
Runtime::FunctionId function_id, TNode<Object> arg0, TNode<Object> arg1,
FrameState frame_state) {
return MayThrow(_ {
return AddNode<Object>(
graph()->NewNode(javascript()->CallRuntime(function_id, 2), arg0, arg1,
ContextInput(), frame_state, effect(), control()));
});
}
TNode<Object> JSCallReducerAssembler::CopyNode() {
return MayThrow(_ {
Node* copy = graph()->CloneNode(node_ptr());
@ -1138,6 +857,7 @@ TNode<JSArray> JSCallReducerAssembler::CreateArrayNoThrow(
graph()->NewNode(javascript()->CreateArray(1, base::nullopt), ctor, ctor,
size, ContextInput(), frame_state, effect(), control()));
}
TNode<JSArray> JSCallReducerAssembler::AllocateEmptyJSArray(
ElementsKind kind, const NativeContextRef& native_context) {
// TODO(jgruber): Port AllocationBuilder to JSGraphAssembler.
@ -2578,6 +2298,26 @@ TNode<Object> PromiseBuiltinReducerAssembler::ReducePromiseConstructor(
#undef _
std::pair<Node*, Node*> JSCallReducer::ReleaseEffectAndControlFromAssembler(
JSCallReducerAssembler* gasm) {
auto catch_scope = gasm->catch_scope();
DCHECK(catch_scope->is_outermost());
if (catch_scope->has_handler() &&
catch_scope->has_exceptional_control_flow()) {
TNode<Object> handler_exception;
Effect handler_effect{nullptr};
Control handler_control{nullptr};
gasm->catch_scope()->MergeExceptionalPaths(
&handler_exception, &handler_effect, &handler_control);
ReplaceWithValue(gasm->outermost_handler(), handler_exception,
handler_effect, handler_control);
}
return {gasm->effect(), gasm->control()};
}
Reduction JSCallReducer::ReplaceWithSubgraph(JSCallReducerAssembler* gasm,
Node* subgraph) {
// TODO(jgruber): Consider a less fiddly way of integrating the new subgraph
@ -4898,13 +4638,11 @@ Reduction JSCallReducer::ReduceJSCall(Node* node,
case Builtin::kArrayBufferIsView:
return ReduceArrayBufferIsView(node);
case Builtin::kDataViewPrototypeGetByteLength:
return ReduceArrayBufferViewAccessor(
node, JS_DATA_VIEW_TYPE,
AccessBuilder::ForJSArrayBufferViewByteLength());
return ReduceArrayBufferViewByteLengthAccessor(node, JS_DATA_VIEW_TYPE);
case Builtin::kDataViewPrototypeGetByteOffset:
return ReduceArrayBufferViewAccessor(
node, JS_DATA_VIEW_TYPE,
AccessBuilder::ForJSArrayBufferViewByteOffset());
AccessBuilder::ForJSArrayBufferViewByteOffset(), builtin);
case Builtin::kDataViewPrototypeGetUint8:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalUint8Array);
@ -4954,16 +4692,13 @@ Reduction JSCallReducer::ReduceJSCall(Node* node,
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalFloat64Array);
case Builtin::kTypedArrayPrototypeByteLength:
return ReduceArrayBufferViewAccessor(
node, JS_TYPED_ARRAY_TYPE,
AccessBuilder::ForJSArrayBufferViewByteLength());
return ReduceArrayBufferViewByteLengthAccessor(node, JS_TYPED_ARRAY_TYPE);
case Builtin::kTypedArrayPrototypeByteOffset:
return ReduceArrayBufferViewAccessor(
node, JS_TYPED_ARRAY_TYPE,
AccessBuilder::ForJSArrayBufferViewByteOffset());
AccessBuilder::ForJSArrayBufferViewByteOffset(), builtin);
case Builtin::kTypedArrayPrototypeLength:
return ReduceArrayBufferViewAccessor(
node, JS_TYPED_ARRAY_TYPE, AccessBuilder::ForJSTypedArrayLength());
return ReduceTypedArrayPrototypeLength(node);
case Builtin::kTypedArrayPrototypeToStringTag:
return ReduceTypedArrayPrototypeToStringTag(node);
case Builtin::kMathAbs:
@ -7497,6 +7232,113 @@ Reduction JSCallReducer::ReduceTypedArrayPrototypeToStringTag(Node* node) {
return Replace(value);
}
Reduction JSCallReducer::ReduceArrayBufferViewByteLengthAccessor(
Node* node, InstanceType instance_type) {
DCHECK(instance_type == JS_TYPED_ARRAY_TYPE ||
instance_type == JS_DATA_VIEW_TYPE);
Node* receiver = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() ||
!inference.AllOfInstanceTypesAre(instance_type)) {
return inference.NoChange();
}
std::set<ElementsKind> elements_kinds;
bool maybe_rab_gsab = false;
if (instance_type == JS_DATA_VIEW_TYPE) {
maybe_rab_gsab = true;
} else {
for (const auto& map : inference.GetMaps()) {
ElementsKind kind = map.elements_kind();
elements_kinds.insert(kind);
if (IsRabGsabTypedArrayElementsKind(kind)) maybe_rab_gsab = true;
}
}
if (!v8_flags.harmony_rab_gsab || !maybe_rab_gsab) {
// We do not perform any change depending on this inference.
Reduction unused_reduction = inference.NoChange();
USE(unused_reduction);
// Call default implementation for non-rab/gsab TAs.
return ReduceArrayBufferViewAccessor(
node, JS_TYPED_ARRAY_TYPE,
AccessBuilder::ForJSArrayBufferViewByteLength(),
Builtin::kTypedArrayPrototypeByteLength);
} else if (!v8_flags.turbo_rab_gsab) {
return inference.NoChange();
}
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control,
CallParametersOf(node->op()).feedback());
const bool depended_on_detaching_protector =
dependencies()->DependOnArrayBufferDetachingProtector();
if (!depended_on_detaching_protector && instance_type == JS_DATA_VIEW_TYPE) {
// DataView prototype accessors throw on detached ArrayBuffers instead of
// return 0, so skip the optimization.
//
// TODO(turbofan): Ideally we would bail out if the buffer is actually
// detached.
return inference.NoChange();
}
JSCallReducerAssembler a(this, node);
TNode<JSTypedArray> typed_array =
TNode<JSTypedArray>::UncheckedCast(receiver);
TNode<Number> length = a.ArrayBufferViewByteLength(
typed_array, std::move(elements_kinds), a.ContextInput());
return ReplaceWithSubgraph(&a, length);
}
Reduction JSCallReducer::ReduceTypedArrayPrototypeLength(Node* node) {
Node* receiver = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() ||
!inference.AllOfInstanceTypesAre(JS_TYPED_ARRAY_TYPE)) {
return inference.NoChange();
}
std::set<ElementsKind> elements_kinds;
bool maybe_rab_gsab = false;
for (const auto& map : inference.GetMaps()) {
ElementsKind kind = map.elements_kind();
elements_kinds.insert(kind);
if (IsRabGsabTypedArrayElementsKind(kind)) maybe_rab_gsab = true;
}
if (!v8_flags.harmony_rab_gsab || !maybe_rab_gsab) {
// We do not perform any change depending on this inference.
Reduction unused_reduction = inference.NoChange();
USE(unused_reduction);
// Call default implementation for non-rab/gsab TAs.
return ReduceArrayBufferViewAccessor(node, JS_TYPED_ARRAY_TYPE,
AccessBuilder::ForJSTypedArrayLength(),
Builtin::kTypedArrayPrototypeLength);
} else if (!v8_flags.turbo_rab_gsab) {
return inference.NoChange();
}
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control,
CallParametersOf(node->op()).feedback());
JSCallReducerAssembler a(this, node);
TNode<JSTypedArray> typed_array =
TNode<JSTypedArray>::UncheckedCast(receiver);
TNode<Number> length = a.TypedArrayLength(
typed_array, std::move(elements_kinds), a.ContextInput());
return ReplaceWithSubgraph(&a, length);
}
// ES #sec-number.isfinite
Reduction JSCallReducer::ReduceNumberIsFinite(Node* node) {
JSCallNode n(node);
@ -8005,7 +7847,8 @@ Reduction JSCallReducer::ReduceArrayBufferIsView(Node* node) {
}
Reduction JSCallReducer::ReduceArrayBufferViewAccessor(
Node* node, InstanceType instance_type, FieldAccess const& access) {
Node* node, InstanceType instance_type, FieldAccess const& access,
Builtin builtin) {
Node* receiver = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
@ -8016,13 +7859,11 @@ Reduction JSCallReducer::ReduceArrayBufferViewAccessor(
return inference.NoChange();
}
// TODO(v8:11111): We skip this optimization for RAB/GSAB for now. Should
// have some optimization here eventually.
for (const auto& map : inference.GetMaps()) {
if (IsRabGsabTypedArrayElementsKind(map.elements_kind())) {
return inference.NoChange();
}
}
DCHECK_IMPLIES((builtin == Builtin::kTypedArrayPrototypeLength ||
builtin == Builtin::kTypedArrayPrototypeByteLength),
base::none_of(inference.GetMaps(), [](const auto& map) {
return IsRabGsabTypedArrayElementsKind(map.elements_kind());
}));
CHECK(inference.RelyOnMapsViaStability(dependencies()));
@ -8128,11 +7969,20 @@ Reduction JSCallReducer::ReduceDataViewAccess(Node* node, DataViewAccess access,
offset = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()),
offset, byte_length, effect, control);
} else {
Node* byte_length;
if (!v8_flags.harmony_rab_gsab) {
// We only deal with DataViews here that have Smi [[ByteLength]]s.
Node* byte_length = effect =
byte_length = effect =
graph()->NewNode(simplified()->LoadField(
AccessBuilder::ForJSArrayBufferViewByteLength()),
receiver, effect, control);
} else {
JSCallReducerAssembler a(this, node);
byte_length = a.ArrayBufferViewByteLength(
TNode<JSArrayBufferView>::UncheckedCast(receiver), {},
a.ContextInput());
std::tie(effect, control) = ReleaseEffectAndControlFromAssembler(&a);
}
if (element_size > 1) {
// For non-byte accesses we also need to check that the {offset}

View File

@ -181,6 +181,9 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Reduction ReduceTypedArrayConstructor(Node* node,
const SharedFunctionInfoRef& shared);
Reduction ReduceTypedArrayPrototypeToStringTag(Node* node);
Reduction ReduceArrayBufferViewByteLengthAccessor(Node* node,
InstanceType instance_type);
Reduction ReduceTypedArrayPrototypeLength(Node* node);
Reduction ReduceForInsufficientFeedback(Node* node, DeoptimizeReason reason);
@ -216,7 +219,8 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Reduction ReduceArrayBufferIsView(Node* node);
Reduction ReduceArrayBufferViewAccessor(Node* node,
InstanceType instance_type,
FieldAccess const& access);
FieldAccess const& access,
Builtin builtin);
enum class DataViewAccess { kGet, kSet };
Reduction ReduceDataViewAccess(Node* node, DataViewAccess access,
@ -234,6 +238,8 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
// The pendant to ReplaceWithValue when using GraphAssembler-based reductions.
Reduction ReplaceWithSubgraph(JSCallReducerAssembler* gasm, Node* subgraph);
std::pair<Node*, Node*> ReleaseEffectAndControlFromAssembler(
JSCallReducerAssembler* gasm);
// Helper to verify promise receiver maps are as expected.
// On bailout from a reduction, be sure to return inference.NoChange().

View File

@ -15,6 +15,7 @@
#include "src/compiler/allocation-builder.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/frame-states.h"
#include "src/compiler/graph-assembler.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/js-operator.h"
@ -25,9 +26,11 @@
#include "src/compiler/property-access-builder.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/type-cache.h"
#include "src/flags/flags.h"
#include "src/handles/handles.h"
#include "src/heap/factory.h"
#include "src/heap/heap-write-barrier-inl.h"
#include "src/objects/elements-kind.h"
#include "src/objects/feedback-vector.h"
#include "src/objects/heap-number.h"
#include "src/objects/string.h"
@ -2116,6 +2119,7 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
Node* receiver = NodeProperties::GetValueInput(node, 0);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
Node* context = NodeProperties::GetContextInput(node);
// TODO(neis): It's odd that we do optimizations below that don't really care
// about the feedback, but we don't do them when the feedback is megamorphic.
@ -2225,8 +2229,8 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
// Access the actual element.
ValueEffectControl continuation =
BuildElementAccess(receiver, index, value, effect, control, access_info,
feedback.keyed_mode());
BuildElementAccess(receiver, index, value, effect, control, context,
access_info, feedback.keyed_mode());
value = continuation.value();
effect = continuation.effect();
control = continuation.control();
@ -2290,9 +2294,9 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
}
// Access the actual element.
ValueEffectControl continuation =
BuildElementAccess(this_receiver, this_index, this_value, this_effect,
this_control, access_info, feedback.keyed_mode());
ValueEffectControl continuation = BuildElementAccess(
this_receiver, this_index, this_value, this_effect, this_control,
context, access_info, feedback.keyed_mode());
values.push_back(continuation.value());
effects.push_back(continuation.effect());
controls.push_back(continuation.control());
@ -3110,6 +3114,7 @@ ExternalArrayType GetArrayTypeFromElementsKind(ElementsKind kind) {
switch (kind) {
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \
case TYPE##_ELEMENTS: \
case RAB_GSAB_##TYPE##_ELEMENTS: \
return kExternal##Type##Array;
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
@ -3124,14 +3129,18 @@ ExternalArrayType GetArrayTypeFromElementsKind(ElementsKind kind) {
JSNativeContextSpecialization::ValueEffectControl
JSNativeContextSpecialization::BuildElementAccess(
Node* receiver, Node* index, Node* value, Node* effect, Node* control,
ElementAccessInfo const& access_info, KeyedAccessMode const& keyed_mode) {
Node* context, ElementAccessInfo const& access_info,
KeyedAccessMode const& keyed_mode) {
// TODO(bmeurer): We currently specialize based on elements kind. We should
// also be able to properly support strings and other JSObjects here.
ElementsKind elements_kind = access_info.elements_kind();
DCHECK_IMPLIES(IsRabGsabTypedArrayElementsKind(elements_kind),
v8_flags.turbo_rab_gsab);
ZoneVector<MapRef> const& receiver_maps =
access_info.lookup_start_object_maps();
if (IsTypedArrayElementsKind(elements_kind)) {
if (IsTypedArrayElementsKind(elements_kind) ||
IsRabGsabTypedArrayElementsKind(elements_kind)) {
Node* buffer_or_receiver = receiver;
Node* length;
Node* base_pointer;
@ -3141,7 +3150,9 @@ JSNativeContextSpecialization::BuildElementAccess(
// for asm.js-like code patterns).
base::Optional<JSTypedArrayRef> typed_array =
GetTypedArrayConstant(broker(), receiver);
if (typed_array.has_value()) {
if (typed_array.has_value() &&
!IsRabGsabTypedArrayElementsKind(elements_kind)) {
// TODO(v8:11111): Add support for rab/gsab here.
length = jsgraph()->Constant(static_cast<double>(typed_array->length()));
DCHECK(!typed_array->is_on_heap());
@ -3154,9 +3165,14 @@ JSNativeContextSpecialization::BuildElementAccess(
external_pointer = jsgraph()->PointerConstant(typed_array->data_ptr());
} else {
// Load the {receiver}s length.
length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSTypedArrayLength()),
receiver, effect, control);
JSGraphAssembler assembler(jsgraph_, zone(), BranchSemantics::kJS,
[this](Node* n) { this->Revisit(n); });
assembler.InitializeEffectControl(effect, control);
length = assembler.TypedArrayLength(
TNode<JSTypedArray>::UncheckedCast(receiver), {elements_kind},
TNode<Context>::UncheckedCast(context));
std::tie(effect, control) =
ReleaseEffectAndControlFromAssembler(&assembler);
// Load the base pointer for the {receiver}. This will always be Smi
// zero unless we allow on-heap TypedArrays, which is only the case
@ -3927,6 +3943,27 @@ Node* JSNativeContextSpecialization::BuildLoadPrototypeFromObject(
control);
}
std::pair<Node*, Node*>
JSNativeContextSpecialization::ReleaseEffectAndControlFromAssembler(
JSGraphAssembler* gasm) {
auto catch_scope = gasm->catch_scope();
DCHECK(catch_scope->is_outermost());
if (catch_scope->has_handler() &&
catch_scope->has_exceptional_control_flow()) {
TNode<Object> handler_exception;
Effect handler_effect{nullptr};
Control handler_control{nullptr};
gasm->catch_scope()->MergeExceptionalPaths(
&handler_exception, &handler_effect, &handler_control);
ReplaceWithValue(gasm->outermost_handler(), handler_exception,
handler_effect, handler_control);
}
return {gasm->effect(), gasm->control()};
}
Graph* JSNativeContextSpecialization::graph() const {
return jsgraph()->graph();
}

View File

@ -7,6 +7,7 @@
#include "src/base/flags.h"
#include "src/base/optional.h"
#include "src/compiler/graph-assembler.h"
#include "src/compiler/graph-reducer.h"
#include "src/compiler/js-heap-broker.h"
#include "src/deoptimizer/deoptimize-reason.h"
@ -189,7 +190,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
// Construct the appropriate subgraph for element access.
ValueEffectControl BuildElementAccess(Node* receiver, Node* index,
Node* value, Node* effect,
Node* control,
Node* control, Node* context,
ElementAccessInfo const& access_info,
KeyedAccessMode const& keyed_mode);
@ -249,6 +250,9 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
Node* BuildLoadPrototypeFromObject(Node* object, Node* effect, Node* control);
std::pair<Node*, Node*> ReleaseEffectAndControlFromAssembler(
JSGraphAssembler* assembler);
Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; }

View File

@ -916,20 +916,18 @@ const Operator* JSOperatorBuilder::CallRuntime(Runtime::FunctionId id) {
return CallRuntime(f, f->nargs);
}
const Operator* JSOperatorBuilder::CallRuntime(Runtime::FunctionId id,
size_t arity) {
const Operator* JSOperatorBuilder::CallRuntime(
Runtime::FunctionId id, size_t arity, Operator::Properties properties) {
const Runtime::Function* f = Runtime::FunctionForId(id);
return CallRuntime(f, arity);
return CallRuntime(f, arity, properties);
}
const Operator* JSOperatorBuilder::CallRuntime(const Runtime::Function* f,
size_t arity) {
const Operator* JSOperatorBuilder::CallRuntime(
const Runtime::Function* f, size_t arity, Operator::Properties properties) {
CallRuntimeParameters parameters(f->function_id, arity);
DCHECK(f->nargs == -1 || f->nargs == static_cast<int>(parameters.arity()));
return zone()->New<Operator1<CallRuntimeParameters>>( // --
IrOpcode::kJSCallRuntime, Operator::kNoProperties, // opcode
IrOpcode::kJSCallRuntime, properties, // opcode
"JSCallRuntime", // name
parameters.arity(), 1, 1, f->result_size, 1, 2, // inputs/outputs
parameters); // parameter

View File

@ -13,6 +13,7 @@
#include "src/compiler/node-properties.h"
#include "src/compiler/node.h"
#include "src/compiler/opcodes.h"
#include "src/compiler/operator-properties.h"
#include "src/objects/feedback-cell.h"
#include "src/runtime/runtime.h"
@ -995,8 +996,12 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
SpeculationMode speculation_mode = SpeculationMode::kDisallowSpeculation,
CallFeedbackRelation feedback_relation = CallFeedbackRelation::kTarget);
const Operator* CallRuntime(Runtime::FunctionId id);
const Operator* CallRuntime(Runtime::FunctionId id, size_t arity);
const Operator* CallRuntime(const Runtime::Function* function, size_t arity);
const Operator* CallRuntime(
Runtime::FunctionId id, size_t arity,
Operator::Properties properties = Operator::kNoProperties);
const Operator* CallRuntime(
const Runtime::Function* function, size_t arity,
Operator::Properties properties = Operator::kNoProperties);
#if V8_ENABLE_WEBASSEMBLY
const Operator* CallWasm(const wasm::WasmModule* wasm_module,

View File

@ -9,6 +9,7 @@
#include "src/codegen/code-factory.h"
#include "src/codegen/interface-descriptors-inl.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/graph-assembler.h"
#include "src/compiler/js-graph.h"
@ -841,7 +842,7 @@ Reduction JSTypedLowering::ReduceJSEqual(Node* node) {
// then ObjectIsUndetectable(left)
// else ReferenceEqual(left, right)
#define __ gasm.
JSGraphAssembler gasm(jsgraph(), jsgraph()->zone());
JSGraphAssembler gasm(jsgraph(), jsgraph()->zone(), BranchSemantics::kJS);
gasm.InitializeEffectControl(r.effect(), r.control());
auto lhs = TNode<Object>::UncheckedCast(r.left());

View File

@ -268,6 +268,7 @@ bool Linkage::NeedsFrameStateInput(Runtime::FunctionId function) {
case Runtime::kAbort:
case Runtime::kAllocateInOldGeneration:
case Runtime::kCreateIterResultObject:
case Runtime::kGrowableSharedArrayBufferByteLength:
case Runtime::kIncBlockCounter:
case Runtime::kIsFunction:
case Runtime::kNewClosure:

View File

@ -6,6 +6,7 @@
#include "src/base/logging.h"
#include "src/codegen/tick-counter.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-properties.h"
@ -183,7 +184,7 @@ MemoryOptimizer::MemoryOptimizer(
JSGraph* jsgraph, Zone* zone,
MemoryLowering::AllocationFolding allocation_folding,
const char* function_debug_name, TickCounter* tick_counter)
: graph_assembler_(jsgraph, zone),
: graph_assembler_(jsgraph, zone, BranchSemantics::kMachine),
memory_lowering_(jsgraph, zone, &graph_assembler_, allocation_folding,
WriteBarrierAssertFailed, function_debug_name),
jsgraph_(jsgraph),

View File

@ -406,7 +406,6 @@ Node::Node(NodeId id, const Operator* op, int inline_count, int inline_capacity)
DCHECK_LE(inline_capacity, kMaxInlineCapacity);
}
void Node::AppendUse(Use* use) {
DCHECK(first_use_ == nullptr || first_use_->prev == nullptr);
DCHECK_EQ(this, *use->input_ptr());

View File

@ -695,6 +695,13 @@ Node::Uses::const_iterator Node::Uses::begin() const {
Node::Uses::const_iterator Node::Uses::end() const { return const_iterator(); }
inline Node::Uses::const_iterator begin(const Node::Uses& uses) {
return uses.begin();
}
inline Node::Uses::const_iterator end(const Node::Uses& uses) {
return uses.end();
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -33,21 +33,27 @@
V(Throw) \
V(End)
// Opcodes for constant operators.
#define CONSTANT_OP_LIST(V) \
#define MACHINE_LEVEL_CONSTANT_OP_LIST(V) \
V(Int32Constant) \
V(Int64Constant) \
V(TaggedIndexConstant) \
V(Float32Constant) \
V(Float64Constant) \
V(ExternalConstant) \
V(NumberConstant) \
V(PointerConstant) \
V(HeapConstant) \
V(CompressedHeapConstant) \
V(RelocatableInt32Constant) \
V(RelocatableInt64Constant)
#define JS_LEVEL_CONSTANT_OP_LIST(V) \
V(ExternalConstant) \
V(NumberConstant) \
V(PointerConstant) \
V(HeapConstant)
// Opcodes for constant operators.
#define CONSTANT_OP_LIST(V) \
JS_LEVEL_CONSTANT_OP_LIST(V) \
MACHINE_LEVEL_CONSTANT_OP_LIST(V)
#define INNER_OP_LIST(V) \
V(Select) \
V(Phi) \
@ -74,7 +80,9 @@
V(Retain) \
V(MapGuard) \
V(FoldConstant) \
V(TypeGuard)
V(TypeGuard) \
V(EnterMachineGraph) \
V(ExitMachineGraph)
#define COMMON_OP_LIST(V) \
CONSTANT_OP_LIST(V) \
@ -547,6 +555,13 @@
SIMPLIFIED_OTHER_OP_LIST(V)
// Opcodes for Machine-level operators.
#define MACHINE_UNOP_32_LIST(V) \
V(Word32Clz) \
V(Word32Ctz) \
V(Int32AbsWithOverflow) \
V(Word32ReverseBits) \
V(Word32ReverseBytes)
#define MACHINE_COMPARE_BINOP_LIST(V) \
V(Word32Equal) \
V(Word64Equal) \
@ -565,13 +580,6 @@
V(Float64LessThan) \
V(Float64LessThanOrEqual)
#define MACHINE_UNOP_32_LIST(V) \
V(Word32Clz) \
V(Word32Ctz) \
V(Int32AbsWithOverflow) \
V(Word32ReverseBits) \
V(Word32ReverseBytes)
#define MACHINE_BINOP_32_LIST(V) \
V(Word32And) \
V(Word32Or) \
@ -1086,6 +1094,24 @@ class V8_EXPORT_PRIVATE IrOpcode {
return kJSEqual <= value && value <= kJSDebugger;
}
// Returns true if opcode for machine operator.
static bool IsMachineOpcode(Value value) {
return kWord32Clz <= value && value <= kTraceInstruction;
}
// Returns true iff opcode is a machine-level constant.
static bool IsMachineConstantOpcode(Value value) {
switch (value) {
#define CASE(name) \
case k##name: \
return true;
MACHINE_LEVEL_CONSTANT_OP_LIST(CASE)
#undef CASE
default:
return false;
}
}
// Returns true if opcode for constant operator.
static bool IsConstantOpcode(Value value) {
#define CASE(Name) \

View File

@ -1132,6 +1132,11 @@ SPECULATIVE_NUMBER_BINOP(NumberShiftRight)
SPECULATIVE_NUMBER_BINOP(NumberShiftRightLogical)
#undef SPECULATIVE_NUMBER_BINOP
#define MACHINE_BINOP(Name) \
Type OperationTyper::Name(Type, Type) { return Type::Machine(); }
TYPER_SUPPORTED_MACHINE_BINOP_LIST(MACHINE_BINOP)
#undef MACHINE_BINOP
Type OperationTyper::BigIntAdd(Type lhs, Type rhs) {
DCHECK(lhs.Is(Type::BigInt()));
DCHECK(rhs.Is(Type::BigInt()));

View File

@ -9,6 +9,26 @@
#include "src/compiler/opcodes.h"
#include "src/compiler/types.h"
#define TYPER_SUPPORTED_MACHINE_BINOP_LIST(V) \
V(Int32Add) \
V(Int64Add) \
V(Int32Sub) \
V(Int64Sub) \
V(Load) \
V(Uint32Div) \
V(Uint64Div) \
V(Uint32LessThan) \
V(Uint32LessThanOrEqual) \
V(Uint64LessThanOrEqual) \
V(Word32And) \
V(Word32Equal) \
V(Word32Or) \
V(Word32Shl) \
V(Word32Shr) \
V(Word64And) \
V(Word64Shl) \
V(Word64Shr)
namespace v8 {
namespace internal {
@ -54,6 +74,7 @@ class V8_EXPORT_PRIVATE OperationTyper {
SIMPLIFIED_BIGINT_BINOP_LIST(DECLARE_METHOD)
SIMPLIFIED_SPECULATIVE_NUMBER_BINOP_LIST(DECLARE_METHOD)
SIMPLIFIED_SPECULATIVE_BIGINT_BINOP_LIST(DECLARE_METHOD)
TYPER_SUPPORTED_MACHINE_BINOP_LIST(DECLARE_METHOD)
#undef DECLARE_METHOD
// Comparison operators.

View File

@ -1986,7 +1986,8 @@ struct LateOptimizationPhase {
CommonOperatorReducer common_reducer(
&graph_reducer, data->graph(), data->broker(), data->common(),
data->machine(), temp_zone, BranchSemantics::kMachine);
JSGraphAssembler graph_assembler(data->jsgraph(), temp_zone);
JSGraphAssembler graph_assembler(data->jsgraph(), temp_zone,
BranchSemantics::kMachine);
SelectLowering select_lowering(&graph_assembler, data->graph());
AddReducer(data, &graph_reducer, &escape_analysis);
AddReducer(data, &graph_reducer, &branch_condition_elimination);

View File

@ -160,6 +160,9 @@ RepresentationChanger::RepresentationChanger(
Node* RepresentationChanger::GetRepresentationFor(
Node* node, MachineRepresentation output_rep, Type output_type,
Node* use_node, UseInfo use_info) {
// We are currently not inserting conversions in machine graphs.
// We might add that, though.
DCHECK_IMPLIES(!output_type.IsNone(), !output_type.Is(Type::Machine()));
if (output_rep == MachineRepresentation::kNone && !output_type.IsNone()) {
// The output representation should be set if the type is inhabited (i.e.,
// if the value is possible).
@ -487,22 +490,16 @@ Node* RepresentationChanger::GetTaggedPointerRepresentationFor(
return TypeError(node, output_rep, output_type,
MachineRepresentation::kTaggedPointer);
}
} else if (CanBeTaggedSigned(output_rep) &&
use_info.type_check() == TypeCheckKind::kHeapObject) {
if (!output_type.Maybe(Type::SignedSmall())) {
return node;
}
// 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)) {
if (use_info.type_check() == TypeCheckKind::kBigInt) {
if (output_type.Is(Type::BigInt())) {
DCHECK_NE(output_rep, MachineRepresentation::kTaggedSigned);
return node;
}
op = simplified()->CheckBigInt(use_info.feedback());
} else if (use_info.type_check() == TypeCheckKind::kBigInt64) {
if (output_type.Is(Type::SignedBigInt64())) {
DCHECK_NE(output_rep, MachineRepresentation::kTaggedSigned);
return node;
}
if (!output_type.Is(Type::BigInt())) {
@ -515,8 +512,7 @@ Node* RepresentationChanger::GetTaggedPointerRepresentationFor(
DCHECK_NE(output_rep, MachineRepresentation::kTaggedSigned);
return node;
} else {
return TypeError(node, output_rep, output_type,
MachineRepresentation::kTaggedPointer);
op = simplified()->CheckedTaggedToTaggedPointer(use_info.feedback());
}
} else {
return TypeError(node, output_rep, output_type,
@ -1047,9 +1043,11 @@ Node* RepresentationChanger::GetBitRepresentationFor(
case IrOpcode::kHeapConstant: {
HeapObjectMatcher m(node);
if (m.Is(factory()->false_value())) {
return jsgraph()->Int32Constant(0);
return InsertTypeOverrideForVerifier(Type::Boolean(),
jsgraph()->Int32Constant(0));
} else if (m.Is(factory()->true_value())) {
return jsgraph()->Int32Constant(1);
return InsertTypeOverrideForVerifier(Type::Boolean(),
jsgraph()->Int32Constant(1));
}
break;
}

View File

@ -4,6 +4,8 @@
#include "src/compiler/simplified-lowering-verifier.h"
#include "src/compiler/backend/instruction-codes.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/operation-typer.h"
#include "src/compiler/type-cache.h"
@ -22,7 +24,8 @@ Truncation LeastGeneralTruncation(const Truncation& t1, const Truncation& t2,
return LeastGeneralTruncation(LeastGeneralTruncation(t1, t2), t3);
}
bool IsNonTruncatingMachineTypeFor(const MachineType& mt, const Type& type) {
bool IsNonTruncatingMachineTypeFor(const MachineType& mt, const Type& type,
Zone* graph_zone) {
if (type.IsNone()) return true;
// TODO(nicohartmann@): Add more cases here.
if (type.Is(Type::BigInt())) {
@ -37,7 +40,7 @@ bool IsNonTruncatingMachineTypeFor(const MachineType& mt, const Type& type) {
case MachineRepresentation::kBit:
CHECK(mt.semantic() == MachineSemantic::kBool ||
mt.semantic() == MachineSemantic::kAny);
return type.Is(Type::Boolean());
return type.Is(Type::Boolean()) || type.Is(Type::Range(0, 1, graph_zone));
default:
return true;
}
@ -75,6 +78,19 @@ void SimplifiedLoweringVerifier::CheckAndSet(Node* node, const Type& type,
SetTruncation(node, GeneralizeTruncation(trunc, type));
}
void SimplifiedLoweringVerifier::ReportInvalidTypeCombination(
Node* node, const std::vector<Type>& types) {
std::ostringstream types_str;
for (size_t i = 0; i < types.size(); ++i) {
if (i != 0) types_str << ", ";
types[i].PrintTo(types_str);
}
FATAL(
"SimplifiedLoweringVerifierError: invalid combination of input types %s "
" for node #%d:%s",
types_str.str().c_str(), node->id(), node->op()->mnemonic());
}
bool IsModuloTruncation(const Truncation& truncation) {
return truncation.IsUsedAsWord32() || truncation.IsUsedAsWord64() ||
Truncation::Any().IsLessGeneralThan(truncation);
@ -134,7 +150,21 @@ void SimplifiedLoweringVerifier::VisitNode(Node* node,
case IrOpcode::kFrameState:
case IrOpcode::kJSStackCheck:
break;
case IrOpcode::kInt32Constant:
case IrOpcode::kInt32Constant: {
// NOTE: Constants require special handling as they are shared between
// machine graphs and non-machine graphs lowered during SL. The former
// might have assigned Type::Machine() to the constant, but to be able
// to provide a different type for uses of constants that don't come
// from machine graphs, the machine-uses of Int32Constants have been
// put behind additional SLVerifierHints to provide the required
// Type::Machine() to them, such that we can treat constants here as
// having JS types to satisfy their non-machine uses.
int32_t value = OpParameter<int32_t>(node->op());
Type type = Type::Constant(value, graph_zone());
SetType(node, type);
SetTruncation(node, GeneralizeTruncation(Truncation::Word32(), type));
break;
}
case IrOpcode::kInt64Constant:
case IrOpcode::kFloat64Constant: {
// Constants might be untyped, because they are cached in the graph and
@ -183,8 +213,22 @@ void SimplifiedLoweringVerifier::VisitNode(Node* node,
break;
}
case IrOpcode::kInt32Add: {
Type output_type =
op_typer.NumberAdd(InputType(node, 0), InputType(node, 1));
Type left_type = InputType(node, 0);
Type right_type = InputType(node, 1);
Type output_type;
if (left_type.IsNone() && right_type.IsNone()) {
output_type = Type::None();
} else if (left_type.Is(Type::Machine()) &&
right_type.Is(Type::Machine())) {
output_type = Type::Machine();
} else if (left_type.Is(Type::NumberOrOddball()) &&
right_type.Is(Type::NumberOrOddball())) {
left_type = op_typer.ToNumber(left_type);
right_type = op_typer.ToNumber(right_type);
output_type = op_typer.NumberAdd(left_type, right_type);
} else {
ReportInvalidTypeCombination(node, {left_type, right_type});
}
Truncation output_trunc = LeastGeneralTruncation(InputTruncation(node, 0),
InputTruncation(node, 1),
Truncation::Word32());
@ -193,8 +237,22 @@ void SimplifiedLoweringVerifier::VisitNode(Node* node,
break;
}
case IrOpcode::kInt32Sub: {
Type output_type =
op_typer.NumberSubtract(InputType(node, 0), InputType(node, 1));
Type left_type = InputType(node, 0);
Type right_type = InputType(node, 1);
Type output_type;
if (left_type.IsNone() && right_type.IsNone()) {
output_type = Type::None();
} else if (left_type.Is(Type::Machine()) &&
right_type.Is(Type::Machine())) {
output_type = Type::Machine();
} else if (left_type.Is(Type::NumberOrOddball()) &&
right_type.Is(Type::NumberOrOddball())) {
left_type = op_typer.ToNumber(left_type);
right_type = op_typer.ToNumber(right_type);
output_type = op_typer.NumberSubtract(left_type, right_type);
} else {
ReportInvalidTypeCombination(node, {left_type, right_type});
}
Truncation output_trunc = LeastGeneralTruncation(InputTruncation(node, 0),
InputTruncation(node, 1),
Truncation::Word32());
@ -222,9 +280,16 @@ void SimplifiedLoweringVerifier::VisitNode(Node* node,
case IrOpcode::kInt64Add: {
Type left_type = InputType(node, 0);
Type right_type = InputType(node, 1);
Type output_type;
if (left_type.Is(Type::BigInt()) && right_type.Is(Type::BigInt())) {
if (left_type.IsNone() && right_type.IsNone()) {
// None x None -> None
output_type = Type::None();
} else if (left_type.Is(Type::Machine()) &&
right_type.Is(Type::Machine())) {
// Machine x Machine -> Machine
output_type = Type::Machine();
} else if (left_type.Is(Type::BigInt()) &&
right_type.Is(Type::BigInt())) {
// BigInt x BigInt -> BigInt
output_type = op_typer.BigIntAdd(left_type, right_type);
} else if (left_type.Is(Type::Number()) &&
@ -233,17 +298,38 @@ void SimplifiedLoweringVerifier::VisitNode(Node* node,
output_type = op_typer.NumberAdd(left_type, right_type);
} else {
// Invalid type combination.
std::ostringstream left_str, right_str;
left_type.PrintTo(left_str);
right_type.PrintTo(right_str);
FATAL(
"SimplifiedLoweringVerifierError: invalid combination of input "
"types "
"%s and %s for node #%d:%s",
left_str.str().c_str(), right_str.str().c_str(), node->id(),
node->op()->mnemonic());
ReportInvalidTypeCombination(node, {left_type, right_type});
}
Truncation output_trunc = LeastGeneralTruncation(InputTruncation(node, 0),
InputTruncation(node, 1),
Truncation::Word64());
CHECK(IsModuloTruncation(output_trunc));
CheckAndSet(node, output_type, output_trunc);
break;
}
case IrOpcode::kInt64Sub: {
Type left_type = InputType(node, 0);
Type right_type = InputType(node, 1);
Type output_type;
if (left_type.IsNone() && right_type.IsNone()) {
// None x None -> None
output_type = Type::None();
} else if (left_type.Is(Type::Machine()) &&
right_type.Is(Type::Machine())) {
// Machine x Machine -> Machine
output_type = Type::Machine();
} else if (left_type.Is(Type::BigInt()) &&
right_type.Is(Type::BigInt())) {
// BigInt x BigInt -> BigInt
output_type = op_typer.BigIntSubtract(left_type, right_type);
} else if (left_type.Is(Type::Number()) &&
right_type.Is(Type::Number())) {
// Number x Number -> Number
output_type = op_typer.NumberSubtract(left_type, right_type);
} else {
// Invalid type combination.
ReportInvalidTypeCombination(node, {left_type, right_type});
}
Truncation output_trunc = LeastGeneralTruncation(InputTruncation(node, 0),
InputTruncation(node, 1),
Truncation::Word64());
@ -327,8 +413,10 @@ void SimplifiedLoweringVerifier::VisitNode(Node* node,
break;
}
case IrOpcode::kBranch: {
CHECK(InputType(node, 0).Is(Type::Boolean()));
CHECK_EQ(InputTruncation(node, 0), Truncation::Any());
CHECK_EQ(BranchParametersOf(node->op()).semantics(),
BranchSemantics::kMachine);
Type input_type = InputType(node, 0);
CHECK(input_type.Is(Type::Boolean()) || input_type.Is(Type::Machine()));
break;
}
case IrOpcode::kTypedStateValues: {
@ -337,7 +425,7 @@ void SimplifiedLoweringVerifier::VisitNode(Node* node,
// Inputs must not be truncated.
CHECK_EQ(InputTruncation(node, i), Truncation::Any());
CHECK(IsNonTruncatingMachineTypeFor(machine_types->at(i),
InputType(node, i)));
InputType(node, i), graph_zone()));
}
break;
}
@ -346,6 +434,11 @@ void SimplifiedLoweringVerifier::VisitNode(Node* node,
SetTruncation(node, Truncation::Any());
break;
}
case IrOpcode::kEnterMachineGraph:
case IrOpcode::kExitMachineGraph: {
// Eliminated during lowering.
UNREACHABLE();
}
#define CASE(code, ...) case IrOpcode::k##code:
// Control operators
@ -489,7 +582,6 @@ void SimplifiedLoweringVerifier::VisitNode(Node* node,
CASE(Word64RolLowerable)
CASE(Word64RorLowerable)
CASE(Int64AddWithOverflow)
CASE(Int64Sub)
CASE(Int64SubWithOverflow)
CASE(Int64Mul)
CASE(Int64MulHigh)

View File

@ -5,6 +5,8 @@
#ifndef V8_COMPILER_SIMPLIFIED_LOWERING_VERIFIER_H_
#define V8_COMPILER_SIMPLIFIED_LOWERING_VERIFIER_H_
#include "src/base/container-utils.h"
#include "src/compiler/opcodes.h"
#include "src/compiler/representation-change.h"
namespace v8 {
@ -21,7 +23,11 @@ class SimplifiedLoweringVerifier final {
};
SimplifiedLoweringVerifier(Zone* zone, Graph* graph)
: hints_(zone), data_(zone), graph_(graph) {}
: hints_(zone),
machine_uses_of_constants_(zone),
data_(zone),
graph_(graph),
zone_(zone) {}
void VisitNode(Node* node, OperationTyper& op_typer);
@ -30,10 +36,33 @@ class SimplifiedLoweringVerifier final {
hints_.push_back(node);
}
const ZoneVector<Node*>& inserted_hints() const { return hints_; }
void RecordMachineUsesOfConstant(Node* constant, Node::Uses uses) {
DCHECK(IrOpcode::IsMachineConstantOpcode(constant->opcode()));
auto it = machine_uses_of_constants_.find(constant);
if (it == machine_uses_of_constants_.end()) {
it =
machine_uses_of_constants_.emplace(constant, ZoneVector<Node*>(zone_))
.first;
}
base::vector_append(it->second, uses);
}
const ZoneUnorderedMap<Node*, ZoneVector<Node*>>& machine_uses_of_constants()
const {
return machine_uses_of_constants_;
}
base::Optional<Type> GetType(Node* node) const {
if (NodeProperties::IsTyped(node)) {
return NodeProperties::GetType(node);
Type type = NodeProperties::GetType(node);
// We do not use the static type for constants, even if we have one,
// because those are cached in the graph and shared between machine
// and non-machine subgraphs. The former might have assigned
// Type::Machine() to them.
if (IrOpcode::IsMachineConstantOpcode(node->opcode())) {
DCHECK(type.Is(Type::Machine()));
} else {
return type;
}
}
// For nodes that have not been typed before SL, we use the type that has
// been inferred by the verifier.
@ -60,16 +89,7 @@ class SimplifiedLoweringVerifier final {
Type InputType(Node* node, int input_index) const {
// TODO(nicohartmann): Check that inputs are typed, once all operators are
// supported.
Node* input = node->InputAt(input_index);
if (NodeProperties::IsTyped(input)) {
return NodeProperties::GetType(input);
}
// For nodes that have not been typed before SL, we use the type that has
// been inferred by the verifier.
base::Optional<Type> type_opt;
if (input->id() < data_.size()) {
type_opt = data_[input->id()].type;
}
auto type_opt = GetType(node->InputAt(input_index));
return type_opt.has_value() ? *type_opt : Type::None();
}
@ -91,6 +111,7 @@ class SimplifiedLoweringVerifier final {
void CheckType(Node* node, const Type& type);
void CheckAndSet(Node* node, const Type& type, const Truncation& trunc);
void ReportInvalidTypeCombination(Node* node, const std::vector<Type>& types);
// Generalize to a less strict truncation in the context of a given type. For
// example, a Truncation::kWord32[kIdentifyZeros] does not have any effect on
@ -104,8 +125,10 @@ class SimplifiedLoweringVerifier final {
Zone* graph_zone() const { return graph_->zone(); }
ZoneVector<Node*> hints_;
ZoneUnorderedMap<Node*, ZoneVector<Node*>> machine_uses_of_constants_;
ZoneVector<PerNodeData> data_;
Graph* graph_;
Zone* zone_;
};
} // namespace compiler

View File

@ -396,6 +396,11 @@ class RepresentationSelector {
bool UpdateFeedbackType(Node* node) {
if (node->op()->ValueOutputCount() == 0) return false;
if ((IrOpcode::IsMachineOpcode(node->opcode()) ||
IrOpcode::IsMachineConstantOpcode(node->opcode())) &&
node->opcode() != IrOpcode::kLoadFramePointer) {
DCHECK(NodeProperties::GetType(node).Is(Type::Machine()));
}
// For any non-phi node just wait until we get all inputs typed. We only
// allow untyped inputs for phi nodes because phis are the only places
@ -595,7 +600,6 @@ class RepresentationSelector {
while (!stack.empty()) {
NodeState& current = stack.top();
Node* node = current.node;
// If there is an unvisited input, push it and continue with that node.
bool pushed_unvisited = false;
while (current.input_index < node->InputCount()) {
@ -750,6 +754,19 @@ class RepresentationSelector {
TRACE("--{Verify Phase}--\n");
// Patch pending type overrides.
for (auto [constant, uses] : verifier_->machine_uses_of_constants()) {
Node* typed_constant =
InsertTypeOverrideForVerifier(Type::Machine(), constant);
for (auto use : uses) {
for (int i = 0; i < use->InputCount(); ++i) {
if (use->InputAt(i) == constant) {
use->ReplaceInput(i, typed_constant);
}
}
}
}
// Generate a new traversal containing all the new nodes created during
// lowering.
GenerateTraversal();
@ -774,7 +791,9 @@ class RepresentationSelector {
}
// Verify all nodes.
for (Node* node : traversal_nodes_) verifier_->VisitNode(node, op_typer_);
for (Node* node : traversal_nodes_) {
verifier_->VisitNode(node, op_typer_);
}
// Print graph.
if (info != nullptr && info->trace_turbo_json()) {
@ -1065,7 +1084,7 @@ class RepresentationSelector {
void VisitNoop(Node* node, Truncation truncation) {
if (truncation.IsUnused()) return VisitUnused<T>(node);
MachineRepresentation representation =
GetOutputInfoForPhi(node, TypeOf(node), truncation);
GetOutputInfoForPhi(TypeOf(node), truncation);
VisitUnop<T>(node, UseInfo(representation, truncation), representation);
if (lower<T>()) DeferReplacement(node, node->InputAt(0));
}
@ -1141,11 +1160,7 @@ class RepresentationSelector {
}
// Infer representation for phi-like nodes.
// The {node} parameter is only used to decide on the int64 representation.
// Once the type system supports an external pointer type, the {node}
// parameter can be removed.
MachineRepresentation GetOutputInfoForPhi(Node* node, Type type,
Truncation use) {
MachineRepresentation GetOutputInfoForPhi(Type type, Truncation use) {
// Compute the representation.
if (type.Is(Type::None())) {
return MachineRepresentation::kNone;
@ -1183,7 +1198,7 @@ class RepresentationSelector {
ProcessInput<T>(node, 0, UseInfo::Bool());
MachineRepresentation output =
GetOutputInfoForPhi(node, TypeOf(node), truncation);
GetOutputInfoForPhi(TypeOf(node), truncation);
SetOutput<T>(node, output);
if (lower<T>()) {
@ -1204,8 +1219,13 @@ class RepresentationSelector {
template <Phase T>
void VisitPhi(Node* node, Truncation truncation,
SimplifiedLowering* lowering) {
MachineRepresentation output =
GetOutputInfoForPhi(node, TypeOf(node), truncation);
// If we already have a non-tagged representation set in the Phi node, it
// does come from subgraphs using machine operators we introduced early in
// the pipeline. In this case, we just keep the representation.
MachineRepresentation output = PhiRepresentationOf(node->op());
if (output == MachineRepresentation::kTagged) {
output = GetOutputInfoForPhi(TypeOf(node), truncation);
}
// Only set the output representation if not running with type
// feedback. (Feedback typing will set the representation.)
SetOutput<T>(node, output);
@ -1232,12 +1252,16 @@ class RepresentationSelector {
if (input_type.Is(type)) {
VisitUnop<T>(node, UseInfo::None(), MachineRepresentation::kBit);
if (lower<T>()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(1));
DeferReplacement(
node, InsertTypeOverrideForVerifier(
Type::Boolean(), lowering->jsgraph()->Int32Constant(1)));
}
} else {
VisitUnop<T>(node, UseInfo::AnyTagged(), MachineRepresentation::kBit);
if (lower<T>() && !input_type.Maybe(type)) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(0));
DeferReplacement(
node, InsertTypeOverrideForVerifier(
Type::Boolean(), lowering->jsgraph()->Int32Constant(0)));
}
}
}
@ -2140,7 +2164,18 @@ class RepresentationSelector {
->GetParameterType(ParameterIndexOf(node->op()))
.representation());
case IrOpcode::kInt32Constant:
return VisitLeaf<T>(node, MachineRepresentation::kWord32);
DCHECK_EQ(0, node->InputCount());
SetOutput<T>(node, MachineRepresentation::kWord32);
DCHECK(NodeProperties::GetType(node).Is(Type::Machine()));
if (verification_enabled()) {
// During lowering, SimplifiedLowering generates Int32Constants which
// need to be treated differently by the verifier than the
// Int32Constants introduced explicitly in machine graphs. To be able
// to distinguish them, we record those that are being visited here
// because they were generated before SimplifiedLowering.
verifier_->RecordMachineUsesOfConstant(node, node->uses());
}
return;
case IrOpcode::kInt64Constant:
return VisitLeaf<T>(node, MachineRepresentation::kWord64);
case IrOpcode::kExternalConstant:
@ -2174,8 +2209,19 @@ class RepresentationSelector {
}
case IrOpcode::kBranch: {
const auto& p = BranchParametersOf(node->op());
if (p.semantics() == BranchSemantics::kMachine) {
// If this is a machine branch, the condition is a machine operator,
// so we enter machine branch here.
ProcessInput<T>(node, 0, UseInfo::Any());
} else {
DCHECK(TypeOf(node->InputAt(0)).Is(Type::Boolean()));
ProcessInput<T>(node, 0, UseInfo::Bool());
if (lower<T>()) {
ChangeOp(node,
common()->Branch(p.hint(), BranchSemantics::kMachine));
}
}
EnqueueInput<T>(node, NodeProperties::FirstControlIndex(node));
return;
}
@ -3737,7 +3783,7 @@ class RepresentationSelector {
if (InputIs(node, Type::Boolean())) {
VisitUnop<T>(node, UseInfo::Bool(), MachineRepresentation::kWord32);
if (lower<T>()) {
ChangeToSemanticsHintForVerifier(node, node->op());
DeferReplacement(node, node->InputAt(0));
}
} else if (InputIs(node, Type::String())) {
VisitUnop<T>(node, UseInfo::AnyTagged(),
@ -3750,7 +3796,7 @@ class RepresentationSelector {
VisitUnop<T>(node, UseInfo::TruncatingWord32(),
MachineRepresentation::kWord32);
if (lower<T>()) {
ChangeToSemanticsHintForVerifier(node, node->op());
DeferReplacement(node, node->InputAt(0));
}
} else {
VisitUnop<T>(node, UseInfo::AnyTagged(),
@ -3764,7 +3810,7 @@ class RepresentationSelector {
VisitUnop<T>(node, UseInfo::TruncatingFloat64(),
MachineRepresentation::kFloat64);
if (lower<T>()) {
ChangeToSemanticsHintForVerifier(node, node->op());
DeferReplacement(node, node->InputAt(0));
}
} else {
VisitUnop<T>(node, UseInfo::AnyTagged(),
@ -3828,12 +3874,16 @@ class RepresentationSelector {
if (input_type.Is(type_cache_->kSafeInteger)) {
VisitUnop<T>(node, UseInfo::None(), MachineRepresentation::kBit);
if (lower<T>()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(1));
DeferReplacement(node, InsertTypeOverrideForVerifier(
Type::Boolean(),
lowering->jsgraph()->Int32Constant(1)));
}
} else if (!input_type.Maybe(Type::Number())) {
VisitUnop<T>(node, UseInfo::Any(), MachineRepresentation::kBit);
if (lower<T>()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(0));
DeferReplacement(node, InsertTypeOverrideForVerifier(
Type::Boolean(),
lowering->jsgraph()->Int32Constant(0)));
}
} else if (input_type.Is(Type::Number())) {
VisitUnop<T>(node, UseInfo::TruncatingFloat64(),
@ -3856,12 +3906,16 @@ class RepresentationSelector {
if (input_type.Is(type_cache_->kSafeInteger)) {
VisitUnop<T>(node, UseInfo::None(), MachineRepresentation::kBit);
if (lower<T>()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(1));
DeferReplacement(node, InsertTypeOverrideForVerifier(
Type::Boolean(),
lowering->jsgraph()->Int32Constant(1)));
}
} else if (!input_type.Maybe(Type::Number())) {
VisitUnop<T>(node, UseInfo::Any(), MachineRepresentation::kBit);
if (lower<T>()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(0));
DeferReplacement(node, InsertTypeOverrideForVerifier(
Type::Boolean(),
lowering->jsgraph()->Int32Constant(0)));
}
} else if (input_type.Is(Type::Number())) {
VisitUnop<T>(node, UseInfo::TruncatingFloat64(),
@ -3882,12 +3936,16 @@ class RepresentationSelector {
if (input_type.Is(type_cache_->kSafeInteger)) {
VisitUnop<T>(node, UseInfo::None(), MachineRepresentation::kBit);
if (lower<T>()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(1));
DeferReplacement(node, InsertTypeOverrideForVerifier(
Type::Boolean(),
lowering->jsgraph()->Int32Constant(1)));
}
} else if (!input_type.Maybe(Type::Number())) {
VisitUnop<T>(node, UseInfo::Any(), MachineRepresentation::kBit);
if (lower<T>()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(0));
DeferReplacement(node, InsertTypeOverrideForVerifier(
Type::Boolean(),
lowering->jsgraph()->Int32Constant(0)));
}
} else if (input_type.Is(Type::Number())) {
VisitUnop<T>(node, UseInfo::TruncatingFloat64(),
@ -3910,12 +3968,16 @@ class RepresentationSelector {
if (input_type.Is(Type::MinusZero())) {
VisitUnop<T>(node, UseInfo::None(), MachineRepresentation::kBit);
if (lower<T>()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(1));
DeferReplacement(node, InsertTypeOverrideForVerifier(
Type::Boolean(),
lowering->jsgraph()->Int32Constant(1)));
}
} else if (!input_type.Maybe(Type::MinusZero())) {
VisitUnop<T>(node, UseInfo::Any(), MachineRepresentation::kBit);
if (lower<T>()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(0));
DeferReplacement(node, InsertTypeOverrideForVerifier(
Type::Boolean(),
lowering->jsgraph()->Int32Constant(0)));
}
} else if (input_type.Is(Type::Number())) {
VisitUnop<T>(node, UseInfo::TruncatingFloat64(),
@ -3933,12 +3995,16 @@ class RepresentationSelector {
if (input_type.Is(Type::NaN())) {
VisitUnop<T>(node, UseInfo::None(), MachineRepresentation::kBit);
if (lower<T>()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(1));
DeferReplacement(node, InsertTypeOverrideForVerifier(
Type::Boolean(),
lowering->jsgraph()->Int32Constant(1)));
}
} else if (!input_type.Maybe(Type::NaN())) {
VisitUnop<T>(node, UseInfo::Any(), MachineRepresentation::kBit);
if (lower<T>()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(0));
DeferReplacement(node, InsertTypeOverrideForVerifier(
Type::Boolean(),
lowering->jsgraph()->Int32Constant(0)));
}
} else if (input_type.Is(Type::Number())) {
VisitUnop<T>(node, UseInfo::TruncatingFloat64(),
@ -4123,7 +4189,7 @@ class RepresentationSelector {
// for the sigma's type.
Type type = TypeOf(node);
MachineRepresentation representation =
GetOutputInfoForPhi(node, type, truncation);
GetOutputInfoForPhi(type, truncation);
// Here we pretend that the input has the sigma's type for the
// conversion.
@ -4256,6 +4322,65 @@ class RepresentationSelector {
}
return;
}
case IrOpcode::kDebugBreak:
return;
// Nodes from machine graphs.
case IrOpcode::kEnterMachineGraph: {
DCHECK_EQ(1, node->op()->ValueInputCount());
UseInfo use_info = OpParameter<UseInfo>(node->op());
ProcessInput<T>(node, 0, use_info);
SetOutput<T>(node, use_info.representation());
if (lower<T>()) {
DeferReplacement(node, InsertTypeOverrideForVerifier(
Type::Machine(), node->InputAt(0)));
}
return;
}
case IrOpcode::kExitMachineGraph: {
DCHECK_EQ(1, node->op()->ValueInputCount());
ProcessInput<T>(node, 0, UseInfo::Any());
const auto& p = ExitMachineGraphParametersOf(node->op());
SetOutput<T>(node, p.output_representation(), p.output_type());
if (lower<T>()) {
DeferReplacement(node, InsertTypeOverrideForVerifier(
p.output_type(), node->InputAt(0)));
}
return;
}
case IrOpcode::kInt32Add:
case IrOpcode::kInt32Sub:
case IrOpcode::kUint32LessThan:
case IrOpcode::kUint32LessThanOrEqual:
case IrOpcode::kUint64LessThanOrEqual:
case IrOpcode::kUint32Div:
case IrOpcode::kWord32And:
case IrOpcode::kWord32Equal:
case IrOpcode::kWord32Or:
case IrOpcode::kWord32Shl:
case IrOpcode::kWord32Shr:
for (int i = 0; i < node->InputCount(); ++i) {
ProcessInput<T>(node, i, UseInfo::Any());
}
SetOutput<T>(node, MachineRepresentation::kWord32);
return;
case IrOpcode::kInt64Add:
case IrOpcode::kInt64Sub:
case IrOpcode::kUint64Div:
case IrOpcode::kWord64And:
case IrOpcode::kWord64Shl:
case IrOpcode::kWord64Shr:
for (int i = 0; i < node->InputCount(); ++i) {
ProcessInput<T>(node, i, UseInfo::Any());
}
SetOutput<T>(node, MachineRepresentation::kWord64);
return;
case IrOpcode::kLoad:
for (int i = 0; i < node->InputCount(); ++i) {
ProcessInput<T>(node, i, UseInfo::Any());
}
SetOutput<T>(node, LoadRepresentationOf(node->op()).representation());
return;
default:
FATAL(
@ -4301,18 +4426,6 @@ class RepresentationSelector {
return node;
}
void ChangeToSemanticsHintForVerifier(Node* node, const Operator* semantics) {
DCHECK_EQ(node->op()->ValueInputCount(), 1);
DCHECK_EQ(node->op()->EffectInputCount(), 0);
DCHECK_EQ(node->op()->ControlInputCount(), 0);
if (verification_enabled()) {
ChangeOp(node, common()->SLVerifierHint(semantics, base::nullopt));
verifier_->RecordHint(node);
} else {
DeferReplacement(node, node->InputAt(0));
}
}
private:
void ChangeOp(Node* node, const Operator* new_op) {
compiler::NodeProperties::ChangeOp(node, new_op);
@ -4565,8 +4678,9 @@ void SimplifiedLowering::DoJSToNumberOrNumericTruncatesToFloat64(
Node* control = node->InputAt(4);
Node* check0 = graph()->NewNode(simplified()->ObjectIsSmi(), value);
Node* branch0 =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check0, control);
Node* branch0 = graph()->NewNode(
common()->Branch(BranchHint::kTrue, BranchSemantics::kMachine), check0,
control);
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
Node* etrue0 = effect;
@ -4604,7 +4718,9 @@ void SimplifiedLowering::DoJSToNumberOrNumericTruncatesToFloat64(
}
Node* check1 = graph()->NewNode(simplified()->ObjectIsSmi(), vfalse0);
Node* branch1 = graph()->NewNode(common()->Branch(), check1, if_false0);
Node* branch1 = graph()->NewNode(
common()->Branch(BranchHint::kNone, BranchSemantics::kMachine), check1,
if_false0);
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* etrue1 = efalse0;
@ -4667,8 +4783,9 @@ void SimplifiedLowering::DoJSToNumberOrNumericTruncatesToWord32(
Node* control = node->InputAt(4);
Node* check0 = graph()->NewNode(simplified()->ObjectIsSmi(), value);
Node* branch0 =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check0, control);
Node* branch0 = graph()->NewNode(
common()->Branch(BranchHint::kTrue, BranchSemantics::kMachine), check0,
control);
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
Node* etrue0 = effect;
@ -4703,7 +4820,9 @@ void SimplifiedLowering::DoJSToNumberOrNumericTruncatesToWord32(
}
Node* check1 = graph()->NewNode(simplified()->ObjectIsSmi(), vfalse0);
Node* branch1 = graph()->NewNode(common()->Branch(), check1, if_false0);
Node* branch1 = graph()->NewNode(
common()->Branch(BranchHint::kNone, BranchSemantics::kMachine), check1,
if_false0);
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* etrue1 = efalse0;
@ -4831,7 +4950,8 @@ Node* SimplifiedLowering::Int32Div(Node* const node) {
common()->Phi(MachineRepresentation::kWord32, 2);
Node* check0 = graph()->NewNode(machine()->Int32LessThan(), zero, rhs);
Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kTrue), check0,
Node* branch0 = graph()->NewNode(
common()->Branch(BranchHint::kTrue, BranchSemantics::kMachine), check0,
graph()->start());
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
@ -4841,7 +4961,9 @@ Node* SimplifiedLowering::Int32Div(Node* const node) {
Node* false0;
{
Node* check1 = graph()->NewNode(machine()->Int32LessThan(), rhs, minus_one);
Node* branch1 = graph()->NewNode(common()->Branch(), check1, if_false0);
Node* branch1 = graph()->NewNode(
common()->Branch(BranchHint::kNone, BranchSemantics::kMachine), check1,
if_false0);
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* true1 = graph()->NewNode(machine()->Int32Div(), lhs, rhs, if_true1);
@ -4850,7 +4972,9 @@ Node* SimplifiedLowering::Int32Div(Node* const node) {
Node* false1;
{
Node* check2 = graph()->NewNode(machine()->Word32Equal(), rhs, zero);
Node* branch2 = graph()->NewNode(common()->Branch(), check2, if_false1);
Node* branch2 = graph()->NewNode(
common()->Branch(BranchHint::kNone, BranchSemantics::kMachine),
check2, if_false1);
Node* if_true2 = graph()->NewNode(common()->IfTrue(), branch2);
Node* true2 = zero;
@ -4908,7 +5032,8 @@ Node* SimplifiedLowering::Int32Mod(Node* const node) {
common()->Phi(MachineRepresentation::kWord32, 2);
Node* check0 = graph()->NewNode(machine()->Int32LessThan(), zero, rhs);
Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kTrue), check0,
Node* branch0 = graph()->NewNode(
common()->Branch(BranchHint::kTrue, BranchSemantics::kMachine), check0,
graph()->start());
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
@ -4917,7 +5042,9 @@ Node* SimplifiedLowering::Int32Mod(Node* const node) {
Node* msk = graph()->NewNode(machine()->Int32Add(), rhs, minus_one);
Node* check1 = graph()->NewNode(machine()->Word32And(), rhs, msk);
Node* branch1 = graph()->NewNode(common()->Branch(), check1, if_true0);
Node* branch1 = graph()->NewNode(
common()->Branch(BranchHint::kNone, BranchSemantics::kMachine), check1,
if_true0);
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* true1 = graph()->NewNode(machine()->Int32Mod(), lhs, rhs, if_true1);
@ -4926,7 +5053,8 @@ Node* SimplifiedLowering::Int32Mod(Node* const node) {
Node* false1;
{
Node* check2 = graph()->NewNode(machine()->Int32LessThan(), lhs, zero);
Node* branch2 = graph()->NewNode(common()->Branch(BranchHint::kFalse),
Node* branch2 = graph()->NewNode(
common()->Branch(BranchHint::kFalse, BranchSemantics::kMachine),
check2, if_false1);
Node* if_true2 = graph()->NewNode(common()->IfTrue(), branch2);
@ -4951,8 +5079,9 @@ Node* SimplifiedLowering::Int32Mod(Node* const node) {
Node* false0;
{
Node* check1 = graph()->NewNode(machine()->Int32LessThan(), rhs, minus_one);
Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kTrue),
check1, if_false0);
Node* branch1 = graph()->NewNode(
common()->Branch(BranchHint::kTrue, BranchSemantics::kMachine), check1,
if_false0);
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* true1 = graph()->NewNode(machine()->Int32Mod(), lhs, rhs, if_true1);
@ -4997,7 +5126,8 @@ Node* SimplifiedLowering::Uint32Div(Node* const node) {
}
Node* check = graph()->NewNode(machine()->Word32Equal(), rhs, zero);
Diamond d(graph(), common(), check, BranchHint::kFalse);
Diamond d(graph(), common(), check, BranchHint::kFalse,
BranchSemantics::kMachine);
Node* div = graph()->NewNode(machine()->Uint32Div(), lhs, rhs, d.if_false);
return d.Phi(MachineRepresentation::kWord32, zero, div);
}
@ -5034,7 +5164,8 @@ Node* SimplifiedLowering::Uint32Mod(Node* const node) {
common()->Phi(MachineRepresentation::kWord32, 2);
Node* check0 = graph()->NewNode(machine()->Word32Equal(), rhs, zero);
Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kFalse), check0,
Node* branch0 = graph()->NewNode(
common()->Branch(BranchHint::kFalse, BranchSemantics::kMachine), check0,
graph()->start());
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
@ -5046,7 +5177,9 @@ Node* SimplifiedLowering::Uint32Mod(Node* const node) {
Node* msk = graph()->NewNode(machine()->Int32Add(), rhs, minus_one);
Node* check1 = graph()->NewNode(machine()->Word32And(), rhs, msk);
Node* branch1 = graph()->NewNode(common()->Branch(), check1, if_false0);
Node* branch1 = graph()->NewNode(
common()->Branch(BranchHint::kNone, BranchSemantics::kMachine), check1,
if_false0);
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* true1 = graph()->NewNode(machine()->Uint32Mod(), lhs, rhs, if_true1);

View File

@ -88,6 +88,7 @@ class Typer::Visitor : public Reducer {
SIMPLIFIED_BIGINT_BINOP_LIST(DECLARE_BINARY_CASE)
SIMPLIFIED_SPECULATIVE_NUMBER_BINOP_LIST(DECLARE_BINARY_CASE)
SIMPLIFIED_SPECULATIVE_BIGINT_BINOP_LIST(DECLARE_BINARY_CASE)
TYPER_SUPPORTED_MACHINE_BINOP_LIST(DECLARE_BINARY_CASE)
#undef DECLARE_BINARY_CASE
#define DECLARE_OTHER_CASE(x, ...) \
case IrOpcode::k##x: \
@ -125,7 +126,139 @@ class Typer::Visitor : public Reducer {
SIMPLIFIED_CHECKED_OP_LIST(DECLARE_IMPOSSIBLE_CASE)
IF_WASM(SIMPLIFIED_WASM_OP_LIST, DECLARE_IMPOSSIBLE_CASE)
MACHINE_SIMD_OP_LIST(DECLARE_IMPOSSIBLE_CASE)
MACHINE_OP_LIST(DECLARE_IMPOSSIBLE_CASE)
MACHINE_UNOP_32_LIST(DECLARE_IMPOSSIBLE_CASE)
DECLARE_IMPOSSIBLE_CASE(Word32Xor)
DECLARE_IMPOSSIBLE_CASE(Word32Sar)
DECLARE_IMPOSSIBLE_CASE(Word32Rol)
DECLARE_IMPOSSIBLE_CASE(Word32Ror)
DECLARE_IMPOSSIBLE_CASE(Int32AddWithOverflow)
DECLARE_IMPOSSIBLE_CASE(Int32SubWithOverflow)
DECLARE_IMPOSSIBLE_CASE(Int32Mul)
DECLARE_IMPOSSIBLE_CASE(Int32MulWithOverflow)
DECLARE_IMPOSSIBLE_CASE(Int32MulHigh)
DECLARE_IMPOSSIBLE_CASE(Int32Div)
DECLARE_IMPOSSIBLE_CASE(Int32Mod)
DECLARE_IMPOSSIBLE_CASE(Uint32Mod)
DECLARE_IMPOSSIBLE_CASE(Uint32MulHigh)
DECLARE_IMPOSSIBLE_CASE(Word64Or)
DECLARE_IMPOSSIBLE_CASE(Word64Xor)
DECLARE_IMPOSSIBLE_CASE(Word64Sar)
DECLARE_IMPOSSIBLE_CASE(Word64Rol)
DECLARE_IMPOSSIBLE_CASE(Word64Ror)
DECLARE_IMPOSSIBLE_CASE(Word64RolLowerable)
DECLARE_IMPOSSIBLE_CASE(Word64RorLowerable)
DECLARE_IMPOSSIBLE_CASE(Int64AddWithOverflow)
DECLARE_IMPOSSIBLE_CASE(Int64SubWithOverflow)
DECLARE_IMPOSSIBLE_CASE(Int64Mul)
DECLARE_IMPOSSIBLE_CASE(Int64MulHigh)
DECLARE_IMPOSSIBLE_CASE(Int64MulWithOverflow)
DECLARE_IMPOSSIBLE_CASE(Int64Div)
DECLARE_IMPOSSIBLE_CASE(Int64Mod)
DECLARE_IMPOSSIBLE_CASE(Uint64Mod)
DECLARE_IMPOSSIBLE_CASE(Uint64MulHigh)
DECLARE_IMPOSSIBLE_CASE(Word64Equal)
DECLARE_IMPOSSIBLE_CASE(Int32LessThan)
DECLARE_IMPOSSIBLE_CASE(Int32LessThanOrEqual)
DECLARE_IMPOSSIBLE_CASE(Int64LessThan)
DECLARE_IMPOSSIBLE_CASE(Int64LessThanOrEqual)
DECLARE_IMPOSSIBLE_CASE(Uint64LessThan)
DECLARE_IMPOSSIBLE_CASE(Float32Equal)
DECLARE_IMPOSSIBLE_CASE(Float32LessThan)
DECLARE_IMPOSSIBLE_CASE(Float32LessThanOrEqual)
DECLARE_IMPOSSIBLE_CASE(Float64Equal)
DECLARE_IMPOSSIBLE_CASE(Float64LessThan)
DECLARE_IMPOSSIBLE_CASE(Float64LessThanOrEqual)
MACHINE_FLOAT32_BINOP_LIST(DECLARE_IMPOSSIBLE_CASE)
MACHINE_FLOAT32_UNOP_LIST(DECLARE_IMPOSSIBLE_CASE)
MACHINE_FLOAT64_BINOP_LIST(DECLARE_IMPOSSIBLE_CASE)
MACHINE_FLOAT64_UNOP_LIST(DECLARE_IMPOSSIBLE_CASE)
MACHINE_ATOMIC_OP_LIST(DECLARE_IMPOSSIBLE_CASE)
DECLARE_IMPOSSIBLE_CASE(AbortCSADcheck)
DECLARE_IMPOSSIBLE_CASE(DebugBreak)
DECLARE_IMPOSSIBLE_CASE(Comment)
DECLARE_IMPOSSIBLE_CASE(LoadImmutable)
DECLARE_IMPOSSIBLE_CASE(Store)
DECLARE_IMPOSSIBLE_CASE(StackSlot)
DECLARE_IMPOSSIBLE_CASE(Word32Popcnt)
DECLARE_IMPOSSIBLE_CASE(Word64Popcnt)
DECLARE_IMPOSSIBLE_CASE(Word64Clz)
DECLARE_IMPOSSIBLE_CASE(Word64Ctz)
DECLARE_IMPOSSIBLE_CASE(Word64ClzLowerable)
DECLARE_IMPOSSIBLE_CASE(Word64CtzLowerable)
DECLARE_IMPOSSIBLE_CASE(Word64ReverseBits)
DECLARE_IMPOSSIBLE_CASE(Word64ReverseBytes)
DECLARE_IMPOSSIBLE_CASE(Simd128ReverseBytes)
DECLARE_IMPOSSIBLE_CASE(Int64AbsWithOverflow)
DECLARE_IMPOSSIBLE_CASE(BitcastTaggedToWord)
DECLARE_IMPOSSIBLE_CASE(BitcastTaggedToWordForTagAndSmiBits)
DECLARE_IMPOSSIBLE_CASE(BitcastWordToTagged)
DECLARE_IMPOSSIBLE_CASE(BitcastWordToTaggedSigned)
DECLARE_IMPOSSIBLE_CASE(TruncateFloat64ToWord32)
DECLARE_IMPOSSIBLE_CASE(ChangeFloat32ToFloat64)
DECLARE_IMPOSSIBLE_CASE(ChangeFloat64ToInt32)
DECLARE_IMPOSSIBLE_CASE(ChangeFloat64ToInt64)
DECLARE_IMPOSSIBLE_CASE(ChangeFloat64ToUint32)
DECLARE_IMPOSSIBLE_CASE(ChangeFloat64ToUint64)
DECLARE_IMPOSSIBLE_CASE(Float64SilenceNaN)
DECLARE_IMPOSSIBLE_CASE(TruncateFloat64ToInt64)
DECLARE_IMPOSSIBLE_CASE(TruncateFloat64ToUint32)
DECLARE_IMPOSSIBLE_CASE(TruncateFloat32ToInt32)
DECLARE_IMPOSSIBLE_CASE(TruncateFloat32ToUint32)
DECLARE_IMPOSSIBLE_CASE(TryTruncateFloat32ToInt64)
DECLARE_IMPOSSIBLE_CASE(TryTruncateFloat64ToInt64)
DECLARE_IMPOSSIBLE_CASE(TryTruncateFloat32ToUint64)
DECLARE_IMPOSSIBLE_CASE(TryTruncateFloat64ToUint64)
DECLARE_IMPOSSIBLE_CASE(TryTruncateFloat64ToInt32)
DECLARE_IMPOSSIBLE_CASE(TryTruncateFloat64ToUint32)
DECLARE_IMPOSSIBLE_CASE(ChangeInt32ToFloat64)
DECLARE_IMPOSSIBLE_CASE(BitcastWord32ToWord64)
DECLARE_IMPOSSIBLE_CASE(ChangeInt32ToInt64)
DECLARE_IMPOSSIBLE_CASE(ChangeInt64ToFloat64)
DECLARE_IMPOSSIBLE_CASE(ChangeUint32ToFloat64)
DECLARE_IMPOSSIBLE_CASE(ChangeUint32ToUint64)
DECLARE_IMPOSSIBLE_CASE(TruncateFloat64ToFloat32)
DECLARE_IMPOSSIBLE_CASE(TruncateInt64ToInt32)
DECLARE_IMPOSSIBLE_CASE(RoundFloat64ToInt32)
DECLARE_IMPOSSIBLE_CASE(RoundInt32ToFloat32)
DECLARE_IMPOSSIBLE_CASE(RoundInt64ToFloat32)
DECLARE_IMPOSSIBLE_CASE(RoundInt64ToFloat64)
DECLARE_IMPOSSIBLE_CASE(RoundUint32ToFloat32)
DECLARE_IMPOSSIBLE_CASE(RoundUint64ToFloat32)
DECLARE_IMPOSSIBLE_CASE(RoundUint64ToFloat64)
DECLARE_IMPOSSIBLE_CASE(BitcastFloat32ToInt32)
DECLARE_IMPOSSIBLE_CASE(BitcastFloat64ToInt64)
DECLARE_IMPOSSIBLE_CASE(BitcastInt32ToFloat32)
DECLARE_IMPOSSIBLE_CASE(BitcastInt64ToFloat64)
DECLARE_IMPOSSIBLE_CASE(Float64ExtractLowWord32)
DECLARE_IMPOSSIBLE_CASE(Float64ExtractHighWord32)
DECLARE_IMPOSSIBLE_CASE(Float64InsertLowWord32)
DECLARE_IMPOSSIBLE_CASE(Float64InsertHighWord32)
DECLARE_IMPOSSIBLE_CASE(Word32Select)
DECLARE_IMPOSSIBLE_CASE(Word64Select)
DECLARE_IMPOSSIBLE_CASE(Float32Select)
DECLARE_IMPOSSIBLE_CASE(Float64Select)
DECLARE_IMPOSSIBLE_CASE(LoadStackCheckOffset)
DECLARE_IMPOSSIBLE_CASE(LoadFramePointer)
DECLARE_IMPOSSIBLE_CASE(LoadParentFramePointer)
DECLARE_IMPOSSIBLE_CASE(UnalignedLoad)
DECLARE_IMPOSSIBLE_CASE(UnalignedStore)
DECLARE_IMPOSSIBLE_CASE(Int32PairAdd)
DECLARE_IMPOSSIBLE_CASE(Int32PairSub)
DECLARE_IMPOSSIBLE_CASE(Int32PairMul)
DECLARE_IMPOSSIBLE_CASE(Word32PairShl)
DECLARE_IMPOSSIBLE_CASE(Word32PairShr)
DECLARE_IMPOSSIBLE_CASE(Word32PairSar)
DECLARE_IMPOSSIBLE_CASE(ProtectedLoad)
DECLARE_IMPOSSIBLE_CASE(ProtectedStore)
DECLARE_IMPOSSIBLE_CASE(MemoryBarrier)
DECLARE_IMPOSSIBLE_CASE(SignExtendWord8ToInt32)
DECLARE_IMPOSSIBLE_CASE(SignExtendWord16ToInt32)
DECLARE_IMPOSSIBLE_CASE(SignExtendWord8ToInt64)
DECLARE_IMPOSSIBLE_CASE(SignExtendWord16ToInt64)
DECLARE_IMPOSSIBLE_CASE(SignExtendWord32ToInt64)
DECLARE_IMPOSSIBLE_CASE(StackPointerGreaterThan)
DECLARE_IMPOSSIBLE_CASE(TraceInstruction)
#undef DECLARE_IMPOSSIBLE_CASE
UNREACHABLE();
}
@ -228,6 +361,7 @@ class Typer::Visitor : public Reducer {
SIMPLIFIED_BIGINT_BINOP_LIST(DECLARE_METHOD)
SIMPLIFIED_SPECULATIVE_NUMBER_BINOP_LIST(DECLARE_METHOD)
SIMPLIFIED_SPECULATIVE_BIGINT_BINOP_LIST(DECLARE_METHOD)
TYPER_SUPPORTED_MACHINE_BINOP_LIST(DECLARE_METHOD)
#undef DECLARE_METHOD
#define DECLARE_METHOD(Name, ...) \
inline Type Type##Name(Type left, Type right) { \
@ -243,6 +377,7 @@ class Typer::Visitor : public Reducer {
SIMPLIFIED_BIGINT_BINOP_LIST(DECLARE_METHOD)
SIMPLIFIED_SPECULATIVE_NUMBER_BINOP_LIST(DECLARE_METHOD)
SIMPLIFIED_SPECULATIVE_BIGINT_BINOP_LIST(DECLARE_METHOD)
TYPER_SUPPORTED_MACHINE_BINOP_LIST(DECLARE_METHOD)
#undef DECLARE_METHOD
#define DECLARE_METHOD(Name, ...) \
inline Type Type##Name(Type input) { return TypeUnaryOp(input, Name); }
@ -728,9 +863,9 @@ Type Typer::Visitor::TypeOsrValue(Node* node) {
Type Typer::Visitor::TypeRetain(Node* node) { UNREACHABLE(); }
Type Typer::Visitor::TypeInt32Constant(Node* node) { UNREACHABLE(); }
Type Typer::Visitor::TypeInt32Constant(Node* node) { return Type::Machine(); }
Type Typer::Visitor::TypeInt64Constant(Node* node) { UNREACHABLE(); }
Type Typer::Visitor::TypeInt64Constant(Node* node) { return Type::Machine(); }
Type Typer::Visitor::TypeTaggedIndexConstant(Node* node) { UNREACHABLE(); }
@ -774,6 +909,14 @@ Type Typer::Visitor::TypePhi(Node* node) {
return type;
}
Type Typer::Visitor::TypeEnterMachineGraph(Node* node) {
return Type::Machine();
}
Type Typer::Visitor::TypeExitMachineGraph(Node* node) {
return ExitMachineGraphParametersOf(node->op()).output_type();
}
Type Typer::Visitor::TypeInductionVariablePhi(Node* node) {
int arity = NodeProperties::GetControlInput(node)->op()->ControlInputCount();
DCHECK_EQ(IrOpcode::kLoop, NodeProperties::GetControlInput(node)->opcode());

View File

@ -49,6 +49,7 @@ namespace compiler {
//
// Constant(x) < T iff instance_type(map(x)) < T
//
// None <= Machine <= Any
//
// RANGE TYPES
//
@ -140,7 +141,8 @@ namespace compiler {
// We split the macro list into two parts because the Torque equivalent in
// turbofan-types.tq uses two 32bit bitfield structs.
#define PROPER_ATOMIC_BITSET_TYPE_HIGH_LIST(V) \
V(SandboxedPointer, uint64_t{1} << 32)
V(SandboxedPointer, uint64_t{1} << 32) \
V(Machine, uint64_t{1} << 33)
#define PROPER_BITSET_TYPE_LIST(V) \
V(None, uint64_t{0}) \

View File

@ -312,8 +312,16 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
}
CHECK_EQ(1, count_true);
CHECK_EQ(1, count_false);
switch (BranchParametersOf(node->op()).semantics()) {
case BranchSemantics::kJS:
case BranchSemantics::kUnspecified:
// The condition must be a Boolean.
CheckValueInputIs(node, 0, Type::Boolean());
break;
case BranchSemantics::kMachine:
CheckValueInputIs(node, 0, Type::Machine());
break;
}
CheckNotTyped(node);
break;
}
@ -606,6 +614,12 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
case IrOpcode::kTailCall:
// TODO(bmeurer): what are the constraints on these?
break;
case IrOpcode::kEnterMachineGraph:
CheckTypeIs(node, Type::Machine());
break;
case IrOpcode::kExitMachineGraph:
CheckValueInputIs(node, 0, Type::Machine());
break;
// JavaScript operators
// --------------------

View File

@ -41,11 +41,12 @@ ObjectAccess ObjectAccessForGCStores(wasm::ValueType type);
class WasmGraphAssembler : public GraphAssembler {
public:
WasmGraphAssembler(MachineGraph* mcgraph, Zone* zone)
: GraphAssembler(mcgraph, zone), simplified_(zone) {}
: GraphAssembler(mcgraph, zone, BranchSemantics::kMachine),
simplified_(zone) {}
template <typename... Args>
Node* CallRuntimeStub(wasm::WasmCode::RuntimeStubId stub_id,
Operator::Properties properties, Args*... args) {
Operator::Properties properties, Args... args) {
auto* call_descriptor = GetBuiltinCallDescriptor(
WasmRuntimeStubIdToBuiltinName(stub_id), temp_zone(),
StubCallMode::kCallWasmRuntimeStub, false, properties);
@ -63,7 +64,7 @@ class WasmGraphAssembler : public GraphAssembler {
template <typename... Args>
Node* CallBuiltin(Builtin name, Operator::Properties properties,
Args*... args) {
Args... args) {
auto* call_descriptor = GetBuiltinCallDescriptor(
name, temp_zone(), StubCallMode::kCallBuiltinPointer, false,
properties);
@ -268,7 +269,7 @@ class WasmGraphAssembler : public GraphAssembler {
effect(), control()));
}
SimplifiedOperatorBuilder* simplified() { return &simplified_; }
SimplifiedOperatorBuilder* simplified() override { return &simplified_; }
private:
SimplifiedOperatorBuilder simplified_;

View File

@ -833,7 +833,7 @@ DEFINE_BOOL(verify_csa, DEBUG_BOOL,
// non-ENABLE_VERIFY_CSA configuration.
DEFINE_BOOL_READONLY(verify_csa, false,
"verify TurboFan machine graph of code stubs")
#endif
#endif // ENABLE_VERIFY_CSA
DEFINE_BOOL(trace_verify_csa, false, "trace code stubs verification")
DEFINE_STRING(csa_trap_on_node, nullptr,
"trigger break point when a node with given id is created in "
@ -934,6 +934,9 @@ DEFINE_BOOL_READONLY(turbo_rewrite_far_jumps, false,
"rewrite far to near jumps (ia32,x64)")
#endif
DEFINE_BOOL(
turbo_rab_gsab, true,
"optimize ResizableArrayBuffer / GrowableSharedArrayBuffer in TurboFan")
DEFINE_BOOL(
stress_gc_during_compilation, false,
"simulate GC/compiler thread race related to https://crbug.com/v8/8520")

View File

@ -12,62 +12,65 @@
namespace v8 {
namespace internal {
int ElementsKindToShiftSize(ElementsKind elements_kind) {
switch (elements_kind) {
case UINT8_ELEMENTS:
case INT8_ELEMENTS:
case UINT8_CLAMPED_ELEMENTS:
case RAB_GSAB_UINT8_ELEMENTS:
case RAB_GSAB_INT8_ELEMENTS:
case RAB_GSAB_UINT8_CLAMPED_ELEMENTS:
namespace {
constexpr size_t size_to_shift(size_t size) {
switch (size) {
case 1:
return 0;
case UINT16_ELEMENTS:
case INT16_ELEMENTS:
case RAB_GSAB_UINT16_ELEMENTS:
case RAB_GSAB_INT16_ELEMENTS:
case 2:
return 1;
case UINT32_ELEMENTS:
case INT32_ELEMENTS:
case FLOAT32_ELEMENTS:
case RAB_GSAB_UINT32_ELEMENTS:
case RAB_GSAB_INT32_ELEMENTS:
case RAB_GSAB_FLOAT32_ELEMENTS:
case 4:
return 2;
case PACKED_DOUBLE_ELEMENTS:
case HOLEY_DOUBLE_ELEMENTS:
case FLOAT64_ELEMENTS:
case BIGINT64_ELEMENTS:
case BIGUINT64_ELEMENTS:
case RAB_GSAB_FLOAT64_ELEMENTS:
case RAB_GSAB_BIGINT64_ELEMENTS:
case RAB_GSAB_BIGUINT64_ELEMENTS:
case 8:
return 3;
case PACKED_SMI_ELEMENTS:
case PACKED_ELEMENTS:
case PACKED_FROZEN_ELEMENTS:
case PACKED_SEALED_ELEMENTS:
case PACKED_NONEXTENSIBLE_ELEMENTS:
case HOLEY_SMI_ELEMENTS:
case HOLEY_ELEMENTS:
case HOLEY_FROZEN_ELEMENTS:
case HOLEY_SEALED_ELEMENTS:
case HOLEY_NONEXTENSIBLE_ELEMENTS:
case DICTIONARY_ELEMENTS:
case FAST_SLOPPY_ARGUMENTS_ELEMENTS:
case SLOW_SLOPPY_ARGUMENTS_ELEMENTS:
case FAST_STRING_WRAPPER_ELEMENTS:
case SLOW_STRING_WRAPPER_ELEMENTS:
case SHARED_ARRAY_ELEMENTS:
return kTaggedSizeLog2;
case WASM_ARRAY_ELEMENTS:
case NO_ELEMENTS:
default:
UNREACHABLE();
}
UNREACHABLE();
}
} // namespace
constexpr uint8_t kTypedArrayAndRabGsabTypedArrayElementsKindShifts[] = {
#define SHIFT(Type, type, TYPE, ctype) size_to_shift(sizeof(ctype)),
TYPED_ARRAYS(SHIFT) RAB_GSAB_TYPED_ARRAYS(SHIFT)
#undef SHIFT
};
constexpr uint8_t kTypedArrayAndRabGsabTypedArrayElementsKindSizes[] = {
#define SIZE(Type, type, TYPE, ctype) sizeof(ctype),
TYPED_ARRAYS(SIZE) RAB_GSAB_TYPED_ARRAYS(SIZE)
#undef SIZE
};
#define VERIFY_SHIFT(Type, type, TYPE, ctype) \
static_assert( \
kTypedArrayAndRabGsabTypedArrayElementsKindShifts \
[ElementsKind::TYPE##_ELEMENTS - \
ElementsKind::FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND] == \
ElementsKindToShiftSize(ElementsKind::TYPE##_ELEMENTS), \
"Shift of ElementsKind::" #TYPE \
"_ELEMENTS does not match in static table");
TYPED_ARRAYS(VERIFY_SHIFT)
RAB_GSAB_TYPED_ARRAYS(VERIFY_SHIFT)
#undef VERIFY_SHIFT
#define VERIFY_SIZE(Type, type, TYPE, ctype) \
static_assert( \
kTypedArrayAndRabGsabTypedArrayElementsKindSizes \
[ElementsKind::TYPE##_ELEMENTS - \
ElementsKind::FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND] == \
ElementsKindToByteSize(ElementsKind::TYPE##_ELEMENTS), \
"Size of ElementsKind::" #TYPE \
"_ELEMENTS does not match in static table");
TYPED_ARRAYS(VERIFY_SIZE)
RAB_GSAB_TYPED_ARRAYS(VERIFY_SIZE)
#undef VERIFY_SIZE
const uint8_t* TypedArrayAndRabGsabTypedArrayElementsKindShifts() {
return &kTypedArrayAndRabGsabTypedArrayElementsKindShifts[0];
}
int ElementsKindToByteSize(ElementsKind elements_kind) {
return 1 << ElementsKindToShiftSize(elements_kind);
const uint8_t* TypedArrayAndRabGsabTypedArrayElementsKindSizes() {
return &kTypedArrayAndRabGsabTypedArrayElementsKindSizes[0];
}
int GetDefaultHeaderSizeForElementsKind(ElementsKind elements_kind) {

View File

@ -169,8 +169,64 @@ constexpr int kFastElementsKindBits = 3;
static_assert((1 << kFastElementsKindBits) > LAST_FAST_ELEMENTS_KIND);
static_assert((1 << (kFastElementsKindBits - 1)) <= LAST_FAST_ELEMENTS_KIND);
V8_EXPORT_PRIVATE int ElementsKindToShiftSize(ElementsKind elements_kind);
V8_EXPORT_PRIVATE int ElementsKindToByteSize(ElementsKind elements_kind);
const uint8_t* TypedArrayAndRabGsabTypedArrayElementsKindShifts();
const uint8_t* TypedArrayAndRabGsabTypedArrayElementsKindSizes();
inline constexpr int ElementsKindToShiftSize(ElementsKind elements_kind) {
switch (elements_kind) {
case UINT8_ELEMENTS:
case INT8_ELEMENTS:
case UINT8_CLAMPED_ELEMENTS:
case RAB_GSAB_UINT8_ELEMENTS:
case RAB_GSAB_INT8_ELEMENTS:
case RAB_GSAB_UINT8_CLAMPED_ELEMENTS:
return 0;
case UINT16_ELEMENTS:
case INT16_ELEMENTS:
case RAB_GSAB_UINT16_ELEMENTS:
case RAB_GSAB_INT16_ELEMENTS:
return 1;
case UINT32_ELEMENTS:
case INT32_ELEMENTS:
case FLOAT32_ELEMENTS:
case RAB_GSAB_UINT32_ELEMENTS:
case RAB_GSAB_INT32_ELEMENTS:
case RAB_GSAB_FLOAT32_ELEMENTS:
return 2;
case PACKED_DOUBLE_ELEMENTS:
case HOLEY_DOUBLE_ELEMENTS:
case FLOAT64_ELEMENTS:
case BIGINT64_ELEMENTS:
case BIGUINT64_ELEMENTS:
case RAB_GSAB_FLOAT64_ELEMENTS:
case RAB_GSAB_BIGINT64_ELEMENTS:
case RAB_GSAB_BIGUINT64_ELEMENTS:
return 3;
case PACKED_SMI_ELEMENTS:
case PACKED_ELEMENTS:
case PACKED_FROZEN_ELEMENTS:
case PACKED_SEALED_ELEMENTS:
case PACKED_NONEXTENSIBLE_ELEMENTS:
case HOLEY_SMI_ELEMENTS:
case HOLEY_ELEMENTS:
case HOLEY_FROZEN_ELEMENTS:
case HOLEY_SEALED_ELEMENTS:
case HOLEY_NONEXTENSIBLE_ELEMENTS:
case DICTIONARY_ELEMENTS:
case FAST_SLOPPY_ARGUMENTS_ELEMENTS:
case SLOW_SLOPPY_ARGUMENTS_ELEMENTS:
case FAST_STRING_WRAPPER_ELEMENTS:
case SLOW_STRING_WRAPPER_ELEMENTS:
case SHARED_ARRAY_ELEMENTS:
return kTaggedSizeLog2;
case WASM_ARRAY_ELEMENTS:
case NO_ELEMENTS:
UNREACHABLE();
}
UNREACHABLE();
}
inline constexpr int ElementsKindToByteSize(ElementsKind elements_kind) {
return 1 << ElementsKindToShiftSize(elements_kind);
}
int GetDefaultHeaderSizeForElementsKind(ElementsKind elements_kind);
const char* ElementsKindToString(ElementsKind kind);

View File

@ -54,6 +54,7 @@ bitfield struct TurbofanTypeLowBits extends uint32 {
bitfield struct TurbofanTypeHighBits extends uint32 {
sandboxed_pointer: bool: 1 bit;
machine: bool: 1 bit;
}
@export

View File

@ -3,25 +3,823 @@
// found in the LICENSE file.
// Flags: --harmony-rab-gsab --allow-natives-syntax --turbofan
// Flags: --no-always-turbofan --turbo-rab-gsab
"use strict";
d8.file.execute('test/mjsunit/typedarray-helpers.js');
(function TypedArrayLength() {
for(let ctor of ctors) {
// We have to make sure that we construct a new string for each case to
// prevent the compiled function from being reused with spoiled feedback.
const test = new Function('\
const rab = CreateResizableArrayBuffer(16, 40); \
const ta = new ' + ctor.name + '(rab); \
rab.resize(32); \
return ta.length;');
const is_little_endian = (() => {
var buffer = new ArrayBuffer(4);
const HEAP32 = new Int32Array(buffer);
const HEAPU8 = new Uint8Array(buffer);
HEAP32[0] = 255;
return (HEAPU8[0] === 255 && HEAPU8[3] === 0);
})();
%PrepareFunctionForOptimization(test);
assertEquals(32 / ctor.BYTES_PER_ELEMENT, test(ctor));
assertEquals(32 / ctor.BYTES_PER_ELEMENT, test(ctor));
%OptimizeFunctionOnNextCall(test);
assertEquals(32 / ctor.BYTES_PER_ELEMENT, test(ctor));
function FillBuffer(buffer) {
const view = new Uint8Array(buffer);
for (let i = 0; i < view.length; ++i) {
view[i] = i;
}
}
%NeverOptimizeFunction(FillBuffer);
function asU16(index) {
const start = index * 2;
if (is_little_endian) {
return (start + 1) * 256 + start;
} else {
return start * 256 + start + 1;
}
}
%NeverOptimizeFunction(asU16);
function asU32(index) {
const start = index * 4;
if (is_little_endian) {
return (((start + 3) * 256 + start + 2) * 256 + start + 1) * 256 + start;
} else {
return ((((start * 256) + start + 1) * 256) + start + 2) * 256 + start + 3;
}
}
%NeverOptimizeFunction(asU32);
function asF32(index) {
const start = index * 4;
const ab = new ArrayBuffer(4);
const ta = new Uint8Array(ab);
for (let i = 0; i < 4; ++i) ta[i] = start + i;
return new Float32Array(ab)[0];
}
%NeverOptimizeFunction(asF32);
function asF64(index) {
const start = index * 8;
const ab = new ArrayBuffer(8);
const ta = new Uint8Array(ab);
for (let i = 0; i < 8; ++i) ta[i] = start + i;
return new Float64Array(ab)[0];
}
%NeverOptimizeFunction(asF64);
function asB64(index) {
const start = index * 8;
let result = 0n;
if (is_little_endian) {
for (let i = 0; i < 8; ++i) {
result = result << 8n;
result += BigInt(start + 7 - i);
}
} else {
for (let i = 0; i < 8; ++i) {
result = result << 8n;
result += BigInt(start + i);
}
}
return result;
}
%NeverOptimizeFunction(asB64);
function CreateBuffer(shared, len, max_len) {
return shared ? new SharedArrayBuffer(len, {maxByteLength: max_len}) :
new ArrayBuffer(len, {maxByteLength: max_len});
}
%NeverOptimizeFunction(CreateBuffer);
function MakeResize(target, shared, offset, fixed_len) {
const bpe = target.name === 'DataView' ? 1 : target.BYTES_PER_ELEMENT;
function RoundDownToElementSize(blen) {
return Math.floor(blen / bpe) * bpe;
}
if (!shared) {
if (fixed_len === undefined) {
return (b, len) => {
b.resize(len);
const blen = Math.max(0, len - offset);
return RoundDownToElementSize(blen);
};
} else {
const fixed_blen = fixed_len * bpe;
return (b, len) => {
b.resize(len);
const blen = fixed_blen <= (len - offset) ? fixed_blen : 0;
return RoundDownToElementSize(blen);
}
}
} else {
if (fixed_len === undefined) {
return (b, len) => {
let blen = 0;
if (len > b.byteLength) {
b.grow(len);
blen = Math.max(0, len - offset);
} else {
blen = b.byteLength - offset;
}
return RoundDownToElementSize(blen);
};
} else {
return (b, len) => {
if (len > b.byteLength) {
b.grow(len);
}
return fixed_len * bpe;
};
}
}
}
%NeverOptimizeFunction(MakeResize);
function MakeElement(target, offset) {
const o = offset / target.BYTES_PER_ELEMENT;
if (target.name === 'Int8Array') {
return (index) => {
return o + index;
};
} else if (target.name === 'Uint32Array') {
return (index) => {
return asU32(o + index);
};
} else if (target.name === 'Float64Array') {
return (index) => {
return asF64(o + index);
};
} else if (target.name === 'BigInt64Array') {
return (index) => {
return asB64(o + index);
};
} else {
console.log(`unimplemented: MakeElement(${target.name})`);
return () => undefined;
}
}
%NeverOptimizeFunction(MakeElement);
function MakeCheckBuffer(target, offset) {
return (ab, up_to) => {
const view = new Uint8Array(ab);
for (let i = 0; i < offset; ++i) {
assertEquals(0, view[i]);
}
for (let i = 0; i < (up_to * target.BYTES_PER_ELEMENT) + 1; ++i) {
// Use PrintBuffer(ab) for debugging.
assertEquals(offset + i, view[offset + i]);
}
}
}
%NeverOptimizeFunction(MakeCheckBuffer);
function ClearBuffer(ab) {
for (let i = 0; i < ab.byteLength; ++i) ab[i] = 0;
}
%NeverOptimizeFunction(ClearBuffer);
// Use this for debugging these tests.
function PrintBuffer(buffer) {
const view = new Uint8Array(buffer);
for (let i = 0; i < 32; ++i) {
console.log(`[${i}]: ${view[i]}`)
}
}
%NeverOptimizeFunction(PrintBuffer);
(function() {
for (let shared of [false, true]) {
for (let length_tracking of [false, true]) {
for (let with_offset of [false, true]) {
for (let target
of [Int8Array, Uint32Array, Float64Array, BigInt64Array]) {
const test_case = `Testing: Length_${shared ? 'GSAB' : 'RAB'}_${
length_tracking ? 'LengthTracking' : 'FixedLength'}${
with_offset ? 'WithOffset' : ''}_${target.name}`;
// console.log(test_case);
const byte_length_code = 'return ta.byteLength; // ' + test_case;
const ByteLength = new Function('ta', byte_length_code);
const length_code = 'return ta.length; // ' + test_case;
const Length = new Function('ta', length_code);
const offset = with_offset ? 8 : 0;
let blen = 16 - offset;
const fixed_len =
length_tracking ? undefined : (blen / target.BYTES_PER_ELEMENT);
const ab = CreateBuffer(shared, 16, 40);
const ta = new target(ab, offset, fixed_len);
const Resize = MakeResize(target, shared, offset, fixed_len);
assertUnoptimized(ByteLength);
assertUnoptimized(Length);
%PrepareFunctionForOptimization(ByteLength);
%PrepareFunctionForOptimization(Length);
assertEquals(blen, ByteLength(ta));
assertEquals(blen, ByteLength(ta));
assertEquals(Math.floor(blen / target.BYTES_PER_ELEMENT), Length(ta));
assertEquals(Math.floor(blen / target.BYTES_PER_ELEMENT), Length(ta));
%OptimizeFunctionOnNextCall(ByteLength);
%OptimizeFunctionOnNextCall(Length);
assertEquals(blen, ByteLength(ta));
assertEquals(Math.floor(blen / target.BYTES_PER_ELEMENT), Length(ta));
blen = Resize(ab, 32);
assertEquals(blen, ByteLength(ta));
assertEquals(Math.floor(blen / target.BYTES_PER_ELEMENT), Length(ta));
blen = Resize(ab, 9);
assertEquals(blen, ByteLength(ta));
assertEquals(Math.floor(blen / target.BYTES_PER_ELEMENT), Length(ta));
assertOptimized(ByteLength);
assertOptimized(Length);
blen = Resize(ab, 24);
assertEquals(blen, ByteLength(ta));
assertEquals(Math.floor(blen / target.BYTES_PER_ELEMENT), Length(ta));
assertOptimized(ByteLength);
assertOptimized(Length);
if (!shared) {
%ArrayBufferDetach(ab);
assertEquals(0, ByteLength(ta));
assertEquals(0, Length(ta));
assertOptimized(Length);
}
}
}
}
}
})();
(function() {
for (let shared of [false, true]) {
for (let length_tracking of [false, true]) {
for (let with_offset of [false, true]) {
for (let target
of [Int8Array, Uint32Array, Float64Array, BigInt64Array]) {
const test_case = `Testing: Read_${shared ? 'GSAB' : 'RAB'}_${
length_tracking ? 'LengthTracking' : 'FixedLength'}${
with_offset ? 'WithOffset' : ''}_${target.name}`;
// console.log(test_case);
const read_code = 'return ta[index]; // ' + test_case;
const Read = new Function('ta', 'index', read_code);
const offset = with_offset ? 8 : 0;
let blen = 16 - offset;
let len = Math.floor(blen / target.BYTES_PER_ELEMENT);
const fixed_len = length_tracking ? undefined : len;
const ab = CreateBuffer(shared, 16, 40);
const ta = new target(ab, offset, fixed_len);
const Resize = MakeResize(target, shared, offset, fixed_len);
const Element = MakeElement(target, offset);
FillBuffer(ab);
assertUnoptimized(Read);
%PrepareFunctionForOptimization(Read);
for (let i = 0; i < len * 2; ++i)
assertEquals(i < len ? Element(i) : undefined, Read(ta, i));
%OptimizeFunctionOnNextCall(Read);
for (let i = 0; i < len * 2; ++i)
assertEquals(i < len ? Element(i) : undefined, Read(ta, i));
assertOptimized(Read);
blen = Resize(ab, 32);
FillBuffer(ab);
len = Math.floor(blen / target.BYTES_PER_ELEMENT);
for (let i = 0; i < len * 2; ++i)
assertEquals(i < len ? Element(i) : undefined, Read(ta, i));
assertOptimized(Read);
blen = Resize(ab, 9);
FillBuffer(ab);
len = Math.floor(blen / target.BYTES_PER_ELEMENT);
for (let i = 0; i < len * 2; ++i)
assertEquals(i < len ? Element(i) : undefined, Read(ta, i));
assertOptimized(Read);
blen = Resize(ab, 0);
len = Math.floor(blen / target.BYTES_PER_ELEMENT);
for (let i = 0; i < len * 2; ++i)
assertEquals(i < len ? Element(i) : undefined, Read(ta, i));
assertOptimized(Read);
blen = Resize(ab, 24);
FillBuffer(ab);
len = Math.floor(blen / target.BYTES_PER_ELEMENT);
for (let i = 0; i < len * 2; ++i)
assertEquals(i < len ? Element(i) : undefined, Read(ta, i));
assertOptimized(Read);
if (!shared) {
%ArrayBufferDetach(ab);
assertEquals(undefined, Read(ta, 0));
// assertOptimized(Read);
}
}
}
}
}
})();
(function() {
for (let shared of [false, true]) {
for (let length_tracking of [false, true]) {
for (let with_offset of [false, true]) {
for (let target
of [Int8Array, Uint32Array, Float64Array, BigInt64Array]) {
const test_case = `Testing: Write_${shared ? 'GSAB' : 'RAB'}_${
length_tracking ? 'LengthTracking' : 'FixedLength'}${
with_offset ? 'WithOffset' : ''}_${target.name}`;
// console.log(test_case);
const write_code = 'ta[index] = value; // ' + test_case;
const Write = new Function('ta', 'index', 'value', write_code);
const offset = with_offset ? 8 : 0;
let blen = 16 - offset;
let len = Math.floor(blen / target.BYTES_PER_ELEMENT);
const fixed_len = length_tracking ? undefined : len;
const ab = CreateBuffer(shared, 16, 40);
const ta = new target(ab, offset, fixed_len);
const Resize = MakeResize(target, shared, offset, fixed_len);
const Element = MakeElement(target, offset);
const CheckBuffer = MakeCheckBuffer(target, offset);
ClearBuffer(ab);
assertUnoptimized(Write);
%PrepareFunctionForOptimization(Write);
for (let i = 0; i < len; ++i) {
Write(ta, i, Element(i));
CheckBuffer(ab, i);
}
ClearBuffer(ab);
%OptimizeFunctionOnNextCall(Write);
for (let i = 0; i < len; ++i) {
Write(ta, i, Element(i));
CheckBuffer(ab, i);
}
assertOptimized(Write);
blen = Resize(ab, 32);
ClearBuffer(ab);
len = Math.floor(blen / target.BYTES_PER_ELEMENT);
for (let i = 0; i < len; ++i) {
Write(ta, i, Element(i));
CheckBuffer(ab, i);
}
assertOptimized(Write);
blen = Resize(ab, 9);
ClearBuffer(ab);
len = Math.floor(blen / target.BYTES_PER_ELEMENT);
for (let i = 0; i < len; ++i) {
Write(ta, i, Element(i));
CheckBuffer(ab, i);
}
assertOptimized(Write);
blen = Resize(ab, 24);
ClearBuffer(ab);
len = Math.floor(blen / target.BYTES_PER_ELEMENT);
for (let i = 0; i < len; ++i) {
Write(ta, i, Element(i));
CheckBuffer(ab, i);
}
assertOptimized(Write);
}
}
}
}
})();
(function() {
for (let shared of [false, true]) {
for (let length_tracking of [false, true]) {
for (let with_offset of [false, true]) {
const test_case = `Testing: ByteLength_${shared ? 'GSAB' : 'RAB'}_${
length_tracking ?
'LengthTracking' :
'FixedLength'}${with_offset ? 'WithOffset' : ''}_DataView`;
// console.log(test_case);
const byte_length_code = 'return dv.byteLength; // ' + test_case;
const ByteLength = new Function('dv', byte_length_code);
const offset = with_offset ? 8 : 0;
let blen = 16 - offset;
const fixed_blen = length_tracking ? undefined : blen;
const ab = CreateBuffer(shared, 16, 40);
const dv = new DataView(ab, offset, fixed_blen);
const Resize = MakeResize(DataView, shared, offset, fixed_blen);
assertUnoptimized(ByteLength);
%PrepareFunctionForOptimization(ByteLength);
assertEquals(blen, ByteLength(dv));
assertEquals(blen, ByteLength(dv));
%OptimizeFunctionOnNextCall(ByteLength);
assertEquals(blen, ByteLength(dv));
assertOptimized(ByteLength);
blen = Resize(ab, 32);
assertEquals(blen, ByteLength(dv));
assertOptimized(ByteLength);
blen = Resize(ab, 9);
if (length_tracking || shared) {
assertEquals(blen, ByteLength(dv));
} else {
// For fixed length rabs, Resize(ab, 9) will put the ArrayBuffer in
// detached state, for which DataView.prototype.byteLength has to throw.
assertThrows(() => { ByteLength(dv); }, TypeError);
}
assertOptimized(ByteLength);
blen = Resize(ab, 24);
assertEquals(blen, ByteLength(dv));
assertOptimized(ByteLength);
if (!shared) {
%ArrayBufferDetach(ab);
assertThrows(() => { ByteLength(dv); }, TypeError);
assertOptimized(ByteLength);
}
}
}
}
})();
(function() {
function ByteLength_RAB_LengthTrackingWithOffset_DataView(dv) {
return dv.byteLength;
}
const ByteLength = ByteLength_RAB_LengthTrackingWithOffset_DataView;
const rab = CreateResizableArrayBuffer(16, 40);
const dv = new DataView(rab, 7);
%PrepareFunctionForOptimization(ByteLength);
assertEquals(9, ByteLength(dv));
assertEquals(9, ByteLength(dv));
%OptimizeFunctionOnNextCall(ByteLength);
assertEquals(9, ByteLength(dv));
assertOptimized(ByteLength);
})();
(function() {
function Read_TA_RAB_LengthTracking_Mixed(ta, index) {
return ta[index];
}
const Get = Read_TA_RAB_LengthTracking_Mixed;
const ab = new ArrayBuffer(16);
FillBuffer(ab);
const rab = CreateResizableArrayBuffer(16, 40);
FillBuffer(rab);
let ta_int8 = new Int8Array(ab);
let ta_uint16 = new Uint16Array(rab);
let ta_float32 = new Float32Array(ab);
let ta_float64 = new Float64Array(rab);
// Train with feedback for all elements kinds.
%PrepareFunctionForOptimization(Get);
assertEquals(0, Get(ta_int8, 0));
assertEquals(3, Get(ta_int8, 3));
assertEquals(15, Get(ta_int8, 15));
assertEquals(undefined, Get(ta_int8, 16));
assertEquals(undefined, Get(ta_int8, 32));
assertEquals(asU16(0), Get(ta_uint16, 0));
assertEquals(asU16(3), Get(ta_uint16, 3));
assertEquals(asU16(7), Get(ta_uint16, 7));
assertEquals(undefined, Get(ta_uint16, 8));
assertEquals(undefined, Get(ta_uint16, 12));
assertEquals(asF32(0), Get(ta_float32, 0));
assertEquals(asF32(3), Get(ta_float32, 3));
assertEquals(undefined, Get(ta_float32, 4));
assertEquals(undefined, Get(ta_float32, 12));
assertEquals(asF64(0), Get(ta_float64, 0));
assertEquals(asF64(1), Get(ta_float64, 1));
assertEquals(undefined, Get(ta_float64, 2));
assertEquals(undefined, Get(ta_float64, 12));
%OptimizeFunctionOnNextCall(Get);
assertEquals(0, Get(ta_int8, 0));
assertEquals(3, Get(ta_int8, 3));
assertEquals(15, Get(ta_int8, 15));
assertEquals(undefined, Get(ta_int8, 16));
assertEquals(undefined, Get(ta_int8, 32));
assertEquals(asU16(0), Get(ta_uint16, 0));
assertEquals(asU16(3), Get(ta_uint16, 3));
assertEquals(asU16(7), Get(ta_uint16, 7));
assertEquals(undefined, Get(ta_uint16, 8));
assertEquals(undefined, Get(ta_uint16, 12));
assertEquals(asF32(0), Get(ta_float32, 0));
assertEquals(asF32(3), Get(ta_float32, 3));
assertEquals(undefined, Get(ta_float32, 4));
assertEquals(undefined, Get(ta_float32, 12));
assertEquals(asF64(0), Get(ta_float64, 0));
assertEquals(asF64(1), Get(ta_float64, 1));
assertEquals(undefined, Get(ta_float64, 2));
assertEquals(undefined, Get(ta_float64, 12));
assertOptimized(Get);
rab.resize(32);
FillBuffer(rab);
assertEquals(0, Get(ta_int8, 0));
assertEquals(3, Get(ta_int8, 3));
assertEquals(15, Get(ta_int8, 15));
assertEquals(undefined, Get(ta_int8, 16));
assertEquals(undefined, Get(ta_int8, 32));
assertEquals(asU16(0), Get(ta_uint16, 0));
assertEquals(asU16(3), Get(ta_uint16, 3));
assertEquals(asU16(15), Get(ta_uint16, 15));
assertEquals(undefined, Get(ta_uint16, 16));
assertEquals(undefined, Get(ta_uint16, 40));
assertEquals(asF32(0), Get(ta_float32, 0));
assertEquals(asF32(3), Get(ta_float32, 3));
assertEquals(undefined, Get(ta_float32, 4));
assertEquals(undefined, Get(ta_float32, 12));
assertEquals(asF64(0), Get(ta_float64, 0));
assertEquals(asF64(1), Get(ta_float64, 1));
assertEquals(asF64(3), Get(ta_float64, 3));
assertEquals(undefined, Get(ta_float64, 4));
assertEquals(undefined, Get(ta_float64, 12));
assertOptimized(Get);
rab.resize(9);
assertEquals(0, Get(ta_int8, 0));
assertEquals(3, Get(ta_int8, 3));
assertEquals(15, Get(ta_int8, 15));
assertEquals(undefined, Get(ta_int8, 16));
assertEquals(undefined, Get(ta_int8, 32));
assertEquals(asU16(0), Get(ta_uint16, 0));
assertEquals(asU16(3), Get(ta_uint16, 3));
assertEquals(undefined, Get(ta_uint16, 4));
assertEquals(undefined, Get(ta_uint16, 12));
assertEquals(asF32(0), Get(ta_float32, 0));
assertEquals(asF32(3), Get(ta_float32, 3));
assertEquals(undefined, Get(ta_float32, 4));
assertEquals(undefined, Get(ta_float32, 12));
assertEquals(asF64(0), Get(ta_float64, 0));
assertEquals(undefined, Get(ta_float64, 1));
assertEquals(undefined, Get(ta_float64, 12));
assertOptimized(Get);
// Call with a different map to trigger deoptimization. We use this
// to verify that we have actually specialized on the above maps only.
let ta_uint8 = new Uint8Array(rab);
assertEquals(7, Get(ta_uint8, 7));
assertUnoptimized(Get);
}());
(function() {
function Read_TA_RAB_LengthTracking_Mixed(ta, index) {
return ta[index];
}
const Get = Read_TA_RAB_LengthTracking_Mixed;
const ab = new ArrayBuffer(16);
FillBuffer(ab);
const rab = CreateResizableArrayBuffer(16, 40);
FillBuffer(rab);
let ta_int8 = new Int8Array(ab);
let ta_uint16 = new Uint16Array(rab);
let ta_float32 = new Float32Array(ab);
let ta_float64 = new Float64Array(rab);
// Train with feedback for all elements kinds.
%PrepareFunctionForOptimization(Get);
assertEquals(0, Get(ta_int8, 0));
assertEquals(3, Get(ta_int8, 3));
assertEquals(15, Get(ta_int8, 15));
assertEquals(undefined, Get(ta_int8, 16));
assertEquals(undefined, Get(ta_int8, 32));
assertEquals(asU16(0), Get(ta_uint16, 0));
assertEquals(asU16(3), Get(ta_uint16, 3));
assertEquals(asU16(7), Get(ta_uint16, 7));
assertEquals(undefined, Get(ta_uint16, 8));
assertEquals(undefined, Get(ta_uint16, 12));
assertEquals(asF32(0), Get(ta_float32, 0));
assertEquals(asF32(3), Get(ta_float32, 3));
assertEquals(undefined, Get(ta_float32, 4));
assertEquals(undefined, Get(ta_float32, 12));
assertEquals(asF64(0), Get(ta_float64, 0));
assertEquals(asF64(1), Get(ta_float64, 1));
assertEquals(undefined, Get(ta_float64, 2));
assertEquals(undefined, Get(ta_float64, 12));
%OptimizeFunctionOnNextCall(Get);
assertEquals(0, Get(ta_int8, 0));
assertEquals(3, Get(ta_int8, 3));
assertEquals(15, Get(ta_int8, 15));
assertEquals(undefined, Get(ta_int8, 16));
assertEquals(undefined, Get(ta_int8, 32));
assertEquals(asU16(0), Get(ta_uint16, 0));
assertEquals(asU16(3), Get(ta_uint16, 3));
assertEquals(asU16(7), Get(ta_uint16, 7));
assertEquals(undefined, Get(ta_uint16, 8));
assertEquals(undefined, Get(ta_uint16, 12));
assertEquals(asF32(0), Get(ta_float32, 0));
assertEquals(asF32(3), Get(ta_float32, 3));
assertEquals(undefined, Get(ta_float32, 4));
assertEquals(undefined, Get(ta_float32, 12));
assertEquals(asF64(0), Get(ta_float64, 0));
assertEquals(asF64(1), Get(ta_float64, 1));
assertEquals(undefined, Get(ta_float64, 2));
assertEquals(undefined, Get(ta_float64, 12));
assertOptimized(Get);
rab.resize(32);
FillBuffer(rab);
assertEquals(0, Get(ta_int8, 0));
assertEquals(3, Get(ta_int8, 3));
assertEquals(15, Get(ta_int8, 15));
assertEquals(undefined, Get(ta_int8, 16));
assertEquals(undefined, Get(ta_int8, 32));
assertEquals(asU16(0), Get(ta_uint16, 0));
assertEquals(asU16(3), Get(ta_uint16, 3));
assertEquals(asU16(15), Get(ta_uint16, 15));
assertEquals(undefined, Get(ta_uint16, 16));
assertEquals(undefined, Get(ta_uint16, 40));
assertEquals(asF32(0), Get(ta_float32, 0));
assertEquals(asF32(3), Get(ta_float32, 3));
assertEquals(undefined, Get(ta_float32, 4));
assertEquals(undefined, Get(ta_float32, 12));
assertEquals(asF64(0), Get(ta_float64, 0));
assertEquals(asF64(1), Get(ta_float64, 1));
assertEquals(asF64(3), Get(ta_float64, 3));
assertEquals(undefined, Get(ta_float64, 4));
assertEquals(undefined, Get(ta_float64, 12));
assertOptimized(Get);
rab.resize(9);
assertEquals(0, Get(ta_int8, 0));
assertEquals(3, Get(ta_int8, 3));
assertEquals(15, Get(ta_int8, 15));
assertEquals(undefined, Get(ta_int8, 16));
assertEquals(undefined, Get(ta_int8, 32));
assertEquals(asU16(0), Get(ta_uint16, 0));
assertEquals(asU16(3), Get(ta_uint16, 3));
assertEquals(undefined, Get(ta_uint16, 4));
assertEquals(undefined, Get(ta_uint16, 12));
assertEquals(asF32(0), Get(ta_float32, 0));
assertEquals(asF32(3), Get(ta_float32, 3));
assertEquals(undefined, Get(ta_float32, 4));
assertEquals(undefined, Get(ta_float32, 12));
assertEquals(asF64(0), Get(ta_float64, 0));
assertEquals(undefined, Get(ta_float64, 1));
assertEquals(undefined, Get(ta_float64, 12));
assertOptimized(Get);
// Call with a different map to trigger deoptimization. We use this
// to verify that we have actually specialized on the above maps only.
let ta_uint8 = new Uint8Array(rab);
Get(7, Get(ta_uint8, 7));
assertUnoptimized(Get);
}());
(function() {
function Length_TA_RAB_LengthTracking_Mixed(ta) {
return ta.length;
}
let Length = Length_TA_RAB_LengthTracking_Mixed;
const ab = new ArrayBuffer(32);
const rab = CreateResizableArrayBuffer(16, 40);
let ta_int8 = new Int8Array(ab);
let ta_uint16 = new Uint16Array(rab);
let ta_float32 = new Float32Array(ab);
let ta_bigint64 = new BigInt64Array(rab);
// Train with feedback for all elements kinds.
%PrepareFunctionForOptimization(Length);
assertEquals(32, Length(ta_int8));
assertEquals(8, Length(ta_uint16));
assertEquals(8, Length(ta_float32));
assertEquals(2, Length(ta_bigint64));
%OptimizeFunctionOnNextCall(Length);
assertEquals(32, Length(ta_int8));
assertEquals(8, Length(ta_uint16));
assertEquals(8, Length(ta_float32));
assertEquals(2, Length(ta_bigint64));
assertOptimized(Length);
}());
(function() {
function Length_RAB_GSAB_LengthTrackingWithOffset_Mixed(ta) {
return ta.length;
}
const Length = Length_RAB_GSAB_LengthTrackingWithOffset_Mixed;
const rab = CreateResizableArrayBuffer(16, 40);
let ta_int8 = new Int8Array(rab);
let ta_float64 = new Float64Array(rab);
// Train with feedback for Int8Array and Float64Array.
%PrepareFunctionForOptimization(Length);
assertEquals(16, Length(ta_int8));
assertEquals(2, Length(ta_float64));
%OptimizeFunctionOnNextCall(Length);
assertEquals(16, Length(ta_int8));
assertEquals(2, Length(ta_float64));
assertOptimized(Length);
let ta_uint32 = new Uint32Array(rab);
let ta_bigint64 = new BigInt64Array(rab);
// Calling with Uint32Array will deopt because of the map check on length.
assertEquals(4, Length(ta_uint32));
assertUnoptimized(Length);
%PrepareFunctionForOptimization(Length);
assertEquals(2, Length(ta_bigint64));
// Recompile with additional feedback for Uint32Array and BigInt64Array.
%OptimizeFunctionOnNextCall(Length);
assertEquals(2, Length(ta_bigint64));
assertOptimized(Length);
// Length handles all four TypedArrays without deopting.
assertEquals(16, Length(ta_int8));
assertEquals(2, Length(ta_float64));
assertEquals(4, Length(ta_uint32));
assertEquals(2, Length(ta_bigint64));
assertOptimized(Length);
// Length handles corresponding gsab-backed TypedArrays without deopting.
const gsab = CreateGrowableSharedArrayBuffer(16, 40);
let ta2_uint32 = new Uint32Array(gsab, 8);
let ta2_float64 = new Float64Array(gsab, 8);
let ta2_bigint64 = new BigInt64Array(gsab, 8);
let ta2_int8 = new Int8Array(gsab, 8);
assertEquals(8, Length(ta2_int8));
assertEquals(1, Length(ta2_float64));
assertEquals(2, Length(ta2_uint32));
assertEquals(1, Length(ta2_bigint64));
assertOptimized(Length);
// Test Length after rab has been resized to a smaller size.
rab.resize(5);
assertEquals(5, Length(ta_int8));
assertEquals(0, Length(ta_float64));
assertEquals(1, Length(ta_uint32));
assertEquals(0, Length(ta_bigint64));
assertOptimized(Length);
// Test Length after rab has been resized to a larger size.
rab.resize(40);
assertEquals(40, Length(ta_int8));
assertEquals(5, Length(ta_float64));
assertEquals(10, Length(ta_uint32));
assertEquals(5, Length(ta_bigint64));
assertOptimized(Length);
// Test Length after gsab has been grown to a larger size.
gsab.grow(25);
assertEquals(17, Length(ta2_int8));
assertEquals(2, Length(ta2_float64));
assertEquals(4, Length(ta2_uint32));
assertEquals(2, Length(ta2_bigint64));
assertOptimized(Length);
})();
(function() {
function Length_AB_RAB_GSAB_LengthTrackingWithOffset_Mixed(ta) {
return ta.length;
}
const Length = Length_AB_RAB_GSAB_LengthTrackingWithOffset_Mixed;
let ab = new ArrayBuffer(32);
let rab = CreateResizableArrayBuffer(16, 40);
let gsab = CreateGrowableSharedArrayBuffer(16, 40);
let ta_ab_int32 = new Int32Array(ab, 8, 3);
let ta_rab_int32 = new Int32Array(rab, 4);
let ta_gsab_float64 = new Float64Array(gsab);
let ta_gsab_bigint64 = new BigInt64Array(gsab, 0, 2);
// Optimize Length with polymorphic feedback.
%PrepareFunctionForOptimization(Length);
assertEquals(3, Length(ta_ab_int32));
assertEquals(3, Length(ta_rab_int32));
assertEquals(2, Length(ta_gsab_float64));
assertEquals(2, Length(ta_gsab_bigint64));
%OptimizeFunctionOnNextCall(Length);
assertEquals(3, Length(ta_ab_int32));
assertEquals(3, Length(ta_rab_int32));
assertEquals(2, Length(ta_gsab_float64));
assertEquals(2, Length(ta_gsab_bigint64));
assertOptimized(Length);
// Test resizing and growing the underlying rab/gsab buffers.
rab.resize(8);
gsab.grow(36);
assertEquals(3, Length(ta_ab_int32));
assertEquals(1, Length(ta_rab_int32));
assertEquals(4, Length(ta_gsab_float64));
assertEquals(2, Length(ta_gsab_bigint64));
assertOptimized(Length);
// Construct additional TypedArrays with the same ElementsKind.
let ta2_ab_bigint64 = new BigInt64Array(ab, 0, 1);
let ta2_gsab_int32 = new Int32Array(gsab, 16);
let ta2_rab_float64 = new Float64Array(rab, 8);
let ta2_rab_int32 = new Int32Array(rab, 0, 1);
assertEquals(1, Length(ta2_ab_bigint64));
assertEquals(5, Length(ta2_gsab_int32));
assertEquals(0, Length(ta2_rab_float64));
assertEquals(1, Length(ta2_rab_int32));
assertOptimized(Length);
})();
(function() {
function ByteOffset(ta) {
return ta.byteOffset;
}
const rab = CreateResizableArrayBuffer(16, 40);
const ta = new Int32Array(rab, 4);
%PrepareFunctionForOptimization(ByteOffset);
assertEquals(4, ByteOffset(ta));
assertEquals(4, ByteOffset(ta));
%OptimizeFunctionOnNextCall(ByteOffset);
assertEquals(4, ByteOffset(ta));
assertOptimized(ByteOffset);
})();

View File

@ -0,0 +1,51 @@
// Copyright 2022 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 --harmony-rab-gsab
(function Test_OOB() {
function f() {
try {
const buffer = new ArrayBuffer(42, {'maxByteLength': 42});
const view = new DataView(buffer, 0, 42);
// Resize the buffer to smaller than the view.
buffer.resize(20);
// Any access in the view should throw.
view.setInt8(11, 0xab);
return 'did not prevent out-of-bounds access';
} catch (e) {
return 'ok';
}
}
%PrepareFunctionForOptimization(f);
assertEquals('ok', f());
assertEquals('ok', f());
%OptimizeFunctionOnNextCall(f);
assertEquals('ok', f());
assertEquals('ok', f());
}());
(function Test_OOB_WithOffset() {
function f() {
try {
const buffer = new ArrayBuffer(42, {'maxByteLength': 42});
const view = new DataView(buffer, 30, 42);
// Resize the buffer to smaller than the view.
buffer.resize(40);
// Any access in the view should throw.
view.setInt8(11, 0xab);
return 'did not prevent out-of-bounds access';
} catch (e) {
return 'ok';
}
}
%PrepareFunctionForOptimization(f);
assertEquals('ok', f());
assertEquals('ok', f());
%OptimizeFunctionOnNextCall(f);
assertEquals('ok', f());
assertEquals('ok', f());
}());