sourcemod/sourcepawn/jit/interpreter.cpp
2015-02-24 20:41:51 -08:00

901 lines
20 KiB
C++

// vim: set ts=8 sts=2 sw=2 tw=99 et:
//
// This file is part of SourcePawn.
//
// SourcePawn is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// SourcePawn 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 SourcePawn. If not, see <http://www.gnu.org/licenses/>.
#include <limits.h>
#include <string.h>
#include "interpreter.h"
#include "opcodes.h"
#include "watchdog_timer.h"
#include "environment.h"
#define STACK_MARGIN 64
using namespace sp;
using namespace SourcePawn;
static inline bool
IsValidOffset(uint32_t cip)
{
return cip % 4 == 0;
}
static inline cell_t
Read(const sp_plugin_t *plugin, cell_t offset)
{
return *reinterpret_cast<cell_t *>(plugin->memory + offset);
}
static inline void
Write(const sp_plugin_t *plugin, cell_t offset, cell_t value)
{
*reinterpret_cast<cell_t *>(plugin->memory + offset) = value;
}
static inline cell_t *
Jump(const sp_plugin_t *plugin, sp_context_t *ctx, cell_t target)
{
if (!IsValidOffset(target) || uint32_t(target) >= plugin->pcode_size)
return NULL;
return reinterpret_cast<cell_t *>(plugin->pcode + target);
}
static inline cell_t *
JumpTarget(const sp_plugin_t *plugin, sp_context_t *ctx, cell_t *cip, bool cond, int *errp)
{
if (!cond)
return cip + 1;
cell_t target = *cip;
if (!IsValidOffset(target) || uint32_t(target) >= plugin->pcode_size) {
*errp = SP_ERROR_INVALID_INSTRUCTION;
return NULL;
}
cell_t *next = reinterpret_cast<cell_t *>(plugin->pcode + target);
if (next < cip && !Environment::get()->watchdog()->HandleInterrupt()) {
*errp = SP_ERROR_TIMEOUT;
return NULL;
}
return next;
}
static inline bool
CheckAddress(const sp_plugin_t *plugin, sp_context_t *ctx, cell_t *stk, cell_t addr)
{
if (uint32_t(addr) >= plugin->mem_size)
return false;
if (addr < ctx->hp)
return true;
if (reinterpret_cast<cell_t *>(plugin->memory + addr) < stk)
return false;
return true;
}
static inline int
GenerateArray(PluginRuntime *rt, sp_context_t *ctx, cell_t dims, cell_t *stk, bool autozero)
{
if (dims == 1) {
uint32_t size = *stk;
if (size == 0 || !ke::IsUint32MultiplySafe(size, 4))
return SP_ERROR_ARRAY_TOO_BIG;
*stk = ctx->hp;
uint32_t bytes = size * 4;
ctx->hp += bytes;
if (uintptr_t(ctx->plugin->memory + ctx->hp) >= uintptr_t(stk))
return SP_ERROR_HEAPLOW;
if (int err = rt->GetBaseContext()->pushTracker(bytes))
return err;
if (autozero)
memset(ctx->plugin->memory + ctx->hp, 0, bytes);
return SP_ERROR_NONE;
}
if (int err = GenerateFullArray(rt, dims, stk, autozero))
return err;
return SP_ERROR_NONE;
}
int
Interpret(PluginRuntime *rt, uint32_t aCodeStart, cell_t *rval)
{
const sp_plugin_t *plugin = rt->plugin();
cell_t *code = reinterpret_cast<cell_t *>(plugin->pcode);
cell_t *codeend = reinterpret_cast<cell_t *>(plugin->pcode + plugin->pcode_size);
if (!IsValidOffset(aCodeStart) || aCodeStart > plugin->pcode_size)
return SP_ERROR_INVALID_INSTRUCTION;
PluginContext *cx = rt->GetBaseContext();
sp_context_t *ctx = cx->GetCtx();
int err = SP_ERROR_NONE;
// Save the original frm. BaseContext won't, and if we error, we won't hit
// the stack unwinding code.
cell_t orig_frm = ctx->frm;
cell_t pri = 0;
cell_t alt = 0;
cell_t *cip = code + (aCodeStart / 4);
cell_t *stk = reinterpret_cast<cell_t *>(plugin->memory + ctx->sp);
for (;;) {
if (cip >= codeend) {
err = SP_ERROR_INVALID_INSTRUCTION;
goto error;
}
#if 0
SpewOpcode(plugin, reinterpret_cast<cell_t *>(plugin->pcode + aCodeStart), cip);
#endif
OPCODE op = (OPCODE)*cip++;
switch (op) {
case OP_MOVE_PRI:
pri = alt;
break;
case OP_MOVE_ALT:
alt = pri;
break;
case OP_XCHG:
{
cell_t tmp = pri;
pri = alt;
alt = tmp;
break;
}
case OP_ZERO:
Write(plugin, *cip++, 0);
break;
case OP_ZERO_S:
Write(plugin, ctx->frm + *cip++, 0);
break;
case OP_PUSH_PRI:
*--stk = pri;
break;
case OP_PUSH_ALT:
*--stk = alt;
break;
case OP_PUSH_C:
case OP_PUSH2_C:
case OP_PUSH3_C:
case OP_PUSH4_C:
case OP_PUSH5_C:
{
int n = 1;
if (op >= OP_PUSH2_C)
n = ((op - OP_PUSH2_C) / 4) + 2;
int i = 1;
do {
*--stk = *cip++;
} while (i++ < n);
break;
}
case OP_PUSH_ADR:
case OP_PUSH2_ADR:
case OP_PUSH3_ADR:
case OP_PUSH4_ADR:
case OP_PUSH5_ADR:
{
int n = 1;
if (op >= OP_PUSH2_ADR)
n = ((op - OP_PUSH2_ADR) / 4) + 2;
int i = 1;
do {
cell_t addr = ctx->frm + *cip++;
*--stk = addr;
} while (i++ < n);
break;
}
case OP_PUSH_S:
case OP_PUSH2_S:
case OP_PUSH3_S:
case OP_PUSH4_S:
case OP_PUSH5_S:
{
int n = 1;
if (op >= OP_PUSH2_S)
n = ((op - OP_PUSH2_S) / 4) + 2;
int i = 1;
do {
cell_t value = Read(plugin, ctx->frm + *cip++);
*--stk = value;
} while (i++ < n);
break;
}
case OP_PUSH:
case OP_PUSH2:
case OP_PUSH3:
case OP_PUSH4:
case OP_PUSH5:
{
int n = 1;
if (op >= OP_PUSH2)
n = ((op - OP_PUSH2) / 4) + 2;
int i = 1;
do {
cell_t value = Read(plugin, *cip++);
*--stk = value;
} while (i++ < n);
break;
}
case OP_ZERO_PRI:
pri = 0;
break;
case OP_ZERO_ALT:
alt = 0;
break;
case OP_ADD:
pri += alt;
break;
case OP_SUB:
pri -= alt;
break;
case OP_SUB_ALT:
pri = alt - pri;
break;
case OP_PROC:
{
*--stk = ctx->frm;
*--stk = 0;
ctx->frm = uintptr_t(stk) - uintptr_t(plugin->memory);
break;
}
case OP_IDXADDR_B:
pri <<= *cip++;
pri += alt;
break;
case OP_SHL:
pri <<= alt;
break;
case OP_SHR:
pri = unsigned(pri) >> unsigned(alt);
break;
case OP_SSHR:
pri >>= alt;
break;
case OP_SHL_C_PRI:
pri <<= *cip++;
break;
case OP_SHL_C_ALT:
alt <<= *cip++;
break;
case OP_SHR_C_PRI:
pri >>= *cip++;
break;
case OP_SHR_C_ALT:
alt >>= *cip++;
break;
case OP_SMUL:
pri *= alt;
break;
case OP_NOT:
pri = pri ? 0 : 1;
break;
case OP_NEG:
pri = -pri;
break;
case OP_XOR:
pri ^= alt;
break;
case OP_OR:
pri |= alt;
break;
case OP_AND:
pri &= alt;
break;
case OP_INVERT:
pri = ~pri;
break;
case OP_ADD_C:
pri += *cip++;
break;
case OP_SMUL_C:
pri *= *cip++;
break;
case OP_EQ:
pri = pri == alt;
break;
case OP_NEQ:
pri = pri != alt;
break;
case OP_SLESS:
pri = pri < alt;
break;
case OP_SLEQ:
pri = pri <= alt;
break;
case OP_SGRTR:
pri = pri > alt;
break;
case OP_SGEQ:
pri = pri >= alt;
break;
case OP_EQ_C_PRI:
pri = pri == *cip++;
break;
case OP_EQ_C_ALT:
pri = alt == *cip++;
break;
case OP_INC_PRI:
pri++;
break;
case OP_INC_ALT:
alt++;
break;
case OP_INC:
{
cell_t offset = *cip++;
Write(plugin, offset, Read(plugin, offset) + 1);
break;
}
case OP_INC_S:
{
cell_t offset = *cip++;
cell_t value = Read(plugin, ctx->frm + offset);
Write(plugin, ctx->frm + offset, value + 1);
break;
}
case OP_INC_I:
if (!CheckAddress(plugin, ctx, stk, pri)) {
err = SP_ERROR_MEMACCESS;
goto error;
}
Write(plugin, pri, Read(plugin, pri) + 1);
break;
case OP_DEC_PRI:
pri--;
break;
case OP_DEC_ALT:
alt--;
break;
case OP_DEC:
{
cell_t offset = *cip++;
Write(plugin, offset, Read(plugin, offset) - 1);
break;
}
case OP_DEC_S:
{
cell_t offset = *cip++;
cell_t value = Read(plugin, ctx->frm + offset);
Write(plugin, ctx->frm + offset, value - 1);
break;
}
case OP_DEC_I:
if (!CheckAddress(plugin, ctx, stk, pri)) {
err = SP_ERROR_MEMACCESS;
goto error;
}
Write(plugin, pri, Read(plugin, pri) - 1);
break;
case OP_LOAD_PRI:
pri = Read(plugin, *cip++);
break;
case OP_LOAD_ALT:
alt = Read(plugin, *cip++);
break;
case OP_LOAD_S_PRI:
pri = Read(plugin, ctx->frm + *cip++);
break;
case OP_LOAD_S_ALT:
alt = Read(plugin, ctx->frm + *cip++);
break;
case OP_LOAD_S_BOTH:
pri = Read(plugin, ctx->frm + *cip++);
alt = Read(plugin, ctx->frm + *cip++);
break;
case OP_LREF_S_PRI:
{
pri = Read(plugin, ctx->frm + *cip++);
pri = Read(plugin, pri);
break;
}
case OP_LREF_S_ALT:
{
alt = Read(plugin, ctx->frm + *cip++);
alt = Read(plugin, alt);
break;
}
case OP_CONST_PRI:
pri = *cip++;
break;
case OP_CONST_ALT:
alt = *cip++;
break;
case OP_ADDR_PRI:
pri = ctx->frm + *cip++;
break;
case OP_ADDR_ALT:
alt = ctx->frm + *cip++;
break;
case OP_STOR_PRI:
Write(plugin, *cip++, pri);
break;
case OP_STOR_ALT:
Write(plugin, *cip++, alt);
break;
case OP_STOR_S_PRI:
Write(plugin, ctx->frm + *cip++, pri);
break;
case OP_STOR_S_ALT:
Write(plugin, ctx->frm +*cip++, alt);
break;
case OP_IDXADDR:
pri = alt + pri * 4;
break;
case OP_SREF_S_PRI:
{
cell_t offset = *cip++;
cell_t addr = Read(plugin, ctx->frm + offset);
Write(plugin, addr, pri);
break;
}
case OP_SREF_S_ALT:
{
cell_t offset = *cip++;
cell_t addr = Read(plugin, ctx->frm + offset);
Write(plugin, addr, alt);
break;
}
case OP_POP_PRI:
pri = *stk++;
break;
case OP_POP_ALT:
alt = *stk++;
break;
case OP_SWAP_PRI:
case OP_SWAP_ALT:
{
cell_t reg = (op == OP_SREF_S_PRI) ? pri : alt;
cell_t temp = *stk;
*stk = reg;
reg = temp;
break;
}
case OP_LIDX:
pri = alt + pri * 4;
if (!CheckAddress(plugin, ctx, stk, pri)) {
err = SP_ERROR_MEMACCESS;
goto error;
}
pri = Read(plugin, pri);
break;
case OP_LIDX_B:
{
cell_t val = *cip++;
pri = alt + (pri << val);
if (!CheckAddress(plugin, ctx, stk, pri)) {
err = SP_ERROR_MEMACCESS;
goto error;
}
pri = Read(plugin, pri);
break;
}
case OP_CONST:
{
cell_t offset = *cip++;
cell_t value = *cip++;
Write(plugin, offset, value);
break;
}
case OP_CONST_S:
{
cell_t offset = *cip++;
cell_t value = *cip++;
Write(plugin, ctx->frm + offset, value);
break;
}
case OP_LOAD_I:
if (!CheckAddress(plugin, ctx, stk, pri)) {
err = SP_ERROR_MEMACCESS;
goto error;
}
pri = Read(plugin, pri);
break;
case OP_STOR_I:
if (!CheckAddress(plugin, ctx, stk, alt)) {
err = SP_ERROR_MEMACCESS;
goto error;
}
Write(plugin, alt, pri);
break;
case OP_SDIV:
case OP_SDIV_ALT:
{
cell_t dividend = (op == OP_SDIV) ? pri : alt;
cell_t divisor = (op == OP_SDIV) ? alt : pri;
if (divisor == 0) {
err = SP_ERROR_DIVIDE_BY_ZERO;
goto error;
}
if (dividend == INT_MIN && divisor == -1) {
err = SP_ERROR_INTEGER_OVERFLOW;
goto error;
}
pri = dividend / divisor;
alt = dividend % divisor;
break;
}
case OP_LODB_I:
{
cell_t val = *cip++;
if (!CheckAddress(plugin, ctx, stk, pri)) {
err = SP_ERROR_MEMACCESS;
goto error;
}
pri = Read(plugin, pri);
if (val == 1)
pri &= 0xff;
else if (val == 2)
pri &= 0xffff;
break;
}
case OP_STRB_I:
{
cell_t val = *cip++;
if (!CheckAddress(plugin, ctx, stk, alt)) {
err = SP_ERROR_MEMACCESS;
goto error;
}
if (val == 1)
*reinterpret_cast<int8_t *>(plugin->memory + alt) = pri;
else if (val == 2)
*reinterpret_cast<int16_t *>(plugin->memory + alt) = pri;
else if (val == 4)
*reinterpret_cast<int32_t *>(plugin->memory + alt) = pri;
break;
}
case OP_RETN:
{
stk++;
ctx->frm = *stk++;
stk += *stk + 1;
*rval = pri;
err = SP_ERROR_NONE;
goto done;
}
case OP_MOVS:
{
uint8_t *src = plugin->memory + pri;
uint8_t *dest = plugin->memory + alt;
memcpy(dest, src, *cip++);
break;
}
case OP_FILL:
{
uint8_t *dest = plugin->memory + alt;
memset(dest, pri, *cip++);
break;
}
case OP_STRADJUST_PRI:
pri += 4;
pri >>= 2;
break;
case OP_STACK:
{
cell_t amount = *cip++;
if (!IsValidOffset(amount)) {
err = SP_ERROR_INVALID_INSTRUCTION;
goto error;
}
stk += amount / 4;
if (amount > 0) {
if (uintptr_t(stk) >= uintptr_t(plugin->memory + plugin->mem_size)) {
err = SP_ERROR_STACKMIN;
goto error;
}
} else {
if (uintptr_t(stk) < uintptr_t(plugin->memory + ctx->hp + STACK_MARGIN)) {
err = SP_ERROR_STACKLOW;
goto error;
}
}
break;
}
case OP_HEAP:
{
cell_t amount = *cip++;
alt = ctx->hp;
ctx->hp += amount;
if (amount > 0) {
if (uintptr_t(plugin->memory + ctx->hp) > uintptr_t(stk)) {
err = SP_ERROR_HEAPLOW;
goto error;
}
} else {
if (uint32_t(ctx->hp) < plugin->data_size) {
err = SP_ERROR_HEAPMIN;
goto error;
}
}
break;
}
case OP_JUMP:
if ((cip = JumpTarget(plugin, ctx, cip, true, &err)) == NULL)
goto error;
break;
case OP_JZER:
if ((cip = JumpTarget(plugin, ctx, cip, pri == 0, &err)) == NULL)
goto error;
break;
case OP_JNZ:
if ((cip = JumpTarget(plugin, ctx, cip, pri != 0, &err)) == NULL)
goto error;
break;
case OP_JEQ:
if ((cip = JumpTarget(plugin, ctx, cip, pri == alt, &err)) == NULL)
goto error;
break;
case OP_JNEQ:
if ((cip = JumpTarget(plugin, ctx, cip, pri != alt, &err)) == NULL)
goto error;
break;
case OP_JSLESS:
if ((cip = JumpTarget(plugin, ctx, cip, pri < alt, &err)) == NULL)
goto error;
break;
case OP_JSLEQ:
if ((cip = JumpTarget(plugin, ctx, cip, pri <= alt, &err)) == NULL)
goto error;
break;
case OP_JSGRTR:
if ((cip = JumpTarget(plugin, ctx, cip, pri > alt, &err)) == NULL)
goto error;
break;
case OP_JSGEQ:
if ((cip = JumpTarget(plugin, ctx, cip, pri >= alt, &err)) == NULL)
goto error;
break;
case OP_TRACKER_PUSH_C:
{
cell_t amount = *cip++;
int error = cx->pushTracker(amount * 4);
if (error != SP_ERROR_NONE) {
err = error;
goto error;
}
break;
}
case OP_TRACKER_POP_SETHEAP:
{
int error = cx->popTrackerAndSetHeap();
if (error != SP_ERROR_NONE) {
err = error;
goto error;
}
break;
}
case OP_BREAK:
ctx->cip = uintptr_t(cip - 1) - uintptr_t(plugin->pcode);
break;
case OP_BOUNDS:
{
cell_t value = *cip++;
if (uint32_t(pri) > uint32_t(value)) {
err = SP_ERROR_ARRAY_BOUNDS;
goto error;
}
break;
}
case OP_CALL:
{
cell_t offset = *cip++;
if (!IsValidOffset(offset) || uint32_t(offset) >= plugin->pcode_size) {
err = SP_ERROR_INSTRUCTION_PARAM;
goto error;
}
// For debugging.
uintptr_t rcip = uintptr_t(cip - 2) - uintptr_t(plugin->pcode);
if (!cx->pushReturnCip(rcip)) {
err = SP_ERROR_STACKLOW;
goto error;
}
ctx->cip = offset;
ctx->sp = uintptr_t(stk) - uintptr_t(plugin->memory);
int err = Interpret(rt, offset, &pri);
stk = reinterpret_cast<cell_t *>(plugin->memory + ctx->sp);
ctx->cip = rcip;
cx->popReturnCip();
if (err != SP_ERROR_NONE)
goto error;
break;
}
case OP_GENARRAY:
case OP_GENARRAY_Z:
{
cell_t val = *cip++;
if ((err = GenerateArray(rt, ctx, val, stk, op == OP_GENARRAY_Z)) != SP_ERROR_NONE)
goto error;
stk += (val - 1) * 4;
break;
}
case OP_SYSREQ_C:
case OP_SYSREQ_N:
{
uint32_t native_index = *cip++;
if (native_index >= plugin->num_natives) {
err = SP_ERROR_INSTRUCTION_PARAM;
goto error;
}
uint32_t num_params;
if (op == OP_SYSREQ_N) {
num_params = *cip++;
*--stk = num_params;
}
ctx->sp = uintptr_t(stk) - uintptr_t(plugin->memory);
pri = cx->invokeNative(native_index, stk);
if (cx->GetLastNativeError() != SP_ERROR_NONE) {
err = cx->GetLastNativeError();
goto error;
}
if (op == OP_SYSREQ_N)
stk += num_params + 1;
break;
}
case OP_SWITCH:
{
cell_t offset = *cip++;
cell_t *table = reinterpret_cast<cell_t *>(plugin->pcode + offset + sizeof(cell_t));
size_t ncases = *table++;
cell_t target = *table++; // default case
for (size_t i = 0; i < ncases; i++) {
if (table[i * 2] == pri) {
target = table[i * 2 + 1];
break;
}
}
if ((cip = Jump(plugin, ctx, target)) == NULL) {
err = SP_ERROR_INVALID_INSTRUCTION;
goto error;
}
break;
}
default:
{
err = SP_ERROR_INVALID_INSTRUCTION;
goto error;
}
} // switch
}
done:
assert(orig_frm == ctx->frm);
ctx->sp = uintptr_t(stk) - uintptr_t(plugin->memory);
return err;
error:
ctx->frm = orig_frm;
goto done;
}