sourcemod/public/jit/assembler.h

295 lines
7.4 KiB
C++

/**
* vim: set ts=8 sts=2 sw=2 tw=99 et:
* =============================================================================
* SourcePawn JIT SDK
* Copyright (C) 2004-2013 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _include_sourcepawn_assembler_h__
#define _include_sourcepawn_assembler_h__
#include <assert.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <limits.h>
class Assembler
{
public:
static const size_t kMinBufferSize = 4096;
static const size_t kMaxInstructionSize = 32;
static const size_t kMaxBufferSize = INT_MAX / 2;
public:
Assembler() {
buffer_ = (uint8_t *)malloc(kMinBufferSize);
pos_ = buffer_;
end_ = buffer_ + kMinBufferSize;
outOfMemory_ = !buffer_;
}
~Assembler() {
free(buffer_);
}
bool outOfMemory() const {
return outOfMemory_;
}
// Amount needed to allocate for executable code.
size_t length() const {
return pos_ - buffer_;
}
protected:
void writeByte(uint8_t byte) {
write<uint8_t>(byte);
}
void writeInt32(int32_t word) {
write<int32_t>(word);
}
void writeUint32(uint32_t word) {
write<uint32_t>(word);
}
void writePointer(void *ptr) {
write<void *>(ptr);
}
template <typename T>
void write(const T &t) {
assertCanWrite(sizeof(T));
*reinterpret_cast<T *>(pos_) = t;
pos_ += sizeof(T);
}
// Normally this does not need to be checked, but it must be called before
// emitting any instruction.
bool ensureSpace() {
if (pos_ + kMaxInstructionSize <= end_)
return true;
if (outOfMemory())
return false;
size_t oldlength = size_t(end_ - buffer_);
if (oldlength * 2 > kMaxBufferSize) {
// See comment when if realloc() fails.
pos_ = buffer_;
outOfMemory_ = true;
return false;
}
size_t oldpos = size_t(pos_ - buffer_);
uint8_t *newbuf = (uint8_t *)realloc(buffer_, oldlength * 2);
if (!newbuf) {
// Writes will be safe, though we'll corrupt the instruction stream, so
// actually using the buffer will be invalid and compilation should be
// aborted when possible.
pos_ = buffer_;
outOfMemory_ = true;
return false;
}
buffer_ = newbuf;
end_ = newbuf + oldlength * 2;
pos_ = buffer_ + oldpos;
return true;
}
// Position will never be negative, but it's nice to have signed results
// for relative address calculation.
int32_t position() const {
return int32_t(pos_ - buffer_);
}
// pc is the unsigned version of position().
uint32_t pc() const {
return uint32_t(pos_ - buffer_);
}
protected:
void assertCanWrite(size_t bytes) {
assert(pos_ + bytes <= end_);
}
uint8_t *buffer() const {
return buffer_;
}
private:
uint8_t *buffer_;
uint8_t *end_;
protected:
uint8_t *pos_;
bool outOfMemory_;
};
class ExternalAddress
{
public:
explicit ExternalAddress(void *p)
: p_(p)
{
}
void *address() const {
return p_;
}
uintptr_t value() const {
return uintptr_t(p_);
}
private:
void *p_;
};
// A label is a lightweight object to assist in managing relative jumps. It
// exists in three states:
// * Unbound, Unused: The label has no incoming jumps, and its position has
// not yet been fixed in the instruction stream.
// * Unbound, Used: The label has not yet been fixed at a position in the
// instruction stream, but it has incoming jumps.
// * Bound: The label has been fixed at a position in the instruction stream.
//
// When a label is unbound and used, the offset stored in the Label is a linked
// list threaded through each individual jump. When the label is bound, each
// jump instruction in this list is immediately patched with the correctly
// computed relative distance to the label.
//
// We keep sizeof(Label) == 4 to make it embeddable within code streams if
// need be (for example, SourcePawn mirrors the source code to maintain jump
// maps).
class Label
{
// If set on status_, the label is bound.
static const int32_t kBound = (1 << 0);
public:
Label()
: status_(0)
{
}
~Label()
{
assert(!used() || bound());
}
static inline bool More(uint32_t status) {
return status != 0;
}
static inline uint32_t ToOffset(uint32_t status) {
return status >> 1;
}
bool used() const {
return bound() || !!(status_ >> 1);
}
bool bound() const {
return !!(status_ & kBound);
}
uint32_t offset() const {
assert(bound());
return ToOffset(status_);
}
uint32_t status() const {
assert(!bound());
return status_;
}
uint32_t addPending(uint32_t pc) {
assert(pc <= INT_MAX / 2);
uint32_t prev = status_;
status_ = pc << 1;
return prev;
}
void bind(uint32_t offset) {
assert(!bound());
status_ = (offset << 1) | kBound;
assert(this->offset() == offset);
}
private:
// Note that 0 as an invalid offset is okay, because the offset we save for
// pending jumps are after the jump opcode itself, and therefore 0 is never
// valid, since there are no 0-byte jumps.
uint32_t status_;
};
// A DataLabel is a special form of Label intended for absolute addresses that
// are within the code buffer, and thus aren't known yet, and will be
// automatically fixed up when calling emitToExecutableMemory().
//
// Unlike normal Labels, these do not store a list of incoming uses.
class DataLabel
{
// If set on status_, the label is bound.
static const int32_t kBound = (1 << 0);
public:
DataLabel()
: status_(0)
{
}
~DataLabel()
{
assert(!used() || bound());
}
static inline uint32_t ToOffset(uint32_t status) {
return status >> 1;
}
bool used() const {
return bound() || !!(status_ >> 1);
}
bool bound() const {
return !!(status_ & kBound);
}
uint32_t offset() const {
assert(bound());
return ToOffset(status_);
}
uint32_t status() const {
assert(!bound());
return status_;
}
void use(uint32_t pc) {
assert(!used());
status_ = (pc << 1);
assert(ToOffset(status_) == pc);
}
void bind(uint32_t offset) {
assert(!bound());
status_ = (offset << 1) | kBound;
assert(this->offset() == offset);
}
private:
uint32_t status_;
};
#endif // _include_sourcepawn_assembler_h__