/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=4 sw=4 et tw=99: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released * May 28, 2008. * * The Initial Developer of the Original Code is * Brendan Eich * * Contributor(s): * David Anderson * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "jscntxt.h" #include "FrameState.h" #include "FrameState-inl.h" using namespace js; using namespace js::mjit; /* Because of Value alignment */ JS_STATIC_ASSERT(sizeof(FrameEntry) % 8 == 0); FrameState::FrameState(JSContext *cx, JSScript *script, JSFunction *fun, Assembler &masm) : cx(cx), script(script), fun(fun), nargs(fun ? fun->nargs : 0), masm(masm), entries(NULL), #if defined JS_NUNBOX32 reifier(cx, *thisFromCtor()), #endif closedVars(NULL), closedArgs(NULL), usesArguments(script->usesArguments), inTryBlock(false) { } FrameState::~FrameState() { cx->free(entries); } bool FrameState::init() { // nslots + nargs + 2 (callee, this) uint32 nentries = feLimit(); if (!nentries) { sp = spBase = locals = args = NULL; return true; } eval = script->usesEval || cx->compartment->debugMode; size_t totalBytes = sizeof(FrameEntry) * nentries + // entries[], w/ callee+this sizeof(FrameEntry *) * nentries + // tracker.entries (eval ? 0 : sizeof(JSPackedBool) * script->nslots) + // closedVars[] (eval || usesArguments ? 0 : sizeof(JSPackedBool) * nargs); // closedArgs[] uint8 *cursor = (uint8 *)cx->calloc(totalBytes); if (!cursor) return false; #if defined JS_NUNBOX32 if (!reifier.init(nentries)) return false; #endif entries = (FrameEntry *)cursor; cursor += sizeof(FrameEntry) * nentries; callee_ = entries; this_ = entries + 1; args = entries + 2; locals = args + nargs; spBase = locals + script->nfixed; sp = spBase; tracker.entries = (FrameEntry **)cursor; cursor += sizeof(FrameEntry *) * nentries; if (!eval) { if (script->nslots) { closedVars = (JSPackedBool *)cursor; cursor += sizeof(JSPackedBool) * script->nslots; } if (!usesArguments && nargs) { closedArgs = (JSPackedBool *)cursor; cursor += sizeof(JSPackedBool) * nargs; } } JS_ASSERT(reinterpret_cast(entries) + totalBytes == cursor); return true; } void FrameState::takeReg(RegisterID reg) { if (freeRegs.hasReg(reg)) { freeRegs.takeReg(reg); JS_ASSERT(!regstate[reg].usedBy()); } else { JS_ASSERT(regstate[reg].fe()); evictReg(reg); regstate[reg].forget(); } } void FrameState::evictReg(RegisterID reg) { FrameEntry *fe = regstate[reg].fe(); if (regstate[reg].type() == RematInfo::TYPE) { ensureTypeSynced(fe, masm); fe->type.setMemory(); } else { ensureDataSynced(fe, masm); fe->data.setMemory(); } } JSC::MacroAssembler::RegisterID FrameState::evictSomeReg(uint32 mask) { #ifdef DEBUG bool fallbackSet = false; #endif RegisterID fallback = Registers::ReturnReg; for (uint32 i = 0; i < JSC::MacroAssembler::TotalRegisters; i++) { RegisterID reg = RegisterID(i); /* Register is not allocatable, don't bother. */ if (!(Registers::maskReg(reg) & mask)) continue; /* Register is not owned by the FrameState. */ FrameEntry *fe = regstate[i].fe(); if (!fe) continue; /* Try to find a candidate... that doesn't need spilling. */ #ifdef DEBUG fallbackSet = true; #endif fallback = reg; if (regstate[i].type() == RematInfo::TYPE && fe->type.synced()) { fe->type.setMemory(); return fallback; } if (regstate[i].type() == RematInfo::DATA && fe->data.synced()) { fe->data.setMemory(); return fallback; } } JS_ASSERT(fallbackSet); evictReg(fallback); return fallback; } void FrameState::syncAndForgetEverything() { syncAndKill(Registers(Registers::AvailRegs), Uses(frameSlots())); forgetEverything(); } void FrameState::resetInternalState() { for (uint32 i = 0; i < tracker.nentries; i++) tracker[i]->untrack(); tracker.reset(); freeRegs.reset(); } void FrameState::discardFrame() { resetInternalState(); memset(regstate, 0, sizeof(regstate)); } void FrameState::forgetEverything() { resetInternalState(); #ifdef DEBUG for (uint32 i = 0; i < JSC::MacroAssembler::TotalRegisters; i++) { JS_ASSERT(!regstate[i].usedBy()); } #endif } void FrameState::storeTo(FrameEntry *fe, Address address, bool popped) { if (fe->isConstant()) { masm.storeValue(fe->getValue(), address); return; } if (fe->isCopy()) fe = fe->copyOf(); /* Cannot clobber the address's register. */ JS_ASSERT(!freeRegs.hasReg(address.base)); /* If loading from memory, ensure destination differs. */ JS_ASSERT_IF((fe->type.inMemory() || fe->data.inMemory()), addressOf(fe).base != address.base || addressOf(fe).offset != address.offset); #if defined JS_PUNBOX64 if (fe->type.inMemory() && fe->data.inMemory()) { /* Future optimization: track that the Value is in a register. */ RegisterID vreg = Registers::ValueReg; masm.loadPtr(addressOf(fe), vreg); masm.storePtr(vreg, address); return; } /* * If dreg is obtained via allocReg(), then calling * pinReg() trips an assertion. But in all other cases, * calling pinReg() is necessary in the fe->type.inMemory() path. * Remember whether pinReg() can be safely called. */ bool canPinDreg = true; bool wasInRegister = fe->data.inRegister(); /* Get a register for the payload. */ MaybeRegisterID dreg; if (fe->data.inRegister()) { dreg = fe->data.reg(); } else { JS_ASSERT(fe->data.inMemory()); if (popped) { dreg = allocReg(); canPinDreg = false; } else { dreg = allocReg(fe, RematInfo::DATA); fe->data.setRegister(dreg.reg()); } masm.loadPayload(addressOf(fe), dreg.reg()); } /* Store the Value. */ if (fe->type.inRegister()) { masm.storeValueFromComponents(fe->type.reg(), dreg.reg(), address); } else if (fe->isTypeKnown()) { masm.storeValueFromComponents(ImmType(fe->getKnownType()), dreg.reg(), address); } else { JS_ASSERT(fe->type.inMemory()); if (canPinDreg) pinReg(dreg.reg()); RegisterID treg = popped ? allocReg() : allocReg(fe, RematInfo::TYPE); masm.loadTypeTag(addressOf(fe), treg); masm.storeValueFromComponents(treg, dreg.reg(), address); if (popped) freeReg(treg); else fe->type.setRegister(treg); if (canPinDreg) unpinReg(dreg.reg()); } /* If register is untracked, free it. */ if (!wasInRegister && popped) freeReg(dreg.reg()); #elif defined JS_NUNBOX32 if (fe->data.inRegister()) { masm.storePayload(fe->data.reg(), address); } else { JS_ASSERT(fe->data.inMemory()); RegisterID reg = popped ? allocReg() : allocReg(fe, RematInfo::DATA); masm.loadPayload(addressOf(fe), reg); masm.storePayload(reg, address); if (popped) freeReg(reg); else fe->data.setRegister(reg); } if (fe->isTypeKnown()) { masm.storeTypeTag(ImmType(fe->getKnownType()), address); } else if (fe->type.inRegister()) { masm.storeTypeTag(fe->type.reg(), address); } else { JS_ASSERT(fe->type.inMemory()); RegisterID reg = popped ? allocReg() : allocReg(fe, RematInfo::TYPE); masm.loadTypeTag(addressOf(fe), reg); masm.storeTypeTag(reg, address); if (popped) freeReg(reg); else fe->type.setRegister(reg); } #endif } void FrameState::loadThisForReturn(RegisterID typeReg, RegisterID dataReg, RegisterID tempReg) { return loadForReturn(getThis(), typeReg, dataReg, tempReg); } void FrameState::loadForReturn(FrameEntry *fe, RegisterID typeReg, RegisterID dataReg, RegisterID tempReg) { JS_ASSERT(dataReg != typeReg && dataReg != tempReg && typeReg != tempReg); if (fe->isConstant()) { masm.loadValueAsComponents(fe->getValue(), typeReg, dataReg); return; } if (fe->isCopy()) fe = fe->copyOf(); MaybeRegisterID maybeType = maybePinType(fe); MaybeRegisterID maybeData = maybePinData(fe); if (fe->isTypeKnown()) { // If the data is in memory, or in the wrong reg, load/move it. if (!maybeData.isSet()) masm.loadPayload(addressOf(fe), dataReg); else if (maybeData.reg() != dataReg) masm.move(maybeData.reg(), dataReg); masm.move(ImmType(fe->getKnownType()), typeReg); return; } // If both halves of the value are in memory, make this easier and load // both pieces into their respective registers. if (fe->type.inMemory() && fe->data.inMemory()) { masm.loadValueAsComponents(addressOf(fe), typeReg, dataReg); return; } // Now, we should be guaranteed that at least one part is in a register. JS_ASSERT(maybeType.isSet() || maybeData.isSet()); // Make sure we have two registers while making sure not clobber either half. // Here we are allowed to mess up the FrameState invariants, because this // is specialized code for a path that is about to discard the entire frame. if (!maybeType.isSet()) { JS_ASSERT(maybeData.isSet()); if (maybeData.reg() != typeReg) maybeType = typeReg; else maybeType = tempReg; masm.loadTypeTag(addressOf(fe), maybeType.reg()); } else if (!maybeData.isSet()) { JS_ASSERT(maybeType.isSet()); if (maybeType.reg() != dataReg) maybeData = dataReg; else maybeData = tempReg; masm.loadPayload(addressOf(fe), maybeData.reg()); } RegisterID type = maybeType.reg(); RegisterID data = maybeData.reg(); if (data == typeReg && type == dataReg) { masm.move(type, tempReg); masm.move(data, dataReg); masm.move(tempReg, typeReg); } else if (data != dataReg) { if (type == typeReg) { masm.move(data, dataReg); } else if (type != dataReg) { masm.move(data, dataReg); if (type != typeReg) masm.move(type, typeReg); } else { JS_ASSERT(data != typeReg); masm.move(type, typeReg); masm.move(data, dataReg); } } else if (type != typeReg) { masm.move(type, typeReg); } } #ifdef DEBUG void FrameState::assertValidRegisterState() const { Registers checkedFreeRegs; for (uint32 i = 0; i < tracker.nentries; i++) { FrameEntry *fe = tracker[i]; if (fe >= sp) continue; JS_ASSERT(i == fe->trackerIndex()); JS_ASSERT_IF(fe->isCopy(), fe->trackerIndex() > fe->copyOf()->trackerIndex()); JS_ASSERT_IF(fe->isCopy(), fe > fe->copyOf()); JS_ASSERT_IF(fe->isCopy(), !fe->type.inRegister() && !fe->data.inRegister()); JS_ASSERT_IF(fe->isCopy(), fe->copyOf() < sp); JS_ASSERT_IF(fe->isCopy(), fe->copyOf()->isCopied()); if (fe->isCopy()) continue; if (fe->type.inRegister()) { checkedFreeRegs.takeReg(fe->type.reg()); JS_ASSERT(regstate[fe->type.reg()].fe() == fe); } if (fe->data.inRegister()) { checkedFreeRegs.takeReg(fe->data.reg()); JS_ASSERT(regstate[fe->data.reg()].fe() == fe); } } JS_ASSERT(checkedFreeRegs == freeRegs); for (uint32 i = 0; i < JSC::MacroAssembler::TotalRegisters; i++) { JS_ASSERT(!regstate[i].isPinned()); JS_ASSERT_IF(regstate[i].fe(), !freeRegs.hasReg(RegisterID(i))); JS_ASSERT_IF(regstate[i].fe(), regstate[i].fe()->isTracked()); } } #endif #if defined JS_NUNBOX32 void FrameState::syncFancy(Assembler &masm, Registers avail, FrameEntry *resumeAt, FrameEntry *bottom) const { reifier.reset(&masm, avail, resumeAt, bottom); for (FrameEntry *fe = resumeAt; fe >= bottom; fe--) { if (!fe->isTracked()) continue; reifier.sync(fe); } } #endif void FrameState::sync(Assembler &masm, Uses uses) const { if (!entries) return; /* Sync all registers up-front. */ Registers allRegs(Registers::AvailRegs); while (!allRegs.empty()) { RegisterID reg = allRegs.takeAnyReg(); FrameEntry *fe = regstate[reg].usedBy(); if (!fe) continue; JS_ASSERT(fe->isTracked()); #if defined JS_PUNBOX64 /* Sync entire FE to prevent loads. */ ensureFeSynced(fe, masm); /* Take the other register in the pair, if one exists. */ if (regstate[reg].type() == RematInfo::DATA && fe->type.inRegister()) allRegs.takeReg(fe->type.reg()); else if (regstate[reg].type() == RematInfo::TYPE && fe->data.inRegister()) allRegs.takeReg(fe->data.reg()); #elif defined JS_NUNBOX32 /* Sync register if unsynced. */ if (regstate[reg].type() == RematInfo::DATA) { JS_ASSERT(fe->data.reg() == reg); ensureDataSynced(fe, masm); } else { JS_ASSERT(fe->type.reg() == reg); ensureTypeSynced(fe, masm); } #endif } /* * Keep track of free registers using a bitmask. If we have to drop into * syncFancy(), then this mask will help avoid eviction. */ Registers avail(freeRegs); Registers temp(Registers::TempRegs); FrameEntry *bottom = sp - uses.nuses; for (FrameEntry *fe = sp - 1; fe >= bottom; fe--) { if (!fe->isTracked()) continue; FrameEntry *backing = fe; if (!fe->isCopy()) { if (fe->data.inRegister()) avail.putReg(fe->data.reg()); if (fe->type.inRegister()) avail.putReg(fe->type.reg()); } else { backing = fe->copyOf(); JS_ASSERT(!backing->isConstant() && !fe->isConstant()); #if defined JS_PUNBOX64 if ((!fe->type.synced() && backing->type.inMemory()) || (!fe->data.synced() && backing->data.inMemory())) { RegisterID syncReg = Registers::ValueReg; /* Load the entire Value into syncReg. */ if (backing->type.synced() && backing->data.synced()) { masm.loadValue(addressOf(backing), syncReg); } else if (backing->type.inMemory()) { masm.loadTypeTag(addressOf(backing), syncReg); masm.orPtr(backing->data.reg(), syncReg); } else { JS_ASSERT(backing->data.inMemory()); masm.loadPayload(addressOf(backing), syncReg); if (backing->isTypeKnown()) masm.orPtr(ImmType(backing->getKnownType()), syncReg); else masm.orPtr(backing->type.reg(), syncReg); } masm.storeValue(syncReg, addressOf(fe)); continue; } #elif defined JS_NUNBOX32 /* Fall back to a slower sync algorithm if load required. */ if ((!fe->type.synced() && backing->type.inMemory()) || (!fe->data.synced() && backing->data.inMemory())) { syncFancy(masm, avail, fe, bottom); return; } #endif } /* If a part still needs syncing, it is either a copy or constant. */ #if defined JS_PUNBOX64 /* All register-backed FEs have been entirely synced up-front. */ if (!fe->type.inRegister() && !fe->data.inRegister()) ensureFeSynced(fe, masm); #elif defined JS_NUNBOX32 /* All components held in registers have been already synced. */ if (!fe->data.inRegister()) ensureDataSynced(fe, masm); if (!fe->type.inRegister()) ensureTypeSynced(fe, masm); #endif } } void FrameState::syncAndKill(Registers kill, Uses uses, Uses ignore) { FrameEntry *spStop = sp - ignore.nuses; /* Sync all kill-registers up-front. */ Registers search(kill.freeMask & ~freeRegs.freeMask); while (!search.empty()) { RegisterID reg = search.takeAnyReg(); FrameEntry *fe = regstate[reg].usedBy(); if (!fe || fe >= spStop) continue; JS_ASSERT(fe->isTracked()); #if defined JS_PUNBOX64 /* Don't use syncFe(), since that may clobber more registers. */ ensureFeSynced(fe, masm); if (!fe->type.synced()) fe->type.sync(); if (!fe->data.synced()) fe->data.sync(); /* Take the other register in the pair, if one exists. */ if (regstate[reg].type() == RematInfo::DATA) { JS_ASSERT(fe->data.reg() == reg); if (fe->type.inRegister() && search.hasReg(fe->type.reg())) search.takeReg(fe->type.reg()); } else { JS_ASSERT(fe->type.reg() == reg); if (fe->data.inRegister() && search.hasReg(fe->data.reg())) search.takeReg(fe->data.reg()); } #elif defined JS_NUNBOX32 /* Sync this register. */ if (regstate[reg].type() == RematInfo::DATA) { JS_ASSERT(fe->data.reg() == reg); syncData(fe); } else { JS_ASSERT(fe->type.reg() == reg); syncType(fe); } #endif } uint32 maxvisits = tracker.nentries; FrameEntry *bottom = sp - uses.nuses; for (FrameEntry *fe = sp - 1; fe >= bottom && maxvisits; fe--) { if (!fe->isTracked()) continue; maxvisits--; if (fe >= spStop) continue; syncFe(fe); /* Forget registers. */ if (fe->data.inRegister() && kill.hasReg(fe->data.reg()) && !regstate[fe->data.reg()].isPinned()) { forgetReg(fe->data.reg()); fe->data.setMemory(); } if (fe->type.inRegister() && kill.hasReg(fe->type.reg()) && !regstate[fe->type.reg()].isPinned()) { forgetReg(fe->type.reg()); fe->type.setMemory(); } } /* * Anything still alive at this point is guaranteed to be synced. However, * it is necessary to evict temporary registers. */ search = Registers(kill.freeMask & ~freeRegs.freeMask); while (!search.empty()) { RegisterID reg = search.takeAnyReg(); FrameEntry *fe = regstate[reg].usedBy(); if (!fe || fe >= spStop) continue; JS_ASSERT(fe->isTracked()); if (regstate[reg].type() == RematInfo::DATA) { JS_ASSERT(fe->data.reg() == reg); JS_ASSERT(fe->data.synced()); fe->data.setMemory(); } else { JS_ASSERT(fe->type.reg() == reg); JS_ASSERT(fe->type.synced()); fe->type.setMemory(); } forgetReg(reg); } } void FrameState::merge(Assembler &masm, Changes changes) const { Registers search(Registers::AvailRegs & ~freeRegs.freeMask); while (!search.empty()) { RegisterID reg = search.peekReg(); FrameEntry *fe = regstate[reg].usedBy(); if (!fe) { search.takeReg(reg); continue; } if (fe->data.inRegister() && fe->type.inRegister()) { search.takeReg(fe->data.reg()); search.takeReg(fe->type.reg()); masm.loadValueAsComponents(addressOf(fe), fe->type.reg(), fe->data.reg()); } else { if (fe->data.inRegister()) { search.takeReg(fe->data.reg()); masm.loadPayload(addressOf(fe), fe->data.reg()); } if (fe->type.inRegister()) { search.takeReg(fe->type.reg()); masm.loadTypeTag(addressOf(fe), fe->type.reg()); } } } } JSC::MacroAssembler::RegisterID FrameState::copyDataIntoReg(FrameEntry *fe) { return copyDataIntoReg(this->masm, fe); } void FrameState::copyDataIntoReg(FrameEntry *fe, RegisterID hint) { JS_ASSERT(!fe->data.isConstant()); if (fe->isCopy()) fe = fe->copyOf(); if (!fe->data.inRegister()) tempRegForData(fe); RegisterID reg = fe->data.reg(); if (reg == hint) { if (freeRegs.empty()) { ensureDataSynced(fe, masm); fe->data.setMemory(); } else { reg = allocReg(); masm.move(hint, reg); fe->data.setRegister(reg); regstate[reg].associate(regstate[hint].fe(), RematInfo::DATA); } regstate[hint].forget(); } else { pinReg(reg); takeReg(hint); unpinReg(reg); masm.move(reg, hint); } } JSC::MacroAssembler::RegisterID FrameState::copyDataIntoReg(Assembler &masm, FrameEntry *fe) { JS_ASSERT(!fe->data.isConstant()); if (fe->isCopy()) fe = fe->copyOf(); if (fe->data.inRegister()) { RegisterID reg = fe->data.reg(); if (freeRegs.empty()) { ensureDataSynced(fe, masm); fe->data.setMemory(); regstate[reg].forget(); } else { RegisterID newReg = allocReg(); masm.move(reg, newReg); reg = newReg; } return reg; } RegisterID reg = allocReg(); if (!freeRegs.empty()) masm.move(tempRegForData(fe), reg); else masm.loadPayload(addressOf(fe),reg); return reg; } JSC::MacroAssembler::RegisterID FrameState::copyTypeIntoReg(FrameEntry *fe) { JS_ASSERT(!fe->type.isConstant()); if (fe->isCopy()) fe = fe->copyOf(); if (fe->type.inRegister()) { RegisterID reg = fe->type.reg(); if (freeRegs.empty()) { ensureTypeSynced(fe, masm); fe->type.setMemory(); regstate[reg].forget(); } else { RegisterID newReg = allocReg(); masm.move(reg, newReg); reg = newReg; } return reg; } RegisterID reg = allocReg(); if (!freeRegs.empty()) masm.move(tempRegForType(fe), reg); else masm.loadTypeTag(addressOf(fe), reg); return reg; } JSC::MacroAssembler::RegisterID FrameState::copyInt32ConstantIntoReg(FrameEntry *fe) { return copyInt32ConstantIntoReg(masm, fe); } JSC::MacroAssembler::RegisterID FrameState::copyInt32ConstantIntoReg(Assembler &masm, FrameEntry *fe) { JS_ASSERT(fe->data.isConstant()); if (fe->isCopy()) fe = fe->copyOf(); RegisterID reg = allocReg(); masm.move(Imm32(fe->getValue().toInt32()), reg); return reg; } JSC::MacroAssembler::FPRegisterID FrameState::copyEntryIntoFPReg(FrameEntry *fe, FPRegisterID fpreg) { return copyEntryIntoFPReg(this->masm, fe, fpreg); } JSC::MacroAssembler::FPRegisterID FrameState::copyEntryIntoFPReg(Assembler &masm, FrameEntry *fe, FPRegisterID fpreg) { if (fe->isCopy()) fe = fe->copyOf(); ensureFeSynced(fe, masm); masm.loadDouble(addressOf(fe), fpreg); return fpreg; } JSC::MacroAssembler::RegisterID FrameState::ownRegForType(FrameEntry *fe) { JS_ASSERT(!fe->type.isConstant()); RegisterID reg; if (fe->isCopy()) { /* For now, just do an extra move. The reg must be mutable. */ FrameEntry *backing = fe->copyOf(); if (!backing->type.inRegister()) { JS_ASSERT(backing->type.inMemory()); tempRegForType(backing); } if (freeRegs.empty()) { /* For now... just steal the register that already exists. */ ensureTypeSynced(backing, masm); reg = backing->type.reg(); backing->type.setMemory(); regstate[reg].forget(); } else { reg = allocReg(); masm.move(backing->type.reg(), reg); } return reg; } if (fe->type.inRegister()) { reg = fe->type.reg(); /* Remove ownership of this register. */ JS_ASSERT(regstate[reg].fe() == fe); JS_ASSERT(regstate[reg].type() == RematInfo::TYPE); regstate[reg].forget(); fe->type.invalidate(); } else { JS_ASSERT(fe->type.inMemory()); reg = allocReg(); masm.loadTypeTag(addressOf(fe), reg); } return reg; } JSC::MacroAssembler::RegisterID FrameState::ownRegForData(FrameEntry *fe) { JS_ASSERT(!fe->data.isConstant()); RegisterID reg; if (fe->isCopy()) { /* For now, just do an extra move. The reg must be mutable. */ FrameEntry *backing = fe->copyOf(); if (!backing->data.inRegister()) { JS_ASSERT(backing->data.inMemory()); tempRegForData(backing); } if (freeRegs.empty()) { /* For now... just steal the register that already exists. */ ensureDataSynced(backing, masm); reg = backing->data.reg(); backing->data.setMemory(); regstate[reg].forget(); } else { reg = allocReg(); masm.move(backing->data.reg(), reg); } return reg; } if (fe->isCopied()) { FrameEntry *copy = uncopy(fe); if (fe->isCopied()) { fe->type.invalidate(); fe->data.invalidate(); return copyDataIntoReg(copy); } } if (fe->data.inRegister()) { reg = fe->data.reg(); /* Remove ownership of this register. */ JS_ASSERT(regstate[reg].fe() == fe); JS_ASSERT(regstate[reg].type() == RematInfo::DATA); regstate[reg].forget(); fe->data.invalidate(); } else { JS_ASSERT(fe->data.inMemory()); reg = allocReg(); masm.loadPayload(addressOf(fe), reg); } return reg; } void FrameState::discardFe(FrameEntry *fe) { forgetEntry(fe); fe->type.setMemory(); fe->data.setMemory(); } void FrameState::pushCopyOf(uint32 index) { FrameEntry *backing = entryFor(index); FrameEntry *fe = rawPush(); fe->resetUnsynced(); if (backing->isConstant()) { fe->setConstant(Jsvalify(backing->getValue())); } else { if (backing->isTypeKnown()) fe->setType(backing->getKnownType()); else fe->type.invalidate(); fe->isNumber = backing->isNumber; fe->data.invalidate(); if (backing->isCopy()) { backing = backing->copyOf(); fe->setCopyOf(backing); } else { fe->setCopyOf(backing); backing->setCopied(); } /* Maintain tracker ordering guarantees for copies. */ JS_ASSERT(backing->isCopied()); if (fe->trackerIndex() < backing->trackerIndex()) swapInTracker(fe, backing); } } FrameEntry * FrameState::walkTrackerForUncopy(FrameEntry *original) { uint32 firstCopy = InvalidIndex; FrameEntry *bestFe = NULL; uint32 ncopies = 0; for (uint32 i = original->trackerIndex() + 1; i < tracker.nentries; i++) { FrameEntry *fe = tracker[i]; if (fe >= sp) continue; if (fe->isCopy() && fe->copyOf() == original) { if (firstCopy == InvalidIndex) { firstCopy = i; bestFe = fe; } else if (fe < bestFe) { bestFe = fe; } ncopies++; } } if (!ncopies) { JS_ASSERT(firstCopy == InvalidIndex); JS_ASSERT(!bestFe); return NULL; } JS_ASSERT(firstCopy != InvalidIndex); JS_ASSERT(bestFe); JS_ASSERT(bestFe > original); /* Mark all extra copies as copies of the new backing index. */ bestFe->setCopyOf(NULL); if (ncopies > 1) { bestFe->setCopied(); for (uint32 i = firstCopy; i < tracker.nentries; i++) { FrameEntry *other = tracker[i]; if (other >= sp || other == bestFe) continue; /* The original must be tracked before copies. */ JS_ASSERT(other != original); if (!other->isCopy() || other->copyOf() != original) continue; other->setCopyOf(bestFe); /* * This is safe even though we're mutating during iteration. There * are two cases. The first is that both indexes are <= i, and :. * will never be observed. The other case is we're placing the * other FE such that it will be observed later. Luckily, copyOf() * will return != original, so nothing will happen. */ if (other->trackerIndex() < bestFe->trackerIndex()) swapInTracker(bestFe, other); } } else { bestFe->setNotCopied(); } return bestFe; } FrameEntry * FrameState::walkFrameForUncopy(FrameEntry *original) { FrameEntry *bestFe = NULL; uint32 ncopies = 0; /* It's only necessary to visit as many FEs are being tracked. */ uint32 maxvisits = tracker.nentries; for (FrameEntry *fe = original + 1; fe < sp && maxvisits; fe++) { if (!fe->isTracked()) continue; maxvisits--; if (fe->isCopy() && fe->copyOf() == original) { if (!bestFe) { bestFe = fe; bestFe->setCopyOf(NULL); } else { fe->setCopyOf(bestFe); if (fe->trackerIndex() < bestFe->trackerIndex()) swapInTracker(bestFe, fe); } ncopies++; } } if (ncopies) bestFe->setCopied(); return bestFe; } FrameEntry * FrameState::uncopy(FrameEntry *original) { JS_ASSERT(original->isCopied()); /* * Copies have three critical invariants: * 1) The backing store precedes all copies in the tracker. * 2) The backing store precedes all copies in the FrameState. * 3) The backing store of a copy cannot be popped from the stack * while the copy is still live. * * Maintaining this invariant iteratively is kind of hard, so we choose * the "lowest" copy in the frame up-front. * * For example, if the stack is: * [A, B, C, D] * And the tracker has: * [A, D, C, B] * * If B, C, and D are copies of A - we will walk the tracker to the end * and select B, not D (see bug 583684). * * Note: |tracker.nentries <= (nslots + nargs)|. However, this walk is * sub-optimal if |tracker.nentries - original->trackerIndex() > sp - original|. * With large scripts this may be a problem worth investigating. Note that * the tracker is walked twice, so we multiply by 2 for pessimism. */ FrameEntry *fe; if ((tracker.nentries - original->trackerIndex()) * 2 > uint32(sp - original)) fe = walkFrameForUncopy(original); else fe = walkTrackerForUncopy(original); if (!fe) { original->setNotCopied(); return NULL; } /* * Switch the new backing store to the old backing store. During * this process we also necessarily make sure the copy can be * synced. */ if (!original->isTypeKnown()) { /* * If the copy is unsynced, and the original is in memory, * give the original a register. We do this below too; it's * okay if it's spilled. */ if (original->type.inMemory() && !fe->type.synced()) tempRegForType(original); fe->type.inherit(original->type); if (fe->type.inRegister()) regstate[fe->type.reg()].reassociate(fe); } else { JS_ASSERT(fe->isTypeKnown()); JS_ASSERT(fe->getKnownType() == original->getKnownType()); } if (original->data.inMemory() && !fe->data.synced()) tempRegForData(original); fe->data.inherit(original->data); if (fe->data.inRegister()) regstate[fe->data.reg()].reassociate(fe); return fe; } void FrameState::finishStore(FrameEntry *fe, bool closed) { // Make sure the backing store entry is synced to memory, then if it's // closed, forget it entirely (removing all copies) and reset it to a // synced, in-memory state. syncFe(fe); if (closed) { if (!fe->isCopy()) forgetEntry(fe); fe->resetSynced(); } } void FrameState::storeLocal(uint32 n, bool popGuaranteed, bool typeChange) { FrameEntry *local = getLocal(n); storeTop(local, popGuaranteed, typeChange); bool closed = isClosedVar(n); if (!closed && !inTryBlock) return; finishStore(local, closed); } void FrameState::storeArg(uint32 n, bool popGuaranteed) { // Note that args are always immediately synced, because they can be // aliased (but not written to) via f.arguments. FrameEntry *arg = getArg(n); storeTop(arg, popGuaranteed, true); finishStore(arg, isClosedArg(n)); } void FrameState::forgetEntry(FrameEntry *fe) { if (fe->isCopied()) { uncopy(fe); if (!fe->isCopied()) forgetAllRegs(fe); } else { forgetAllRegs(fe); } } void FrameState::storeTop(FrameEntry *target, bool popGuaranteed, bool typeChange) { bool wasSynced = target->type.synced(); /* Detect something like (x = x) which is a no-op. */ FrameEntry *top = peek(-1); if (top->isCopy() && top->copyOf() == target) { JS_ASSERT(target->isCopied()); return; } /* Completely invalidate the local variable. */ forgetEntry(target); target->resetUnsynced(); /* Constants are easy to propagate. */ if (top->isConstant()) { target->setCopyOf(NULL); target->setNotCopied(); target->setConstant(Jsvalify(top->getValue())); return; } /* * When dealing with copies, there are three important invariants: * * 1) The backing store precedes all copies in the tracker. * 2) The backing store precedes all copies in the FrameState. * 2) The backing store of a local is never a stack slot, UNLESS the local * variable itself is a stack slot (blocks) that precedes the stack * slot. * * If the top is a copy, and the second condition holds true, the local * can be rewritten as a copy of the original backing slot. If the first * condition does not hold, force it to hold by swapping in-place. */ FrameEntry *backing = top; bool copied = false; if (top->isCopy()) { backing = top->copyOf(); JS_ASSERT(backing->trackerIndex() < top->trackerIndex()); if (backing < target) { /* local.idx < backing.idx means local cannot be a copy yet */ if (target->trackerIndex() < backing->trackerIndex()) swapInTracker(backing, target); target->setNotCopied(); target->setCopyOf(backing); if (backing->isTypeKnown()) target->setType(backing->getKnownType()); else target->type.invalidate(); target->data.invalidate(); target->isNumber = backing->isNumber; return; } /* * If control flow lands here, then there was a bytecode sequence like * * ENTERBLOCK 2 * GETLOCAL 1 * SETLOCAL 0 * * The problem is slot N can't be backed by M if M could be popped * before N. We want a guarantee that when we pop M, even if it was * copied, it has no outstanding copies. * * Because of |let| expressions, it's kind of hard to really know * whether a region on the stack will be popped all at once. Bleh! * * This should be rare except in browser code (and maybe even then), * but even so there's a quick workaround. We take all copies of the * backing fe, and redirect them to be copies of the destination. */ for (uint32 i = backing->trackerIndex() + 1; i < tracker.nentries; i++) { FrameEntry *fe = tracker[i]; if (fe >= sp) continue; if (fe->isCopy() && fe->copyOf() == backing) { fe->setCopyOf(target); copied = true; } } } backing->setNotCopied(); /* * This is valid from the top->isCopy() path because we're guaranteed a * consistent ordering - all copies of |backing| are tracked after * |backing|. Transitively, only one swap is needed. */ if (backing->trackerIndex() < target->trackerIndex()) swapInTracker(backing, target); /* * Move the backing store down - we spill registers here, but we could be * smarter and re-use the type reg. */ RegisterID reg = tempRegForData(backing); target->data.setRegister(reg); regstate[reg].reassociate(target); if (typeChange) { if (backing->isTypeKnown()) { target->setType(backing->getKnownType()); } else { RegisterID reg = tempRegForType(backing); target->type.setRegister(reg); regstate[reg].reassociate(target); } } else { if (!wasSynced) masm.storeTypeTag(ImmType(backing->getKnownType()), addressOf(target)); target->type.setMemory(); } if (!backing->isTypeKnown()) backing->type.invalidate(); backing->data.invalidate(); backing->setCopyOf(target); backing->isNumber = target->isNumber; JS_ASSERT(top->copyOf() == target); /* * Right now, |backing| is a copy of |target| (note the reversal), but * |target| is not marked as copied. This is an optimization so uncopy() * may avoid frame traversal. * * There are two cases where we must set the copy bit, however: * - The fixup phase redirected more copies to |target|. * - An immediate pop is not guaranteed. */ if (copied || !popGuaranteed) target->setCopied(); } void FrameState::shimmy(uint32 n) { JS_ASSERT(sp - n >= spBase); int32 depth = 0 - int32(n); storeTop(peek(depth - 1), true); popn(n); } void FrameState::shift(int32 n) { JS_ASSERT(n < 0); JS_ASSERT(sp + n - 1 >= spBase); storeTop(peek(n - 1), true); pop(); } void FrameState::pinEntry(FrameEntry *fe, ValueRemat &vr) { if (fe->isConstant()) { vr = ValueRemat::FromConstant(fe->getValue()); } else { // Pin the type register so it can't spill. MaybeRegisterID maybePinnedType = maybePinType(fe); // Get and pin the data register. RegisterID dataReg = tempRegForData(fe); pinReg(dataReg); if (fe->isTypeKnown()) { vr = ValueRemat::FromKnownType(fe->getKnownType(), dataReg); } else { // The type might not be loaded yet, so unpin for simplicity. maybeUnpinReg(maybePinnedType); vr = ValueRemat::FromRegisters(tempRegForType(fe), dataReg); pinReg(vr.typeReg()); } } // Set these bits last, since allocation could have caused a sync. vr.isDataSynced = fe->data.synced(); vr.isTypeSynced = fe->type.synced(); } void FrameState::unpinEntry(const ValueRemat &vr) { if (!vr.isConstant()) { if (!vr.isTypeKnown()) unpinReg(vr.typeReg()); unpinReg(vr.dataReg()); } } void FrameState::ensureValueSynced(Assembler &masm, FrameEntry *fe, const ValueRemat &vr) { #if defined JS_PUNBOX64 if (!vr.isDataSynced || !vr.isTypeSynced) masm.storeValue(vr, addressOf(fe)); #elif defined JS_NUNBOX32 if (vr.isConstant()) { if (!vr.isDataSynced || !vr.isTypeSynced) masm.storeValue(vr.value(), addressOf(fe)); } else { if (!vr.isDataSynced) masm.storePayload(vr.dataReg(), addressOf(fe)); if (!vr.isTypeSynced) { if (vr.isTypeKnown()) masm.storeTypeTag(ImmType(vr.knownType()), addressOf(fe)); else masm.storeTypeTag(vr.typeReg(), addressOf(fe)); } } #endif } static inline bool AllocHelper(RematInfo &info, MaybeRegisterID &maybe) { if (info.inRegister()) { maybe = info.reg(); return true; } return false; } void FrameState::allocForSameBinary(FrameEntry *fe, JSOp op, BinaryAlloc &alloc) { if (!fe->isTypeKnown()) { alloc.lhsType = tempRegForType(fe); pinReg(alloc.lhsType.reg()); } alloc.lhsData = tempRegForData(fe); if (!freeRegs.empty()) { alloc.result = allocReg(); masm.move(alloc.lhsData.reg(), alloc.result); alloc.lhsNeedsRemat = false; } else { alloc.result = alloc.lhsData.reg(); takeReg(alloc.result); alloc.lhsNeedsRemat = true; } if (alloc.lhsType.isSet()) unpinReg(alloc.lhsType.reg()); } void FrameState::ensureFullRegs(FrameEntry *fe, MaybeRegisterID *type, MaybeRegisterID *data) { fe = fe->isCopy() ? fe->copyOf() : fe; JS_ASSERT(!data->isSet() && !type->isSet()); if (!fe->type.inMemory()) { if (fe->type.inRegister()) *type = fe->type.reg(); if (fe->data.isConstant()) return; if (fe->data.inRegister()) { *data = fe->data.reg(); return; } if (fe->type.inRegister()) pinReg(fe->type.reg()); *data = tempRegForData(fe); if (fe->type.inRegister()) unpinReg(fe->type.reg()); } else if (!fe->data.inMemory()) { if (fe->data.inRegister()) *data = fe->data.reg(); if (fe->type.isConstant()) return; if (fe->type.inRegister()) { *type = fe->type.reg(); return; } if (fe->data.inRegister()) pinReg(fe->data.reg()); *type = tempRegForType(fe); if (fe->data.inRegister()) unpinReg(fe->data.reg()); } else { *data = tempRegForData(fe); pinReg(data->reg()); *type = tempRegForType(fe); unpinReg(data->reg()); } } void FrameState::allocForBinary(FrameEntry *lhs, FrameEntry *rhs, JSOp op, BinaryAlloc &alloc, bool needsResult) { FrameEntry *backingLeft = lhs; FrameEntry *backingRight = rhs; if (backingLeft->isCopy()) backingLeft = backingLeft->copyOf(); if (backingRight->isCopy()) backingRight = backingRight->copyOf(); /* * For each remat piece of both FEs, if a register is assigned, get it now * and pin it. This is safe - constants and known types will be avoided. */ if (AllocHelper(backingLeft->type, alloc.lhsType)) pinReg(alloc.lhsType.reg()); if (AllocHelper(backingLeft->data, alloc.lhsData)) pinReg(alloc.lhsData.reg()); if (AllocHelper(backingRight->type, alloc.rhsType)) pinReg(alloc.rhsType.reg()); if (AllocHelper(backingRight->data, alloc.rhsData)) pinReg(alloc.rhsData.reg()); /* For each type without a register, give it a register if needed. */ if (!alloc.lhsType.isSet() && backingLeft->type.inMemory()) { alloc.lhsType = tempRegForType(lhs); pinReg(alloc.lhsType.reg()); } if (!alloc.rhsType.isSet() && backingRight->type.inMemory()) { alloc.rhsType = tempRegForType(rhs); pinReg(alloc.rhsType.reg()); } bool commu; switch (op) { case JSOP_EQ: case JSOP_GT: case JSOP_GE: case JSOP_LT: case JSOP_LE: /* fall through */ case JSOP_ADD: case JSOP_MUL: case JSOP_SUB: commu = true; break; case JSOP_DIV: commu = false; break; default: JS_NOT_REACHED("unknown op"); return; } /* * Data is a little more complicated. If the op is MUL, not all CPUs * have multiplication on immediates, so a register is needed. Also, * if the op is not commutative, the LHS _must_ be in a register. */ JS_ASSERT_IF(lhs->isConstant(), !rhs->isConstant()); JS_ASSERT_IF(rhs->isConstant(), !lhs->isConstant()); if (!alloc.lhsData.isSet()) { if (backingLeft->data.inMemory()) { alloc.lhsData = tempRegForData(lhs); pinReg(alloc.lhsData.reg()); } else if (op == JSOP_MUL || !commu) { JS_ASSERT(lhs->isConstant()); alloc.lhsData = allocReg(); alloc.extraFree = alloc.lhsData; masm.move(Imm32(lhs->getValue().toInt32()), alloc.lhsData.reg()); } } if (!alloc.rhsData.isSet()) { if (backingRight->data.inMemory()) { alloc.rhsData = tempRegForData(rhs); pinReg(alloc.rhsData.reg()); } else if (op == JSOP_MUL) { JS_ASSERT(rhs->isConstant()); alloc.rhsData = allocReg(); alloc.extraFree = alloc.rhsData; masm.move(Imm32(rhs->getValue().toInt32()), alloc.rhsData.reg()); } } alloc.lhsNeedsRemat = false; alloc.rhsNeedsRemat = false; if (!needsResult) goto skip; /* * Now a result register is needed. It must contain a mutable copy of the * LHS. For commutative operations, we can opt to use the RHS instead. At * this point, if for some reason either must be in a register, that has * already been guaranteed at this point. */ if (!freeRegs.empty()) { /* Free reg - just grab it. */ alloc.result = allocReg(); if (!alloc.lhsData.isSet()) { JS_ASSERT(alloc.rhsData.isSet()); JS_ASSERT(commu); masm.move(alloc.rhsData.reg(), alloc.result); alloc.resultHasRhs = true; } else { masm.move(alloc.lhsData.reg(), alloc.result); alloc.resultHasRhs = false; } } else { /* * No free regs. Find a good candidate to re-use. Best candidates don't * require syncs on the inline path. */ bool leftInReg = backingLeft->data.inRegister(); bool rightInReg = backingRight->data.inRegister(); bool leftSynced = backingLeft->data.synced(); bool rightSynced = backingRight->data.synced(); if (!commu || (leftInReg && (leftSynced || (!rightInReg || !rightSynced)))) { JS_ASSERT(backingLeft->data.inRegister() || !commu); JS_ASSERT_IF(backingLeft->data.inRegister(), backingLeft->data.reg() == alloc.lhsData.reg()); if (backingLeft->data.inRegister()) { alloc.result = backingLeft->data.reg(); unpinReg(alloc.result); takeReg(alloc.result); alloc.lhsNeedsRemat = true; } else { /* For now, just spill... */ alloc.result = allocReg(); masm.move(alloc.lhsData.reg(), alloc.result); } alloc.resultHasRhs = false; } else { JS_ASSERT(commu); JS_ASSERT(!leftInReg || (rightInReg && rightSynced)); alloc.result = backingRight->data.reg(); unpinReg(alloc.result); takeReg(alloc.result); alloc.resultHasRhs = true; alloc.rhsNeedsRemat = true; } } skip: /* Unpin everything that was pinned. */ if (backingLeft->type.inRegister()) unpinReg(backingLeft->type.reg()); if (backingRight->type.inRegister()) unpinReg(backingRight->type.reg()); if (backingLeft->data.inRegister()) unpinReg(backingLeft->data.reg()); if (backingRight->data.inRegister()) unpinReg(backingRight->data.reg()); } MaybeRegisterID FrameState::maybePinData(FrameEntry *fe) { fe = fe->isCopy() ? fe->copyOf() : fe; if (fe->data.inRegister()) { pinReg(fe->data.reg()); return fe->data.reg(); } return MaybeRegisterID(); } MaybeRegisterID FrameState::maybePinType(FrameEntry *fe) { fe = fe->isCopy() ? fe->copyOf() : fe; if (fe->type.inRegister()) { pinReg(fe->type.reg()); return fe->type.reg(); } return MaybeRegisterID(); } void FrameState::maybeUnpinReg(MaybeRegisterID reg) { if (reg.isSet()) unpinReg(reg.reg()); }