Jit/hir/hir.h (2,884 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #pragma once #include "Python.h" #include "code.h" #include "funccredobject.h" #include "opcode.h" #include "Jit/bytecode.h" #include "Jit/deopt_patcher.h" #include "Jit/hir/type.h" #include "Jit/intrusive_list.h" #include "Jit/jit_rt.h" #include "Jit/jit_time_log.h" #include "Jit/ref.h" #include "Jit/stack.h" #include "Jit/util.h" #include <algorithm> #include <array> #include <functional> #include <memory> #include <optional> #include <string> #include <unordered_map> #include <unordered_set> #include <vector> namespace jit { namespace hir { /* * This file defines the high-level intermediate representation (HIR) used by * the JIT. * * The main goals for the IR are: * 1. Stay close to Python. The HIR is machine independent and tries to stay * close to Python in order to enable optimizations that are easier to * perform at a higher level of abstraction. For example, null checks for * variable accesses are represented explicitly so that they may be * optimized away when it can be statically determined that a variable is * defined. * 2. Be as explicit as possible. The CPython bytecode has a large amount of * implicit logic (e.g. refcounting, null checks). Making that logic * explicit in the IR makes it possible to optimize away. * 3. Be easy to lower into a lower-level IR for code generation. It should be * possible to lower the HIR into C or LLVM IR mechanically. * * Functions are converted into HIR by performing an abstract interpretation * over the function's bytecode. * * Functions are represented as a control flow graph of basic blocks. Each * basic block contains a list of instructions that ends in a * terminator. Instructions operate on an arbitrary set of variables and are * not in SSA form. */ class Instr; // The IR operates on an infinite number of virtual registers. class Register { public: explicit Register(int i) : id_(i) {} // An integer identifier for this register. This is unique per `Function`. int id() const { return id_; } // The type of this value. Only meaningful for SSA-form HIR. Type type() const { return type_; } void set_type(Type type) { type_ = type; } // Shorthand for checking the type of this Register. bool isA(Type type) const { return type_ <= type; } // The instruction that defined this value. Always set, but only meaningful // for SSA-form HIR. Instr* instr() const { return instr_; } void set_instr(Instr* instr) { instr_ = instr; } // A unique name for this value. This name has no connection to the original // Python program. const std::string& name() const; private: DISALLOW_COPY_AND_ASSIGN(Register); Type type_{TTop}; Instr* instr_{nullptr}; int id_{-1}; mutable std::string name_; }; std::ostream& operator<<(std::ostream& os, const Register& reg); // The refcount semantics of a value held in a Register. enum class RefKind { // A PyObject* that is either null or points to an immortal object, and // doesn't need to be reference counted, or a primitive. kUncounted, // A PyObject* with a borrowed reference. kBorrowed, // A PyObject* that owns a reference. kOwned, }; // The kind of value held in a Register. enum class ValueKind { // A PyObject*. kObject, // A signed 64-bit integer. kSigned, // An unsigned 64-bit integer. kUnsigned, // A C bool. kBool, // A C Double kDouble, }; std::ostream& operator<<(std::ostream& os, RefKind kind); // An entry in the CPython block stack struct ExecutionBlock { // The CPython opcode for the block int opcode; // Offset in the bytecode of the handler for this block int handler_off; // Level to pop the operand stack when the block is exited int stack_level; bool operator==(const ExecutionBlock& other) const { return (opcode == other.opcode) && (handler_off == other.handler_off) && (stack_level == other.stack_level); } bool operator!=(const ExecutionBlock& other) const { return !(*this == other); } bool isTryBlock() const { return opcode == SETUP_FINALLY; } bool isAsyncForHeaderBlock(const BytecodeInstructionBlock& instrs) const { Py_ssize_t idx = handler_off / sizeof(_Py_CODEUNIT); return opcode == SETUP_FINALLY && instrs.at(idx).opcode() == END_ASYNC_FOR; } }; using BlockStack = jit::Stack<ExecutionBlock>; using OperandStack = jit::Stack<Register*>; // The abstract state of the python frame struct FrameState { FrameState() = default; FrameState(const FrameState& other) { *this = other; } FrameState& operator=(const FrameState& other) { next_instr_offset = other.next_instr_offset; locals = other.locals; cells = other.cells; stack = other.stack; JIT_DCHECK( this != other.parent, "FrameStates should not be self-referential"); parent = other.parent; block_stack = other.block_stack; code = other.code; globals = other.globals; builtins = other.builtins; return *this; } FrameState( BorrowedRef<PyCodeObject> code, BorrowedRef<PyDictObject> globals, BorrowedRef<PyDictObject> builtins, FrameState* parent) : code(code), globals(globals), builtins(builtins), parent(parent) { JIT_DCHECK(this != parent, "FrameStates should not be self-referential"); } // Used for testing only. explicit FrameState(int bc_off) : next_instr_offset(bc_off) {} // If the function is inlined into another function, the depth at which it // is inlined (nested function calls may be inlined). Starts at 1. If the // function is not inlined, 0. int inlineDepth() const { int inline_depth = -1; const FrameState* frame = this; while (frame != nullptr) { frame = frame->parent; inline_depth++; } JIT_DCHECK( inline_depth >= 0, "expected positive inline depth but got %d", inline_depth); return inline_depth; } // The bytecode offset of the next instruction to be executed once control has // transferred to the interpreter. int next_instr_offset{0}; // Local variables std::vector<Register*> locals; // Cells for cellvars (used by closures of inner functions) and freevars (our // closure) std::vector<Register*> cells; OperandStack stack; BlockStack block_stack; BorrowedRef<PyCodeObject> code; BorrowedRef<PyDictObject> globals; BorrowedRef<PyDictObject> builtins; // Points to the FrameState, if any, into which this was inlined. Used to // construct the metadata needed to reify PyFrameObjects for inlined // functions during e.g. deopt. FrameState* parent{nullptr}; // The bytecode offset of the current instruction, or -1 if no instruction // has executed. This corresponds to the `f_lasti` field of PyFrameObject. int instr_offset() const { return std::max( next_instr_offset - static_cast<int>(sizeof(_Py_CODEUNIT)), -1); } bool visitUses(const std::function<bool(Register*&)>& func) { for (auto& reg : stack) { if (!func(reg)) { return false; } } for (auto& reg : locals) { if (reg != nullptr && !func(reg)) { return false; } } for (auto& reg : cells) { if (reg != nullptr && !func(reg)) { return false; } } if (parent != nullptr) { return parent->visitUses(func); } return true; } bool operator==(const FrameState& other) const { return (next_instr_offset == other.next_instr_offset) && (stack == other.stack) && (block_stack == other.block_stack) && (locals == other.locals) && (cells == other.cells) && (code == other.code); } bool operator!=(const FrameState& other) const { return !(*this == other); } bool hasTryBlock() const { for (auto& bse : block_stack) { if (bse.isTryBlock()) { return true; } } return false; } }; #define FOREACH_OPCODE(V) \ V(Assign) \ V(BatchDecref) \ V(BeginInlinedFunction) \ V(BinaryOp) \ V(Branch) \ V(BuildSlice) \ V(BuildString) \ V(CallCFunc) \ V(CallEx) \ V(CallExKw) \ V(CallMethod) \ V(CallStatic) \ V(CallStaticRetVoid) \ V(Cast) \ V(CheckSequenceBounds) \ V(CheckExc) \ V(CheckNeg) \ V(CheckVar) \ V(CheckFreevar) \ V(CheckField) \ V(ClearError) \ V(Compare) \ V(CompareBool) \ V(CondBranch) \ V(CondBranchIterNotDone) \ V(CondBranchCheckType) \ V(Decref) \ V(DeleteAttr) \ V(DeleteSubscr) \ V(Deopt) \ V(DeoptPatchpoint) \ V(DoubleBinaryOp) \ V(EndInlinedFunction) \ V(FillTypeAttrCache) \ V(FormatValue) \ V(GetIter) \ V(GetTuple) \ V(Guard) \ V(GuardIs) \ V(GuardType) \ V(HintType) \ V(ImportFrom) \ V(ImportName) \ V(InPlaceOp) \ V(Incref) \ V(InitFunction) \ V(InitListTuple) \ V(InitialYield) \ V(IntBinaryOp) \ V(PrimitiveBox) \ V(PrimitiveCompare) \ V(IntConvert) \ V(PrimitiveUnaryOp) \ V(PrimitiveUnbox) \ V(InvokeIterNext) \ V(InvokeMethod) \ V(IsInstance) \ V(IsSubtype) \ V(InvokeStaticFunction) \ V(IsErrStopAsyncIteration) \ V(IsNegativeAndErrOccurred) \ V(IsTruthy) \ V(ListAppend) \ V(ListExtend) \ V(LoadArrayItem) \ V(LoadFieldAddress) \ V(LoadArg) \ V(LoadAttr) \ V(LoadAttrSpecial) \ V(LoadAttrSuper) \ V(LoadCellItem) \ V(LoadConst) \ V(LoadCurrentFunc) \ V(LoadEvalBreaker) \ V(LoadField) \ V(LoadFunctionIndirect) \ V(LoadGlobalCached) \ V(LoadGlobal) \ V(LoadMethod) \ V(LoadMethodSuper) \ V(LoadTupleItem) \ V(LoadTypeAttrCacheItem) \ V(LoadVarObjectSize) \ V(LongCompare) \ V(LongBinaryOp) \ V(MakeCheckedDict) \ V(MakeCheckedList) \ V(MakeCell) \ V(MakeDict) \ V(MakeFunction) \ V(MakeListTuple) \ V(MakeSet) \ V(MakeTupleFromList) \ V(MergeDictUnpack) \ V(MergeSetUnpack) \ V(Phi) \ V(Raise) \ V(RaiseStatic) \ V(RaiseAwaitableError) \ V(RefineType) \ V(RepeatList) \ V(RepeatTuple) \ V(Return) \ V(RunPeriodicTasks) \ V(SetCellItem) \ V(SetCurrentAwaiter) \ V(SetDictItem) \ V(SetFunctionAttr) \ V(SetSetItem) \ V(Snapshot) \ V(StealCellItem) \ V(StoreArrayItem) \ V(StoreAttr) \ V(StoreField) \ V(StoreSubscr) \ V(TpAlloc) \ V(UnaryOp) \ V(UnpackExToTuple) \ V(UseType) \ V(VectorCall) \ V(VectorCallStatic) \ V(VectorCallKW) \ V(WaitHandleLoadCoroOrResult) \ V(WaitHandleLoadWaiter) \ V(WaitHandleRelease) \ V(XDecref) \ V(XIncref) \ V(YieldAndYieldFrom) \ V(YieldFrom) \ V(YieldValue) enum class Opcode { #define DECLARE_OP(opname) k##opname, FOREACH_OPCODE(DECLARE_OP) #undef DECLARE_OP }; #define COUNT_OP(opname) +1 const size_t kNumOpcodes = FOREACH_OPCODE(COUNT_OP); #undef COUNT_OP extern const char* const kOpcodeNames[kNumOpcodes]; class BasicBlock; // Every control flow instruction has one or more Edges. BasicBlocks that // contain or are targets of these instructions hold pointers to their Edges in // sets of in- and out-edges. class Edge { public: Edge() = default; ~Edge(); BasicBlock* from() const { return from_; } BasicBlock* to() const { return to_; } void set_from(BasicBlock* from); void set_to(BasicBlock* to); private: DISALLOW_COPY_AND_ASSIGN(Edge); BasicBlock* from_{nullptr}; BasicBlock* to_{nullptr}; }; // Used to represent that a type must be a subclass of one of the types // specified in the constraint. This is done to prevent accepting a register // that's typed as the union of the types in the Constraint enum class Constraint { kType, kMatchAllAsCInt, kMatchAllAsPrimitive, kTupleExactOrCPtr, kListOrChkList, kDictOrChkDict, kOptObjectOrCInt, kOptObjectOrCIntOrCBool, }; struct OperandType { OperandType(Type ty) : kind{Constraint::kType}, type{ty} {} OperandType(Constraint c) : kind{c}, type{TBottom} {} Constraint kind; Type type; }; std::ostream& operator<<(std::ostream& os, OperandType kind); template <typename... Args> inline std::vector<OperandType> makeTypeVec(Args&&... args) { return {args...}; } // Base class that all concrete HIR instructions must derive from. // // Instructions have variable sized instances; the operands are stored // **before** the instruction. The memory layout for an instruction looks // like: // // +--------------+ // | Operand 0 | // | Operand 1 | // | ... | // | Num operands | // | Vtable ptr | <--- Where `this` points // | .... | // +--------------+ // // Given that instructions have variable sized instances, they must be // allocated using the `create` methods that are defined on concrete // subclasses. Attempting to heap allocate instructions should result // in a compiler error, however, automatic allocation will still compile. // Don't do that. class Instr { // Instructions are part of a doubly linked list in the basic block they // belong to IntrusiveListNode block_node_; public: using List = IntrusiveList<Instr, &Instr::block_node_>; static constexpr bool has_output = false; virtual ~Instr(); static void operator delete(void* ptr) { auto instr = static_cast<Instr*>(ptr); free(instr->base()); } // This defines a predicate per opcode that can be used to determine // if an instance of an instruction is a particular subclass // (e.g. `instr->IsBranch()`) #define DEFINE_OP_PREDICATE(opname) \ bool Is##opname() const { \ return opcode() == Opcode::k##opname; \ } FOREACH_OPCODE(DEFINE_OP_PREDICATE) #undef DEFINE_OP_PREDICATE Opcode opcode() const { return opcode_; } const char* opname() const { auto opnum = static_cast<size_t>(opcode()); if (opnum < kNumOpcodes) { return kOpcodeNames[opnum]; } return "<invalid>"; } // Return the number of operands that the instruction takes std::size_t NumOperands() const { return *(reinterpret_cast<const std::size_t*>(this) - 1); } // Return the i-th operand Register* GetOperand(std::size_t i) const { return const_cast<Instr*>(this)->operandAt(i); } // Update the i-th operand void SetOperand(std::size_t i, Register* reg) { operandAt(i) = reg; } // Return the i-th operand type virtual OperandType GetOperandType(std::size_t /* i */) const = 0; // Visit all Registers used by the instruction, whether they're normal // operands or other data. Iteration can be stopped early by returning false // from the callback. virtual bool visitUses(const std::function<bool(Register*&)>& func) { auto num_uses = NumOperands(); for (std::size_t i = 0; i < num_uses; i++) { if (!func(operandAt(i))) { return false; } } return true; } // Visit all Registers used by the instruction, without allowing mutation of // the uses. bool visitUses(const std::function<bool(Register*)>& func) const { return const_cast<Instr*>(this)->visitUses( [&func](Register*& reg) { return func(reg); }); } // Return whether or not the instruction uses the supplied register as an // input bool Uses(Register* needle) const { bool found = false; visitUses([&](const Register* reg) { if (reg == needle) { found = true; return false; } return true; }); return found; } // Replace uses of orig with replacement. void ReplaceUsesOf(Register* orig, Register* replacement) { visitUses([&](Register*& reg) { if (reg == orig) { reg = replacement; } return true; }); } // If this instruction produces a value, return where it will be stored Register* GetOutput() const { return output_; } // Set where the output from this instruction will be stored void SetOutput(Register* dst) { if (output_ != nullptr) { output_->set_instr(nullptr); } if (dst != nullptr) { dst->set_instr(this); } output_ = dst; } // Basic blocks must be terminated with control flow ops bool IsTerminator() const; // If this is a control instruction, return the number of outgoing edges virtual std::size_t numEdges() const { return 0; } // If this is a control instruction, return the i-th edge virtual Edge* edge(std::size_t /* i */) { JIT_DCHECK(false, "not a control instruction"); return nullptr; } const Edge* edge(std::size_t i) const { return const_cast<Instr*>(this)->edge(i); } // Get or set the i-th successor. BasicBlock* successor(std::size_t i) const { return edge(i)->to(); } void set_successor(std::size_t i, BasicBlock* to) { edge(i)->set_to(to); } void InsertBefore(Instr& instr) { block_node_.InsertBefore(&instr.block_node_); link(instr.block()); } void InsertAfter(Instr& instr) { block_node_.InsertAfter(&instr.block_node_); link(instr.block()); } // Unlink this Instr from its block. void unlink(); BasicBlock* block() const { return block_; } void ReplaceWith(Instr& instr) { instr.InsertBefore(*this); instr.setBytecodeOffset(bytecodeOffset()); unlink(); } void ExpandInto(const std::vector<Instr*>& expansion) { Instr* last = this; for (Instr* instr : expansion) { instr->InsertAfter(*last); instr->setBytecodeOffset(bytecodeOffset()); last = instr; } unlink(); } // Returns the `FrameState` that dominates this instruction, if one exists // and there are no non-replayable instructions between it and the // instruction. const FrameState* getDominatingFrameState() const; // Returns whether or not this instruction can be safely re-executed. bool isReplayable() const; // Set/get the bytecode offset that this instruction is associated with void setBytecodeOffset(int off) { bytecode_offset_ = off; } int bytecodeOffset() const { return bytecode_offset_; } void copyBytecodeOffset(const Instr& instr) { setBytecodeOffset(instr.bytecodeOffset()); } int lineNumber() const { PyCodeObject* code = this->code(); if (code == nullptr) { return -1; } return PyCode_Addr2Line(code, bytecodeOffset()); } // This assumes that inlined functions have a dominating FrameState from // BeginInlinedFunction to use. If we start optimizing that out for inlined // functions that cannot deopt, we will have to do something different. virtual BorrowedRef<PyCodeObject> code() const; protected: DISALLOW_COPY_AND_ASSIGN(Instr); explicit Instr(Opcode opcode) : opcode_(opcode) {} void* operator new(std::size_t count, void* ptr) { return ::operator new(count, ptr); } // Allocate a block of memory suitable to house an `Instr`. This function is // intended to be used by the various `create` functions that are defined on // concrete `Instr` subclasses. static void* allocate(std::size_t fixed_size, std::size_t num_operands) { auto variable_size = num_operands * kPointerSize; char* ptr = static_cast<char*>( malloc(variable_size + fixed_size + sizeof(std::size_t))); ptr += variable_size; *reinterpret_cast<size_t*>(ptr) = num_operands; ptr += sizeof(std::size_t); return ptr; } void* base() { return reinterpret_cast<char*>(this) - (NumOperands() * kPointerSize) - sizeof(size_t); } Register** operands() { return static_cast<Register**>(base()); } Register*& operandAt(std::size_t i) { JIT_DCHECK( i < NumOperands(), "operand %d out of range (max is %d)", i, NumOperands() - 1); return operands()[i]; } friend class BasicBlock; // Link this Instr into its block. Meant to be called after inserting it into // the appropriate position in the block. void link(BasicBlock* block); // Set this Instr's block, updating any edges as appropriate. void set_block(BasicBlock* block); Opcode opcode_; Register* output_{nullptr}; BasicBlock* block_{nullptr}; int bytecode_offset_{-1}; }; using InstrPredicate = std::function<bool(const Instr&)>; struct RegState { RegState() = default; RegState(Register* reg, RefKind ref_kind, ValueKind value_kind) : reg{reg}, ref_kind{ref_kind}, value_kind{value_kind} {} Register* reg{nullptr}; RefKind ref_kind{RefKind::kUncounted}; ValueKind value_kind{ValueKind::kObject}; }; class DeoptBase : public Instr { public: explicit DeoptBase(Opcode op) : Instr(op) {} DeoptBase(Opcode op, const FrameState& frame) : Instr(op) { setFrameState(frame); } template <typename... Args> void emplaceLiveReg(Args&&... args) { live_regs_.emplace_back(std::forward<Args>(args)...); } const std::vector<RegState>& live_regs() const { return live_regs_; } std::vector<RegState>& live_regs() { return live_regs_; } // Set/get the metadata needed to reconstruct the state of the interpreter // after this instruction executes. void setFrameState(std::unique_ptr<FrameState> state) { frame_state_ = std::move(state); } void setFrameState(const FrameState& state) { frame_state_ = std::make_unique<FrameState>(state); } FrameState* frameState() const { return frame_state_.get(); } std::unique_ptr<FrameState> takeFrameState() { return std::move(frame_state_); } BorrowedRef<PyCodeObject> code() const override { FrameState* state = frameState(); if (state == nullptr) { // TODO(emacs): Why does GuardIs have a null FrameState after SSAify? return nullptr; } return state->code; } bool visitUses(const std::function<bool(Register*&)>& func) override { if (!Instr::visitUses(func)) { return false; } if (auto fs = frameState()) { if (!fs->visitUses(func)) { return false; } } for (auto& reg_state : live_regs_) { if (!func(reg_state.reg)) { return false; } } if (guilty_reg_ != nullptr) { if (!func(guilty_reg_)) { return false; } } return true; } int nonce() const { return nonce_; } void set_nonce(int nonce) { nonce_ = nonce; } // Get or set the human-readable description of why this instruction might // deopt. const std::string& descr() const { return descr_; } void setDescr(std::string r) { descr_ = std::move(r); } // Get or set the optional value that is responsible for this deopt // event. Its exact meaning depends on the opcode of this instruction. Register* guiltyReg() const { return guilty_reg_; } void setGuiltyReg(Register* reg) { guilty_reg_ = reg; } private: std::vector<RegState> live_regs_; std::unique_ptr<FrameState> frame_state_{nullptr}; // If set and this instruction deopts at runtime, this value is made // conveniently available in the deopt machinery. Register* guilty_reg_{nullptr}; int nonce_{-1}; // A human-readable description of why this instruction might deopt. std::string descr_; }; // This pile of template metaprogramming provides a convenient way to define // concrete subclasses of `Instr`. It allows users to // // - Specify whether or not the instruction has an output via the `HasOutput` // tag type. // - Specify the number of operands via the `Operands` tag type. Variadic // instructions are defined using `Operands<>`. // - Specify an optional different base class. If given, it must derive from // `Instr` and appear as the last template argument. It's constructor must // accept an `Opcode` as the first argument. template <class T, Opcode opcode, typename... Tys> class InstrT; // Base classes. template <class T, Opcode opc, class Base, typename... Tys> class InstrT<T, opc, Base, Tys...> : public Base { public: OperandType GetOperandType(std::size_t i) const override { JIT_DCHECK( i < this->NumOperands(), "operand %d out of range (max is %d)", i, this->NumOperands() - 1); return static_cast<const T*>(this)->GetOperandTypeImpl(i); } static_assert( std::is_base_of<Instr, Base>::value, "base type must derive from Instr"); static_assert( sizeof...(Tys) == 0, "base type must appear as last template parameter"); template <typename... Args> InstrT(Args&&... args) : Base(opc, std::forward<Args>(args)...) {} }; template <class T, Opcode opc> class InstrT<T, opc> : public InstrT<T, opc, Instr> { public: using InstrT<T, opc, Instr>::InstrT; }; // Support for specifying the number of operands expected by the instruction. // // Caveats: // // - Custom constructors must be public in order to be accessible by the // `create` methods defined below. // - Constructors are provided for common arities that expect operands to be // provided and handle setting them on the instruction. template <int n = -1> struct Operands; template <class T, Opcode opcode, int arity, typename... Tys> class InstrT<T, opcode, Operands<arity>, Tys...> : public InstrT<T, opcode, Tys...> { public: // Define a `create` method for non-variadic `T`. // // Usage: // auto instr = T::create(<args for T's constructor>); template <typename... Args, class T1 = T> static std::enable_if_t<arity >= 0, T1>* create(Args&&... args) { auto ptr = Instr::allocate(sizeof(T1), arity); return new (ptr) T1(std::forward<Args>(args)...); } // Define a `create` method for variadic `T`. // // Usage: // auto instr = T::create(<num_operands>, <args for T's constructor>); template <typename... Args, class T1 = T> static std::enable_if_t < arity<0, T1>* create(std::size_t num_ops, Args&&... args) { auto ptr = Instr::allocate(sizeof(T1), num_ops); return new (ptr) T1(std::forward<Args>(args)...); } // Forwarding constructor for variadic `T`. template < typename... Args, int a = arity, typename = std::enable_if_t<a <= 0>> InstrT(Args&&... args) : InstrT<T, opcode, Tys...>(std::forward<Args>(args)...) {} // Constructor for unary `T`. template < typename... Args, int a = arity, typename = std::enable_if_t<a == 1>> InstrT(Register* reg, Args&&... args) : InstrT<T, opcode, Tys...>(std::forward<Args>(args)...) { this->operandAt(0) = reg; } // TODO(mpage) - Get rid of this? template <int a = arity, typename T1 = std::enable_if_t<a == 1, Register>> T1* reg() const { return this->GetOperand(0); } // Constructor for binary `T`. template < typename... Args, int a = arity, typename = std::enable_if_t<a == 2>> InstrT(Register* lhs, Register* rhs, Args&&... args) : InstrT<T, opcode, Tys...>(std::forward<Args>(args)...) { this->operandAt(0) = lhs; this->operandAt(1) = rhs; } // Constructor for trinary `T`. template < typename... Args, int x = arity, typename = std::enable_if_t<x == 3>> InstrT(Register* a, Register* b, Register* c, Args&&... args) : InstrT<T, opcode, Tys...>(std::forward<Args>(args)...) { this->operandAt(0) = a; this->operandAt(1) = b; this->operandAt(2) = c; } // Constructor for 4 operand `T`. template < typename... Args, int x = arity, typename = std::enable_if_t<x == 4>> InstrT(Register* a, Register* b, Register* c, Register* d, Args&&... args) : InstrT<T, opcode, Tys...>(std::forward<Args>(args)...) { this->operandAt(0) = a; this->operandAt(1) = b; this->operandAt(2) = c; this->operandAt(3) = d; } }; // Support for setting the output struct HasOutput; template <class T, Opcode opcode, typename... Tys> class InstrT<T, opcode, HasOutput, Tys...> : public InstrT<T, opcode, Tys...> { public: static constexpr bool has_output = true; template <typename... Args> InstrT(Register* dst, Args&&... args) : InstrT<T, opcode, Tys...>(std::forward<Args>(args)...) { this->SetOutput(dst); } Register* dst() const { return this->GetOutput(); } }; // TODO(T105350013): Add a compile-time op_types size check #define INSTR_CLASS(name, types, ...) \ name## \ _OperandTypes{public : OperandType GetOperandTypeImpl(std::size_t i) const { \ static const std::vector<OperandType> op_types = makeTypeVec types; \ std::size_t num_ops = op_types.size(); \ if (i >= num_ops) { \ return op_types[num_ops - 1]; \ } else { \ return op_types[i]; \ } \ } \ } \ ; \ class name final : public InstrT<name, Opcode::k##name, __VA_ARGS__>, \ public name##_OperandTypes #define DEFINE_SIMPLE_INSTR(name, types, ...) \ class INSTR_CLASS(name, types, __VA_ARGS__) { \ private: \ friend InstrT; \ using InstrT::InstrT; \ }; enum class BinaryOpKind { kAdd = 0, kAnd, kFloorDivide, kLShift, kMatrixMultiply, kModulo, kMultiply, kOr, kPower, kRShift, kSubscript, kSubtract, kTrueDivide, kXor, kFloorDivideUnsigned, kModuloUnsigned, kRShiftUnsigned, kNumBinaryOps, kPowerUnsigned, }; const char* GetBinaryOpName(BinaryOpKind op); BinaryOpKind ParseBinaryOpName(const char* name); // Perform a binary operation (e.g. '+', '-') class INSTR_CLASS( BinaryOp, (TObject, TObject), HasOutput, Operands<2>, DeoptBase) { public: BinaryOp( Register* dst, BinaryOpKind op, Register* left, Register* right, const FrameState& frame) : InstrT(dst, left, right, frame), op_(op) {} BinaryOpKind op() const { return op_; } Register* left() const { return GetOperand(0); } Register* right() const { return GetOperand(1); } private: BinaryOpKind op_; }; enum class UnaryOpKind { kNot = 0, kPositive = 1, kNegate = 2, kInvert = 3, }; const char* GetUnaryOpName(UnaryOpKind op); UnaryOpKind ParseUnaryOpName(const char* name); // Perform a unary operator (-x, ~x, etc...) class INSTR_CLASS(UnaryOp, (TObject), HasOutput, Operands<1>, DeoptBase) { public: UnaryOp( Register* dst, UnaryOpKind op, Register* operand, const FrameState& frame) : InstrT(dst, operand, frame), op_(op) {} UnaryOpKind op() const { return op_; } Register* operand() const { return GetOperand(0); } private: UnaryOpKind op_; }; enum class InPlaceOpKind { kAdd = 0, kAnd = 1, kFloorDivide = 2, kLShift = 3, kMatrixMultiply = 4, kModulo = 5, kMultiply = 6, kOr = 7, kPower = 8, kRShift = 9, kSubtract = 10, kTrueDivide = 11, kXor = 12, }; const char* GetInPlaceOpName(InPlaceOpKind op); InPlaceOpKind ParseInPlaceOpName(const char* name); // Perform a in place operator x += 2 class INSTR_CLASS( InPlaceOp, (TObject, TObject), HasOutput, Operands<2>, DeoptBase) { public: InPlaceOp( Register* dst, InPlaceOpKind op, Register* left, Register* right, const FrameState& frame) : InstrT(dst, left, right, frame), op_(op) {} InPlaceOpKind op() const { return op_; } Register* left() const { return GetOperand(0); } Register* right() const { return GetOperand(1); } private: InPlaceOpKind op_; }; // Builds a slice object, with 2 or 3 operands from the stack class INSTR_CLASS(BuildSlice, (TObject), HasOutput, Operands<>, DeoptBase) { public: using InstrT::InstrT; Register* start() const { return GetOperand(0); } Register* stop() const { return GetOperand(1); } Register* step() const { return NumOperands() == 2 ? nullptr : GetOperand(2); } }; // Builds a new Function object, with the given qualified name and codeobj // Takes a qualname as operand 0 // Takes a codeobj as operand 1 DEFINE_SIMPLE_INSTR( MakeFunction, (TObject, TCode), HasOutput, Operands<2>, DeoptBase); // Calls PyEntry_Init(func) DEFINE_SIMPLE_INSTR(InitFunction, (TFunc), Operands<1>); // Takes a list as operand 0 // Takes an item as operand 1 DEFINE_SIMPLE_INSTR( ListAppend, (Constraint::kListOrChkList, TOptObject), HasOutput, Operands<2>, DeoptBase); // extend the list with the elements in iterable // Takes a list as operand 0 // Takes an iterable as operand 1 // Takes a func as operand 2 DEFINE_SIMPLE_INSTR( ListExtend, (Constraint::kListOrChkList, TObject, TOptObject), HasOutput, Operands<3>, DeoptBase); // Gets a tuple representation from a sequence. DEFINE_SIMPLE_INSTR(GetTuple, (TObject), HasOutput, Operands<1>, DeoptBase); // An unconditional branch class INSTR_CLASS(Branch, (), Operands<0>) { public: Branch(BasicBlock* target) : InstrT() { set_target(target); } BasicBlock* target() const { return edge_.to(); } void set_target(BasicBlock* target) { edge_.set_to(target); } std::size_t numEdges() const override { return 1; } Edge* edge(std::size_t i) override { JIT_CHECK(i == 0, "only have 1 edge"); return &edge_; } private: Edge edge_; }; enum class FunctionAttr { kClosure, kAnnotations, kKwDefaults, kDefaults, }; const char* functionFieldName(FunctionAttr field); class INSTR_CLASS(SetFunctionAttr, (TObject, TFunc), Operands<2>) { public: SetFunctionAttr(Register* value, Register* base, FunctionAttr field) : InstrT(value, base), field_(field) {} Register* value() const { return GetOperand(0); } Register* base() const { return GetOperand(1); } FunctionAttr field() const { return field_; }; uint64_t offset() const { switch (field_) { case FunctionAttr::kClosure: return offsetof(PyFunctionObject, func_closure); case FunctionAttr::kAnnotations: return offsetof(PyFunctionObject, func_annotations); case FunctionAttr::kKwDefaults: return offsetof(PyFunctionObject, func_kwdefaults); case FunctionAttr::kDefaults: return offsetof(PyFunctionObject, func_defaults); } JIT_CHECK(false, "invalid field %d", static_cast<int>(field_)); } private: FunctionAttr field_; }; class VectorCallBase : public DeoptBase { public: VectorCallBase(Opcode op, bool is_awaited) : DeoptBase(op), is_awaited_(is_awaited) {} VectorCallBase(Opcode op, bool is_awaited, const FrameState& frame) : DeoptBase(op, frame), is_awaited_(is_awaited) { setFrameState(frame); } // The function to call Register* func() const { return GetOperand(0); } std::size_t numArgs() const { return NumOperands() - 1; } Register* arg(std::size_t i) const { return GetOperand(i + 1); } Register* dst() const { return this->GetOutput(); } bool isAwaited() const { return is_awaited_; } private: const bool is_awaited_; }; DEFINE_SIMPLE_INSTR( VectorCall, (TOptObject), HasOutput, Operands<>, VectorCallBase); DEFINE_SIMPLE_INSTR( VectorCallStatic, (TOptObject), HasOutput, Operands<>, VectorCallBase); DEFINE_SIMPLE_INSTR( VectorCallKW, (TOptObject), HasOutput, Operands<>, VectorCallBase); class INSTR_CLASS( CallEx, (TObject, TObject), HasOutput, Operands<2>, DeoptBase) { public: CallEx(Register* dst, Register* func, Register* pargs, bool is_awaited) : InstrT(dst, func, pargs), is_awaited_(is_awaited) {} CallEx( Register* dst, Register* func, Register* pargs, bool is_awaited, const FrameState& frame) : InstrT(dst, func, pargs, frame), is_awaited_(is_awaited) {} // The function to call Register* func() const { return GetOperand(0); } Register* pargs() const { return GetOperand(1); } bool isAwaited() const { return is_awaited_; } private: const bool is_awaited_; }; class INSTR_CLASS( CallExKw, (TObject, TObject, TObject), HasOutput, Operands<3>, DeoptBase) { public: CallExKw( Register* dst, Register* func, Register* pargs, Register* kwargs, bool is_awaited) : InstrT(dst, func, pargs, kwargs), is_awaited_(is_awaited) {} CallExKw( Register* dst, Register* func, Register* pargs, Register* kwargs, bool is_awaited, const FrameState& frame) : InstrT(dst, func, pargs, kwargs, frame), is_awaited_(is_awaited) {} // The function to call Register* func() const { return GetOperand(0); } Register* pargs() const { return GetOperand(1); } Register* kwargs() const { return GetOperand(2); } bool isAwaited() const { return is_awaited_; } private: const bool is_awaited_; }; // Call to one of the C functions defined by CallCFunc_FUNCS. We have a static // set of functions so we can (one day) safely (de)serialize HIR fully. class INSTR_CLASS(CallCFunc, (TOptObject), HasOutput, Operands<>) { public: // List of allowed functions #define CallCFunc_FUNCS(X) \ X(_PyAsyncGenValueWrapperNew) \ X(_PyCoro_GetAwaitableIter) \ X(_PyGen_yf) \ X(_PyEval_GetAIter) \ X(_PyEval_GetANext) \ X(func_cred_new) enum class Func { #define ENUM_FUNC(name, ...) k##name, CallCFunc_FUNCS(ENUM_FUNC) #undef ENUM_FUNC }; CallCFunc(Register* dst, Func func, const std::vector<Register*>& args) : InstrT(dst), func_(func) { size_t i = 0; for (Register* arg : args) { SetOperand(i++, arg); } } uint64_t funcAddr() const { return reinterpret_cast<uint64_t>(kFuncPtrMap[static_cast<size_t>(func_)]); } const char* funcName() const { return kFuncNames[static_cast<size_t>(func_)]; } private: const Func func_; static const std::vector<void*> kFuncPtrMap; static const std::vector<const char*> kFuncNames; }; // Phi instruction class INSTR_CLASS(Phi, (TTop), HasOutput, Operands<>) { public: Phi(Register* dst) : InstrT(dst) {} static Phi* create( Register* dst, const std::unordered_map<BasicBlock*, Register*>& args) { void* ptr = Instr::allocate(sizeof(Phi), args.size()); auto phi = new (ptr) Phi(dst); phi->setArgs(args); return phi; } // A trivial phi merges its output with only one other value. Register* isTrivial() const { Register* out = GetOutput(); Register* val = nullptr; for (std::size_t i = 0; i < NumOperands(); i++) { Register* reg = GetOperand(i); if (reg != out && reg != val) { if (val != nullptr) { return nullptr; } val = reg; } } return val; } // Return the index of the given predecessor in basic_blocks. std::size_t blockIndex(const BasicBlock* block) const; const std::vector<BasicBlock*> basic_blocks() const { return basic_blocks_; } void setArgs(const std::unordered_map<BasicBlock*, Register*>& args); private: // List of incoming blocks, sorted by ascending block ID. std::vector<BasicBlock*> basic_blocks_; }; // The first operand is the receiver that was used for the corresponding // LoadMethod. The second operand is the callable to call. The remaining // operands are arguments to the call. class INSTR_CLASS(CallMethod, (TOptObject), HasOutput, Operands<>, DeoptBase) { public: CallMethod(Register* dst, bool is_awaited, const FrameState& frame) : InstrT(dst, frame), is_awaited_(is_awaited) {} // The function to call Register* func() const { return GetOperand(1); } // The register containing the receiver used to perform the method lookup Register* self() const { return GetOperand(0); } std::size_t NumArgs() const { return NumOperands() - 2; } Register* arg(std::size_t i) const { return GetOperand(i + 2); } bool isAwaited() const { return is_awaited_; } private: const bool is_awaited_; }; class INSTR_CLASS(InvokeMethod, (TObject), HasOutput, Operands<>, DeoptBase) { public: InvokeMethod( Register* dst, std::size_t slot, bool is_awaited, bool is_classmethod) : InstrT(dst), slot_(slot), is_awaited_(is_awaited), is_classmethod_(is_classmethod) {} // The function to call Register* func() const { return GetOperand(1); } // The register containing the receiver used to perform the method lookup Register* self() const { return GetOperand(0); } std::size_t NumArgs() const { return NumOperands() - 2; } Register* arg(std::size_t i) const { return GetOperand(i + 2); } int slot() const { return slot_; } bool isAwaited() const { return is_awaited_; } bool isClassmethod() const { return is_classmethod_; } private: const std::size_t slot_; const bool is_awaited_; const bool is_classmethod_; }; // A call to a function at a known address class INSTR_CLASS(CallStatic, (TTop), HasOutput, Operands<>) { public: CallStatic(Register* out, void* addr, Type ret_type) : InstrT(out), addr_(addr), ret_type_(ret_type) {} std::size_t NumArgs() const { return NumOperands(); } Register* arg(std::size_t i) const { return GetOperand(i); } void* addr() const { return addr_; } Type ret_type() const { return ret_type_; } private: void* addr_; Type ret_type_; }; // A call to a function at a known address class INSTR_CLASS(CallStaticRetVoid, (TTop), Operands<>) { public: CallStaticRetVoid(void* addr) : InstrT(), addr_(addr) {} std::size_t NumArgs() const { return NumOperands(); } Register* arg(std::size_t i) const { return GetOperand(i); } void* addr() const { return addr_; } private: void* addr_; }; // Invokes a function with a static entry point, where we can // directly provide the arguments using the x64 calling convention. class INSTR_CLASS( InvokeStaticFunction, (TTop), HasOutput, Operands<>, DeoptBase) { public: // Would be better not to have this constructor, we shouldn't use it, but // currently newInstr in the parser requires it, T85605140 InvokeStaticFunction( Register* dst, PyFunctionObject* func, Type ret_type, const FrameState& frame) : InstrT(dst, frame), func_(func), ret_type_(ret_type) {} InvokeStaticFunction(Register* dst, PyFunctionObject* func, Type ret_type) : InstrT(dst), func_(func), ret_type_(ret_type) {} std::size_t NumArgs() const { return NumOperands(); } Register* arg(std::size_t i) const { return GetOperand(i); } PyFunctionObject* func() const { return func_; } Type ret_type() const { return ret_type_; } private: PyFunctionObject* func_; Type ret_type_; }; class CheckBase : public DeoptBase { protected: // Used only for tests. CheckBase(Opcode op) : DeoptBase(op) { auto new_frame = std::make_unique<FrameState>(); setFrameState(std::move(new_frame)); } CheckBase(Opcode op, const FrameState& frame) : DeoptBase(op, frame) {} public: Register* reg() const { return GetOperand(0); } }; // Check if an exception has occurred (implied by var being NULL). // If so, transfer control to the exception handler for the block. DEFINE_SIMPLE_INSTR( CheckExc, (Constraint::kOptObjectOrCInt), HasOutput, Operands<1>, CheckBase); // Check if an exception has occurred as indicated by a negative // return code. DEFINE_SIMPLE_INSTR(CheckNeg, (TCInt), HasOutput, Operands<1>, CheckBase); class CheckBaseWithName : public CheckBase { protected: // Used only for tests. CheckBaseWithName(Opcode op, BorrowedRef<> name) : CheckBase(op), name_(name) {} CheckBaseWithName(Opcode op, BorrowedRef<> name, const FrameState& frame) : CheckBase(op, frame), name_(name) {} public: BorrowedRef<> name() const { return name_; } private: BorrowedRef<> name_; }; // If the operand is Nullptr, raise an UnboundLocalError referencing the // given local variable name. DEFINE_SIMPLE_INSTR( CheckVar, (TOptObject), HasOutput, Operands<1>, CheckBaseWithName); // If the operand is Nullptr, raise a NameError referencing the given free // variable name. DEFINE_SIMPLE_INSTR( CheckFreevar, (TOptObject), HasOutput, Operands<1>, CheckBaseWithName); // If the operand is Nullptr, raise an AttributeError referencing the given // attribute/field name. DEFINE_SIMPLE_INSTR( CheckField, (TOptObject), HasOutput, Operands<1>, CheckBaseWithName); DEFINE_SIMPLE_INSTR( IsNegativeAndErrOccurred, (TCInt), HasOutput, Operands<1>, DeoptBase); class INSTR_CLASS(LoadField, (TOptObject), HasOutput, Operands<1>) { public: LoadField( Register* dst, Register* receiver, const std::string& name, std::size_t offset, Type type, bool borrowed = true) : InstrT(dst, receiver), name_(name), offset_(offset), type_(type), borrowed_(borrowed) {} // The object we're loading the attribute from Register* receiver() const { return reg(); } std::string name() const { return name_; } // Offset where the field is stored std::size_t offset() const { return offset_; } Type type() const { return type_; } bool borrowed() const { return borrowed_; } private: std::string name_; std::size_t offset_; Type type_; bool borrowed_; }; class INSTR_CLASS(StoreField, (TObject, TTop, TOptObject), Operands<3>) { public: StoreField( Register* receiver, const std::string& name, std::size_t offset, Register* value, Type type, Register* previous) : InstrT(receiver, value, previous), name_(name), offset_(offset), type_(type) {} // The object we're loading the attribute from Register* receiver() const { return GetOperand(0); } void set_receiver(Register* receiver) { SetOperand(0, receiver); } // The value being stored Register* value() const { return GetOperand(1); } void set_value(Register* value) { SetOperand(1, value); } Register* previous() const { return GetOperand(2); } std::string name() const { return name_; } // Offset where the field is stored std::size_t offset() const { return offset_; } Type type() const { return type_; } private: std::string name_; std::size_t offset_; Type type_; }; class INSTR_CLASS(Cast, (TObject), HasOutput, Operands<1>, DeoptBase) { public: Cast( Register* dst, Register* receiver, PyTypeObject* pytype, bool optional, bool exact, bool iserror, const FrameState& frame) : InstrT(dst, receiver, frame), pytype_(pytype), optional_(optional), exact_(exact), iserror_(iserror) {} Register* value() const { return reg(); } PyTypeObject* pytype() const { return pytype_; } bool optional() const { return optional_; } bool exact() const { return exact_; } bool iserror() const { return iserror_; } private: PyTypeObject* pytype_; bool optional_, exact_, iserror_; }; class INSTR_CLASS(TpAlloc, (), HasOutput, Operands<0>, DeoptBase) { public: TpAlloc(Register* dst, PyTypeObject* pytype, const FrameState& frame) : InstrT(dst, frame), pytype_(pytype) {} PyTypeObject* pytype() const { return pytype_; } private: PyTypeObject* pytype_; }; // Perform a binary operation (e.g. '+', '-') on primitive int operands class INSTR_CLASS( IntBinaryOp, (Constraint::kMatchAllAsCInt, Constraint::kMatchAllAsCInt), HasOutput, Operands<2>) { public: IntBinaryOp(Register* dst, BinaryOpKind op, Register* left, Register* right) : InstrT(dst, left, right), op_(op) {} BinaryOpKind op() const { return op_; } Register* left() const { return GetOperand(0); } Register* right() const { return GetOperand(1); } private: BinaryOpKind op_; }; // Perform a binary operation (e.g. '+', '-') on primitive double operands class INSTR_CLASS( DoubleBinaryOp, (TCDouble, TCDouble), HasOutput, Operands<2>) { public: DoubleBinaryOp( Register* dst, BinaryOpKind op, Register* left, Register* right) : InstrT(dst, left, right), op_(op) {} BinaryOpKind op() const { return op_; } Register* left() const { return GetOperand(0); } Register* right() const { return GetOperand(1); } private: BinaryOpKind op_; }; class InlineBase { public: virtual int inlineDepth() const = 0; }; // Owns a FrameState that all inlined FrameState-owning instructions will point // to via FrameState's `parent' pointer. class INSTR_CLASS(BeginInlinedFunction, (), Operands<0>), public InlineBase { public: BeginInlinedFunction( BorrowedRef<PyCodeObject> code, BorrowedRef<PyObject> globals, std::unique_ptr<FrameState> caller_state, const std::string& fullname) : InstrT(), code_(code), globals_(globals), fullname_(fullname) { caller_state_ = std::move(caller_state); } const FrameState* callerFrameState() const { return caller_state_.get(); } BorrowedRef<PyCodeObject> code() const { return code_.get(); } std::string fullname() const { return fullname_; } BorrowedRef<PyObject> globals() const { return globals_.get(); } int inlineDepth() const { return caller_state_->inlineDepth() + 1; } private: // BeginInlinedFunction must own the FrameState that is used for building the // linked list of FrameStates as well as its parent FrameState. The parent is // originally owned by the Call instruction, but that gets destroyed. // Used for printing. BorrowedRef<PyCodeObject> code_; BorrowedRef<PyObject> globals_; std::unique_ptr<FrameState> caller_state_{nullptr}; std::string fullname_; }; class INSTR_CLASS(EndInlinedFunction, (), Operands<0>), public InlineBase { public: EndInlinedFunction(int inline_depth) : InstrT(), inline_depth(inline_depth) {} int inlineDepth() const { return inline_depth; } private: int inline_depth{-1}; }; enum class PrimitiveUnaryOpKind { kNegateInt = 0, kInvertInt = 1, kNotInt = 2, }; const char* GetPrimitiveUnaryOpName(PrimitiveUnaryOpKind op); PrimitiveUnaryOpKind ParsePrimitiveUnaryOpName(const char* name); // Perform a unary operation (e.g. '~', '-') on primitive operands class INSTR_CLASS(PrimitiveUnaryOp, (TPrimitive), HasOutput, Operands<1>) { public: PrimitiveUnaryOp(Register* dst, PrimitiveUnaryOpKind op, Register* value) : InstrT(dst, value), op_(op) {} PrimitiveUnaryOpKind op() const { return op_; } Register* value() const { return GetOperand(0); } private: PrimitiveUnaryOpKind op_; }; enum class CompareOp { kLessThan = 0, kLessThanEqual, kEqual, kNotEqual, kGreaterThan, kGreaterThanEqual, kIn, kNotIn, kIs, kIsNot, kExcMatch, kGreaterThanUnsigned, kGreaterThanEqualUnsigned, kLessThanUnsigned, kLessThanEqualUnsigned, kNumCompareOps }; const char* GetCompareOpName(CompareOp op); std::optional<CompareOp> ParseCompareOpName(const char* name); // Perform the comparison indicated by op class INSTR_CLASS( Compare, (TOptObject, TOptObject), HasOutput, Operands<2>, DeoptBase) { public: Compare( Register* dst, CompareOp op, Register* left, Register* right, const FrameState& frame) : InstrT(dst, left, right, frame), op_(op) {} CompareOp op() const { return op_; } Register* left() const { return GetOperand(0); } Register* right() const { return GetOperand(1); } private: CompareOp op_; }; // Perform the comparison indicated by op class INSTR_CLASS( LongCompare, (TLongExact, TLongExact), HasOutput, Operands<2>) { public: LongCompare(Register* dst, CompareOp op, Register* left, Register* right) : InstrT(dst, left, right), op_(op) {} CompareOp op() const { return op_; } Register* left() const { return GetOperand(0); } Register* right() const { return GetOperand(1); } private: CompareOp op_; }; // Perform the operation indicated by op class INSTR_CLASS( LongBinaryOp, (TLongExact, TLongExact), HasOutput, Operands<2>, DeoptBase) { public: LongBinaryOp( Register* dst, BinaryOpKind op, Register* left, Register* right, const FrameState& frame) : InstrT(dst, left, right, frame), op_(op) {} BinaryOpKind op() const { return op_; } Register* left() const { return GetOperand(0); } Register* right() const { return GetOperand(1); } private: BinaryOpKind op_; }; // Like Compare but has an Int32 output so it can be used to replace // a Compare + IsTruthy. class INSTR_CLASS( CompareBool, (TObject, TObject), HasOutput, Operands<2>, DeoptBase) { public: CompareBool( Register* dst, CompareOp op, Register* left, Register* right, const FrameState& frame) : InstrT(dst, left, right, frame), op_(op) {} CompareOp op() const { return op_; } Register* left() const { return GetOperand(0); } Register* right() const { return GetOperand(1); } private: CompareOp op_; }; class INSTR_CLASS(IntConvert, (TPrimitive), HasOutput, Operands<1>) { public: IntConvert(Register* dst, Register* src, Type type) : InstrT(dst, src), type_(type) {} Register* src() const { return GetOperand(0); } Type type() const { return type_; } private: Type type_; }; // Perform the comparison indicated by op enum class PrimitiveCompareOp { kLessThan = 0, kLessThanEqual, kEqual, kNotEqual, kGreaterThan, kGreaterThanEqual, kGreaterThanUnsigned, kGreaterThanEqualUnsigned, kLessThanUnsigned, kLessThanEqualUnsigned, kNumPrimitiveCompareOps }; const char* GetPrimitiveCompareOpName(PrimitiveCompareOp op); PrimitiveCompareOp ParsePrimitiveCompareOpName(const char* name); class INSTR_CLASS(PrimitiveCompare, (), HasOutput, Operands<2>) { public: PrimitiveCompare( Register* dst, PrimitiveCompareOp op, Register* left, Register* right) : InstrT(dst, left, right), op_(op) {} PrimitiveCompareOp op() const { return op_; } Register* left() const { return GetOperand(0); } Register* right() const { return GetOperand(1); } OperandType GetOperandTypeImpl(std::size_t /* i */) const { // `is` gets treated as a PrimtiveCompare and can hold anything if (op_ == PrimitiveCompareOp::kEqual || op_ == PrimitiveCompareOp::kNotEqual) { return TTop; } else { return {Constraint::kMatchAllAsPrimitive}; } } private: PrimitiveCompareOp op_; }; class INSTR_CLASS(PrimitiveBox, (TPrimitive), HasOutput, Operands<1>) { public: PrimitiveBox(Register* dst, Register* value, Type type) : InstrT(dst, value), type_(type) {} Register* value() const { return GetOperand(0); } Type type() const { return type_; } private: Type type_; }; class INSTR_CLASS(PrimitiveUnbox, (), HasOutput, Operands<1>) { public: PrimitiveUnbox(Register* dst, Register* value, Type type) : InstrT(dst, value), type_(type) {} Register* value() const { return GetOperand(0); } Type type() const { return type_; } OperandType GetOperandTypeImpl(std::size_t /* i */) const { return type_.asBoxed(); } private: Type type_; }; class CondBranchBase : public Instr { public: CondBranchBase(Opcode opcode, BasicBlock* true_bb, BasicBlock* false_bb) : Instr(opcode) { set_true_bb(true_bb); set_false_bb(false_bb); } BasicBlock* true_bb() const { return true_edge_.to(); } void set_true_bb(BasicBlock* block) { true_edge_.set_to(block); } BasicBlock* false_bb() const { return false_edge_.to(); } void set_false_bb(BasicBlock* block) { false_edge_.set_to(block); } std::size_t numEdges() const override { return 2; } Edge* edge(std::size_t i) override { JIT_DCHECK(i < 2, "only have 2 edges"); return i == 0 ? &true_edge_ : &false_edge_; } private: Edge true_edge_; Edge false_edge_; }; // Transfer control to `true_bb` if `reg` is nonzero, otherwise `false_bb`. DEFINE_SIMPLE_INSTR( CondBranch, (Constraint::kOptObjectOrCIntOrCBool), Operands<1>, CondBranchBase); // Branch to `true_bb` if the operand is not the sentinel value that indicates // an iterator is exhausted, or `false_bb` otherwise. DEFINE_SIMPLE_INSTR( CondBranchIterNotDone, (TObject), Operands<1>, CondBranchBase); // Branch to `true_bb` if the operand matches the supplied type specification, // or `false_bb` otherwise. class INSTR_CLASS( CondBranchCheckType, (TOptObject), Operands<1>, CondBranchBase) { public: CondBranchCheckType( Register* target, const Type& type, BasicBlock* true_bb, BasicBlock* false_bb) : InstrT(target, true_bb, false_bb), type_(type) {} const Type& type() const { return type_; } private: const Type type_; }; // Decrement the reference count of `reg` DEFINE_SIMPLE_INSTR(Decref, (TObject), Operands<1>); // Decrement the reference count of `reg`, if `reg` is not NULL DEFINE_SIMPLE_INSTR(XDecref, (TOptObject), Operands<1>); // Increment the reference count of `reg` DEFINE_SIMPLE_INSTR(Incref, (TObject), Operands<1>); // Increment the refrence count of `reg`, if `reg` is not NULL DEFINE_SIMPLE_INSTR(XIncref, (TOptObject), Operands<1>); // batch decrement references DEFINE_SIMPLE_INSTR(BatchDecref, (TObject), Operands<>); class DeoptBaseWithNameIdx : public DeoptBase { public: DeoptBaseWithNameIdx(Opcode op, int name_idx, const FrameState& frame) : DeoptBase(op, frame), name_idx_(name_idx) {} // Index of the attribute name in the code object's co_names tuple int name_idx() const { return name_idx_; } private: int name_idx_; }; // Load an attribute from an object DEFINE_SIMPLE_INSTR( LoadAttr, (TObject), HasOutput, Operands<1>, DeoptBaseWithNameIdx); // Set the attribute of an object // // Places NULL in dst if an error occurred or a non-NULL value otherwise DEFINE_SIMPLE_INSTR( StoreAttr, (TObject, TObject), HasOutput, Operands<2>, DeoptBaseWithNameIdx); // Delete an attribute from an object DEFINE_SIMPLE_INSTR(DeleteAttr, (TObject), Operands<1>, DeoptBaseWithNameIdx); // Load an attribute from an object, skipping the instance dictionary but still // calling descriptors as appropriate (to create bound methods, for example). class INSTR_CLASS( LoadAttrSpecial, (TObject), HasOutput, Operands<1>, DeoptBase) { public: LoadAttrSpecial( Register* dst, Register* receiver, _Py_Identifier* id, const FrameState& frame) : InstrT(dst, receiver, frame), id_(id) {} _Py_Identifier* id() const { return id_; } private: _Py_Identifier* id_; }; // Format and raise an error after failing to get an iterator for 'async with'. class INSTR_CLASS(RaiseAwaitableError, (TType), Operands<1>, DeoptBase) { public: RaiseAwaitableError( Register* type, _Py_CODEUNIT with_opcode, const FrameState& frame) : InstrT(type, frame), with_opcode_(with_opcode) {} _Py_CODEUNIT with_opcode() const { return with_opcode_; } private: _Py_CODEUNIT with_opcode_; }; // Load a guard (index 0) or value (index 1) from a cache specialized for // loading attributes from type receivers class INSTR_CLASS(LoadTypeAttrCacheItem, (), HasOutput, Operands<0>) { public: LoadTypeAttrCacheItem(Register* dst, int cache_id, int item_idx) : InstrT(dst), cache_id_(cache_id), item_idx_(item_idx) { JIT_CHECK(item_idx < 2, "only two elements in the cache"); } int cache_id() const { return cache_id_; } int item_idx() const { return item_idx_; } private: int cache_id_; int item_idx_; }; // Perform a full attribute lookup. Fill the cache if the receiver is a type // object. class INSTR_CLASS( FillTypeAttrCache, (TType), HasOutput, Operands<1>, DeoptBase) { public: FillTypeAttrCache( Register* dst, Register* receiver, int name_idx, int cache_id, const FrameState& frame) : InstrT(dst, receiver, frame), name_idx_(name_idx), cache_id_(cache_id) {} FillTypeAttrCache( Register* dst, Register* receiver, int name_idx, int cache_id, std::unique_ptr<FrameState> frame) : InstrT(dst, receiver), name_idx_(name_idx), cache_id_(cache_id) { setFrameState(std::move(frame)); } // The object we're loading the attribute from Register* receiver() const { return reg(); } // Index of the attribute name in the code object's co_names tuple int name_idx() const { return name_idx_; } int cache_id() const { return cache_id_; } private: int name_idx_; int cache_id_; }; // Like LoadAttr, but when we know that we're loading an attribute that will be // used for a method call. class INSTR_CLASS(LoadMethod, (TObject), HasOutput, Operands<1>, DeoptBase) { public: LoadMethod( Register* dst, Register* receiver, int name_idx, const FrameState& frame) : InstrT(dst, receiver, frame), name_idx_(name_idx) {} // The object we're loading the attribute from Register* receiver() const { return GetOperand(0); } // Index of the attribute name in the code object's co_names tuple int name_idx() const { return name_idx_; } private: int name_idx_; }; class LoadSuperBase : public DeoptBase { protected: LoadSuperBase(Opcode op, int name_idx, bool no_args_in_super_call) : DeoptBase(op), name_idx_(name_idx), no_args_in_super_call_(no_args_in_super_call) {} LoadSuperBase( Opcode op, int name_idx, bool no_args_in_super_call, const FrameState& frame) : DeoptBase(op, frame), name_idx_(name_idx), no_args_in_super_call_(no_args_in_super_call) {} public: // Global 'super' value Register* global_super() const { return GetOperand(0); } // See comment for 'receiver' Register* type() const { return GetOperand(1); } // The object that determines mro to be searched. // Search will be started from the class right after the 'type' Register* receiver() const { return GetOperand(2); } // Index of the attribute name in the code object's co_names tuple int name_idx() const { return name_idx_; } bool no_args_in_super_call() const { return no_args_in_super_call_; } private: int name_idx_; bool no_args_in_super_call_; }; DEFINE_SIMPLE_INSTR( LoadMethodSuper, (TObject, TObject, TObject), HasOutput, Operands<3>, LoadSuperBase); DEFINE_SIMPLE_INSTR( LoadAttrSuper, (TObject, TObject, TObject), HasOutput, Operands<3>, LoadSuperBase); // Load the current PyFunctionObject* into a Register. Must not appear after // any non-LoadArg instructions. DEFINE_SIMPLE_INSTR(LoadCurrentFunc, (), HasOutput, Operands<0>); // Load the value from the cell in operand DEFINE_SIMPLE_INSTR(LoadCellItem, (TOptObject), HasOutput, Operands<1>); // Load the value from the cell in src, stealing the reference to it. This is // used only as the precursor to SetCellItem, so that we can decref the old item // in the cell that the cell is about to lose its reference to. DEFINE_SIMPLE_INSTR(StealCellItem, (TObject), HasOutput, Operands<1>); // Store a value to the cell in dst. The `old` arg is unused but exists in order // to ensure that the previous cell contents are not decref-ed until after the // new cell contents are in place. // Takes a cell as operand 0 // Takes a src as operand 1 // Takes in anything as operand 2 DEFINE_SIMPLE_INSTR( SetCellItem, (TObject, TOptObject, TOptObject), Operands<3>); // Load a constant value (given as a Type) into a register. class INSTR_CLASS(LoadConst, (), HasOutput, Operands<0>) { public: LoadConst(Register* dst, Type type) : InstrT(dst), type_(type) { JIT_DCHECK( type.isSingleValue(), "Given Type must represent a single value"); } Type type() const { return type_; } private: Type type_; }; class INSTR_CLASS(LoadFunctionIndirect, (), HasOutput, Operands<0>, DeoptBase) { public: LoadFunctionIndirect( PyObject** funcptr, PyObject* descr, Register* dst, const FrameState& frame) : InstrT(dst, frame), funcptr_(funcptr), descr_(descr) {} PyObject** funcptr() const { return funcptr_; } PyObject* descr() const { return descr_; } private: PyObject** funcptr_; PyObject* descr_; }; // Load a global. // // The name is specified by the name_idx in the co_names tuple of the code // object. class INSTR_CLASS(LoadGlobalCached, (), HasOutput, Operands<0>) { public: LoadGlobalCached( Register* dst, BorrowedRef<PyCodeObject> code, BorrowedRef<PyDictObject> globals, int name_idx) : InstrT(dst), code_(code), globals_(globals), name_idx_(name_idx) {} virtual BorrowedRef<PyCodeObject> code() const { return code_; } BorrowedRef<PyDictObject> globals() const { return globals_; } int name_idx() const { return name_idx_; } private: BorrowedRef<PyCodeObject> code_; BorrowedRef<PyDictObject> globals_; int name_idx_; }; class INSTR_CLASS(LoadGlobal, (), HasOutput, Operands<0>, DeoptBase) { public: LoadGlobal(Register* dst, int name_idx, const FrameState& frame) : InstrT(dst, frame), name_idx_(name_idx) {} int name_idx() const { return name_idx_; } private: int name_idx_; }; // Return a copy of the input with a refined Type. The output Type is the // intersection of the given Type and the input's Type. class INSTR_CLASS(RefineType, (TTop), HasOutput, Operands<1>) { public: RefineType(Register* dst, Type type, Register* src) : InstrT(dst, src), type_(type) {} Type type() const { return type_; } private: Type type_; }; class RepeatBase : public DeoptBase { protected: RepeatBase(Opcode op) : DeoptBase(op) { auto new_frame = std::make_unique<FrameState>(); setFrameState(std::move(new_frame)); } RepeatBase(Opcode op, const FrameState& frame) : DeoptBase(op, frame) {} public: Register* seq() const { return GetOperand(0); } Register* num() const { return GetOperand(1); } }; // Repeat a list; e.g. [1, 2] * 2 == [1, 2, 1, 2] // Expects `num` to be a primitive integer DEFINE_SIMPLE_INSTR( RepeatList, (TList, TCInt), HasOutput, Operands<2>, RepeatBase); // Repeat a tuple; e.g. (1, 2) * 2 == (1, 2, 1, 2) // Expects `num` to be a primitive integer DEFINE_SIMPLE_INSTR( RepeatTuple, (TTuple, TCInt), HasOutput, Operands<2>, RepeatBase); // Return from the function class INSTR_CLASS(Return, (), Operands<1>) { public: Return(Register* val) : InstrT(val), type_(TObject) {} Return(Register* val, Type type) : InstrT(val), type_(type) {} Type type() const { return type_; } OperandType GetOperandTypeImpl(std::size_t /* i */) const { return type_; } private: Type type_; }; // Should be generated whenever an optimization removes the usage of a register // but still relies on that register being of a certain type // (see simplifyIsTruthy) // // Ensures that we don't accidentally remove a type check (such as in GuardType) // despite a register not having any explicit users class INSTR_CLASS(UseType, (), Operands<1>) { public: UseType(Register* val, Type type) : InstrT(val), type_(type) {} Type type() const { return type_; } OperandType GetOperandTypeImpl(std::size_t /* i */) const { return type_; } private: Type type_; }; // Assign one register to another DEFINE_SIMPLE_INSTR(Assign, (TTop), HasOutput, Operands<1>); // Load the value of an argument to the current function. Reads from implicit // state set up by the function prologue and must not appear after any // non-LoadArg instruction. class INSTR_CLASS(LoadArg, (), HasOutput, Operands<0>) { public: LoadArg(Register* dst, uint arg_idx) : InstrT(dst), arg_idx_(arg_idx), type_(TObject) {} LoadArg(Register* dst, uint arg_idx, Type type) : InstrT(dst), arg_idx_(arg_idx), type_(type) {} uint arg_idx() const { return arg_idx_; } Type type() const { return type_; } private: uint arg_idx_; Type type_; }; // Allocate a tuple or list object with number of values class INSTR_CLASS(MakeListTuple, (), HasOutput, Operands<0>, DeoptBase) { public: MakeListTuple( bool is_tuple, Register* dst, size_t nvalues, const FrameState& frame) : InstrT(dst, frame), tuple_(is_tuple), nvalues_(nvalues) {} size_t nvalues() const { return nvalues_; } bool is_tuple() const { return tuple_; } private: bool tuple_; size_t nvalues_; }; // Initialize a tuple or a list with the arguments class INSTR_CLASS(InitListTuple, (), Operands<>) { public: InitListTuple(bool is_tuple) : InstrT(), tuple_(is_tuple) {} bool is_tuple() const { return tuple_; } size_t num_args() const { return NumOperands() - 1; } OperandType GetOperandTypeImpl(std::size_t i) const { if (i == 0) { if (tuple_) { return TTuple; } return Constraint::kListOrChkList; } return TOptObject; } private: bool tuple_; }; // Initialize a tuple from a list DEFINE_SIMPLE_INSTR( MakeTupleFromList, (TList), HasOutput, Operands<1>, DeoptBase); // Load an element from a tuple at a known index, with no bounds checking. class INSTR_CLASS(LoadTupleItem, (TTuple), HasOutput, Operands<1>) { public: LoadTupleItem(Register* dst, Register* tuple, size_t idx) : InstrT(dst, tuple), idx_(idx) {} Register* tuple() const { return GetOperand(0); } size_t idx() const { return idx_; } private: size_t idx_; }; // Load an element from an array at a known index and offset, with no bounds // checking. Equivalent to ((type*)(((char*)ob_item)+offset))[idx] class INSTR_CLASS( LoadArrayItem, (Constraint::kTupleExactOrCPtr, TCInt, TOptObject), HasOutput, Operands<3>) { public: LoadArrayItem( Register* dst, Register* ob_item, Register* idx, // This operand is never actually used, but it's an input for this because // we need to keep a reference to the container alive. The refcount // insertion pass handles this for us if the container is an input for // this instruction. Register* array_unused, ssize_t offset, Type type) : InstrT(dst, ob_item, idx, array_unused), offset_(offset), type_(type) {} Register* ob_item() const { return GetOperand(0); } Register* idx() const { return GetOperand(1); } ssize_t offset() const { return offset_; } Type type() const { return type_; } private: ssize_t offset_; Type type_; }; class INSTR_CLASS( LoadFieldAddress, (TOptObject, TCInt64), HasOutput, Operands<2>) { public: LoadFieldAddress(Register* dst, Register* object, Register* offset) : InstrT(dst, object, offset) {} Register* object() const { return GetOperand(0); } Register* offset() const { return GetOperand(1); } }; // Store an element to an array at a known index, with no bounds checking. class INSTR_CLASS(StoreArrayItem, (TCPtr, TCInt, TTop, TObject), Operands<4>) { public: StoreArrayItem( Register* ob_item, Register* idx, Register* value, // This operand is never actually used, but it's an input for this because // we need to keep a reference to the container alive. The refcount // insertion pass handles this for us if the container is an input for // this instruction. Register* container_unused, Type type) : InstrT(ob_item, idx, value, container_unused), type_(type) {} Register* ob_item() const { return GetOperand(0); } Register* idx() const { return GetOperand(1); } Register* value() const { return GetOperand(2); } Type type() const { return type_; } private: Type type_; }; // Check whether the given index lies within the array boundary. // Returns the actual index between [0, len(array)) into the array (in case it's // negative). Returns -1 if the given index is not within bounds. // Takes an array as operand 0 // Takes an idx as operand 1 DEFINE_SIMPLE_INSTR( CheckSequenceBounds, (TObject, TCInt), HasOutput, Operands<2>, DeoptBase); // Create a cell holding given value and place the cell in dst. // Calls PyCell_New, so it implicitly increfs the value placed in the cell. DEFINE_SIMPLE_INSTR(MakeCell, (TOptObject), HasOutput, Operands<1>, DeoptBase); // Allocate an empty dict with the given capacity, or the default capacity if 0 // is given. class INSTR_CLASS(MakeDict, (), HasOutput, Operands<0>, DeoptBase) { public: MakeDict(Register* dst, size_t capacity, const FrameState& frame) : InstrT(dst, frame), capacity_(capacity) {} size_t GetCapacity() const { return capacity_; } private: size_t capacity_; }; // Allocate an empty checked dict with the given capacity, or the default // capacity if 0 is given. class INSTR_CLASS(MakeCheckedDict, (), HasOutput, Operands<0>, DeoptBase) { public: MakeCheckedDict( Register* dst, size_t capacity, Type dict_type, const FrameState& frame) : InstrT(dst, frame), capacity_(capacity), type_(dict_type) {} size_t GetCapacity() const { return capacity_; } Type type() const { return type_; } private: size_t capacity_; Type type_; }; // Allocate an empty checked list with the given capacity, or the default // capacity if 0 is given. class INSTR_CLASS(MakeCheckedList, (), HasOutput, Operands<0>, DeoptBase) { public: MakeCheckedList( Register* dst, size_t capacity, Type list_type, const FrameState& frame) : InstrT(dst, frame), capacity_(capacity), type_(list_type) {} size_t GetCapacity() const { return capacity_; } Type type() const { return type_; } private: size_t capacity_; Type type_; }; // merge two maps by (ultimately) calling _PyDict_MergeEx DEFINE_SIMPLE_INSTR( MergeDictUnpack, (TDict, TObject, TOptObject), HasOutput, Operands<3>, DeoptBase); // Allocate an empty set DEFINE_SIMPLE_INSTR(MakeSet, (), HasOutput, Operands<0>, DeoptBase); // merge two sets by calling _PySet_Update DEFINE_SIMPLE_INSTR( MergeSetUnpack, (TSet, TObject), HasOutput, Operands<2>, DeoptBase); // Takes a dict as operand 0 // Takes a key as operand 1 // Takes a value as operand 2 DEFINE_SIMPLE_INSTR( SetDictItem, (Constraint::kDictOrChkDict, TObject, TOptObject), HasOutput, Operands<3>, DeoptBase); // Takes a set as operand 0 // Takes a key as operand 1 DEFINE_SIMPLE_INSTR( SetSetItem, (TSet, TObject), HasOutput, Operands<2>, DeoptBase); // Load the size of a PyVarObject as a CInt64. DEFINE_SIMPLE_INSTR(LoadVarObjectSize, (TOptObject), HasOutput, Operands<1>); // Stores into an index // // Places NULL in dst if an error occurred or a non-NULL value otherwise class INSTR_CLASS( StoreSubscr, (TObject, TObject, TOptObject), HasOutput, Operands<3>, DeoptBase) { public: using InstrT::InstrT; // The index we're doing the subscript with Register* index() const { return GetOperand(1); } void set_index(Register* receiver) { SetOperand(1, receiver); } // The container we're doing the subscript with Register* container() const { return GetOperand(0); } void set_container(Register* receiver) { SetOperand(0, receiver); } // The value being stored Register* value() const { return GetOperand(2); } void set_value(Register* value) { SetOperand(2, value); } }; // Return a new iterator for the object, or return it if it's an iterator DEFINE_SIMPLE_INSTR(GetIter, (TObject), HasOutput, Operands<1>, DeoptBase); // Invoke next() on the iterator. // // The output is one of three values: // // 1. A sentinel value that indicates the iterator is exhausted. // 2. NULL to indicate an error has occurred. // 3. Any other value is the output of the iterator. DEFINE_SIMPLE_INSTR( InvokeIterNext, (TObject), HasOutput, Operands<1>, DeoptBase); // Returns a non-zero value if we need to release the GIL or run pending calls // (e.g. signal handlers). Returns 0 otherwise. This is intended to be // followed immediately by a CondBranch. DEFINE_SIMPLE_INSTR(LoadEvalBreaker, (), HasOutput, Operands<0>); // Let other threads run, run signal handlers, etc. DEFINE_SIMPLE_INSTR(RunPeriodicTasks, (), HasOutput, Operands<0>, DeoptBase); class INSTR_CLASS(Snapshot, (), Operands<0>) { public: Snapshot(const FrameState& frame_state) : InstrT() { setFrameState(frame_state); } Snapshot() : InstrT() {} // Set/get the metadata needed to reconstruct the state of the interpreter // after this instruction executes. void setFrameState(std::unique_ptr<FrameState> state) { frame_state_ = std::move(state); } void setFrameState(const FrameState& state) { frame_state_ = std::make_unique<FrameState>(state); } FrameState* frameState() const { return frame_state_.get(); } bool visitUses(const std::function<bool(Register*&)>& func) override { if (auto fs = frameState()) { return fs->visitUses(func); } return true; } private: std::unique_ptr<FrameState> frame_state_{nullptr}; }; // Always deopt. DEFINE_SIMPLE_INSTR(Deopt, (), Operands<0>, DeoptBase); // A DeoptPatchpoint reserves space in the instruction stream that may be // overwritten at runtime with a Deopt instruction. // // These are typically used by optimizations that want to invalidate compiled // code at runtime when an invariant that the code depends on is violated. // // See the comment in Jit/deopt_patcher.h for a description of how to use // these. class INSTR_CLASS(DeoptPatchpoint, (), Operands<0>, DeoptBase) { public: DeoptPatchpoint(DeoptPatcher* patcher) : InstrT(), patcher_(patcher) {} DeoptPatcher* patcher() const { return patcher_; } private: DeoptPatcher* patcher_; }; // A guard verifies that the value of pred is true. When it's not, control is // transferred to the interpreter at the point specified by the attached // FrameState. DEFINE_SIMPLE_INSTR(Guard, (TOptObject), Operands<1>, DeoptBase); // A guard that verifies that its src is the same object as the target, or // deopts if not. class INSTR_CLASS(GuardIs, (TOptObject), HasOutput, Operands<1>, DeoptBase) { public: GuardIs(Register* dst, PyObject* target, Register* src) : InstrT(dst, src), target_(target) {} PyObject* target() const { return target_; } private: PyObject* target_; }; // Return a copy of the input with a refined Type. The output Type is the // intersection of the source's type with the target Type. class INSTR_CLASS(GuardType, (TObject), HasOutput, Operands<1>, DeoptBase) { public: GuardType(Register* dst, Type target, Register* src) : InstrT(dst, src), target_(target) {} GuardType(Register* dst, Type target, Register* src, const FrameState& fs) : InstrT(dst, src, fs), target_(target) {} Type target() const { return target_; } private: Type target_; }; using ProfiledTypes = std::vector<std::vector<Type>>; // Stores all profiled types for a set of operands at a bytecode location. // // The top-level vector represents the different profiles seen (sorted by // frequency), and each inner vector represents the type of each operand for // that profile. // Used informatively - has no output and does not compile down to LIR. class INSTR_CLASS(HintType, (TObject), Operands<>) { public: HintType(ProfiledTypes op_types, const std::vector<Register*>& args) : InstrT(), types_(op_types) { size_t i = 0; for (Register* arg : args) { SetOperand(i++, arg); } } ProfiledTypes seenTypes() const { return types_; } private: ProfiledTypes types_; }; // Output 1, 0, if `value` is truthy or not truthy. DEFINE_SIMPLE_INSTR(IsTruthy, (TObject), HasOutput, Operands<1>, DeoptBase); DEFINE_SIMPLE_INSTR( IsInstance, (TObject, TType), HasOutput, Operands<2>, DeoptBase); DEFINE_SIMPLE_INSTR(IsSubtype, (TType, TType), HasOutput, Operands<2>); class INSTR_CLASS(ImportFrom, (TObject), HasOutput, Operands<1>, DeoptBase) { public: ImportFrom( Register* dst, Register* module, int name_idx, const FrameState& frame) : InstrT(dst, module, frame), name_idx_(name_idx) {} Register* module() const { return GetOperand(0); } int nameIdx() const { return name_idx_; } private: int name_idx_; }; class INSTR_CLASS( ImportName, (TObject, TLong), HasOutput, Operands<2>, DeoptBase) { public: ImportName( Register* dst, int name_idx, Register* fromlist, Register* level, const FrameState& frame) : InstrT(dst, fromlist, level, frame), name_idx_(name_idx) {} Register* GetFromList() const { return GetOperand(0); } Register* GetLevel() const { return GetOperand(1); } int name_idx() const { return name_idx_; } private: int name_idx_; }; // (Re)raises an exception with optional cause. class INSTR_CLASS(Raise, (TObject, TObject), Operands<>, DeoptBase) { public: enum class Kind { kReraise, kRaiseWithExc, kRaiseWithExcAndCause, }; private: Raise(Kind kind, const FrameState& frame) : InstrT(frame), kind_(kind) {} public: explicit Raise(const FrameState& frame) : Raise(Kind::kReraise, frame) {} Raise(const FrameState& frame, Register* exc) : Raise(Raise::Kind::kRaiseWithExc, frame) { SetOperand(0, exc); } Raise(const FrameState& frame, Register* exc, Register* cause) : Raise(Raise::Kind::kRaiseWithExcAndCause, frame) { SetOperand(0, exc); SetOperand(1, cause); } Kind kind() const { return kind_; } private: const Kind kind_; }; // Set an error by calling PyErr_Format() and then raising. This is typically // used when a runtime assertion implemented as part of a Python opcode is hit. class INSTR_CLASS(RaiseStatic, (TObject), Operands<>, DeoptBase) { public: RaiseStatic(PyObject* exc_type, const char* fmt, const FrameState& frame) : InstrT(frame), fmt_(fmt), exc_type_(exc_type) { JIT_CHECK(PyExceptionClass_Check(exc_type), "Expecting exception type"); } const char* fmt() const { return fmt_; } PyObject* excType() const { return exc_type_; } private: const char* fmt_; PyObject* exc_type_; }; DEFINE_SIMPLE_INSTR(SetCurrentAwaiter, (TOptObject), Operands<1>); class YieldBase : public Instr { public: explicit YieldBase(Opcode op, const FrameState& state) : Instr(op) { frame_state_ = std::make_unique<FrameState>(state); } void emplaceLiveOwnedReg(Register* reg) { live_owned_regs_.emplace_back(reg); } void emplaceLiveUnownedReg(Register* reg) { live_unowned_regs_.emplace_back(reg); } const std::vector<Register*>& liveOwnedRegs() const { return live_owned_regs_; } const std::vector<Register*>& liveUnownedRegs() const { return live_unowned_regs_; } FrameState* frameState() const { return frame_state_.get(); } private: std::vector<Register*> live_owned_regs_; std::vector<Register*> live_unowned_regs_; std::unique_ptr<FrameState> frame_state_{nullptr}; }; DEFINE_SIMPLE_INSTR(YieldValue, (TObject), HasOutput, Operands<1>, YieldBase); // InitialYield causes a generator function to suspend and return a new // 'PyGenObject' object holding its state. This should only appear in generator // functions and there should be exactly one instance before execution begins. DEFINE_SIMPLE_INSTR(InitialYield, (), HasOutput, Operands<0>, YieldBase); // Send the value in operand 0 to the subiterator in operand 1, forwarding // yielded values from the subiterator back to our caller until it is // exhausted. DEFINE_SIMPLE_INSTR( YieldFrom, (TObject, TOptObject), HasOutput, Operands<2>, YieldBase); // A more compact (in terms of emitted code) equivalent to YieldValue followed // by YieldFrom. DEFINE_SIMPLE_INSTR( YieldAndYieldFrom, (TOptObject, TObject), HasOutput, Operands<2>, YieldBase); // Implements BUILD_STRING opcode. DEFINE_SIMPLE_INSTR(BuildString, (TUnicode), HasOutput, Operands<>, DeoptBase); // Implements FORMAT_VALUE opcode, which handles f-string value formatting. class INSTR_CLASS( FormatValue, (TOptUnicode, TObject), HasOutput, Operands<2>, DeoptBase) { public: FormatValue( Register* dst, Register* fmt_spec, Register* value, int conversion, const FrameState& frame) : InstrT(dst, fmt_spec, value, frame), conversion_(conversion) {} int conversion() const { return conversion_; } private: int conversion_; }; // Implements `del container[sub]` // Takes a container as operand 0 // Takes a sub as operand 1 DEFINE_SIMPLE_INSTR(DeleteSubscr, (TObject, TObject), Operands<2>, DeoptBase); // Unpack a sequence as UNPACK_EX opcode and save the results // to a tuple class INSTR_CLASS( UnpackExToTuple, (TObject), HasOutput, Operands<1>, DeoptBase) { public: UnpackExToTuple( Register* dst, Register* seq, int before, int after, const FrameState& frame) : InstrT(dst, seq, frame), before_(before), after_(after) {} Register* seq() const { return GetOperand(0); } int before() const { return before_; } int after() const { return after_; } private: int before_; int after_; }; DEFINE_SIMPLE_INSTR( WaitHandleLoadCoroOrResult, (TObject), HasOutput, Operands<1>); DEFINE_SIMPLE_INSTR(WaitHandleLoadWaiter, (TObject), HasOutput, Operands<1>); DEFINE_SIMPLE_INSTR(WaitHandleRelease, (TObject), Operands<1>); DEFINE_SIMPLE_INSTR(IsErrStopAsyncIteration, (), HasOutput, Operands<0>); DEFINE_SIMPLE_INSTR(ClearError, (), Operands<0>); class CFG; class BasicBlock { public: BasicBlock() : BasicBlock(0) {} explicit BasicBlock(int id_) : id(id_), cfg(nullptr) {} ~BasicBlock(); // Split this block after instr. Once split, this block will contain all // instructions up to and including instr. A newly allocated block is returned // that contains all instructions following instr. BasicBlock* splitAfter(Instr& instr); // Replace any references to old_pred in this block's Phis with new_pred. void fixupPhis(BasicBlock* old_pred, BasicBlock* new_pred); // Adds a new predecessor to the phi that follows from the old predecessor void addPhiPredecessor(BasicBlock* old_pred, BasicBlock* new_pred); // Removes any references to old_pred in this block's Phis void removePhiPredecessor(BasicBlock* old_pred); // Read-only access to the incoming and outgoing edges. const std::unordered_set<const Edge*>& in_edges() const { return in_edges_; } const std::unordered_set<const Edge*>& out_edges() const { return out_edges_; } // Append or prepend an instruction to the instructions in the basic block. // // NB: The block takes ownership of the insruction and frees it when the block // is deleted. Instr* Append(Instr* instr); void push_front(Instr* instr); Instr* pop_front(); // Insert the given Instr before `it'. void insert(Instr* instr, Instr::List::iterator it); template <typename T, typename... Args> T* append(Args&&... args) { T* instr = T::create(std::forward<Args>(args)...); Append(instr); return instr; } template <typename T, typename... Args> T* appendWithOff(int bc_off, Args&&... args) { auto instr = append<T>(std::forward<Args>(args)...); instr->setBytecodeOffset(bc_off); return instr; } template <typename T, typename... Args> T* push_front(Args&&... args) { T* instr = T::create(std::forward<Args>(args)...); push_front(instr); return instr; } void retargetPreds(BasicBlock* target) { JIT_CHECK(target != this, "Can't retarget to self"); for (auto it = in_edges_.begin(); it != in_edges_.end();) { auto edge = *it; ++it; const_cast<Edge*>(edge)->set_to(target); } } BasicBlock* successor(std::size_t i) const { return GetTerminator()->successor(i); } void set_successor(std::size_t i, BasicBlock* succ) { GetTerminator()->set_successor(i, succ); } // Remove and delete all contained instructions, leaving the block empty. void clear(); // BasicBlock holds a list of instructions, delegating most operations // directly to its IntrusiveList. auto empty() const { return instrs_.IsEmpty(); } auto& front() { return instrs_.Front(); } auto& front() const { return instrs_.Front(); } auto& back() { return instrs_.Back(); } auto& back() const { return instrs_.Back(); } auto iterator_to(Instr& instr) { return instrs_.iterator_to(instr); } auto begin() { return instrs_.begin(); } auto begin() const { return instrs_.begin(); } auto end() { return instrs_.end(); } auto end() const { return instrs_.end(); } auto reverse_iterator_to(Instr& instr) { return instrs_.reverse_iterator_to(instr); } auto const_reverse_iterator_to(const Instr& instr) const { return instrs_.const_reverse_iterator_to(instr); } auto rbegin() { return instrs_.rbegin(); } auto rbegin() const { return instrs_.rbegin(); } auto rend() { return instrs_.rend(); } auto rend() const { return instrs_.rend(); } auto crend() const { return instrs_.crend(); } // Return the snapshot on entry to this block Snapshot* entrySnapshot(); // Return the last instruction in the block Instr* GetTerminator(); const Instr* GetTerminator() const { return const_cast<BasicBlock*>(this)->GetTerminator(); } // A trampoline block consists of a single direct jump to another block bool IsTrampoline(); void Print() const; // Call f with each Phi instruction at the beginning of this block. template <typename F> void forEachPhi(F f) { for (auto& instr : *this) { if (!instr.IsPhi()) { break; } f(static_cast<Phi&>(instr)); } } int id; // CFG that this block belongs to; may be NULL CFG* cfg; // Basic blocks belong to a list of all blocks in their CFG IntrusiveListNode cfg_node; private: DISALLOW_COPY_AND_ASSIGN(BasicBlock); friend class Edge; // Instructions for this basic block. // // The last instruction is guaranteed to be a terminator, which must be one // of: // // - Branch // - CondBranch // - Return // Instr::List instrs_; // Outgoing edges. std::unordered_set<const Edge*> out_edges_; // Incoming edges. std::unordered_set<const Edge*> in_edges_; }; class Function; class CFG { public: CFG() = default; ~CFG(); // Allocate a new basic block and insert it into this CFG BasicBlock* AllocateBlock(); // Allocate a block without linking it into the CFG BasicBlock* AllocateUnlinkedBlock(); // Insert a block into the CFG. The CFG takes ownership and will free it // upon destruction of the CFG. void InsertBlock(BasicBlock* block); // Remove block from the CFG void RemoveBlock(BasicBlock* block); // Split any critical edges by inserting trampoline blocks. void splitCriticalEdges(); // Return the RPO traversal of the basic blocks in the CFG starting from // entry_block. std::vector<BasicBlock*> GetRPOTraversal() const; // Return the BasicBlock in the CFG with the specified id, or nullptr if none // exist const BasicBlock* getBlockById(int id) const; // Return the RPO traversal of the reachable basic blocks in the CFG starting // from the given block. static std::vector<BasicBlock*> GetRPOTraversal(BasicBlock* start); // Entry point into the CFG; may be null BasicBlock* entry_block{nullptr}; // List of all blocks in the CFG IntrusiveList<BasicBlock, &BasicBlock::cfg_node> blocks; // The Function this CFG belongs to. May be null in tests. Function* func{nullptr}; private: DISALLOW_COPY_AND_ASSIGN(CFG); int next_block_id_{0}; }; class Environment { public: using RegisterMap = std::unordered_map<int, std::unique_ptr<Register>>; using ReferenceSet = std::unordered_set<Ref<>>; Environment() = default; Register* AllocateRegister(); const RegisterMap& GetRegisters() const; // Only intended to be used in tests and parsing code. Register* addRegister(std::unique_ptr<Register> reg); // Only intended to be used in tests and parsing code. Add a strong reference // to the given object to this Environment, returning a borrowed reference to // the object. BorrowedRef<> addReference(Ref<> obj); const ReferenceSet& references() const; // Returns nullptr if a register with the given `id` isn't found Register* getRegister(int id); int nextRegisterId() const { return next_register_id_; } void setNextRegisterId(int id) { next_register_id_ = id; } int allocateLoadAttrCache() { return next_load_attr_cache_++; } int numLoadAttrCaches() const { return next_load_attr_cache_; } private: DISALLOW_COPY_AND_ASSIGN(Environment); RegisterMap registers_; ReferenceSet references_; int next_register_id_{0}; int next_load_attr_cache_{0}; }; enum class FrameMode { kNormal, kShadow, }; struct TypedArgument { TypedArgument( long locals_idx, BorrowedRef<PyTypeObject> pytype, int optional, int exact, Type jit_type) : locals_idx(locals_idx), pytype(pytype), optional(optional), exact(exact), jit_type(jit_type){}; long locals_idx; Ref<PyTypeObject> pytype; int optional; int exact; Type jit_type; }; // Does the given code object need access to its containing PyFunctionObject at // runtime? bool usesRuntimeFunc(BorrowedRef<PyCodeObject> code); class Function { public: Function(); ~Function(); Ref<PyCodeObject> code; Ref<PyDictObject> globals; // for primitive args only, null if there are none Ref<_PyTypedArgsInfo> prim_args_info; // Fully-qualified name of the function std::string fullname; // Does this function need its PyFunctionObject* at runtime? bool uses_runtime_func{false}; // Does this function have primitive args? bool has_primitive_args{false}; // is the first argument a primitive? bool has_primitive_first_arg{false}; // vector of {locals_idx, type, optional} // in argument order, may have gaps for unchecked args std::vector<TypedArgument> typed_args; // Return type Type return_type{TObject}; FrameMode frameMode{FrameMode::kNormal}; CFG cfg; Environment env; // optional property used to track time taken for individual compilation // phases std::unique_ptr<CompilationPhaseTimer> compilation_phase_timer{nullptr}; // Return the total number of arguments (positional + kwonly + varargs + // varkeywords) int numArgs() const; // Return the number of locals + cellvars + freevars Py_ssize_t numVars() const; // Set code and a number of other members that are derived from it. void setCode(BorrowedRef<PyCodeObject> code); void Print() const; // Count the number of instructions that match the predicate std::size_t CountInstrs(InstrPredicate pred) const; // Does this function return a primitive type? bool returnsPrimitive() const { return return_type <= TPrimitive; } // Does this function return a primitive double? bool returnsPrimitiveDouble() const { return return_type <= TCDouble; } void setCompilationPhaseTimer(std::unique_ptr<CompilationPhaseTimer> cpt) { compilation_phase_timer = std::move(cpt); } private: DISALLOW_COPY_AND_ASSIGN(Function); }; FrameState* get_frame_state(Instr& instr); const FrameState* get_frame_state(const Instr& instr); }; // namespace hir }; // namespace jit