void do_optimize(const Index& index, FuncAnalysis ainfo) { FTRACE(2, "{:-^70}\n", "Optimize Func"); visit_blocks("first pass", index, ainfo, first_pass); /* * Note, it's useful to do dead block removal before DCE, so it can * remove code relating to the branch to the dead block. */ remove_unreachable_blocks(index, ainfo); if (options.LocalDCE) { visit_blocks("local DCE", index, ainfo, local_dce); } if (options.GlobalDCE) { global_dce(index, ainfo); assert(check(*ainfo.ctx.func)); /* * Global DCE can change types of locals across blocks. See * dce.cpp for an explanation. * * We need to perform a final type analysis before we do anything * else. */ ainfo = analyze_func(index, ainfo.ctx); } if (options.InsertAssertions) { visit_blocks("insert assertions", index, ainfo, insert_assertions); } }
file_contents parse(parse_source source) { file_contents contents; contents.imports = ll_new(); contents.enums = ll_new(); contents.unions = ll_new(); contents.structs = ll_new(); contents.functions = ll_new(); optional op = get_token(&source); while(op_has(op)) { parse_token current = *((parse_token*)op_get(op)); if(equals(current.data, new_slice("struct"))) { struct_declaration *dec = analyze_struct(&source); ll_add_last(contents.structs, dec); } else if(equals(current.data, new_slice("union"))) { struct_declaration *dec = analyze_struct(&source); ll_add_last(contents.unions, dec); } else if(equals(current.data, new_slice("func"))) { func_declaration *func = analyze_func(&source); ll_add_last(contents.functions, func); } else if(equals(current.data, new_slice("import"))) { import_declaration *imp = parse_import(&source); ll_add_last(contents.imports, imp); } else if(equals(current.data, new_slice("enum"))) { enum_declaration *enm = parse_enum(&source); ll_add_last(contents.enums, enm); }//TODO: Throw an error for an unexpected token op = get_token(&source); } return contents; }
void do_optimize(const Index& index, const FuncAnalysis& ainfo) { FTRACE(2, "{:-^70}\n", "Optimize Func"); visit_blocks("first pass", index, ainfo, first_pass); /* * Note, it's useful to do dead block removal before DCE, so it can * remove code relating to the branch to the dead block. * * If we didn't remove jumps to dead blocks, we replace all * supposedly unreachable blocks with fatal instructions. * * TODO(#3751005): removedeadblocks doesn't remove the contents of * the blocks which it should. */ if (!options.RemoveDeadBlocks) { for (auto& blk : ainfo.rpoBlocks) { auto const& state = ainfo.bdata[blk->id].stateIn; if (state.initialized) continue; auto const srcLoc = blk->hhbcs.front().srcLoc; blk->hhbcs = { bc_with_loc(srcLoc, bc::String { s_unreachable.get() }), bc_with_loc(srcLoc, bc::Fatal { FatalOp::Runtime }) }; blk->fallthrough = nullptr; } } if (options.LocalDCE) { visit_blocks("local DCE", index, ainfo, local_dce); } if (options.GlobalDCE) { global_dce(index, ainfo); assert(check(*ainfo.ctx.func)); } if (options.InsertAssertions) { /* * Global DCE can change types of locals across blocks. See * dce.cpp for an explanation. * * We need to perform a final type analysis before we insert type * assertions. */ auto const ainfo2 = analyze_func(index, ainfo.ctx); visit_blocks("insert assertions", index, ainfo2, insert_assertions); } }
void collect_func(Stats& stats, const Index& index, php::Func& func) { if (!func.cls) { ++stats.totalFunctions; if (func.attrs & AttrPersistent) { ++stats.persistentFunctions; } if (func.attrs & AttrUnique) { ++stats.uniqueFunctions; } } auto const ty = index.lookup_return_type_raw(&func); add_type(stats.returns, ty); for (auto& blk : func.blocks) { if (blk->id == NoBlockId) continue; for (auto& bc : blk->hhbcs) { collect_simple(stats, bc); } } if (!options.extendedStats) return; auto const ctx = Context { func.unit, &func, func.cls }; auto const fa = analyze_func(index, ctx); { Trace::Bump bumper{Trace::hhbbc, kStatsBump}; for (auto& blk : func.blocks) { if (blk->id == NoBlockId) continue; auto state = fa.bdata[blk->id].stateIn; if (!state.initialized) continue; CollectedInfo collect { index, ctx, nullptr, nullptr }; Interp interp { index, ctx, collect, borrow(blk), state }; for (auto& bc : blk->hhbcs) { auto noop = [] (BlockId, const State&) {}; auto flags = StepFlags {}; ISS env { interp, flags, noop }; StatsSS sss { env, stats }; dispatch(sss, bc); } } } }
void optimize(Index& index, php::Program& program) { assert(check(program)); trace_time tracer("optimize"); SCOPE_EXIT { state_after("optimize", program); }; // Counters, just for debug printing. std::atomic<uint32_t> total_funcs{0}; auto round = uint32_t{0}; /* * Algorithm: * * Start by running an analyze pass on every function. During * analysis, information about functions or classes will be * requested from the Index, which initially won't really know much, * but will record a dependency. This part is done in parallel: no * passes are mutating anything, just reading from the Index. * * After a pass, we do a single-threaded "update" step to prepare * for the next pass: for each function that was analyzed, note the * facts we learned that may aid analyzing other functions in the * program, and register them in the index. At this point, if any * of these facts are more useful than they used to be, add all the * Contexts that had a dependency on the new information to the work * list again, in case they can do better based on the new fact. * * Repeat until the work list is empty. */ auto work = initial_work(program); while (!work.empty()) { auto const results = [&] { trace_time trace( "analyzing", folly::format("round {} -- {} work items", round, work.size()).str() ); return parallel_map( work, [&] (const Context& ctx) -> folly::Optional<FuncAnalysis> { total_funcs.fetch_add(1, std::memory_order_relaxed); return analyze_func(index, ctx); } ); }(); work.clear(); ++round; trace_time update_time("updating"); std::set<Context> revisit; for (auto i = size_t{0}; i < results.size(); ++i) { auto& result = *results[i]; assert(result.ctx.func == work[i].func); assert(result.ctx.cls == work[i].cls); assert(result.ctx.unit == work[i].unit); auto deps = index.refine_return_type( result.ctx.func, result.inferredReturn ); for (auto& d : deps) revisit.insert(d); } std::copy(begin(revisit), end(revisit), std::back_inserter(work)); } if (Trace::moduleEnabledRelease(Trace::hhbbc_time, 1)) { Trace::traceRelease("total function visits %u\n", total_funcs.load()); } /* * Finally, use the results of all these iterations to perform * optimization. This reanalyzes every function using our * now-very-updated Index, and then runs optimize_func with the * results. * * We do this in parallel: all the shared information is queried out * of the index, and each thread is allowed to modify the bytecode * for the function it is looking at. * * NOTE: currently they can't modify anything other than the * bytecode/Blocks, because other threads may be doing unlocked * queries to php::Func and php::Class structures. */ trace_time final_pass("final pass"); work = initial_work(program); parallel_for_each( initial_work(program), [&] (Context ctx) { optimize_func(index, analyze_func(index, ctx)); } ); }