void DexInstruction::verify_encoding() const { auto test = m_count ? new DexInstruction(opcode()) : new DexInstruction(opcode(), 0); if (dests_size()) { test->set_dest(dest()); } for (unsigned i = 0; i < srcs_size(); i++) { test->set_src(i, src(i)); } if (has_range_base()) test->set_range_base(range_base()); if (has_range_size()) test->set_range_size(range_size()); if (has_arg_word_count()) test->set_arg_word_count(arg_word_count()); if (has_literal()) test->set_literal(literal()); if (has_offset()) test->set_offset(offset()); assert_log(m_opcode == test->m_opcode, "%x %x\n", m_opcode, test->m_opcode); for (unsigned i = 0; i < m_count; i++) { assert_log(m_arg[i] == test->m_arg[i], "(%x %x) (%x %x)", m_opcode, m_arg[i], test->m_opcode, test->m_arg[i]); } delete test; }
SSATmp* TraceBuilder::preOptimizeAssertLoc(IRInstruction* inst) { auto const locId = inst->extra<AssertLoc>()->locId; auto const prevType = localType(locId, DataTypeGeneric); auto const typeParam = inst->typeParam(); if (prevType.not(typeParam)) { /* Task #2553746 * This is triggering for a case where the tracked state says the local is * InitNull but the AssertLoc says it's Str. */ static auto const error = makeStaticString("Internal error: static analysis was " "wrong about a local variable's type."); auto* errorInst = m_unit.gen(RaiseError, inst->marker(), cns(error)); inst->become(m_unit, errorInst); // It's not a disaster to generate this in unreachable code for // now. t2590033. if (false) { assert_log(false, [&]{ return folly::format("\npreOptimizeAssertLoc: prevType: {} " "typeParam: {}\nin instr: {}\nin trace: {}\n", prevType.toString(), typeParam.toString(), inst->toString(), m_unit.main()->toString()).str(); }); } } else if (shouldElideAssertType(prevType, typeParam, nullptr)) { // The type we're asserting is worse than the last known type. return inst->src(0); } return nullptr; }
SSATmp* TraceBuilder::preOptimizeAssertLoc(IRInstruction* inst) { auto const locId = inst->extra<AssertLoc>()->locId; auto const prevType = localType(locId, DataTypeGeneric); auto const typeParam = inst->typeParam(); if (!prevType.equals(Type::None) && !typeParam.strictSubtypeOf(prevType)) { if (!prevType.subtypeOf(typeParam)) { /* Task #2553746 * This is triggering for a case where the tracked state says the local is * InitNull but the AssertLoc says it's Str. */ static auto const error = StringData::GetStaticString("Internal error: static analysis was " "wrong about a local variable's type."); auto* errorInst = m_irFactory.gen(RaiseError, inst->marker(), cns(error)); inst->become(&m_irFactory, errorInst); // It's not a disaster to generate this in unreachable code for // now. t2590033. if (false) { assert_log(false, [&]{ IRTrace& mainTrace = trace()->isMain() ? *trace() : *(trace()->main()); return folly::format("\npreOptimizeAssertLoc: prevType: {} " "typeParam: {}\nin instr: {}\nin trace: {}\n", prevType.toString(), typeParam.toString(), inst->toString(), mainTrace.toString()).str(); }); } } else { inst->convertToNop(); } } return nullptr; }
void ConfigFiles::load_method_to_weight() { std::ifstream infile(m_profiled_methods_filename.c_str()); assert_log(infile, "Can't open method profile file: %s\n", m_profiled_methods_filename.c_str()); std::string deobfuscated_name; unsigned int weight; TRACE(CUSTOMSORT, 2, "Setting sort start file %s\n", m_profiled_methods_filename.c_str()); unsigned int count = 0; while (infile >> deobfuscated_name >> weight) { m_method_to_weight[deobfuscated_name] = weight; count++; } assert_log(count > 0, "Method profile file %s didn't contain valid entries\n", m_profiled_methods_filename.c_str()); TRACE(CUSTOMSORT, 2, "Preset sort weight count=%d\n", count); }
/* * Build the CFG, then the dominator tree, then use it to validate SSA. * 1. Each src must be defined by some other instruction, and each dst must * be defined by the current instruction. * 2. Each src must be defined earlier in the same block or in a dominator. * 3. Each dst must not be previously defined. * 4. Treat tmps defined by DefConst as always defined. * 5. Each predecessor of a reachable block must be reachable (deleted * blocks must not have out-edges to reachable blocks). * 6. The entry block must not have any predecessors. */ bool checkCfg(const IRUnit& unit) { auto const blocksIds = rpoSortCfgWithIds(unit); auto const& blocks = blocksIds.blocks; jit::hash_set<const Edge*> edges; // Entry block can't have predecessors. assert(unit.entry()->numPreds() == 0); // Check valid successor/predecessor edges. for (Block* b : blocks) { auto checkEdge = [&] (const Edge* e) { assert(e->from() == b); edges.insert(e); for (auto& p : e->to()->preds()) if (&p == e) return; assert(false); // did not find edge. }; checkBlock(b); if (auto *e = b->nextEdge()) checkEdge(e); if (auto *e = b->takenEdge()) checkEdge(e); } for (Block* b : blocks) { for (DEBUG_ONLY auto const &e : b->preds()) { assert(&e == e.inst()->takenEdge() || &e == e.inst()->nextEdge()); assert(e.to() == b); } } // visit dom tree in preorder, checking all tmps auto const children = findDomChildren(unit, blocksIds); StateVector<SSATmp, bool> defined0(unit, false); forPreorderDoms(blocks.front(), children, defined0, [] (Block* block, StateVector<SSATmp, bool>& defined) { for (IRInstruction& inst : *block) { for (DEBUG_ONLY SSATmp* src : inst.srcs()) { assert(src->inst() != &inst); assert_log(src->inst()->op() == DefConst || defined[src], [&]{ return folly::format( "src '{}' in '{}' came from '{}', which is not a " "DefConst and is not defined at this use site", src->toString(), inst.toString(), src->inst()->toString()).str(); }); } for (SSATmp& dst : inst.dsts()) { assert(dst.inst() == &inst && inst.op() != DefConst); assert(!defined[dst]); defined[dst] = true; } } }); return true; }
/* * Build the CFG, then the dominator tree, then use it to validate SSA. * 1. Each src must be defined by some other instruction, and each dst must * be defined by the current instruction. * 2. Each src must be defined earlier in the same block or in a dominator. * 3. Each dst must not be previously defined. * 4. Treat tmps defined by DefConst as always defined. * 5. Each predecessor of a reachable block must be reachable (deleted * blocks must not have out-edges to reachable blocks). */ bool checkCfg(IRTrace* trace, const IRFactory& factory) { forEachTraceBlock(trace, checkBlock); // Check valid successor/predecessor edges. auto const blocks = rpoSortCfg(trace, factory); std::unordered_set<const Edge*> edges; for (Block* b : blocks) { auto checkEdge = [&] (const Edge* e) { assert(e->from() == b); edges.insert(e); for (auto& p : e->to()->preds()) if (&p == e) return; assert(false); // did not find edge. }; if (auto *e = nextEdge(b)) checkEdge(e); if (auto *e = takenEdge(b)) checkEdge(e); } for (Block* b : blocks) { for (DEBUG_ONLY auto const &e : b->preds()) { assert(&e == takenEdge(e.from()) || &e == nextEdge(e.from())); assert(e.to() == b); } } checkCatchTraces(trace, factory); // visit dom tree in preorder, checking all tmps auto const children = findDomChildren(blocks); StateVector<SSATmp, bool> defined0(&factory, false); forPreorderDoms(blocks.front(), children, defined0, [] (Block* block, StateVector<SSATmp, bool>& defined) { for (IRInstruction& inst : *block) { for (DEBUG_ONLY SSATmp* src : inst.srcs()) { assert(src->inst() != &inst); assert_log(src->inst()->op() == DefConst || defined[src], [&]{ return folly::format( "src '{}' in '{}' came from '{}', which is not a " "DefConst and is not defined at this use site", src->toString(), inst.toString(), src->inst()->toString()).str(); }); } for (SSATmp& dst : inst.dsts()) { assert(dst.inst() == &inst && inst.op() != DefConst); assert(!defined[dst]); defined[dst] = true; } } }); return true; }
/* * Build the interference graph by adding edges between nodes that are * simultaneously live. * * check-cast instructions have to be handled specially. They are represented * with both a dest (via a move-result-pseudo) and a src in our IR. However, in * actual Dex bytecode, it only takes a single operand which acts as both src * and dest. So when converting IR to Dex bytecode, we need to insert a move * instruction if the src and dest operands differ. We must insert the move * before, not after, the check-cast. Suppose we did not: * * IR | Dex * sget-object v0 LFoo; | sget-object v0 LFoo; * check-cast v0 LBar; | check-cast v0 LBar; * move-result-pseudo v1 | move-object v1 v0 * invoke-static v0 LFoo.a; | invoke-static v0 LFoo.a; // v0 is of type Bar! * * However, inserting before the check-cast is tricky to get right. If the * check-cast is in a try region, we must be careful to not clobber other * live registers. For example, if we had some IRCode like * * B0: * load-param v1 Ljava/lang/Object; * TRY_START * const v0 123 * check-cast v1 LFoo; * B1: * move-result-pseudo v0 * return v0 * TRY_END * B2: * CATCH * // handle failure of check-cast * // Note that v0 has the value of 123 here because the check-cast failed * add-int v0, v0, v0 * * Inserting the move before the check-cast would cause v0 to have an object * (instead of integer) type inside the exception handler. * * The solution is to have the interference graph make check-cast's dest * register interfere with the live registers in both B0 and B1, so that when * the move gets inserted, it does not clobber any live registers. */ Graph GraphBuilder::build(const LivenessFixpointIterator& fixpoint_iter, IRCode* code, reg_t initial_regs, const RangeSet& range_set) { Graph graph; auto ii = InstructionIterable(code); for (auto it = ii.begin(); it != ii.end(); ++it) { GraphBuilder::update_node_constraints(it.unwrap(), range_set, &graph); } auto& cfg = code->cfg(); for (cfg::Block* block : cfg.blocks()) { LivenessDomain live_out = fixpoint_iter.get_live_out_vars_at(block); for (auto it = block->rbegin(); it != block->rend(); ++it) { if (it->type != MFLOW_OPCODE) { continue; } auto insn = it->insn; auto op = insn->opcode(); if (opcode::has_range_form(op)) { graph.m_range_liveness.emplace(insn, live_out); } if (insn->dests_size()) { for (auto reg : live_out.elements()) { if (is_move(op) && reg == insn->src(0)) { continue; } graph.add_edge(insn->dest(), reg); } // We add interference edges between the wide src and dest operands of // an instruction even if the srcs are not live-out. This avoids // allocations like `xor-long v1, v0, v9`, where v1 and v0 overlap -- // even though this is not a verification error, we have observed bugs // in the ART interpreter when handling these sorts of instructions. // However, we still want to be able to coalesce these symregs if they // don't actually interfere based on liveness information, so that we // can remove move-wide opcodes and/or use /2addr encodings. As such, // we insert a specially marked edge that coalescing ignores but // coloring respects. if (insn->dest_is_wide()) { for (size_t i = 0; i < insn->srcs_size(); ++i) { if (insn->src_is_wide(i)) { graph.add_coalesceable_edge(insn->dest(), insn->src(i)); } } } } if (op == OPCODE_CHECK_CAST) { auto move_result_pseudo = std::prev(it)->insn; for (auto reg : live_out.elements()) { graph.add_edge(move_result_pseudo->dest(), reg); } } // adding containment edge between liverange defined in insn and elements // in live-out set of insn if (insn->dests_size()) { for (auto reg : live_out.elements()) { graph.add_containment_edge(insn->dest(), reg); } } fixpoint_iter.analyze_instruction(it->insn, &live_out); // adding containment edge between liverange used in insn and elements // in live-in set of insn for (size_t i = 0; i < insn->srcs_size(); ++i) { for (auto reg : live_out.elements()) { graph.add_containment_edge(insn->src(i), reg); } } } } for (auto& pair : graph.nodes()) { auto reg = pair.first; auto& node = pair.second; if (reg >= initial_regs) { node.m_props.set(Node::SPILL); } assert_log(!node.m_type_domain.is_bottom(), "Type violation of v%u in code:\n%s\n", reg, SHOW(code)); } return graph; }