diff options
Diffstat (limited to 'deps/v8/src/compiler/turboshaft/operations.h')
-rw-r--r-- | deps/v8/src/compiler/turboshaft/operations.h | 1903 |
1 files changed, 1653 insertions, 250 deletions
diff --git a/deps/v8/src/compiler/turboshaft/operations.h b/deps/v8/src/compiler/turboshaft/operations.h index e240c41115..8ef0f79b8e 100644 --- a/deps/v8/src/compiler/turboshaft/operations.h +++ b/deps/v8/src/compiler/turboshaft/operations.h @@ -17,14 +17,18 @@ #include "src/base/macros.h" #include "src/base/optional.h" #include "src/base/platform/mutex.h" +#include "src/base/small-vector.h" #include "src/base/template-utils.h" #include "src/base/vector.h" #include "src/codegen/external-reference.h" #include "src/common/globals.h" #include "src/compiler/common-operator.h" #include "src/compiler/globals.h" +#include "src/compiler/turboshaft/deopt-data.h" #include "src/compiler/turboshaft/fast-hash.h" +#include "src/compiler/turboshaft/index.h" #include "src/compiler/turboshaft/representations.h" +#include "src/compiler/turboshaft/types.h" #include "src/compiler/turboshaft/utils.h" #include "src/compiler/write-barrier-kind.h" @@ -41,8 +45,8 @@ enum class TrapId : uint32_t; namespace v8::internal::compiler::turboshaft { class Block; struct FrameStateData; -class Variable; class Graph; +struct FrameStateOp; // DEFINING NEW OPERATIONS // ======================= @@ -65,7 +69,14 @@ class Graph; // non-static method `Properties()` if the properties depend on the particular // operation and not just the opcode. +#ifdef V8_INTL_SUPPORT +#define TURBOSHAFT_INTL_OPERATION_LIST(V) V(StringToCaseIntl) +#else +#define TURBOSHAFT_INTL_OPERATION_LIST(V) +#endif // V8_INTL_SUPPORT + #define TURBOSHAFT_OPERATION_LIST(V) \ + TURBOSHAFT_INTL_OPERATION_LIST(V) \ V(WordBinop) \ V(FloatBinop) \ V(OverflowCheckedBinop) \ @@ -75,6 +86,7 @@ class Graph; V(Equal) \ V(Comparison) \ V(Change) \ + V(ChangeOrDeopt) \ V(TryChange) \ V(Float64InsertWord32) \ V(TaggedBitcast) \ @@ -83,6 +95,8 @@ class Graph; V(Constant) \ V(Load) \ V(Store) \ + V(Allocate) \ + V(DecodeExternalPointer) \ V(Retain) \ V(Parameter) \ V(OsrValue) \ @@ -90,21 +104,49 @@ class Graph; V(StackPointerGreaterThan) \ V(StackSlot) \ V(FrameConstant) \ - V(CheckLazyDeopt) \ V(Deoptimize) \ V(DeoptimizeIf) \ V(TrapIf) \ V(Phi) \ V(FrameState) \ V(Call) \ + V(CallAndCatchException) \ + V(LoadException) \ V(TailCall) \ V(Unreachable) \ V(Return) \ V(Branch) \ - V(CatchException) \ V(Switch) \ V(Tuple) \ - V(Projection) + V(Projection) \ + V(StaticAssert) \ + V(CheckTurboshaftTypeOf) \ + V(ObjectIs) \ + V(FloatIs) \ + V(ConvertToObject) \ + V(ConvertToObjectOrDeopt) \ + V(ConvertObjectToPrimitive) \ + V(ConvertObjectToPrimitiveOrDeopt) \ + V(TruncateObjectToPrimitive) \ + V(Tag) \ + V(Untag) \ + V(NewConsString) \ + V(NewArray) \ + V(DoubleArrayMinMax) \ + V(LoadFieldByIndex) \ + V(DebugBreak) \ + V(BigIntBinop) \ + V(BigIntEqual) \ + V(BigIntComparison) \ + V(BigIntUnary) \ + V(LoadRootRegister) \ + V(StringAt) \ + V(StringLength) \ + V(StringIndexOf) \ + V(StringFromCodePointAt) \ + V(StringSubstring) \ + V(StringEqual) \ + V(StringComparison) enum class Opcode : uint8_t { #define ENUM_CONSTANT(Name) k##Name, @@ -122,110 +164,17 @@ constexpr uint16_t kNumberOfOpcodes = 0 TURBOSHAFT_OPERATION_LIST(COUNT_OPCODES); #undef COUNT_OPCODES -// Operations are stored in possibly muliple sequential storage slots. -using OperationStorageSlot = std::aligned_storage_t<8, 8>; -// Operations occupy at least 2 slots, therefore we assign one id per two slots. -constexpr size_t kSlotsPerId = 2; - -// `OpIndex` is an offset from the beginning of the operations buffer. -// Compared to `Operation*`, it is more memory efficient (32bit) and stable when -// the operations buffer is re-allocated. -class OpIndex { - public: - explicit constexpr OpIndex(uint32_t offset) : offset_(offset) { - DCHECK_EQ(offset % sizeof(OperationStorageSlot), 0); - } - constexpr OpIndex() : offset_(std::numeric_limits<uint32_t>::max()) {} - - uint32_t id() const { - // Operations are stored at an offset that's a multiple of - // `sizeof(OperationStorageSlot)`. In addition, an operation occupies at - // least `kSlotsPerId` many `OperationSlot`s. Therefore, we can assign id's - // by dividing by `kSlotsPerId`. A compact id space is important, because it - // makes side-tables smaller. - DCHECK_EQ(offset_ % sizeof(OperationStorageSlot), 0); - return offset_ / sizeof(OperationStorageSlot) / kSlotsPerId; - } - uint32_t offset() const { - DCHECK_EQ(offset_ % sizeof(OperationStorageSlot), 0); - return offset_; - } - - bool valid() const { return *this != Invalid(); } - - static constexpr OpIndex Invalid() { return OpIndex(); } - - // Encode a sea-of-nodes node id in the `OpIndex` type. - // Only used for node origins that actually point to sea-of-nodes graph nodes. - static OpIndex EncodeTurbofanNodeId(uint32_t id) { - OpIndex result = OpIndex(id * sizeof(OperationStorageSlot)); - result.offset_ += kTurbofanNodeIdFlag; - return result; - } - uint32_t DecodeTurbofanNodeId() const { - DCHECK(IsTurbofanNodeId()); - return offset_ / sizeof(OperationStorageSlot); - } - bool IsTurbofanNodeId() const { - return offset_ % sizeof(OperationStorageSlot) == kTurbofanNodeIdFlag; - } - - bool operator==(OpIndex other) const { return offset_ == other.offset_; } - bool operator!=(OpIndex other) const { return offset_ != other.offset_; } - bool operator<(OpIndex other) const { return offset_ < other.offset_; } - bool operator>(OpIndex other) const { return offset_ > other.offset_; } - bool operator<=(OpIndex other) const { return offset_ <= other.offset_; } - bool operator>=(OpIndex other) const { return offset_ >= other.offset_; } - - private: - uint32_t offset_; - - static constexpr uint32_t kTurbofanNodeIdFlag = 1; -}; - -template <> -struct fast_hash<OpIndex> { - V8_INLINE size_t operator()(OpIndex op) { return op.id(); } -}; - -// `BlockIndex` is the index of a bound block. -// A dominating block always has a smaller index. -// It corresponds to the ordering of basic blocks in the operations buffer. -class BlockIndex { - public: - explicit constexpr BlockIndex(uint32_t id) : id_(id) {} - constexpr BlockIndex() : id_(std::numeric_limits<uint32_t>::max()) {} - - uint32_t id() const { return id_; } - bool valid() const { return *this != Invalid(); } - - static constexpr BlockIndex Invalid() { return BlockIndex(); } - - bool operator==(BlockIndex other) const { return id_ == other.id_; } - bool operator!=(BlockIndex other) const { return id_ != other.id_; } - bool operator<(BlockIndex other) const { return id_ < other.id_; } - bool operator>(BlockIndex other) const { return id_ > other.id_; } - bool operator<=(BlockIndex other) const { return id_ <= other.id_; } - bool operator>=(BlockIndex other) const { return id_ >= other.id_; } - - private: - uint32_t id_; -}; - -template <> -struct fast_hash<BlockIndex> { - V8_INLINE size_t operator()(BlockIndex op) { return op.id(); } -}; - -std::ostream& operator<<(std::ostream& os, BlockIndex b); -std::ostream& operator<<(std::ostream& os, const Block* b); - struct OpProperties { // The operation may read memory or depend on other information beyond its - // inputs. + // inputs. Generating random numbers or nondeterministic behavior counts as + // reading. const bool can_read; // The operation may write memory or have other observable side-effects. + // Writing to memory allocated as part of the operation does not count, since + // it is not observable. const bool can_write; + // The operation can allocate memory on the heap, which might also trigger GC. + const bool can_allocate; // The operation can abort the current execution by throwing an exception or // deoptimizing. const bool can_abort; @@ -233,47 +182,50 @@ struct OpProperties { const bool is_block_terminator; // By being const and not being set in the constructor, these properties are // guaranteed to be derived. - const bool is_pure = - !(can_read || can_write || can_abort || is_block_terminator); + const bool is_pure_no_allocation = !(can_read || can_write || can_allocate || + can_abort || is_block_terminator); const bool is_required_when_unused = can_write || can_abort || is_block_terminator; - // Nodes that don't read, write and aren't block terminators can be eliminated - // via value numbering. + // Operations that don't read, write, allocate and aren't block terminators + // can be eliminated via value numbering, which means that if there are two + // identical operations where one dominates the other, then the second can be + // replaced with the first one. This is safe for deopting or throwing + // operations, because the first instance would have aborted the execution + // already as + // `!can_read` guarantees deterministic behavior. const bool can_be_eliminated = - !(can_read || can_write || is_block_terminator); + !(can_read || can_write || can_allocate || is_block_terminator); - constexpr OpProperties(bool can_read, bool can_write, bool can_abort, - bool is_block_terminator) + constexpr OpProperties(bool can_read, bool can_write, bool can_allocate, + bool can_abort, bool is_block_terminator) : can_read(can_read), can_write(can_write), + can_allocate(can_allocate), can_abort(can_abort), is_block_terminator(is_block_terminator) {} - static constexpr OpProperties Pure() { return {false, false, false, false}; } - static constexpr OpProperties Reading() { - return {true, false, false, false}; - } - static constexpr OpProperties Writing() { - return {false, true, false, false}; - } - static constexpr OpProperties CanAbort() { - return {false, false, true, false}; - } - static constexpr OpProperties AnySideEffects() { - return {true, true, true, false}; - } - static constexpr OpProperties BlockTerminator() { - return {false, false, false, true}; - } - static constexpr OpProperties BlockTerminatorWithAnySideEffect() { - return {true, true, true, true}; - } - static constexpr OpProperties ReadingAndCanAbort() { - return {true, false, true, false}; - } - static constexpr OpProperties WritingAndCanAbort() { - return {false, true, true, false}; +#define ALL_OP_PROPERTIES(V) \ + V(PureNoAllocation, false, false, false, false, false) \ + V(PureMayAllocate, false, false, true, false, false) \ + V(Reading, true, false, false, false, false) \ + V(Writing, false, true, false, false, false) \ + V(CanAbort, false, false, false, true, false) \ + V(AnySideEffects, true, true, true, true, false) \ + V(BlockTerminator, false, false, false, false, true) \ + V(BlockTerminatorWithAnySideEffect, true, true, true, true, true) \ + V(ReadingAndCanAbort, true, false, false, true, false) \ + V(WritingAndCanAbort, false, true, false, true, false) + +#define DEFINE_OP_PROPERTY(Name, can_read, can_write, can_allocate, can_abort, \ + is_block_terminator) \ + static constexpr OpProperties Name() { \ + return {can_read, can_write, can_allocate, can_abort, \ + is_block_terminator}; \ } + + ALL_OP_PROPERTIES(DEFINE_OP_PROPERTY) +#undef DEFINE_OP_PROPERTY + bool operator==(const OpProperties& other) const { return can_read == other.can_read && can_write == other.can_write && can_abort == other.can_abort && @@ -310,6 +262,8 @@ struct alignas(OpIndex) Operation { return StorageSlotCount(opcode, input_count); } + base::Vector<const RegisterRepresentation> outputs_rep() const; + template <class Op> bool Is() const { return opcode == Op::opcode; @@ -362,9 +316,10 @@ std::ostream& operator<<(std::ostream& os, OperationPrintStyle op); inline std::ostream& operator<<(std::ostream& os, const Operation& op) { return os << OperationPrintStyle{op}; } -inline void Print(const Operation& op) { std::cout << op << "\n"; } +void Print(const Operation& op); OperationStorageSlot* AllocateOpStorage(Graph* graph, size_t slot_count); +const Operation& Get(const Graph& graph, OpIndex index); // Determine if an operation declares `properties`, which means that its // properties are static and don't depend on inputs or options. @@ -435,6 +390,9 @@ struct OperationT : Operation { OperationStorageSlot* ptr = AllocateOpStorage(graph, StorageSlotCount(input_count)); Derived* result = new (ptr) Derived(std::move(args)...); +#ifdef DEBUG + result->Validate(*graph); +#endif // If this DCHECK fails, then the number of inputs specified in the // operation constructor and in the static New function disagree. DCHECK_EQ(input_count, result->Operation::input_count); @@ -489,6 +447,11 @@ struct OperationT : Operation { PrintOptionsHelper(os, options, std::make_index_sequence<options_count>()); } + // Check graph invariants for this operation. Will be invoked in debug mode + // immediately upon construction. + // Concrete Operator classes are expected to re-define it. + void Validate(const Graph& graph) const = delete; + private: template <class... T, size_t... I> static void PrintOptionsHelper(std::ostream& os, @@ -502,6 +465,15 @@ struct OperationT : Operation { ...); os << "]"; } + + // All Operations have to define the outputs_rep function, to which + // Operation::outputs_rep() will forward, based on their opcode. If you forget + // to define it, then Operation::outputs_rep() would forward to itself, + // resulting in an infinite loop. To avoid this, we define here in OperationT + // a private version outputs_rep (with no implementation): if an operation + // forgets to define outputs_rep, then Operation::outputs_rep() tries to call + // this private version, which fails at compile time. + base::Vector<const RegisterRepresentation> outputs_rep() const; }; template <size_t InputCount, class Derived> @@ -587,6 +559,20 @@ class SupportedOperations { #undef DECLARE_GETTER }; +template <RegisterRepresentation::Enum... reps> +base::Vector<const RegisterRepresentation> RepVector() { + static const std::array<RegisterRepresentation, sizeof...(reps)> rep_array{ + RegisterRepresentation{reps}...}; + return base::VectorOf(rep_array); +} + +bool ValidOpInputRep(const Graph& graph, OpIndex input, + std::initializer_list<RegisterRepresentation> expected_rep, + base::Optional<size_t> projection_index = {}); +bool ValidOpInputRep(const Graph& graph, OpIndex input, + RegisterRepresentation expected_rep, + base::Optional<size_t> projection_index = {}); + struct WordBinopOp : FixedArityOperationT<2, WordBinopOp> { enum class Kind : uint8_t { kAdd, @@ -605,7 +591,10 @@ struct WordBinopOp : FixedArityOperationT<2, WordBinopOp> { Kind kind; WordRepresentation rep; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(static_cast<const RegisterRepresentation*>(&rep), 1); + } OpIndex left() const { return input(0); } OpIndex right() const { return input(1); } @@ -670,6 +659,11 @@ struct WordBinopOp : FixedArityOperationT<2, WordBinopOp> { WordBinopOp(OpIndex left, OpIndex right, Kind kind, WordRepresentation rep) : Base(left, right), kind(kind), rep(rep) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, left(), rep)); + DCHECK(ValidOpInputRep(graph, right(), rep)); + } auto options() const { return std::tuple{kind, rep}; } void PrintOptions(std::ostream& os) const; }; @@ -689,7 +683,10 @@ struct FloatBinopOp : FixedArityOperationT<2, FloatBinopOp> { Kind kind; FloatRepresentation rep; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(static_cast<const RegisterRepresentation*>(&rep), 1); + } OpIndex left() const { return input(0); } OpIndex right() const { return input(1); } @@ -711,9 +708,13 @@ struct FloatBinopOp : FixedArityOperationT<2, FloatBinopOp> { } FloatBinopOp(OpIndex left, OpIndex right, Kind kind, FloatRepresentation rep) - : Base(left, right), kind(kind), rep(rep) { + : Base(left, right), kind(kind), rep(rep) {} + + void Validate(const Graph& graph) const { DCHECK_IMPLIES(kind == any_of(Kind::kPower, Kind::kAtan2, Kind::kMod), rep == FloatRepresentation::Float64()); + DCHECK(ValidOpInputRep(graph, left(), rep)); + DCHECK(ValidOpInputRep(graph, right(), rep)); } auto options() const { return std::tuple{kind, rep}; } void PrintOptions(std::ostream& os) const; @@ -729,7 +730,17 @@ struct OverflowCheckedBinopOp Kind kind; WordRepresentation rep; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + switch (rep.value()) { + case WordRepresentation::Word32(): + return RepVector<RegisterRepresentation::Word32(), + RegisterRepresentation::Word32()>(); + case WordRepresentation::Word64(): + return RepVector<RegisterRepresentation::Word64(), + RegisterRepresentation::Word32()>(); + } + } OpIndex left() const { return input(0); } OpIndex right() const { return input(1); } @@ -747,6 +758,11 @@ struct OverflowCheckedBinopOp OverflowCheckedBinopOp(OpIndex left, OpIndex right, Kind kind, WordRepresentation rep) : Base(left, right), kind(kind), rep(rep) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, left(), rep)); + DCHECK(ValidOpInputRep(graph, right(), rep)); + } auto options() const { return std::tuple{kind, rep}; } void PrintOptions(std::ostream& os) const; }; @@ -762,15 +778,21 @@ struct WordUnaryOp : FixedArityOperationT<1, WordUnaryOp> { }; Kind kind; WordRepresentation rep; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(static_cast<const RegisterRepresentation*>(&rep), 1); + } OpIndex input() const { return Base::input(0); } static bool IsSupported(Kind kind, WordRepresentation rep); explicit WordUnaryOp(OpIndex input, Kind kind, WordRepresentation rep) - : Base(input), kind(kind), rep(rep) { + : Base(input), kind(kind), rep(rep) {} + + void Validate(const Graph& graph) const { DCHECK(IsSupported(kind, rep)); + DCHECK(ValidOpInputRep(graph, input(), rep)); } auto options() const { return std::tuple{kind, rep}; } }; @@ -808,15 +830,21 @@ struct FloatUnaryOp : FixedArityOperationT<1, FloatUnaryOp> { }; Kind kind; FloatRepresentation rep; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(static_cast<const RegisterRepresentation*>(&rep), 1); + } OpIndex input() const { return Base::input(0); } static bool IsSupported(Kind kind, FloatRepresentation rep); explicit FloatUnaryOp(OpIndex input, Kind kind, FloatRepresentation rep) - : Base(input), kind(kind), rep(rep) { + : Base(input), kind(kind), rep(rep) {} + + void Validate(const Graph& graph) const { DCHECK(IsSupported(kind, rep)); + DCHECK(ValidOpInputRep(graph, input(), rep)); } auto options() const { return std::tuple{kind, rep}; } }; @@ -834,7 +862,10 @@ struct ShiftOp : FixedArityOperationT<2, ShiftOp> { Kind kind; WordRepresentation rep; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(static_cast<const RegisterRepresentation*>(&rep), 1); + } OpIndex left() const { return input(0); } OpIndex right() const { return input(1); } @@ -868,6 +899,11 @@ struct ShiftOp : FixedArityOperationT<2, ShiftOp> { ShiftOp(OpIndex left, OpIndex right, Kind kind, WordRepresentation rep) : Base(left, right), kind(kind), rep(rep) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, left(), rep)); + DCHECK(ValidOpInputRep(graph, right(), WordRepresentation::Word32())); + } auto options() const { return std::tuple{kind, rep}; } }; std::ostream& operator<<(std::ostream& os, ShiftOp::Kind kind); @@ -875,17 +911,37 @@ std::ostream& operator<<(std::ostream& os, ShiftOp::Kind kind); struct EqualOp : FixedArityOperationT<2, EqualOp> { RegisterRepresentation rep; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Word32()>(); + } OpIndex left() const { return input(0); } OpIndex right() const { return input(1); } + bool ValidInputRep( + base::Vector<const RegisterRepresentation> input_reps) const; + EqualOp(OpIndex left, OpIndex right, RegisterRepresentation rep) - : Base(left, right), rep(rep) { + : Base(left, right), rep(rep) {} + + void Validate(const Graph& graph) const { +#ifdef DEBUG DCHECK(rep == any_of(RegisterRepresentation::Word32(), RegisterRepresentation::Word64(), RegisterRepresentation::Float32(), - RegisterRepresentation::Float64())); + RegisterRepresentation::Float64(), + RegisterRepresentation::Tagged())); + RegisterRepresentation input_rep = rep; +#ifdef V8_COMPRESS_POINTERS + // In the presence of pointer compression, we only compare the lower 32bit. + if (input_rep == RegisterRepresentation::Tagged()) { + input_rep = RegisterRepresentation::Compressed(); + } +#endif // V8_COMPRESS_POINTERS + DCHECK(ValidOpInputRep(graph, left(), input_rep)); + DCHECK(ValidOpInputRep(graph, right(), input_rep)); +#endif // DEBUG } auto options() const { return std::tuple{rep}; } }; @@ -900,14 +956,19 @@ struct ComparisonOp : FixedArityOperationT<2, ComparisonOp> { Kind kind; RegisterRepresentation rep; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Word32()>(); + } OpIndex left() const { return input(0); } OpIndex right() const { return input(1); } ComparisonOp(OpIndex left, OpIndex right, Kind kind, RegisterRepresentation rep) - : Base(left, right), kind(kind), rep(rep) { + : Base(left, right), kind(kind), rep(rep) {} + + void Validate(const Graph& graph) const { DCHECK_EQ(rep, any_of(RegisterRepresentation::Word32(), RegisterRepresentation::Word64(), RegisterRepresentation::Float32(), @@ -916,9 +977,21 @@ struct ComparisonOp : FixedArityOperationT<2, ComparisonOp> { rep == any_of(RegisterRepresentation::Float32(), RegisterRepresentation::Float64()), kind == any_of(Kind::kSignedLessThan, Kind::kSignedLessThanOrEqual)); + DCHECK(ValidOpInputRep(graph, left(), rep)); + DCHECK(ValidOpInputRep(graph, right(), rep)); } auto options() const { return std::tuple{kind, rep}; } + static bool IsLessThan(Kind kind) { + switch (kind) { + case Kind::kSignedLessThan: + case Kind::kUnsignedLessThan: + return true; + case Kind::kSignedLessThanOrEqual: + case Kind::kUnsignedLessThanOrEqual: + return false; + } + } static bool IsSigned(Kind kind) { switch (kind) { case Kind::kSignedLessThan: @@ -1036,21 +1109,92 @@ struct ChangeOp : FixedArityOperationT<1, ChangeOp> { signalling_nan_possible); } - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(&to, 1); + } OpIndex input() const { return Base::input(0); } ChangeOp(OpIndex input, Kind kind, Assumption assumption, RegisterRepresentation from, RegisterRepresentation to) : Base(input), kind(kind), assumption(assumption), from(from), to(to) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), from)); + } auto options() const { return std::tuple{kind, assumption, from, to}; } }; std::ostream& operator<<(std::ostream& os, ChangeOp::Kind kind); std::ostream& operator<<(std::ostream& os, ChangeOp::Assumption assumption); +struct ChangeOrDeoptOp : FixedArityOperationT<2, ChangeOrDeoptOp> { + enum class Kind : uint8_t { + kUint32ToInt32, + kInt64ToInt32, + kUint64ToInt32, + kUint64ToInt64, + kFloat64ToInt32, + kFloat64ToInt64, + }; + Kind kind; + CheckForMinusZeroMode minus_zero_mode; + FeedbackSource feedback; + + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + switch (kind) { + case Kind::kUint32ToInt32: + case Kind::kInt64ToInt32: + case Kind::kUint64ToInt32: + case Kind::kFloat64ToInt32: + return RepVector<RegisterRepresentation::Word32()>(); + case Kind::kUint64ToInt64: + case Kind::kFloat64ToInt64: + return RepVector<RegisterRepresentation::Word64()>(); + } + } + + OpIndex input() const { return Base::input(0); } + OpIndex frame_state() const { return Base::input(1); } + + ChangeOrDeoptOp(OpIndex input, OpIndex frame_state, Kind kind, + CheckForMinusZeroMode minus_zero_mode, + const FeedbackSource& feedback) + : Base(input, frame_state), + kind(kind), + minus_zero_mode(minus_zero_mode), + feedback(feedback) {} + + void Validate(const Graph& graph) const { + switch (kind) { + case Kind::kUint32ToInt32: + DCHECK( + ValidOpInputRep(graph, input(), RegisterRepresentation::Word32())); + break; + case Kind::kInt64ToInt32: + case Kind::kUint64ToInt32: + case Kind::kUint64ToInt64: + DCHECK( + ValidOpInputRep(graph, input(), RegisterRepresentation::Word64())); + break; + case Kind::kFloat64ToInt32: + case Kind::kFloat64ToInt64: + DCHECK( + ValidOpInputRep(graph, input(), RegisterRepresentation::Float64())); + break; + } + DCHECK(Get(graph, frame_state()).Is<FrameStateOp>()); + } + auto options() const { return std::tuple{kind, minus_zero_mode, feedback}; } +}; +std::ostream& operator<<(std::ostream& os, ChangeOrDeoptOp::Kind kind); + // Perform a conversion and return a pair of the result and a bit if it was // successful. struct TryChangeOp : FixedArityOperationT<1, TryChangeOp> { + static constexpr uint32_t kSuccessValue = 1; + static constexpr uint32_t kFailureValue = 0; enum class Kind : uint8_t { // The result of the truncation is undefined if the result is out of range. kSignedFloatTruncateOverflowUndefined, @@ -1060,13 +1204,27 @@ struct TryChangeOp : FixedArityOperationT<1, TryChangeOp> { FloatRepresentation from; WordRepresentation to; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + switch (to.value()) { + case WordRepresentation::Word32(): + return RepVector<RegisterRepresentation::Word32(), + RegisterRepresentation::Word32()>(); + case WordRepresentation::Word64(): + return RepVector<RegisterRepresentation::Word64(), + RegisterRepresentation::Word32()>(); + } + } OpIndex input() const { return Base::input(0); } TryChangeOp(OpIndex input, Kind kind, FloatRepresentation from, WordRepresentation to) : Base(input), kind(kind), from(from), to(to) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), from)); + } auto options() const { return std::tuple{kind, from, to}; } }; std::ostream& operator<<(std::ostream& os, TryChangeOp::Kind kind); @@ -1076,13 +1234,22 @@ struct Float64InsertWord32Op : FixedArityOperationT<2, Float64InsertWord32Op> { enum class Kind { kLowHalf, kHighHalf }; Kind kind; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Float64()>(); + } OpIndex float64() const { return input(0); } OpIndex word32() const { return input(1); } Float64InsertWord32Op(OpIndex float64, OpIndex word32, Kind kind) : Base(float64, word32), kind(kind) {} + + void Validate(const Graph& graph) const { + DCHECK( + ValidOpInputRep(graph, float64(), RegisterRepresentation::Float64())); + DCHECK(ValidOpInputRep(graph, word32(), RegisterRepresentation::Word32())); + } auto options() const { return std::tuple{kind}; } }; std::ostream& operator<<(std::ostream& os, Float64InsertWord32Op::Kind kind); @@ -1093,16 +1260,24 @@ struct TaggedBitcastOp : FixedArityOperationT<1, TaggedBitcastOp> { // Due to moving GC, converting from or to pointers doesn't commute with GC. static constexpr OpProperties properties = OpProperties::Reading(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(&to, 1); + } OpIndex input() const { return Base::input(0); } TaggedBitcastOp(OpIndex input, RegisterRepresentation from, RegisterRepresentation to) - : Base(input), from(from), to(to) { + : Base(input), from(from), to(to) {} + + void Validate(const Graph& graph) const { DCHECK((from == RegisterRepresentation::PointerSized() && to == RegisterRepresentation::Tagged()) || (from == RegisterRepresentation::Tagged() && - to == RegisterRepresentation::PointerSized())); + to == RegisterRepresentation::PointerSized()) || + (from == RegisterRepresentation::Compressed() && + to == RegisterRepresentation::Word32())); + DCHECK(ValidOpInputRep(graph, input(), from)); } auto options() const { return std::tuple{from, to}; } }; @@ -1110,22 +1285,28 @@ struct TaggedBitcastOp : FixedArityOperationT<1, TaggedBitcastOp> { struct SelectOp : FixedArityOperationT<3, SelectOp> { enum class Implementation : uint8_t { kBranch, kCMove }; - static constexpr OpProperties properties = OpProperties::Pure(); RegisterRepresentation rep; BranchHint hint; Implementation implem; + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(&rep, 1); + } + SelectOp(OpIndex cond, OpIndex vtrue, OpIndex vfalse, RegisterRepresentation rep, BranchHint hint, Implementation implem) - : Base(cond, vtrue, vfalse), rep(rep), hint(hint), implem(implem) { -#ifdef DEBUG - if (implem == Implementation::kCMove) { - DCHECK((rep == RegisterRepresentation::Word32() && - SupportedOperations::word32_select()) || - (rep == RegisterRepresentation::Word64() && - SupportedOperations::word64_select())); - } -#endif + : Base(cond, vtrue, vfalse), rep(rep), hint(hint), implem(implem) {} + + void Validate(const Graph& graph) const { + DCHECK_IMPLIES(implem == Implementation::kCMove, + (rep == RegisterRepresentation::Word32() && + SupportedOperations::word32_select()) || + (rep == RegisterRepresentation::Word64() && + SupportedOperations::word64_select())); + DCHECK(ValidOpInputRep(graph, cond(), RegisterRepresentation::Word32())); + DCHECK(ValidOpInputRep(graph, vtrue(), rep)); + DCHECK(ValidOpInputRep(graph, vfalse(), rep)); } OpIndex cond() const { return input(0); } @@ -1139,39 +1320,64 @@ std::ostream& operator<<(std::ostream& os, SelectOp::Implementation kind); struct PhiOp : OperationT<PhiOp> { RegisterRepresentation rep; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(&rep, 1); + } static constexpr size_t kLoopPhiBackEdgeIndex = 1; explicit PhiOp(base::Vector<const OpIndex> inputs, RegisterRepresentation rep) : Base(inputs), rep(rep) {} + + void Validate(const Graph& graph) const { +#ifdef DEBUG + for (OpIndex input : inputs()) { + DCHECK(ValidOpInputRep(graph, input, rep)); + } +#endif + } auto options() const { return std::tuple{rep}; } }; // Only used when moving a loop phi to a new graph while the loop backedge has // not been emitted yet. struct PendingLoopPhiOp : FixedArityOperationT<1, PendingLoopPhiOp> { - RegisterRepresentation rep; - union { - // Used when transforming a Turboshaft graph. - // This is not an input because it refers to the old graph. - OpIndex old_backedge_index = OpIndex::Invalid(); - // Used when translating from sea-of-nodes. - Node* old_backedge_node; + struct PhiIndex { + int index; + }; + struct Data { + union { + // Used when transforming a Turboshaft graph. + // This is not an input because it refers to the old graph. + OpIndex old_backedge_index = OpIndex::Invalid(); + // Used when translating from sea-of-nodes. + Node* old_backedge_node; + // Used when building loops with the assembler macros. + PhiIndex phi_index; + }; + explicit Data(OpIndex old_backedge_index) + : old_backedge_index(old_backedge_index) {} + explicit Data(Node* old_backedge_node) + : old_backedge_node(old_backedge_node) {} + explicit Data(PhiIndex phi_index) : phi_index(phi_index) {} }; + RegisterRepresentation rep; + Data data; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(&rep, 1); + } OpIndex first() const { return input(0); } - PendingLoopPhiOp(OpIndex first, RegisterRepresentation rep, - OpIndex old_backedge_index) - : Base(first), rep(rep), old_backedge_index(old_backedge_index) { - DCHECK(old_backedge_index.valid()); + PendingLoopPhiOp(OpIndex first, RegisterRepresentation rep, Data data) + : Base(first), rep(rep), data(data) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, first(), rep)); } - PendingLoopPhiOp(OpIndex first, RegisterRepresentation rep, - Node* old_backedge_node) - : Base(first), rep(rep), old_backedge_node(old_backedge_node) {} std::tuple<> options() const { UNREACHABLE(); } void PrintOptions(std::ostream& os) const; }; @@ -1192,6 +1398,7 @@ struct ConstantOp : FixedArityOperationT<0, ConstantOp> { }; Kind kind; + RegisterRepresentation rep = Representation(kind); union Storage { uint64_t integral; float float32; @@ -1206,9 +1413,12 @@ struct ConstantOp : FixedArityOperationT<0, ConstantOp> { Storage(Handle<HeapObject> constant) : handle(constant) {} } storage; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(&rep, 1); + } - RegisterRepresentation Representation() const { + static RegisterRepresentation Representation(Kind kind) { switch (kind) { case Kind::kWord32: return RegisterRepresentation::Word32(); @@ -1232,7 +1442,9 @@ struct ConstantOp : FixedArityOperationT<0, ConstantOp> { } ConstantOp(Kind kind, Storage storage) - : Base(), kind(kind), storage(storage) { + : Base(), kind(kind), storage(storage) {} + + void Validate(const Graph& graph) const { DCHECK_IMPLIES( kind == Kind::kWord32, storage.integral <= WordRepresentation::Word32().MaxUnsignedValue()); @@ -1418,6 +1630,14 @@ struct LoadOp : OperationT<LoadOp> { // There is a Wasm trap handler for out-of-bounds accesses. bool with_trap_handler : 1; + static constexpr Kind Aligned(BaseTaggedness base_is_tagged) { + switch (base_is_tagged) { + case BaseTaggedness::kTaggedBase: + return TaggedBase(); + case BaseTaggedness::kUntaggedBase: + return RawAligned(); + } + } static constexpr Kind TaggedBase() { return Kind{true, false, false}; } static constexpr Kind RawAligned() { return Kind{false, false, false}; } static constexpr Kind RawUnaligned() { return Kind{false, true, false}; } @@ -1439,6 +1659,9 @@ struct LoadOp : OperationT<LoadOp> { return kind.with_trap_handler ? OpProperties::ReadingAndCanAbort() : OpProperties::Reading(); } + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(&result_rep, 1); + } OpIndex base() const { return input(0); } OpIndex index() const { @@ -1454,12 +1677,27 @@ struct LoadOp : OperationT<LoadOp> { result_rep(result_rep), element_size_log2(element_size_log2), offset(offset) { + input(0) = base; + if (index.valid()) { + input(1) = index; + } + } + + void Validate(const Graph& graph) const { DCHECK(loaded_rep.ToRegisterRepresentation() == result_rep || (loaded_rep.IsTagged() && result_rep == RegisterRepresentation::Compressed())); - DCHECK_IMPLIES(element_size_log2 > 0, index.valid()); - input(0) = base; - if (index.valid()) input(1) = index; + DCHECK_IMPLIES(element_size_log2 > 0, index().valid()); + DCHECK( + kind.tagged_base + ? ValidOpInputRep(graph, base(), RegisterRepresentation::Tagged()) + : ValidOpInputRep(graph, base(), + {RegisterRepresentation::PointerSized(), + RegisterRepresentation::Tagged()})); + if (index().valid()) { + DCHECK(ValidOpInputRep(graph, index(), + RegisterRepresentation::PointerSized())); + } } static LoadOp& New(Graph* graph, OpIndex base, OpIndex index, Kind kind, MemoryRepresentation loaded_rep, @@ -1496,6 +1734,7 @@ struct StoreOp : OperationT<StoreOp> { return kind.with_trap_handler ? OpProperties::WritingAndCanAbort() : OpProperties::Writing(); } + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } OpIndex base() const { return input(0); } OpIndex value() const { return input(1); } @@ -1512,10 +1751,27 @@ struct StoreOp : OperationT<StoreOp> { write_barrier(write_barrier), element_size_log2(element_size_log2), offset(offset) { - DCHECK_IMPLIES(element_size_log2 > 0, index.valid()); input(0) = base; input(1) = value; - if (index.valid()) input(2) = index; + if (index.valid()) { + input(2) = index; + } + } + + void Validate(const Graph& graph) const { + DCHECK_IMPLIES(element_size_log2 > 0, index().valid()); + DCHECK( + kind.tagged_base + ? ValidOpInputRep(graph, base(), RegisterRepresentation::Tagged()) + : ValidOpInputRep(graph, base(), + {RegisterRepresentation::PointerSized(), + RegisterRepresentation::Tagged()})); + DCHECK(ValidOpInputRep(graph, value(), + stored_rep.ToRegisterRepresentationForStore())); + if (index().valid()) { + DCHECK(ValidOpInputRep(graph, index(), + RegisterRepresentation::PointerSized())); + } } static StoreOp& New(Graph* graph, OpIndex base, OpIndex index, OpIndex value, Kind kind, MemoryRepresentation stored_rep, @@ -1533,6 +1789,51 @@ struct StoreOp : OperationT<StoreOp> { } }; +struct AllocateOp : FixedArityOperationT<1, AllocateOp> { + AllocationType type; + AllowLargeObjects allow_large_objects; + + static constexpr OpProperties properties = OpProperties::PureMayAllocate(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex size() const { return input(0); } + + AllocateOp(OpIndex size, AllocationType type, + AllowLargeObjects allow_large_objects) + : Base(size), type(type), allow_large_objects(allow_large_objects) {} + + void Validate(const Graph& graph) const { + DCHECK( + ValidOpInputRep(graph, size(), RegisterRepresentation::PointerSized())); + } + void PrintOptions(std::ostream& os) const; + auto options() const { return std::tuple{type, allow_large_objects}; } +}; + +struct DecodeExternalPointerOp + : FixedArityOperationT<1, DecodeExternalPointerOp> { + ExternalPointerTag tag; + + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::PointerSized()>(); + } + + OpIndex handle() const { return input(0); } + + DecodeExternalPointerOp(OpIndex handle, ExternalPointerTag tag) + : Base(handle), tag(tag) {} + + void Validate(const Graph& graph) const { + DCHECK_NE(tag, kExternalPointerNullTag); + DCHECK(ValidOpInputRep(graph, handle(), RegisterRepresentation::Word32())); + } + void PrintOptions(std::ostream& os) const; + auto options() const { return std::tuple{tag}; } +}; + // Retain a HeapObject to prevent it from being garbage collected too early. struct RetainOp : FixedArityOperationT<1, RetainOp> { OpIndex retained() const { return input(0); } @@ -1541,8 +1842,14 @@ struct RetainOp : FixedArityOperationT<1, RetainOp> { // this must not be reordered with operations reading from the heap, we mark // it as writing to prevent such reorderings. static constexpr OpProperties properties = OpProperties::Writing(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } explicit RetainOp(OpIndex retained) : Base(retained) {} + + void Validate(const Graph& graph) const { + DCHECK( + ValidOpInputRep(graph, retained(), RegisterRepresentation::Tagged())); + } auto options() const { return std::tuple{}; } }; @@ -1551,11 +1858,19 @@ struct StackPointerGreaterThanOp StackCheckKind kind; static constexpr OpProperties properties = OpProperties::Reading(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Word32()>(); + } OpIndex stack_limit() const { return input(0); } StackPointerGreaterThanOp(OpIndex stack_limit, StackCheckKind kind) : Base(stack_limit), kind(kind) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, stack_limit(), + RegisterRepresentation::PointerSized())); + } auto options() const { return std::tuple{kind}; } }; @@ -1567,8 +1882,12 @@ struct StackSlotOp : FixedArityOperationT<0, StackSlotOp> { int alignment; static constexpr OpProperties properties = OpProperties::Writing(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::PointerSized()>(); + } StackSlotOp(int size, int alignment) : size(size), alignment(alignment) {} + void Validate(const Graph& graph) const {} auto options() const { return std::tuple{size, alignment}; } }; @@ -1579,9 +1898,19 @@ struct FrameConstantOp : FixedArityOperationT<0, FrameConstantOp> { enum class Kind { kStackCheckOffset, kFramePointer, kParentFramePointer }; Kind kind; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + switch (kind) { + case Kind::kStackCheckOffset: + return RepVector<RegisterRepresentation::Tagged()>(); + case Kind::kFramePointer: + case Kind::kParentFramePointer: + return RepVector<RegisterRepresentation::PointerSized()>(); + } + } explicit FrameConstantOp(Kind kind) : Base(), kind(kind) {} + void Validate(const Graph& graph) const {} auto options() const { return std::tuple{kind}; } }; std::ostream& operator<<(std::ostream& os, FrameConstantOp::Kind kind); @@ -1590,7 +1919,8 @@ struct FrameStateOp : OperationT<FrameStateOp> { bool inlined; const FrameStateData* data; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } OpIndex parent_frame_state() const { DCHECK(inlined); @@ -1601,37 +1931,44 @@ struct FrameStateOp : OperationT<FrameStateOp> { if (inlined) result += 1; return result; } + uint16_t state_values_count() const { + DCHECK_EQ(input_count - inlined, state_values().size()); + return input_count - inlined; + } + const OpIndex state_value(size_t idx) const { return state_values()[idx]; } + + RegisterRepresentation state_value_rep(size_t idx) const { + return RegisterRepresentation::FromMachineRepresentation( + data->machine_types[idx].representation()); + } FrameStateOp(base::Vector<const OpIndex> inputs, bool inlined, const FrameStateData* data) : Base(inputs), inlined(inlined), data(data) {} + + void Validate(const Graph& graph) const { + if (inlined) { + DCHECK(Get(graph, parent_frame_state()).Is<FrameStateOp>()); + } + // TODO(tebbi): Check frame state inputs using `FrameStateData`. + } void PrintOptions(std::ostream& os) const; auto options() const { return std::tuple{inlined, data}; } }; -// CheckLazyDeoptOp should always immediately follow a call. -// Semantically, it deopts if the current code object has been -// deoptimized. But this might also be implemented differently. -struct CheckLazyDeoptOp : FixedArityOperationT<2, CheckLazyDeoptOp> { - static constexpr OpProperties properties = OpProperties::CanAbort(); - - OpIndex call() const { return input(0); } - OpIndex frame_state() const { return input(1); } - - CheckLazyDeoptOp(OpIndex call, OpIndex frame_state) - : Base(call, frame_state) {} - auto options() const { return std::tuple{}; } -}; - struct DeoptimizeOp : FixedArityOperationT<1, DeoptimizeOp> { const DeoptimizeParameters* parameters; static constexpr OpProperties properties = OpProperties::BlockTerminator(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } OpIndex frame_state() const { return input(0); } DeoptimizeOp(OpIndex frame_state, const DeoptimizeParameters* parameters) : Base(frame_state), parameters(parameters) {} + void Validate(const Graph& graph) const { + DCHECK(Get(graph, frame_state()).Is<FrameStateOp>()); + } auto options() const { return std::tuple{parameters}; } }; @@ -1640,6 +1977,7 @@ struct DeoptimizeIfOp : FixedArityOperationT<2, DeoptimizeIfOp> { const DeoptimizeParameters* parameters; static constexpr OpProperties properties = OpProperties::CanAbort(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } OpIndex condition() const { return input(0); } OpIndex frame_state() const { return input(1); } @@ -1649,6 +1987,11 @@ struct DeoptimizeIfOp : FixedArityOperationT<2, DeoptimizeIfOp> { : Base(condition, frame_state), negated(negated), parameters(parameters) {} + + void Validate(const Graph& graph) const { + DCHECK( + ValidOpInputRep(graph, condition(), RegisterRepresentation::Word32())); + } auto options() const { return std::tuple{negated, parameters}; } }; @@ -1657,66 +2000,223 @@ struct TrapIfOp : FixedArityOperationT<1, TrapIfOp> { const TrapId trap_id; static constexpr OpProperties properties = OpProperties::CanAbort(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } OpIndex condition() const { return input(0); } TrapIfOp(OpIndex condition, bool negated, const TrapId trap_id) : Base(condition), negated(negated), trap_id(trap_id) {} + + void Validate(const Graph& graph) const { + DCHECK( + ValidOpInputRep(graph, condition(), RegisterRepresentation::Word32())); + } auto options() const { return std::tuple{negated, trap_id}; } }; +struct StaticAssertOp : FixedArityOperationT<1, StaticAssertOp> { + static constexpr OpProperties properties = OpProperties::CanAbort(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } + const char* source; + + OpIndex condition() const { return Base::input(0); } + + StaticAssertOp(OpIndex condition, const char* source) + : Base(condition), source(source) {} + + void Validate(const Graph& graph) const { + DCHECK( + ValidOpInputRep(graph, condition(), RegisterRepresentation::Word32())); + } + auto options() const { return std::tuple{source}; } +}; + struct ParameterOp : FixedArityOperationT<0, ParameterOp> { int32_t parameter_index; + RegisterRepresentation rep; const char* debug_name; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return {&rep, 1}; + } - explicit ParameterOp(int32_t parameter_index, const char* debug_name = "") - : Base(), parameter_index(parameter_index), debug_name(debug_name) {} - auto options() const { return std::tuple{parameter_index, debug_name}; } + explicit ParameterOp(int32_t parameter_index, RegisterRepresentation rep, + const char* debug_name = "") + : Base(), + parameter_index(parameter_index), + rep(rep), + debug_name(debug_name) {} + void Validate(const Graph& graph) const {} + auto options() const { return std::tuple{parameter_index, rep, debug_name}; } void PrintOptions(std::ostream& os) const; }; struct OsrValueOp : FixedArityOperationT<0, OsrValueOp> { int32_t index; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } explicit OsrValueOp(int32_t index) : Base(), index(index) {} + void Validate(const Graph& graph) const {} auto options() const { return std::tuple{index}; } }; -struct CallOp : OperationT<CallOp> { +struct TSCallDescriptor : public NON_EXPORTED_BASE(ZoneObject) { const CallDescriptor* descriptor; + base::Vector<const RegisterRepresentation> out_reps; + + TSCallDescriptor(const CallDescriptor* descriptor, + base::Vector<const RegisterRepresentation> out_reps) + : descriptor(descriptor), out_reps(out_reps) {} + + static const TSCallDescriptor* Create(const CallDescriptor* descriptor, + Zone* graph_zone) { + base::Vector<RegisterRepresentation> out_reps = + graph_zone->NewVector<RegisterRepresentation>( + descriptor->ReturnCount()); + for (size_t i = 0; i < descriptor->ReturnCount(); ++i) { + out_reps[i] = RegisterRepresentation::FromMachineRepresentation( + descriptor->GetReturnType(i).representation()); + } + return graph_zone->New<TSCallDescriptor>(descriptor, out_reps); + } +}; + +struct CallOp : OperationT<CallOp> { + const TSCallDescriptor* descriptor; static constexpr OpProperties properties = OpProperties::AnySideEffects(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return descriptor->out_reps; + } + + bool HasFrameState() const { + return descriptor->descriptor->NeedsFrameState(); + } OpIndex callee() const { return input(0); } + OpIndex frame_state() const { + return HasFrameState() ? input(1) : OpIndex::Invalid(); + } base::Vector<const OpIndex> arguments() const { - return inputs().SubVector(1, input_count); + return inputs().SubVector(1 + HasFrameState(), input_count); } - CallOp(OpIndex callee, base::Vector<const OpIndex> arguments, - const CallDescriptor* descriptor) - : Base(1 + arguments.size()), descriptor(descriptor) { + CallOp(OpIndex callee, OpIndex frame_state, + base::Vector<const OpIndex> arguments, + const TSCallDescriptor* descriptor) + : Base(1 + frame_state.valid() + arguments.size()), + descriptor(descriptor) { base::Vector<OpIndex> inputs = this->inputs(); inputs[0] = callee; - inputs.SubVector(1, inputs.size()).OverwriteWith(arguments); + if (frame_state.valid()) { + inputs[1] = frame_state; + } + inputs.SubVector(1 + frame_state.valid(), inputs.size()) + .OverwriteWith(arguments); } - static CallOp& New(Graph* graph, OpIndex callee, + void Validate(const Graph& graph) const { + if (frame_state().valid()) { + DCHECK(Get(graph, frame_state()).Is<FrameStateOp>()); + } + // TODO(tebbi): Check call inputs based on `TSCallDescriptor`. + } + + static CallOp& New(Graph* graph, OpIndex callee, OpIndex frame_state, base::Vector<const OpIndex> arguments, - const CallDescriptor* descriptor) { - return Base::New(graph, 1 + arguments.size(), callee, arguments, - descriptor); + const TSCallDescriptor* descriptor) { + return Base::New(graph, 1 + frame_state.valid() + arguments.size(), callee, + frame_state, arguments, descriptor); } auto options() const { return std::tuple{descriptor}; } }; +struct CallAndCatchExceptionOp : OperationT<CallAndCatchExceptionOp> { + const TSCallDescriptor* descriptor; + Block* if_success; + Block* if_exception; + + static constexpr OpProperties properties = + OpProperties::BlockTerminatorWithAnySideEffect(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return descriptor->out_reps; + } + + bool HasFrameState() const { + return descriptor->descriptor->NeedsFrameState(); + } + + OpIndex callee() const { return input(0); } + OpIndex frame_state() const { + return HasFrameState() ? input(1) : OpIndex::Invalid(); + } + base::Vector<const OpIndex> arguments() const { + return inputs().SubVector(1 + HasFrameState(), input_count); + } + + CallAndCatchExceptionOp(OpIndex callee, OpIndex frame_state, + base::Vector<const OpIndex> arguments, + Block* if_success, Block* if_exception, + const TSCallDescriptor* descriptor) + : Base(1 + frame_state.valid() + arguments.size()), + descriptor(descriptor), + if_success(if_success), + if_exception(if_exception) { + base::Vector<OpIndex> inputs = this->inputs(); + inputs[0] = callee; + if (frame_state.valid()) { + inputs[1] = frame_state; + } + inputs.SubVector(1 + frame_state.valid(), inputs.size()) + .OverwriteWith(arguments); + } + + void Validate(const Graph& graph) const { + if (frame_state().valid()) { + DCHECK(Get(graph, frame_state()).Is<FrameStateOp>()); + } + } + + static CallAndCatchExceptionOp& New(Graph* graph, OpIndex callee, + OpIndex frame_state, + base::Vector<const OpIndex> arguments, + Block* if_success, Block* if_exception, + const TSCallDescriptor* descriptor) { + return Base::New(graph, 1 + frame_state.valid() + arguments.size(), callee, + frame_state, arguments, if_success, if_exception, + descriptor); + } + + auto options() const { + return std::tuple{descriptor, if_success, if_exception}; + } +}; + +struct LoadExceptionOp : FixedArityOperationT<0, LoadExceptionOp> { + static constexpr OpProperties properties = OpProperties::Reading(); + + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + LoadExceptionOp() : Base() {} + void Validate(const Graph& graph) const {} + + auto options() const { return std::tuple{}; } +}; + struct TailCallOp : OperationT<TailCallOp> { - const CallDescriptor* descriptor; + const TSCallDescriptor* descriptor; static constexpr OpProperties properties = OpProperties::BlockTerminatorWithAnySideEffect(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return descriptor->out_reps; + } OpIndex callee() const { return input(0); } base::Vector<const OpIndex> arguments() const { @@ -1724,15 +2224,16 @@ struct TailCallOp : OperationT<TailCallOp> { } TailCallOp(OpIndex callee, base::Vector<const OpIndex> arguments, - const CallDescriptor* descriptor) + const TSCallDescriptor* descriptor) : Base(1 + arguments.size()), descriptor(descriptor) { base::Vector<OpIndex> inputs = this->inputs(); inputs[0] = callee; inputs.SubVector(1, inputs.size()).OverwriteWith(arguments); } + void Validate(const Graph& graph) const {} static TailCallOp& New(Graph* graph, OpIndex callee, base::Vector<const OpIndex> arguments, - const CallDescriptor* descriptor) { + const TSCallDescriptor* descriptor) { return Base::New(graph, 1 + arguments.size(), callee, arguments, descriptor); } @@ -1742,13 +2243,16 @@ struct TailCallOp : OperationT<TailCallOp> { // Control-flow should never reach here. struct UnreachableOp : FixedArityOperationT<0, UnreachableOp> { static constexpr OpProperties properties = OpProperties::BlockTerminator(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } UnreachableOp() : Base() {} + void Validate(const Graph& graph) const {} auto options() const { return std::tuple{}; } }; struct ReturnOp : OperationT<ReturnOp> { static constexpr OpProperties properties = OpProperties::BlockTerminator(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } // Number of additional stack slots to be removed. OpIndex pop_count() const { return input(0); } @@ -1763,6 +2267,11 @@ struct ReturnOp : OperationT<ReturnOp> { inputs[0] = pop_count; inputs.SubVector(1, inputs.size()).OverwriteWith(return_values); } + + void Validate(const Graph& graph) const { + DCHECK( + ValidOpInputRep(graph, pop_count(), RegisterRepresentation::Word32())); + } static ReturnOp& New(Graph* graph, OpIndex pop_count, base::Vector<const OpIndex> return_values) { return Base::New(graph, 1 + return_values.size(), pop_count, return_values); @@ -1774,64 +2283,68 @@ struct GotoOp : FixedArityOperationT<0, GotoOp> { Block* destination; static constexpr OpProperties properties = OpProperties::BlockTerminator(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } explicit GotoOp(Block* destination) : Base(), destination(destination) {} + void Validate(const Graph& graph) const {} auto options() const { return std::tuple{destination}; } }; struct BranchOp : FixedArityOperationT<1, BranchOp> { Block* if_true; Block* if_false; + BranchHint hint; static constexpr OpProperties properties = OpProperties::BlockTerminator(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } OpIndex condition() const { return input(0); } - BranchOp(OpIndex condition, Block* if_true, Block* if_false) - : Base(condition), if_true(if_true), if_false(if_false) {} - auto options() const { return std::tuple{if_true, if_false}; } -}; - -// `CatchExceptionOp` has to follow a `CallOp` with a subsequent -// `CheckLazyDeoptOp`. It provides the exception value, which might only be used -// from the `if_exception` successor. -struct CatchExceptionOp : FixedArityOperationT<1, CatchExceptionOp> { - Block* if_success; - Block* if_exception; - - static constexpr OpProperties properties = OpProperties::BlockTerminator(); + BranchOp(OpIndex condition, Block* if_true, Block* if_false, BranchHint hint) + : Base(condition), if_true(if_true), if_false(if_false), hint(hint) {} - OpIndex call() const { return input(0); } - - explicit CatchExceptionOp(OpIndex call, Block* if_success, - Block* if_exception) - : Base(call), if_success(if_success), if_exception(if_exception) {} - auto options() const { return std::tuple{if_success, if_exception}; } + void Validate(const Graph& graph) const { + DCHECK( + ValidOpInputRep(graph, condition(), RegisterRepresentation::Word32())); + } + auto options() const { return std::tuple{if_true, if_false, hint}; } }; struct SwitchOp : FixedArityOperationT<1, SwitchOp> { struct Case { int32_t value; Block* destination; + BranchHint hint; - Case(int32_t value, Block* destination) - : value(value), destination(destination) {} + Case(int32_t value, Block* destination, BranchHint hint) + : value(value), destination(destination), hint(hint) {} bool operator==(const Case& other) const { - return value == other.value && destination == other.destination; + return value == other.value && destination == other.destination && + hint == other.hint; } }; base::Vector<const Case> cases; Block* default_case; + BranchHint default_hint; static constexpr OpProperties properties = OpProperties::BlockTerminator(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } OpIndex input() const { return Base::input(0); } - SwitchOp(OpIndex input, base::Vector<const Case> cases, Block* default_case) - : Base(input), cases(cases), default_case(default_case) {} + SwitchOp(OpIndex input, base::Vector<const Case> cases, Block* default_case, + BranchHint default_hint) + : Base(input), + cases(cases), + default_case(default_case), + default_hint(default_hint) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), RegisterRepresentation::Word32())); + } void PrintOptions(std::ostream& os) const; - auto options() const { return std::tuple{cases, default_case}; } + auto options() const { return std::tuple{cases, default_case, default_hint}; } }; template <> @@ -1841,12 +2354,47 @@ struct fast_hash<SwitchOp::Case> { } }; +inline base::SmallVector<Block*, 4> SuccessorBlocks(const Operation& op) { + DCHECK(op.Properties().is_block_terminator); + switch (op.opcode) { + case Opcode::kCallAndCatchException: { + auto& casted = op.Cast<CallAndCatchExceptionOp>(); + return {casted.if_success, casted.if_exception}; + } + case Opcode::kGoto: { + auto& casted = op.Cast<GotoOp>(); + return {casted.destination}; + } + case Opcode::kBranch: { + auto& casted = op.Cast<BranchOp>(); + return {casted.if_true, casted.if_false}; + } + case Opcode::kReturn: + case Opcode::kDeoptimize: + case Opcode::kUnreachable: + return base::SmallVector<Block*, 4>{}; + case Opcode::kSwitch: { + auto& casted = op.Cast<SwitchOp>(); + base::SmallVector<Block*, 4> result; + for (const SwitchOp::Case& c : casted.cases) { + result.push_back(c.destination); + } + result.push_back(casted.default_case); + return result; + } + default: + UNREACHABLE(); + } +} + // Tuples are only used to lower operations with multiple outputs. // `TupleOp` should be folded away by subsequent `ProjectionOp`s. struct TupleOp : OperationT<TupleOp> { - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } explicit TupleOp(base::Vector<const OpIndex> inputs) : Base(inputs) {} + void Validate(const Graph& graph) const {} auto options() const { return std::tuple{}; } }; @@ -1854,15 +2402,845 @@ struct TupleOp : OperationT<TupleOp> { // distinguish them. struct ProjectionOp : FixedArityOperationT<1, ProjectionOp> { uint16_t index; + RegisterRepresentation rep; - static constexpr OpProperties properties = OpProperties::Pure(); + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(&rep, 1); + } OpIndex input() const { return Base::input(0); } - ProjectionOp(OpIndex input, uint16_t index) : Base(input), index(index) {} + ProjectionOp(OpIndex input, uint16_t index, RegisterRepresentation rep) + : Base(input), index(index), rep(rep) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), rep, index)); + } auto options() const { return std::tuple{index}; } }; +struct CheckTurboshaftTypeOfOp + : FixedArityOperationT<1, CheckTurboshaftTypeOfOp> { + RegisterRepresentation rep; + Type type; + bool successful; + + static constexpr OpProperties properties = OpProperties::AnySideEffects(); + base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; } + + OpIndex input() const { return Base::input(0); } + + CheckTurboshaftTypeOfOp(OpIndex input, RegisterRepresentation rep, Type type, + bool successful) + : Base(input), rep(rep), type(std::move(type)), successful(successful) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), rep)); + } + auto options() const { return std::tuple{rep, type, successful}; } +}; + +struct ObjectIsOp : FixedArityOperationT<1, ObjectIsOp> { + enum class Kind : uint8_t { + kArrayBufferView, + kBigInt, + kBigInt64, + kCallable, + kConstructor, + kDetectableCallable, + kInternalizedString, + kNonCallable, + kNumber, + kReceiver, + kReceiverOrNullOrUndefined, + kSmi, + kString, + kSymbol, + kUndetectable, + }; + enum class InputAssumptions : uint8_t { + kNone, + kHeapObject, + kBigInt, + }; + Kind kind; + InputAssumptions input_assumptions; + + static constexpr OpProperties properties = OpProperties::Reading(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Word32()>(); + } + + OpIndex input() const { return Base::input(0); } + + ObjectIsOp(OpIndex input, Kind kind, InputAssumptions input_assumptions) + : Base(input), kind(kind), input_assumptions(input_assumptions) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), RegisterRepresentation::Tagged())); + } + auto options() const { return std::tuple{kind, input_assumptions}; } +}; +std::ostream& operator<<(std::ostream& os, ObjectIsOp::Kind kind); +std::ostream& operator<<(std::ostream& os, + ObjectIsOp::InputAssumptions input_assumptions); + +struct FloatIsOp : FixedArityOperationT<1, FloatIsOp> { + enum class Kind : uint8_t { + kNaN, + }; + Kind kind; + FloatRepresentation input_rep; + + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Word32()>(); + } + + OpIndex input() const { return Base::input(0); } + + FloatIsOp(OpIndex input, Kind kind, FloatRepresentation input_rep) + : Base(input), kind(kind), input_rep(input_rep) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), input_rep)); + } + auto options() const { return std::tuple{kind, input_rep}; } +}; +std::ostream& operator<<(std::ostream& os, FloatIsOp::Kind kind); + +struct ConvertToObjectOp : FixedArityOperationT<1, ConvertToObjectOp> { + enum class Kind : uint8_t { + kBigInt, + kBoolean, + kHeapNumber, + kNumber, + kSmi, + kString, + }; + enum class InputInterpretation : uint8_t { + kSigned, + kUnsigned, + kCharCode, + kCodePoint, + }; + Kind kind; + RegisterRepresentation input_rep; + InputInterpretation input_interpretation; + CheckForMinusZeroMode minus_zero_mode; + + static constexpr OpProperties properties = OpProperties::PureMayAllocate(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex input() const { return Base::input(0); } + + ConvertToObjectOp(OpIndex input, Kind kind, RegisterRepresentation input_rep, + InputInterpretation input_interpretation, + CheckForMinusZeroMode minus_zero_mode) + : Base(input), + kind(kind), + input_rep(input_rep), + input_interpretation(input_interpretation), + minus_zero_mode(minus_zero_mode) {} + + void Validate(const Graph& graph) const { + switch (kind) { + case Kind::kBigInt: + DCHECK_EQ(input_rep, RegisterRepresentation::Word64()); + DCHECK(ValidOpInputRep(graph, input(), input_rep)); + DCHECK_EQ(minus_zero_mode, + CheckForMinusZeroMode::kDontCheckForMinusZero); + break; + case Kind::kBoolean: + DCHECK_EQ(input_rep, RegisterRepresentation::Word32()); + DCHECK(ValidOpInputRep(graph, input(), input_rep)); + DCHECK_EQ(minus_zero_mode, + CheckForMinusZeroMode::kDontCheckForMinusZero); + break; + case Kind::kNumber: + case Kind::kHeapNumber: + DCHECK(ValidOpInputRep(graph, input(), input_rep)); + DCHECK_IMPLIES( + minus_zero_mode == CheckForMinusZeroMode::kCheckForMinusZero, + input_rep == RegisterRepresentation::Float64()); + break; + case Kind::kSmi: + DCHECK_EQ(input_rep, WordRepresentation::Word32()); + DCHECK_EQ(minus_zero_mode, + CheckForMinusZeroMode::kDontCheckForMinusZero); + DCHECK(ValidOpInputRep(graph, input(), input_rep)); + break; + case Kind::kString: + DCHECK_EQ(input_rep, WordRepresentation::Word32()); + DCHECK_EQ(input_interpretation, + any_of(InputInterpretation::kCharCode, + InputInterpretation::kCodePoint)); + DCHECK(ValidOpInputRep(graph, input(), input_rep)); + break; + } + } + + auto options() const { + return std::tuple{kind, input_rep, input_interpretation, minus_zero_mode}; + } +}; +std::ostream& operator<<(std::ostream& os, ConvertToObjectOp::Kind kind); + +struct ConvertToObjectOrDeoptOp + : FixedArityOperationT<2, ConvertToObjectOrDeoptOp> { + enum class Kind : uint8_t { + kSmi, + }; + enum class InputInterpretation : uint8_t { + kSigned, + kUnsigned, + }; + Kind kind; + RegisterRepresentation input_rep; + InputInterpretation input_interpretation; + FeedbackSource feedback; + + static constexpr OpProperties properties = OpProperties::CanAbort(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex input() const { return Base::input(0); } + OpIndex frame_state() const { return Base::input(1); } + + ConvertToObjectOrDeoptOp(OpIndex input, OpIndex frame_state, Kind kind, + RegisterRepresentation input_rep, + InputInterpretation input_interpretation, + const FeedbackSource& feedback) + : Base(input, frame_state), + kind(kind), + input_rep(input_rep), + input_interpretation(input_interpretation), + feedback(feedback) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), input_rep)); + } + + auto options() const { + return std::tuple{kind, input_rep, input_interpretation, feedback}; + } +}; +std::ostream& operator<<(std::ostream& os, ConvertToObjectOrDeoptOp::Kind kind); +std::ostream& operator<<( + std::ostream& os, + ConvertToObjectOrDeoptOp::InputInterpretation input_interpretation); + +struct ConvertObjectToPrimitiveOp + : FixedArityOperationT<1, ConvertObjectToPrimitiveOp> { + enum class Kind : uint8_t { + kInt32, + kInt64, + kUint32, + kBit, + kFloat64, + }; + enum class InputAssumptions : uint8_t { + kObject, + kSmi, + kNumberOrOddball, + }; + Kind kind; + InputAssumptions input_assumptions; + + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + switch (kind) { + case Kind::kInt32: + case Kind::kUint32: + case Kind::kBit: + return RepVector<RegisterRepresentation::Word32()>(); + case Kind::kInt64: + return RepVector<RegisterRepresentation::Word64()>(); + case Kind::kFloat64: + return RepVector<RegisterRepresentation::Float64()>(); + } + } + + OpIndex input() const { return Base::input(0); } + + ConvertObjectToPrimitiveOp(OpIndex input, Kind kind, + InputAssumptions input_assumptions) + : Base(input), kind(kind), input_assumptions(input_assumptions) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{kind, input_assumptions}; } +}; +std::ostream& operator<<(std::ostream& os, + ConvertObjectToPrimitiveOp::Kind kind); +std::ostream& operator<<( + std::ostream& os, + ConvertObjectToPrimitiveOp::InputAssumptions input_assumptions); + +struct ConvertObjectToPrimitiveOrDeoptOp + : FixedArityOperationT<2, ConvertObjectToPrimitiveOrDeoptOp> { + enum class PrimitiveKind : uint8_t { + kInt32, + kInt64, + kFloat64, + kArrayIndex, + }; + enum class ObjectKind : uint8_t { + kNumber, + kNumberOrBoolean, + kNumberOrOddball, + kNumberOrString, + kSmi, + }; + ObjectKind from_kind; + PrimitiveKind to_kind; + CheckForMinusZeroMode minus_zero_mode; + FeedbackSource feedback; + + static constexpr OpProperties properties = OpProperties::ReadingAndCanAbort(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + switch (to_kind) { + case PrimitiveKind::kInt32: + return RepVector<RegisterRepresentation::Word32()>(); + case PrimitiveKind::kInt64: + return RepVector<RegisterRepresentation::Word64()>(); + case PrimitiveKind::kFloat64: + return RepVector<RegisterRepresentation::Float64()>(); + case PrimitiveKind::kArrayIndex: + return Is64() ? RepVector<RegisterRepresentation::Word64()>() + : RepVector<RegisterRepresentation::Word32()>(); + } + } + + OpIndex input() const { return Base::input(0); } + OpIndex frame_state() const { return Base::input(1); } + + ConvertObjectToPrimitiveOrDeoptOp(OpIndex input, OpIndex frame_state, + ObjectKind from_kind, PrimitiveKind to_kind, + CheckForMinusZeroMode minus_zero_mode, + const FeedbackSource& feedback) + : Base(input, frame_state), + from_kind(from_kind), + to_kind(to_kind), + minus_zero_mode(minus_zero_mode), + feedback(feedback) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), RegisterRepresentation::Tagged())); + DCHECK(Get(graph, frame_state()).Is<FrameStateOp>()); + } + + auto options() const { + return std::tuple{from_kind, to_kind, minus_zero_mode, feedback}; + } +}; +std::ostream& operator<<(std::ostream& os, + ConvertObjectToPrimitiveOrDeoptOp::ObjectKind kind); +std::ostream& operator<<(std::ostream& os, + ConvertObjectToPrimitiveOrDeoptOp::PrimitiveKind kind); + +struct TruncateObjectToPrimitiveOp + : FixedArityOperationT<1, TruncateObjectToPrimitiveOp> { + enum class Kind : uint8_t { + kInt32, + kInt64, + kBit, + }; + enum class InputAssumptions : uint8_t { + kBigInt, + kNumberOrOddball, + kHeapObject, + kObject, + }; + Kind kind; + InputAssumptions input_assumptions; + + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + switch (kind) { + case Kind::kInt32: + case Kind::kBit: + return RepVector<RegisterRepresentation::Word32()>(); + case Kind::kInt64: + return RepVector<RegisterRepresentation::Word64()>(); + } + } + + OpIndex input() const { return Base::input(0); } + + TruncateObjectToPrimitiveOp(OpIndex input, Kind kind, + InputAssumptions input_assumptions) + : Base(input), kind(kind), input_assumptions(input_assumptions) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{kind, input_assumptions}; } +}; +std::ostream& operator<<(std::ostream& os, + TruncateObjectToPrimitiveOp::Kind kind); +std::ostream& operator<<( + std::ostream& os, + TruncateObjectToPrimitiveOp::InputAssumptions input_assumptions); + +enum class TagKind { + kSmiTag, +}; +std::ostream& operator<<(std::ostream& os, TagKind kind); + +struct TagOp : FixedArityOperationT<1, TagOp> { + TagKind kind; + + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex input() const { return Base::input(0); } + + TagOp(OpIndex input, TagKind kind) : Base(input), kind(kind) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), RegisterRepresentation::Word32())); + } + + auto options() const { return std::tuple{kind}; } +}; + +struct UntagOp : FixedArityOperationT<1, UntagOp> { + TagKind kind; + RegisterRepresentation rep; + + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return base::VectorOf(&rep, 1); + } + + OpIndex input() const { return Base::input(0); } + + UntagOp(OpIndex input, TagKind kind, RegisterRepresentation rep) + : Base(input), kind(kind), rep(rep) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{kind, rep}; } +}; + +struct NewConsStringOp : FixedArityOperationT<3, NewConsStringOp> { + static constexpr OpProperties properties = OpProperties::PureMayAllocate(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex length() const { return Base::input(0); } + OpIndex first() const { return Base::input(1); } + OpIndex second() const { return Base::input(2); } + + NewConsStringOp(OpIndex length, OpIndex first, OpIndex second) + : Base(length, first, second) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, length(), RegisterRepresentation::Word32())); + DCHECK(ValidOpInputRep(graph, first(), RegisterRepresentation::Tagged())); + DCHECK(ValidOpInputRep(graph, second(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{}; } +}; + +struct NewArrayOp : FixedArityOperationT<1, NewArrayOp> { + enum class Kind : uint8_t { + kDouble, + kObject, + }; + Kind kind; + AllocationType allocation_type; + + static constexpr OpProperties properties = OpProperties::PureMayAllocate(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex length() const { return Base::input(0); } + + NewArrayOp(OpIndex length, Kind kind, AllocationType allocation_type) + : Base(length), kind(kind), allocation_type(allocation_type) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, length(), + RegisterRepresentation::PointerSized())); + } + + auto options() const { return std::tuple{kind, allocation_type}; } +}; +std::ostream& operator<<(std::ostream& os, NewArrayOp::Kind kind); + +struct DoubleArrayMinMaxOp : FixedArityOperationT<1, DoubleArrayMinMaxOp> { + enum class Kind : uint8_t { + kMin, + kMax, + }; + Kind kind; + + static constexpr OpProperties properties = OpProperties::PureMayAllocate(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex array() const { return Base::input(0); } + + DoubleArrayMinMaxOp(OpIndex array, Kind kind) : Base(array), kind(kind) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, array(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{kind}; } +}; +std::ostream& operator<<(std::ostream& os, DoubleArrayMinMaxOp::Kind kind); + +// TODO(nicohartmann@): We should consider getting rid of the LoadFieldByIndex +// operation. +struct LoadFieldByIndexOp : FixedArityOperationT<2, LoadFieldByIndexOp> { + static constexpr OpProperties properties = OpProperties::PureMayAllocate(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex object() const { return Base::input(0); } + // Index encoding (see `src/objects/field-index-inl.h`): + // For efficiency, the LoadByFieldIndex instruction takes an index that is + // optimized for quick access. If the property is inline, the index is + // positive. If it's out-of-line, the encoded index is -raw_index - 1 to + // disambiguate the zero out-of-line index from the zero inobject case. + // The index itself is shifted up by one bit, the lower-most bit + // signifying if the field is a mutable double box (1) or not (0). + OpIndex index() const { return Base::input(1); } + + LoadFieldByIndexOp(OpIndex object, OpIndex index) : Base(object, index) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, object(), RegisterRepresentation::Tagged())); + DCHECK(ValidOpInputRep(graph, index(), RegisterRepresentation::Word32())); + } + + auto options() const { return std::tuple{}; } +}; + +struct DebugBreakOp : FixedArityOperationT<0, DebugBreakOp> { + static constexpr OpProperties properties = OpProperties::AnySideEffects(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<>(); + } + + DebugBreakOp() : Base() {} + void Validate(const Graph& graph) const {} + + auto options() const { return std::tuple{}; } +}; + +struct BigIntBinopOp : FixedArityOperationT<3, BigIntBinopOp> { + enum class Kind : uint8_t { + kAdd, + kSub, + kMul, + kDiv, + kMod, + kBitwiseAnd, + kBitwiseOr, + kBitwiseXor, + kShiftLeft, + kShiftRightArithmetic, + }; + Kind kind; + + // TODO(nicohartmann@): Maybe we can specify more precise properties here. + // These operations can deopt (abort), allocate and read immutable data. + static constexpr OpProperties properties = OpProperties::AnySideEffects(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex left() const { return Base::input(0); } + OpIndex right() const { return Base::input(1); } + OpIndex frame_state() const { return Base::input(2); } + + BigIntBinopOp(OpIndex left, OpIndex right, OpIndex frame_state, Kind kind) + : Base(left, right, frame_state), kind(kind) {} + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, left(), RegisterRepresentation::Tagged())); + DCHECK(ValidOpInputRep(graph, right(), RegisterRepresentation::Tagged())); + DCHECK(Get(graph, frame_state()).Is<FrameStateOp>()); + } + + auto options() const { return std::tuple{kind}; } +}; +std::ostream& operator<<(std::ostream& os, BigIntBinopOp::Kind kind); + +struct BigIntEqualOp : FixedArityOperationT<2, BigIntEqualOp> { + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex left() const { return Base::input(0); } + OpIndex right() const { return Base::input(1); } + + BigIntEqualOp(OpIndex left, OpIndex right) : Base(left, right) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, left(), RegisterRepresentation::Tagged())); + DCHECK(ValidOpInputRep(graph, right(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{}; } +}; + +struct BigIntComparisonOp : FixedArityOperationT<2, BigIntComparisonOp> { + enum class Kind : uint8_t { + kLessThan, + kLessThanOrEqual, + }; + Kind kind; + + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex left() const { return Base::input(0); } + OpIndex right() const { return Base::input(1); } + + BigIntComparisonOp(OpIndex left, OpIndex right, Kind kind) + : Base(left, right), kind(kind) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, left(), RegisterRepresentation::Tagged())); + DCHECK(ValidOpInputRep(graph, right(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{kind}; } +}; +std::ostream& operator<<(std::ostream& os, BigIntComparisonOp::Kind kind); + +struct BigIntUnaryOp : FixedArityOperationT<1, BigIntUnaryOp> { + enum class Kind : uint8_t { + kNegate, + }; + Kind kind; + + static constexpr OpProperties properties = OpProperties::PureMayAllocate(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex input() const { return Base::input(0); } + + BigIntUnaryOp(OpIndex input, Kind kind) : Base(input), kind(kind) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, input(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{kind}; } +}; +std::ostream& operator<<(std::ostream& os, BigIntUnaryOp::Kind kind); + +struct LoadRootRegisterOp : FixedArityOperationT<0, LoadRootRegisterOp> { + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::PointerSized()>(); + } + + LoadRootRegisterOp() : Base() {} + void Validate(const Graph& graph) const {} + std::tuple<> options() const { return {}; } +}; + +struct StringAtOp : FixedArityOperationT<2, StringAtOp> { + enum class Kind : uint8_t { + kCharCode, + kCodePoint, + }; + Kind kind; + + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Word32()>(); + } + + OpIndex string() const { return Base::input(0); } + OpIndex position() const { return Base::input(1); } + + StringAtOp(OpIndex string, OpIndex position, Kind kind) + : Base(string, position), kind(kind) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, string(), RegisterRepresentation::Tagged())); + DCHECK(ValidOpInputRep(graph, position(), + RegisterRepresentation::PointerSized())); + } + + auto options() const { return std::tuple{kind}; } +}; +std::ostream& operator<<(std::ostream& os, StringAtOp::Kind kind); + +#ifdef V8_INTL_SUPPORT +struct StringToCaseIntlOp : FixedArityOperationT<1, StringToCaseIntlOp> { + enum class Kind : uint8_t { + kLower, + kUpper, + }; + Kind kind; + + static constexpr OpProperties properties = OpProperties::PureMayAllocate(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex string() const { return Base::input(0); } + + StringToCaseIntlOp(OpIndex string, Kind kind) : Base(string), kind(kind) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, string(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{kind}; } +}; +std::ostream& operator<<(std::ostream& os, StringToCaseIntlOp::Kind kind); +#endif // V8_INTL_SUPPORT + +struct StringLengthOp : FixedArityOperationT<1, StringLengthOp> { + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Word32()>(); + } + + OpIndex string() const { return Base::input(0); } + + explicit StringLengthOp(OpIndex string) : Base(string) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, string(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{}; } +}; + +struct StringIndexOfOp : FixedArityOperationT<3, StringIndexOfOp> { + static constexpr OpProperties properties = OpProperties::PureMayAllocate(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + // Search the string `search` within the string `string` starting at + // `position`. + OpIndex string() const { return Base::input(0); } + OpIndex search() const { return Base::input(1); } + OpIndex position() const { return Base::input(2); } + + StringIndexOfOp(OpIndex string, OpIndex search, OpIndex position) + : Base(string, search, position) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, string(), RegisterRepresentation::Tagged())); + DCHECK(ValidOpInputRep(graph, search(), RegisterRepresentation::Tagged())); + DCHECK( + ValidOpInputRep(graph, position(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{}; } +}; + +struct StringFromCodePointAtOp + : FixedArityOperationT<2, StringFromCodePointAtOp> { + static constexpr OpProperties properties = OpProperties::PureMayAllocate(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex string() const { return Base::input(0); } + OpIndex index() const { return Base::input(1); } + + StringFromCodePointAtOp(OpIndex string, OpIndex index) + : Base(string, index) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, string(), RegisterRepresentation::Tagged())); + DCHECK(ValidOpInputRep(graph, index(), + RegisterRepresentation::PointerSized())); + } + + auto options() const { return std::tuple{}; } +}; + +struct StringSubstringOp : FixedArityOperationT<3, StringSubstringOp> { + static constexpr OpProperties properties = OpProperties::PureMayAllocate(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex string() const { return Base::input(0); } + OpIndex start() const { return Base::input(1); } + OpIndex end() const { return Base::input(2); } + + StringSubstringOp(OpIndex string, OpIndex start, OpIndex end) + : Base(string, start, end) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, string(), RegisterRepresentation::Tagged())); + DCHECK(ValidOpInputRep(graph, start(), RegisterRepresentation::Word32())); + DCHECK(ValidOpInputRep(graph, end(), RegisterRepresentation::Word32())); + } + + auto options() const { return std::tuple{}; } +}; + +struct StringEqualOp : FixedArityOperationT<2, StringEqualOp> { + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex left() const { return Base::input(0); } + OpIndex right() const { return Base::input(1); } + + StringEqualOp(OpIndex left, OpIndex right) : Base(left, right) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, left(), RegisterRepresentation::Tagged())); + DCHECK(ValidOpInputRep(graph, right(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{}; } +}; + +struct StringComparisonOp : FixedArityOperationT<2, StringComparisonOp> { + enum class Kind : uint8_t { + kLessThan, + kLessThanOrEqual, + }; + Kind kind; + + static constexpr OpProperties properties = OpProperties::PureNoAllocation(); + base::Vector<const RegisterRepresentation> outputs_rep() const { + return RepVector<RegisterRepresentation::Tagged()>(); + } + + OpIndex left() const { return Base::input(0); } + OpIndex right() const { return Base::input(1); } + + StringComparisonOp(OpIndex left, OpIndex right, Kind kind) + : Base(left, right), kind(kind) {} + + void Validate(const Graph& graph) const { + DCHECK(ValidOpInputRep(graph, left(), RegisterRepresentation::Tagged())); + DCHECK(ValidOpInputRep(graph, right(), RegisterRepresentation::Tagged())); + } + + auto options() const { return std::tuple{kind}; } +}; +std::ostream& operator<<(std::ostream& os, StringComparisonOp::Kind kind); + #define OPERATION_PROPERTIES_CASE(Name) Name##Op::PropertiesIfStatic(), static constexpr base::Optional<OpProperties> kOperationPropertiesTable[kNumberOfOpcodes] = { @@ -1929,6 +3307,31 @@ inline size_t Operation::StorageSlotCount(Opcode opcode, size_t input_count) { return std::max<size_t>(2, (r - 1 + size + input_count) / r); } +template <class Op> +V8_INLINE bool CanBeUsedAsInput(const Op& op) { + if (std::is_same<Op, FrameStateOp>::value) { + // FrameStateOp is the only Operation that can be used as an input but has + // empty `outputs_rep`. + return true; + } + // For all other Operations, they can only be used as an input if they have at + // least one output. + return op.outputs_rep().size() > 0; +} + +inline base::Vector<const RegisterRepresentation> Operation::outputs_rep() + const { + switch (opcode) { +#define CASE(type) \ + case Opcode::k##type: { \ + const type##Op& op = Cast<type##Op>(); \ + return op.outputs_rep(); \ + } + TURBOSHAFT_OPERATION_LIST(CASE) +#undef CASE + } +} + } // namespace v8::internal::compiler::turboshaft #endif // V8_COMPILER_TURBOSHAFT_OPERATIONS_H_ |