Jit/lir/block_builder.cpp (494 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #include "Jit/lir/block_builder.h" #include "Jit/lir/generator.h" #include "Jit/lir/instruction.h" #include "Jit/lir/lir.h" #include "Jit/util.h" #include <dlfcn.h> #include <sstream> // XXX: this file needs to be revisited when we optimize HIR-to-LIR translation // in codegen.cpp/h. Currently, this file is almost an identical copy from // bbbuilder.cpp with some interfaces changes so that it works with the new // LIR. namespace jit { namespace lir { static inline uint64_t stoull(std::string& s) { return std::stoull(s, 0, 0); } static inline std::string GetId(const std::string& s) { size_t colon; if ((colon = s.find(':')) != std::string::npos) { return s.substr(0, colon); } return s; } static inline std::pair<std::string, Operand::DataType> GetIdAndType( const std::string& name) { size_t colon; Operand::DataType data_type = Operand::kObject; if ((colon = name.find(':')) != std::string::npos) { std::string type = name.substr(colon + 1); if (type == "CInt8" || type == "CUInt8" || type == "CBool") { data_type = Operand::k8bit; } else if (type == "CInt16" || type == "CUInt16") { data_type = Operand::k16bit; } else if (type == "CInt32" || type == "CUInt32") { data_type = Operand::k32bit; } else if (type == "CInt64" || type == "CUInt64") { data_type = Operand::k64bit; } else if (type == "CDouble") { data_type = Operand::kDouble; } return {name.substr(0, colon), data_type}; } else { return {name, data_type}; } } BasicBlockBuilder::BasicBlockBuilder(jit::codegen::Environ* env, Function* func) : env_(env), func_(func) { cur_bb_ = GetBasicBlockByLabel("__main__"); bbs_.push_back(cur_bb_); } void BasicBlockBuilder::AppendCode(const std::string& s) { auto stream = std::stringstream{s}; for (std::string line; std::getline(stream, line, '\n');) { if (IsLabel(line)) { line.pop_back(); auto next_bb = GetBasicBlockByLabel(line); if (cur_bb_->successors().size() < 2) { cur_bb_->addSuccessor(next_bb); } cur_bb_ = next_bb; bbs_.push_back(cur_bb_); } else { AppendCodeLine(line); } } } std::vector<std::string> BasicBlockBuilder::Tokenize(const std::string& s) { std::vector<std::string> tokens; auto it = s.begin(); it = std::find_if( it, s.end(), [](auto c) -> bool { return c != ' ' && c != ','; }); while (it != s.end()) { auto end = std::find_if( it, s.end(), [](auto c) -> bool { return c == ' ' || c == ','; }); tokens.emplace_back(it, end); it = std::find_if( end, s.end(), [](auto c) -> bool { return c != ' ' && c != ','; }); } return tokens; } Instruction* BasicBlockBuilder::createInstr(Instruction::Opcode opcode) { return cur_bb_->allocateInstr(opcode, cur_hir_instr_); } void BasicBlockBuilder::AppendCodeLine(const std::string& s) { // this function assumes that input is syntactically correct. // there is very limited syntax checking in the following parsing process. std::vector<std::string> tokens = Tokenize(s); const std::string& instr_str = tokens[0]; if (instr_str == "Load") { auto instr = createInstr(Instruction::kMove); if (tokens.size() == 3) { instr->allocateAddressInput(reinterpret_cast<void*>(stoull(tokens[2]))); } else { CreateInstrIndirect(instr, tokens[2], stoull(tokens[3])); } CreateInstrOutput(instr, tokens[1]); } else if (instr_str == "LoadArg") { JIT_CHECK(tokens.size() == 3, "expected 3 args"); auto instr = createInstr(Instruction::kLoadArg); CreateInstrImmediateInput(instr, tokens[2]); CreateInstrOutput(instr, tokens[1]); } else if (instr_str == "Store") { auto instr = createInstr(Instruction::kMove); JIT_CHECK( tokens.size() == 3 || tokens.size() == 4, "Syntax error for Store"); if (IsConstant(tokens[1])) { CreateInstrImmediateInput(instr, tokens[1]); } else { CreateInstrInput(instr, tokens[1]); } if (tokens.size() == 3) { instr->output()->setMemoryAddress( reinterpret_cast<void*>(stoull(tokens[2]))); } else { CreateInstrIndirectOutput(instr, tokens[2], stoull(tokens[3])); } instr->output()->setDataType(instr->getInput(0)->dataType()); } else if (instr_str == "Move") { JIT_CHECK(tokens.size() == 3, "Syntax error for Move."); JIT_CHECK( !IsConstant(tokens[1]), "Syntax error for Move: %s", tokens[1].c_str()); auto instr = createInstr(Instruction::kMove); if (IsConstant(tokens[2])) { CreateInstrImmediateInput(instr, tokens[2]); } else { CreateInstrInput(instr, tokens[2]); } CreateInstrOutput(instr, tokens[1]); } else if (instr_str == "Lea") { JIT_CHECK(tokens.size() == 4, "Syntax error for LoadAddress."); JIT_CHECK( !IsConstant(tokens[1]), "Syntax error for LoadAddress: %s", tokens[1].c_str()); auto instr = createInstr(Instruction::kLea); CreateInstrIndirect(instr, tokens[2], stoull(tokens[3])); CreateInstrOutput(instr, tokens[1]); } else if (instr_str == "Return") { auto instr = createInstr(Instruction::kReturn); CreateInstrInput(instr, tokens[1]); } else if (instr_str == "Convert") { auto instr = createInstr(Instruction::kSext); if (IsConstant(tokens[2])) { CreateInstrImmediateInput(instr, tokens[2]); } else { CreateInstrInput(instr, tokens[2]); } CreateInstrOutput(instr, tokens[1]); } else if (instr_str == "ConvertUnsigned") { auto instr = createInstr(Instruction::kZext); if (IsConstant(tokens[2])) { CreateInstrImmediateInput(instr, tokens[2]); } else { CreateInstrInput(instr, tokens[2]); } CreateInstrOutput(instr, tokens[1]); } else if ( instr_str == "Add" || instr_str == "Sub" || instr_str == "And" || instr_str == "Xor" || instr_str == "Or" || instr_str == "LShift" || instr_str == "RShift" || instr_str == "RShiftUn" || instr_str == "Mul" || instr_str == "Div" || instr_str == "DivUn" || instr_str == "Equal" || instr_str == "NotEqual" || instr_str == "GreaterThanSigned" || instr_str == "LessThanSigned" || instr_str == "GreaterThanEqualSigned" || instr_str == "LessThanEqualSigned" || instr_str == "GreaterThanUnsigned" || instr_str == "LessThanUnsigned" || instr_str == "GreaterThanEqualUnsigned" || instr_str == "LessThanEqualUnsigned" || instr_str == "Fadd" || instr_str == "Fsub" || instr_str == "Fmul" || instr_str == "Fdiv") { Instruction* instr = nullptr; if (instr_str == "Add") { instr = createInstr(Instruction::kAdd); } else if (instr_str == "Sub") { instr = createInstr(Instruction::kSub); } else if (instr_str == "And") { instr = createInstr(Instruction::kAnd); } else if (instr_str == "Xor") { instr = createInstr(Instruction::kXor); } else if (instr_str == "Or") { instr = createInstr(Instruction::kOr); } else if (instr_str == "LShift") { instr = createInstr(Instruction::kLShift); } else if (instr_str == "RShift") { instr = createInstr(Instruction::kRShift); } else if (instr_str == "RShiftUn") { instr = createInstr(Instruction::kRShiftUn); } else if (instr_str == "Mul") { instr = createInstr(Instruction::kMul); } else if (instr_str == "Equal") { instr = createInstr(Instruction::kEqual); } else if (instr_str == "NotEqual") { instr = createInstr(Instruction::kNotEqual); } else if (instr_str == "GreaterThanSigned") { instr = createInstr(Instruction::kGreaterThanSigned); } else if (instr_str == "LessThanSigned") { instr = createInstr(Instruction::kLessThanSigned); } else if (instr_str == "GreaterThanEqualSigned") { instr = createInstr(Instruction::kGreaterThanEqualSigned); } else if (instr_str == "LessThanEqualSigned") { instr = createInstr(Instruction::kLessThanEqualSigned); } else if (instr_str == "GreaterThanUnsigned") { instr = createInstr(Instruction::kGreaterThanUnsigned); } else if (instr_str == "LessThanUnsigned") { instr = createInstr(Instruction::kLessThanUnsigned); } else if (instr_str == "GreaterThanEqualUnsigned") { instr = createInstr(Instruction::kGreaterThanEqualUnsigned); } else if (instr_str == "LessThanEqualUnsigned") { instr = createInstr(Instruction::kLessThanEqualUnsigned); } else if (instr_str == "Fadd") { instr = createInstr(Instruction::kFadd); } else if (instr_str == "Fsub") { instr = createInstr(Instruction::kFsub); } else if (instr_str == "Fmul") { instr = createInstr(Instruction::kFmul); } else if (instr_str == "Fdiv") { instr = createInstr(Instruction::kFdiv); } else if (instr_str == "Div") { instr = createInstr(Instruction::kDiv); } else if (instr_str == "DivUn") { instr = createInstr(Instruction::kDivUn); } else { JIT_CHECK(false, "Unknown LIR instruction: %s", instr_str); } for (size_t i = 2; i < tokens.size(); i++) { if (IsConstant(tokens[i])) { CreateInstrImmediateInput(instr, tokens[i]); } else { CreateInstrInput(instr, tokens[i]); } } CreateInstrOutput(instr, tokens[1]); } else if (instr_str == "Negate") { auto instr = createInstr(Instruction::kNegate); if (IsConstant(tokens[2])) { CreateInstrImmediateInput(instr, tokens[2]); } else { CreateInstrInput(instr, tokens[2]); } CreateInstrOutput(instr, tokens[1]); } else if (instr_str == "Invert") { auto instr = createInstr(Instruction::kInvert); if (IsConstant(tokens[2])) { CreateInstrImmediateInput(instr, tokens[2]); } else { CreateInstrInput(instr, tokens[2]); } CreateInstrOutput(instr, tokens[1]); } else if ( instr_str == "Call" || instr_str == "Vectorcall" || instr_str == "Invoke") { bool is_invoke = (instr_str == "Invoke"); bool is_vector_call = (instr_str == "Vectorcall"); if (g_dump_c_helper) { size_t dest_idx = is_invoke ? 1 : 2; if (dest_idx < tokens.size() && IsConstant(tokens[dest_idx])) { std::string helper_id = GetId(tokens[dest_idx]); uint64_t helper_addr = stoull(helper_id); Dl_info helper_info; if (dladdr(reinterpret_cast<void*>(helper_addr), &helper_info) != 0 && helper_info.dli_sname != NULL) { JIT_LOG("Call to function %s.", helper_info.dli_sname); } else { JIT_LOG("Call to function at %s.", tokens[dest_idx]); } } } auto instr = createInstr( is_vector_call ? Instruction::kVectorCall : Instruction::kCall); for (size_t i = is_invoke ? 1 : 2; i < tokens.size(); i++) { if (IsConstant(tokens[i])) { CreateInstrImmediateInput(instr, tokens[i]); } else { CreateInstrInput(instr, tokens[i]); } } if (!is_invoke) { CreateInstrOutput(instr, tokens[1]); } } else if (instr_str == "CondBranch") { auto instr = createInstr(Instruction::kCondBranch); auto cond = tokens[1]; if (IsConstant(cond)) { CreateInstrImmediateInput(instr, cond); } else { CreateInstrInput(instr, cond); } } else if (instr_str == "JumpIf") { // the difference between CondBranch and JumpIf is that the // arguments of CondBranch is HIR basic block ids, while those // of JumpIf are label names. // TODO: we can merge CondBranch and JumpIf by translating // HIR basic block ids into label names. auto instr = createInstr(Instruction::kCondBranch); auto cond = tokens[1]; if (IsConstant(cond)) { CreateInstrImmediateInput(instr, cond); } else { CreateInstrInput(instr, cond); } auto true_bb = GetBasicBlockByLabel(tokens[2]); auto false_bb = GetBasicBlockByLabel(tokens[3]); cur_bb_->addSuccessor(true_bb); cur_bb_->addSuccessor(false_bb); } else if (instr_str == "Branch") { createInstr(Instruction::kBranch); } else if (instr_str == "BranchB" || instr_str == "BranchC") { createInstr(Instruction::kBranchB); auto succ_bb = GetBasicBlockByLabel(tokens[1]); cur_bb_->addSuccessor(succ_bb); } else if (instr_str == "BranchNZ") { createInstr(Instruction::kBranchNZ); auto succ_bb = GetBasicBlockByLabel(tokens[1]); cur_bb_->addSuccessor(succ_bb); } else if (instr_str == "BranchC") { createInstr(Instruction::kBranchC); auto succ_bb = GetBasicBlockByLabel(tokens[1]); cur_bb_->addSuccessor(succ_bb); } else if (instr_str == "BranchNC") { createInstr(Instruction::kBranchNC); auto succ_bb = GetBasicBlockByLabel(tokens[1]); cur_bb_->addSuccessor(succ_bb); } else if (instr_str == "BitTest") { auto instr = createInstr(Instruction::kBitTest); CreateInstrInput(instr, tokens[1]); CreateInstrImmediateInput(instr, tokens[2]); } else if (instr_str == "Inc" || instr_str == "Dec") { auto instr = createInstr(instr_str == "Inc" ? Instruction::kInc : Instruction::kDec); CreateInstrInput(instr, tokens[1]); } else if (instr_str == "Guard") { auto instr = createInstr(Instruction::kGuard); enum InstrGuardKind guard_kind; const std::string& kind = tokens[1]; if (kind == "NotZero") { guard_kind = InstrGuardKind::kNotZero; } else if (kind == "NotNegative") { guard_kind = InstrGuardKind::kNotNegative; } else if (kind == "AlwaysFail") { guard_kind = InstrGuardKind::kAlwaysFail; } else if (kind == "Is") { guard_kind = InstrGuardKind::kIs; } else if (kind == "HasType") { guard_kind = InstrGuardKind::kHasType; } else { JIT_CHECK(false, "unknown check kind: {}", kind); } instr->allocateImmediateInput(guard_kind); CreateInstrImmediateInput(instr, tokens[2]); for (size_t i = 3; i < tokens.size(); i++) { if (tokens[i] == "reg:edx") { Operand* opnd = instr->allocatePhyRegisterInput(PhyLocation::RDX); opnd->setDataType(OperandBase::k32bit); } else if (tokens[i] == "reg:xmm1") { Operand* opnd = instr->allocatePhyRegisterInput(PhyLocation::XMM1); opnd->setDataType(OperandBase::kDouble); } else if (IsConstant(tokens[i])) { CreateInstrImmediateInput(instr, tokens[i]); } else { CreateInstrInput(instr, tokens[i]); } } } else if (instr_str == "DeoptPatchpoint") { auto instr = createInstr(Instruction::kDeoptPatchpoint); for (size_t i = 1; i < tokens.size(); i++) { if (IsConstant(tokens[i])) { CreateInstrImmediateInput(instr, tokens[i]); } else { CreateInstrInput(instr, tokens[i]); } } } else if (instr_str == "Phi") { auto instr = createInstr(Instruction::kPhi); JIT_CHECK((tokens.size() & 1) == 0, "Expected even number of tokens"); for (size_t i = 2; i < tokens.size() - 1; i += 2) { instr->allocateLabelInput( reinterpret_cast<BasicBlock*>(stoull(tokens[i]))); CreateInstrInput(instr, tokens[i + 1]); } CreateInstrOutput(instr, tokens[1]); } else if ( instr_str == "YieldInitial" || instr_str == "YieldValue" || instr_str == "YieldFrom" || instr_str == "YieldFromSkipInitialSend") { Instruction* instr; if (instr_str == "YieldInitial") { instr = createInstr(Instruction::kYieldInitial); } else if (instr_str == "YieldValue") { instr = createInstr(Instruction::kYieldValue); } else if (instr_str == "YieldFromSkipInitialSend") { instr = createInstr(Instruction::kYieldFromSkipInitialSend); } else { instr = createInstr(Instruction::kYieldFrom); } CreateInstrOutput(instr, tokens[1]); for (size_t tok_n = 2; tok_n < tokens.size() - 1; tok_n++) { CreateInstrInput(instr, tokens[tok_n]); } CreateInstrImmediateInput(instr, tokens[tokens.size() - 1]); } else if (instr_str == "BatchDecref") { auto instr = createInstr(Instruction::kBatchDecref); for (size_t i = 1; i < tokens.size(); i++) { CreateInstrInput(instr, tokens[i]); } } else { JIT_CHECK(false, "Unknown LIR instruction: %s", instr_str); } } BasicBlock* BasicBlockBuilder::GetBasicBlockByLabel(const std::string& label) { auto iter = label_to_bb_.find(label); if (iter == label_to_bb_.end()) { auto bb = func_->allocateBasicBlock(); label_to_bb_.emplace(label, bb); return bb; } return iter->second; } void BasicBlockBuilder::CreateInstrImmediateInput( Instruction* instr, const std::string& val_type) { std::string sval; Operand::DataType type; std::tie(sval, type) = GetIdAndType(val_type); if (type == Operand::kDouble) { instr->allocateImmediateInput(bit_cast<uint64_t>(stod(sval)), type); } else { instr->allocateImmediateInput(stoull(sval), type); } } Instruction* BasicBlockBuilder::getDefInstr(const std::string& name) { auto def_instr = map_get(env_->output_map, name, nullptr); if (def_instr == nullptr) { // the output has to be copy propagated. auto iter = env_->copy_propagation_map.find(name); const char* prop_name = nullptr; while (iter != env_->copy_propagation_map.end()) { prop_name = iter->second.c_str(); iter = env_->copy_propagation_map.find(prop_name); } if (prop_name != nullptr) { def_instr = map_get(env_->output_map, prop_name, nullptr); } } return def_instr; } void BasicBlockBuilder::CreateInstrInput( Instruction* instr, const std::string& name_size) { std::string name = GetId(name_size); auto def_instr = getDefInstr(name); auto operand = instr->allocateLinkedInput(def_instr); // if def_instr is still nullptr, it means that the output is defined // later in the function. This can happen when the function has a // backward edge. if (def_instr == nullptr) { // we need to fix later env_->operand_to_fix[name].push_back(operand); } } void BasicBlockBuilder::CreateInstrOutput( Instruction* instr, const std::string& name_size) { std::string name; Operand::DataType data_type; std::tie(name, data_type) = GetIdAndType(name_size); auto pair = env_->output_map.emplace(name, instr); JIT_DCHECK( pair.second, "Multiple outputs with the same name (%s)- HIR is not in SSA form.", name); auto output = instr->output(); output->setVirtualRegister(); output->setDataType(data_type); } void BasicBlockBuilder::CreateInstrIndirect( Instruction* instr, const std::string& name_size, int offset) { auto name = GetId(name_size); if (name == "__native_frame_base") { instr->allocateMemoryIndirectInput(PhyLocation::RBP, offset); return; } auto def_instr = getDefInstr(name); auto indirect = instr->allocateMemoryIndirectInput(def_instr, offset); if (def_instr == nullptr) { auto ind_opnd = indirect->getMemoryIndirect()->getBaseRegOperand(); JIT_DCHECK( ind_opnd->isLinked(), "Should not have generated unlinked operand."); env_->operand_to_fix[name].push_back(static_cast<LinkedOperand*>(ind_opnd)); } } void BasicBlockBuilder::CreateInstrIndirectOutput( Instruction* instr, const std::string& name_size, int offset) { auto name = GetId(name_size); if (name == "__native_frame_base") { instr->output()->setMemoryIndirect(PhyLocation::RBP, offset); return; } auto def_instr = getDefInstr(name); auto output = instr->output(); output->setMemoryIndirect(def_instr, offset); if (def_instr == nullptr) { auto ind_opnd = output->getMemoryIndirect()->getBaseRegOperand(); JIT_DCHECK( ind_opnd->isLinked(), "Should not have generated unlinked operand."); env_->operand_to_fix[name].push_back(static_cast<LinkedOperand*>(ind_opnd)); } } } // namespace lir } // namespace jit