bool InliningDecider::shouldInline(const Func* callee, const RegionDesc& region, uint32_t maxTotalCost) { auto sk = region.empty() ? SrcKey() : region.start(); assertx(callee); assertx(sk.func() == callee); // Tracing return lambdas. auto refuse = [&] (const char* why) { FTRACE(1, "shouldInline: rejecting callee region: {}", show(region)); return traceRefusal(m_topFunc, callee, why); }; auto accept = [&, this] (const char* kind) { FTRACE(1, "InliningDecider: inlining {}() <- {}()\t<reason: {}>\n", m_topFunc->fullName()->data(), callee->fullName()->data(), kind); return true; }; // Check inlining depths. if (m_callDepth + 1 >= RuntimeOption::EvalHHIRInliningMaxDepth) { return refuse("inlining call depth limit exceeded"); } if (m_stackDepth + callee->maxStackCells() >= kStackCheckLeafPadding) { return refuse("inlining stack depth limit exceeded"); } // Even if the func contains NativeImpl we may have broken the trace before // we hit it. auto containsNativeImpl = [&] { for (auto block : region.blocks()) { if (!block->empty() && block->last().op() == OpNativeImpl) return true; } return false; }; // Try to inline CPP builtin functions. // The NativeImpl opcode may appear later in the function because of Asserts // generated in hhbbc if (callee->isCPPBuiltin() && containsNativeImpl()) { if (isInlinableCPPBuiltin(callee)) { return accept("inlinable CPP builtin"); } return refuse("non-inlinable CPP builtin"); } // If the function may use a VarEnv (which is stored in the ActRec) or may be // variadic, we restrict inlined callees to certain whitelisted instructions // which we know won't actually require these features. const bool needsCheckVVSafe = callee->attrs() & AttrMayUseVV; int numRets = 0; int numExits = 0; // Iterate through the region, checking its suitability for inlining. for (auto const& block : region.blocks()) { sk = block->start(); for (auto i = 0, n = block->length(); i < n; ++i, sk.advance()) { auto op = sk.op(); // We don't allow inlined functions in the region. The client is // expected to disable inlining for the region it gives us to peek. if (sk.func() != callee) { return refuse("got region with inlined calls"); } // Restrict to VV-safe opcodes if necessary. if (needsCheckVVSafe && !isInliningVVSafe(op)) { return refuse(folly::format("{} may use dynamic environment", opcodeToName(op)).str().c_str()); } // Count the returns. if (isReturnish(op)) { if (++numRets > RuntimeOption::EvalHHIRInliningMaxReturns) { return refuse("region has too many returns"); } continue; } // We can't inline FCallArray. XXX: Why? if (op == Op::FCallArray) { return refuse("can't inline FCallArray"); } } if (region.isExit(block->id())) { if (++numExits > RuntimeOption::EvalHHIRInliningMaxBindJmps + numRets) { return refuse("region has too many non return exits"); } } } // Refuse if the cost exceeds our thresholds. // We measure the cost of inlining each callstack and stop when it exceeds a // certain threshold. (Note that we do not measure the total cost of all the // inlined calls for a given caller---just the cost of each nested stack.) const int maxCost = maxTotalCost - m_cost; const int cost = computeCost(region); if (cost > maxCost) { return refuse("too expensive"); } if (numRets == 0) { return refuse("region has no returns"); } return accept("small region with single return"); }
FuncAnalysis do_analyze_collect(const Index& index, Context const inputCtx, CollectedInfo& collect, ClassAnalysis* clsAnalysis, const std::vector<Type>* knownArgs) { auto const ctx = adjust_closure_context(inputCtx); FuncAnalysis ai(ctx); Trace::Bump bumper{Trace::hhbbc, kTraceFuncBump, is_trace_function(ctx.cls, ctx.func)}; FTRACE(2, "{:-^70}\n-- {}\n", "Analyze", show(ctx)); /* * Set of RPO ids that still need to be visited. * * Initially, we need each entry block in this list. As we visit * blocks, we propagate states to their successors and across their * back edges---when state merges cause a change to the block * stateIn, we will add it to this queue so it gets visited again. */ auto incompleteQ = prepare_incompleteQ(index, ai, clsAnalysis, knownArgs); /* * There are potentially infinitely growing types when we're using * union_of to merge states, so occasonially we need to apply a * widening operator. * * Currently this is done by having a straight-forward hueristic: if * you visit a block too many times, we'll start doing all the * merges with the widening operator until we've had a chance to * visit the block again. We must then continue iterating in case * the actual fixed point is higher than the result of widening. * * Terminiation is guaranteed because the widening operator has only * finite chains in the type lattice. */ auto nonWideVisits = std::vector<uint32_t>(ctx.func->nextBlockId); // For debugging, count how many times basic blocks get interpreted. auto interp_counter = uint32_t{0}; /* * Iterate until a fixed point. * * Each time a stateIn for a block changes, we re-insert the block's * rpo ID in incompleteQ. Since incompleteQ is ordered, we'll * always visit blocks with earlier RPO ids first, which hopefully * means less iterations. */ while (!incompleteQ.empty()) { auto const blk = ai.rpoBlocks[incompleteQ.pop()]; if (nonWideVisits[blk->id]++ > options.analyzeFuncWideningLimit) { nonWideVisits[blk->id] = 0; } FTRACE(2, "block #{}\nin {}{}", blk->id, state_string(*ctx.func, ai.bdata[blk->id].stateIn), property_state_string(collect.props)); ++interp_counter; auto propagate = [&] (php::Block& target, const State& st) { auto const needsWiden = nonWideVisits[target.id] >= options.analyzeFuncWideningLimit; // We haven't optimized the widening operator much, because it // doesn't happen in practice right now. We want to know when // it starts happening: if (needsWiden) { std::fprintf(stderr, "widening in %s on %s\n", ctx.unit->filename->data(), ctx.func->name->data()); } FTRACE(2, " {}-> {}\n", needsWiden ? "widening " : "", target.id); FTRACE(4, "target old {}", state_string(*ctx.func, ai.bdata[target.id].stateIn)); auto const changed = needsWiden ? widen_into(ai.bdata[target.id].stateIn, st) : merge_into(ai.bdata[target.id].stateIn, st); if (changed) { incompleteQ.push(rpoId(ai, &target)); } FTRACE(4, "target new {}", state_string(*ctx.func, ai.bdata[target.id].stateIn)); }; auto stateOut = ai.bdata[blk->id].stateIn; auto interp = Interp { index, ctx, collect, blk, stateOut }; auto flags = run(interp, propagate); if (flags.returned) { ai.inferredReturn = union_of(std::move(ai.inferredReturn), std::move(*flags.returned)); } } ai.closureUseTypes = std::move(collect.closureUseTypes); if (ctx.func->isGenerator) { if (ctx.func->isAsync) { // Async generators always return AsyncGenerator object. ai.inferredReturn = objExact(index.builtin_class(s_AsyncGenerator.get())); } else { // Non-async generators always return Generator object. ai.inferredReturn = objExact(index.builtin_class(s_Generator.get())); } } else if (ctx.func->isAsync) { // Async functions always return WaitH<T>, where T is the type returned // internally. ai.inferredReturn = wait_handle(index, ai.inferredReturn); } /* * If inferredReturn is TBottom, the callee didn't execute a return * at all. (E.g. it unconditionally throws, or is an abstract * function body.) * * In this case, we leave the return type as TBottom, to indicate * the same to callers. */ assert(ai.inferredReturn.subtypeOf(TGen)); // For debugging, print the final input states for each block. FTRACE(2, "{}", [&] { auto const bsep = std::string(60, '=') + "\n"; auto const sep = std::string(60, '-') + "\n"; auto ret = folly::format( "{}function {} ({} block interps):\n{}", bsep, show(ctx), interp_counter, bsep ).str(); for (auto& bd : ai.bdata) { ret += folly::format( "{}block {}:\nin {}", sep, ai.rpoBlocks[bd.rpoId]->id, state_string(*ctx.func, bd.stateIn) ).str(); } ret += sep + bsep; folly::format(&ret, "Inferred return type: {}\n", show(ai.inferredReturn)); ret += bsep; return ret; }()); return ai; }
RegionDescPtr selectHotTrace(TransID triggerId, const ProfData* profData, TransCFG& cfg, TransIDSet& selectedSet, TransIDVec* selectedVec) { auto region = std::make_shared<RegionDesc>(); TransID tid = triggerId; TransID prevId = kInvalidTransID; selectedSet.clear(); if (selectedVec) selectedVec->clear(); PostConditions accumPostConds; // Maps BlockIds to the set of BC offsets for its successor blocks. // Used to prevent multiple successors with the same SrcKey for now. // This can go away once task #4157613 is done. hphp_hash_map<RegionDesc::BlockId, SrcKeySet> succSKSet; // Maps from BlockIds to accumulated post conditions for that block. // Used to determine if we can add branch-over edges by checking the // pre-conditions of the successor block. hphp_hash_map<RegionDesc::BlockId, PostConditions> blockPostConds; while (!selectedSet.count(tid)) { RegionDescPtr blockRegion = profData->transRegion(tid); if (blockRegion == nullptr) break; // If the debugger is attached, only allow single-block regions. if (prevId != kInvalidTransID && isDebuggerAttachedProcess()) { FTRACE(2, "selectHotTrace: breaking region at Translation {} " "because of debugger is attached\n", tid); break; } // Break if block is not the first and requires reffiness checks. // Task #2589970: fix translateRegion to support mid-region reffiness checks if (prevId != kInvalidTransID) { auto nRefDeps = blockRegion->entry()->reffinessPreds().size(); if (nRefDeps > 0) { FTRACE(2, "selectHotTrace: breaking region because of refDeps ({}) at " "Translation {}\n", nRefDeps, tid); break; } } // Break if block is not the first and it corresponds to the main // function body entry. This is to prevent creating multiple // large regions containing the function body (starting at various // DV funclets). if (prevId != kInvalidTransID) { const Func* func = profData->transFunc(tid); Offset bcOffset = profData->transStartBcOff(tid); if (func->base() == bcOffset) { FTRACE(2, "selectHotTrace: breaking region because reached the main " "function body entry at Translation {} (BC offset {})\n", tid, bcOffset); break; } } if (prevId != kInvalidTransID) { auto sk = profData->transSrcKey(tid); if (profData->optimized(sk)) { FTRACE(2, "selectHotTrace: breaking region because next sk already " "optimized, for Translation {}\n", tid); break; } } // Break trace if translation tid cannot follow the execution of // the entire translation prevId. This can only happen if the // execution of prevId takes a side exit that leads to the // execution of tid. if (prevId != kInvalidTransID) { Op* lastInstr = profData->transLastInstr(prevId); const Unit* unit = profData->transFunc(prevId)->unit(); OffsetSet succOffs = instrSuccOffsets(lastInstr, unit); if (!succOffs.count(profData->transSrcKey(tid).offset())) { if (HPHP::Trace::moduleEnabled(HPHP::Trace::pgo, 2)) { FTRACE(2, "selectHotTrace: WARNING: Breaking region @: {}\n", show(*region)); FTRACE(2, "selectHotTrace: next translation selected: tid = {}\n{}\n", tid, show(*blockRegion)); FTRACE(2, "\nsuccOffs = {}\n", folly::join(", ", succOffs)); } break; } } bool hasPredBlock = !region->empty(); RegionDesc::BlockId predBlockId = (hasPredBlock ? region->blocks().back().get()->id() : 0); auto const& newFirstBlock = blockRegion->entry(); auto newFirstBlockId = newFirstBlock->id(); auto newFirstBlockSk = newFirstBlock->start(); auto newLastBlockId = blockRegion->blocks().back()->id(); // Make sure we don't end up with multiple successors for the same // SrcKey. Task #4157613 will allow the following check to go away. // This needs to be done before we insert blockRegion into region, // to avoid creating unreachable blocks. if (RuntimeOption::EvalHHIRBytecodeControlFlow && hasPredBlock && succSKSet[predBlockId].count(newFirstBlockSk)) { break; } // Add blockRegion's blocks and arcs to region. region->append(*blockRegion); if (hasPredBlock) { if (RuntimeOption::EvalHHIRBytecodeControlFlow) { // This is checked above. assert(succSKSet[predBlockId].count(newFirstBlockSk) == 0); succSKSet[predBlockId].insert(newFirstBlockSk); } region->addArc(predBlockId, newFirstBlockId); } // With bytecode control-flow, we add all forward arcs in the TransCFG // that are induced by the blocks in the region, as a simple way // to expose control-flow for now. // This can go away once Task #4075822 is done. if (RuntimeOption::EvalHHIRBytecodeControlFlow) { assert(hasTransId(newFirstBlockId)); auto newTransId = getTransId(newFirstBlockId); auto& blocks = region->blocks(); for (auto iOther = 0; iOther < blocks.size(); iOther++) { auto other = blocks[iOther]; auto otherFirstBlockId = other.get()->id(); if (!hasTransId(otherFirstBlockId)) continue; auto otherTransId = getTransId(otherFirstBlockId); auto otherFirstBlockSk = other.get()->start(); auto otherRegion = profData->transRegion(otherTransId); auto otherLastBlockId = otherRegion->blocks().back()->id(); // When loops are off, stop once we hit the newTransId we just inserted. if (!RuntimeOption::EvalJitLoops && otherTransId == newTransId) break; if (cfg.hasArc(otherTransId, newTransId) && // Task #4157613 will allow the following check to go away !succSKSet[otherLastBlockId].count(newFirstBlockSk) && preCondsAreSatisfied(newFirstBlock, blockPostConds[otherLastBlockId])) { region->addArc(otherLastBlockId, newFirstBlockId); succSKSet[otherLastBlockId].insert(newFirstBlockSk); } // When Eval.JitLoops is set, insert back-edges in the // region if they exist in the TransCFG. if (RuntimeOption::EvalJitLoops && cfg.hasArc(newTransId, otherTransId) && // Task #4157613 will allow the following check to go away !succSKSet[newLastBlockId].count(otherFirstBlockSk)) { region->addArc(newLastBlockId, otherFirstBlockId); succSKSet[newLastBlockId].insert(otherFirstBlockSk); } } } if (cfg.outArcs(tid).size() > 1) { region->setSideExitingBlock(blockRegion->entry()->id()); } selectedSet.insert(tid); if (selectedVec) selectedVec->push_back(tid); Op lastOp = *(profData->transLastInstr(tid)); if (breaksRegion(lastOp)) { FTRACE(2, "selectHotTrace: breaking region because of last instruction " "in Translation {}: {}\n", tid, opcodeToName(lastOp)); break; } auto outArcs = cfg.outArcs(tid); if (outArcs.size() == 0) { FTRACE(2, "selectHotTrace: breaking region because there's no successor " "for Translation {}\n", tid); break; } auto newLastBlock = blockRegion->blocks().back(); discardPoppedTypes(accumPostConds, blockRegion->entry()->initialSpOffset()); mergePostConds(accumPostConds, newLastBlock->postConds()); blockPostConds[newLastBlock->id()] = accumPostConds; TransCFG::ArcPtrVec possibleOutArcs; for (auto arc : outArcs) { RegionDesc::BlockPtr possibleNext = profData->transRegion(arc->dst())->entry(); if (preCondsAreSatisfied(possibleNext, accumPostConds)) { possibleOutArcs.emplace_back(arc); } } if (possibleOutArcs.size() == 0) { FTRACE(2, "selectHotTrace: breaking region because postcondition check " "pruned all successors of Translation {}\n", tid); break; } auto maxWeight = std::numeric_limits<int64_t>::min(); TransCFG::Arc* maxArc = nullptr; for (auto arc : possibleOutArcs) { if (arc->weight() >= maxWeight) { maxWeight = arc->weight(); maxArc = arc; } } assert(maxArc != nullptr); prevId = tid; tid = maxArc->dst(); } return region; }
void FrameState::update(const IRInstruction* inst) { FTRACE(3, "FrameState::update processing {}\n", *inst); if (auto* taken = inst->taken()) { // When we're building the IR, we append a conditional jump after // generating its target block: see emitJmpCondHelper, where we // call makeExit() before gen(JmpZero). It doesn't make sense to // update the target block state at this point, so don't. The // state doesn't have this problem during optimization passes, // because we'll always process the jump before the target block. if (!m_building || taken->empty()) save(taken); } auto const opc = inst->op(); getLocalEffects(inst, *this); switch (opc) { case DefInlineFP: trackDefInlineFP(inst); break; case InlineReturn: trackInlineReturn(inst); break; case Call: m_spValue = inst->dst(); m_frameSpansCall = true; // A call pops the ActRec and pushes a return value. m_spOffset -= kNumActRecCells; m_spOffset += 1; assert(m_spOffset >= 0); clearCse(); break; case CallArray: m_spValue = inst->dst(); m_frameSpansCall = true; // A CallArray pops the ActRec an array arg and pushes a return value. m_spOffset -= kNumActRecCells; assert(m_spOffset >= 0); clearCse(); break; case ContEnter: clearCse(); break; case DefFP: case FreeActRec: m_fpValue = inst->dst(); break; case ReDefResumableSP: m_spValue = inst->dst(); break; case ReDefSP: m_spValue = inst->dst(); m_spOffset = inst->extra<ReDefSP>()->spOffset; break; case DefInlineSP: case DefSP: m_spValue = inst->dst(); m_spOffset = inst->extra<StackOffset>()->offset; break; case AssertStk: case CastStk: case CoerceStk: case CheckStk: case GuardStk: case ExceptionBarrier: m_spValue = inst->dst(); break; case SpillStack: { m_spValue = inst->dst(); // Push the spilled values but adjust for the popped values int64_t stackAdjustment = inst->src(1)->intVal(); m_spOffset -= stackAdjustment; m_spOffset += spillValueCells(inst); break; } case SpillFrame: case CufIterSpillFrame: m_spValue = inst->dst(); m_spOffset += kNumActRecCells; break; case InterpOne: case InterpOneCF: { m_spValue = inst->dst(); auto const& extra = *inst->extra<InterpOneData>(); int64_t stackAdjustment = extra.cellsPopped - extra.cellsPushed; // push the return value if any and adjust for the popped values m_spOffset -= stackAdjustment; break; } case AssertLoc: case GuardLoc: case CheckLoc: m_fpValue = inst->dst(); break; case LdThis: m_thisAvailable = true; break; default: break; } if (inst->modifiesStack()) { m_spValue = inst->modifiedStkPtr(); } // update the CSE table if (m_enableCse && inst->canCSE()) { cseInsert(inst); } // if the instruction kills any of its sources, remove them from the // CSE table if (inst->killsSources()) { for (int i = 0; i < inst->numSrcs(); ++i) { if (inst->killsSource(i)) { cseKill(inst->src(i)); } } } // Save state for each block at the end. if (inst->isTerminal()) { save(inst->block()); } }
// fill out the icon with the stop symbol from app_server void ConflictView::_FillSavedIcon() { // return if the fSavedIcon has already been filled out if (fSavedIcon != NULL && fSavedIcon->InitCheck() == B_OK) return; BPath path; status_t status = find_directory(B_BEOS_SERVERS_DIRECTORY, &path); if (status < B_OK) { FTRACE((stderr, "_FillWarningIcon() - find_directory failed: %s\n", strerror(status))); delete fSavedIcon; fSavedIcon = NULL; return; } path.Append("app_server"); BFile file; status = file.SetTo(path.Path(), B_READ_ONLY); if (status < B_OK) { FTRACE((stderr, "_FillWarningIcon() - BFile init failed: %s\n", strerror(status))); delete fSavedIcon; fSavedIcon = NULL; return; } BResources resources; status = resources.SetTo(&file); if (status < B_OK) { FTRACE((stderr, "_WarningIcon() - BResources init failed: %s\n", strerror(status))); delete fSavedIcon; fSavedIcon = NULL; return; } // Allocate the fSavedIcon bitmap fSavedIcon = new(std::nothrow) BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32); if (fSavedIcon->InitCheck() < B_OK) { FTRACE((stderr, "_WarningIcon() - No memory for warning bitmap\n")); delete fSavedIcon; fSavedIcon = NULL; return; } // Load the raw stop icon data size_t size = 0; const uint8* rawIcon; rawIcon = (const uint8*)resources.LoadResource(B_VECTOR_ICON_TYPE, "stop", &size); // load vector warning icon into fSavedIcon if (rawIcon == NULL || BIconUtils::GetVectorIcon(rawIcon, size, fSavedIcon) < B_OK) { delete fSavedIcon; fSavedIcon = NULL; } }
/* ------------------------------------------------------------------------------- Class: CSimpleTimeout Method: CSimpleTimeout Description: Default constructor C++ default constructor can NOT contain any code, that might leave. Parameters: None Return Values: None Errors/Exceptions: None Status: Approved ------------------------------------------------------------------------------- */ CSimpleTimeout::CSimpleTimeout() : CActive (CActive::EPriorityStandard) { FTRACE(FPrint(_L("CSimpleTimeout::CSimpleTimeout"))); }
/* * reoptimize() runs a trace through a second pass of TraceBuilder * optimizations, like this: * * reset state. * move all blocks to a temporary list. * compute immediate dominators. * for each block in trace order: * if we have a snapshot state for this block: * clear cse entries that don't dominate this block. * use snapshot state. * move all instructions to a temporary list. * for each instruction: * optimizeWork - do CSE and simplify again * if not simplified: * append existing instruction and update state. * else: * if the instruction has a result, insert a mov from the * simplified tmp to the original tmp and discard the instruction. * if the last conditional branch was turned into a jump, remove the * fall-through edge to the next block. */ void TraceBuilder::reoptimize() { FTRACE(5, "ReOptimize:vvvvvvvvvvvvvvvvvvvv\n"); SCOPE_EXIT { FTRACE(5, "ReOptimize:^^^^^^^^^^^^^^^^^^^^\n"); }; assert(m_curTrace->isMain()); assert(m_savedTraces.empty()); m_state.setEnableCse(RuntimeOption::EvalHHIRCse); m_enableSimplification = RuntimeOption::EvalHHIRSimplification; if (!m_state.enableCse() && !m_enableSimplification) return; always_assert(!m_inReoptimize); m_inReoptimize = true; BlockList sortedBlocks = rpoSortCfg(m_unit); auto const idoms = findDominators(m_unit, sortedBlocks); m_state.clear(); auto blocks = std::move(m_curTrace->blocks()); assert(m_curTrace->blocks().empty()); while (!blocks.empty()) { Block* block = blocks.front(); blocks.pop_front(); assert(block->trace() == m_curTrace); FTRACE(5, "Block: {}\n", block->id()); assert(m_curTrace->isMain()); m_state.startBlock(block); m_curTrace->push_back(block); auto instructions = std::move(block->instrs()); assert(block->empty()); while (!instructions.empty()) { auto *inst = &instructions.front(); instructions.pop_front(); m_state.setMarker(inst->marker()); // merging state looks at the current marker, and optimizeWork // below may create new instructions. Use the marker from this // instruction. assert(inst->marker().valid()); setMarker(inst->marker()); auto const tmp = optimizeWork(inst, idoms); // Can generate new instrs! if (!tmp) { // Could not optimize; keep the old instruction appendInstruction(inst, block); m_state.update(inst); continue; } SSATmp* dst = inst->dst(); if (dst->type() != Type::None && dst != tmp) { // The result of optimization has a different destination than the inst. // Generate a mov(tmp->dst) to get result into dst. If we get here then // assume the last instruction in the block isn't a guard. If it was, // we would have to insert the mov on the fall-through edge. assert(block->empty() || !block->back().isBlockEnd()); IRInstruction* mov = m_unit.mov(dst, tmp, inst->marker()); appendInstruction(mov, block); m_state.update(mov); } // Not re-adding inst; remove the inst->taken edge if (inst->taken()) inst->setTaken(nullptr); } if (block->empty()) { // If all the instructions in the block were optimized away, remove it // from the trace. auto it = m_curTrace->blocks().end(); --it; assert(*it == block); m_curTrace->unlink(it); } else { if (block->back().isTerminal()) { // Could have converted a conditional branch to Jmp; clear next. block->setNext(nullptr); } m_state.finishBlock(block); } } }
void IRTranslator::interpretInstr(const NormalizedInstruction& i) { FTRACE(5, "HHIR: BC Instr {}\n", i.toString()); m_hhbcTrans.emitInterpOne(i); }
bool shouldIRInline(const Func* caller, const Func* callee, RegionIter& iter) { if (!RuntimeOption::EvalHHIREnableGenTimeInlining) { return false; } if (arch() == Arch::ARM) { // TODO(#3331014): hack until more ARM codegen is working. return false; } if (caller->isPseudoMain()) { // TODO(#4238160): Hack inlining into pseudomain callsites is still buggy return false; } auto refuse = [&](const char* why) -> bool { FTRACE(1, "shouldIRInline: refusing {} <reason: {}> [NI = {}]\n", callee->fullName()->data(), why, iter.finished() ? "<end>" : iter.sk().showInst()); return false; }; auto accept = [&](const char* kind) -> bool { FTRACE(1, "shouldIRInline: inlining {} <kind: {}>\n", callee->fullName()->data(), kind); return true; }; if (callee->numIterators() != 0) { return refuse("iterators"); } if (callee->isMagic() || Func::isSpecial(callee->name())) { return refuse("special or magic function"); } if (callee->attrs() & AttrMayUseVV) { return refuse("may use dynamic environment"); } if (callee->isResumable()) { return refuse("resumables"); } if (callee->numSlotsInFrame() + callee->maxStackCells() >= kStackCheckLeafPadding) { return refuse("function stack depth too deep"); } //////////// assert(!iter.finished() && "shouldIRInline given empty region"); bool hotCallingCold = !(callee->attrs() & AttrHot) && (caller->attrs() & AttrHot); uint64_t cost = 0; int inlineDepth = 0; Op op = OpLowInvalid; smart::vector<const Func*> funcs; const Func* func = callee; funcs.push_back(func); for (; !iter.finished(); iter.advance()) { // If func has changed after an FCall, we've started an inlined call. This // will have to change when we support inlining recursive calls. if (func != iter.sk().func()) { assert(isRet(op) || op == Op::FCall || op == Op::FCallD); if (op == Op::FCall || op == Op::FCallD) { funcs.push_back(iter.sk().func()); int totalDepth = 0; for (auto* f : funcs) { totalDepth += f->numSlotsInFrame() + f->maxStackCells(); } if (totalDepth >= kStackCheckLeafPadding) { return refuse("stack too deep after nested inlining"); } ++inlineDepth; } } op = iter.sk().op(); func = iter.sk().func(); // If we hit a RetC/V while inlining, leave that level and // continue. Otherwise, accept the tracelet. if (isRet(op)) { if (inlineDepth > 0) { --inlineDepth; funcs.pop_back(); continue; } else { assert(inlineDepth == 0); return accept("entire function fits in one region"); } } if (op == Op::FCallArray) return refuse("FCallArray"); // These opcodes don't indicate any additional work in the callee, // so they shouldn't count toward the inlining cost. if (op == Op::AssertRATL || op == Op::AssertRATStk) { continue; } cost += 1; // Check for an immediate vector, and if it's present add its size to the // cost. auto const pc = reinterpret_cast<const Op*>(iter.sk().pc()); if (hasMVector(op)) { cost += getMVector(pc).size(); } else if (hasImmVector(op)) { cost += getImmVector(pc).size(); } if (cost > RuntimeOption::EvalHHIRInliningMaxCost) { return refuse("too expensive"); } if (cost > RuntimeOption::EvalHHIRAlwaysInlineMaxCost && hotCallingCold) { return refuse("inlining sizeable cold func into hot func"); } if (JIT::opcodeBreaksBB(op)) { return refuse("breaks tracelet"); } } return refuse("region doesn't end in RetC/RetV"); }
/** * Sorts the regions vector in a linear order to be used for * translation. The goal is to obtain an order that improves locality * when the function is executed. */ static void sortRegion(RegionVec& regions, const Func* func, const TransCFG& cfg, const ProfData* profData, const TransIDToRegionMap& headToRegion, const RegionToTransIDsMap& regionToTransIds) { RegionVec sorted; RegionSet selected; if (regions.size() == 0) return; // First, pick the region starting at the lowest bytecode offset. // This will normally correspond to the main function entry (for // normal, regular bytecode), but it may not be for irregular // functions written in hhas (like array_map and array_filter). If // there multiple regions starting at the lowest bytecode offset, // pick the one with the largest profile weight. RegionDescPtr entryRegion = nullptr; int64_t maxEntryWeight = -1; Offset lowestOffset = kInvalidOffset; for (const auto& pair : regionToTransIds) { auto r = pair.first; auto& tids = pair.second; TransID firstTid = tids[0]; Offset firstOffset = profData->transSrcKey(firstTid).offset(); int64_t weight = cfg.weight(firstTid); if (lowestOffset == kInvalidOffset || firstOffset < lowestOffset || (firstOffset == lowestOffset && weight > maxEntryWeight)) { entryRegion = r; maxEntryWeight = weight; lowestOffset = firstOffset; } } assert(entryRegion); sorted.push_back(entryRegion); selected.insert(entryRegion); RegionDescPtr region = entryRegion; // Select the remaining regions, iteratively picking the most likely // region to execute next. for (auto i = 1; i < regions.size(); i++) { int64_t maxWeight = -1; int64_t maxHeadWeight = -1; RegionDescPtr bestNext = nullptr; auto regionTransIds = getRegionTransIDVec(regionToTransIds, region); for (auto next : regions) { if (setContains(selected, next)) continue; auto nextTransIds = getRegionTransIDVec(regionToTransIds, next); int64_t weight = interRegionWeight(regionTransIds, nextTransIds[0], cfg); int64_t headWeight = cfg.weight(nextTransIds[0]); if ((weight > maxWeight) || (weight == maxWeight && headWeight > maxHeadWeight)) { maxWeight = weight; maxHeadWeight = headWeight; bestNext = next; } } assert(bestNext); sorted.push_back(bestNext); selected.insert(bestNext); region = bestNext; } assert(sorted.size() == regions.size()); regions = sorted; if (debug && Trace::moduleEnabled(HPHP::Trace::pgo, 5)) { for (size_t i = 0; i < regions.size(); i++) { auto r = regions[i]; auto tids = getRegionTransIDVec(regionToTransIds, r); std::string transIds = folly::join(", ", tids); FTRACE(6, "sortRegion: region[{}]: {}\n", i, transIds); } } }
/** * Regionize a func, so that each node and each arc in its TransCFG is * "covered". A node is covered if any region contains it. An arc T1->T2 * is covered if either: * * a) T1 and T2 are in the same region R and T2 immediately follows * T1 in R. * b) T2 is the head (first translation) of a region. * * Basic algorithm: * * 1) sort nodes in decreasing weight order * 2) for each node N: * 2.1) if N and all its incoming arcs are covered, then continue * 2.2) select a region starting at this node and mark nodes/arcs as * covered appropriately */ void regionizeFunc(const Func* func, JIT::TranslatorX64* tx64, RegionVec& regions) { assert(RuntimeOption::EvalJitPGO); FuncId funcId = func->getFuncId(); ProfData* profData = tx64->profData(); TransCFG cfg(funcId, profData, tx64->getSrcDB(), tx64->getJmpToTransIDMap()); if (Trace::moduleEnabled(HPHP::Trace::pgo, 5)) { string dotFileName = folly::to<string>("/tmp/func-cfg-", funcId, ".dot"); cfg.print(dotFileName, funcId, profData, nullptr); FTRACE(5, "regionizeFunc: initial CFG for func {} saved to file {}\n", funcId, dotFileName); } TransCFG::ArcPtrVec arcs = cfg.arcs(); vector<TransID> nodes = cfg.nodes(); std::sort(nodes.begin(), nodes.end(), [&](TransID tid1, TransID tid2) -> bool { if (cfg.weight(tid1) != cfg.weight(tid2)) { return cfg.weight(tid1) > cfg.weight(tid2); } // In case of ties, pick older translations first, in an // attempt to start loops at their headers. return tid1 < tid2; }); TransCFG::ArcPtrSet coveredArcs; TransIDSet coveredNodes; TransIDSet heads; TransIDToRegionMap headToRegion; RegionToTransIDsMap regionToTransIds; regions.clear(); for (auto node : nodes) { if (!setContains(coveredNodes, node) || !allArcsCovered(cfg.inArcs(node), coveredArcs)) { TransID newHead = node; FTRACE(6, "regionizeFunc: selecting trace to cover node {}\n", newHead); TransIDSet selectedSet; TransIDVec selectedVec; RegionDescPtr region = selectHotTrace(newHead, profData, cfg, selectedSet, &selectedVec); profData->setOptimized(profData->transSrcKey(newHead)); assert(selectedVec.size() > 0 && selectedVec[0] == newHead); regions.push_back(region); heads.insert(newHead); markCovered(cfg, selectedVec, heads, coveredNodes, coveredArcs); regionToTransIds[region] = selectedVec; headToRegion[newHead] = region; FTRACE(6, "regionizeFunc: selected trace: {}\n", folly::join(", ", selectedVec)); } } assert(coveredNodes.size() == cfg.nodes().size()); assert(coveredArcs.size() == arcs.size()); sortRegion(regions, func, cfg, profData, headToRegion, regionToTransIds); if (debug && Trace::moduleEnabled(HPHP::Trace::pgo, 5)) { FTRACE(5, "\n--------------------------------------------\n" "regionizeFunc({}): computed regions:\n", funcId); for (auto region : regions) { FTRACE(5, "{}\n\n", show(*region)); } } }
TInt CIlbcEncoderIntfcTestClass::CreateInputStreamInstance( CStifItemParser& /*aItem */) { FTRACE(FPrint(_L("CIlbcEncoderIntfcTestClass::CreateInputStreamInstance"))); iLog->Log(_L("CIlbcEncoderIntfcTestClass::CreateInputStreamInstance")); TInt error = KErrNone; TRAP(error, iAudioInputStream = CMdaAudioInputStream::NewL(*this););
FuncAnalysis do_analyze_collect(const Index& index, Context const ctx, CollectedInfo& collect, ClassAnalysis* clsAnalysis, const std::vector<Type>* knownArgs) { assertx(ctx.cls == adjust_closure_context(ctx).cls); FuncAnalysis ai{ctx}; auto const bump = trace_bump_for(ctx.cls, ctx.func); Trace::Bump bumper1{Trace::hhbbc, bump}; Trace::Bump bumper2{Trace::hhbbc_cfg, bump}; if (knownArgs) { FTRACE(2, "{:.^70}\n", "Inline Interp"); } SCOPE_EXIT { if (knownArgs) { FTRACE(2, "{:.^70}\n", "End Inline Interp"); } }; FTRACE(2, "{:-^70}\n-- {}\n", "Analyze", show(ctx)); /* * Set of RPO ids that still need to be visited. * * Initially, we need each entry block in this list. As we visit * blocks, we propagate states to their successors and across their * back edges---when state merges cause a change to the block * stateIn, we will add it to this queue so it gets visited again. */ auto incompleteQ = prepare_incompleteQ(index, ai, clsAnalysis, knownArgs); /* * There are potentially infinitely growing types when we're using union_of to * merge states, so occasionally we need to apply a widening operator. * * Currently this is done by having a straight-forward hueristic: if you visit * a block too many times, we'll start doing all the merges with the widening * operator. We must then continue iterating in case the actual fixed point is * higher than the result of widening. Likewise if we loop too much because of * local static types changing, we'll widen those. * * Termination is guaranteed because the widening operator has only finite * chains in the type lattice. */ auto totalVisits = std::vector<uint32_t>(ctx.func->blocks.size()); auto totalLoops = uint32_t{0}; // For debugging, count how many times basic blocks get interpreted. auto interp_counter = uint32_t{0}; // Used to force blocks that depended on the types of local statics // to be re-analyzed when the local statics change. std::unordered_map<borrowed_ptr<const php::Block>, std::map<LocalId, Type>> usedLocalStatics; /* * Iterate until a fixed point. * * Each time a stateIn for a block changes, we re-insert the block's * rpo ID in incompleteQ. Since incompleteQ is ordered, we'll * always visit blocks with earlier RPO ids first, which hopefully * means less iterations. */ do { while (!incompleteQ.empty()) { auto const blk = ai.rpoBlocks[incompleteQ.pop()]; totalVisits[blk->id]++; FTRACE(2, "block #{}\nin {}{}", blk->id, state_string(*ctx.func, ai.bdata[blk->id].stateIn, collect), property_state_string(collect.props)); ++interp_counter; auto propagate = [&] (BlockId target, const State* st) { if (!st) { FTRACE(2, " Force reprocess: {}\n", target); incompleteQ.push(rpoId(ai, target)); return; } auto const needsWiden = totalVisits[target] >= options.analyzeFuncWideningLimit; FTRACE(2, " {}-> {}\n", needsWiden ? "widening " : "", target); FTRACE(4, "target old {}", state_string(*ctx.func, ai.bdata[target].stateIn, collect)); auto const changed = needsWiden ? widen_into(ai.bdata[target].stateIn, *st) : merge_into(ai.bdata[target].stateIn, *st); if (changed) { incompleteQ.push(rpoId(ai, target)); } FTRACE(4, "target new {}", state_string(*ctx.func, ai.bdata[target].stateIn, collect)); }; auto stateOut = ai.bdata[blk->id].stateIn; auto interp = Interp { index, ctx, collect, blk, stateOut }; auto flags = run(interp, propagate); if (any(collect.opts & CollectionOpts::EffectFreeOnly) && !collect.effectFree) { break; } // We only care about the usedLocalStatics from the last visit if (flags.usedLocalStatics) { usedLocalStatics[blk] = std::move(*flags.usedLocalStatics); } else { usedLocalStatics.erase(blk); } if (flags.returned) { ai.inferredReturn |= std::move(*flags.returned); } } if (any(collect.opts & CollectionOpts::EffectFreeOnly) && !collect.effectFree) { break; } // maybe some local statics changed type since the last time their // blocks were visited. if (totalLoops++ >= options.analyzeFuncWideningLimit) { // If we loop too many times because of static locals, widen them to // ensure termination. for (auto& t : collect.localStaticTypes) { t = widen_type(std::move(t)); } } for (auto const& elm : usedLocalStatics) { for (auto const& ls : elm.second) { if (collect.localStaticTypes[ls.first] != ls.second) { incompleteQ.push(rpoId(ai, elm.first->id)); break; } } } } while (!incompleteQ.empty()); ai.closureUseTypes = std::move(collect.closureUseTypes); ai.cnsMap = std::move(collect.cnsMap); ai.readsUntrackedConstants = collect.readsUntrackedConstants; ai.mayUseVV = collect.mayUseVV; ai.effectFree = collect.effectFree; ai.unfoldableFuncs = collect.unfoldableFuncs; index.fixup_return_type(ctx.func, ai.inferredReturn); /* * If inferredReturn is TBottom, the callee didn't execute a return * at all. (E.g. it unconditionally throws, or is an abstract * function body.) * * In this case, we leave the return type as TBottom, to indicate * the same to callers. */ assert(ai.inferredReturn.subtypeOf(TGen)); // For debugging, print the final input states for each block. FTRACE(2, "{}", [&] { auto const bsep = std::string(60, '=') + "\n"; auto const sep = std::string(60, '-') + "\n"; auto ret = folly::format( "{}function {} ({} block interps):\n{}", bsep, show(ctx), interp_counter, bsep ).str(); for (auto& bd : ai.bdata) { folly::format( &ret, "{}block {}:\nin {}", sep, ai.rpoBlocks[bd.rpoId]->id, state_string(*ctx.func, bd.stateIn, collect) ); } ret += sep + bsep; folly::format(&ret, "Inferred return type: {}\n", show(ai.inferredReturn)); ret += bsep; return ret; }()); // Do this after the tracing above ai.localStaticTypes = std::move(collect.localStaticTypes); return ai; }
// ----------------------------------------------------------------------------- // CBTServiceDelayedDestroyer::RunError() // ----------------------------------------------------------------------------- // TInt CBTServiceDelayedDestroyer::RunError(TInt aError) { FTRACE(FPrint(_L("[BTSU]\t CBTServiceStarter::RunError() aError = %d"), aError) ); (void) aError; return KErrNone; }
/* ------------------------------------------------------------------------------- Class: CSimpleTimeout Method: ~CSimpleTimeout Description: Destructor. Cancel request Parameters: None Return Values: None Errors/Exceptions: None Status: Approved ------------------------------------------------------------------------------- */ CSimpleTimeout::~CSimpleTimeout() { FTRACE(FPrint(_L("CSimpleTimeout::~CSimpleTimeout"))); Cancel(); iTimer.Close(); }
TransCFG::TransCFG(FuncId funcId, const ProfData* profData, const SrcDB& srcDB, const TcaTransIDMap& jmpToTransID) { assert(profData); // add nodes for (auto tid : profData->funcProfTransIDs(funcId)) { assert(profData->transRegion(tid) != nullptr); // This will skip DV Funclets if they were already // retranslated w/ the prologues: if (!profData->optimized(profData->transSrcKey(tid))) { int64_t counter = profData->transCounter(tid); int64_t weight = RuntimeOption::EvalJitPGOThreshold - counter; addNode(tid, weight); } } // add arcs for (TransID dstId : nodes()) { SrcKey dstSK = profData->transSrcKey(dstId); RegionDesc::BlockPtr dstBlock = profData->transRegion(dstId)->blocks[0]; const SrcRec* dstSR = srcDB.find(dstSK); FTRACE(5, "TransCFG: adding incoming arcs in dstId = {}\n", dstId); TransIDSet predIDs = findPredTrans(dstSR, jmpToTransID); for (auto predId : predIDs) { if (hasNode(predId)) { auto predPostConds = profData->transRegion(predId)->blocks.back()->postConds(); SrcKey predSK = profData->transSrcKey(predId); if (preCondsAreSatisfied(dstBlock, predPostConds) && predSK.resumed() == dstSK.resumed()) { FTRACE(5, "TransCFG: adding arc {} -> {} ({} -> {})\n", predId, dstId, showShort(predSK), showShort(dstSK)); addArc(predId, dstId, TransCFG::Arc::kUnknownWeight); } } } } // infer arc weights bool changed; do { changed = false; for (TransID tid : nodes()) { int64_t nodeWeight = weight(tid); if (inferredArcWeight(inArcs(tid), nodeWeight)) changed = true; if (inferredArcWeight(outArcs(tid), nodeWeight)) changed = true; } } while (changed); // guess weight or non-inferred arcs for (TransID tid : nodes()) { for (auto arc : outArcs(tid)) { if (arc->weight() == Arc::kUnknownWeight) { arc->setGuessed(); int64_t arcWgt = std::min(weight(arc->src()), weight(arc->dst())) / 2; arc->setWeight(arcWgt); } } } }
/* ------------------------------------------------------------------------------- Class: CSimpleTimeout Method: DoCancel Description: Cancel active request Parameters: None Return Values: None Errors/Exceptions: None Status: Approved ------------------------------------------------------------------------------- */ void CSimpleTimeout::DoCancel() { FTRACE(FPrint(_L("CSimpleTimeout::DoCancel"))); iTimer.Cancel(); }
/* * reoptimize() runs a trace through a second pass of TraceBuilder * optimizations, like this: * * reset state. * move all blocks to a temporary list. * compute immediate dominators. * for each block in trace order: * if we have a snapshot state for this block: * clear cse entries that don't dominate this block. * use snapshot state. * move all instructions to a temporary list. * for each instruction: * optimizeWork - do CSE and simplify again * if not simplified: * append existing instruction and update state. * else: * if the instruction has a result, insert a mov from the * simplified tmp to the original tmp and discard the instruction. * if the last conditional branch was turned into a jump, remove the * fall-through edge to the next block. */ void TraceBuilder::reoptimize() { FTRACE(5, "ReOptimize:vvvvvvvvvvvvvvvvvvvv\n"); SCOPE_EXIT { FTRACE(5, "ReOptimize:^^^^^^^^^^^^^^^^^^^^\n"); }; assert(m_savedBlocks.empty()); assert(!m_curWhere); m_state.setEnableCse(RuntimeOption::EvalHHIRCse); m_enableSimplification = RuntimeOption::EvalHHIRSimplification; if (!m_state.enableCse() && !m_enableSimplification) return; setConstrainGuards(false); BlockList sortedBlocks = rpoSortCfg(m_unit); auto const idoms = findDominators(m_unit, sortedBlocks); m_state.clear(); for (auto* block : rpoSortCfg(m_unit)) { FTRACE(5, "Block: {}\n", block->id()); m_state.startBlock(block); m_curBlock = block; auto instructions = std::move(block->instrs()); assert(block->empty()); while (!instructions.empty()) { auto *inst = &instructions.front(); instructions.pop_front(); // merging state looks at the current marker, and optimizeWork // below may create new instructions. Use the marker from this // instruction. assert(inst->marker().valid()); setMarker(inst->marker()); auto const tmp = optimizeWork(inst, idoms); // Can generate new instrs! if (!tmp) { // Could not optimize; keep the old instruction appendInstruction(inst); continue; } SSATmp* dst = inst->dst(); if (dst->type() != Type::None && dst != tmp) { // The result of optimization has a different destination than the inst. // Generate a mov(tmp->dst) to get result into dst. If we get here then // assume the last instruction in the block isn't a guard. If it was, // we would have to insert the mov on the fall-through edge. assert(block->empty() || !block->back().isBlockEnd()); IRInstruction* mov = m_unit.mov(dst, tmp, inst->marker()); appendInstruction(mov); } if (inst->isBlockEnd()) { // Not re-adding inst; replace it with a jump to the next block. auto next = inst->next(); appendInstruction(m_unit.gen(Jmp, inst->marker(), next)); inst->setTaken(nullptr); inst->setNext(nullptr); } } assert(!block->empty()); m_state.finishBlock(block); } }
SSATmp* TraceBuilder::optimizeWork(IRInstruction* inst, const folly::Optional<IdomVector>& idoms) { // Since some of these optimizations inspect tracked state, we don't // perform any of them on non-main traces. if (m_savedTraces.size() > 0) return nullptr; static DEBUG_ONLY __thread int instNest = 0; if (debug) ++instNest; SCOPE_EXIT { if (debug) --instNest; }; DEBUG_ONLY auto indent = [&] { return std::string(instNest * 2, ' '); }; FTRACE(1, "optimizing {}{}\n", indent(), inst->toString()); // First pass of tracebuilder optimizations try to replace an // instruction based on tracked state before we do anything else. // May mutate the IRInstruction in place (and return nullptr) or // return an SSATmp*. if (SSATmp* preOpt = preOptimize(inst)) { FTRACE(1, " {}preOptimize returned: {}\n", indent(), preOpt->inst()->toString()); return preOpt; } if (inst->op() == Nop) return nullptr; // copy propagation on inst source operands copyProp(inst); SSATmp* result = nullptr; if (m_enableSimplification) { result = m_simplifier.simplify(inst); if (result) { inst = result->inst(); if (inst->producesReference(0)) { // This effectively prevents CSE from kicking in below, which // would replace the instruction with an IncRef. That is // correct if the simplifier morphed the instruction, but it's // incorrect if the simplifier returned one of original // instruction sources. We currently have no way to // distinguish the two cases, so we prevent CSE completely for // now. return result; } } } if (m_state.enableCse() && inst->canCSE()) { SSATmp* cseResult = m_state.cseLookup(inst, idoms); if (cseResult) { // Found a dominating instruction that can be used instead of inst FTRACE(1, " {}cse found: {}\n", indent(), cseResult->inst()->toString()); assert(!inst->consumesReferences()); if (inst->producesReference(0)) { // Replace with an IncRef FTRACE(1, " {}cse of refcount-producing instruction\n", indent()); gen(IncRef, cseResult); } return cseResult; } } return result; }
// --------------------------------------------------------------------------- // Destructor. // --------------------------------------------------------------------------- // CDunBtPlugin::~CDunBtPlugin() { FTRACE(FPrint( _L( "CDunBtPlugin::~CDunBtPlugin()" ) )); Uninitialize(); FTRACE(FPrint( _L( "CDunBtPlugin::~CDunBtPlugin() complete" ) )); }
/** * Trace back to the guard that provided the type of val, if * any. Constrain it so its type will not be relaxed beyond the given * DataTypeCategory. Returns true iff one or more guard instructions * were constrained. */ bool TraceBuilder::constrainValue(SSATmp* const val, TypeConstraint tc) { if (!shouldConstrainGuards()) return false; if (!val) { FTRACE(1, "constrainValue(nullptr, {}), bailing\n", tc); return false; } FTRACE(1, "constrainValue({}, {})\n", *val->inst(), tc); auto inst = val->inst(); if (inst->is(LdLoc, LdLocAddr)) { // We've hit a LdLoc(Addr). If the source of the value is non-null and not // a FramePtr, it's a real value that was killed by a Call. The value won't // be live but it's ok to use it to track down the guard. auto source = inst->extra<LocalData>()->valSrc; if (!source) { // val was newly created in this trace. Nothing to constrain. FTRACE(2, " - valSrc is null, bailing\n"); return false; } // If valSrc is a FramePtr, it represents the frame the value was // originally loaded from. Look for the guard for this local. if (source->isA(Type::FramePtr)) { return constrainLocal(inst->extra<LocalId>()->locId, source, tc, "constrainValue"); } // Otherwise, keep chasing down the source of val. return constrainValue(source, tc); } else if (inst->is(LdStack, LdStackAddr)) { return constrainStack(inst->src(0), inst->extra<StackOffset>()->offset, tc); } else if (inst->is(CheckType, AssertType)) { // If the dest type of the instruction fits the constraint we want, we can // stop here without constraining any further. Otherwise, continue through // to the source. auto changed = false; if (inst->is(CheckType)) changed = constrainGuard(inst, tc) || changed; auto dstType = inst->typeParam(); if (!typeFitsConstraint(dstType, tc.category)) { changed = constrainValue(inst->src(0), tc) || changed; } return changed; } else if (inst->is(StRef, StRefNT, Box, BoxPtr)) { // If our caller cares about the inner type, propagate that through. // Otherwise we're done. if (tc.innerCat) { auto src = inst->src(inst->is(StRef, StRefNT) ? 1 : 0); tc.innerCat.reset(); return constrainValue(src, tc); } return false; } else if (inst->is(LdRef, Unbox, UnboxPtr)) { // Pass through to the source of the box, remembering that we care about // the inner type of the box. assert(!tc.innerCat); tc.innerCat = tc.category; return constrainValue(inst->src(0), tc); } else if (inst->isPassthrough()) { return constrainValue(inst->getPassthroughValue(), tc); } else { // Any instructions not special cased above produce a new value, so // there's no guard for us to constrain. FTRACE(2, " - value is new in this trace, bailing\n"); return false; } // TODO(t2598894): Should be able to do something with LdMem<T> here }
void RegionDesc::Block::addPredicted(SrcKey sk, TypePred pred) { FTRACE(2, "Block::addPredicted({}, {})\n", showShort(sk), show(pred)); assert(pred.type.subtypeOf(Type::Gen | Type::Cls)); assert(contains(sk)); m_typePreds.insert(std::make_pair(sk, pred)); }
/* * reoptimize() runs a trace through a second pass of TraceBuilder * optimizations, like this: * * reset state. * move all blocks to a temporary list. * compute immediate dominators. * for each block in trace order: * if we have a snapshot state for this block: * clear cse entries that don't dominate this block. * use snapshot state. * move all instructions to a temporary list. * for each instruction: * optimizeWork - do CSE and simplify again * if not simplified: * append existing instruction and update state. * else: * if the instruction has a result, insert a mov from the * simplified tmp to the original tmp and discard the instruction. * if the last conditional branch was turned into a jump, remove the * fall-through edge to the next block. */ void TraceBuilder::reoptimize() { FTRACE(5, "ReOptimize:vvvvvvvvvvvvvvvvvvvv\n"); SCOPE_EXIT { FTRACE(5, "ReOptimize:^^^^^^^^^^^^^^^^^^^^\n"); }; assert(m_curTrace == m_mainTrace.get()); assert(m_savedTraces.size() == 0); m_enableCse = RuntimeOption::EvalHHIRCse; m_enableSimplification = RuntimeOption::EvalHHIRSimplification; if (!m_enableCse && !m_enableSimplification) return; if (m_mainTrace->blocks().size() > RuntimeOption::EvalHHIRSimplificationMaxBlocks) { // TODO CSEHash::filter is very slow for large block sizes // t2135219 should address that return; } BlockList sortedBlocks = rpoSortCfg(m_mainTrace.get(), m_irFactory); auto const idoms = findDominators(sortedBlocks); clearTrackedState(); auto blocks = std::move(m_mainTrace->blocks()); assert(m_mainTrace->blocks().empty()); while (!blocks.empty()) { Block* block = blocks.front(); blocks.pop_front(); assert(block->trace() == m_mainTrace.get()); FTRACE(5, "Block: {}\n", block->id()); m_mainTrace->push_back(block); if (m_snapshots[block]) { useState(block); } auto instructions = std::move(block->instrs()); assert(block->empty()); while (!instructions.empty()) { auto *inst = &instructions.front(); instructions.pop_front(); // merging state looks at the current marker, and optimizeWork // below may create new instructions. Use the marker from this // instruction. assert(inst->marker().valid()); setMarker(inst->marker()); auto const tmp = optimizeWork(inst, idoms); // Can generate new instrs! if (!tmp) { // Could not optimize; keep the old instruction appendInstruction(inst, block); updateTrackedState(inst); continue; } SSATmp* dst = inst->dst(); if (dst->type() != Type::None && dst != tmp) { // The result of optimization has a different destination than the inst. // Generate a mov(tmp->dst) to get result into dst. If we get here then // assume the last instruction in the block isn't a guard. If it was, // we would have to insert the mov on the fall-through edge. assert(block->empty() || !block->back()->isBlockEnd()); IRInstruction* mov = m_irFactory.mov(dst, tmp, inst->marker()); appendInstruction(mov, block); updateTrackedState(mov); } // Not re-adding inst; remove the inst->taken edge if (inst->taken()) inst->setTaken(nullptr); } if (block->back()->isTerminal()) { // Could have converted a conditional branch to Jmp; clear next. block->setNext(nullptr); } else { // if the last instruction was a branch, we already saved state // for the target in updateTrackedState(). Now save state for // the fall-through path. saveState(block->next()); } } }
void RegionDesc::Block::addReffinessPred(SrcKey sk, const ReffinessPred& pred) { FTRACE(2, "Block::addReffinessPred({}, {})\n", showShort(sk), show(pred)); assert(contains(sk)); m_refPreds.insert(std::make_pair(sk, pred)); }
void region_prune_arcs(RegionDesc& region, std::vector<Type>* input) { FTRACE(4, "region_prune_arcs\n"); region.sortBlocks(); auto const sortedBlocks = region.blocks(); // Maps region block ids to their RPO ids. auto blockToRPO = std::unordered_map<RegionDesc::BlockId,uint32_t>{}; auto blockInfos = std::vector<BlockInfo>(sortedBlocks.size()); auto workQ = dataflow_worklist<uint32_t>(sortedBlocks.size()); for (auto rpoID = uint32_t{0}; rpoID < sortedBlocks.size(); ++rpoID) { auto const& b = sortedBlocks[rpoID]; auto& binfo = blockInfos[rpoID]; binfo.blockID = b->id(); blockToRPO[binfo.blockID] = rpoID; } workQ.push(0); blockInfos[0].in = entry_state(region, input); FTRACE(4, "Iterating:\n"); do { auto const rpoID = workQ.pop(); auto& binfo = blockInfos[rpoID]; FTRACE(4, "B{}\n", binfo.blockID); binfo.out = binfo.in; apply_transfer_function( binfo.out, region.block(binfo.blockID)->postConds() ); for (auto& succ : region.succs(binfo.blockID)) { auto const succRPO = blockToRPO.find(succ); assertx(succRPO != end(blockToRPO)); auto& succInfo = blockInfos[succRPO->second]; if (preconds_may_pass(*region.block(succInfo.blockID), binfo.out)) { if (merge_into(succInfo.in, binfo.out)) { FTRACE(5, " -> {}\n", succInfo.blockID); workQ.push(succRPO->second); } } } } while (!workQ.empty()); FTRACE(2, "\nPostConds fixed point:\n{}\n", [&] () -> std::string { auto ret = std::string{}; for (auto& s : blockInfos) { folly::format(&ret, "B{}:\n{}", s.blockID, show(s.in)); } return ret; }() ); // Now remove any edge that looks like it will unconditionally fail type // predictions, and completely remove any block that can't be reached. using ArcIDs = std::pair<RegionDesc::BlockId,RegionDesc::BlockId>; auto toRemove = std::vector<ArcIDs>{}; for (auto rpoID = uint32_t{0}; rpoID < sortedBlocks.size(); ++rpoID) { auto const& binfo = blockInfos[rpoID]; for (auto& succ : region.succs(binfo.blockID)) { auto const succRPO = blockToRPO.find(succ); assertx(succRPO != end(blockToRPO)); auto const& succInfo = blockInfos[succRPO->second]; if (!binfo.in.initialized || !succInfo.in.initialized || !preconds_may_pass(*region.block(succInfo.blockID), binfo.out)) { FTRACE(2, "Pruning arc: B{} -> B{}\n", binfo.blockID, succInfo.blockID); toRemove.emplace_back(binfo.blockID, succInfo.blockID); } } for (auto& r : toRemove) region.removeArc(r.first, r.second); toRemove.clear(); } // Get rid of the completely unreachable blocks, now that any arcs to/from // them are gone. for (auto rpoID = uint32_t{0}; rpoID < sortedBlocks.size(); ++rpoID) { auto const& binfo = blockInfos[rpoID]; if (!binfo.in.initialized) { FTRACE(2, "Pruning block: B{}\n", binfo.blockID); region.deleteBlock(binfo.blockID); } } FTRACE(2, "\n"); }
RegionDescPtr selectTraceletLegacy(const Transl::Tracelet& tlet) { typedef RegionDesc::Block Block; auto region = std::make_shared<RegionDesc>(); SrcKey sk(tlet.m_sk); auto unit = tlet.func()->unit(); const Func* topFunc = nullptr; Block* curBlock = nullptr; auto newBlock = [&](const Func* func, SrcKey start) { assert(curBlock == nullptr || curBlock->length() > 0); region->blocks.push_back( std::make_shared<Block>(func, start.offset(), 0)); curBlock = region->blocks.back().get(); }; newBlock(tlet.func(), sk); for (auto ni = tlet.m_instrStream.first; ni; ni = ni->next) { assert(sk == ni->source); assert(ni->unit() == unit); curBlock->addInstruction(); if ((curBlock->length() == 1 && ni->funcd != nullptr) || ni->funcd != topFunc) { topFunc = ni->funcd; curBlock->setKnownFunc(sk, topFunc); } if (ni->calleeTrace && !ni->calleeTrace->m_inliningFailed) { assert(ni->op() == OpFCall); assert(ni->funcd == ni->calleeTrace->func()); // This should be translated as an inlined call. Insert the blocks of the // callee in the region. auto const& callee = *ni->calleeTrace; curBlock->setInlinedCallee(ni->funcd); SrcKey cSk = callee.m_sk; Unit* cUnit = callee.func()->unit(); newBlock(callee.func(), cSk); for (auto cni = callee.m_instrStream.first; cni; cni = cni->next) { assert(cSk == cni->source); assert(cni->op() == OpRetC || cni->op() == OpContRetC || cni->op() == OpNativeImpl || !instrIsNonCallControlFlow(cni->op())); curBlock->addInstruction(); cSk.advance(cUnit); } if (ni->next) { sk.advance(unit); newBlock(tlet.func(), sk); } continue; } if (!ni->noOp && isFPassStar(ni->op())) { curBlock->setParamByRef(sk, ni->preppedByRef); } if (ni->next && ni->op() == OpJmp) { // A Jmp that isn't the final instruction in a Tracelet means we traced // through a forward jump in analyze. Update sk to point to the next NI // in the stream. auto dest = ni->offset() + ni->imm[0].u_BA; assert(dest > sk.offset()); // We only trace for forward Jmps for now. sk.setOffset(dest); // The Jmp terminates this block. newBlock(tlet.func(), sk); } else { sk.advance(unit); } } auto& frontBlock = *region->blocks.front(); // Add tracelet guards as predictions on the first instruction. Predictions // and known types from static analysis will be applied by // Translator::translateRegion. for (auto const& dep : tlet.m_dependencies) { if (dep.second->rtt.isVagueValue() || dep.second->location.isThis()) continue; typedef RegionDesc R; auto addPred = [&](const R::Location& loc) { auto type = Type::fromRuntimeType(dep.second->rtt); frontBlock.addPredicted(tlet.m_sk, {loc, type}); }; switch (dep.first.space) { case Transl::Location::Stack: addPred(R::Location::Stack{uint32_t(-dep.first.offset - 1)}); break; case Transl::Location::Local: addPred(R::Location::Local{uint32_t(dep.first.offset)}); break; default: not_reached(); } } // Add reffiness dependencies as predictions on the first instruction. for (auto const& dep : tlet.m_refDeps.m_arMap) { RegionDesc::ReffinessPred pred{dep.second.m_mask, dep.second.m_vals, dep.first}; frontBlock.addReffinessPred(tlet.m_sk, pred); } FTRACE(2, "Converted Tracelet:\n{}\nInto RegionDesc:\n{}\n", tlet.toString(), show(*region)); return region; }
void FontManager::MessageReceived(BMessage* message) { switch (message->what) { case B_NODE_MONITOR: { // TODO: support removing fonts! int32 opcode; if (message->FindInt32("opcode", &opcode) != B_OK) return; switch (opcode) { case B_ENTRY_CREATED: { const char* name; node_ref nodeRef; if (message->FindInt32("device", &nodeRef.device) != B_OK || message->FindInt64("directory", &nodeRef.node) != B_OK || message->FindString("name", &name) != B_OK) break; // TODO: make this better (possible under Haiku) snooze(100000); // let the font be written completely before trying to open it BEntry entry; if (set_entry(nodeRef, name, entry) != B_OK) break; if (entry.IsDirectory()) { // a new directory to watch for us _AddPath(entry); } else { // a new font font_directory* directory = _FindDirectory(nodeRef); if (directory == NULL) { // unknown directory? how come? break; } _AddFont(*directory, entry); } break; } case B_ENTRY_MOVED: { // has the entry been moved into a monitored directory or has // it been removed from one? const char* name; node_ref nodeRef; uint64 fromNode; uint64 node; if (message->FindInt32("device", &nodeRef.device) != B_OK || message->FindInt64("to directory", &nodeRef.node) != B_OK || message->FindInt64("from directory", (int64 *)&fromNode) != B_OK || message->FindInt64("node", (int64 *)&node) != B_OK || message->FindString("name", &name) != B_OK) break; font_directory* directory = _FindDirectory(nodeRef); BEntry entry; if (set_entry(nodeRef, name, entry) != B_OK) break; if (directory != NULL) { // something has been added to our watched font directories // test, if the source directory is one of ours as well nodeRef.node = fromNode; font_directory* fromDirectory = _FindDirectory(nodeRef); if (entry.IsDirectory()) { if (fromDirectory == NULL) { // there is a new directory to watch for us _AddPath(entry); FTRACE("new directory moved in"); } else { // A directory from our watched directories has // been renamed or moved within the watched // directories - we only need to update the // path names of the styles in that directory nodeRef.node = node; directory = _FindDirectory(nodeRef); if (directory != NULL) { for (int32 i = 0; i < directory->styles.CountItems(); i++) { FontStyle* style = directory->styles.ItemAt(i); style->UpdatePath(directory->directory); } } FTRACE("directory renamed"); } } else { if (fromDirectory != NULL) { // find style in source and move it to the target nodeRef.node = node; FontStyle* style = fromDirectory->FindStyle(nodeRef); if (style != NULL) { fromDirectory->styles.RemoveItem(style, false); directory->styles.AddItem(style); style->UpdatePath(directory->directory); } FTRACE(("font moved")); } else { FTRACE(("font added: %s\n", name)); _AddFont(*directory, entry); } } } else { // and entry has been removed from our font directories if (entry.IsDirectory()) { if (entry.GetNodeRef(&nodeRef) == B_OK && (directory = _FindDirectory(nodeRef)) != NULL) _RemoveDirectory(directory); } else { // remove font style from directory _RemoveStyle(nodeRef.device, fromNode, node); } } break; } case B_ENTRY_REMOVED: { node_ref nodeRef; uint64 directoryNode; if (message->FindInt32("device", &nodeRef.device) != B_OK || message->FindInt64("directory", (int64 *)&directoryNode) != B_OK || message->FindInt64("node", &nodeRef.node) != B_OK) break; font_directory* directory = _FindDirectory(nodeRef); if (directory != NULL) { // the directory has been removed, so we remove it as well _RemoveDirectory(directory); } else { // remove font style from directory _RemoveStyle(nodeRef.device, directoryNode, nodeRef.node); } break; } } break; } } }
void insertIncRefs(PrcEnv& env) { auto antQ = dataflow_worklist<uint32_t, std::less<uint32_t>>(env.rpoBlocks.size()); auto avlQ = dataflow_worklist<uint32_t, std::greater<uint32_t>>(env.rpoBlocks.size()); env.states.resize(env.unit.numBlocks()); for (uint32_t i = 0; i < env.rpoBlocks.size(); i++) { auto blk = env.rpoBlocks[i]; auto& state = env.states[blk->id()]; state.rpoId = i; if (blk->numSuccs()) state.antOut.set(); if (blk->numPreds()) state.avlIn.set(); antQ.push(i); avlQ.push(i); } auto id = 0; for (auto& v : env.insertMap) { for (auto const tmp : v) { auto const blk = tmp->inst()->block(); auto& state = env.states[blk->id()]; if (!state.local.test(id)) { state.local.set(id); continue; } } id++; } using Bits = PrcState::Bits; // compute anticipated do { auto const blk = env.rpoBlocks[antQ.pop()]; auto& state = env.states[blk->id()]; state.antIn = state.antOut | state.local; state.pantIn = state.pantOut | state.local; blk->forEachPred( [&] (Block* b) { auto& s = env.states[b->id()]; auto const antOut = s.antOut & state.antIn; auto const pantOut = s.pantOut | state.pantIn; if (antOut != s.antOut || pantOut != s.pantOut) { s.antOut = antOut; s.pantOut = pantOut; antQ.push(s.rpoId); } } ); } while (!antQ.empty()); // compute available do { auto const blk = env.rpoBlocks[avlQ.pop()]; auto& state = env.states[blk->id()]; state.avlOut = state.avlIn | state.local; blk->forEachSucc( [&] (Block* b) { auto& s = env.states[b->id()]; auto const avlIn = s.avlIn & state.avlOut; if (avlIn != s.avlIn) { s.avlIn = avlIn; avlQ.push(s.rpoId); } }); } while (!avlQ.empty()); for (auto blk : env.rpoBlocks) { auto& state = env.states[blk->id()]; FTRACE(4, "InsertIncDecs: Blk(B{}) <- {}\n" "{}" " ->{}\n", blk->id(), [&] { std::string ret; blk->forEachPred([&] (Block* pred) { folly::format(&ret, " B{}", pred->id()); }); return ret; }(), show(state), [&] { std::string ret; blk->forEachSucc([&] (Block* succ) { folly::format(&ret, " B{}", succ->id()); }); return ret; }()); auto inc = state.local; for (auto inc_id = 0; inc.any(); inc >>= 1, inc_id++) { if (inc.test(0)) { auto const& tmps = env.insertMap[inc_id]; auto insert = [&] (IRInstruction* inst) { FTRACE(3, "Inserting IncRef into B{}\n", blk->id()); auto const iter = std::next(blk->iteratorTo(inst)); blk->insert(iter, env.unit.gen(IncRef, inst->bcctx(), tmps[0])); }; SSATmp* last = nullptr; // Insert an IncRef after every candidate in this block except // the last one (since we know for all but the last that its // successor is anticipated). Note that entries in tmps from // the same block are guaranteed to be in program order. for (auto const tmp : tmps) { if (tmp->inst()->block() != blk) continue; if (last) insert(last->inst()); last = tmp; } // If it's partially anticipated out, insert an inc after the // last one too. always_assert(last); if (state.pantOut.test(inc_id)) insert(last->inst()); } } auto dec = state.avlIn & ~state.pantIn; if (dec.any()) { blk->forEachPred( [&] (Block* pred) { auto& pstate = env.states[pred->id()]; dec &= pstate.pantOut; }); for (auto dec_id = 0; dec.any(); dec >>= 1, dec_id++) { if (dec.test(0)) { FTRACE(3, "Inserting DecRef into B{}\n", blk->id()); auto const tmp = env.insertMap[dec_id][0]; blk->prepend(env.unit.gen(DecRef, tmp->inst()->bcctx(), DecRefData{}, tmp)); } } } }
void MemoryManager::resetStatsImpl(bool isInternalCall) { #ifdef USE_JEMALLOC FTRACE(1, "resetStatsImpl({}) pre:\n", isInternalCall); FTRACE(1, "usage: {}\nalloc: {}\npeak usage: {}\npeak alloc: {}\n", m_stats.usage, m_stats.alloc, m_stats.peakUsage, m_stats.peakAlloc); FTRACE(1, "total alloc: {}\nje alloc: {}\nje dealloc: {}\n", m_stats.totalAlloc, m_prevAllocated, m_prevDeallocated); FTRACE(1, "je debt: {}\n\n", m_stats.jemallocDebt); #else FTRACE(1, "resetStatsImpl({}) pre:\n" "usage: {}\nalloc: {}\npeak usage: {}\npeak alloc: {}\n\n", isInternalCall, m_stats.usage, m_stats.alloc, m_stats.peakUsage, m_stats.peakAlloc); #endif if (isInternalCall) { m_statsIntervalActive = false; m_stats.usage = 0; m_stats.alloc = 0; m_stats.peakUsage = 0; m_stats.peakAlloc = 0; m_stats.totalAlloc = 0; m_stats.peakIntervalUsage = 0; m_stats.peakIntervalAlloc = 0; #ifdef USE_JEMALLOC m_enableStatsSync = false; #endif } else { // This is only set by the jemalloc stats sync which we don't enable until // after this has been called. assert(m_stats.totalAlloc == 0); // We expect some thread local initialization to have been done already. assert(m_slabs.size() > 0); #ifdef USE_JEMALLOC assert(m_stats.jemallocDebt >= m_stats.alloc); #endif // The effect of this call is simply to ignore anything we've done *outside* // the smart allocator after we initialized to avoid attributing shared // structure initialization that happens during init_thread_locals() to this // session. // We don't want to clear the other values because we do already have some // sized smart allocator usage and live slabs and wiping now will result in // negative values when we try to reconcile our accounting with jemalloc. #ifdef USE_JEMALLOC // Anything that was definitively allocated by the smart allocator should // be counted in this number even if we're otherwise zeroing out the count // for each thread. m_stats.totalAlloc = s_statsEnabled ? m_stats.jemallocDebt : 0; // Ignore any attempt to sync jemalloc stats before this point since if we // do before this it is impossible to tell what the former usage was before // the sync. assert(!m_enableStatsSync); m_enableStatsSync = s_statsEnabled; #else m_stats.totalAlloc = 0; #endif } #ifdef USE_JEMALLOC if (s_statsEnabled) { m_stats.jemallocDebt = 0; m_prevDeallocated = *m_deallocated; m_prevAllocated = *m_allocated; } #endif #ifdef USE_JEMALLOC FTRACE(1, "resetStatsImpl({}) post:\n", isInternalCall); FTRACE(1, "usage: {}\nalloc: {}\npeak usage: {}\npeak alloc: {}\n", m_stats.usage, m_stats.alloc, m_stats.peakUsage, m_stats.peakAlloc); FTRACE(1, "total alloc: {}\nje alloc: {}\nje dealloc: {}\n", m_stats.totalAlloc, m_prevAllocated, m_prevDeallocated); FTRACE(1, "je debt: {}\n\n", m_stats.jemallocDebt); #else FTRACE(1, "resetStatsImpl({}) post:\n" "usage: {}\nalloc: {}\npeak usage: {}\npeak alloc: {}\n\n", isInternalCall, m_stats.usage, m_stats.alloc, m_stats.peakUsage, m_stats.peakAlloc); #endif }
// --------------------------------------------------------------------------- // // --------------------------------------------------------------------------- // void CRequestManager::ExecuteStartJobRequestL() { FLOG(_L("[IMAGEPRINTUI]<<< CRequestManager, ExecuteStartJobRequestL ")); iStart.iReqParam.Reset(); TUint layout = iCapabilityManager->Layout(); TUint quality = iCapabilityManager->Quality(); TUint paperSize = iCapabilityManager->PaperSize(); //fill request parameter by retrieved values TDpsArgsInt req_quality,req_papersize, req_layout; req_quality.iElement = EDpsArgQuality; req_quality.iContent = quality; iStart.iReqParam.iJobConfig.AppendL(req_quality); req_papersize.iElement = EDpsArgPaperSize; req_papersize.iContent = paperSize; iStart.iReqParam.iJobConfig.AppendL(req_papersize); req_layout.iElement = EDpsArgLayout; req_layout.iContent = layout; iStart.iReqParam.iJobConfig.AppendL(req_layout); // retrieve images FLOG(_L("[IMAGEPRINTUI]<<< CRequestManager, Get Images")); iImageArrayFlat = iAppUi->ImagesToPrint(); // not taking ownership iNumberOfImages = iImageArrayFlat->Count(); TDpsPrintInfo* helpTDpsPrintInfo = new (ELeave) TDpsPrintInfo[iNumberOfImages]; CleanupStack::PushL(helpTDpsPrintInfo); // go through the list of images and add info for start job request for(int i=0; i<iNumberOfImages; i++) { FLOG(_L("[IMAGEPRINTUI]<<< CRequestManager, Start job, append image")); // check if file really exist, use may have delete it after choose print // that will set printer unstable state iFileExist = ConeUtils::FileExists(iImageArrayFlat->operator[](i)); if(iFileExist) { FLOG(_L("[IMAGEPRINTUI]<<< CRequestManager, Start job, file exist")); helpTDpsPrintInfo[i].iFile.Copy(iImageArrayFlat->operator[](i)); iStart.iReqParam.iPrintInfo.AppendL(helpTDpsPrintInfo[i]); } } FTRACE(FPrint(_L("[IMAGEPRINTUI]\t CRequestManager iNumberOfImages is %d"), iNumberOfImages)); iAppUi->NumberOfImages(iNumberOfImages); if(!iFileExist) { FLOG(_L("[IMAGEPRINTUI]>>> CRequestManager, ExecuteStartJobRequestL, file not exist ")); iAppUi->Notes()->ShowErrorMsgL(R_ERROR_FILE_NOT_FOUND); } else { iDpsEngine->DoDpsRequestL(&iStart, iStatus); } CleanupStack::PopAndDestroy(helpTDpsPrintInfo); FLOG(_L("[IMAGEPRINTUI]>>> CRequestManager, ExecuteStartJobRequestL ")); }