/** * 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 . * * 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 . * * Version: $Id$ */ #ifndef _include_sourcepawn_assembler_h__ #define _include_sourcepawn_assembler_h__ #include #include #include #include #include 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(byte); } void writeInt32(int32_t word) { write(word); } void writeUint32(uint32_t word) { write(word); } void writePointer(void *ptr) { write(ptr); } template void write(const T &t) { assertCanWrite(sizeof(T)); *reinterpret_cast(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__