blob: abec1626323717ec61f46a1b146d5a743d7f6f59 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/DebugOnly.h"
#include "CodeGenerator-shared.h"
#include "jit/MIRGenerator.h"
#include "jit/IonFrames-inl.h"
#include "jit/MIR.h"
#include "CodeGenerator-shared-inl.h"
#include "jit/IonSpewer.h"
#include "jit/IonMacroAssembler.h"
#include "jit/ParallelFunctions.h"
#include "builtin/ParallelArray.h"
using namespace js;
using namespace js::jit;
using mozilla::DebugOnly;
namespace js {
namespace jit {
MacroAssembler &
CodeGeneratorShared::ensureMasm(MacroAssembler *masmArg)
{
if (masmArg)
return *masmArg;
maybeMasm_.construct();
return maybeMasm_.ref();
}
CodeGeneratorShared::CodeGeneratorShared(MIRGenerator *gen, LIRGraph *graph, MacroAssembler *masmArg)
: oolIns(NULL),
maybeMasm_(),
masm(ensureMasm(masmArg)),
gen(gen),
graph(*graph),
current(NULL),
deoptTable_(NULL),
#ifdef DEBUG
pushedArgs_(0),
#endif
lastOsiPointOffset_(0),
sps_(&gen->compartment->rt->spsProfiler, &lastPC_),
osrEntryOffset_(0),
skipArgCheckEntryOffset_(0),
frameDepth_(graph->localSlotCount() * sizeof(STACK_SLOT_SIZE) +
graph->argumentSlotCount() * sizeof(Value))
{
if (!gen->compilingAsmJS())
masm.setInstrumentation(&sps_);
// Since asm.js uses the system ABI which does not necessarily use a
// regular array where all slots are sizeof(Value), it maintains the max
// argument stack depth separately.
if (gen->compilingAsmJS()) {
JS_ASSERT(graph->argumentSlotCount() == 0);
frameDepth_ += gen->maxAsmJSStackArgBytes();
// An MAsmJSCall does not align the stack pointer at calls sites but instead
// relies on the a priori stack adjustment (in the prologue) on platforms
// (like x64) which require the stack to be aligned.
#if defined(JS_CPU_ARM) || defined(JS_CPU_MIPS)
bool forceAlign = true;
#else
bool forceAlign = false;
#endif
if (gen->performsAsmJSCall() || forceAlign) {
unsigned alignmentAtCall = AlignmentMidPrologue + frameDepth_;
if (unsigned rem = alignmentAtCall % StackAlignment)
frameDepth_ += StackAlignment - rem;
}
// FrameSizeClass is only used for bailing, which cannot happen in
// asm.js code.
frameClass_ = FrameSizeClass::None();
} else {
frameClass_ = FrameSizeClass::FromDepth(frameDepth_);
}
}
bool
CodeGeneratorShared::generateOutOfLineCode()
{
for (size_t i = 0; i < outOfLineCode_.length(); i++) {
if (!gen->temp().ensureBallast())
return false;
masm.setFramePushed(outOfLineCode_[i]->framePushed());
lastPC_ = outOfLineCode_[i]->pc();
sps_.setPushed(outOfLineCode_[i]->script());
outOfLineCode_[i]->bind(&masm);
oolIns = outOfLineCode_[i];
if (!outOfLineCode_[i]->generate(this))
return false;
}
oolIns = NULL;
return true;
}
bool
CodeGeneratorShared::addOutOfLineCode(OutOfLineCode *code)
{
code->setFramePushed(masm.framePushed());
// If an OOL instruction adds another OOL instruction, then use the original
// instruction's script/pc instead of the basic block's that we're on
// because they're probably not relevant any more.
if (oolIns)
code->setSource(oolIns->script(), oolIns->pc());
else
code->setSource(current ? current->mir()->info().script() : NULL, lastPC_);
return outOfLineCode_.append(code);
}
// see OffsetOfFrameSlot
static inline int32_t
ToStackIndex(LAllocation *a)
{
if (a->isStackSlot()) {
JS_ASSERT(a->toStackSlot()->slot() >= 1);
return a->toStackSlot()->slot();
}
JS_ASSERT(-int32_t(sizeof(IonJSFrameLayout)) <= a->toArgument()->index());
return -(sizeof(IonJSFrameLayout) + a->toArgument()->index());
}
bool
CodeGeneratorShared::encodeSlots(LSnapshot *snapshot, MResumePoint *resumePoint,
uint32_t *startIndex)
{
IonSpew(IonSpew_Codegen, "Encoding %u of resume point %p's operands starting from %u",
resumePoint->numOperands(), (void *) resumePoint, *startIndex);
for (uint32_t slotno = 0; slotno < resumePoint->numOperands(); slotno++) {
uint32_t i = slotno + *startIndex;
MDefinition *mir = resumePoint->getOperand(slotno);
if (mir->isPassArg())
mir = mir->toPassArg()->getArgument();
JS_ASSERT(!mir->isPassArg());
MIRType type = mir->isUnused()
? MIRType_Undefined
: mir->type();
switch (type) {
case MIRType_Undefined:
snapshots_.addUndefinedSlot();
break;
case MIRType_Null:
snapshots_.addNullSlot();
break;
case MIRType_Int32:
case MIRType_String:
case MIRType_Object:
case MIRType_Boolean:
case MIRType_Double:
{
LAllocation *payload = snapshot->payloadOfSlot(i);
JSValueType type = ValueTypeFromMIRType(mir->type());
if (payload->isMemory()) {
snapshots_.addSlot(type, ToStackIndex(payload));
} else if (payload->isGeneralReg()) {
snapshots_.addSlot(type, ToRegister(payload));
} else if (payload->isFloatReg()) {
snapshots_.addSlot(ToFloatRegister(payload));
} else {
MConstant *constant = mir->toConstant();
const Value &v = constant->value();
// Don't bother with the constant pool for smallish integers.
if (v.isInt32() && v.toInt32() >= -32 && v.toInt32() <= 32) {
snapshots_.addInt32Slot(v.toInt32());
} else {
uint32_t index;
if (!graph.addConstantToPool(constant->value(), &index))
return false;
snapshots_.addConstantPoolSlot(index);
}
}
break;
}
case MIRType_Magic:
{
uint32_t index;
if (!graph.addConstantToPool(MagicValue(JS_OPTIMIZED_ARGUMENTS), &index))
return false;
snapshots_.addConstantPoolSlot(index);
break;
}
default:
{
JS_ASSERT(mir->type() == MIRType_Value);
LAllocation *payload = snapshot->payloadOfSlot(i);
#ifdef JS_NUNBOX32
LAllocation *type = snapshot->typeOfSlot(i);
if (type->isRegister()) {
if (payload->isRegister())
snapshots_.addSlot(ToRegister(type), ToRegister(payload));
else
snapshots_.addSlot(ToRegister(type), ToStackIndex(payload));
} else {
if (payload->isRegister())
snapshots_.addSlot(ToStackIndex(type), ToRegister(payload));
else
snapshots_.addSlot(ToStackIndex(type), ToStackIndex(payload));
}
#elif JS_PUNBOX64
if (payload->isRegister())
snapshots_.addSlot(ToRegister(payload));
else
snapshots_.addSlot(ToStackIndex(payload));
#endif
break;
}
}
}
*startIndex += resumePoint->numOperands();
return true;
}
bool
CodeGeneratorShared::encode(LSnapshot *snapshot)
{
if (snapshot->snapshotOffset() != INVALID_SNAPSHOT_OFFSET)
return true;
uint32_t frameCount = snapshot->mir()->frameCount();
IonSpew(IonSpew_Snapshots, "Encoding LSnapshot %p (frameCount %u)",
(void *)snapshot, frameCount);
MResumePoint::Mode mode = snapshot->mir()->mode();
JS_ASSERT(mode != MResumePoint::Outer);
bool resumeAfter = (mode == MResumePoint::ResumeAfter);
SnapshotOffset offset = snapshots_.startSnapshot(frameCount, snapshot->bailoutKind(),
resumeAfter);
FlattenedMResumePointIter mirOperandIter(snapshot->mir());
if (!mirOperandIter.init())
return false;
uint32_t startIndex = 0;
for (MResumePoint **it = mirOperandIter.begin(), **end = mirOperandIter.end();
it != end;
++it)
{
MResumePoint *mir = *it;
MBasicBlock *block = mir->block();
JSFunction *fun = block->info().fun();
JSScript *script = block->info().script();
jsbytecode *pc = mir->pc();
uint32_t exprStack = mir->stackDepth() - block->info().ninvoke();
snapshots_.startFrame(fun, script, pc, exprStack);
// Ensure that all snapshot which are encoded can safely be used for
// bailouts.
DebugOnly<jsbytecode *> bailPC = pc;
if (mir->mode() == MResumePoint::ResumeAfter)
bailPC = GetNextPc(pc);
#ifdef DEBUG
if (GetIonContext()->cx) {
uint32_t stackDepth = js_ReconstructStackDepth(GetIonContext()->cx, script, bailPC);
if (JSOp(*bailPC) == JSOP_FUNCALL) {
// For fun.call(this, ...); the reconstructStackDepth will
// include the this. When inlining that is not included.
// So the exprStackSlots will be one less.
JS_ASSERT(stackDepth - exprStack <= 1);
} else if (JSOp(*bailPC) != JSOP_FUNAPPLY) {
// For fun.apply({}, arguments) the reconstructStackDepth will
// have stackdepth 4, but it could be that we inlined the
// funapply. In that case exprStackSlots, will have the real
// arguments in the slots and not be 4.
JS_ASSERT(exprStack == stackDepth);
}
}
#endif
#ifdef TRACK_SNAPSHOTS
LInstruction *ins = instruction();
uint32_t pcOpcode = 0;
uint32_t lirOpcode = 0;
uint32_t lirId = 0;
uint32_t mirOpcode = 0;
uint32_t mirId = 0;
if (ins) {
lirOpcode = ins->op();
lirId = ins->id();
if (ins->mirRaw()) {
mirOpcode = ins->mirRaw()->op();
mirId = ins->mirRaw()->id();
if (ins->mirRaw()->trackedPc())
pcOpcode = *ins->mirRaw()->trackedPc();
}
}
snapshots_.trackFrame(pcOpcode, mirOpcode, mirId, lirOpcode, lirId);
#endif
if (!encodeSlots(snapshot, mir, &startIndex))
return false;
snapshots_.endFrame();
}
snapshots_.endSnapshot();
snapshot->setSnapshotOffset(offset);
return !snapshots_.oom();
}
bool
CodeGeneratorShared::assignBailoutId(LSnapshot *snapshot)
{
JS_ASSERT(snapshot->snapshotOffset() != INVALID_SNAPSHOT_OFFSET);
// Can we not use bailout tables at all?
if (!deoptTable_)
return false;
JS_ASSERT(frameClass_ != FrameSizeClass::None());
if (snapshot->bailoutId() != INVALID_BAILOUT_ID)
return true;
// Is the bailout table full?
if (bailouts_.length() >= BAILOUT_TABLE_SIZE)
return false;
unsigned bailoutId = bailouts_.length();
snapshot->setBailoutId(bailoutId);
IonSpew(IonSpew_Snapshots, "Assigned snapshot bailout id %u", bailoutId);
return bailouts_.append(snapshot->snapshotOffset());
}
void
CodeGeneratorShared::encodeSafepoints()
{
for (SafepointIndex *it = safepointIndices_.begin(), *end = safepointIndices_.end();
it != end;
++it)
{
LSafepoint *safepoint = it->safepoint();
if (!safepoint->encoded()) {
safepoint->fixupOffset(&masm);
safepoints_.encode(safepoint);
}
it->resolve();
}
}
bool
CodeGeneratorShared::markSafepoint(LInstruction *ins)
{
return markSafepointAt(masm.currentOffset(), ins);
}
bool
CodeGeneratorShared::markSafepointAt(uint32_t offset, LInstruction *ins)
{
JS_ASSERT_IF(safepointIndices_.length(),
offset - safepointIndices_.back().displacement() >= sizeof(uint32_t));
return safepointIndices_.append(SafepointIndex(offset, ins->safepoint()));
}
void
CodeGeneratorShared::ensureOsiSpace()
{
// For a refresher, an invalidation point is of the form:
// 1: call <target>
// 2: ...
// 3: <osipoint>
//
// The four bytes *before* instruction 2 are overwritten with an offset.
// Callers must ensure that the instruction itself has enough bytes to
// support this.
//
// The bytes *at* instruction 3 are overwritten with an invalidation jump.
// jump. These bytes may be in a completely different IR sequence, but
// represent the join point of the call out of the function.
//
// At points where we want to ensure that invalidation won't corrupt an
// important instruction, we make sure to pad with nops.
if (masm.currentOffset() - lastOsiPointOffset_ < Assembler::patchWrite_NearCallSize()) {
int32_t paddingSize = Assembler::patchWrite_NearCallSize();
paddingSize -= masm.currentOffset() - lastOsiPointOffset_;
for (int32_t i = 0; i < paddingSize; ++i)
masm.nop();
}
JS_ASSERT(masm.currentOffset() - lastOsiPointOffset_ >= Assembler::patchWrite_NearCallSize());
lastOsiPointOffset_ = masm.currentOffset();
}
bool
CodeGeneratorShared::markOsiPoint(LOsiPoint *ins, uint32_t *callPointOffset)
{
if (!encode(ins->snapshot()))
return false;
ensureOsiSpace();
*callPointOffset = masm.currentOffset();
SnapshotOffset so = ins->snapshot()->snapshotOffset();
return osiIndices_.append(OsiIndex(*callPointOffset, so));
}
// Before doing any call to Cpp, you should ensure that volatile
// registers are evicted by the register allocator.
bool
CodeGeneratorShared::callVM(const VMFunction &fun, LInstruction *ins, const Register *dynStack)
{
// Different execution modes have different sets of VM functions.
JS_ASSERT(fun.executionMode == gen->info().executionMode());
#ifdef DEBUG
if (ins->mirRaw()) {
JS_ASSERT(ins->mirRaw()->isInstruction());
MInstruction *mir = ins->mirRaw()->toInstruction();
JS_ASSERT_IF(mir->isEffectful(), mir->resumePoint());
}
#endif
// Stack is:
// ... frame ...
// [args]
#ifdef DEBUG
JS_ASSERT(pushedArgs_ == fun.explicitArgs);
pushedArgs_ = 0;
#endif
// Get the wrapper of the VM function.
IonCompartment *ion = GetIonContext()->compartment->ionCompartment();
IonCode *wrapper = ion->getVMWrapper(fun);
if (!wrapper)
return false;
// Call the wrapper function. The wrapper is in charge to unwind the stack
// when returning from the call. Failures are handled with exceptions based
// on the return value of the C functions. To guard the outcome of the
// returned value, use another LIR instruction.
uint32_t callOffset;
if (dynStack)
callOffset = masm.callWithExitFrame(wrapper, *dynStack);
else
callOffset = masm.callWithExitFrame(wrapper);
if (!markSafepointAt(callOffset, ins))
return false;
// Remove rest of the frame left on the stack. We remove the return address
// which is implicitly poped when returning.
int framePop = sizeof(IonExitFrameLayout) - sizeof(void*);
// Pop arguments from framePushed.
masm.implicitPop(fun.explicitStackSlots() * sizeof(void *) + framePop);
// Stack is:
// ... frame ...
return true;
}
class OutOfLineTruncateSlow : public OutOfLineCodeBase<CodeGeneratorShared>
{
FloatRegister src_;
Register dest_;
public:
OutOfLineTruncateSlow(FloatRegister src, Register dest)
: src_(src), dest_(dest)
{ }
bool accept(CodeGeneratorShared *codegen) {
return codegen->visitOutOfLineTruncateSlow(this);
}
FloatRegister src() const {
return src_;
}
Register dest() const {
return dest_;
}
};
bool
CodeGeneratorShared::emitTruncateDouble(const FloatRegister &src, const Register &dest)
{
OutOfLineTruncateSlow *ool = new OutOfLineTruncateSlow(src, dest);
if (!addOutOfLineCode(ool))
return false;
masm.branchTruncateDouble(src, dest, ool->entry());
masm.bind(ool->rejoin());
return true;
}
bool
CodeGeneratorShared::visitOutOfLineTruncateSlow(OutOfLineTruncateSlow *ool)
{
FloatRegister src = ool->src();
Register dest = ool->dest();
saveVolatile(dest);
masm.setupUnalignedABICall(1, dest);
masm.passABIArg(src);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, js::ToInt32));
masm.storeCallResult(dest);
restoreVolatile(dest);
masm.jump(ool->rejoin());
return true;
}
void
CodeGeneratorShared::emitPreBarrier(Register base, const LAllocation *index, MIRType type)
{
if (index->isConstant()) {
Address address(base, ToInt32(index) * sizeof(Value));
masm.patchableCallPreBarrier(address, type);
} else {
BaseIndex address(base, ToRegister(index), TimesEight);
masm.patchableCallPreBarrier(address, type);
}
}
void
CodeGeneratorShared::emitPreBarrier(Address address, MIRType type)
{
masm.patchableCallPreBarrier(address, type);
}
void
CodeGeneratorShared::dropArguments(unsigned argc)
{
for (unsigned i = 0; i < argc; i++)
pushedArgumentSlots_.popBack();
}
bool
CodeGeneratorShared::markArgumentSlots(LSafepoint *safepoint)
{
for (size_t i = 0; i < pushedArgumentSlots_.length(); i++) {
if (!safepoint->addValueSlot(pushedArgumentSlots_[i]))
return false;
}
return true;
}
OutOfLineParallelAbort *
CodeGeneratorShared::oolParallelAbort(ParallelBailoutCause cause,
MBasicBlock *basicBlock,
jsbytecode *bytecode)
{
OutOfLineParallelAbort *ool = new OutOfLineParallelAbort(cause, basicBlock, bytecode);
if (!ool || !addOutOfLineCode(ool))
return NULL;
return ool;
}
OutOfLineParallelAbort *
CodeGeneratorShared::oolParallelAbort(ParallelBailoutCause cause,
LInstruction *lir)
{
MDefinition *mir = lir->mirRaw();
MBasicBlock *block = mir->block();
jsbytecode *pc = mir->trackedPc();
if (!pc) {
if (lir->snapshot())
pc = lir->snapshot()->mir()->pc();
else
pc = block->pc();
}
return oolParallelAbort(cause, block, pc);
}
OutOfLinePropagateParallelAbort *
CodeGeneratorShared::oolPropagateParallelAbort(LInstruction *lir)
{
OutOfLinePropagateParallelAbort *ool = new OutOfLinePropagateParallelAbort(lir);
if (!ool || !addOutOfLineCode(ool))
return NULL;
return ool;
}
bool
OutOfLineParallelAbort::generate(CodeGeneratorShared *codegen)
{
codegen->callTraceLIR(0xDEADBEEF, NULL, "ParallelBailout");
return codegen->visitOutOfLineParallelAbort(this);
}
bool
OutOfLinePropagateParallelAbort::generate(CodeGeneratorShared *codegen)
{
codegen->callTraceLIR(0xDEADBEEF, NULL, "ParallelBailout");
return codegen->visitOutOfLinePropagateParallelAbort(this);
}
bool
CodeGeneratorShared::callTraceLIR(uint32_t blockIndex, LInstruction *lir,
const char *bailoutName)
{
JS_ASSERT_IF(!lir, bailoutName);
uint32_t emi = (uint32_t) gen->info().executionMode();
if (!IonSpewEnabled(IonSpew_Trace))
return true;
masm.PushRegsInMask(RegisterSet::All());
RegisterSet regSet(RegisterSet::All());
Register blockIndexReg = regSet.takeGeneral();
Register lirIndexReg = regSet.takeGeneral();
Register emiReg = regSet.takeGeneral();
Register lirOpNameReg = regSet.takeGeneral();
Register mirOpNameReg = regSet.takeGeneral();
Register scriptReg = regSet.takeGeneral();
Register pcReg = regSet.takeGeneral();
// This first move is here so that when you scan the disassembly,
// you can easily pick out where each instruction begins. The
// next few items indicate to you the Basic Block / LIR.
masm.move32(Imm32(0xDEADBEEF), blockIndexReg);
if (lir) {
masm.move32(Imm32(blockIndex), blockIndexReg);
masm.move32(Imm32(lir->id()), lirIndexReg);
masm.move32(Imm32(emi), emiReg);
masm.movePtr(ImmWord(lir->opName()), lirOpNameReg);
if (MDefinition *mir = lir->mirRaw()) {
masm.movePtr(ImmWord(mir->opName()), mirOpNameReg);
masm.movePtr(ImmWord((void *)mir->block()->info().script()), scriptReg);
masm.movePtr(ImmWord(mir->trackedPc()), pcReg);
} else {
masm.movePtr(ImmWord((void *)NULL), mirOpNameReg);
masm.movePtr(ImmWord((void *)NULL), scriptReg);
masm.movePtr(ImmWord((void *)NULL), pcReg);
}
} else {
masm.move32(Imm32(0xDEADBEEF), blockIndexReg);
masm.move32(Imm32(0xDEADBEEF), lirIndexReg);
masm.move32(Imm32(emi), emiReg);
masm.movePtr(ImmWord(bailoutName), lirOpNameReg);
masm.movePtr(ImmWord(bailoutName), mirOpNameReg);
masm.movePtr(ImmWord((void *)NULL), scriptReg);
masm.movePtr(ImmWord((void *)NULL), pcReg);
}
masm.setupUnalignedABICall(7, CallTempReg4);
masm.passABIArg(blockIndexReg);
masm.passABIArg(lirIndexReg);
masm.passABIArg(emiReg);
masm.passABIArg(lirOpNameReg);
masm.passABIArg(mirOpNameReg);
masm.passABIArg(scriptReg);
masm.passABIArg(pcReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, TraceLIR));
masm.PopRegsInMask(RegisterSet::All());
return true;
}
} // namespace jit
} // namespace js