uox3/spidermonkey/methodjit/FrameState.cpp
2025-03-19 21:25:58 +10:00

1695 lines
49 KiB
C++

/* -*- 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 <brendan@mozilla.org>
*
* Contributor(s):
* David Anderson <danderson@mozilla.com>
*
* 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<uint8 *>(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());
}