From f9f4c7dcd6457b986a4d190e046710019497bd4e Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sun, 1 Sep 2013 00:23:44 -0700 Subject: [PATCH] Add an interpreter because. (bug 5902, r=ds). --- configs/core.cfg | 8 + core/sm_srvcmds.cpp | 5 +- core/sourcemod.cpp | 12 + public/sourcepawn/sp_vm_api.h | 17 +- sourcepawn/jit/AMBuilder | 1 + sourcepawn/jit/Makefile.shell | 4 +- sourcepawn/jit/dll_exports.cpp | 3 + sourcepawn/jit/engine2.cpp | 3 +- sourcepawn/jit/engine2.h | 11 + sourcepawn/jit/interpreter.cpp | 965 +++++++++++++++++++++++++++ sourcepawn/jit/interpreter.h | 39 ++ sourcepawn/jit/jit_shared.h | 7 + sourcepawn/jit/sp_vm_basecontext.cpp | 9 +- sourcepawn/jit/x86/jit_x86.cpp | 119 +--- sourcepawn/jit/x86/jit_x86.h | 12 - 15 files changed, 1091 insertions(+), 124 deletions(-) create mode 100644 sourcepawn/jit/interpreter.cpp create mode 100644 sourcepawn/jit/interpreter.h diff --git a/configs/core.cfg b/configs/core.cfg index 54c01e2f..94285095 100644 --- a/configs/core.cfg +++ b/configs/core.cfg @@ -132,5 +132,13 @@ * passed. You can disable this feature by setting the value to "0". */ "SlowScriptTimeout" "8" + + /** + * Disable the SourcePawn just-in-time compiler. This is intended for C++ developers using + * debugging tools and finding it difficult to inspect JIT stack frames. The interpreter + * is not as well-tested as the JIT and will make script execution roughly 10X slower, so + * it is not intended for general purpose use. + */ + "DisableJIT" "no" } diff --git a/core/sm_srvcmds.cpp b/core/sm_srvcmds.cpp index a5a46a94..d3fa7281 100644 --- a/core/sm_srvcmds.cpp +++ b/core/sm_srvcmds.cpp @@ -334,7 +334,10 @@ void RootConsoleMenu::OnRootConsoleCommand(const char *cmdname, const CCommand & { ConsolePrint(" SourceMod Version Information:"); ConsolePrint(" SourceMod Version: %s", SM_VERSION_STRING); - ConsolePrint(" SourcePawn Engine: %s (build %s)", g_pSourcePawn2->GetEngineName(), g_pSourcePawn2->GetVersionString()); + if (g_pSourcePawn2->IsJitEnabled()) + ConsolePrint(" SourcePawn Engine: %s (build %s)", g_pSourcePawn2->GetEngineName(), g_pSourcePawn2->GetVersionString()); + else + ConsolePrint(" SourcePawn Engine: %s (build %s NO JIT)", g_pSourcePawn2->GetEngineName(), g_pSourcePawn2->GetVersionString()); ConsolePrint(" SourcePawn API: v1 = %d, v2 = %d", g_pSourcePawn->GetEngineAPIVersion(), g_pSourcePawn2->GetAPIVersion()); ConsolePrint(" Compiled on: %s %s", __DATE__, __TIME__); ConsolePrint(" Build ID: %s", SM_BUILD_UNIQUEID); diff --git a/core/sourcemod.cpp b/core/sourcemod.cpp index db525627..caa5d2aa 100644 --- a/core/sourcemod.cpp +++ b/core/sourcemod.cpp @@ -61,6 +61,7 @@ IForward *g_pOnMapEnd = NULL; IGameConfig *g_pGameConf = NULL; bool g_Loaded = false; bool sm_show_debug_spew = false; +bool sm_disable_jit = false; typedef ISourcePawnEngine *(*GET_SP_V1)(); typedef ISourcePawnEngine2 *(*GET_SP_V2)(); @@ -125,6 +126,14 @@ ConfigResult SourceModBase::OnSourceModConfigChanged(const char *key, return ConfigResult_Accept; } + else if (strcasecmp(key, "DisableJIT") == 0) + { + sm_disable_jit = (strcasecmp(value, "yes") == 0) ? true : false; + if (g_pSourcePawn2) + g_pSourcePawn2->SetJitEnabled(!sm_disable_jit); + + return ConfigResult_Accept; + } return ConfigResult_Ignore; } @@ -242,6 +251,9 @@ bool SourceModBase::InitializeSourceMod(char *error, size_t maxlength, bool late g_pSourcePawn2->SetDebugListener(logicore.debugger); + if (sm_disable_jit) + g_pSourcePawn2->SetJitEnabled(!sm_disable_jit); + sSourceModInitialized = true; /* Hook this now so we can detect startup without calling StartSourceMod() */ diff --git a/public/sourcepawn/sp_vm_api.h b/public/sourcepawn/sp_vm_api.h index f152b071..623b2103 100644 --- a/public/sourcepawn/sp_vm_api.h +++ b/public/sourcepawn/sp_vm_api.h @@ -40,7 +40,7 @@ /** SourcePawn Engine API Version */ #define SOURCEPAWN_ENGINE_API_VERSION 4 -#define SOURCEPAWN_ENGINE2_API_VERSION 5 +#define SOURCEPAWN_ENGINE2_API_VERSION 6 #if !defined SOURCEMOD_BUILD #define SOURCEMOD_BUILD @@ -1295,6 +1295,21 @@ namespace SourcePawn * @return True on success, false on failure. */ virtual bool InstallWatchdogTimer(size_t timeout_ms) =0; + + /** + * @brief Sets whether the JIT is enabled or disabled. + * + * @param enabled True or false to enable or disable. + * @return True if successful, false otherwise. + */ + virtual bool SetJitEnabled(bool enabled) =0; + + /** + * @brief Returns whether the JIT is enabled. + * + * @return True if the JIT is enabled, false otherwise. + */ + virtual bool IsJitEnabled() =0; }; }; diff --git a/sourcepawn/jit/AMBuilder b/sourcepawn/jit/AMBuilder index 10fd1692..311ed4bb 100644 --- a/sourcepawn/jit/AMBuilder +++ b/sourcepawn/jit/AMBuilder @@ -31,6 +31,7 @@ binary.AddSourceFiles('sourcepawn/jit', [ 'sp_vm_engine.cpp', 'sp_vm_function.cpp', 'opcodes.cpp', + 'interpreter.cpp', 'watchdog_timer.cpp', 'x86/jit_x86.cpp', 'zlib/adler32.c', diff --git a/sourcepawn/jit/Makefile.shell b/sourcepawn/jit/Makefile.shell index 41a4aee7..ad2fd0ae 100644 --- a/sourcepawn/jit/Makefile.shell +++ b/sourcepawn/jit/Makefile.shell @@ -20,6 +20,7 @@ OBJECTS = dll_exports.cpp \ jit_function.cpp \ opcodes.cpp \ watchdog_timer.cpp \ + interpreter.cpp \ md5/md5.cpp \ zlib/adler32.c \ zlib/compress.c \ @@ -51,7 +52,8 @@ CC = cc LINK = -m32 -lm -lpthread -lrt INCLUDE = -I. -I.. -I$(SMSDK)/public -I$(SMSDK)/public/jit -I$(SMSDK)/public/jit/x86 \ - -I$(SMSDK)/public/sourcepawn -I$(MMSOURCE17)/core/sourcehook -I$(SMSDK)/knight/shared -Ix86 + -I$(SMSDK)/public/sourcepawn -I$(MMSOURCE17)/core/sourcehook -I$(SMSDK)/knight/shared -Ix86 \ + -I$(SMSDK)/public/amtl CFLAGS += -D_LINUX -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp \ -D_snprintf=snprintf -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp -Wall -DHAVE_STDINT_H \ diff --git a/sourcepawn/jit/dll_exports.cpp b/sourcepawn/jit/dll_exports.cpp index 6e0e23f0..9a7bc354 100644 --- a/sourcepawn/jit/dll_exports.cpp +++ b/sourcepawn/jit/dll_exports.cpp @@ -219,6 +219,9 @@ int main(int argc, char **argv) return 1; } + if (getenv("DISABLE_JIT")) + g_engine2.SetJitEnabled(false); + ShellDebugListener debug; g_engine1.SetDebugListener(&debug); g_engine2.InstallWatchdogTimer(5000); diff --git a/sourcepawn/jit/engine2.cpp b/sourcepawn/jit/engine2.cpp index 284256e9..840367a0 100644 --- a/sourcepawn/jit/engine2.cpp +++ b/sourcepawn/jit/engine2.cpp @@ -15,6 +15,7 @@ using namespace SourcePawn; SourcePawnEngine2::SourcePawnEngine2() { m_Profiler = NULL; + jit_enabled_ = true; } IPluginRuntime *SourcePawnEngine2::LoadPlugin(ICompilation *co, const char *file, int *err) @@ -146,7 +147,7 @@ void SourcePawnEngine2::DestroyFakeNative(SPVM_NATIVE_FUNC func) const char *SourcePawnEngine2::GetEngineName() { - return "SourcePawn 1.1, jit-x86"; + return "SourcePawn 1.2, jit-x86"; } const char *SourcePawnEngine2::GetVersionString() diff --git a/sourcepawn/jit/engine2.h b/sourcepawn/jit/engine2.h index 0d20975c..e9cb980e 100644 --- a/sourcepawn/jit/engine2.h +++ b/sourcepawn/jit/engine2.h @@ -1,3 +1,4 @@ +// vim: set ts=4 sw=4 tw=99 noet: #ifndef _INCLUDE_SOURCEPAWN_ENGINE_2_H_ #define _INCLUDE_SOURCEPAWN_ENGINE_2_H_ @@ -27,10 +28,20 @@ namespace SourcePawn void Shutdown(); IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory); bool InstallWatchdogTimer(size_t timeout_ms); + + bool SetJitEnabled(bool enabled) { + jit_enabled_ = enabled; + return true; + } + + bool IsJitEnabled() { + return jit_enabled_; + } public: IProfiler *GetProfiler(); private: IProfiler *m_Profiler; + bool jit_enabled_; }; } diff --git a/sourcepawn/jit/interpreter.cpp b/sourcepawn/jit/interpreter.cpp new file mode 100644 index 00000000..75b4e238 --- /dev/null +++ b/sourcepawn/jit/interpreter.cpp @@ -0,0 +1,965 @@ +// 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 . +#include +#include +#include "interpreter.h" +#include "opcodes.h" +#include "watchdog_timer.h" + +#define STACK_MARGIN 64 + +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(plugin->memory + offset); +} + +static inline void +Write(const sp_plugin_t *plugin, cell_t offset, cell_t value) +{ + *reinterpret_cast(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) { + ctx->err = SP_ERROR_INVALID_INSTRUCTION; + return NULL; + } + + return reinterpret_cast(plugin->pcode + target); +} + +static inline cell_t * +JumpTarget(const sp_plugin_t *plugin, sp_context_t *ctx, cell_t *cip, bool cond) +{ + if (!cond) + return cip + 1; + + cell_t target = *cip; + if (!IsValidOffset(target) || uint32_t(target) >= plugin->pcode_size) { + ctx->err = SP_ERROR_INVALID_INSTRUCTION; + return NULL; + } + + cell_t *next = reinterpret_cast(plugin->pcode + target); + if (next < cip && !g_WatchdogTimer.HandleInterrupt()) { + ctx->err = 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) { + ctx->err = SP_ERROR_MEMACCESS; + return false; + } + + if (addr < ctx->hp) + return true; + + if (reinterpret_cast(plugin->memory + addr) < stk) { + ctx->err = SP_ERROR_MEMACCESS; + return false; + } + + return true; +} + +int +PopTrackerAndSetHeap(BaseRuntime *rt) +{ + sp_context_t *ctx = rt->GetBaseContext()->GetCtx(); + tracker_t *trk = ctx->tracker; + assert(trk->pCur > trk->pBase); + + trk->pCur--; + if (trk->pCur < trk->pBase) + return SP_ERROR_TRACKER_BOUNDS; + + ucell_t amt = *trk->pCur; + if (amt > (ctx->hp - rt->plugin()->data_size)) + return SP_ERROR_HEAPMIN; + + ctx->hp -= amt; + return SP_ERROR_NONE; +} + +int +PushTracker(sp_context_t *ctx, size_t amount) +{ + tracker_t *trk = ctx->tracker; + + if ((size_t)(trk->pCur - trk->pBase) >= trk->size) + return SP_ERROR_TRACKER_BOUNDS; + + if (trk->pCur + 1 - (trk->pBase + trk->size) == 0) { + size_t disp = trk->size - 1; + trk->size *= 2; + trk->pBase = (ucell_t *)realloc(trk->pBase, trk->size * sizeof(cell_t)); + + if (!trk->pBase) + return SP_ERROR_TRACKER_BOUNDS; + + trk->pCur = trk->pBase + disp; + } + + *trk->pCur++ = amount; + return SP_ERROR_NONE; +} + +cell_t +NativeCallback(sp_context_t *ctx, ucell_t native_idx, cell_t *params) +{ + cell_t save_sp = ctx->sp; + cell_t save_hp = ctx->hp; + + ctx->n_idx = native_idx; + + sp_native_t *native = &ctx->plugin->natives[native_idx]; + + if (native->status == SP_NATIVE_UNBOUND) { + ctx->n_err = SP_ERROR_INVALID_NATIVE; + return 0; + } + + cell_t result = native->pfn(ctx->basecx, params); + + if (ctx->n_err != SP_ERROR_NONE) + return result; + + if (save_sp != ctx->sp) { + ctx->n_err = SP_ERROR_STACKLEAK; + return result; + } + if (save_hp != ctx->hp) { + ctx->n_err = SP_ERROR_HEAPLEAK; + return result; + } + + return result; +} + +static inline bool +GenerateArray(BaseRuntime *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)) { + ctx->err = SP_ERROR_ARRAY_TOO_BIG; + return false; + } + *stk = ctx->hp; + + uint32_t bytes = size * 4; + + ctx->hp += bytes; + if (uintptr_t(ctx->plugin->memory + ctx->hp) >= uintptr_t(stk)) { + ctx->err = SP_ERROR_HEAPLOW; + return false; + } + + if ((ctx->err = PushTracker(ctx, bytes)) != SP_ERROR_NONE) + return false; + + if (autozero) + memset(ctx->plugin->memory + ctx->hp, 0, bytes); + + return true; + } + + if ((ctx->err = GenerateFullArray(rt, dims, stk, autozero)) != SP_ERROR_NONE) + return false; + + return true; +} + +int +Interpret(BaseRuntime *rt, uint32_t aCodeStart, cell_t *rval) +{ + const sp_plugin_t *plugin = rt->plugin(); + cell_t *code = reinterpret_cast(plugin->pcode); + cell_t *codeend = reinterpret_cast(plugin->pcode + plugin->pcode_size); + + if (!IsValidOffset(aCodeStart) || aCodeStart > plugin->pcode_size) + return SP_ERROR_INVALID_INSTRUCTION; + + sp_context_t *ctx = rt->GetBaseContext()->GetCtx(); + ctx->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(plugin->memory + ctx->sp); + + for (;;) { + if (cip >= codeend) { + ctx->err = SP_ERROR_INVALID_INSTRUCTION; + goto error; + } + +#if 0 + SpewOpcode(plugin, reinterpret_cast(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, *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)) + 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)) + 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)) + 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)) + 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)) + goto error; + pri = Read(plugin, pri); + break; + + case OP_STOR_I: + if (!CheckAddress(plugin, ctx, stk, alt)) + 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) { + ctx->err = SP_ERROR_DIVIDE_BY_ZERO; + goto error; + } + if (dividend == INT_MIN && divisor == -1) { + ctx->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)) + 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)) + goto error; + if (val == 1) + *reinterpret_cast(plugin->memory + alt) = pri; + else if (val == 2) + *reinterpret_cast(plugin->memory + alt) = pri; + else if (val == 4) + *reinterpret_cast(plugin->memory + alt) = pri; + break; + } + + case OP_RETN: + { + stk++; + ctx->frm = *stk++; + stk += *stk + 1; + *rval = pri; + ctx->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)) { + ctx->err = SP_ERROR_INVALID_INSTRUCTION; + goto error; + } + + stk += amount / 4; + if (amount > 0) { + if (uintptr_t(stk) >= uintptr_t(plugin->memory + plugin->mem_size)) { + ctx->err = SP_ERROR_STACKMIN; + goto error; + } + } else { + if (uintptr_t(stk) < uintptr_t(plugin->memory + ctx->hp + STACK_MARGIN)) { + ctx->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)) { + ctx->err = SP_ERROR_HEAPLOW; + goto error; + } + } else { + if (uint32_t(ctx->hp) < plugin->data_size) { + ctx->err = SP_ERROR_HEAPMIN; + goto error; + } + } + break; + } + + case OP_JUMP: + if ((cip = JumpTarget(plugin, ctx, cip, true)) == NULL) + goto error; + break; + + case OP_JZER: + if ((cip = JumpTarget(plugin, ctx, cip, pri == 0)) == NULL) + goto error; + break; + case OP_JNZ: + if ((cip = JumpTarget(plugin, ctx, cip, pri != 0)) == NULL) + goto error; + break; + + case OP_JEQ: + if ((cip = JumpTarget(plugin, ctx, cip, pri == alt)) == NULL) + goto error; + break; + case OP_JNEQ: + if ((cip = JumpTarget(plugin, ctx, cip, pri != alt)) == NULL) + goto error; + break; + case OP_JSLESS: + if ((cip = JumpTarget(plugin, ctx, cip, pri < alt)) == NULL) + goto error; + break; + case OP_JSLEQ: + if ((cip = JumpTarget(plugin, ctx, cip, pri <= alt)) == NULL) + goto error; + break; + case OP_JSGRTR: + if ((cip = JumpTarget(plugin, ctx, cip, pri > alt)) == NULL) + goto error; + break; + case OP_JSGEQ: + if ((cip = JumpTarget(plugin, ctx, cip, pri >= alt)) == NULL) + goto error; + break; + + case OP_TRACKER_PUSH_C: + { + cell_t amount = *cip++; + int error = PushTracker(ctx, amount * 4); + if (error != SP_ERROR_NONE) { + ctx->err = error; + goto error; + } + break; + } + + case OP_TRACKER_POP_SETHEAP: + { + int error = PopTrackerAndSetHeap(rt); + if (error != SP_ERROR_NONE) { + ctx->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)) { + ctx->err = SP_ERROR_ARRAY_BOUNDS; + goto error; + } + break; + } + + case OP_CALL: + { + cell_t offset = *cip++; + + if (!IsValidOffset(offset) || uint32_t(offset) >= plugin->pcode_size) { + ctx->err = SP_ERROR_INSTRUCTION_PARAM; + goto error; + } + + if (ctx->rp >= SP_MAX_RETURN_STACK) { + ctx->err = SP_ERROR_STACKLOW; + goto error; + } + + // For debugging. + uintptr_t rcip = uintptr_t(cip - 2) - uintptr_t(plugin->pcode); + ctx->rstk_cips[ctx->rp++] = rcip; + ctx->cip = offset; + ctx->sp = uintptr_t(stk) - uintptr_t(plugin->memory); + + int err = Interpret(rt, offset, &pri); + + stk = reinterpret_cast(plugin->memory + ctx->sp); + ctx->cip = rcip; + ctx->rp--; + + if (err != SP_ERROR_NONE) + goto error; + break; + } + + case OP_GENARRAY: + case OP_GENARRAY_Z: + { + cell_t val = *cip++; + if (!GenerateArray(rt, ctx, val, stk, op == OP_GENARRAY_Z)) + 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) { + ctx->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 = NativeCallback(ctx, native_index, stk); + if (ctx->n_err != SP_ERROR_NONE) { + ctx->err = ctx->n_err; + goto error; + } + + if (op == OP_SYSREQ_N) + stk += num_params + 1; + break; + } + + case OP_SWITCH: + { + cell_t offset = *cip++; + cell_t *table = reinterpret_cast(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) + goto error; + break; + } + + default: + { + ctx->err = SP_ERROR_INVALID_INSTRUCTION; + goto error; + } + } // switch + } + + done: + assert(orig_frm == ctx->frm); + ctx->sp = uintptr_t(stk) - uintptr_t(plugin->memory); + return ctx->err; + + error: + ctx->frm = orig_frm; + goto done; +} + diff --git a/sourcepawn/jit/interpreter.h b/sourcepawn/jit/interpreter.h new file mode 100644 index 00000000..60e3f2f5 --- /dev/null +++ b/sourcepawn/jit/interpreter.h @@ -0,0 +1,39 @@ +// 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 . +#ifndef _include_sourcepawn_interpreter_h_ +#define _include_sourcepawn_interpreter_h_ + +#include +#include +#include "BaseRuntime.h" +#include "sp_vm_basecontext.h" + +struct tracker_t +{ + size_t size; + ucell_t *pBase; + ucell_t *pCur; +}; + +int Interpret(BaseRuntime *rt, uint32_t aCodeStart, cell_t *rval); + +int GenerateFullArray(BaseRuntime *rt, uint32_t argc, cell_t *argv, int autozero); +cell_t NativeCallback(sp_context_t *ctx, ucell_t native_idx, cell_t *params); +int PopTrackerAndSetHeap(BaseRuntime *rt); +int PushTracker(sp_context_t *ctx, size_t amount); + +#endif // _include_sourcepawn_interpreter_h_ diff --git a/sourcepawn/jit/jit_shared.h b/sourcepawn/jit/jit_shared.h index 778ccb86..883f3bd4 100644 --- a/sourcepawn/jit/jit_shared.h +++ b/sourcepawn/jit/jit_shared.h @@ -67,6 +67,9 @@ namespace SourcePawn } sp_plugin_t; } +struct tracker_t; +class BaseContext; + typedef struct sp_context_s { cell_t hp; /**< Heap pointer */ @@ -74,8 +77,12 @@ typedef struct sp_context_s cell_t frm; /**< Frame pointer */ cell_t rval; /**< Return value from InvokeFunction() */ int32_t cip; /**< Code pointer last error occurred in */ + int32_t err; /**< Error last set by interpreter */ int32_t n_err; /**< Error code set by a native */ uint32_t n_idx; /**< Current native index being executed */ + tracker_t *tracker; + sp_plugin_t *plugin; + BaseContext *basecx; void * vm[8]; /**< VM-specific pointers */ cell_t rp; /**< Return stack pointer */ cell_t rstk_cips[SP_MAX_RETURN_STACK]; diff --git a/sourcepawn/jit/sp_vm_basecontext.cpp b/sourcepawn/jit/sp_vm_basecontext.cpp index 82ee6bb7..666c6cfb 100644 --- a/sourcepawn/jit/sp_vm_basecontext.cpp +++ b/sourcepawn/jit/sp_vm_basecontext.cpp @@ -38,6 +38,8 @@ #include "sp_vm_engine.h" #include "watchdog_timer.h" #include "x86/jit_x86.h" +#include "engine2.h" +#include "interpreter.h" using namespace SourcePawn; @@ -595,7 +597,7 @@ int BaseContext::Execute2(IPluginFunction *function, const cell_t *params, unsig } /* See if we have to compile the callee. */ - if ((fn = m_pRuntime->m_PubJitFuncs[public_id]) == NULL) + if (g_engine2.IsJitEnabled() && (fn = m_pRuntime->m_PubJitFuncs[public_id]) == NULL) { /* We might not have to - check pcode offset. */ fn = m_pRuntime->GetJittedFunctionByOffset(pubfunc->code_offs); @@ -646,7 +648,10 @@ int BaseContext::Execute2(IPluginFunction *function, const cell_t *params, unsig /* Start the frame tracer */ - ir = g_Jit.InvokeFunction(m_pRuntime, fn, result); + if (g_engine2.IsJitEnabled()) + ir = g_Jit.InvokeFunction(m_pRuntime, fn, result); + else + ir = Interpret(m_pRuntime, pubfunc->code_offs, result); /* Restore some states, stop the frame tracer */ diff --git a/sourcepawn/jit/x86/jit_x86.cpp b/sourcepawn/jit/x86/jit_x86.cpp index 8e50501d..74cb7ed7 100644 --- a/sourcepawn/jit/x86/jit_x86.cpp +++ b/sourcepawn/jit/x86/jit_x86.cpp @@ -38,6 +38,7 @@ #include "../BaseRuntime.h" #include "../sp_vm_basecontext.h" #include "watchdog_timer.h" +#include "interpreter.h" using namespace Knight; @@ -166,50 +167,8 @@ GenerateArrayIndirectionVectors(cell_t *arraybase, cell_t dims[], cell_t _dimcou return data_offs; } -static int -PopTrackerAndSetHeap(BaseRuntime *rt) -{ - sp_context_t *ctx = rt->GetBaseContext()->GetCtx(); - tracker_t *trk = (tracker_t *)(ctx->vm[JITVARS_TRACKER]); - assert(trk->pCur > trk->pBase); - - trk->pCur--; - if (trk->pCur < trk->pBase) - return SP_ERROR_TRACKER_BOUNDS; - - ucell_t amt = *trk->pCur; - if (amt > (ctx->hp - rt->plugin()->data_size)) - return SP_ERROR_HEAPMIN; - - ctx->hp -= amt; - return SP_ERROR_NONE; -} - -static int -PushTracker(sp_context_t *ctx, size_t amount) -{ - tracker_t *trk = (tracker_t *)(ctx->vm[JITVARS_TRACKER]); - - if ((size_t)(trk->pCur - trk->pBase) >= trk->size) - return SP_ERROR_TRACKER_BOUNDS; - - if (trk->pCur + 1 - (trk->pBase + trk->size) == 0) { - size_t disp = trk->size - 1; - trk->size *= 2; - trk->pBase = (ucell_t *)realloc(trk->pBase, trk->size * sizeof(cell_t)); - - if (!trk->pBase) - return SP_ERROR_TRACKER_BOUNDS; - - trk->pCur = trk->pBase + disp; - } - - *trk->pCur++ = amount; - return SP_ERROR_NONE; -} - -static int -GenerateArray(BaseRuntime *rt, uint32_t argc, cell_t *argv, int autozero) +int +GenerateFullArray(BaseRuntime *rt, uint32_t argc, cell_t *argv, int autozero) { sp_context_t *ctx = rt->GetBaseContext()->GetCtx(); @@ -342,55 +301,6 @@ CompileFromThunk(BaseRuntime *runtime, cell_t pcode_offs, void **addrp, char *pc return SP_ERROR_NONE; } -static cell_t -NativeCallback(sp_context_t *ctx, ucell_t native_idx, cell_t *params) -{ - sp_native_t *native; - cell_t save_sp = ctx->sp; - cell_t save_hp = ctx->hp; - sp_plugin_t *pl = (sp_plugin_t *)(ctx->vm[JITVARS_PLUGIN]); - - ctx->n_idx = native_idx; - - if (ctx->hp < (cell_t)pl->data_size) { - ctx->n_err = SP_ERROR_HEAPMIN; - return 0; - } - - if (ctx->hp + STACK_MARGIN > ctx->sp) { - ctx->n_err = SP_ERROR_STACKLOW; - return 0; - } - - if ((uint32_t)ctx->sp >= pl->mem_size) { - ctx->n_err = SP_ERROR_STACKMIN; - return 0; - } - - native = &pl->natives[native_idx]; - - if (native->status == SP_NATIVE_UNBOUND) { - ctx->n_err = SP_ERROR_INVALID_NATIVE; - return 0; - } - - cell_t result = native->pfn(GET_CONTEXT(ctx), params); - - if (ctx->n_err != SP_ERROR_NONE) - return result; - - if (save_sp != ctx->sp) { - ctx->n_err = SP_ERROR_STACKLEAK; - return result; - } - if (save_hp != ctx->hp) { - ctx->n_err = SP_ERROR_HEAPLEAK; - return result; - } - - return result; -} - Compiler::Compiler(BaseRuntime *rt, cell_t pcode_offs) : rt_(rt), plugin_(rt->plugin()), @@ -1226,7 +1136,7 @@ Compiler::emitOp(OPCODE op) __ movl(alt, Operand(hpAddr())); __ addl(Operand(hpAddr()), amount); - if (amount > 0) { + if (amount < 0) { __ cmpl(Operand(hpAddr()), plugin_->data_size); __ j(below, &error_heap_min_); } else { @@ -1473,7 +1383,7 @@ Compiler::emitGenArray(bool autozero) __ push(stk); __ push(val); __ push(intptr_t(rt_)); - __ call(ExternalAddress((void *)GenerateArray)); + __ call(ExternalAddress((void *)GenerateFullArray)); __ addl(esp, 4 * sizeof(void *)); // restore pri to tmp @@ -1921,16 +1831,13 @@ JITX86::CompileFunction(BaseRuntime *prt, cell_t pcode_offs, int *err) void JITX86::SetupContextVars(BaseRuntime *runtime, BaseContext *pCtx, sp_context_t *ctx) { - tracker_t *trk = new tracker_t; - - ctx->vm[JITVARS_TRACKER] = trk; - ctx->vm[JITVARS_BASECTX] = pCtx; /* GetDefaultContext() is not constructed yet */ + ctx->tracker = new tracker_t; + ctx->tracker->pBase = (ucell_t *)malloc(1024); + ctx->tracker->pCur = ctx->tracker->pBase; + ctx->tracker->size = 1024 / sizeof(cell_t); + ctx->basecx = pCtx; ctx->vm[JITVARS_PROFILER] = g_engine2.GetProfiler(); - ctx->vm[JITVARS_PLUGIN] = const_cast(runtime->plugin()); - - trk->pBase = (ucell_t *)malloc(1024); - trk->pCur = trk->pBase; - trk->size = 1024 / sizeof(cell_t); + ctx->plugin = const_cast(runtime->plugin()); } SPVM_NATIVE_FUNC @@ -1987,8 +1894,8 @@ CompData::Abort() void JITX86::FreeContextVars(sp_context_t *ctx) { - free(((tracker_t *)(ctx->vm[JITVARS_TRACKER]))->pBase); - delete (tracker_t *)ctx->vm[JITVARS_TRACKER]; + free(ctx->tracker->pBase); + delete ctx->tracker; } bool diff --git a/sourcepawn/jit/x86/jit_x86.h b/sourcepawn/jit/x86/jit_x86.h index eae3bff8..5fa7dc7e 100644 --- a/sourcepawn/jit/x86/jit_x86.h +++ b/sourcepawn/jit/x86/jit_x86.h @@ -36,22 +36,10 @@ using namespace SourcePawn; #define STACK_MARGIN 64 //8 parameters of safety, I guess #define JIT_FUNCMAGIC 0x214D4148 //magic function offset -#define JITVARS_TRACKER 0 //important: don't change this to avoid trouble -#define JITVARS_BASECTX 1 //important: don't change this aWOAWOGJQG I LIKE HAM #define JITVARS_PROFILER 2 //profiler -#define JITVARS_PLUGIN 3 //sp_plugin_t #define sDIMEN_MAX 5 //this must mirror what the compiler has. -#define GET_CONTEXT(c) ((IPluginContext *)c->vm[JITVARS_BASECTX]) - -typedef struct tracker_s -{ - size_t size; - ucell_t *pBase; - ucell_t *pCur; -} tracker_t; - typedef struct funcinfo_s { unsigned int magic;