void UnitEmitter::initMain(int line1, int line2) { assertx(m_fes.size() == 0); StringData* name = staticEmptyString(); FuncEmitter* pseudomain = newFuncEmitter(name); Attr attrs = AttrMayUseVV; pseudomain->init(line1, line2, 0, attrs, false, name); }
ExnTreeInfo build_exn_tree(const FuncEmitter& fe, php::Func& func, FindBlock findBlock) { ExnTreeInfo ret; uint32_t nextExnNode = 0; for (auto& eh : fe.ehtab()) { auto node = folly::make_unique<php::ExnNode>(); node->id = nextExnNode++; node->parent = nullptr; switch (eh.m_type) { case EHEnt::Type::Fault: { auto const fault = findBlock(eh.m_fault); assert(ret.funcletNodes[fault] == nullptr); ret.funcletNodes[fault] = borrow(node); /* * We know the block for this offset starts a fault funclet, * but we won't know its extents until we've built the cfg and * can look at the control flow in the funclet. Set the block * type to Fault for now, but we won't propagate the value to * the rest of the funclet blocks until find_fault_funclets. */ fault->kind = php::Block::Kind::Fault; node->info = php::FaultRegion { fault, eh.m_iterId, eh.m_itRef }; } break; case EHEnt::Type::Catch: { auto treg = php::TryRegion {}; for (auto& centry : eh.m_catches) { auto const catchBlk = findBlock(centry.second); catchBlk->kind = php::Block::Kind::CatchEntry; treg.catches.emplace_back( fe.ue().lookupLitstr(centry.first), catchBlk ); } node->info = treg; } break; } ret.ehMap[&eh] = borrow(node); if (eh.m_parentIndex != -1) { auto it = ret.ehMap.find(&fe.ehtab()[eh.m_parentIndex]); assert(it != end(ret.ehMap)); node->parent = it->second; it->second->children.emplace_back(std::move(node)); } else { func.exnNodes.emplace_back(std::move(node)); } } return ret; }
void add_frame_variables(php::Func& func, const FuncEmitter& fe) { for (auto& param : fe.params) { func.params.push_back( php::Param { param.defaultValue, NoBlockId, param.typeConstraint, param.userType, param.phpCode, param.userAttributes, param.builtinType, param.byRef, param.byRef, param.variadic } ); } func.locals.reserve(fe.numLocals()); for (LocalId id = 0; id < fe.numLocals(); ++id) { func.locals.push_back({nullptr, id, false}); } for (auto& kv : fe.localNameMap()) { func.locals[kv.second].name = kv.first; } func.numIters = fe.numIterators(); func.staticLocals.reserve(fe.staticVars.size()); for (auto& sv : fe.staticVars) { func.staticLocals.push_back( php::StaticLocalInfo { sv.name, sv.phpCode } ); } }
void UnitEmitter::initMain(int line1, int line2) { ASSERT(m_fes.size() == 0); StringData* name = StringData::GetStaticString(""); FuncEmitter* pseudomain = newFuncEmitter(name, false); Attr attrs = AttrMayUseVV; pseudomain->init(line1, line2, 0, attrs, false, name); }
void emit_init_func(FuncEmitter& fe, const php::Func& func) { fe.init( std::get<0>(func.srcInfo.loc), std::get<1>(func.srcInfo.loc), fe.ue().bcPos(), func.attrs, func.top, func.srcInfo.docComment ); }
void build_cfg(ParseUnitState& puState, php::Func& func, const FuncEmitter& fe) { auto const blockStarts = findBasicBlocks(fe); FTRACE(3, " blocks are at: {}\n", [&]() -> std::string { using namespace folly::gen; return from(blockStarts) | eachTo<std::string>() | unsplit<std::string>(" "); }() ); std::map<Offset,std::unique_ptr<php::Block>> blockMap; auto const bc = fe.ue().bc(); auto findBlock = [&] (Offset off) { auto& ptr = blockMap[off]; if (!ptr) { ptr = folly::make_unique<php::Block>(); ptr->id = func.nextBlockId++; ptr->section = php::Block::Section::Main; ptr->exnNode = nullptr; } return borrow(ptr); }; auto exnTreeInfo = build_exn_tree(fe, func, findBlock); for (auto it = begin(blockStarts); boost::next(it) != end(blockStarts); ++it) { auto const block = findBlock(*it); auto const bcStart = bc + *it; auto const bcStop = bc + *boost::next(it); if (auto const eh = findEH(fe.ehtab(), *it)) { auto it = exnTreeInfo.ehMap.find(eh); assert(it != end(exnTreeInfo.ehMap)); block->exnNode = it->second; add_factored_exits(*block, block->exnNode); } populate_block(puState, fe, func, *block, bcStart, bcStop, findBlock); } link_entry_points(func, fe, findBlock); find_fault_funclets(exnTreeInfo, func, blockStarts, findBlock); for (auto& kv : blockMap) { func.blocks.emplace_back(std::move(kv.second)); } }
void link_entry_points(php::Func& func, const FuncEmitter& fe, FindBlk findBlock) { func.dvEntries.resize(fe.params().size()); for (size_t i = 0, sz = fe.params().size(); i < sz; ++i) { if (fe.params()[i].hasDefaultValue()) { auto const dv = findBlock(fe.params()[i].funcletOff()); func.params[i].dvEntryPoint = dv; func.dvEntries[i] = dv; } } func.mainEntry = findBlock(fe.base()); }
ExnTreeInfo build_exn_tree(const FuncEmitter& fe, php::Func& func, FindBlock findBlock) { ExnTreeInfo ret; auto nextExnNode = uint32_t{0}; for (auto& eh : fe.ehtab()) { auto node = folly::make_unique<php::ExnNode>(); node->id = nextExnNode++; node->parent = nullptr; switch (eh.m_type) { case EHEnt::Type::Fault: { auto const fault = findBlock(eh.m_fault); ret.funcletNodes[fault].push_back(borrow(node)); ret.faultFuncletStarts.insert(eh.m_fault); node->info = php::FaultRegion { fault, eh.m_iterId, eh.m_itRef }; } break; case EHEnt::Type::Catch: { auto treg = php::TryRegion {}; for (auto& centry : eh.m_catches) { auto const catchBlk = findBlock(centry.second); treg.catches.emplace_back( fe.ue().lookupLitstr(centry.first), catchBlk ); } node->info = treg; } break; } ret.ehMap[&eh] = borrow(node); if (eh.m_parentIndex != -1) { auto it = ret.ehMap.find(&fe.ehtab()[eh.m_parentIndex]); assert(it != end(ret.ehMap)); node->parent = it->second; it->second->children.emplace_back(std::move(node)); } else { func.exnNodes.emplace_back(std::move(node)); } } ret.faultFuncletStarts.insert(fe.past()); return ret; }
std::unique_ptr<UnitEmitter> createFatalUnit(StringData* filename, const MD5& md5, FatalOp /*op*/, StringData* err) { auto ue = std::make_unique<UnitEmitter>(md5); ue->m_filepath = filename; ue->initMain(1, 1); ue->emitOp(OpString); ue->emitInt32(ue->mergeLitstr(err)); ue->emitOp(OpFatal); ue->emitByte(static_cast<uint8_t>(FatalOp::Runtime)); FuncEmitter* fe = ue->getMain(); fe->maxStackCells = 1; // XXX line numbers are bogus fe->finish(ue->bcPos(), false); ue->recordFunction(fe); return ue; }
void emit_eh_region(FuncEmitter& fe, borrowed_ptr<const EHRegion> region, const BlockInfo& blockInfo, ParentIndexMap& parentIndexMap) { // A region on a single empty block. if (region->start == region->past) return; auto& eh = fe.addEHEnt(); eh.m_base = region->start; eh.m_past = region->past; assert(eh.m_past >= eh.m_base); assert(eh.m_base != kInvalidOffset && eh.m_past != kInvalidOffset); if (region->parent) { auto parentIt = parentIndexMap.find(region->parent); assert(parentIt != end(parentIndexMap)); eh.m_parentIndex = parentIt->second; } else { eh.m_parentIndex = -1; } parentIndexMap[region] = fe.ehtab.size() - 1; match<void>( region->node->info, [&] (const php::TryRegion& tr) { eh.m_type = EHEnt::Type::Catch; for (auto& c : tr.catches) { eh.m_catches.emplace_back( fe.ue().mergeLitstr(c.first), blockInfo[c.second->id].offset ); } eh.m_fault = kInvalidOffset; eh.m_iterId = -1; eh.m_itRef = false; }, [&] (const php::FaultRegion& fr) { eh.m_type = EHEnt::Type::Fault; eh.m_fault = blockInfo[fr.faultEntry->id].offset; eh.m_iterId = fr.iterId; eh.m_itRef = fr.itRef; } ); }
void add_frame_variables(php::Func& func, const FuncEmitter& fe) { for (auto& param : fe.params) { func.params.push_back( php::Param { param.defaultValue, nullptr, param.typeConstraint, param.userType, param.phpCode, param.userAttributes, param.builtinType, param.byRef, param.variadic } ); } func.locals.resize(fe.numLocals()); for (size_t id = 0; id < func.locals.size(); ++id) { auto& loc = func.locals[id]; loc = folly::make_unique<php::Local>(); loc->id = id; loc->name = nullptr; } for (auto& kv : fe.localNameMap()) { func.locals[kv.second]->name = kv.first; } func.iters.resize(fe.numIterators()); for (uint32_t i = 0; i < func.iters.size(); ++i) { func.iters[i] = folly::make_unique<php::Iter>(); func.iters[i]->id = i; } func.staticLocals.reserve(fe.staticVars.size()); for (auto& sv : fe.staticVars) { func.staticLocals.push_back( php::StaticLocalInfo { sv.name, sv.phpCode } ); } }
void emit_finish_func(const php::Func& func, FuncEmitter& fe, const EmitBcInfo& info) { if (info.containsCalls) fe.containsCalls = true;; for (auto& fpi : info.fpiRegions) { auto& e = fe.addFPIEnt(); e.m_fpushOff = fpi.fpushOff; e.m_fcallOff = fpi.fcallOff; e.m_fpOff = fpi.fpDelta; } emit_locals_and_params(fe, func, info); emit_ehent_tree(fe, func, info); fe.userAttributes = func.userAttributes; fe.retUserType = func.returnUserType; fe.originalFilename = func.originalFilename; fe.isClosureBody = func.isClosureBody; fe.isAsync = func.isAsync; fe.isGenerator = func.isGenerator; fe.isPairGenerator = func.isPairGenerator; if (func.nativeInfo) { fe.returnType = func.nativeInfo->returnType; } fe.retTypeConstraint = func.retTypeConstraint; fe.maxStackCells = info.maxStackDepth + fe.numLocals() + fe.numIterators() * kNumIterCells + info.maxFpiDepth * kNumActRecCells; fe.finish(fe.ue().bcPos(), false /* load */); fe.ue().recordFunction(&fe); }
std::unique_ptr<php::Func> parse_func(ParseUnitState& puState, borrowed_ptr<php::Unit> unit, borrowed_ptr<php::Class> cls, const FuncEmitter& fe) { FTRACE(2, " func: {}\n", fe.name->data() && *fe.name->data() ? fe.name->data() : "pseudomain"); auto ret = folly::make_unique<php::Func>(); ret->name = fe.name; ret->srcInfo = php::SrcInfo { fe.getLocation(), fe.docComment }; ret->unit = unit; ret->cls = cls; ret->nextBlockId = 0; ret->attrs = static_cast<Attr>(fe.attrs & ~AttrNoOverride); ret->userAttributes = fe.userAttributes; ret->returnUserType = fe.retUserType; ret->retTypeConstraint = fe.retTypeConstraint; ret->originalFilename = fe.originalFilename; ret->top = fe.top; ret->isClosureBody = fe.isClosureBody; ret->isAsync = fe.isAsync; ret->isGenerator = fe.isGenerator; ret->isPairGenerator = fe.isPairGenerator; ret->isNative = fe.isNative; /* * Builtin functions get some extra information. The returnType flag is only * non-folly::none for these, but note that something may be a builtin and * still have a folly::none return type. */ if (fe.attrs & AttrBuiltin) { ret->nativeInfo = folly::make_unique<php::NativeInfo>(); ret->nativeInfo->returnType = fe.returnType; } add_frame_variables(*ret, fe); build_cfg(puState, *ret, fe); return ret; }
void FuncRepoProxy::GetFuncsStmt ::get(UnitEmitter& ue) { RepoTxn txn(m_repo); if (!prepared()) { std::stringstream ssSelect; ssSelect << "SELECT funcSn,preClassId,name,top,extraData " "FROM " << m_repo.table(m_repoId, "Func") << " WHERE unitSn == @unitSn ORDER BY funcSn ASC;"; txn.prepare(*this, ssSelect.str()); } RepoTxnQuery query(txn, *this); query.bindInt64("@unitSn", ue.m_sn); do { query.step(); if (query.row()) { int funcSn; /**/ query.getInt(0, funcSn); Id preClassId; /**/ query.getId(1, preClassId); StringData* name; /**/ query.getStaticString(2, name); bool top; /**/ query.getBool(3, top); BlobDecoder extraBlob = /**/ query.getBlob(4); FuncEmitter* fe; if (preClassId < 0) { fe = ue.newFuncEmitter(name); } else { PreClassEmitter* pce = ue.pce(preClassId); fe = ue.newMethodEmitter(name, pce); bool added UNUSED = pce->addMethod(fe); assert(added); } assert(fe->sn() == funcSn); fe->top = top; fe->serdeMetaData(extraBlob); if (!SystemLib::s_inited && !fe->isPseudoMain()) { assert(fe->attrs & AttrBuiltin); if (preClassId < 0) { assert(fe->attrs & AttrPersistent); assert(fe->attrs & AttrUnique); assert(fe->attrs & AttrSkipFrame); } } fe->setEHTabIsSorted(); fe->finish(fe->past, true); ue.recordFunction(fe); } } while (!query.done()); txn.commit(); }
void emit_finish_func(const php::Func& func, FuncEmitter& fe, const EmitBcInfo& info) { fe.setMaxStackCells( info.maxStackDepth + fe.numLocals() + info.maxFpiDepth * kNumActRecCells ); if (info.containsCalls) fe.setContainsCalls(); for (auto& fpi : info.fpiRegions) { auto& e = fe.addFPIEnt(); e.m_fpushOff = fpi.fpushOff; e.m_fcallOff = fpi.fcallOff; e.m_fpOff = fpi.fpDelta; } emit_locals_and_params(fe, func, info); emit_ehent_tree(fe, func, info); fe.setUserAttributes(func.userAttributes); fe.setReturnUserType(func.returnUserType); fe.setOriginalFilename(func.originalFilename); fe.setIsClosureBody(func.isClosureBody); fe.setIsGenerator(func.isGeneratorBody); fe.setIsGeneratorFromClosure(func.isGeneratorFromClosure); fe.setIsPairGenerator(func.isPairGenerator); fe.setGeneratorBodyName(func.generatorBodyName); fe.setIsAsync(func.isAsync); fe.finish(fe.ue().bcPos(), false /* load */); fe.ue().recordFunction(&fe); }
std::unique_ptr<php::Func> parse_func(ParseUnitState& puState, borrowed_ptr<php::Unit> unit, borrowed_ptr<php::Class> cls, const FuncEmitter& fe) { FTRACE(2, " func: {}\n", fe.name()->data() && *fe.name()->data() ? fe.name()->data() : "pseudomain"); auto ret = folly::make_unique<php::Func>(); ret->name = fe.name(); ret->srcInfo = php::SrcInfo { fe.getLocation(), fe.getDocComment() }; ret->unit = unit; ret->cls = cls; ret->nextBlockId = 0; ret->attrs = fe.attrs(); ret->userAttributes = fe.getUserAttributes(); ret->returnUserType = fe.returnUserType(); ret->originalFilename = fe.originalFilename(); ret->top = fe.top(); ret->isClosureBody = fe.isClosureBody(); ret->isGeneratorBody = fe.isGenerator(); ret->isGeneratorFromClosure = fe.isGeneratorFromClosure(); ret->isPairGenerator = fe.isPairGenerator(); ret->isAsync = fe.isAsync(); ret->innerGeneratorFunc = nullptr; ret->outerGeneratorFunc = nullptr; /* * Generators that aren't inside classes (includes generators from * closures) end up with inner generator bodies living as free * functions, not on a class. We track them here to link them after * we've finished parsing the whole unit. parse_methods handles the * within-class generator linking cases. */ if (auto const innerName = fe.getGeneratorBodyName()) { if (!ret->cls || ret->isClosureBody) { puState.generatorsToLink.emplace_back(borrow(ret), innerName); } } if (ret->isGeneratorBody && !cls) { always_assert(!puState.innerGenerators.count(ret->name)); puState.innerGenerators[ret->name] = borrow(ret); } /* * HNI-style native functions get some extra information. */ if (fe.isHNINative()) { ret->nativeInfo = folly::make_unique<php::NativeInfo>(); ret->nativeInfo->returnType = fe.getReturnType(); } add_frame_variables(*ret, fe); build_cfg(puState, *ret, fe); return ret; }
void populate_block(ParseUnitState& puState, const FuncEmitter& fe, php::Func& func, php::Block& blk, PC pc, PC const past, FindBlock findBlock) { auto const& ue = fe.ue(); auto decode_stringvec = [&] { auto const vecLen = decode<int32_t>(pc); CompactVector<LSString> keys; for (auto i = size_t{0}; i < vecLen; ++i) { keys.push_back(ue.lookupLitstr(decode<int32_t>(pc))); } return keys; }; auto decode_switch = [&] (PC opPC) { SwitchTab ret; auto const vecLen = decode<int32_t>(pc); for (int32_t i = 0; i < vecLen; ++i) { ret.push_back(findBlock( opPC + decode<Offset>(pc) - ue.bc() )->id); } return ret; }; auto decode_sswitch = [&] (PC opPC) { SSwitchTab ret; auto const vecLen = decode<int32_t>(pc); for (int32_t i = 0; i < vecLen - 1; ++i) { auto const id = decode<Id>(pc); auto const offset = decode<Offset>(pc); ret.emplace_back(ue.lookupLitstr(id), findBlock(opPC + offset - ue.bc())->id); } // Final case is the default, and must have a litstr id of -1. DEBUG_ONLY auto const defId = decode<Id>(pc); auto const defOff = decode<Offset>(pc); assert(defId == -1); ret.emplace_back(nullptr, findBlock(opPC + defOff - ue.bc())->id); return ret; }; auto decode_itertab = [&] { IterTab ret; auto const vecLen = decode<int32_t>(pc); for (int32_t i = 0; i < vecLen; ++i) { auto const kind = static_cast<IterKind>(decode<int32_t>(pc)); auto const id = decode<int32_t>(pc); ret.emplace_back(kind, id); } return ret; }; auto defcls = [&] (const Bytecode& b) { puState.defClsMap[b.DefCls.arg1] = &func; }; auto defclsnop = [&] (const Bytecode& b) { puState.defClsMap[b.DefClsNop.arg1] = &func; }; auto createcl = [&] (const Bytecode& b) { puState.createClMap[b.CreateCl.arg2].insert(&func); }; auto has_call_unpack = [&] { auto const fpi = Func::findFPI(&*fe.fpitab.begin(), &*fe.fpitab.end(), pc - ue.bc()); auto const op = peek_op(ue.bc() + fpi->m_fpiEndOff); return op == OpFCallArray || op == OpFCallUnpack; }; #define IMM_BLA(n) auto targets = decode_switch(opPC); #define IMM_SLA(n) auto targets = decode_sswitch(opPC); #define IMM_ILA(n) auto iterTab = decode_itertab(); #define IMM_IVA(n) auto arg##n = decode_iva(pc); #define IMM_I64A(n) auto arg##n = decode<int64_t>(pc); #define IMM_LA(n) auto loc##n = [&] { \ LocalId id = decode_iva(pc); \ always_assert(id < func.locals.size()); \ return id; \ }(); #define IMM_IA(n) auto iter##n = [&] { \ IterId id = decode_iva(pc); \ always_assert(id < func.numIters); \ return id; \ }(); #define IMM_DA(n) auto dbl##n = decode<double>(pc); #define IMM_SA(n) auto str##n = ue.lookupLitstr(decode<Id>(pc)); #define IMM_RATA(n) auto rat = decodeRAT(ue, pc); #define IMM_AA(n) auto arr##n = ue.lookupArray(decode<Id>(pc)); #define IMM_BA(n) assert(next == past); \ auto target = findBlock( \ opPC + decode<Offset>(pc) - ue.bc())->id; #define IMM_OA_IMPL(n) subop##n; decode(pc, subop##n); #define IMM_OA(type) type IMM_OA_IMPL #define IMM_VSA(n) auto keys = decode_stringvec(); #define IMM_KA(n) auto mkey = make_mkey(func, decode_member_key(pc, &ue)); #define IMM_LAR(n) auto locrange = [&] { \ auto const range = decodeLocalRange(pc); \ always_assert(range.first + range.restCount \ < func.locals.size()); \ return LocalRange { range.first, range.restCount }; \ }(); #define IMM_NA #define IMM_ONE(x) IMM_##x(1) #define IMM_TWO(x, y) IMM_##x(1) IMM_##y(2) #define IMM_THREE(x, y, z) IMM_TWO(x, y) IMM_##z(3) #define IMM_FOUR(x, y, z, n) IMM_THREE(x, y, z) IMM_##n(4) #define IMM_ARG(which, n) IMM_NAME_##which(n) #define IMM_ARG_NA #define IMM_ARG_ONE(x) IMM_ARG(x, 1) #define IMM_ARG_TWO(x, y) IMM_ARG(x, 1), IMM_ARG(y, 2) #define IMM_ARG_THREE(x, y, z) IMM_ARG(x, 1), IMM_ARG(y, 2), \ IMM_ARG(z, 3) #define IMM_ARG_FOUR(x, y, z, l) IMM_ARG(x, 1), IMM_ARG(y, 2), \ IMM_ARG(z, 3), IMM_ARG(l, 4) #define FLAGS_NF #define FLAGS_TF #define FLAGS_CF #define FLAGS_FF #define FLAGS_PF auto hu = has_call_unpack(); #define FLAGS_CF_TF #define FLAGS_CF_FF #define FLAGS_ARG_NF #define FLAGS_ARG_TF #define FLAGS_ARG_CF #define FLAGS_ARG_FF #define FLAGS_ARG_PF ,hu #define FLAGS_ARG_CF_TF #define FLAGS_ARG_CF_FF #define O(opcode, imms, inputs, outputs, flags) \ case Op::opcode: \ { \ auto b = Bytecode {}; \ b.op = Op::opcode; \ b.srcLoc = srcLocIx; \ IMM_##imms \ FLAGS_##flags \ new (&b.opcode) bc::opcode { IMM_ARG_##imms \ FLAGS_ARG_##flags }; \ if (Op::opcode == Op::DefCls) defcls(b); \ if (Op::opcode == Op::DefClsNop) defclsnop(b); \ if (Op::opcode == Op::CreateCl) createcl(b); \ blk.hhbcs.push_back(std::move(b)); \ assert(pc == next); \ } \ break; assert(pc != past); do { auto const opPC = pc; auto const next = pc + instrLen(opPC); assert(next <= past); auto const srcLoc = match<php::SrcLoc>( puState.srcLocInfo, [&] (const SourceLocTable& tab) { SourceLoc sloc; if (getSourceLoc(tab, opPC - ue.bc(), sloc)) { return php::SrcLoc { { static_cast<uint32_t>(sloc.line0), static_cast<uint32_t>(sloc.char0) }, { static_cast<uint32_t>(sloc.line1), static_cast<uint32_t>(sloc.char1) } }; } return php::SrcLoc{}; }, [&] (const LineTable& tab) { auto const line = getLineNumber(tab, opPC - ue.bc()); if (line != -1) { return php::SrcLoc { { static_cast<uint32_t>(line), 0 }, { static_cast<uint32_t>(line), 0 }, }; }; return php::SrcLoc{}; } ); auto const srcLocIx = puState.srcLocs.emplace( srcLoc, puState.srcLocs.size()).first->second; auto const op = decode_op(pc); switch (op) { OPCODES } if (next == past) { if (instrAllowsFallThru(op)) { blk.fallthrough = findBlock(next - ue.bc())->id; } } pc = next; } while (pc != past); #undef O #undef FLAGS_NF #undef FLAGS_TF #undef FLAGS_CF #undef FLAGS_FF #undef FLAGS_PF #undef FLAGS_CF_TF #undef FLAGS_CF_FF #undef FLAGS_ARG_NF #undef FLAGS_ARG_TF #undef FLAGS_ARG_CF #undef FLAGS_ARG_FF #undef FLAGS_ARG_PF #undef FLAGS_ARG_CF_TF #undef FLAGS_ARG_CF_FF #undef IMM_BLA #undef IMM_SLA #undef IMM_ILA #undef IMM_IVA #undef IMM_I64A #undef IMM_LA #undef IMM_IA #undef IMM_DA #undef IMM_SA #undef IMM_RATA #undef IMM_AA #undef IMM_BA #undef IMM_OA_IMPL #undef IMM_OA #undef IMM_VSA #undef IMM_LAR #undef IMM_NA #undef IMM_ONE #undef IMM_TWO #undef IMM_THREE #undef IMM_FOUR #undef IMM_ARG #undef IMM_ARG_NA #undef IMM_ARG_ONE #undef IMM_ARG_TWO #undef IMM_ARG_THREE #undef IMM_ARG_FOUR /* * If a block ends with an unconditional jump, change it to a * fallthrough edge. * * Just convert the opcode to a Nop, because this could create an * empty block and we have an invariant that no blocks are empty. */ auto make_fallthrough = [&] { blk.fallthrough = blk.hhbcs.back().Jmp.target; blk.hhbcs.back() = bc_with_loc(blk.hhbcs.back().srcLoc, bc::Nop{}); }; switch (blk.hhbcs.back().op) { case Op::Jmp: make_fallthrough(); break; case Op::JmpNS: make_fallthrough(); blk.fallthroughNS = true; break; default: break; } }
void populate_block(ParseUnitState& puState, const FuncEmitter& fe, php::Func& func, php::Block& blk, PC pc, PC const past, FindBlock findBlock) { auto const& ue = fe.ue(); auto decode_minstr = [&] { auto const immVec = ImmVector::createFromStream(pc); pc += immVec.size() + sizeof(int32_t) + sizeof(int32_t); auto ret = MVector {}; auto vec = immVec.vec(); ret.lcode = static_cast<LocationCode>(*vec++); if (numLocationCodeImms(ret.lcode)) { assert(numLocationCodeImms(ret.lcode) == 1); ret.locBase = borrow(func.locals[decodeVariableSizeImm(&vec)]); } while (vec < pc) { auto elm = MElem {}; elm.mcode = static_cast<MemberCode>(*vec++); switch (memberCodeImmType(elm.mcode)) { case MCodeImm::None: break; case MCodeImm::Local: elm.immLoc = borrow(func.locals[decodeMemberCodeImm(&vec, elm.mcode)]); break; case MCodeImm::String: elm.immStr = ue.lookupLitstr(decodeMemberCodeImm(&vec, elm.mcode)); break; case MCodeImm::Int: elm.immInt = decodeMemberCodeImm(&vec, elm.mcode); break; } ret.mcodes.push_back(elm); } assert(vec == pc); return ret; }; auto decode_stringvec = [&] { auto const vecLen = decode<int32_t>(pc); std::vector<SString> keys; for (auto i = size_t{0}; i < vecLen; ++i) { keys.push_back(ue.lookupLitstr(decode<int32_t>(pc))); } return keys; }; auto decode_switch = [&] (PC opPC) { SwitchTab ret; auto const vecLen = decode<int32_t>(pc); for (int32_t i = 0; i < vecLen; ++i) { ret.push_back(findBlock( opPC + decode<Offset>(pc) - ue.bc() )); } return ret; }; auto decode_sswitch = [&] (PC opPC) { SSwitchTab ret; auto const vecLen = decode<int32_t>(pc); for (int32_t i = 0; i < vecLen - 1; ++i) { auto const id = decode<Id>(pc); auto const offset = decode<Offset>(pc); ret.emplace_back( ue.lookupLitstr(id), findBlock(opPC + offset - ue.bc()) ); } // Final case is the default, and must have a litstr id of -1. DEBUG_ONLY auto const defId = decode<Id>(pc); auto const defOff = decode<Offset>(pc); assert(defId == -1); ret.emplace_back(nullptr, findBlock(opPC + defOff - ue.bc())); return ret; }; auto decode_itertab = [&] { IterTab ret; auto const vecLen = decode<int32_t>(pc); for (int32_t i = 0; i < vecLen; ++i) { auto const kind = static_cast<IterKind>(decode<int32_t>(pc)); auto const id = decode<int32_t>(pc); ret.emplace_back(kind, borrow(func.iters[id])); } return ret; }; auto defcls = [&] (const Bytecode& b) { puState.defClsMap[b.DefCls.arg1] = &func; }; auto nopdefcls = [&] (const Bytecode& b) { puState.defClsMap[b.NopDefCls.arg1] = &func; }; auto createcl = [&] (const Bytecode& b) { puState.createClMap[b.CreateCl.str2].insert(&func); }; #define IMM_MA(n) auto mvec = decode_minstr(); #define IMM_BLA(n) auto targets = decode_switch(opPC); #define IMM_SLA(n) auto targets = decode_sswitch(opPC); #define IMM_ILA(n) auto iterTab = decode_itertab(); #define IMM_IVA(n) auto arg##n = decodeVariableSizeImm(&pc); #define IMM_I64A(n) auto arg##n = decode<int64_t>(pc); #define IMM_LA(n) auto loc##n = [&] { \ auto id = decodeVariableSizeImm(&pc); \ always_assert(id < func.locals.size()); \ return borrow(func.locals[id]); \ }(); #define IMM_IA(n) auto iter##n = [&] { \ auto id = decodeVariableSizeImm(&pc); \ always_assert(id < func.iters.size()); \ return borrow(func.iters[id]); \ }(); #define IMM_DA(n) auto dbl##n = decode<double>(pc); #define IMM_SA(n) auto str##n = ue.lookupLitstr(decode<Id>(pc)); #define IMM_AA(n) auto arr##n = ue.lookupArray(decode<Id>(pc)); #define IMM_BA(n) assert(next == past); \ auto target = findBlock( \ opPC + decode<Offset>(pc) - ue.bc()); #define IMM_OA_IMPL(n) decode<uint8_t>(pc); #define IMM_OA(type) auto subop = (type)IMM_OA_IMPL #define IMM_VSA(n) auto keys = decode_stringvec(); #define IMM_NA #define IMM_ONE(x) IMM_##x(1) #define IMM_TWO(x, y) IMM_##x(1) IMM_##y(2) #define IMM_THREE(x, y, z) IMM_TWO(x, y) IMM_##z(3) #define IMM_FOUR(x, y, z, n) IMM_THREE(x, y, z) IMM_##n(4) #define IMM_ARG(which, n) IMM_NAME_##which(n) #define IMM_ARG_NA #define IMM_ARG_ONE(x) IMM_ARG(x, 1) #define IMM_ARG_TWO(x, y) IMM_ARG(x, 1), IMM_ARG(y, 2) #define IMM_ARG_THREE(x, y, z) IMM_ARG(x, 1), IMM_ARG(y, 2), \ IMM_ARG(z, 3) #define IMM_ARG_FOUR(x, y, z, l) IMM_ARG(x, 1), IMM_ARG(y, 2), \ IMM_ARG(z, 3), IMM_ARG(l, 4) #define O(opcode, imms, inputs, outputs, flags) \ case Op::opcode: \ { \ ++pc; \ auto b = Bytecode {}; \ b.op = Op::opcode; \ b.srcLoc = srcLoc; \ IMM_##imms \ new (&b.opcode) bc::opcode { IMM_ARG_##imms }; \ if (Op::opcode == Op::DefCls) defcls(b); \ if (Op::opcode == Op::NopDefCls) nopdefcls(b); \ if (Op::opcode == Op::CreateCl) createcl(b); \ blk.hhbcs.push_back(std::move(b)); \ assert(pc == next); \ } \ break; assert(pc != past); do { auto const opPC = pc; auto const pop = reinterpret_cast<const Op*>(pc); auto const next = pc + instrLen(pop); assert(next <= past); auto const srcLoc = [&] { SourceLoc sloc; if (getSourceLoc(puState.srcLocTable, opPC - ue.bc(), sloc)) { return php::SrcLoc { { static_cast<uint32_t>(sloc.line0), static_cast<uint32_t>(sloc.char0) }, { static_cast<uint32_t>(sloc.line1), static_cast<uint32_t>(sloc.char1) } }; } return php::SrcLoc{}; }(); switch (*pop) { OPCODES } if (next == past) { if (instrAllowsFallThru(*pop)) { blk.fallthrough = findBlock(next - ue.bc()); } } pc = next; } while (pc != past); #undef O #undef IMM_MA #undef IMM_BLA #undef IMM_SLA #undef IMM_ILA #undef IMM_IVA #undef IMM_I64A #undef IMM_LA #undef IMM_IA #undef IMM_DA #undef IMM_SA #undef IMM_AA #undef IMM_BA #undef IMM_OA_IMPL #undef IMM_OA #undef IMM_VSA #undef IMM_NA #undef IMM_ONE #undef IMM_TWO #undef IMM_THREE #undef IMM_FOUR #undef IMM_ARG #undef IMM_ARG_NA #undef IMM_ARG_ONE #undef IMM_ARG_TWO #undef IMM_ARG_THREE #undef IMM_ARG_FOUR /* * If a block ends with an unconditional jump, change it to a * fallthrough edge. * * Just convert the opcode to a Nop, because this could create an * empty block and we have an invariant that no blocks are empty. */ auto make_fallthrough = [&] { blk.fallthrough = blk.hhbcs.back().Jmp.target; blk.hhbcs.back() = bc_with_loc(blk.hhbcs.back().srcLoc, bc::Nop{}); }; switch (blk.hhbcs.back().op) { case Op::Jmp: make_fallthrough(); break; case Op::JmpNS: make_fallthrough(); blk.fallthroughNS = true; break; default: break; } }
std::unique_ptr<php::Func> parse_func(ParseUnitState& puState, borrowed_ptr<php::Unit> unit, borrowed_ptr<php::Class> cls, const FuncEmitter& fe) { FTRACE(2, " func: {}\n", fe.name->data() && *fe.name->data() ? fe.name->data() : "pseudomain"); auto ret = folly::make_unique<php::Func>(); ret->name = fe.name; ret->srcInfo = php::SrcInfo { fe.getLocation(), fe.docComment }; ret->unit = unit; ret->cls = cls; ret->attrs = static_cast<Attr>(fe.attrs & ~AttrNoOverride); ret->userAttributes = fe.userAttributes; ret->returnUserType = fe.retUserType; ret->retTypeConstraint = fe.retTypeConstraint; ret->originalFilename = fe.originalFilename; ret->top = fe.top; ret->isClosureBody = fe.isClosureBody; ret->isAsync = fe.isAsync; ret->isGenerator = fe.isGenerator; ret->isPairGenerator = fe.isPairGenerator; ret->isMemoizeWrapper = fe.isMemoizeWrapper; add_frame_variables(*ret, fe); /* * Builtin functions get some extra information. The returnType flag is only * non-folly::none for these, but note that something may be a builtin and * still have a folly::none return type. */ if (fe.isNative) { auto const f = [&] () -> HPHP::Func* { if (ret->cls) { auto const cls = Unit::lookupClass(ret->cls->name); return cls ? cls->lookupMethod(ret->name) : nullptr; } else { return Unit::lookupFunc(ret->name); } }(); ret->nativeInfo = folly::make_unique<php::NativeInfo>(); ret->nativeInfo->returnType = fe.hniReturnType; ret->nativeInfo->dynCallWrapperId = fe.dynCallWrapperId; if (f && !ret->cls && ret->params.size()) { // There are a handful of functions whose first parameter is by // ref, but which don't require a reference. There is also // array_multisort, which has this property for all its // parameters; but that if (ret->params[0].byRef && !f->mustBeRef(0)) { ret->params[0].mustBeRef = false; } } if (!f || !f->nativeFuncPtr() || (f->userAttributes().count( LowStringPtr(s_attr_Deprecated.get())))) { ret->attrs |= AttrNoFCallBuiltin; } } build_cfg(puState, *ret, fe); return ret; }
std::unique_ptr<php::Func> parse_func(ParseUnitState& puState, borrowed_ptr<php::Unit> unit, borrowed_ptr<php::Class> cls, const FuncEmitter& fe) { FTRACE(2, " func: {}\n", fe.name()->data() && *fe.name()->data() ? fe.name()->data() : "pseudomain"); auto ret = folly::make_unique<php::Func>(); ret->name = fe.name(); ret->srcInfo = php::SrcInfo { fe.getLocation(), fe.getDocComment() }; ret->unit = unit; ret->cls = cls; // Note: probably need to clear AttrLeaf here eventually. ret->attrs = fe.attrs(); ret->userAttributes = fe.getUserAttributes(); ret->userRetTypeConstraint = fe.returnTypeConstraint(); ret->originalFilename = fe.originalFilename(); ret->top = fe.top(); ret->isClosureBody = fe.isClosureBody(); ret->isGeneratorBody = fe.isGenerator(); ret->isGeneratorFromClosure = fe.isGeneratorFromClosure(); ret->isPairGenerator = fe.isPairGenerator(); ret->hasGeneratorAsBody = fe.hasGeneratorAsBody(); ret->nextBlockId = 0; add_frame_variables(*ret, fe); build_cfg(puState, *ret, fe); return ret; }
std::unique_ptr<php::Func> parse_func(ParseUnitState& puState, borrowed_ptr<php::Unit> unit, borrowed_ptr<php::Class> cls, const FuncEmitter& fe) { FTRACE(2, " func: {}\n", fe.name()->data() && *fe.name()->data() ? fe.name()->data() : "pseudomain"); auto ret = folly::make_unique<php::Func>(); ret->name = fe.name(); ret->srcInfo = php::SrcInfo { fe.getLocation(), fe.getDocComment() }; ret->unit = unit; ret->cls = cls; ret->nextBlockId = 0; ret->attrs = fe.attrs(); ret->userAttributes = fe.getUserAttributes(); ret->returnUserType = fe.returnUserType(); ret->retTypeConstraint = fe.returnTypeConstraint(); ret->originalFilename = fe.originalFilename(); ret->top = fe.top(); ret->isClosureBody = fe.isClosureBody(); ret->isAsync = fe.isAsync(); ret->isGenerator = fe.isGenerator(); ret->isPairGenerator = fe.isPairGenerator(); /* * HNI-style native functions get some extra information. */ if (fe.isHNINative()) { ret->nativeInfo = folly::make_unique<php::NativeInfo>(); ret->nativeInfo->returnType = fe.getReturnType(); } add_frame_variables(*ret, fe); build_cfg(puState, *ret, fe); return ret; }
/* * Traverse the actual block layout, and find out the intervals for * each exception region in the tree. * * The basic idea here is that we haven't constrained block layout * based on the exception tree, but adjacent blocks are still * reasonably likely to have the same ExnNode. Try to coalesce the EH * regions we create for in those cases. */ void emit_ehent_tree(FuncEmitter& fe, const php::Func& func, const EmitBcInfo& info) { std::map< borrowed_ptr<const php::ExnNode>, std::vector<std::unique_ptr<EHRegion>> > exnMap; /* * While walking over the blocks in layout order, we track the set * of "active" exnNodes. This are a list of exnNodes that inherit * from each other. When a new active node is pushed, begin an * EHEnt, and when it's popped, it's done. */ std::vector<borrowed_ptr<const php::ExnNode>> activeList; auto pop_active = [&] (Offset past) { auto p = activeList.back(); activeList.pop_back(); exnMap[p].back()->past = past; }; auto push_active = [&] (const php::ExnNode* p, Offset start) { auto const parent = activeList.empty() ? nullptr : borrow(exnMap[activeList.back()].back()); exnMap[p].push_back( folly::make_unique<EHRegion>( EHRegion { p, parent, start, kInvalidOffset } ) ); activeList.push_back(p); }; /* * Walk over the blocks, and compare the new block's exnNode path to * the active one. Find the least common ancestor of the two paths, * then modify the active list by popping and then pushing nodes to * set it to the new block's path. */ for (auto& b : info.blockOrder) { auto const offset = info.blockInfo[b->id].offset; if (!b->exnNode) { while (!activeList.empty()) pop_active(offset); continue; } std::vector<borrowed_ptr<const php::ExnNode>> current; exn_path(current, b->exnNode); auto const prefix = shared_prefix(current, activeList); for (size_t i = prefix, sz = activeList.size(); i < sz; ++i) { pop_active(offset); } for (size_t i = prefix, sz = current.size(); i < sz; ++i) { push_active(current[i], offset); } if (debug && !activeList.empty()) { current.clear(); exn_path(current, activeList.back()); assert(current == activeList); } } while (!activeList.empty()) { pop_active(info.blockInfo[info.blockOrder.back()->id].past); } /* * We've created all our regions, but we need to sort them instead * of trying to get the UnitEmitter to do it. * * The UnitEmitter expects EH regions that look a certain way * (basically the way emitter.cpp likes them). There are some rules * about the order it needs to have at runtime, which we set up * here. * * Essentially, an entry a is less than an entry b iff: * * - a starts before b * - a starts at the same place, but encloses b entirely * - a has the same extents as b, but is a parent of b */ std::vector<borrowed_ptr<EHRegion>> regions; for (auto& mapEnt : exnMap) { for (auto& region : mapEnt.second) { regions.push_back(borrow(region)); } } std::sort( begin(regions), end(regions), [&] (borrowed_ptr<const EHRegion> a, borrowed_ptr<const EHRegion> b) { if (a == b) return false; if (a->start == b->start) { if (a->past == b->past) { // When regions exactly overlap, the parent is less than the // child. for (auto p = b->parent; p != nullptr; p = p->parent) { if (p == a) return true; } // If a is not a parent of b, and they have the same region; // then b better be a parent of a. if (debug) { auto p = a->parent; for (; p != b && p != nullptr; p = p->parent) continue; assert(p == b); } return false; } return a->past > b->past; } return a->start < b->start; } ); std::map<borrowed_ptr<const EHRegion>,uint32_t> parentIndexMap; for (auto& r : regions) { emit_eh_region(fe, r, info.blockInfo, parentIndexMap); } fe.setEHTabIsSorted(); }