blob: eed3ffdec7050fe727ea1a62e2a89c8150761f81 [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/. */
#ifndef jit_shared_IonAssemblerBufferWithConstantPools_h
#define jit_shared_IonAssemblerBufferWithConstantPools_h
#include "jit/shared/IonAssemblerBuffer.h"
#include "assembler/wtf/SegmentedVector.h"
#include "jit/IonSpewer.h"
namespace js {
namespace jit {
typedef Vector<BufferOffset, 512, IonAllocPolicy> LoadOffsets;
struct Pool
: public IonAllocPolicy
{
const int maxOffset;
const int immSize;
const int instSize;
const int bias;
private:
const int alignment;
public:
const bool isBackref;
const bool canDedup;
// "other" is the backwards half of this pool, it is held in another pool structure
Pool *other;
uint8_t *poolData;
uint32_t numEntries;
uint32_t buffSize;
LoadOffsets loadOffsets;
// When filling pools where the the size of an immediate is larger
// than the size of an instruction, we find we're in a case where the distance between the
// next instruction and the next pool slot is increasing!
// Moreover, If we want to do fancy things like deduplicate pool entries at
// dump time, we may not know the location in a pool (and thus the limiting load)
// until very late.
// Lastly, it may be beneficial to interleave the pools. I have absolutely no idea
// how that will work, but my suspicions are that it will be difficult.
BufferOffset limitingUser;
int limitingUsee;
Pool(int maxOffset_, int immSize_, int instSize_, int bias_, int alignment_, LifoAlloc &LifoAlloc_,
bool isBackref_ = false, bool canDedup_ = false, Pool *other_ = NULL)
: maxOffset(maxOffset_), immSize(immSize_), instSize(instSize),
bias(bias_), alignment(alignment_),
isBackref(isBackref_), canDedup(canDedup_), other(other_),
poolData(static_cast<uint8_t *>(LifoAlloc_.alloc(8*immSize))), numEntries(0),
buffSize(8), loadOffsets(), limitingUser(), limitingUsee(INT_MIN)
{
}
static const int garbage=0xa5a5a5a5;
Pool() : maxOffset(garbage), immSize(garbage), instSize(garbage), bias(garbage),
alignment(garbage), isBackref(garbage), canDedup(garbage), other((Pool*)garbage)
{
}
// Sometimes, when we are adding large values to a pool, the limiting use may change.
// Handle this case. nextInst is the address of the
void updateLimiter(BufferOffset nextInst) {
int oldRange, newRange;
if (isBackref) {
// common expressions that are not subtracted: the location of the pool, ...
oldRange = limitingUser.getOffset() - ((numEntries - limitingUsee) * immSize);
newRange = nextInst.getOffset();
} else {
oldRange = (limitingUsee * immSize) - limitingUser.getOffset();
newRange = (numEntries * immSize) - nextInst.getOffset();
}
if (!limitingUser.assigned() || newRange > oldRange) {
// We have a new largest range!
limitingUser = nextInst;
limitingUsee = numEntries;
}
}
// checkFull is called before any modifications have been made.
// It is "if we were to add this instruction and pool entry,
// would we be in an invalid state?". If it is true, then it is in fact
// time for a "pool dump".
// poolOffset is the distance from the end of the current section to the end of the pool.
// For the last section of the pool, this will be the size of the footer
// For the first section of the pool, it will be the size of every other
// section and the footer
// codeOffset is the instruction-distance from the pool to the beginning of the buffer.
// Since codeOffset only includes instructions, the number is the same for
// the beginning and end of the pool.
// instOffset is the offset from the beginning of the buffer to the instruction that
// is about to be placed.
bool checkFullBackref(int poolOffset, int codeOffset) {
if (!limitingUser.assigned())
return false;
signed int distance =
limitingUser.getOffset() + bias
- codeOffset + poolOffset +
(numEntries - limitingUsee + 1) * immSize;
if (distance >= maxOffset)
return true;
return false;
}
// checkFull answers the question "If a pool were placed at poolOffset, would
// any reference into the pool be out of range?". It is meant to be used as instructions
// and elements are inserted, to determine if a saved perforation point needs to be used.
bool checkFull(int poolOffset) {
// Inserting an instruction into the stream can
// push any of the pools out of range.
// Similarly, inserting into a pool can push the pool entry out of range
JS_ASSERT(!isBackref);
// Not full if there aren't any uses.
if (!limitingUser.assigned()) {
return false;
}
// We're considered "full" when:
// bias + abs(poolOffset + limitingeUsee * numEntries - limitingUser) + sizeof(other_pools) >= maxOffset
if (poolOffset + limitingUsee * immSize - (limitingUser.getOffset() + bias) >= maxOffset) {
return true;
}
return false;
}
// By the time this function is called, we'd damn well better know that this is going to succeed.
uint32_t insertEntry(uint8_t *data, BufferOffset off, LifoAlloc &LifoAlloc_) {
if (numEntries == buffSize) {
buffSize <<= 1;
uint8_t *tmp = static_cast<uint8_t*>(LifoAlloc_.alloc(immSize * buffSize));
memcpy(tmp, poolData, immSize * numEntries);
if (poolData == NULL) {
buffSize = 0;
return -1;
}
poolData = tmp;
}
memcpy(&poolData[numEntries * immSize], data, immSize);
loadOffsets.append(off.getOffset());
return numEntries++;
}
bool reset(LifoAlloc &a) {
numEntries = 0;
buffSize = 8;
poolData = static_cast<uint8_t*>(a.alloc(buffSize * immSize));
if (poolData == NULL)
return false;
void *otherSpace = a.alloc(sizeof(Pool));
if (otherSpace == NULL)
return false;
other = new (otherSpace) Pool(other->maxOffset, other->immSize, other->instSize,
other->bias, other->alignment, a, other->isBackref,
other->canDedup);
new (&loadOffsets) LoadOffsets;
limitingUser = BufferOffset();
limitingUsee = -1;
return true;
}
// WARNING: This will not always align values. It will only
// align to the requirement of the pool. If the pool is empty,
// there is nothing to be aligned, so it will not perform any alignment
uint8_t* align(uint8_t *ptr) {
return (uint8_t*)align((uint32_t)ptr);
}
uint32_t align(uint32_t ptr) {
if (numEntries == 0)
return ptr;
return (ptr + alignment-1) & ~(alignment-1);
}
uint32_t forceAlign(uint32_t ptr) {
return (ptr + alignment-1) & ~(alignment-1);
}
bool isAligned(uint32_t ptr) {
return ptr == align(ptr);
}
int getAlignment() {
return alignment;
}
uint32_t addPoolSize(uint32_t start) {
start = align(start);
start += immSize * numEntries;
return start;
}
uint8_t *addPoolSize(uint8_t *start) {
start = align(start);
start += immSize * numEntries;
return start;
}
uint32_t getPoolSize() {
return immSize * numEntries;
}
};
template <int SliceSize, int InstBaseSize>
struct BufferSliceTail : public BufferSlice<SliceSize> {
Pool *data;
uint8_t isBranch[(SliceSize + (InstBaseSize * 8 - 1)) / (InstBaseSize * 8)];
bool isNatural : 1;
BufferSliceTail *getNext() {
return (BufferSliceTail *)this->next;
}
BufferSliceTail() : data(NULL), isNatural(true) {
memset(isBranch, 0, sizeof(isBranch));
}
void markNextAsBranch() {
int idx = this->nodeSize / InstBaseSize;
isBranch[idx >> 3] |= 1 << (idx & 0x7);
}
bool isNextBranch() {
int idx = this->nodeSize / InstBaseSize;
return (isBranch[idx >> 3] >> (idx & 0x7)) & 1;
}
};
#if 0
static int getId() {
if (MaybeGetIonContext())
return MaybeGetIonContext()->getNextAssemblerId();
return NULL_ID;
}
#endif
static void spewEntry(uint8_t *ptr, int length) {
#if IS_LITTLE_ENDIAN
for (int idx = 0; idx < length; idx++) {
IonSpewCont(IonSpew_Pools, "%02x", ptr[length - idx - 1]);
if (((idx & 3) == 3) && (idx + 1 != length))
IonSpewCont(IonSpew_Pools, "_");
}
#else
for (int idx = 0; idx < length; idx++) {
IonSpewCont(IonSpew_Pools, "%02x", ptr[idx]);
if (((idx & 3) == 3) && (idx + 1 != length))
IonSpewCont(IonSpew_Pools, "_");
}
#endif
}
// NOTE: Adding in the ability to retroactively insert a pool has consequences!
// Most notably, Labels stop working. Normally, we create a label, later bind it.
// when the label is bound, We back-patch all previous references to the label with
// the correct offset. However, since a pool may be retroactively inserted, we don't
// actually know what the final offset is going to be until much later. This will
// happen (in some instances) after the pools have been finalized. Attempting to compute
// the correct offsets for branches as the pools are finalized is quite infeasible.
// Instead, I write *just* the number of instructions that will be jumped over, then
// when we go to copy the instructions into the executable buffer, fix up all of the
// offsets to include the pools. Since we have about 32 megabytes worth of offset,
// I am not very worried about the pools moving it out of range.
// Now, How exactly do we generate these? The first step is to identify which
// instructions are actually branches that need to be fixed up. A single bit
// per instruction should be enough to determine which ones are branches, but
// we have no guarantee that all instructions are the same size, so the start of
// each branch instruction will be marked with a bit (1 bit per byte).
// then we neet to call up to the assembler to determine what the offset of the branch
// is. The offset will be the number of instructions that are being skipped over
// along with any processor bias. We then need to calculate the offset, including pools
// and write that value into the buffer. At this point, we can write it into the
// executable buffer, or the AssemblerBuffer, and copy the data over later.
// Previously, This was all handled by the assembler, since the location
// and size of pools were always known as soon as its location had been reached.
// A class for indexing into constant pools.
// Each time a pool entry is added, one of these is generated.
// This can be supplied to read and write that entry after the fact.
// And it can be used to get the address of the entry once the buffer
// has been finalized, and an executable copy allocated.
template <int SliceSize, int InstBaseSize, class Inst, class Asm, int poolKindBits>
struct AssemblerBufferWithConstantPool : public AssemblerBuffer<SliceSize, Inst> {
private:
int entryCount[1 << poolKindBits];
static const int offsetBits = 32 - poolKindBits;
public:
class PoolEntry {
template <int ss, int ibs, class i, class a, int pkb>
friend struct AssemblerBufferWithConstantPool;
uint32_t offset_ : offsetBits;
uint32_t kind_ : poolKindBits;
PoolEntry(int offset, int kind) : offset_(offset), kind_(kind) {
}
public:
uint32_t encode() {
uint32_t ret;
memcpy(&ret, this, sizeof(uint32_t));
return ret;
}
PoolEntry(uint32_t bits) : offset_(((1u << offsetBits) - 1) & bits),
kind_(bits >> offsetBits) {
}
PoolEntry() : offset_((1u << offsetBits) - 1), kind_((1u << poolKindBits) - 1) {
}
uint32_t poolKind() const {
return kind_;
}
uint32_t offset() const {
return offset_;
}
};
private:
typedef BufferSliceTail<SliceSize, InstBaseSize> BufferSlice;
typedef AssemblerBuffer<SliceSize, Inst> Parent;
// The size of a guard instruction
const int guardSize;
// The size of the header that is put at the beginning of a full pool
const int headerSize;
// The size of a footer that is put in a pool after it is full.
const int footerSize;
// the number of sub-pools that we can allocate into.
static const int numPoolKinds = 1 << poolKindBits;
Pool *pools;
// The buffer should be aligned to this address.
const int instBufferAlign;
// the number of times we've dumped the pool.
int numDumps;
struct PoolInfo {
int offset; // the number of instructions before the start of the pool
int size; // the size of the pool, including padding
int finalPos; // the end of the buffer, in bytes from the beginning of the buffer
BufferSlice *slice;
};
PoolInfo *poolInfo;
// we need to keep track of how large the pools are, so we can allocate
// enough space for them later. This should include any amount of padding
// necessary to keep the pools aligned.
int poolSize;
// The Assembler should set this to true if it does not want us to dump a pool here
int canNotPlacePool;
// Are we filling up the forwards or backwards pools?
bool inBackref;
// Cache the last place we saw an opportunity to dump the pool
BufferOffset perforation;
BufferSlice *perforatedNode;
public:
int id;
private:
static const int logBasePoolInfo = 3;
BufferSlice ** getHead() {
return (BufferSlice**)&this->head;
}
BufferSlice ** getTail() {
return (BufferSlice**)&this->tail;
}
virtual BufferSlice *newSlice(LifoAlloc &a) {
BufferSlice *tmp = static_cast<BufferSlice*>(a.alloc(sizeof(BufferSlice)));
if (!tmp) {
this->m_oom = true;
return NULL;
}
new (tmp) BufferSlice;
return tmp;
}
public:
AssemblerBufferWithConstantPool(int guardSize_, int headerSize_, int footerSize_, Pool *pools_, int instBufferAlign_)
: guardSize(guardSize_), headerSize(headerSize_),
footerSize(footerSize_),
pools(pools_),
instBufferAlign(instBufferAlign_), numDumps(0),
poolInfo(NULL),
poolSize(0), canNotPlacePool(0), inBackref(false),
perforatedNode(NULL), id(-1)
{
for (int idx = 0; idx < numPoolKinds; idx++) {
entryCount[idx] = 0;
}
}
// We need to wait until an AutoIonContextAlloc is created by the
// IonMacroAssembler, before allocating any space.
void initWithAllocator() {
poolInfo = static_cast<PoolInfo*>(this->LifoAlloc_.alloc(sizeof(PoolInfo) * (1 << logBasePoolInfo)));
}
const PoolInfo & getInfo(int x) const {
static const PoolInfo nil = {0,0,0};
if (x < 0 || x >= numDumps)
return nil;
return poolInfo[x];
}
void executableCopy(uint8_t *dest_) {
if (this->oom())
return;
// TODO: only do this when the pool actually has a value in it
flushPool();
for (int idx = 0; idx < numPoolKinds; idx++) {
JS_ASSERT(pools[idx].numEntries == 0 && pools[idx].other->numEntries == 0);
}
typedef uint8_t Chunk[InstBaseSize];
Chunk *start = (Chunk*)dest_;
Chunk *dest = (Chunk*)(((uint32_t)dest_ + instBufferAlign - 1) & ~(instBufferAlign -1));
int curIndex = 0;
int curInstOffset = 0;
JS_ASSERT(start == dest);
for (BufferSlice * cur = *getHead(); cur != NULL; cur = cur->getNext()) {
Chunk *src = (Chunk*)cur->instructions;
for (unsigned int idx = 0; idx <cur->size()/InstBaseSize;
idx++, curInstOffset += InstBaseSize) {
// Is the current instruction a branch?
if (cur->isBranch[idx >> 3] & (1<<(idx&7))) {
// It's a branch. fix up the branchiness!
patchBranch((Inst*)&src[idx], curIndex, BufferOffset(curInstOffset));
}
memcpy(&dest[idx], &src[idx], sizeof(Chunk));
}
dest+=cur->size()/InstBaseSize;
if (cur->data != NULL) {
// have the repatcher move on to the next pool
curIndex ++;
// loop over all of the pools, copying them into place.
uint8_t *poolDest = (uint8_t*)dest;
Asm::writePoolHeader(poolDest, cur->data, cur->isNatural);
poolDest += headerSize;
for (int idx = 0; idx < numPoolKinds; idx++) {
Pool *curPool = &cur->data[idx];
// align the pool.
poolDest = curPool->align(poolDest);
memcpy(poolDest, curPool->poolData, curPool->immSize * curPool->numEntries);
poolDest += curPool->immSize * curPool->numEntries;
}
// now go over the whole list backwards, and copy in the reverse portions
for (int idx = numPoolKinds-1; idx >= 0; idx--) {
Pool *curPool = cur->data[idx].other;
// align the pool.
poolDest = curPool->align(poolDest);
memcpy(poolDest, curPool->poolData, curPool->immSize * curPool->numEntries);
poolDest += curPool->immSize * curPool->numEntries;
}
// write a footer in place
Asm::writePoolFooter(poolDest, cur->data, cur->isNatural);
poolDest += footerSize;
// at this point, poolDest had better still be aligned to a chunk boundary.
dest = (Chunk*) poolDest;
}
}
}
BufferOffset insertEntry(uint32_t instSize, uint8_t *inst, Pool *p, uint8_t *data, PoolEntry *pe = NULL) {
if (this->oom() && !this->bail())
return BufferOffset();
int token;
if (p != NULL) {
int poolId = p - pools;
const char sigil = inBackref ? 'B' : 'F';
IonSpew(IonSpew_Pools, "[%d]{%c} Inserting entry into pool %d", id, sigil, poolId);
IonSpewStart(IonSpew_Pools, "[%d] data is: 0x", id);
spewEntry(data, p->immSize);
IonSpewFin(IonSpew_Pools);
}
// insert the pool value
if (inBackref)
token = insertEntryBackwards(instSize, inst, p, data);
else
token = insertEntryForwards(instSize, inst, p, data);
// now to get an instruction to write
PoolEntry retPE;
if (p != NULL) {
if (this->oom())
return BufferOffset();
int poolId = p - pools;
IonSpew(IonSpew_Pools, "[%d] Entry has token %d, offset ~%d", id, token, size());
Asm::insertTokenIntoTag(instSize, inst, token);
JS_ASSERT(poolId < (1 << poolKindBits));
JS_ASSERT(poolId >= 0);
// Figure out the offset within like-kinded pool entries
retPE = PoolEntry(entryCount[poolId], poolId);
entryCount[poolId]++;
}
// Now inst is a valid thing to insert into the instruction stream
if (pe != NULL)
*pe = retPE;
return this->putBlob(instSize, inst);
}
uint32_t insertEntryBackwards(uint32_t instSize, uint8_t *inst, Pool *p, uint8_t *data) {
// unlike the forward case, inserting an instruction without inserting
// anything into a pool after a pool has been placed, we don't affect
// anything relevant, so we can skip this check entirely!
if (p == NULL)
return INT_MIN;
// TODO: calculating offsets for the alignment requirements is *hard*
// Instead, assume that we always add the maximum.
int poolOffset = footerSize;
Pool *cur, *tmp;
// NOTE: we want to process the pools from last to first.
// Since the last pool is pools[0].other, and the first pool
// is pools[numPoolKinds-1], we actually want to process this
// forwards.
for (cur = pools; cur < &pools[numPoolKinds]; cur++) {
// fetch the pool for the backwards half.
tmp = cur->other;
if (p == cur)
tmp->updateLimiter(this->nextOffset());
if (tmp->checkFullBackref(poolOffset, perforation.getOffset())) {
// uh-oh, the backwards pool is full. Time to finalize it, and
// switch to a new forward pool.
if (p != NULL)
IonSpew(IonSpew_Pools, "[%d]Inserting pool entry caused a spill", id);
else
IonSpew(IonSpew_Pools, "[%d]Inserting instruction(%d) caused a spill", id, size());
this->finishPool();
if (this->oom())
return uint32_t(-1);
return this->insertEntryForwards(instSize, inst, p, data);
}
// when moving back to front, calculating the alignment is hard, just be
// conservative with it.
poolOffset += tmp->immSize * tmp->numEntries + tmp->getAlignment();
if (p == tmp) {
poolOffset += tmp->immSize;
}
}
return p->numEntries + p->other->insertEntry(data, this->nextOffset(), this->LifoAlloc_);
}
// Simultaneously insert an instSized instruction into the stream,
// and an entry into the pool. There are many things that can happen.
// 1) the insertion goes as planned
// 2) inserting an instruction pushes a previous pool-reference out of range, forcing a dump
// 2a) there isn't a reasonable save point in the instruction stream. We need to save room for
// a guard instruction to branch over the pool.
int insertEntryForwards(uint32_t instSize, uint8_t *inst, Pool *p, uint8_t *data) {
// Advance the "current offset" by an inst, so everyone knows what their offset should be.
uint32_t nextOffset = this->size() + instSize;
uint32_t poolOffset = nextOffset;
Pool *tmp;
// If we need a guard instruction, reserve space for that.
if (!perforatedNode)
poolOffset += guardSize;
// Also, take into account the size of the header that will be placed *after*
// the guard instruction
poolOffset += headerSize;
// Perform the necessary range checks.
for (tmp = pools; tmp < &pools[numPoolKinds]; tmp++) {
// The pool may wish for a particular alignment, Let's give it one.
JS_ASSERT((tmp->getAlignment() & (tmp->getAlignment() - 1)) == 0);
// The pool only needs said alignment *if* there are any entries in the pool
// WARNING: the pool needs said alignment if there are going to be entries in
// the pool after this entry has been inserted
if (p == tmp)
poolOffset = tmp->forceAlign(poolOffset);
else
poolOffset = tmp->align(poolOffset);
// If we're at the pool we want to insert into, find a new limiter
// before we do the range check.
if (p == tmp) {
p->updateLimiter(BufferOffset(nextOffset));
}
if (tmp->checkFull(poolOffset)) {
// uh-oh. DUMP DUMP DUMP
if (p != NULL)
IonSpew(IonSpew_Pools, "[%d] Inserting pool entry caused a spill", id);
else
IonSpew(IonSpew_Pools, "[%d] Inserting instruction(%d) caused a spill", id, size());
this->dumpPool();
return this->insertEntryBackwards(instSize, inst, p, data);
}
// include the size of this pool in the running total
if (p == tmp) {
nextOffset += tmp->immSize;
}
nextOffset += tmp->immSize * tmp->numEntries;
}
if (p == NULL) {
return INT_MIN;
}
return p->insertEntry(data, this->nextOffset(), this->LifoAlloc_);
}
BufferOffset putInt(uint32_t value) {
return insertEntry(sizeof(uint32_t) / sizeof(uint8_t), (uint8_t*)&value, NULL, NULL);
}
// Mark the current section as an area where we can
// later go to dump a pool
void perforate() {
// If we're filling the backrefrences, we don't want to start looking for a new dumpsite.
if (inBackref)
return;
if (canNotPlacePool)
return;
// If there is nothing in the pool, then it is strictly disadvantageous
// to attempt to place a pool here
bool empty = true;
for (int i = 0; i < numPoolKinds; i++) {
if (pools[i].numEntries != 0) {
empty = false;
break;
}
}
if (empty)
return;
perforatedNode = *getTail();
perforation = this->nextOffset();
Parent::perforate();
IonSpew(IonSpew_Pools, "[%d] Adding a perforation at offset %d", id, perforation.getOffset());
}
// After a pool is finished, no more elements may be added to it. During this phase, we
// will know the exact offsets to the pool entries, and those values should be written into
// the given instructions.
PoolInfo getPoolData() const {
int prevOffset = getInfo(numDumps-1).offset;
int prevEnd = getInfo(numDumps-1).finalPos;
// calculate the offset of the start of this pool;
int perfOffset = perforation.assigned() ?
perforation.getOffset() :
this->nextOffset().getOffset() + this->guardSize;
int initOffset = prevEnd + (perfOffset - prevOffset);
int finOffset = initOffset;
bool poolIsEmpty = true;
for (int poolIdx = 0; poolIdx < numPoolKinds; poolIdx++) {
if (pools[poolIdx].numEntries != 0) {
poolIsEmpty = false;
break;
}
if (pools[poolIdx].other != NULL && pools[poolIdx].other->numEntries != 0) {
poolIsEmpty = false;
break;
}
}
if (!poolIsEmpty) {
finOffset += headerSize;
for (int poolIdx = 0; poolIdx < numPoolKinds; poolIdx++) {
finOffset=pools[poolIdx].align(finOffset);
finOffset+=pools[poolIdx].numEntries * pools[poolIdx].immSize;
}
// And compute the necessary adjustments for the second half of the pool.
for (int poolIdx = numPoolKinds-1; poolIdx >= 0; poolIdx--) {
finOffset=pools[poolIdx].other->align(finOffset);
finOffset+=pools[poolIdx].other->numEntries * pools[poolIdx].other->immSize;
}
finOffset += footerSize;
}
PoolInfo ret;
ret.offset = perfOffset;
ret.size = finOffset - initOffset;
ret.finalPos = finOffset;
ret.slice = perforatedNode;
return ret;
}
void finishPool() {
// This function should only be called while the backwards half of the pool
// is being filled in. The backwards half of the pool is always in a state
// where it is sane. Everything that needs to be done here is for "sanity's sake".
// The per-buffer pools need to be reset, and we need to record the size of the pool.
IonSpew(IonSpew_Pools, "[%d] Finishing pool %d", id, numDumps);
JS_ASSERT(inBackref);
PoolInfo newPoolInfo = getPoolData();
if (newPoolInfo.size == 0) {
// The code below also creates a new pool, but that is not necessary, since
// the pools have not been modified at all.
new (&perforation) BufferOffset();
perforatedNode = NULL;
inBackref = false;
IonSpew(IonSpew_Pools, "[%d] Aborting because the pool is empty", id);
// Bail out early, since we don't want to even pretend these pools exist.
return;
}
JS_ASSERT(perforatedNode != NULL);
if (numDumps >= (1<<logBasePoolInfo) && (numDumps & (numDumps-1)) == 0) {
// need to resize.
PoolInfo *tmp = static_cast<PoolInfo*>(this->LifoAlloc_.alloc(sizeof(PoolInfo) * numDumps * 2));
if (tmp == NULL) {
this->fail_oom();
return;
}
memcpy(tmp, poolInfo, sizeof(PoolInfo) * numDumps);
poolInfo = tmp;
}
// In order to figure out how to fix up the loads for the second half of the pool
// we need to find where the bits of the pool that have been implemented end.
int poolOffset = perforation.getOffset();
int magicAlign = getInfo(numDumps-1).finalPos - getInfo(numDumps-1).offset;
poolOffset += magicAlign;
poolOffset += headerSize;
for (int poolIdx = 0; poolIdx < numPoolKinds; poolIdx++) {
poolOffset=pools[poolIdx].align(poolOffset);
poolOffset+=pools[poolIdx].numEntries * pools[poolIdx].immSize;
}
LoadOffsets outcasts[1 << poolKindBits];
uint8_t *outcastEntries[1 << poolKindBits];
// All of the pool loads referred to by this code are going to
// need fixing up here.
int skippedBytes = 0;
for (int poolIdx = numPoolKinds-1; poolIdx >= 0; poolIdx--) {
Pool *p = pools[poolIdx].other;
JS_ASSERT(p != NULL);
unsigned int idx = p->numEntries-1;
// Allocate space for tracking information that needs to be propagated to the next pool
// as well as space for quickly updating the pool entries in the current pool to remove
// the entries that don't actually fit. I probably should change this over to a vector
outcastEntries[poolIdx] = new uint8_t[p->getPoolSize()];
bool *preservedEntries = new bool[p->numEntries];
// Hacks on top of Hacks!
// the patching code takes in the address of the instruction to be patched,
// and the "address" of the element in the pool that we want to load.
// However, since the code isn't actually in an array, we need to lie about
// the address that the pool is in. Furthermore, since the offsets are
// technically from the beginning of the FORWARD reference section, we have
// to lie to ourselves about where this pool starts in order to make sure
// the distance into the pool is interpreted correctly.
// There is a more elegant way to fix this that will need to be implemented
// eventually. We will want to provide the fixup function with a method to
// convert from a 'token' into a pool offset.
poolOffset = p->align(poolOffset);
int numSkips = 0;
int fakePoolOffset = poolOffset - pools[poolIdx].numEntries * pools[poolIdx].immSize;
for (BufferOffset *iter = p->loadOffsets.end()-1;
iter != p->loadOffsets.begin()-1; --iter, --idx)
{
IonSpew(IonSpew_Pools, "[%d] Linking entry %d in pool %d", id, idx+ pools[poolIdx].numEntries, poolIdx);
JS_ASSERT(iter->getOffset() >= perforation.getOffset());
// Everything here is known, we can safely do the necessary substitutions
Inst * inst = this->getInst(*iter);
// Manually compute the offset, including a possible bias.
// Also take into account the whole size of the pool that is being placed.
int codeOffset = fakePoolOffset - iter->getOffset() - newPoolInfo.size + numSkips * p->immSize - skippedBytes;
// That is, patchConstantPoolLoad wants to be handed the address of the
// pool entry that is being loaded. We need to do a non-trivial amount
// of math here, since the pool that we've made does not actually reside there
// in memory.
IonSpew(IonSpew_Pools, "[%d] Fixing offset to %d", id, codeOffset - magicAlign);
if (!Asm::patchConstantPoolLoad(inst, (uint8_t*)inst + codeOffset - magicAlign)) {
// NOTE: if removing this entry happens to change the alignment of the next
// block, chances are you will have a bad time.
// ADDENDUM: this CANNOT happen on ARM, because the only elements that
// fall into this case are doubles loaded via vfp, but they will also be
// the last pool, which means it cannot affect the alignment of any other
// Sub Pools.
IonSpew(IonSpew_Pools, "[%d]***Offset was still out of range!***", id, codeOffset - magicAlign);
IonSpew(IonSpew_Pools, "[%d] Too complicated; bailingp", id);
this->fail_bail();
return;
} else {
preservedEntries[idx] = true;
}
}
// remove the elements of the pool that should not be there (YAY, MEMCPY)
unsigned int idxDest = 0;
// If no elements were skipped, no expensive copy is necessary.
if (numSkips != 0) {
for (idx = 0; idx < p->numEntries; idx++) {
if (preservedEntries[idx]) {
if (idx != idxDest) {
memcpy(&p->poolData[idxDest * p->immSize],
&p->poolData[idx * p->immSize],
p->immSize);
}
idxDest++;
}
}
p->numEntries -= numSkips;
}
poolOffset += p->numEntries * p->immSize;
delete[] preservedEntries;
}
// bind the current pool to the perforation point.
Pool **tmp = &perforatedNode->data;
*tmp = static_cast<Pool*>(this->LifoAlloc_.alloc(sizeof(Pool) * numPoolKinds));
if (tmp == NULL) {
this->fail_oom();
return;
}
// The above operations may have changed the size of pools!
// recalibrate the size of the pool.
newPoolInfo = getPoolData();
poolInfo[numDumps] = newPoolInfo;
poolSize += poolInfo[numDumps].size;
numDumps++;
memcpy(*tmp, pools, sizeof(Pool) * numPoolKinds);
// reset everything to the state that it was in when we started
for (int poolIdx = 0; poolIdx < numPoolKinds; poolIdx++) {
if (!pools[poolIdx].reset(this->LifoAlloc_)) {
this->fail_oom();
return;
}
}
new (&perforation) BufferOffset();
perforatedNode = NULL;
inBackref = false;
// Now that the backwards pool has been emptied, and a new forward pool
// has been allocated, it is time to populate the new forward pool with
// any entries that couldn't fit in the backwards pool.
for (int poolIdx = 0; poolIdx < numPoolKinds; poolIdx++) {
// Technically, the innermost pool will never have this issue, but it is easier
// to just handle this case.
// Since the pool entry was filled back-to-front, and in the next buffer, the elements
// should be front-to-back, this insertion also needs to proceed backwards
int idx = outcasts[poolIdx].length();
for (BufferOffset *iter = outcasts[poolIdx].end()-1;
iter != outcasts[poolIdx].begin()-1;
--iter, --idx) {
pools[poolIdx].updateLimiter(*iter);
Inst *inst = this->getInst(*iter);
Asm::insertTokenIntoTag(pools[poolIdx].instSize, (uint8_t*)inst, outcasts[poolIdx].end()-1-iter);
pools[poolIdx].insertEntry(&outcastEntries[poolIdx][idx*pools[poolIdx].immSize], *iter, this->LifoAlloc_);
}
delete[] outcastEntries[poolIdx];
}
// this (*2) is not technically kosher, but I want to get this bug fixed.
// It should actually be guardSize + the size of the instruction that we're attempting
// to insert. Unfortunately that vaue is never passed in. On ARM, these instructions
// are always 4 bytes, so guardSize is legit to use.
poolOffset = this->size() + guardSize * 2;
poolOffset += headerSize;
for (int poolIdx = 0; poolIdx < numPoolKinds; poolIdx++) {
// There can still be an awkward situation where the element that triggered the
// initial dump didn't fit into the pool backwards, and now, still does not fit into
// this pool. Now it is necessary to go and dump this pool (note: this is almost
// certainly being called from dumpPool()).
poolOffset = pools[poolIdx].align(poolOffset);
if (pools[poolIdx].checkFull(poolOffset)) {
// ONCE AGAIN, UH-OH, TIME TO BAIL
dumpPool();
break;
}
poolOffset += pools[poolIdx].getPoolSize();
}
}
void dumpPool() {
JS_ASSERT(!inBackref);
IonSpew(IonSpew_Pools, "[%d] Attempting to dump the pool", id);
PoolInfo newPoolInfo = getPoolData();
if (newPoolInfo.size == 0) {
// If there is no data in the pool being dumped, don't dump anything.
inBackref = true;
IonSpew(IonSpew_Pools, "[%d]Abort, no pool data", id);
return;
}
IonSpew(IonSpew_Pools, "[%d] Dumping %d bytes", id, newPoolInfo.size);
if (!perforation.assigned()) {
IonSpew(IonSpew_Pools, "[%d] No Perforation point selected, generating a new one", id);
// There isn't a perforation here, we need to dump the pool with a guard.
BufferOffset branch = this->nextOffset();
bool shouldMarkAsBranch = this->isNextBranch();
this->markNextAsBranch();
this->putBlob(guardSize, NULL);
BufferOffset afterPool = this->nextOffset();
Asm::writePoolGuard(branch, this->getInst(branch), afterPool);
markGuard();
perforatedNode->isNatural = false;
if (shouldMarkAsBranch)
this->markNextAsBranch();
}
// We have a perforation. Time to cut the instruction stream, patch in the pool
// and possibly re-arrange the pool to accomodate its new location.
int poolOffset = perforation.getOffset();
int magicAlign = getInfo(numDumps-1).finalPos - getInfo(numDumps-1).offset;
poolOffset += magicAlign;
poolOffset += headerSize;
for (int poolIdx = 0; poolIdx < numPoolKinds; poolIdx++) {
bool beforePool = true;
Pool *p = &pools[poolIdx];
// Any entries that happened to be after the place we put our pool will need to be
// switched from the forward-referenced pool to the backward-refrenced pool.
int idx = 0;
for (BufferOffset *iter = p->loadOffsets.begin();
iter != p->loadOffsets.end(); ++iter, ++idx)
{
if (iter->getOffset() >= perforation.getOffset()) {
IonSpew(IonSpew_Pools, "[%d] Pushing entry %d in pool %d into the backwards section.", id, idx, poolIdx);
// insert this into the rear part of the pool.
int offset = idx * p->immSize;
p->other->insertEntry(&p->poolData[offset], BufferOffset(*iter), this->LifoAlloc_);
// update the limiting entry for this pool.
p->other->updateLimiter(*iter);
// Update the current pool to report fewer entries. They are now in the
// backwards section.
p->numEntries--;
beforePool = false;
} else {
JS_ASSERT(beforePool);
// align the pool offset to the alignment of this pool
// it already only aligns when the pool has data in it, but we want to not
// align when all entries will end up in the backwards half of the pool
poolOffset = p->align(poolOffset);
IonSpew(IonSpew_Pools, "[%d] Entry %d in pool %d is before the pool.", id, idx, poolIdx);
// Everything here is known, we can safely do the necessary substitutions
Inst * inst = this->getInst(*iter);
// We need to manually compute the offset, including a possible bias.
int codeOffset = poolOffset - iter->getOffset();
// That is, patchConstantPoolLoad wants to be handed the address of the
// pool entry that is being loaded. We need to do a non-trivial amount
// of math here, since the pool that we've made does not actually reside there
// in memory.
IonSpew(IonSpew_Pools, "[%d] Fixing offset to %d", id, codeOffset - magicAlign);
Asm::patchConstantPoolLoad(inst, (uint8_t*)inst + codeOffset - magicAlign);
}
}
// Some number of entries have been positively identified as being
// in this section of the pool. Before processing the next pool,
// update the offset from the beginning of the buffer
poolOffset += p->numEntries * p->immSize;
}
poolOffset = footerSize;
inBackref = true;
for (int poolIdx = numPoolKinds-1; poolIdx >= 0; poolIdx--) {
Pool *tmp = pools[poolIdx].other;
if (tmp->checkFullBackref(poolOffset, perforation.getOffset())) {
// GNAAAH. While we rotated elements into the back half, one of them filled up
// Now, dumping the back half is necessary...
finishPool();
break;
}
}
}
void flushPool() {
if (this->oom())
return;
IonSpew(IonSpew_Pools, "[%d] Requesting a pool flush", id);
if (!inBackref)
dumpPool();
finishPool();
}
void patchBranch(Inst *i, int curpool, BufferOffset branch) {
const Inst *ci = i;
ptrdiff_t offset = Asm::getBranchOffset(ci);
// If the offset is 0, then there is nothing to do.
if (offset == 0)
return;
int destOffset = branch.getOffset() + offset;
if (offset > 0) {
while (curpool < numDumps && poolInfo[curpool].offset <= destOffset) {
offset += poolInfo[curpool].size;
curpool++;
}
} else {
// Ignore the pool that comes next, since this is a backwards branch
curpool--;
while (curpool >= 0 && poolInfo[curpool].offset > destOffset) {
offset -= poolInfo[curpool].size;
curpool--;
}
// Can't assert anything here, since the first pool may be after the target.
}
Asm::retargetNearBranch(i, offset, false);
}
// Mark the next instruction as a valid guard. This means we can place a pool here.
void markGuard() {
// If we are in a no pool zone then there is no point in dogearing
// this branch as a place to go back to
if (canNotPlacePool)
return;
// There is no point in trying to grab a new slot if we've already
// found one and are in the process of filling it in.
if (inBackref)
return;
perforate();
}
void enterNoPool() {
if (!canNotPlacePool && !perforation.assigned()) {
// Embarassing mode: The Assembler requests the start of a no pool section
// and there have been no valid places that a pool could be dumped thusfar.
// If a pool were to fill up before this no-pool section ends, we need to go back
// in the stream and enter a pool guard after the fact. This is feasable, but
// for now, it is easier to just allocate a junk instruction, default it to a nop, and
// finally, if the pool *is* needed, patch the nop to apool guard.
// What the assembler requests:
// #request no-pool zone
// push pc
// blx r12
// #end no-pool zone
// however, if we would need to insert a pool, and there is no perforation point...
// so, actual generated code:
// b next; <= perforation point
// next:
// #beginning of no pool zone
// push pc
// blx r12
BufferOffset branch = this->nextOffset();
this->markNextAsBranch();
this->putBlob(guardSize, NULL);
BufferOffset afterPool = this->nextOffset();
Asm::writePoolGuard(branch, this->getInst(branch), afterPool);
markGuard();
if (perforatedNode != NULL)
perforatedNode->isNatural = false;
}
canNotPlacePool++;
}
void leaveNoPool() {
canNotPlacePool--;
}
int size() const {
return uncheckedSize();
}
Pool *getPool(int idx) {
return &pools[idx];
}
void markNextAsBranch() {
// If the previous thing inserted was the last instruction of
// the node, then whoops, we want to mark the first instruction of
// the next node.
this->ensureSpace(InstBaseSize);
JS_ASSERT(*this->getTail() != NULL);
(*this->getTail())->markNextAsBranch();
}
bool isNextBranch() {
JS_ASSERT(*this->getTail() != NULL);
return (*this->getTail())->isNextBranch();
}
int uncheckedSize() const {
PoolInfo pi = getPoolData();
int codeEnd = this->nextOffset().getOffset();
return (codeEnd - pi.offset) + pi.finalPos;
}
ptrdiff_t curDumpsite;
void resetCounter() {
curDumpsite = 0;
}
ptrdiff_t poolSizeBefore(ptrdiff_t offset) const {
int cur = 0;
while(cur < numDumps && poolInfo[cur].offset <= offset)
cur++;
// poolInfo[curDumpsite] is now larger than the offset
// either this is the first one, or the previous is the last one we care about
if (cur == 0)
return 0;
return poolInfo[cur-1].finalPos - poolInfo[cur-1].offset;
}
private:
void getPEPool(PoolEntry pe, Pool **retP, int32_t * retOffset, int32_t *poolNum) const {
int poolKind = pe.poolKind();
Pool *p = NULL;
uint32_t offset = pe.offset() * pools[poolKind].immSize;
int idx;
for (idx = 0; idx < numDumps; idx++) {
p = &poolInfo[idx].slice->data[poolKind];
if (p->getPoolSize() > offset)
break;
offset -= p->getPoolSize();
p = p->other;
if (p->getPoolSize() > offset)
break;
offset -= p->getPoolSize();
p = NULL;
}
if (poolNum != NULL)
*poolNum = idx;
// If this offset is contained in any finished pool, forward or backwards, p now
// points to that pool, if it is not in any pool (should be in the currently building pool)
// then p is NULL.
if (p == NULL) {
p = &pools[poolKind];
if (offset >= p->getPoolSize()) {
p = p->other;
offset -= p->getPoolSize();
}
}
JS_ASSERT(p != NULL);
JS_ASSERT(offset < p->getPoolSize());
*retP = p;
*retOffset = offset;
}
uint8_t *getPoolEntry(PoolEntry pe) {
Pool *p;
int32_t offset;
getPEPool(pe, &p, &offset, NULL);
return &p->poolData[offset];
}
size_t getPoolEntrySize(PoolEntry pe) {
int idx = pe.poolKind();
return pools[idx].immSize;
}
public:
uint32_t poolEntryOffset(PoolEntry pe) const {
Pool *realPool;
// offset is in bytes, not entries.
int32_t offset;
int32_t poolNum;
getPEPool(pe, &realPool, &offset, &poolNum);
PoolInfo *pi = &poolInfo[poolNum];
Pool *poolGroup = pi->slice->data;
uint32_t start = pi->finalPos - pi->size + headerSize;
/// The order of the pools is:
// A B C C_Rev B_Rev A_Rev, so in the initial pass,
// go through the pools forwards, and in the second pass
// go through them in reverse order.
for (int idx = 0; idx < numPoolKinds; idx++) {
if (&poolGroup[idx] == realPool) {
return start + offset;
}
start = poolGroup[idx].addPoolSize(start);
}
for (int idx = numPoolKinds-1; idx >= 0; idx--) {
if (poolGroup[idx].other == realPool) {
return start + offset;
}
start = poolGroup[idx].other->addPoolSize(start);
}
JS_NOT_REACHED("Entry is not in a pool");
return -1;
}
void writePoolEntry(PoolEntry pe, uint8_t *buff) {
size_t size = getPoolEntrySize(pe);
uint8_t *entry = getPoolEntry(pe);
memcpy(entry, buff, size);
}
void readPoolEntry(PoolEntry pe, uint8_t *buff) {
size_t size = getPoolEntrySize(pe);
uint8_t *entry = getPoolEntry(pe);
memcpy(buff, entry, size);
}
};
} // ion
} // js
#endif /* jit_shared_IonAssemblerBufferWithConstantPools_h */