654 lines
16 KiB
C++
654 lines
16 KiB
C++
// [AsmJit]
|
|
// Complete x86/x64 JIT and Remote Assembler for C++.
|
|
//
|
|
// [License]
|
|
// Zlib - See LICENSE.md file in the package.
|
|
|
|
// [Export]
|
|
#define ASMJIT_EXPORTS
|
|
|
|
// [Guard]
|
|
#include "../build.h"
|
|
#if !defined(ASMJIT_DISABLE_COMPILER)
|
|
|
|
// [Dependencies]
|
|
#include "../base/compilercontext_p.h"
|
|
#include "../base/utils.h"
|
|
|
|
// [Api-Begin]
|
|
#include "../apibegin.h"
|
|
|
|
namespace asmjit {
|
|
|
|
// ============================================================================
|
|
// [asmjit::Context - Construction / Destruction]
|
|
// ============================================================================
|
|
|
|
Context::Context(Compiler* compiler) :
|
|
_compiler(compiler),
|
|
_zoneAllocator(8192 - Zone::kZoneOverhead),
|
|
_traceNode(nullptr),
|
|
_varMapToVaListOffset(0) {
|
|
|
|
Context::reset();
|
|
}
|
|
Context::~Context() {}
|
|
|
|
// ============================================================================
|
|
// [asmjit::Context - Reset]
|
|
// ============================================================================
|
|
|
|
void Context::reset(bool releaseMemory) {
|
|
_zoneAllocator.reset(releaseMemory);
|
|
|
|
_func = nullptr;
|
|
_start = nullptr;
|
|
_end = nullptr;
|
|
_extraBlock = nullptr;
|
|
_stop = nullptr;
|
|
|
|
_unreachableList.reset();
|
|
_returningList.reset();
|
|
_jccList.reset();
|
|
_contextVd.reset(releaseMemory);
|
|
|
|
_memVarCells = nullptr;
|
|
_memStackCells = nullptr;
|
|
|
|
_mem1ByteVarsUsed = 0;
|
|
_mem2ByteVarsUsed = 0;
|
|
_mem4ByteVarsUsed = 0;
|
|
_mem8ByteVarsUsed = 0;
|
|
_mem16ByteVarsUsed = 0;
|
|
_mem32ByteVarsUsed = 0;
|
|
_mem64ByteVarsUsed = 0;
|
|
_memStackCellsUsed = 0;
|
|
|
|
_memMaxAlign = 0;
|
|
_memVarTotal = 0;
|
|
_memStackTotal = 0;
|
|
_memAllTotal = 0;
|
|
_annotationLength = 12;
|
|
|
|
_state = nullptr;
|
|
}
|
|
|
|
// ============================================================================
|
|
// [asmjit::Context - Mem]
|
|
// ============================================================================
|
|
|
|
static ASMJIT_INLINE uint32_t BaseContext_getDefaultAlignment(uint32_t size) {
|
|
if (size > 32)
|
|
return 64;
|
|
else if (size > 16)
|
|
return 32;
|
|
else if (size > 8)
|
|
return 16;
|
|
else if (size > 4)
|
|
return 8;
|
|
else if (size > 2)
|
|
return 4;
|
|
else if (size > 1)
|
|
return 2;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
VarCell* Context::_newVarCell(VarData* vd) {
|
|
ASMJIT_ASSERT(vd->_memCell == nullptr);
|
|
|
|
VarCell* cell;
|
|
uint32_t size = vd->getSize();
|
|
|
|
if (vd->isStack()) {
|
|
cell = _newStackCell(size, vd->getAlignment());
|
|
|
|
if (cell == nullptr)
|
|
return nullptr;
|
|
}
|
|
else {
|
|
cell = static_cast<VarCell*>(_zoneAllocator.alloc(sizeof(VarCell)));
|
|
if (cell == nullptr)
|
|
goto _NoMemory;
|
|
|
|
cell->_next = _memVarCells;
|
|
_memVarCells = cell;
|
|
|
|
cell->_offset = 0;
|
|
cell->_size = size;
|
|
cell->_alignment = size;
|
|
|
|
_memMaxAlign = Utils::iMax<uint32_t>(_memMaxAlign, size);
|
|
_memVarTotal += size;
|
|
|
|
switch (size) {
|
|
case 1: _mem1ByteVarsUsed++ ; break;
|
|
case 2: _mem2ByteVarsUsed++ ; break;
|
|
case 4: _mem4ByteVarsUsed++ ; break;
|
|
case 8: _mem8ByteVarsUsed++ ; break;
|
|
case 16: _mem16ByteVarsUsed++; break;
|
|
case 32: _mem32ByteVarsUsed++; break;
|
|
case 64: _mem64ByteVarsUsed++; break;
|
|
|
|
default:
|
|
ASMJIT_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
vd->_memCell = cell;
|
|
return cell;
|
|
|
|
_NoMemory:
|
|
_compiler->setLastError(kErrorNoHeapMemory);
|
|
return nullptr;
|
|
}
|
|
|
|
VarCell* Context::_newStackCell(uint32_t size, uint32_t alignment) {
|
|
VarCell* cell = static_cast<VarCell*>(_zoneAllocator.alloc(sizeof(VarCell)));
|
|
if (cell == nullptr)
|
|
goto _NoMemory;
|
|
|
|
if (alignment == 0)
|
|
alignment = BaseContext_getDefaultAlignment(size);
|
|
|
|
if (alignment > 64)
|
|
alignment = 64;
|
|
|
|
ASMJIT_ASSERT(Utils::isPowerOf2(alignment));
|
|
size = Utils::alignTo<uint32_t>(size, alignment);
|
|
|
|
// Insert it sorted according to the alignment and size.
|
|
{
|
|
VarCell** pPrev = &_memStackCells;
|
|
VarCell* cur = *pPrev;
|
|
|
|
while (cur != nullptr) {
|
|
if ((cur->getAlignment() > alignment) ||
|
|
(cur->getAlignment() == alignment && cur->getSize() > size)) {
|
|
pPrev = &cur->_next;
|
|
cur = *pPrev;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
cell->_next = cur;
|
|
cell->_offset = 0;
|
|
cell->_size = size;
|
|
cell->_alignment = alignment;
|
|
|
|
*pPrev = cell;
|
|
_memStackCellsUsed++;
|
|
|
|
_memMaxAlign = Utils::iMax<uint32_t>(_memMaxAlign, alignment);
|
|
_memStackTotal += size;
|
|
}
|
|
|
|
return cell;
|
|
|
|
_NoMemory:
|
|
_compiler->setLastError(kErrorNoHeapMemory);
|
|
return nullptr;
|
|
}
|
|
|
|
Error Context::resolveCellOffsets() {
|
|
VarCell* varCell = _memVarCells;
|
|
VarCell* stackCell = _memStackCells;
|
|
|
|
uint32_t stackAlignment = 0;
|
|
if (stackCell != nullptr)
|
|
stackAlignment = stackCell->getAlignment();
|
|
|
|
uint32_t pos64 = 0;
|
|
uint32_t pos32 = pos64 + _mem64ByteVarsUsed * 64;
|
|
uint32_t pos16 = pos32 + _mem32ByteVarsUsed * 32;
|
|
uint32_t pos8 = pos16 + _mem16ByteVarsUsed * 16;
|
|
uint32_t pos4 = pos8 + _mem8ByteVarsUsed * 8 ;
|
|
uint32_t pos2 = pos4 + _mem4ByteVarsUsed * 4 ;
|
|
uint32_t pos1 = pos2 + _mem2ByteVarsUsed * 2 ;
|
|
|
|
uint32_t stackPos = pos1 + _mem1ByteVarsUsed;
|
|
|
|
uint32_t gapAlignment = stackAlignment;
|
|
uint32_t gapSize = 0;
|
|
|
|
// TODO: Not used!
|
|
if (gapAlignment)
|
|
Utils::alignDiff(stackPos, gapAlignment);
|
|
stackPos += gapSize;
|
|
|
|
uint32_t gapPos = stackPos;
|
|
uint32_t allTotal = stackPos;
|
|
|
|
// Vars - Allocated according to alignment/width.
|
|
while (varCell != nullptr) {
|
|
uint32_t size = varCell->getSize();
|
|
uint32_t offset = 0;
|
|
|
|
switch (size) {
|
|
case 1: offset = pos1 ; pos1 += 1 ; break;
|
|
case 2: offset = pos2 ; pos2 += 2 ; break;
|
|
case 4: offset = pos4 ; pos4 += 4 ; break;
|
|
case 8: offset = pos8 ; pos8 += 8 ; break;
|
|
case 16: offset = pos16; pos16 += 16; break;
|
|
case 32: offset = pos32; pos32 += 32; break;
|
|
case 64: offset = pos64; pos64 += 64; break;
|
|
|
|
default:
|
|
ASMJIT_NOT_REACHED();
|
|
}
|
|
|
|
varCell->setOffset(static_cast<int32_t>(offset));
|
|
varCell = varCell->_next;
|
|
}
|
|
|
|
// Stack - Allocated according to alignment/width.
|
|
while (stackCell != nullptr) {
|
|
uint32_t size = stackCell->getSize();
|
|
uint32_t alignment = stackCell->getAlignment();
|
|
uint32_t offset;
|
|
|
|
// Try to fill the gap between variables/stack first.
|
|
if (size <= gapSize && alignment <= gapAlignment) {
|
|
offset = gapPos;
|
|
|
|
gapSize -= size;
|
|
gapPos -= size;
|
|
|
|
if (alignment < gapAlignment)
|
|
gapAlignment = alignment;
|
|
}
|
|
else {
|
|
offset = stackPos;
|
|
|
|
stackPos += size;
|
|
allTotal += size;
|
|
}
|
|
|
|
stackCell->setOffset(offset);
|
|
stackCell = stackCell->_next;
|
|
}
|
|
|
|
_memAllTotal = allTotal;
|
|
return kErrorOk;
|
|
}
|
|
|
|
// ============================================================================
|
|
// [asmjit::Context - RemoveUnreachableCode]
|
|
// ============================================================================
|
|
|
|
Error Context::removeUnreachableCode() {
|
|
Compiler* compiler = getCompiler();
|
|
|
|
PodList<HLNode*>::Link* link = _unreachableList.getFirst();
|
|
HLNode* stop = getStop();
|
|
|
|
while (link != nullptr) {
|
|
HLNode* node = link->getValue();
|
|
if (node != nullptr && node->getPrev() != nullptr && node != stop) {
|
|
// Locate all unreachable nodes.
|
|
HLNode* first = node;
|
|
do {
|
|
if (node->isFetched())
|
|
break;
|
|
node = node->getNext();
|
|
} while (node != stop);
|
|
|
|
// Remove unreachable nodes that are neither informative nor directives.
|
|
if (node != first) {
|
|
HLNode* end = node;
|
|
node = first;
|
|
|
|
// NOTE: The strategy is as follows:
|
|
// 1. The algorithm removes everything until it finds a first label.
|
|
// 2. After the first label is found it removes only removable nodes.
|
|
bool removeEverything = true;
|
|
do {
|
|
HLNode* next = node->getNext();
|
|
bool remove = node->isRemovable();
|
|
|
|
if (!remove) {
|
|
if (node->isLabel())
|
|
removeEverything = false;
|
|
remove = removeEverything;
|
|
}
|
|
|
|
if (remove) {
|
|
ASMJIT_TSEC({
|
|
this->_traceNode(this, node, "[REMOVED UNREACHABLE] ");
|
|
});
|
|
compiler->removeNode(node);
|
|
}
|
|
|
|
node = next;
|
|
} while (node != end);
|
|
}
|
|
}
|
|
|
|
link = link->getNext();
|
|
}
|
|
|
|
return kErrorOk;
|
|
}
|
|
|
|
// ============================================================================
|
|
// [asmjit::Context - Liveness Analysis]
|
|
// ============================================================================
|
|
|
|
//! \internal
|
|
struct LivenessTarget {
|
|
//! Previous target.
|
|
LivenessTarget* prev;
|
|
|
|
//! Target node.
|
|
HLLabel* node;
|
|
//! Jumped from.
|
|
HLJump* from;
|
|
};
|
|
|
|
Error Context::livenessAnalysis() {
|
|
uint32_t bLen = static_cast<uint32_t>(
|
|
((_contextVd.getLength() + BitArray::kEntityBits - 1) / BitArray::kEntityBits));
|
|
|
|
// No variables.
|
|
if (bLen == 0)
|
|
return kErrorOk;
|
|
|
|
HLFunc* func = getFunc();
|
|
HLJump* from = nullptr;
|
|
|
|
LivenessTarget* ltCur = nullptr;
|
|
LivenessTarget* ltUnused = nullptr;
|
|
|
|
PodList<HLNode*>::Link* retPtr = _returningList.getFirst();
|
|
ASMJIT_ASSERT(retPtr != nullptr);
|
|
|
|
HLNode* node = retPtr->getValue();
|
|
|
|
size_t varMapToVaListOffset = _varMapToVaListOffset;
|
|
BitArray* bCur = newBits(bLen);
|
|
|
|
if (bCur == nullptr)
|
|
goto _NoMemory;
|
|
|
|
// Allocate bits for code visited first time.
|
|
_OnVisit:
|
|
for (;;) {
|
|
if (node->hasLiveness()) {
|
|
if (bCur->_addBitsDelSource(node->getLiveness(), bCur, bLen))
|
|
goto _OnPatch;
|
|
else
|
|
goto _OnDone;
|
|
}
|
|
|
|
BitArray* bTmp = copyBits(bCur, bLen);
|
|
if (bTmp == nullptr)
|
|
goto _NoMemory;
|
|
|
|
node->setLiveness(bTmp);
|
|
VarMap* map = node->getMap();
|
|
|
|
if (map != nullptr) {
|
|
uint32_t vaCount = map->getVaCount();
|
|
VarAttr* vaList = reinterpret_cast<VarAttr*>(((uint8_t*)map) + varMapToVaListOffset);
|
|
|
|
for (uint32_t i = 0; i < vaCount; i++) {
|
|
VarAttr* va = &vaList[i];
|
|
VarData* vd = va->getVd();
|
|
|
|
uint32_t flags = va->getFlags();
|
|
uint32_t localId = vd->getLocalId();
|
|
|
|
if ((flags & kVarAttrWAll) && !(flags & kVarAttrRAll)) {
|
|
// Write-Only.
|
|
bTmp->setBit(localId);
|
|
bCur->delBit(localId);
|
|
}
|
|
else {
|
|
// Read-Only or Read/Write.
|
|
bTmp->setBit(localId);
|
|
bCur->setBit(localId);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (node->getType() == HLNode::kTypeLabel)
|
|
goto _OnTarget;
|
|
|
|
if (node == func)
|
|
goto _OnDone;
|
|
|
|
ASMJIT_ASSERT(node->getPrev());
|
|
node = node->getPrev();
|
|
}
|
|
|
|
// Patch already generated liveness bits.
|
|
_OnPatch:
|
|
for (;;) {
|
|
ASMJIT_ASSERT(node->hasLiveness());
|
|
BitArray* bNode = node->getLiveness();
|
|
|
|
if (!bNode->_addBitsDelSource(bCur, bLen))
|
|
goto _OnDone;
|
|
|
|
if (node->getType() == HLNode::kTypeLabel)
|
|
goto _OnTarget;
|
|
|
|
if (node == func)
|
|
goto _OnDone;
|
|
|
|
node = node->getPrev();
|
|
}
|
|
|
|
_OnTarget:
|
|
if (static_cast<HLLabel*>(node)->getNumRefs() != 0) {
|
|
// Push a new LivenessTarget onto the stack if needed.
|
|
if (ltCur == nullptr || ltCur->node != node) {
|
|
// Allocate a new LivenessTarget object (from pool or zone).
|
|
LivenessTarget* ltTmp = ltUnused;
|
|
|
|
if (ltTmp != nullptr) {
|
|
ltUnused = ltUnused->prev;
|
|
}
|
|
else {
|
|
ltTmp = _zoneAllocator.allocT<LivenessTarget>(
|
|
sizeof(LivenessTarget) - sizeof(BitArray) + bLen * sizeof(uintptr_t));
|
|
|
|
if (ltTmp == nullptr)
|
|
goto _NoMemory;
|
|
}
|
|
|
|
// Initialize and make current - ltTmp->from will be set later on.
|
|
ltTmp->prev = ltCur;
|
|
ltTmp->node = static_cast<HLLabel*>(node);
|
|
ltCur = ltTmp;
|
|
|
|
from = static_cast<HLLabel*>(node)->getFrom();
|
|
ASMJIT_ASSERT(from != nullptr);
|
|
}
|
|
else {
|
|
from = ltCur->from;
|
|
goto _OnJumpNext;
|
|
}
|
|
|
|
// Visit/Patch.
|
|
do {
|
|
ltCur->from = from;
|
|
bCur->copyBits(node->getLiveness(), bLen);
|
|
|
|
if (!from->hasLiveness()) {
|
|
node = from;
|
|
goto _OnVisit;
|
|
}
|
|
|
|
// Issue #25: Moved '_OnJumpNext' here since it's important to patch
|
|
// code again if there are more live variables than before.
|
|
_OnJumpNext:
|
|
if (bCur->delBits(from->getLiveness(), bLen)) {
|
|
node = from;
|
|
goto _OnPatch;
|
|
}
|
|
|
|
from = from->getJumpNext();
|
|
} while (from != nullptr);
|
|
|
|
// Pop the current LivenessTarget from the stack.
|
|
{
|
|
LivenessTarget* ltTmp = ltCur;
|
|
|
|
ltCur = ltCur->prev;
|
|
ltTmp->prev = ltUnused;
|
|
ltUnused = ltTmp;
|
|
}
|
|
}
|
|
|
|
bCur->copyBits(node->getLiveness(), bLen);
|
|
node = node->getPrev();
|
|
|
|
if (node->isJmp() || !node->isFetched())
|
|
goto _OnDone;
|
|
|
|
if (!node->hasLiveness())
|
|
goto _OnVisit;
|
|
|
|
if (bCur->delBits(node->getLiveness(), bLen))
|
|
goto _OnPatch;
|
|
|
|
_OnDone:
|
|
if (ltCur != nullptr) {
|
|
node = ltCur->node;
|
|
from = ltCur->from;
|
|
|
|
goto _OnJumpNext;
|
|
}
|
|
|
|
retPtr = retPtr->getNext();
|
|
if (retPtr != nullptr) {
|
|
node = retPtr->getValue();
|
|
goto _OnVisit;
|
|
}
|
|
|
|
return kErrorOk;
|
|
|
|
_NoMemory:
|
|
return setLastError(kErrorNoHeapMemory);
|
|
}
|
|
|
|
// ============================================================================
|
|
// [asmjit::Context - Annotate]
|
|
// ============================================================================
|
|
|
|
Error Context::formatInlineComment(StringBuilder& dst, HLNode* node) {
|
|
#if !defined(ASMJIT_DISABLE_LOGGER)
|
|
if (node->getComment())
|
|
dst.appendString(node->getComment());
|
|
|
|
if (node->hasLiveness()) {
|
|
if (dst.getLength() < _annotationLength)
|
|
dst.appendChars(' ', _annotationLength - dst.getLength());
|
|
|
|
uint32_t vdCount = static_cast<uint32_t>(_contextVd.getLength());
|
|
size_t offset = dst.getLength() + 1;
|
|
|
|
dst.appendChar('[');
|
|
dst.appendChars(' ', vdCount);
|
|
dst.appendChar(']');
|
|
|
|
BitArray* liveness = node->getLiveness();
|
|
VarMap* map = node->getMap();
|
|
|
|
uint32_t i;
|
|
for (i = 0; i < vdCount; i++) {
|
|
if (liveness->getBit(i))
|
|
dst.getData()[offset + i] = '.';
|
|
}
|
|
|
|
if (map != nullptr) {
|
|
uint32_t vaCount = map->getVaCount();
|
|
VarAttr* vaList = reinterpret_cast<VarAttr*>(((uint8_t*)map) + _varMapToVaListOffset);
|
|
|
|
for (i = 0; i < vaCount; i++) {
|
|
VarAttr* va = &vaList[i];
|
|
VarData* vd = va->getVd();
|
|
|
|
uint32_t flags = va->getFlags();
|
|
char c = 'u';
|
|
|
|
if ( (flags & kVarAttrRAll) && !(flags & kVarAttrWAll)) c = 'r';
|
|
if (!(flags & kVarAttrRAll) && (flags & kVarAttrWAll)) c = 'w';
|
|
if ( (flags & kVarAttrRAll) && (flags & kVarAttrWAll)) c = 'x';
|
|
|
|
// Uppercase if unused.
|
|
if ((flags & kVarAttrUnuse))
|
|
c -= 'a' - 'A';
|
|
|
|
ASMJIT_ASSERT(offset + vd->getLocalId() < dst.getLength());
|
|
dst._data[offset + vd->getLocalId()] = c;
|
|
}
|
|
}
|
|
}
|
|
#endif // !ASMJIT_DISABLE_LOGGER
|
|
|
|
return kErrorOk;
|
|
}
|
|
|
|
// ============================================================================
|
|
// [asmjit::Context - Cleanup]
|
|
// ============================================================================
|
|
|
|
void Context::cleanup() {
|
|
VarData** array = _contextVd.getData();
|
|
size_t length = _contextVd.getLength();
|
|
|
|
for (size_t i = 0; i < length; i++) {
|
|
VarData* vd = array[i];
|
|
vd->resetLocalId();
|
|
vd->resetRegIndex();
|
|
}
|
|
|
|
_contextVd.reset(false);
|
|
_extraBlock = nullptr;
|
|
}
|
|
|
|
// ============================================================================
|
|
// [asmjit::Context - CompileFunc]
|
|
// ============================================================================
|
|
|
|
Error Context::compile(HLFunc* func) {
|
|
HLNode* end = func->getEnd();
|
|
HLNode* stop = end->getNext();
|
|
|
|
_func = func;
|
|
_stop = stop;
|
|
_extraBlock = end;
|
|
|
|
ASMJIT_PROPAGATE_ERROR(fetch());
|
|
ASMJIT_PROPAGATE_ERROR(removeUnreachableCode());
|
|
ASMJIT_PROPAGATE_ERROR(livenessAnalysis());
|
|
|
|
Compiler* compiler = getCompiler();
|
|
|
|
#if !defined(ASMJIT_DISABLE_LOGGER)
|
|
if (compiler->getAssembler()->hasLogger())
|
|
ASMJIT_PROPAGATE_ERROR(annotate());
|
|
#endif // !ASMJIT_DISABLE_LOGGER
|
|
|
|
ASMJIT_PROPAGATE_ERROR(translate());
|
|
|
|
// We alter the compiler cursor, because it doesn't make sense to reference
|
|
// it after compilation - some nodes may disappear and it's forbidden to add
|
|
// new code after the compilation is done.
|
|
compiler->_setCursor(nullptr);
|
|
|
|
return kErrorOk;
|
|
}
|
|
|
|
} // asmjit namespace
|
|
|
|
// [Api-End]
|
|
#include "../apiend.h"
|
|
|
|
// [Guard]
|
|
#endif // !ASMJIT_DISABLE_COMPILER
|