lib/BCGen/HBC/Passes.cpp (711 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "hermes/BCGen/HBC/Passes.h" #include "hermes/BCGen/BCOpt.h" #include "hermes/BCGen/HBC/Bytecode.h" #include "hermes/BCGen/HBC/BytecodeGenerator.h" #include "hermes/BCGen/HBC/BytecodeStream.h" #include "hermes/BCGen/HBC/HBC.h" #include "hermes/BCGen/HBC/ISel.h" #include "hermes/BCGen/Lowering.h" #include "llvh/ADT/SetVector.h" #define DEBUG_TYPE "hbc-backend" namespace hermes { namespace hbc { namespace { /// In blockToFix, change all incoming Phi values from previousBlock to instead /// come from newBlock. void updateIncomingPhiValues( BasicBlock *blockToFix, BasicBlock *previousBlock, BasicBlock *newBlock) { for (auto &inst : *blockToFix) { auto *phi = llvh::dyn_cast<PhiInst>(&inst); if (!phi) return; for (int i = 0, e = phi->getNumEntries(); i < e; i++) { auto entry = phi->getEntry(i); if (entry.second == previousBlock) { phi->updateEntry(i, entry.first, newBlock); } } } } // Get the next instruction(s) after this Instruction. Creates branches as // necessary. llvh::SmallVector<Instruction *, 4> getInsertionPointsAfter( IRBuilder &builder, Instruction *inst) { llvh::SmallVector<Instruction *, 4> points; if (!llvh::isa<TerminatorInst>(inst)) { // Easy case for non-terminators. Just return the next inst. auto pos = inst->getIterator(); pos++; points.push_back(&*pos); return points; } for (int i = 0, e = inst->getNumOperands(); i < e; i++) { BasicBlock *target = llvh::dyn_cast<BasicBlock>(inst->getOperand(i)); if (!target) continue; auto *detour = builder.createBasicBlock(target->getParent()); builder.setInsertionBlock(detour); auto *bounce = builder.createBranchInst(target); inst->setOperand(detour, i); updateIncomingPhiValues(target, inst->getParent(), detour); points.push_back(bounce); } return points; } /// Update the insertion point of the builder to the "entry insertion point" /// of the function, which is where we insert new lowered instructions that must /// execute on entry. void updateToEntryInsertionPoint(IRBuilder &builder, Function *F) { auto &BB = F->front(); auto it = BB.begin(); auto end = BB.end(); // Skip all HBCCreateEnvironmentInst. while (it != end && llvh::isa<HBCCreateEnvironmentInst>(*it)) ++it; builder.setInsertionPoint(&*it); } } // namespace bool LoadConstants::operandMustBeLiteral(Instruction *Inst, unsigned opIndex) { // HBCLoadConstInst is meant to load a constant if (llvh::isa<HBCLoadConstInst>(Inst)) return true; // The operand of HBCLoadParamInst is a literal index. if (llvh::isa<HBCLoadParamInst>(Inst)) return true; if (llvh::isa<HBCAllocObjectFromBufferInst>(Inst)) return true; // All operands of AllocArrayInst are literals. if (llvh::isa<AllocArrayInst>(Inst)) return true; if (llvh::isa<AllocObjectInst>(Inst)) { // The AllocObjectInst::SizeIdx is a literal. if (opIndex == AllocObjectInst::SizeIdx) return true; // AllocObjectInst::ParentObjectIdx is a literal if it is the EmptySentinel. if (opIndex == AllocObjectInst::ParentObjectIdx && llvh::isa<EmptySentinel>(Inst->getOperand(opIndex))) return true; return false; } // SwitchInst's rest of the operands are case values, // hence they will stay as constant. if (llvh::isa<SwitchInst>(Inst) && opIndex > 0) return true; // StoreOwnPropertyInst and StoreNewOwnPropertyInst. if (auto *SOP = llvh::dyn_cast<StoreOwnPropertyInst>(Inst)) { if (opIndex == StoreOwnPropertyInst::PropertyIdx) { if (llvh::isa<StoreNewOwnPropertyInst>(Inst)) { // In StoreNewOwnPropertyInst the property name must be a literal. return true; } // If the propery is a LiteralNumber, the property is enumerable, and it // is a valid array index, it is coming from an array initialization and // we will emit it as PutByIndex. if (auto *LN = llvh::dyn_cast<LiteralNumber>(Inst->getOperand(opIndex))) { if (SOP->getIsEnumerable() && LN->convertToArrayIndex().hasValue()) return true; } } // StoreOwnPropertyInst's isEnumerable is a boolean constant. if (opIndex == StoreOwnPropertyInst::IsEnumerableIdx) return true; return false; } // If StorePropertyInst's property ID is a LiteralString, we will keep it // untouched and emit try_put_by_id eventually. if (llvh::isa<StorePropertyInst>(Inst) && opIndex == StorePropertyInst::PropertyIdx && llvh::isa<LiteralString>(Inst->getOperand(opIndex))) return true; // If LoadPropertyInst's property ID is a LiteralString, we will keep it // untouched and emit try_put_by_id eventually. if (llvh::isa<LoadPropertyInst>(Inst) && opIndex == LoadPropertyInst::PropertyIdx && llvh::isa<LiteralString>(Inst->getOperand(opIndex))) return true; // If DeletePropertyInst's property ID is a LiteralString, we will keep it // untouched and emit try_put_by_id eventually. if (llvh::isa<DeletePropertyInst>(Inst) && opIndex == DeletePropertyInst::PropertyIdx && llvh::isa<LiteralString>(Inst->getOperand(opIndex))) return true; // StoreGetterSetterInst's isEnumerable is a boolean constant. if (llvh::isa<StoreGetterSetterInst>(Inst) && opIndex == StoreGetterSetterInst::IsEnumerableIdx) return true; // Both pattern and flags operands of the CreateRegExpInst // are literal strings. if (llvh::isa<CreateRegExpInst>(Inst)) return true; if (llvh::isa<SwitchImmInst>(Inst) && (opIndex == SwitchImmInst::MinValueIdx || opIndex == SwitchImmInst::SizeIdx || opIndex >= SwitchImmInst::FirstCaseIdx)) return true; /// CallBuiltin's callee and "this" should always be literals. if (llvh::isa<CallBuiltinInst>(Inst) && (opIndex == CallBuiltinInst::CalleeIdx || opIndex == CallBuiltinInst::ThisIdx)) return true; /// GetBuiltinClosureInst's builtin index is always literal. if (llvh::isa<GetBuiltinClosureInst>(Inst) && opIndex == GetBuiltinClosureInst::BuiltinIndexIdx) return true; #ifdef HERMES_RUN_WASM /// CallIntrinsic's IntrinsicIndexIdx should always be literals. if (llvh::isa<CallIntrinsicInst>(Inst) && (opIndex == CallIntrinsicInst::IntrinsicIndexIdx)) return true; #endif if (llvh::isa<IteratorCloseInst>(Inst) && opIndex == IteratorCloseInst::IgnoreInnerExceptionIdx) { return true; } return false; } bool LoadConstants::runOnFunction(Function *F) { IRBuilder builder(F); bool changed = false; llvh::SmallDenseMap<Literal *, Instruction *, 8> constMap{}; // This is a bit counter-intuitive because the logic appears reversed. // We only want to unique the generated literals if optimization is disabled. // That is the case when it causes too many registers to be generated (one // per literal). // If optimization is enabled, that is not necessary because of CSE and // because doing this now interefers with code motion. const bool uniqueLiterals = !optimizationEnabled_; auto createLoadLiteral = [&builder](Literal *literal) -> Instruction * { return llvh::isa<GlobalObject>(literal) ? cast<Instruction>(builder.createHBCGetGlobalObjectInst()) : cast<Instruction>(builder.createHBCLoadConstInst(literal)); }; updateToEntryInsertionPoint(builder, F); for (BasicBlock &bbit : F->getBasicBlockList()) { for (auto &it : bbit.getInstList()) { for (unsigned i = 0, n = it.getNumOperands(); i < n; i++) { if (operandMustBeLiteral(&it, i)) continue; auto *operand = llvh::dyn_cast<Literal>(it.getOperand(i)); if (!operand) continue; Instruction *load = nullptr; if (uniqueLiterals) { auto &entry = constMap[operand]; if (!entry) entry = createLoadLiteral(operand); load = entry; } else { load = createLoadLiteral(operand); } it.setOperand(load, i); changed = true; } } } return changed; } bool LoadParameters::runOnFunction(Function *F) { IRBuilder builder(F); bool changed = false; updateToEntryInsertionPoint(builder, F); // Index of 0 is the "this" parameter. unsigned index = 1; for (Parameter *p : F->getParameters()) { auto *load = builder.createHBCLoadParamInst(builder.getLiteralNumber(index)); p->replaceAllUsesWith(load); index++; changed = true; } // Lower accesses to "this". auto *thisParam = F->getThisParameter(); if (thisParam && thisParam->hasUsers()) { // In strict mode just use param 0 directly. In non-strict, we must coerce // it to an object. Value *getThisInst = F->isStrictMode() ? cast<Value>( builder.createHBCLoadParamInst(builder.getLiteralNumber(0))) : cast<Value>(builder.createHBCGetThisNSInst()); thisParam->replaceAllUsesWith(getThisInst); changed = true; } return changed; } Instruction *LowerLoadStoreFrameInst::getScope( IRBuilder &builder, Variable *var, HBCCreateEnvironmentInst *captureScope) { if (var->getParent()->getFunction() != builder.getFunction()) { // If the variable is neither from the current scope, // we should get the proper scope for it. return builder.createHBCResolveEnvironment(var->getParent()); } else { // Now we know that the variable belongs to the current scope. // We are going to conservatively assume the variable might get // captured. Hence we use the newly created scope. // This will not cause performance issue as long as optimization // is enabled, because every variable will be moved to stack // if not being captured. return captureScope; } } bool LowerLoadStoreFrameInst::runOnFunction(Function *F) { IRBuilder builder(F); bool changed = false; updateToEntryInsertionPoint(builder, F); // All local captured variables will be stored in this scope (or // "environment"). // It will also be used by all closures created in this function, even if // there are no captured variables in this function. // Closures need a new environment even without captured variables because // we currently use only the lexical nesting level to determine which parent // environment to use - we don't account for the case when an environment may // not be needed somewhere along the chain. HBCCreateEnvironmentInst *captureScope = builder.createHBCCreateEnvironmentInst(); for (BasicBlock &BB : F->getBasicBlockList()) { for (auto I = BB.begin(), E = BB.end(); I != E; /* nothing */) { // Keep the reference and increment iterator first. Instruction *Inst = &*I; ++I; builder.setLocation(Inst->getLocation()); switch (Inst->getKind()) { case ValueKind::LoadFrameInstKind: { auto *LFI = cast<LoadFrameInst>(Inst); auto *var = LFI->getLoadVariable(); builder.setInsertionPoint(Inst); Instruction *scope = getScope(builder, var, captureScope); Instruction *newInst = builder.createHBCLoadFromEnvironmentInst(scope, var); Inst->replaceAllUsesWith(newInst); Inst->eraseFromParent(); changed = true; break; } case ValueKind::StoreFrameInstKind: { auto *SFI = cast<StoreFrameInst>(Inst); auto *var = SFI->getVariable(); auto *val = SFI->getValue(); builder.setInsertionPoint(Inst); Instruction *scope = getScope(builder, var, captureScope); builder.createHBCStoreToEnvironmentInst(scope, val, var); Inst->eraseFromParent(); changed = true; break; } case ValueKind::CreateFunctionInstKind: { auto *CFI = cast<CreateFunctionInst>(Inst); builder.setInsertionPoint(Inst); auto *newInst = builder.createHBCCreateFunctionInst( CFI->getFunctionCode(), captureScope); Inst->replaceAllUsesWith(newInst); Inst->eraseFromParent(); changed = true; break; } case ValueKind::CreateGeneratorInstKind: { auto *CFI = cast<CreateGeneratorInst>(Inst); builder.setInsertionPoint(Inst); auto *newInst = builder.createHBCCreateGeneratorInst( CFI->getFunctionCode(), captureScope); Inst->replaceAllUsesWith(newInst); Inst->eraseFromParent(); changed = true; break; } default: break; } } } return changed; } CreateArgumentsInst *LowerArgumentsArray::getCreateArgumentsInst(Function *F) { // CreateArgumentsInst is always in the first block in normal functions, // but is in the second block in GeneratorInnerFunctions. if (llvh::isa<GeneratorInnerFunction>(F)) { for (BasicBlock *succ : F->front().getTerminator()->successors()) { for (auto &inst : *succ) { if (auto *target = llvh::dyn_cast<CreateArgumentsInst>(&inst)) { return target; } } } } else { for (auto &inst : F->front()) { if (auto *target = llvh::dyn_cast<CreateArgumentsInst>(&inst)) { return target; } } } return nullptr; } bool LowerArgumentsArray::runOnFunction(Function *F) { IRBuilder builder(F); updateToEntryInsertionPoint(builder, F); CreateArgumentsInst *createArguments = getCreateArgumentsInst(F); if (!createArguments) { return false; } builder.setInsertionPoint(createArguments); AllocStackInst *lazyReg = builder.createAllocStackInst("arguments"); builder.createStoreStackInst(builder.getLiteralUndefined(), lazyReg); // Process all LoadPropertyInst's first because they may add another user // to the list of users of createArguments. // Specifically the case when `arguments[arguments]` is accessed. // Note that in such a case, a single LoadPropertyInst will appear twice in // the use list. Use a set so we only remove it once. llvh::SmallSetVector<Instruction *, 16> uniqueUsers; uniqueUsers.insert( createArguments->getUsers().begin(), createArguments->getUsers().end()); for (Value *user : uniqueUsers) { auto *load = llvh::dyn_cast<LoadPropertyInst>(user); if (load && load->getObject() == createArguments) { builder.setInsertionPoint(load); builder.setLocation(load->getLocation()); auto *propertyString = llvh::dyn_cast<LiteralString>(load->getProperty()); if (propertyString && propertyString->getValue().str() == "length") { // For `arguments.length`, get the length. auto *length = builder.createHBCGetArgumentsLengthInst(lazyReg); load->replaceAllUsesWith(length); load->eraseFromParent(); } else { // For all other property loads, get by index. auto *get = builder.createHBCGetArgumentsPropByValInst( load->getProperty(), lazyReg); load->replaceAllUsesWith(get); load->eraseFromParent(); } } } uniqueUsers.clear(); uniqueUsers.insert( createArguments->getUsers().begin(), createArguments->getUsers().end()); for (Value *user : uniqueUsers) { if (auto *phi = llvh::dyn_cast<PhiInst>(user)) { // We have to insert another branch where we can reify the value. for (int i = 0, n = phi->getNumEntries(); i < n; i++) { auto entry = phi->getEntry(i); if (entry.first != createArguments) continue; auto *previousBlock = cast<BasicBlock>(entry.second); auto *thisBlock = phi->getParent(); auto *newBlock = builder.createBasicBlock(F); builder.setInsertionBlock(newBlock); builder.createHBCReifyArgumentsInst(lazyReg); auto *reifiedValue = builder.createLoadStackInst(lazyReg); builder.createBranchInst(thisBlock); phi->updateEntry(i, reifiedValue, newBlock); auto *branch = previousBlock->getTerminator(); for (int j = 0, m = branch->getNumOperands(); j < m; j++) if (branch->getOperand(j) == thisBlock) branch->setOperand(newBlock, j); } } else if (auto *inst = llvh::dyn_cast<Instruction>(user)) { // For other users, insert a reification so we can replace // the usage with this array. builder.setInsertionPoint(inst); builder.setLocation(inst->getLocation()); builder.createHBCReifyArgumentsInst(lazyReg); auto *array = builder.createLoadStackInst(lazyReg); for (int i = 0, n = inst->getNumOperands(); i < n; i++) { if (inst->getOperand(i) == createArguments) { inst->setOperand(array, i); } } } else { llvm_unreachable("CreateArguments used for a non-Instruction."); } } createArguments->eraseFromParent(); return true; } bool DedupReifyArguments::runOnFunction(Function *F) { bool changed = false; // Check if there are any HBCReifyArgumentsInst in the function before // calculating dominator tree and reverse post order to save compile time. bool hasRAI = false; for (auto &BB : *F) { for (auto &inst : BB.getInstList()) { if (llvh::isa<HBCReifyArgumentsInst>(&inst)) { hasRAI = true; break; } } if (hasRAI) break; } if (!hasRAI) return false; DominanceInfo domInfo{F}; PostOrderAnalysis PO(F); IRBuilder::InstructionDestroyer destroyer; llvh::SmallVector<BasicBlock *, 16> reversePO(PO.rbegin(), PO.rend()); llvh::SmallVector<HBCReifyArgumentsInst *, 4> reifications; for (auto *BB : reversePO) { for (auto &inst : BB->getInstList()) { if (auto *reify = llvh::dyn_cast<HBCReifyArgumentsInst>(&inst)) { HBCReifyArgumentsInst *dominator = nullptr; for (auto *possibleParent : reifications) { if (domInfo.properlyDominates(possibleParent, reify)) { dominator = possibleParent; break; } } if (dominator) { destroyer.add(reify); changed = true; } else { reifications.push_back(reify); } } } } return changed; } bool LowerConstruction::runOnFunction(Function *F) { IRBuilder builder(F); auto *prototypeString = builder.getLiteralString("prototype"); for (BasicBlock &BB : F->getBasicBlockList()) { IRBuilder::InstructionDestroyer destroyer; for (Instruction &I : BB) { if (auto *constructor = llvh::dyn_cast<ConstructInst>(&I)) { builder.setInsertionPoint(constructor); builder.setLocation(constructor->getLocation()); auto closure = constructor->getCallee(); auto prototype = builder.createLoadPropertyInst(closure, prototypeString); auto thisObject = builder.createHBCCreateThisInst(prototype, closure); llvh::SmallVector<Value *, 8> args; for (int i = 1, n = constructor->getNumArguments(); i < n; i++) { args.push_back(constructor->getArgument(i)); } auto newConstructor = builder.createHBCConstructInst(closure, thisObject, args); auto finalValue = builder.createHBCGetConstructedObjectInst( thisObject, newConstructor); constructor->replaceAllUsesWith(finalValue); destroyer.add(constructor); } } } return true; } bool LowerCalls::runOnFunction(Function *F) { IRBuilder builder(F); bool changed = false; for (auto &BB : *F) { for (auto &I : BB) { auto *call = llvh::dyn_cast<CallInst>(&I); // This also matches constructors. if (!call) continue; builder.setInsertionPoint(call); changed = true; auto reg = RA_.getLastRegister().getIndex() - HVMRegisterAllocator::CALL_EXTRA_REGISTERS; for (int i = 0, e = call->getNumArguments(); i < e; i++, --reg) { // If this is a Call instruction, emit explicit Movs to the argument // registers. If this is a CallN instruction, emit ImplicitMovs // instead, to express that these registers get written to by the CallN, // even though they are not the destination. // Lastly, if this is argument 0 of CallBuiltinInst emit ImplicitMov to // encode that the "this" register is implicitly set to undefined. Value *arg = call->getArgument(i); if (llvh::isa<HBCCallNInst>(call) || (i == 0 && llvh::isa<CallBuiltinInst>(call))) { auto *imov = builder.createImplicitMovInst(arg); RA_.updateRegister(imov, Register(reg)); } else { auto *mov = builder.createMovInst(arg); RA_.updateRegister(mov, Register(reg)); call->setArgument(mov, i); } } } } return changed; } bool RecreateCheapValues::runOnFunction(Function *F) { IRBuilder builder(F); llvh::SmallPtrSet<Instruction *, 4> potentiallyUnused; bool changed = false; for (auto &BB : *F) { IRBuilder::InstructionDestroyer destroyer; for (auto &I : BB) { auto *mov = llvh::dyn_cast<MovInst>(&I); if (!mov) continue; auto *load = llvh::dyn_cast<HBCLoadConstInst>(mov->getSingleOperand()); if (!load) continue; Literal *literal = load->getConst(); switch (literal->getKind()) { case ValueKind::LiteralUndefinedKind: case ValueKind::LiteralNullKind: case ValueKind::LiteralBoolKind: break; case ValueKind::LiteralNumberKind: if (cast<LiteralNumber>(literal)->isPositiveZero()) { break; } continue; default: continue; } builder.setInsertionPoint(mov); auto *recreation = builder.createHBCLoadConstInst(literal); RA_.updateRegister(recreation, RA_.getRegister(mov)); mov->replaceAllUsesWith(recreation); destroyer.add(mov); potentiallyUnused.insert(load); changed = true; } } { IRBuilder::InstructionDestroyer destroyer; for (auto *inst : potentiallyUnused) { if (!inst->hasUsers()) { destroyer.add(inst); } } } return changed; } bool LoadConstantValueNumbering::runOnFunction(Function *F) { IRBuilder builder(F); bool changed = false; for (auto &BB : *F) { IRBuilder::InstructionDestroyer destroyer; // Maps a register number to the instruction that last modified it. // Every Instruction is either an HBCLoadConstInst or a Mov whose // operand is a HBCLoadConstInst llvh::DenseMap<unsigned, Instruction *> regToInstMap{}; for (auto &I : BB) { HBCLoadConstInst *loadI{nullptr}; // Value numbering currently only tracks the values of registers that // have a constant in them, or that have had a constant moved in them. if (!(loadI = llvh::dyn_cast<HBCLoadConstInst>(&I))) { if (auto *movI = llvh::dyn_cast<MovInst>(&I)) { loadI = llvh::dyn_cast<HBCLoadConstInst>(movI->getSingleOperand()); } } if (RA_.isAllocated(&I)) { unsigned reg = RA_.getRegister(&I).getIndex(); if (loadI) { auto it = regToInstMap.find(reg); if (it != regToInstMap.end()) { auto prevI = it->second; HBCLoadConstInst *prevLoad{nullptr}; // If the key is found, the instruction must be either an // HBCLoadConstInst, or a Mov whose operand is an HBCLoadConstInst. if (!(prevLoad = llvh::dyn_cast<HBCLoadConstInst>(prevI))) { prevLoad = llvh::dyn_cast<HBCLoadConstInst>(prevI->getOperand(0)); } if (prevLoad->isIdenticalTo(loadI)) { I.replaceAllUsesWith(prevI); destroyer.add(&I); changed = true; continue; } } regToInstMap[reg] = &I; continue; } regToInstMap.erase(reg); } for (auto index : I.getChangedOperands()) { auto *operand = I.getOperand(index); unsigned reg = RA_.getRegister(cast<Instruction>(operand)).getIndex(); regToInstMap.erase(reg); } } } return changed; } bool SpillRegisters::requiresShortOutput(Instruction *I) { if (llvh::isa<TerminatorInst>(I)) { // None of our terminators produce results at all // (though GetNextPNameInst modifies operands). return false; } // Instructions that produce no output, don't use the register, even when // allocated. if (I->getType().isNoType()) return false; switch (I->getKind()) { // Some instructions become Movs or other opcodes with long variants: case ValueKind::HBCSpillMovInstKind: case ValueKind::LoadStackInstKind: case ValueKind::MovInstKind: case ValueKind::PhiInstKind: // Some instructions aren't actually encoded at all: case ValueKind::AllocStackInstKind: case ValueKind::TryEndInstKind: case ValueKind::TryStartInstKind: return false; default: return true; } } bool SpillRegisters::requiresShortOperand(Instruction *I, int op) { switch (I->getKind()) { case ValueKind::PhiInstKind: case ValueKind::MovInstKind: case ValueKind::HBCSpillMovInstKind: case ValueKind::LoadStackInstKind: case ValueKind::StoreStackInstKind: return false; case ValueKind::CallInstKind: case ValueKind::ConstructInstKind: case ValueKind::CallBuiltinInstKind: case ValueKind::HBCConstructInstKind: case ValueKind::HBCCallDirectInstKind: return op == 0; default: return true; } } bool SpillRegisters::modifiesOperandRegister(Instruction *I, int op) { return I->getChangedOperands().at((unsigned)op); } bool SpillRegisters::runOnFunction(Function *F) { if (RA_.getMaxRegisterUsage() < boundary_) { return false; } reserveLowRegisters(F); IRBuilder builder(F); llvh::SmallVector<std::pair<Instruction *, Register>, 2> toSpill; for (BasicBlock &BB : F->getBasicBlockList()) { for (Instruction &inst : BB) { if (!RA_.isAllocated(&inst)) { // This instruction is dead. Don't bother spilling. continue; } int tempReg = 0; toSpill.clear(); bool replaceWithFirstSpill = false; builder.setLocation(inst.getLocation()); auto myRegister = RA_.getRegister(&inst); if (requiresShortOutput(&inst) && !isShort(myRegister)) { auto temp = getReserved(tempReg++); RA_.updateRegister(&inst, temp); toSpill.push_back( std::pair<Instruction *, Register>(&inst, myRegister)); replaceWithFirstSpill = true; } for (int i = 0, e = inst.getNumOperands(); i < e; i++) { auto *op = llvh::dyn_cast<Instruction>(inst.getOperand(i)); if (!op || !RA_.isAllocated(op)) { // This is either not an instruction, or a dead instruction. // Either way, we don't have to do anything. continue; } auto opRegister = RA_.getRegister(op); if (requiresShortOperand(&inst, i) && !isShort(opRegister)) { auto temp = getReserved(tempReg++); builder.setInsertionPoint(&inst); auto *load = builder.createHBCSpillMovInst(op); RA_.updateRegister(load, temp); inst.setOperand(load, i); if (modifiesOperandRegister(&inst, i)) { toSpill.push_back( std::pair<Instruction *, Register>(load, opRegister)); } } } if (toSpill.size()) { auto spillPoints = getInsertionPointsAfter(builder, &inst); assert( (!replaceWithFirstSpill || spillPoints.size() <= 1) && "Asked to spill the value of a TerminatorInst. Our terminators " "shouldn't produce values. It wouldn't have mattered, but it " "also has multiple branches so users might need PhiInsts."); for (auto *point : spillPoints) { builder.setInsertionPoint(point); for (auto store : toSpill) { auto *storeInst = builder.createHBCSpillMovInst(store.first); RA_.updateRegister(storeInst, store.second); if (!replaceWithFirstSpill) continue; // Replace all uses of the inst with the spilling inst inst.replaceAllUsesWith(storeInst); // Except for the actual spill of course storeInst->setOperand(&inst, 0); // Disable now that the job is done. replaceWithFirstSpill = false; } } } } } return true; } bool LowerSwitchIntoJumpTables::runOnFunction(Function *F) { bool changed = false; llvh::SmallVector<SwitchInst *, 4> switches; // Collect all switch instructions. for (BasicBlock &BB : *F) for (auto &it : BB) { if (auto *S = llvh::dyn_cast<SwitchInst>(&it)) switches.push_back(S); } for (auto *S : switches) { if (lowerIntoJumpTable(S)) changed = true; } return changed; } bool LowerSwitchIntoJumpTables::lowerIntoJumpTable(SwitchInst *switchInst) { // if its a constant switch don't bother if (llvh::isa<Literal>(switchInst->getInputValue())) { return false; } IRBuilder builder(switchInst->getParent()->getParent()); unsigned numCases = switchInst->getNumCasePair(); uint32_t minValue = 0; uint32_t maxValue = 0; llvh::SmallVector<LiteralNumber *, 8> values; llvh::SmallVector<BasicBlock *, 8> blocks; for (unsigned i = 0; i != numCases; ++i) { auto casePair = switchInst->getCasePair(i); auto *lit = casePair.first; auto *num = llvh::dyn_cast<LiteralNumber>(lit); if (!num) return false; // Check whether it is representable as uint32. if (auto ival = num->isIntTypeRepresentible<uint32_t>()) { values.push_back(num); blocks.push_back(casePair.second); if (i == 0) { minValue = maxValue = ival.getValue(); } else { minValue = std::min(minValue, ival.getValue()); maxValue = std::max(maxValue, ival.getValue()); } } else { return false; } } assert(minValue <= maxValue && "Minimum cannot exceed maximum"); uint32_t range = maxValue - minValue; // We can't generate a table for a zero-sized range. if (range == 0) return false; // The number of cases is range + 1, which must fit in a uint32. if (range == std::numeric_limits<uint32_t>::max()) return false; // Check the "denseness" of the cases. // Don't convert small switches. if (range / numCases > 5 || numCases < 10) return false; builder.setInsertionPoint(switchInst); auto *switchImmInst = builder.createSwitchImmInst( switchInst->getInputValue(), switchInst->getDefaultDestination(), builder.getLiteralNumber(minValue), builder.getLiteralNumber(range + 1), values, blocks); switchInst->replaceAllUsesWith(switchImmInst); switchInst->eraseFromParent(); return true; } } // namespace hbc } // namespace hermes #undef DEBUG_TYPE