Jit/hir/builder.h (410 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#pragma once
#include "Python.h"
#include "Jit/bytecode.h"
#include "Jit/hir/hir.h"
#include "Jit/hir/preload.h"
#include "Jit/profile_data.h"
#include "Jit/stack.h"
#include "Jit/util.h"
#include <deque>
#include <functional>
#include <memory>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace jit {
class BytecodeInstruction;
namespace hir {
class BasicBlock;
class Environment;
class Function;
class Register;
extern const std::unordered_set<int> kSupportedOpcodes;
// Helper class for managing temporary variables
class TempAllocator {
public:
explicit TempAllocator(Environment* env) : env_(env) {}
// Allocate a temp register that may be used for the stack. It should not be a
// register that will be treated specially in the FrameState (e.g. tracked as
// containing a local or cell.)
Register* AllocateStack();
// Get the i-th stack temporary or allocate one
Register* GetOrAllocateStack(std::size_t idx);
// Allocate a temp register that will not be used for a stack value.
Register* AllocateNonStack();
private:
Environment* env_;
std::vector<Register*> cache_;
};
// We expect that on exit from a basic block the stack only contains temporaries
// in increasing order (called the canonical form). For example,
//
// t0
// t1
// t2 <- top of stack
//
// It may be the case that temporaries are re-ordered, duplicated, or the stack
// contains locals. This class is responsible for inserting the necessary
// register moves such that the stack is in canonical form.
class BlockCanonicalizer {
public:
BlockCanonicalizer() : processing_(), done_(), copies_(), moved_() {}
void Run(BasicBlock* block, TempAllocator& temps, OperandStack& stack);
private:
DISALLOW_COPY_AND_ASSIGN(BlockCanonicalizer);
void InsertCopies(
Register* reg,
TempAllocator& temps,
Instr& terminator,
std::vector<Register*>& alloced);
std::unordered_set<Register*> processing_;
std::unordered_set<Register*> done_;
std::unordered_map<Register*, std::vector<Register*>> copies_;
std::unordered_map<Register*, Register*> moved_;
};
// Convenience wrapper, used only in tests
std::unique_ptr<Function> buildHIR(BorrowedRef<PyFunctionObject> func);
// Translate the bytecode for preloader.code into HIR, in the context of the
// preloaded globals and classloader lookups in the preloader.
//
// The resulting HIR is un-optimized, not in SSA form, and does not yet have
// refcount operations or types flowed through it. Later passes will transform
// to SSA, flow types, optimize, and insert refcount operations using liveness
// analysis.
std::unique_ptr<Function> buildHIR(const Preloader& preloader);
// Inlining merges all of the different callee Returns (which terminate blocks,
// leading to a bunch of distinct exit blocks) into Branches to one Return
// block (one exit block), which the caller can transform into an Assign to the
// output register of the original call instruction.
//
// Call InlineResult::succeeded to determine if the inline was successful.
struct InlineResult {
BasicBlock* entry;
BasicBlock* exit;
bool succeeded() const {
return entry != nullptr && exit != nullptr;
}
};
class HIRBuilder {
public:
HIRBuilder(const Preloader& preloader)
: code_(preloader.code()), preloader_(preloader){};
// Translate the bytecode for code_ into HIR, in the context of the preloaded
// globals and classloader lookups from preloader_.
//
// The resulting HIR is un-optimized, not in SSA form, and does not yet have
// refcount operations or types flowed through it. Later passes will transform
// to SSA, flow types, optimize, and insert refcount operations using liveness
// analysis.
//
// TODO(mpage): Consider using something like Either here to indicate reason
// for failure.
std::unique_ptr<Function> buildHIR();
// Given the preloader for the callee (passed into the constructor),
// construct the CFG for the callee in the caller's CFG. Does not link the
// two CFGs, except for FrameState parent pointers. Use caller_frame_state
// as the starting FrameState for the callee.
//
// Use InlineResult::succeeded to check if inlining succeeded.
InlineResult inlineHIR(Function* caller, FrameState* caller_frame_state);
private:
DISALLOW_COPY_AND_ASSIGN(HIRBuilder);
// Used by buildHIR and inlineHIR.
// irfunc is the function being compiled or the caller function.
// frame_state should be nullptr if irfunc matches the preloader (not
// inlining) and non-nullptr otherwise (inlining).
// Returns the entry block.
BasicBlock* buildHIRImpl(Function* irfunc, FrameState* frame_state);
struct TranslationContext;
// Completes compilation of a finally block
using FinallyCompleter =
std::function<void(TranslationContext&, const jit::BytecodeInstruction&)>;
void translate(
Function& irfunc,
const jit::BytecodeInstructionBlock& bc_instrs,
const TranslationContext& tc,
FinallyCompleter complete_finally = nullptr);
void emitProfiledTypes(
TranslationContext& tc,
const CodeProfileData& profile_data,
const BytecodeInstruction& bc_instr);
void emitBinaryOp(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitUnaryOp(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitAnyCall(
CFG& cfg,
TranslationContext& tc,
jit::BytecodeInstructionBlock::Iterator& bc_it,
const jit::BytecodeInstructionBlock& bc_instrs);
void emitCallEx(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr,
bool is_awaited);
void emitCallFunction(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr,
bool is_awaited);
void emitCallKWArgs(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr,
bool is_awaited);
void emitCallMethod(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr,
bool is_awaited);
void emitCompareOp(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitJumpIf(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitDeleteAttr(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitLoadAttr(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitLoadMethod(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitLoadMethodOrAttrSuper(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr,
bool load_method);
void emitLoadDeref(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitStoreDeref(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitLoadConst(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitLoadFast(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitLoadGlobal(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitLoadType(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitMakeFunction(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitFunctionCredential(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitMakeListTuple(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitMakeListTupleUnpack(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitBuildCheckedList(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitBuildCheckedMap(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitBuildMap(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitBuildMapUnpack(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr,
bool with_call);
void emitBuildSet(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitBuildSetUnpack(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitBuildConstKeyMap(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitPopJumpIf(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitStoreAttr(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitStoreFast(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitStoreSubscr(TranslationContext& tc);
void emitInPlaceOp(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitBuildSlice(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitLoadIterableArg(
CFG& cfg,
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
bool emitInvokeFunction(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr,
bool is_awaited);
void emitGetIter(TranslationContext& tc);
void emitGetYieldFromIter(CFG& cfg, TranslationContext& tc);
void emitListAppend(
TranslationContext& tc,
const BytecodeInstruction& bc_instr);
void emitForIter(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
bool emitInvokeMethod(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr,
bool is_awaited);
void emitInvokeTypedMethod(
TranslationContext& tc,
PyMethodDef* method,
Py_ssize_t nargs);
void emitLoadField(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitStoreField(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitCast(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitTpAlloc(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitStoreLocal(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitLoadLocal(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitConvertPrimitive(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitPrimitiveLoadConst(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitIntLoadConstOld(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitPrimitiveBinaryOp(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitPrimitiveCompare(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitPrimitiveBox(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitPrimitiveUnbox(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitImportFrom(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitImportName(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitPrimitiveUnaryOp(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitFastLen(
CFG& cfg,
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitRaiseVarargs(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitRefineType(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitSequenceGet(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitSequenceRepeat(
CFG& cfg,
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitSequenceSet(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitYieldValue(TranslationContext& tc);
void emitGetAwaitable(CFG& cfg, TranslationContext& tc, int prev_op);
void emitUnpackEx(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitUnpackSequence(
CFG& cfg,
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitBeginFinally(
Function& irfunc,
TranslationContext& tc,
const BytecodeInstructionBlock& bc_instrs,
const jit::BytecodeInstruction& bc_instr,
std::deque<TranslationContext>& queue);
void emitCallFinally(
Function& irfunc,
TranslationContext& tc,
const BytecodeInstructionBlock& bc_instrs,
const jit::BytecodeInstruction& bc_instr,
std::deque<TranslationContext>& queue);
void emitEndFinally(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr,
FinallyCompleter complete_finally);
void emitFinallyBlock(
Function& irfunc,
TranslationContext& tc,
const BytecodeInstructionBlock& bc_instrs,
std::deque<TranslationContext>& queue,
Py_ssize_t finally_off,
BasicBlock* ret_block);
void emitPopFinally(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr,
FinallyCompleter complete_finally);
void emitSetupFinally(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitAsyncForHeaderYieldFrom(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitEndAsyncFor(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitGetAIter(TranslationContext& tc);
void emitGetANext(TranslationContext& tc);
Register* emitSetupWithCommon(
TranslationContext& tc,
_Py_Identifier* enter_id,
_Py_Identifier* exit_id,
bool swap_lookup);
void emitBeforeAsyncWith(TranslationContext& tc);
void emitSetupAsyncWith(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitSetupWith(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitWithCleanupStart(TranslationContext& tc);
void emitWithCleanupFinish(TranslationContext& tc);
void emitYieldFrom(TranslationContext& tc, Register* out);
void emitDispatchEagerCoroResult(
CFG& cfg,
TranslationContext& tc,
Register* out,
BasicBlock* await_block,
BasicBlock* post_await_block);
void emitBuildString(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitFormatValue(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitMapAdd(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
void emitSetAdd(
TranslationContext& tc,
const jit::BytecodeInstruction& bc_instr);
BorrowedRef<> constArg(const jit::BytecodeInstruction& bc_instr);
ExecutionBlock popBlock(CFG& cfg, TranslationContext& tc);
void insertEvalBreakerCheckForLoop(CFG& cfg, BasicBlock* loop_header);
void insertEvalBreakerCheckForExcept(CFG& cfg, TranslationContext& tc);
void insertEvalBreakerCheck(
CFG& cfg,
BasicBlock* check_block,
BasicBlock* succ,
const FrameState& frame);
void addInitialYield(TranslationContext& tc);
void addLoadArgs(TranslationContext& tc, int num_args);
void addInitializeCells(TranslationContext& tc, Register* cur_func);
void AllocateRegistersForLocals(Environment* env, FrameState& state);
void AllocateRegistersForCells(Environment* env, FrameState& state);
void moveOverwrittenStackRegisters(TranslationContext& tc, Register* dst);
bool tryEmitDirectMethodCall(
const InvokeTarget& target,
TranslationContext& tc,
long nargs);
struct BlockMap {
std::unordered_map<Py_ssize_t, BasicBlock*> blocks;
std::unordered_map<BasicBlock*, BytecodeInstructionBlock> bc_blocks;
};
BlockMap createBlocks(
Function& irfunc,
const BytecodeInstructionBlock& bc_block);
BasicBlock* getBlockAtOff(Py_ssize_t off);
// When a static function calls another static function indirectly, all args
// are passed boxed and the return value will come back boxed, so we must
// box primitive args and and unbox primitive return values. These functions
// take care of these two, respectively.
std::vector<Register*> setupStaticArgs(
TranslationContext& tc,
const InvokeTarget& target,
long nargs);
void fixStaticReturn(TranslationContext& tc, Register* reg, Type ret_type);
// Unbox the primitive value from src into dst, using the given type. Similar
// to TranslationContext::emitChecked(), but uses IsNegativeAndErrOccurred
// instead of the normal CheckExc because of the primitive output value.
void unboxPrimitive(
TranslationContext& tc,
Register* dst,
Register* src,
Type type);
BorrowedRef<PyCodeObject> code_;
BlockMap block_map_;
const Preloader& preloader_;
// Map index of END_ASYNC_FOR bytecodes to FrameState of paired YIELD_FROMs
std::unordered_map<size_t, FrameState> end_async_for_frame_state_;
TempAllocator temps_{nullptr};
};
} // namespace hir
} // namespace jit