Implement a new stack and error handling model for the SourcePawn VM.
This has three major changes to SourcePawn. First, the API now supports the concept of "exceptions". The exception state is a global property of an instance of the SourcePawn VM. Exceptions can be caught or suppressed. Many places in SourceMod have been updated to check exceptions instead of errors. The new API obsoletes major parts of the embedder API - all but one method of invoking functions is obsoleted, and the debug interface has been scrapped. Extensions using the native API will not be affected, however, ThrowNativeError has been deprecated in favor of ReportError. Second, the SourcePawn concept of a "stack" has been unified at the API level. A stack frame iterator now iterates over all SourcePawn invocations, rather than the topmost plugin. This makes error handling more consistent and removes another dependency on context-per-plugin. Finally, the implementation of stack frames has been changed dramatically. Rather than maintain a complicated and expensive return pointer stack, we now rely on the implicit one provided by the CPU. The stack frame iterator now walks the JIT stack directly. This removes many unnecessary bookkeeping instructions from the generated code, in particular making the CALL instruction 40% faster. These changes required some fair surgery to the JIT. Its error paths are now slightly more complicated, as they have to throw an exception rather than return an error code. In addition, any path that can throw an exception is now responsible for creating an "exit frame", which exists to tell the stack frame iterator about transitions from the JIT to the VM.
This commit is contained in:
@@ -36,13 +36,13 @@ library.sources += [
|
||||
'code-allocator.cpp',
|
||||
'code-stubs.cpp',
|
||||
'compiled-function.cpp',
|
||||
'debug-trace.cpp',
|
||||
'environment.cpp',
|
||||
'file-utils.cpp',
|
||||
'opcodes.cpp',
|
||||
'plugin-context.cpp',
|
||||
'plugin-runtime.cpp',
|
||||
'scripted-invoker.cpp',
|
||||
'stack-frames.cpp',
|
||||
'smx-v1-image.cpp',
|
||||
'watchdog_timer.cpp',
|
||||
'x86/code-stubs-x86.cpp',
|
||||
|
||||
+25
-9
@@ -177,14 +177,10 @@ SourcePawnEngine2::SourcePawnEngine2()
|
||||
{
|
||||
}
|
||||
|
||||
static size_t
|
||||
UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
|
||||
size_t
|
||||
sp::UTIL_FormatVA(char *buffer, size_t maxlength, const char *fmt, va_list ap)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
size_t len = vsnprintf(buffer, maxlength, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (len >= maxlength) {
|
||||
buffer[maxlength - 1] = '\0';
|
||||
@@ -193,6 +189,18 @@ UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t
|
||||
sp::UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
size_t len = UTIL_FormatVA(buffer, maxlength, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
IPluginRuntime *
|
||||
SourcePawnEngine2::LoadPlugin(ICompilation *co, const char *file, int *err)
|
||||
{
|
||||
@@ -255,13 +263,13 @@ SourcePawnEngine2::LoadBinaryFromFile(const char *file, char *error, size_t maxl
|
||||
# endif
|
||||
)
|
||||
{
|
||||
pRuntime->SetName(&file[i + 1]);
|
||||
pRuntime->SetNames(file, &file[i + 1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pRuntime->Name())
|
||||
pRuntime->SetName(file);
|
||||
pRuntime->SetNames(file, file);
|
||||
|
||||
return pRuntime;
|
||||
}
|
||||
@@ -340,7 +348,9 @@ SourcePawnEngine2::CreateEmptyRuntime(const char *name, uint32_t memory)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rt->SetName(name != NULL ? name : "<anonymous>");
|
||||
if (!name)
|
||||
name = "<anonymous>";
|
||||
rt->SetNames(name, name);
|
||||
return rt;
|
||||
}
|
||||
|
||||
@@ -386,3 +396,9 @@ SourcePawnEngine2::SetProfilingTool(IProfilingTool *tool)
|
||||
{
|
||||
Environment::get()->SetProfiler(tool);
|
||||
}
|
||||
|
||||
ISourcePawnEnvironment *
|
||||
SourcePawnEngine2::Environment()
|
||||
{
|
||||
return Environment::get();
|
||||
}
|
||||
|
||||
@@ -68,8 +68,12 @@ class SourcePawnEngine2 : public ISourcePawnEngine2
|
||||
void DisableProfiling() KE_OVERRIDE;
|
||||
void SetProfilingTool(IProfilingTool *tool) KE_OVERRIDE;
|
||||
IPluginRuntime *LoadBinaryFromFile(const char *file, char *error, size_t maxlength) KE_OVERRIDE;
|
||||
ISourcePawnEnvironment *Environment() KE_OVERRIDE;
|
||||
};
|
||||
|
||||
} // namespace SourcePawn
|
||||
extern size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...);
|
||||
extern size_t UTIL_FormatVA(char *buffer, size_t maxlength, const char *fmt, va_list ap);
|
||||
|
||||
} // namespace sp
|
||||
|
||||
#endif // _include_sourcepawn_vm_api_h_
|
||||
|
||||
@@ -18,8 +18,7 @@ using namespace sp;
|
||||
CodeStubs::CodeStubs(Environment *env)
|
||||
: env_(env),
|
||||
invoke_stub_(nullptr),
|
||||
return_stub_(nullptr),
|
||||
timeout_stub_(nullptr)
|
||||
return_stub_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,6 @@ class CodeStubs
|
||||
void *ReturnStub() const {
|
||||
return return_stub_;
|
||||
}
|
||||
void *TimeoutStub() const {
|
||||
return return_stub_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool InitializeFeatureDetection();
|
||||
@@ -52,7 +49,6 @@ class CodeStubs
|
||||
Environment *env_;
|
||||
void *invoke_stub_;
|
||||
void *return_stub_; // Owned by invoke_stub_.
|
||||
void *timeout_stub_; // Owned by invoke_stub_.
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -16,10 +16,15 @@
|
||||
|
||||
using namespace sp;
|
||||
|
||||
CompiledFunction::CompiledFunction(void *entry_addr, cell_t pcode_offs, FixedArray<LoopEdge> *edges)
|
||||
CompiledFunction::CompiledFunction(void *entry_addr, size_t code_length,
|
||||
cell_t pcode_offs,
|
||||
FixedArray<LoopEdge> *edges,
|
||||
FixedArray<CipMapEntry> *cipmap)
|
||||
: entry_(entry_addr),
|
||||
code_length_(code_length),
|
||||
code_offset_(pcode_offs),
|
||||
edges_(edges)
|
||||
edges_(edges),
|
||||
cip_map_(cipmap)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -27,3 +32,40 @@ CompiledFunction::~CompiledFunction()
|
||||
{
|
||||
Environment::get()->FreeCode(entry_);
|
||||
}
|
||||
|
||||
static int cip_map_entry_cmp(const void *a1, const void *aEntry)
|
||||
{
|
||||
uint32_t pcoffs = (uint32_t)a1;
|
||||
const CipMapEntry *entry = reinterpret_cast<const CipMapEntry *>(aEntry);
|
||||
if (pcoffs < entry->pcoffs)
|
||||
return -1;
|
||||
if (pcoffs == entry->pcoffs)
|
||||
return 0;
|
||||
return pcoffs > entry->pcoffs;
|
||||
}
|
||||
|
||||
ucell_t
|
||||
CompiledFunction::FindCipByPc(void *pc)
|
||||
{
|
||||
if (uintptr_t(pc) < uintptr_t(entry_))
|
||||
return kInvalidCip;
|
||||
|
||||
uint32_t pcoffs = intptr_t(pc) - intptr_t(entry_);
|
||||
if (pcoffs > code_length_)
|
||||
return kInvalidCip;
|
||||
|
||||
void *ptr = bsearch(
|
||||
(void *)pcoffs,
|
||||
cip_map_->buffer(),
|
||||
cip_map_->length(),
|
||||
sizeof(CipMapEntry),
|
||||
cip_map_entry_cmp);
|
||||
assert(ptr);
|
||||
|
||||
if (!ptr) {
|
||||
// Shouldn't happen, but fail gracefully.
|
||||
return kInvalidCip;
|
||||
}
|
||||
|
||||
return code_offset_ + reinterpret_cast<CipMapEntry *>(ptr)->cipoffs;
|
||||
}
|
||||
|
||||
@@ -23,14 +23,31 @@ using namespace ke;
|
||||
|
||||
struct LoopEdge
|
||||
{
|
||||
// Offset to the patchable jump instruction, such that (base + offset - 4)
|
||||
// yields a patchable location.
|
||||
uint32_t offset;
|
||||
// The displacement to either the timeout routine or the original
|
||||
// displacement, depending on the timeout state.
|
||||
int32_t disp32;
|
||||
};
|
||||
|
||||
struct CipMapEntry {
|
||||
// Offset from the first cip of the function.
|
||||
uint32_t cipoffs;
|
||||
// Offset from the first pc of the function.
|
||||
uint32_t pcoffs;
|
||||
};
|
||||
|
||||
static const ucell_t kInvalidCip = 0xffffffff;
|
||||
|
||||
class CompiledFunction
|
||||
{
|
||||
public:
|
||||
CompiledFunction(void *entry_addr, cell_t pcode_offs, FixedArray<LoopEdge> *edges);
|
||||
CompiledFunction(void *entry_addr,
|
||||
size_t code_length,
|
||||
cell_t pcode_offs,
|
||||
FixedArray<LoopEdge> *edges,
|
||||
FixedArray<CipMapEntry> *cip_map);
|
||||
~CompiledFunction();
|
||||
|
||||
public:
|
||||
@@ -43,14 +60,18 @@ class CompiledFunction
|
||||
uint32_t NumLoopEdges() const {
|
||||
return edges_->length();
|
||||
}
|
||||
const LoopEdge &GetLoopEdge(size_t i) const {
|
||||
LoopEdge &GetLoopEdge(size_t i) {
|
||||
return edges_->at(i);
|
||||
}
|
||||
|
||||
ucell_t FindCipByPc(void *pc);
|
||||
|
||||
private:
|
||||
void *entry_;
|
||||
size_t code_length_;
|
||||
cell_t code_offset_;
|
||||
AutoPtr<FixedArray<LoopEdge>> edges_;
|
||||
AutoPtr<FixedArray<CipMapEntry>> cip_map_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
// vim: set sts=2 ts=8 sw=2 tw=99 et:
|
||||
//
|
||||
// Copyright (C) 2006-2015 AlliedModders LLC
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// SourcePawn. If not, see http://www.gnu.org/licenses/.
|
||||
//
|
||||
#include "debug-trace.h"
|
||||
#include "plugin-context.h"
|
||||
#include "environment.h"
|
||||
#include "plugin-runtime.h"
|
||||
|
||||
using namespace ke;
|
||||
using namespace sp;
|
||||
using namespace SourcePawn;
|
||||
|
||||
CContextTrace::CContextTrace(PluginRuntime *pRuntime, int err, const char *errstr, cell_t start_rp)
|
||||
: m_pRuntime(pRuntime),
|
||||
context_(pRuntime->GetBaseContext()),
|
||||
m_Error(err),
|
||||
m_pMsg(errstr),
|
||||
m_StartRp(start_rp),
|
||||
m_Level(0)
|
||||
{
|
||||
m_pDebug = m_pRuntime->GetDebugInfo();
|
||||
}
|
||||
|
||||
bool
|
||||
CContextTrace::DebugInfoAvailable()
|
||||
{
|
||||
return (m_pDebug != NULL);
|
||||
}
|
||||
|
||||
const char *
|
||||
CContextTrace::GetCustomErrorString()
|
||||
{
|
||||
return m_pMsg;
|
||||
}
|
||||
|
||||
int
|
||||
CContextTrace::GetErrorCode()
|
||||
{
|
||||
return m_Error;
|
||||
}
|
||||
|
||||
const char *
|
||||
CContextTrace::GetErrorString()
|
||||
{
|
||||
return Environment::get()->GetErrorString(m_Error);
|
||||
}
|
||||
|
||||
void
|
||||
CContextTrace::ResetTrace()
|
||||
{
|
||||
m_Level = 0;
|
||||
}
|
||||
|
||||
bool
|
||||
CContextTrace::GetTraceInfo(CallStackInfo *trace)
|
||||
{
|
||||
cell_t cip;
|
||||
|
||||
if (m_Level == 0) {
|
||||
cip = context_->cip();
|
||||
} else if (context_->rp() > 0) {
|
||||
/* Entries go from ctx.rp - 1 to m_StartRp */
|
||||
cell_t offs, start, end;
|
||||
|
||||
offs = m_Level - 1;
|
||||
start = context_->rp() - 1;
|
||||
end = m_StartRp;
|
||||
|
||||
if (start - offs < end)
|
||||
return false;
|
||||
|
||||
cip = context_->getReturnStackCip(start - offs);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trace == NULL) {
|
||||
m_Level++;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_pDebug->LookupFile(cip, &(trace->filename)) != SP_ERROR_NONE)
|
||||
trace->filename = NULL;
|
||||
|
||||
if (m_pDebug->LookupFunction(cip, &(trace->function)) != SP_ERROR_NONE)
|
||||
trace->function = NULL;
|
||||
|
||||
if (m_pDebug->LookupLine(cip, &(trace->line)) != SP_ERROR_NONE)
|
||||
trace->line = 0;
|
||||
|
||||
m_Level++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *
|
||||
CContextTrace::GetLastNative(uint32_t *index)
|
||||
{
|
||||
if (context_->GetLastNativeError() == SP_ERROR_NONE)
|
||||
return NULL;
|
||||
|
||||
int lastNative = context_->lastNative();
|
||||
if (lastNative < 0)
|
||||
return NULL;
|
||||
|
||||
const sp_native_t *native = m_pRuntime->GetNative(lastNative);
|
||||
if (!native)
|
||||
return NULL;
|
||||
|
||||
if (index)
|
||||
*index = lastNative;
|
||||
|
||||
return native->name;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// vim: set sts=2 ts=8 sw=2 tw=99 et:
|
||||
//
|
||||
// Copyright (C) 2006-2015 AlliedModders LLC
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// SourcePawn. If not, see http://www.gnu.org/licenses/.
|
||||
//
|
||||
#ifndef _include_sourcepawn_vm_debug_trace_h_
|
||||
#define _include_sourcepawn_vm_debug_trace_h_
|
||||
|
||||
#include <sp_vm_api.h>
|
||||
|
||||
namespace sp {
|
||||
|
||||
using namespace SourcePawn;
|
||||
|
||||
class PluginRuntime;
|
||||
class PluginContext;
|
||||
|
||||
class CContextTrace : public IContextTrace
|
||||
{
|
||||
public:
|
||||
CContextTrace(PluginRuntime *pRuntime, int err, const char *errstr, cell_t start_rp);
|
||||
|
||||
public:
|
||||
int GetErrorCode();
|
||||
const char *GetErrorString();
|
||||
bool DebugInfoAvailable();
|
||||
const char *GetCustomErrorString();
|
||||
bool GetTraceInfo(CallStackInfo *trace);
|
||||
void ResetTrace();
|
||||
const char *GetLastNative(uint32_t *index);
|
||||
|
||||
private:
|
||||
PluginRuntime *m_pRuntime;
|
||||
PluginContext *context_;
|
||||
int m_Error;
|
||||
const char *m_pMsg;
|
||||
cell_t m_StartRp;
|
||||
cell_t m_Level;
|
||||
IPluginDebugInfo *m_pDebug;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // _include_sourcepawn_vm_debug_trace_h_
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <am-utility.h> // Replace with am-cxx later.
|
||||
#include "dll_exports.h"
|
||||
#include "environment.h"
|
||||
#include "stack-frames.h"
|
||||
|
||||
using namespace ke;
|
||||
using namespace sp;
|
||||
@@ -55,72 +56,36 @@ public:
|
||||
} sFactory;
|
||||
|
||||
#ifdef SPSHELL
|
||||
template <typename T> class AutoT
|
||||
{
|
||||
public:
|
||||
AutoT(T *t)
|
||||
: t_(t)
|
||||
{
|
||||
}
|
||||
~AutoT()
|
||||
{
|
||||
delete t_;
|
||||
}
|
||||
|
||||
operator T *() const {
|
||||
return t_;
|
||||
}
|
||||
bool operator !() const {
|
||||
return !t_;
|
||||
}
|
||||
T * operator ->() const {
|
||||
return t_;
|
||||
}
|
||||
private:
|
||||
T *t_;
|
||||
};
|
||||
|
||||
Environment *sEnv;
|
||||
|
||||
static void
|
||||
DumpStack(IFrameIterator &iter)
|
||||
{
|
||||
int index = 0;
|
||||
for (; !iter.Done(); iter.Next(), index++) {
|
||||
const char *name = iter.FunctionName();
|
||||
if (!name) {
|
||||
fprintf(stdout, " [%d] <unknown>\n", index);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (iter.IsScriptedFrame()) {
|
||||
const char *file = iter.FilePath();
|
||||
if (!file)
|
||||
file = "<unknown>";
|
||||
fprintf(stdout, " [%d] %s::%s, line %d\n", index, file, name, iter.LineNumber());
|
||||
} else {
|
||||
fprintf(stdout, " [%d] %s()\n", index, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ShellDebugListener : public IDebugListener
|
||||
{
|
||||
public:
|
||||
void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error) {
|
||||
int n_err = error->GetErrorCode();
|
||||
|
||||
if (n_err != SP_ERROR_NATIVE)
|
||||
{
|
||||
fprintf(stderr, "plugin error: %s\n", error->GetErrorString());
|
||||
}
|
||||
|
||||
if (const char *lastname = error->GetLastNative(NULL))
|
||||
{
|
||||
if (const char *custerr = error->GetCustomErrorString())
|
||||
{
|
||||
fprintf(stderr, "Native \"%s\" reported: %s", lastname, custerr);
|
||||
} else {
|
||||
fprintf(stderr, "Native \"%s\" encountered a generic error.", lastname);
|
||||
}
|
||||
}
|
||||
|
||||
if (!error->DebugInfoAvailable())
|
||||
{
|
||||
fprintf(stderr, "Debug info not available!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
CallStackInfo stk_info;
|
||||
int i = 0;
|
||||
fprintf(stderr, "Displaying call stack trace:\n");
|
||||
while (error->GetTraceInfo(&stk_info))
|
||||
{
|
||||
fprintf(stderr,
|
||||
" [%d] Line %d, %s::%s()\n",
|
||||
i++,
|
||||
stk_info.line,
|
||||
stk_info.filename,
|
||||
stk_info.function);
|
||||
}
|
||||
void ReportError(const IErrorReport &report, IFrameIterator &iter) KE_OVERRIDE {
|
||||
fprintf(stdout, "Exception thrown: %s\n", report.Message());
|
||||
DumpStack(iter);
|
||||
}
|
||||
|
||||
void OnDebugSpew(const char *msg, ...) {
|
||||
@@ -181,18 +146,43 @@ static cell_t PrintFloat(IPluginContext *cx, const cell_t *params)
|
||||
return printf("%f\n", sp_ctof(params[1]));
|
||||
}
|
||||
|
||||
static cell_t DoExecute(IPluginContext *cx, const cell_t *params)
|
||||
{
|
||||
int32_t ok = 0;
|
||||
for (size_t i = 0; i < size_t(params[2]); i++) {
|
||||
if (IPluginFunction *fn = cx->GetFunctionById(params[1])) {
|
||||
if (fn->Execute(nullptr) != SP_ERROR_NONE)
|
||||
continue;
|
||||
ok++;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static cell_t DoInvoke(IPluginContext *cx, const cell_t *params)
|
||||
{
|
||||
for (size_t i = 0; i < size_t(params[2]); i++) {
|
||||
if (IPluginFunction *fn = cx->GetFunctionById(params[1])) {
|
||||
if (!fn->Invoke())
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static cell_t DumpStackTrace(IPluginContext *cx, const cell_t *params)
|
||||
{
|
||||
FrameIterator iter;
|
||||
DumpStack(iter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int Execute(const char *file)
|
||||
{
|
||||
ICompilation *co = sEnv->APIv2()->StartCompilation();
|
||||
if (!co) {
|
||||
fprintf(stderr, "Could not create a compilation context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int err;
|
||||
AutoT<IPluginRuntime> rt(sEnv->APIv2()->LoadPlugin(co, file, &err));
|
||||
char error[255];
|
||||
AutoPtr<IPluginRuntime> rt(sEnv->APIv2()->LoadBinaryFromFile(file, error, sizeof(error)));
|
||||
if (!rt) {
|
||||
fprintf(stderr, "Could not load plugin: %s\n", sEnv->GetErrorString(err));
|
||||
fprintf(stderr, "Could not load plugin: %s\n", error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -201,6 +191,9 @@ static int Execute(const char *file)
|
||||
BindNative(rt, "printnums", PrintNums);
|
||||
BindNative(rt, "printfloat", PrintFloat);
|
||||
BindNative(rt, "donothing", DoNothing);
|
||||
BindNative(rt, "execute", DoExecute);
|
||||
BindNative(rt, "invoke", DoInvoke);
|
||||
BindNative(rt, "dump_stack_trace", DumpStackTrace);
|
||||
|
||||
IPluginFunction *fun = rt->GetFunctionByName("main");
|
||||
if (!fun)
|
||||
@@ -208,10 +201,13 @@ static int Execute(const char *file)
|
||||
|
||||
IPluginContext *cx = rt->GetDefaultContext();
|
||||
|
||||
int result = fun->Execute2(cx, &err);
|
||||
if (err != SP_ERROR_NONE) {
|
||||
fprintf(stderr, "Error executing main(): %s\n", sEnv->GetErrorString(err));
|
||||
return 1;
|
||||
int result;
|
||||
{
|
||||
ExceptionHandler eh(cx);
|
||||
if (!fun->Invoke(&result)) {
|
||||
fprintf(stderr, "Error executing main: %s\n", eh.Message());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
+180
-32
@@ -13,7 +13,6 @@
|
||||
#include "environment.h"
|
||||
#include "x86/jit_x86.h"
|
||||
#include "watchdog_timer.h"
|
||||
#include "debug-trace.h"
|
||||
#include "api.h"
|
||||
#include "code-stubs.h"
|
||||
#include "watchdog_timer.h"
|
||||
@@ -25,10 +24,12 @@ static Environment *sEnvironment = nullptr;
|
||||
|
||||
Environment::Environment()
|
||||
: debugger_(nullptr),
|
||||
exception_code_(SP_ERROR_NONE),
|
||||
profiler_(nullptr),
|
||||
jit_enabled_(true),
|
||||
profiling_enabled_(false),
|
||||
code_pool_(nullptr)
|
||||
code_pool_(nullptr),
|
||||
top_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -150,7 +151,9 @@ static const char *sErrorMsgTable[] =
|
||||
"Plugin format is too new",
|
||||
"Out of memory",
|
||||
"Integer overflow",
|
||||
"Script execution timed out"
|
||||
"Script execution timed out",
|
||||
"Custom error",
|
||||
"Fatal error"
|
||||
};
|
||||
|
||||
const char *
|
||||
@@ -161,17 +164,6 @@ Environment::GetErrorString(int error)
|
||||
return sErrorMsgTable[error];
|
||||
}
|
||||
|
||||
void
|
||||
Environment::ReportError(PluginRuntime *runtime, int err, const char *errstr, cell_t rp_start)
|
||||
{
|
||||
if (!debugger_)
|
||||
return;
|
||||
|
||||
CContextTrace trace(runtime, err, errstr, rp_start);
|
||||
|
||||
debugger_->OnContextExecuteError(runtime->GetDefaultContext(), &trace);
|
||||
}
|
||||
|
||||
void *
|
||||
Environment::AllocateCode(size_t size)
|
||||
{
|
||||
@@ -198,6 +190,15 @@ Environment::DeregisterRuntime(PluginRuntime *rt)
|
||||
runtimes_.remove(rt);
|
||||
}
|
||||
|
||||
static inline void
|
||||
SwapLoopEdge(uint8_t *code, LoopEdge &e)
|
||||
{
|
||||
int32_t *loc = reinterpret_cast<int32_t *>(code + e.offset - 4);
|
||||
int32_t new_disp32 = e.disp32;
|
||||
e.disp32 = *loc;
|
||||
*loc = new_disp32;
|
||||
}
|
||||
|
||||
void
|
||||
Environment::PatchAllJumpsForTimeout()
|
||||
{
|
||||
@@ -208,11 +209,8 @@ Environment::PatchAllJumpsForTimeout()
|
||||
CompiledFunction *fun = rt->GetJitFunction(i);
|
||||
uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress());
|
||||
|
||||
for (size_t j = 0; j < fun->NumLoopEdges(); j++) {
|
||||
const LoopEdge &e = fun->GetLoopEdge(j);
|
||||
int32_t diff = intptr_t(code_stubs_->TimeoutStub()) - intptr_t(base + e.offset);
|
||||
*reinterpret_cast<int32_t *>(base + e.offset - 4) = diff;
|
||||
}
|
||||
for (size_t j = 0; j < fun->NumLoopEdges(); j++)
|
||||
SwapLoopEdge(base, fun->GetLoopEdge(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,10 +225,8 @@ Environment::UnpatchAllJumpsFromTimeout()
|
||||
CompiledFunction *fun = rt->GetJitFunction(i);
|
||||
uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress());
|
||||
|
||||
for (size_t j = 0; j < fun->NumLoopEdges(); j++) {
|
||||
const LoopEdge &e = fun->GetLoopEdge(j);
|
||||
*reinterpret_cast<int32_t *>(base + e.offset - 4) = e.disp32;
|
||||
}
|
||||
for (size_t j = 0; j < fun->NumLoopEdges(); j++)
|
||||
SwapLoopEdge(base, fun->GetLoopEdge(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,16 +234,168 @@ Environment::UnpatchAllJumpsFromTimeout()
|
||||
int
|
||||
Environment::Invoke(PluginRuntime *runtime, CompiledFunction *fn, cell_t *result)
|
||||
{
|
||||
// Must be in an invoke frame.
|
||||
assert(top_ && top_->cx() == runtime->GetBaseContext());
|
||||
|
||||
PluginContext *cx = runtime->GetBaseContext();
|
||||
|
||||
// Note that cip, hp, sp are saved and restored by Execute2().
|
||||
*cx->addressOfCip() = fn->GetCodeOffset();
|
||||
|
||||
InvokeStubFn invoke = code_stubs_->InvokeStub();
|
||||
|
||||
EnterInvoke();
|
||||
int err = invoke(cx, fn->GetEntryAddress(), result);
|
||||
LeaveInvoke();
|
||||
|
||||
return err;
|
||||
return invoke(cx, fn->GetEntryAddress(), result);
|
||||
}
|
||||
|
||||
void
|
||||
Environment::ReportError(int code)
|
||||
{
|
||||
const char *message = GetErrorString(code);
|
||||
if (!message) {
|
||||
char buffer[255];
|
||||
UTIL_Format(buffer, sizeof(buffer), "Unknown error code %d", code);
|
||||
ReportError(code, buffer);
|
||||
} else {
|
||||
ReportError(code, message);
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorReport : public SourcePawn::IErrorReport
|
||||
{
|
||||
public:
|
||||
ErrorReport(int code, const char *message, PluginContext *cx)
|
||||
: code_(code),
|
||||
message_(message),
|
||||
context_(cx)
|
||||
{}
|
||||
|
||||
const char *Message() const KE_OVERRIDE {
|
||||
return message_;
|
||||
}
|
||||
bool IsFatal() const KE_OVERRIDE {
|
||||
switch (code_) {
|
||||
case SP_ERROR_HEAPLOW:
|
||||
case SP_ERROR_INVALID_ADDRESS:
|
||||
case SP_ERROR_STACKLOW:
|
||||
case SP_ERROR_INVALID_INSTRUCTION:
|
||||
case SP_ERROR_MEMACCESS:
|
||||
case SP_ERROR_STACKMIN:
|
||||
case SP_ERROR_HEAPMIN:
|
||||
case SP_ERROR_INSTRUCTION_PARAM:
|
||||
case SP_ERROR_STACKLEAK:
|
||||
case SP_ERROR_HEAPLEAK:
|
||||
case SP_ERROR_TRACKER_BOUNDS:
|
||||
case SP_ERROR_PARAMS_MAX:
|
||||
case SP_ERROR_ABORTED:
|
||||
case SP_ERROR_OUT_OF_MEMORY:
|
||||
case SP_ERROR_FATAL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
IPluginContext *Context() const KE_OVERRIDE {
|
||||
return context_;
|
||||
}
|
||||
|
||||
private:
|
||||
int code_;
|
||||
const char *message_;
|
||||
PluginContext *context_;
|
||||
};
|
||||
|
||||
void
|
||||
Environment::ReportErrorVA(const char *fmt, va_list ap)
|
||||
{
|
||||
ReportErrorVA(SP_ERROR_USER, fmt, ap);
|
||||
}
|
||||
|
||||
void
|
||||
Environment::ReportErrorVA(int code, const char *fmt, va_list ap)
|
||||
{
|
||||
// :TODO: right-size the string rather than rely on this buffer.
|
||||
char buffer[1024];
|
||||
UTIL_FormatVA(buffer, sizeof(buffer), fmt, ap);
|
||||
ReportError(code, buffer);
|
||||
}
|
||||
|
||||
void
|
||||
Environment::ReportErrorFmt(int code, const char *message, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, message);
|
||||
ReportErrorVA(code, message, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
Environment::ReportError(int code, const char *message)
|
||||
{
|
||||
FrameIterator iter;
|
||||
ErrorReport report(code, message, top_ ? top_->cx() : nullptr);
|
||||
|
||||
// If this fires, someone forgot to propagate an error.
|
||||
assert(!hasPendingException());
|
||||
|
||||
// Save the exception state.
|
||||
if (eh_top_) {
|
||||
exception_code_ = code;
|
||||
UTIL_Format(exception_message_, sizeof(exception_message_), "%s", message);
|
||||
}
|
||||
|
||||
// For now, we always report exceptions even if they might be handled.
|
||||
if (debugger_)
|
||||
debugger_->ReportError(report, iter);
|
||||
}
|
||||
|
||||
void
|
||||
Environment::EnterExceptionHandlingScope(ExceptionHandler *handler)
|
||||
{
|
||||
handler->next_ = eh_top_;
|
||||
eh_top_ = handler;
|
||||
}
|
||||
|
||||
void
|
||||
Environment::LeaveExceptionHandlingScope(ExceptionHandler *handler)
|
||||
{
|
||||
assert(handler == eh_top_);
|
||||
eh_top_ = eh_top_->next_;
|
||||
|
||||
// To preserve compatibility with older API, we clear the exception state
|
||||
// when there is no EH handler.
|
||||
if (!eh_top_ || handler->catch_)
|
||||
exception_code_ = SP_ERROR_NONE;
|
||||
}
|
||||
|
||||
bool
|
||||
Environment::HasPendingException(const ExceptionHandler *handler)
|
||||
{
|
||||
// Note here and elsewhere - this is not a sanity assert. In the future, the
|
||||
// API may need to query the handler.
|
||||
assert(handler == eh_top_);
|
||||
return hasPendingException();
|
||||
}
|
||||
|
||||
const char *
|
||||
Environment::GetPendingExceptionMessage(const ExceptionHandler *handler)
|
||||
{
|
||||
// Note here and elsewhere - this is not a sanity assert. In the future, the
|
||||
// API may need to query the handler.
|
||||
assert(handler == eh_top_);
|
||||
assert(HasPendingException(handler));
|
||||
return exception_message_;
|
||||
}
|
||||
|
||||
bool
|
||||
Environment::hasPendingException() const
|
||||
{
|
||||
return exception_code_ != SP_ERROR_NONE;
|
||||
}
|
||||
|
||||
void
|
||||
Environment::clearPendingException()
|
||||
{
|
||||
exception_code_ = SP_ERROR_NONE;
|
||||
}
|
||||
|
||||
int
|
||||
Environment::getPendingExceptionCode() const
|
||||
{
|
||||
return exception_code_;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <am-thread-utils.h>
|
||||
#include "code-allocator.h"
|
||||
#include "plugin-runtime.h"
|
||||
#include "stack-frames.h"
|
||||
|
||||
namespace sp {
|
||||
|
||||
@@ -54,9 +55,18 @@ class Environment : public ISourcePawnEnvironment
|
||||
|
||||
bool InstallWatchdogTimer(int timeout_ms);
|
||||
|
||||
void EnterExceptionHandlingScope(ExceptionHandler *handler) KE_OVERRIDE;
|
||||
void LeaveExceptionHandlingScope(ExceptionHandler *handler) KE_OVERRIDE;
|
||||
bool HasPendingException(const ExceptionHandler *handler) KE_OVERRIDE;
|
||||
const char *GetPendingExceptionMessage(const ExceptionHandler *handler) KE_OVERRIDE;
|
||||
|
||||
// Runtime functions.
|
||||
const char *GetErrorString(int err);
|
||||
void ReportError(PluginRuntime *runtime, int err, const char *errstr, cell_t rp_start);
|
||||
void ReportError(int code);
|
||||
void ReportError(int code, const char *message);
|
||||
void ReportErrorFmt(int code, const char *message, ...);
|
||||
void ReportErrorVA(const char *fmt, va_list ap);
|
||||
void ReportErrorVA(int code, const char *fmt, va_list ap);
|
||||
|
||||
// Allocate and free executable memory.
|
||||
void *AllocateCode(size_t size);
|
||||
@@ -104,19 +114,40 @@ class Environment : public ISourcePawnEnvironment
|
||||
return watchdog_timer_;
|
||||
}
|
||||
|
||||
bool hasPendingException() const;
|
||||
void clearPendingException();
|
||||
int getPendingExceptionCode() const;
|
||||
|
||||
// These are indicators used for the watchdog timer.
|
||||
uintptr_t FrameId() const {
|
||||
return frame_id_;
|
||||
}
|
||||
bool RunningCode() const {
|
||||
return invoke_depth_ != 0;
|
||||
return !!top_;
|
||||
}
|
||||
void EnterInvoke() {
|
||||
if (invoke_depth_++ == 0)
|
||||
void enterInvoke(InvokeFrame *frame) {
|
||||
if (!top_)
|
||||
frame_id_++;
|
||||
top_ = frame;
|
||||
}
|
||||
void LeaveInvoke() {
|
||||
invoke_depth_--;
|
||||
void leaveInvoke() {
|
||||
exit_frame_ = top_->prev_exit_frame();
|
||||
top_ = top_->prev();
|
||||
}
|
||||
|
||||
InvokeFrame *top() const {
|
||||
return top_;
|
||||
}
|
||||
const ExitFrame &exit_frame() const {
|
||||
return exit_frame_;
|
||||
}
|
||||
|
||||
public:
|
||||
static inline size_t offsetOfTopFrame() {
|
||||
return offsetof(Environment, top_);
|
||||
}
|
||||
static inline size_t offsetOfExceptionCode() {
|
||||
return offsetof(Environment, exception_code_);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -129,6 +160,10 @@ class Environment : public ISourcePawnEnvironment
|
||||
ke::Mutex mutex_;
|
||||
|
||||
IDebugListener *debugger_;
|
||||
ExceptionHandler *eh_top_;
|
||||
int exception_code_;
|
||||
char exception_message_[1024];
|
||||
|
||||
IProfilingTool *profiler_;
|
||||
bool jit_enabled_;
|
||||
bool profiling_enabled_;
|
||||
@@ -137,9 +172,11 @@ class Environment : public ISourcePawnEnvironment
|
||||
ke::InlineList<PluginRuntime> runtimes_;
|
||||
|
||||
uintptr_t frame_id_;
|
||||
uintptr_t invoke_depth_;
|
||||
|
||||
ke::AutoPtr<CodeStubs> code_stubs_;
|
||||
|
||||
InvokeFrame *top_;
|
||||
ExitFrame exit_frame_;
|
||||
};
|
||||
|
||||
class EnterProfileScope
|
||||
|
||||
+133
-139
@@ -30,14 +30,13 @@ using namespace SourcePawn;
|
||||
static const size_t kMinHeapSize = 16384;
|
||||
|
||||
PluginContext::PluginContext(PluginRuntime *pRuntime)
|
||||
: m_pRuntime(pRuntime),
|
||||
: env_(Environment::get()),
|
||||
m_pRuntime(pRuntime),
|
||||
memory_(nullptr),
|
||||
data_size_(m_pRuntime->data().length),
|
||||
mem_size_(m_pRuntime->image()->HeapSize()),
|
||||
m_pNullVec(nullptr),
|
||||
m_pNullString(nullptr),
|
||||
m_CustomMsg(false),
|
||||
m_InExec(false)
|
||||
m_pNullString(nullptr)
|
||||
{
|
||||
// Compute and align a minimum memory amount.
|
||||
if (mem_size_ < data_size_)
|
||||
@@ -52,9 +51,6 @@ PluginContext::PluginContext(PluginRuntime *pRuntime)
|
||||
hp_ = data_size_;
|
||||
sp_ = mem_size_ - sizeof(cell_t);
|
||||
frm_ = sp_;
|
||||
rp_ = 0;
|
||||
last_native_ = -1;
|
||||
native_error_ = SP_ERROR_NONE;
|
||||
|
||||
tracker_.pBase = (ucell_t *)malloc(1024);
|
||||
tracker_.pCur = tracker_.pBase;
|
||||
@@ -133,56 +129,23 @@ PluginContext::Execute(uint32_t code_addr, cell_t *result)
|
||||
return SP_ERROR_ABORTED;
|
||||
}
|
||||
|
||||
void
|
||||
PluginContext::SetErrorMessage(const char *msg, va_list ap)
|
||||
{
|
||||
m_CustomMsg = true;
|
||||
|
||||
vsnprintf(m_MsgCache, sizeof(m_MsgCache), msg, ap);
|
||||
}
|
||||
|
||||
void
|
||||
PluginContext::_SetErrorMessage(const char *msg, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
SetErrorMessage(msg, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
cell_t
|
||||
PluginContext::ThrowNativeErrorEx(int error, const char *msg, ...)
|
||||
{
|
||||
if (!m_InExec)
|
||||
return 0;
|
||||
|
||||
native_error_ = error;
|
||||
|
||||
if (msg) {
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
SetErrorMessage(msg, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
env_->ReportErrorVA(error, msg, ap);
|
||||
va_end(ap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cell_t
|
||||
PluginContext::ThrowNativeError(const char *msg, ...)
|
||||
{
|
||||
if (!m_InExec)
|
||||
return 0;
|
||||
|
||||
native_error_ = SP_ERROR_NATIVE;
|
||||
|
||||
if (msg) {
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
SetErrorMessage(msg, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
env_->ReportErrorVA(SP_ERROR_NATIVE, msg, ap);
|
||||
va_end(ap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -540,133 +503,124 @@ PluginContext::GetNullRef(SP_NULL_TYPE type)
|
||||
bool
|
||||
PluginContext::IsInExec()
|
||||
{
|
||||
return m_InExec;
|
||||
for (InvokeFrame *ivk = env_->top(); ivk; ivk = ivk->prev()) {
|
||||
if (ivk->cx() == this)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int
|
||||
PluginContext::Execute2(IPluginFunction *function, const cell_t *params, unsigned int num_params, cell_t *result)
|
||||
{
|
||||
int ir;
|
||||
int serial;
|
||||
cell_t *sp;
|
||||
CompiledFunction *fn;
|
||||
cell_t _ignore_result;
|
||||
ReportErrorNumber(SP_ERROR_ABORTED);
|
||||
return SP_ERROR_ABORTED;
|
||||
}
|
||||
|
||||
bool
|
||||
PluginContext::Invoke(funcid_t fnid, const cell_t *params, unsigned int num_params, cell_t *result)
|
||||
{
|
||||
EnterProfileScope profileScope("SourcePawn", "EnterJIT");
|
||||
|
||||
if (!Environment::get()->watchdog()->HandleInterrupt())
|
||||
return SP_ERROR_TIMEOUT;
|
||||
if (!env_->watchdog()->HandleInterrupt()) {
|
||||
ReportErrorNumber(SP_ERROR_TIMEOUT);
|
||||
return false;
|
||||
}
|
||||
|
||||
funcid_t fnid = function->GetFunctionID();
|
||||
if (!(fnid & 1))
|
||||
return SP_ERROR_INVALID_ADDRESS;
|
||||
assert((fnid & 1) != 0);
|
||||
|
||||
unsigned public_id = fnid >> 1;
|
||||
ScriptedInvoker *cfun = m_pRuntime->GetPublicFunction(public_id);
|
||||
if (!cfun)
|
||||
return SP_ERROR_NOT_FOUND;
|
||||
if (!cfun) {
|
||||
ReportErrorNumber(SP_ERROR_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_pRuntime->IsPaused())
|
||||
return SP_ERROR_NOT_RUNNABLE;
|
||||
if (m_pRuntime->IsPaused()) {
|
||||
ReportErrorNumber(SP_ERROR_NOT_RUNNABLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((cell_t)(hp_ + 16*sizeof(cell_t)) > (cell_t)(sp_ - (sizeof(cell_t) * (num_params + 1))))
|
||||
return SP_ERROR_STACKLOW;
|
||||
if ((cell_t)(hp_ + 16*sizeof(cell_t)) > (cell_t)(sp_ - (sizeof(cell_t) * (num_params + 1)))) {
|
||||
ReportErrorNumber(SP_ERROR_STACKLOW);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Yuck. We have to do this for compatibility, otherwise something like
|
||||
// ForwardSys or any sort of multi-callback-fire code would die. Later,
|
||||
// we'll expose an Invoke() or something that doesn't do this.
|
||||
env_->clearPendingException();
|
||||
|
||||
cell_t ignore_result;
|
||||
if (result == NULL)
|
||||
result = &_ignore_result;
|
||||
result = &ignore_result;
|
||||
|
||||
/* We got this far. It's time to start profiling. */
|
||||
EnterProfileScope scriptScope("SourcePawn", cfun->FullName());
|
||||
|
||||
/* See if we have to compile the callee. */
|
||||
if (Environment::get()->IsJitEnabled()) {
|
||||
CompiledFunction *fn = nullptr;
|
||||
if (env_->IsJitEnabled()) {
|
||||
/* We might not have to - check pcode offset. */
|
||||
if ((fn = cfun->cachedCompiledFunction()) == nullptr) {
|
||||
fn = m_pRuntime->GetJittedFunctionByOffset(cfun->Public()->code_offs);
|
||||
if (!fn) {
|
||||
if ((fn = CompileFunction(m_pRuntime, cfun->Public()->code_offs, &ir)) == NULL)
|
||||
return ir;
|
||||
int err = SP_ERROR_NONE;
|
||||
if ((fn = CompileFunction(m_pRuntime, cfun->Public()->code_offs, &err)) == NULL) {
|
||||
ReportErrorNumber(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
cfun->setCachedCompiledFunction(fn);
|
||||
}
|
||||
} else {
|
||||
ReportError("JIT is not enabled!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Save our previous state. */
|
||||
|
||||
bool save_exec;
|
||||
uint32_t save_n_idx;
|
||||
cell_t save_sp, save_hp, save_rp, save_cip;
|
||||
|
||||
save_sp = sp_;
|
||||
save_hp = hp_;
|
||||
save_exec = m_InExec;
|
||||
save_n_idx = last_native_;
|
||||
save_rp = rp_;
|
||||
save_cip = cip_;
|
||||
cell_t save_sp = sp_;
|
||||
cell_t save_hp = hp_;
|
||||
|
||||
/* Push parameters */
|
||||
|
||||
sp_ -= sizeof(cell_t) * (num_params + 1);
|
||||
sp = (cell_t *)(memory_ + sp_);
|
||||
cell_t *sp = (cell_t *)(memory_ + sp_);
|
||||
|
||||
sp[0] = num_params;
|
||||
for (unsigned int i = 0; i < num_params; i++)
|
||||
sp[i + 1] = params[i];
|
||||
|
||||
/* Clear internal state */
|
||||
native_error_ = SP_ERROR_NONE;
|
||||
last_native_ = -1;
|
||||
m_MsgCache[0] = '\0';
|
||||
m_CustomMsg = false;
|
||||
m_InExec = true;
|
||||
|
||||
// Enter the execution engine.
|
||||
Environment *env = Environment::get();
|
||||
ir = env->Invoke(m_pRuntime, fn, result);
|
||||
|
||||
/* Restore some states, stop the frame tracer */
|
||||
|
||||
m_InExec = save_exec;
|
||||
int ir;
|
||||
{
|
||||
InvokeFrame ivkframe(this, fn->GetCodeOffset());
|
||||
Environment *env = env_;
|
||||
ir = env->Invoke(m_pRuntime, fn, result);
|
||||
}
|
||||
|
||||
if (ir == SP_ERROR_NONE) {
|
||||
native_error_ = SP_ERROR_NONE;
|
||||
// Verify that our state is still sane.
|
||||
if (sp_ != save_sp) {
|
||||
ir = SP_ERROR_STACKLEAK;
|
||||
_SetErrorMessage("Stack leak detected: sp:%d should be %d!",
|
||||
env_->ReportErrorFmt(
|
||||
SP_ERROR_STACKLEAK,
|
||||
"Stack leak detected: sp:%d should be %d!",
|
||||
sp_,
|
||||
save_sp);
|
||||
return false;
|
||||
}
|
||||
if (hp_ != save_hp) {
|
||||
ir = SP_ERROR_HEAPLEAK;
|
||||
_SetErrorMessage("Heap leak detected: hp:%d should be %d!",
|
||||
env_->ReportErrorFmt(
|
||||
SP_ERROR_HEAPLEAK,
|
||||
"Heap leak detected: hp:%d should be %d!",
|
||||
hp_,
|
||||
save_hp);
|
||||
}
|
||||
if (rp_ != save_rp) {
|
||||
ir = SP_ERROR_STACKLEAK;
|
||||
_SetErrorMessage("Return stack leak detected: rp:%d should be %d!",
|
||||
rp_,
|
||||
save_rp);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ir == SP_ERROR_TIMEOUT)
|
||||
Environment::get()->watchdog()->NotifyTimeoutReceived();
|
||||
|
||||
if (ir != SP_ERROR_NONE)
|
||||
Environment::get()->ReportError(m_pRuntime, ir, m_MsgCache, save_rp);
|
||||
|
||||
sp_ = save_sp;
|
||||
hp_ = save_hp;
|
||||
rp_ = save_rp;
|
||||
|
||||
cip_ = save_cip;
|
||||
last_native_ = save_n_idx;
|
||||
native_error_ = SP_ERROR_NONE;
|
||||
m_MsgCache[0] = '\0';
|
||||
m_CustomMsg = false;
|
||||
|
||||
return ir;
|
||||
return ir == SP_ERROR_NONE;
|
||||
}
|
||||
|
||||
IPluginRuntime *
|
||||
@@ -678,7 +632,10 @@ PluginContext::GetRuntime()
|
||||
int
|
||||
PluginContext::GetLastNativeError()
|
||||
{
|
||||
return native_error_;
|
||||
Environment *env = env_;
|
||||
if (!env->hasPendingException())
|
||||
return SP_ERROR_NONE;
|
||||
return env->getPendingExceptionCode();
|
||||
}
|
||||
|
||||
cell_t *
|
||||
@@ -710,7 +667,8 @@ PluginContext::GetKey(int k, void **value)
|
||||
void
|
||||
PluginContext::ClearLastNativeError()
|
||||
{
|
||||
native_error_ = SP_ERROR_NONE;
|
||||
if (env_->hasPendingException())
|
||||
env_->clearPendingException();
|
||||
}
|
||||
|
||||
int
|
||||
@@ -757,28 +715,24 @@ PluginContext::invokeNative(ucell_t native_idx, cell_t *params)
|
||||
cell_t save_sp = sp_;
|
||||
cell_t save_hp = hp_;
|
||||
|
||||
// Note: Invoke() saves the last native, so we don't need to here.
|
||||
last_native_ = native_idx;
|
||||
|
||||
const sp_native_t *native = m_pRuntime->GetNative(native_idx);
|
||||
|
||||
if (native->status == SP_NATIVE_UNBOUND) {
|
||||
native_error_ = SP_ERROR_INVALID_NATIVE;
|
||||
ReportErrorNumber(SP_ERROR_INVALID_NATIVE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cell_t result = native->pfn(this, params);
|
||||
|
||||
if (native_error_ != SP_ERROR_NONE)
|
||||
return result;
|
||||
|
||||
if (save_sp != sp_) {
|
||||
native_error_ = SP_ERROR_STACKLEAK;
|
||||
return result;
|
||||
if (!env_->hasPendingException())
|
||||
ReportErrorNumber(SP_ERROR_STACKLEAK);
|
||||
return 0;
|
||||
}
|
||||
if (save_hp != hp_) {
|
||||
native_error_ = SP_ERROR_HEAPLEAK;
|
||||
return result;
|
||||
if (!env_->hasPendingException())
|
||||
ReportErrorNumber(SP_ERROR_HEAPLEAK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -792,15 +746,14 @@ PluginContext::invokeBoundNative(SPVM_NATIVE_FUNC pfn, cell_t *params)
|
||||
|
||||
cell_t result = pfn(this, params);
|
||||
|
||||
if (native_error_ != SP_ERROR_NONE)
|
||||
return result;
|
||||
|
||||
if (save_sp != sp_) {
|
||||
native_error_ = SP_ERROR_STACKLEAK;
|
||||
if (!env_->hasPendingException())
|
||||
ReportErrorNumber(SP_ERROR_STACKLEAK);
|
||||
return result;
|
||||
}
|
||||
if (save_hp != hp_) {
|
||||
native_error_ = SP_ERROR_HEAPLEAK;
|
||||
if (!env_->hasPendingException())
|
||||
ReportErrorNumber(SP_ERROR_HEAPLEAK);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -957,3 +910,44 @@ PluginContext::generateArray(cell_t dims, cell_t *stk, bool autozero)
|
||||
return SP_ERROR_NONE;
|
||||
}
|
||||
|
||||
ISourcePawnEngine2 *
|
||||
PluginContext::APIv2()
|
||||
{
|
||||
return env_->APIv2();
|
||||
}
|
||||
|
||||
void
|
||||
PluginContext::ReportError(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
env_->ReportErrorVA(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
PluginContext::ReportErrorVA(const char *fmt, va_list ap)
|
||||
{
|
||||
env_->ReportErrorVA(fmt, ap);
|
||||
}
|
||||
|
||||
void
|
||||
PluginContext::ReportFatalError(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
env_->ReportErrorVA(SP_ERROR_FATAL, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
PluginContext::ReportFatalErrorVA(const char *fmt, va_list ap)
|
||||
{
|
||||
env_->ReportErrorVA(SP_ERROR_FATAL, fmt, ap);
|
||||
}
|
||||
|
||||
void
|
||||
PluginContext::ReportErrorNumber(int error)
|
||||
{
|
||||
env_->ReportError(error);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ struct HeapTracker
|
||||
|
||||
static const size_t SP_MAX_RETURN_STACK = 1024;
|
||||
|
||||
class Environment;
|
||||
class PluginContext;
|
||||
|
||||
class PluginContext : public IPluginContext
|
||||
{
|
||||
public:
|
||||
@@ -88,6 +91,14 @@ class PluginContext : public IPluginContext
|
||||
bool GetKey(int k, void **value);
|
||||
void Refresh();
|
||||
void ClearLastNativeError();
|
||||
ISourcePawnEngine2 *APIv2() KE_OVERRIDE;
|
||||
void ReportError(const char *fmt, ...) KE_OVERRIDE;
|
||||
void ReportErrorVA(const char *fmt, va_list ap) KE_OVERRIDE;
|
||||
void ReportFatalError(const char *fmt, ...) KE_OVERRIDE;
|
||||
void ReportFatalErrorVA(const char *fmt, va_list ap) KE_OVERRIDE;
|
||||
void ReportErrorNumber(int error) KE_OVERRIDE;
|
||||
|
||||
bool Invoke(funcid_t fnid, const cell_t *params, unsigned int num_params, cell_t *result);
|
||||
|
||||
size_t HeapSize() const {
|
||||
return mem_size_;
|
||||
@@ -98,25 +109,16 @@ class PluginContext : public IPluginContext
|
||||
size_t DataSize() const {
|
||||
return data_size_;
|
||||
}
|
||||
PluginRuntime *runtime() const {
|
||||
return m_pRuntime;
|
||||
}
|
||||
|
||||
public:
|
||||
bool IsInExec();
|
||||
|
||||
static inline size_t offsetOfRp() {
|
||||
return offsetof(PluginContext, rp_);
|
||||
}
|
||||
static inline size_t offsetOfRstkCips() {
|
||||
return offsetof(PluginContext, rstk_cips_);
|
||||
}
|
||||
static inline size_t offsetOfTracker() {
|
||||
return offsetof(PluginContext, tracker_);
|
||||
}
|
||||
static inline size_t offsetOfLastNative() {
|
||||
return offsetof(PluginContext, last_native_);
|
||||
}
|
||||
static inline size_t offsetOfNativeError() {
|
||||
return offsetof(PluginContext, native_error_);
|
||||
}
|
||||
static inline size_t offsetOfSp() {
|
||||
return offsetof(PluginContext, sp_);
|
||||
}
|
||||
@@ -127,9 +129,6 @@ class PluginContext : public IPluginContext
|
||||
return offsetof(PluginContext, memory_);
|
||||
}
|
||||
|
||||
int32_t *addressOfCip() {
|
||||
return &cip_;
|
||||
}
|
||||
int32_t *addressOfSp() {
|
||||
return &sp_;
|
||||
}
|
||||
@@ -140,9 +139,6 @@ class PluginContext : public IPluginContext
|
||||
return &hp_;
|
||||
}
|
||||
|
||||
int32_t cip() const {
|
||||
return cip_;
|
||||
}
|
||||
cell_t frm() const {
|
||||
return frm_;
|
||||
}
|
||||
@@ -150,25 +146,6 @@ class PluginContext : public IPluginContext
|
||||
return hp_;
|
||||
}
|
||||
|
||||
// Return stack logic.
|
||||
bool pushReturnCip(cell_t cip) {
|
||||
if (rp_ >= SP_MAX_RETURN_STACK)
|
||||
return false;
|
||||
rstk_cips_[rp_++] = cip;
|
||||
return true;
|
||||
}
|
||||
void popReturnCip() {
|
||||
assert(rp_ > 0);
|
||||
rp_--;
|
||||
}
|
||||
cell_t rp() const {
|
||||
return rp_;
|
||||
}
|
||||
cell_t getReturnStackCip(int index) {
|
||||
assert(index >= 0 && index < SP_MAX_RETURN_STACK);
|
||||
return rstk_cips_[index];
|
||||
}
|
||||
|
||||
int popTrackerAndSetHeap();
|
||||
int pushTracker(uint32_t amount);
|
||||
|
||||
@@ -176,9 +153,6 @@ class PluginContext : public IPluginContext
|
||||
int generateFullArray(uint32_t argc, cell_t *argv, int autozero);
|
||||
cell_t invokeNative(ucell_t native_idx, cell_t *params);
|
||||
cell_t invokeBoundNative(SPVM_NATIVE_FUNC pfn, cell_t *params);
|
||||
int lastNative() const {
|
||||
return last_native_;
|
||||
}
|
||||
|
||||
inline bool checkAddress(cell_t *stk, cell_t addr) {
|
||||
if (uint32_t(addr) >= mem_size_)
|
||||
@@ -194,10 +168,7 @@ class PluginContext : public IPluginContext
|
||||
}
|
||||
|
||||
private:
|
||||
void SetErrorMessage(const char *msg, va_list ap);
|
||||
void _SetErrorMessage(const char *msg, ...);
|
||||
|
||||
private:
|
||||
Environment *env_;
|
||||
PluginRuntime *m_pRuntime;
|
||||
uint8_t *memory_;
|
||||
uint32_t data_size_;
|
||||
@@ -205,26 +176,12 @@ class PluginContext : public IPluginContext
|
||||
|
||||
cell_t *m_pNullVec;
|
||||
cell_t *m_pNullString;
|
||||
char m_MsgCache[1024];
|
||||
bool m_CustomMsg;
|
||||
bool m_InExec;
|
||||
void *m_keys[4];
|
||||
bool m_keys_set[4];
|
||||
|
||||
// Tracker for local HEA growth.
|
||||
HeapTracker tracker_;
|
||||
|
||||
// Return stack.
|
||||
cell_t rp_;
|
||||
cell_t rstk_cips_[SP_MAX_RETURN_STACK];
|
||||
|
||||
// Track the currently executing native index, and any error it throws.
|
||||
int32_t last_native_;
|
||||
int native_error_;
|
||||
|
||||
// Most recent CIP.
|
||||
int32_t cip_;
|
||||
|
||||
// Stack, heap, and frame pointer.
|
||||
cell_t sp_;
|
||||
cell_t hp_;
|
||||
|
||||
@@ -161,11 +161,10 @@ PluginRuntime::GetNativeReplacement(size_t index)
|
||||
}
|
||||
|
||||
void
|
||||
PluginRuntime::SetName(const char *name)
|
||||
PluginRuntime::SetNames(const char *fullname, const char *name)
|
||||
{
|
||||
size_t len = strlen(name);
|
||||
name_ = new char[len + 1];
|
||||
strcpy(name_, name);
|
||||
name_ = name;
|
||||
full_name_ = name;
|
||||
}
|
||||
|
||||
static cell_t
|
||||
@@ -180,19 +179,21 @@ PluginRuntime::AddJittedFunction(CompiledFunction *fn)
|
||||
m_JitFunctions.append(fn);
|
||||
|
||||
ucell_t pcode_offset = fn->GetCodeOffset();
|
||||
FunctionMap::Insert p = function_map_.findForAdd(pcode_offset);
|
||||
assert(!p.found());
|
||||
{
|
||||
FunctionMap::Insert p = function_map_.findForAdd(pcode_offset);
|
||||
assert(!p.found());
|
||||
|
||||
function_map_.add(p, pcode_offset, fn);
|
||||
function_map_.add(p, pcode_offset, fn);
|
||||
}
|
||||
}
|
||||
|
||||
CompiledFunction *
|
||||
PluginRuntime::GetJittedFunctionByOffset(cell_t pcode_offset)
|
||||
{
|
||||
FunctionMap::Result r = function_map_.find(pcode_offset);
|
||||
if (r.found())
|
||||
return r->value;
|
||||
return nullptr;
|
||||
if (!r.found())
|
||||
return nullptr;
|
||||
return r->value;
|
||||
}
|
||||
|
||||
int
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <sp_vm_api.h>
|
||||
#include <am-vector.h>
|
||||
#include <am-string.h>
|
||||
#include <am-inlinelist.h>
|
||||
#include <am-hashmap.h>
|
||||
#include "compiled-function.h"
|
||||
@@ -71,7 +72,7 @@ class PluginRuntime
|
||||
virtual unsigned char *GetDataHash();
|
||||
CompiledFunction *GetJittedFunctionByOffset(cell_t pcode_offset);
|
||||
void AddJittedFunction(CompiledFunction *fn);
|
||||
void SetName(const char *name);
|
||||
void SetNames(const char *fullname, const char *name);
|
||||
unsigned GetNativeReplacement(size_t index);
|
||||
ScriptedInvoker *GetPublicFunction(size_t index);
|
||||
int UpdateNativeBinding(uint32_t index, SPVM_NATIVE_FUNC pfn, uint32_t flags, void *data) KE_OVERRIDE;
|
||||
@@ -79,6 +80,9 @@ class PluginRuntime
|
||||
int LookupLine(ucell_t addr, uint32_t *line) KE_OVERRIDE;
|
||||
int LookupFunction(ucell_t addr, const char **name) KE_OVERRIDE;
|
||||
int LookupFile(ucell_t addr, const char **filename) KE_OVERRIDE;
|
||||
const char *GetFilename() KE_OVERRIDE {
|
||||
return full_name_.chars();
|
||||
}
|
||||
|
||||
PluginContext *GetBaseContext();
|
||||
|
||||
@@ -89,11 +93,7 @@ class PluginRuntime
|
||||
return m_JitFunctions[i];
|
||||
}
|
||||
const char *Name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
static inline size_t offsetToPlugin() {
|
||||
return 0x0fff0000;
|
||||
return name_.chars();
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -117,7 +117,8 @@ class PluginRuntime
|
||||
ke::AutoPtr<sp::LegacyImage> image_;
|
||||
ke::AutoArray<uint8_t> aligned_code_;
|
||||
ke::AutoArray<floattbl_t> float_table_;
|
||||
ke::AutoArray<char> name_;
|
||||
ke::AString name_;
|
||||
ke::AString full_name_;
|
||||
Code code_;
|
||||
Data data_;
|
||||
ke::AutoArray<sp_native_t> natives_;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <string.h>
|
||||
#include "scripted-invoker.h"
|
||||
#include "plugin-runtime.h"
|
||||
#include "environment.h"
|
||||
#include "plugin-context.h"
|
||||
|
||||
/********************
|
||||
* FUNCTION CALLING *
|
||||
@@ -23,43 +25,14 @@
|
||||
using namespace sp;
|
||||
using namespace SourcePawn;
|
||||
|
||||
ScriptedInvoker::~ScriptedInvoker()
|
||||
{
|
||||
delete [] full_name_;
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptedInvoker::IsRunnable()
|
||||
{
|
||||
return !m_pRuntime->IsPaused();
|
||||
}
|
||||
|
||||
int
|
||||
ScriptedInvoker::CallFunction(const cell_t *params, unsigned int num_params, cell_t *result)
|
||||
{
|
||||
return CallFunction2(m_pRuntime->GetDefaultContext(), params, num_params, result);
|
||||
}
|
||||
|
||||
int
|
||||
ScriptedInvoker::CallFunction2(IPluginContext *pContext, const cell_t *params, unsigned int num_params, cell_t *result)
|
||||
{
|
||||
return pContext->Execute2(this, params, num_params, result);
|
||||
}
|
||||
|
||||
IPluginContext *
|
||||
ScriptedInvoker::GetParentContext()
|
||||
{
|
||||
return m_pRuntime->GetDefaultContext();
|
||||
}
|
||||
|
||||
ScriptedInvoker::ScriptedInvoker(PluginRuntime *runtime, funcid_t id, uint32_t pub_id)
|
||||
: m_curparam(0),
|
||||
: env_(Environment::get()),
|
||||
context_(runtime->GetBaseContext()),
|
||||
m_curparam(0),
|
||||
m_errorstate(SP_ERROR_NONE),
|
||||
m_FnId(id),
|
||||
cc_function_(nullptr)
|
||||
{
|
||||
m_pRuntime = runtime;
|
||||
|
||||
runtime->GetPublicByIndex(pub_id, &public_);
|
||||
|
||||
size_t rt_len = strlen(runtime->Name());
|
||||
@@ -71,6 +44,36 @@ ScriptedInvoker::ScriptedInvoker(PluginRuntime *runtime, funcid_t id, uint32_t p
|
||||
strcpy(&full_name_[rt_len + 2], public_->name);
|
||||
}
|
||||
|
||||
ScriptedInvoker::~ScriptedInvoker()
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptedInvoker::IsRunnable()
|
||||
{
|
||||
return !context_->runtime()->IsPaused();
|
||||
}
|
||||
|
||||
int
|
||||
ScriptedInvoker::CallFunction(const cell_t *params, unsigned int num_params, cell_t *result)
|
||||
{
|
||||
Environment::get()->ReportError(SP_ERROR_ABORTED);
|
||||
return SP_ERROR_ABORTED;
|
||||
}
|
||||
|
||||
int
|
||||
ScriptedInvoker::CallFunction2(IPluginContext *pContext, const cell_t *params, unsigned int num_params, cell_t *result)
|
||||
{
|
||||
Environment::get()->ReportError(SP_ERROR_ABORTED);
|
||||
return SP_ERROR_ABORTED;
|
||||
}
|
||||
|
||||
IPluginContext *
|
||||
ScriptedInvoker::GetParentContext()
|
||||
{
|
||||
return context_;
|
||||
}
|
||||
|
||||
int ScriptedInvoker::PushCell(cell_t cell)
|
||||
{
|
||||
if (m_curparam >= SP_MAX_EXEC_PARAMS)
|
||||
@@ -169,21 +172,41 @@ ScriptedInvoker::Cancel()
|
||||
int
|
||||
ScriptedInvoker::Execute(cell_t *result)
|
||||
{
|
||||
return Execute2(m_pRuntime->GetDefaultContext(), result);
|
||||
Environment *env = Environment::get();
|
||||
env->clearPendingException();
|
||||
|
||||
// For backward compatibility, we have to clear the exception state.
|
||||
// Otherwise code like this:
|
||||
//
|
||||
// static cell_t native(cx, params) {
|
||||
// for (auto callback : callbacks) {
|
||||
// callback->Execute();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Could unintentionally leak a pending exception back to the caller,
|
||||
// which wouldn't have happened before the Great Exception Refactoring.
|
||||
ExceptionHandler eh(context_);
|
||||
if (!Invoke(result)) {
|
||||
assert(env->hasPendingException());
|
||||
return env->getPendingExceptionCode();
|
||||
}
|
||||
|
||||
return SP_ERROR_NONE;
|
||||
}
|
||||
|
||||
int
|
||||
ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
|
||||
bool
|
||||
ScriptedInvoker::Invoke(cell_t *result)
|
||||
{
|
||||
int err = SP_ERROR_NONE;
|
||||
|
||||
if (!IsRunnable())
|
||||
m_errorstate = SP_ERROR_NOT_RUNNABLE;
|
||||
|
||||
if (m_errorstate != SP_ERROR_NONE) {
|
||||
err = m_errorstate;
|
||||
if (!IsRunnable()) {
|
||||
Cancel();
|
||||
return err;
|
||||
env_->ReportError(SP_ERROR_NOT_RUNNABLE);
|
||||
return false;
|
||||
}
|
||||
if (int err = m_errorstate) {
|
||||
Cancel();
|
||||
env_->ReportError(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
//This is for re-entrancy!
|
||||
@@ -191,7 +214,6 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
|
||||
ParamInfo temp_info[SP_MAX_EXEC_PARAMS];
|
||||
unsigned int numparams = m_curparam;
|
||||
unsigned int i;
|
||||
bool docopies = true;
|
||||
|
||||
if (numparams)
|
||||
{
|
||||
@@ -201,16 +223,19 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
|
||||
m_curparam = 0;
|
||||
|
||||
/* Browse the parameters and build arrays */
|
||||
bool ok = true;
|
||||
for (i=0; i<numparams; i++) {
|
||||
/* Is this marked as an array? */
|
||||
if (temp_info[i].marked) {
|
||||
if (!temp_info[i].str.is_sz) {
|
||||
/* Allocate a normal/generic array */
|
||||
if ((err=ctx->HeapAlloc(temp_info[i].size,
|
||||
&(temp_info[i].local_addr),
|
||||
&(temp_info[i].phys_addr)))
|
||||
!= SP_ERROR_NONE)
|
||||
{
|
||||
int err = context_->HeapAlloc(
|
||||
temp_info[i].size,
|
||||
&(temp_info[i].local_addr),
|
||||
&(temp_info[i].phys_addr));
|
||||
if (err != SP_ERROR_NONE) {
|
||||
env_->ReportError(err);
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if (temp_info[i].orig_addr)
|
||||
@@ -222,26 +247,26 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
|
||||
size_t cells = (temp_info[i].size + sizeof(cell_t) - 1) / sizeof(cell_t);
|
||||
|
||||
/* Allocate the buffer */
|
||||
if ((err=ctx->HeapAlloc(cells,
|
||||
&(temp_info[i].local_addr),
|
||||
&(temp_info[i].phys_addr)))
|
||||
!= SP_ERROR_NONE)
|
||||
{
|
||||
int err = context_->HeapAlloc(
|
||||
cells,
|
||||
&(temp_info[i].local_addr),
|
||||
&(temp_info[i].phys_addr));
|
||||
if (err != SP_ERROR_NONE) {
|
||||
env_->ReportError(err);
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Copy original string if necessary */
|
||||
if ((temp_info[i].str.sz_flags & SM_PARAM_STRING_COPY) && (temp_info[i].orig_addr != NULL))
|
||||
{
|
||||
/* Cut off UTF-8 properly */
|
||||
if (temp_info[i].str.sz_flags & SM_PARAM_STRING_UTF8) {
|
||||
if ((err=ctx->StringToLocalUTF8(temp_info[i].local_addr,
|
||||
temp_info[i].size,
|
||||
(const char *)temp_info[i].orig_addr,
|
||||
NULL))
|
||||
!= SP_ERROR_NONE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
context_->StringToLocalUTF8(
|
||||
temp_info[i].local_addr,
|
||||
temp_info[i].size,
|
||||
(const char *)temp_info[i].orig_addr,
|
||||
NULL);
|
||||
}
|
||||
/* Copy a binary blob */
|
||||
else if (temp_info[i].str.sz_flags & SM_PARAM_STRING_BINARY)
|
||||
@@ -251,13 +276,10 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
|
||||
/* Copy ASCII characters */
|
||||
else
|
||||
{
|
||||
if ((err=ctx->StringToLocal(temp_info[i].local_addr,
|
||||
temp_info[i].size,
|
||||
(const char *)temp_info[i].orig_addr))
|
||||
!= SP_ERROR_NONE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
context_->StringToLocal(
|
||||
temp_info[i].local_addr,
|
||||
temp_info[i].size,
|
||||
(const char *)temp_info[i].orig_addr);
|
||||
}
|
||||
}
|
||||
} /* End array/string calculation */
|
||||
@@ -270,14 +292,11 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
|
||||
}
|
||||
|
||||
/* Make the call if we can */
|
||||
if (err == SP_ERROR_NONE) {
|
||||
if ((err = CallFunction2(ctx, temp_params, numparams, result)) != SP_ERROR_NONE)
|
||||
docopies = false;
|
||||
} else {
|
||||
docopies = false;
|
||||
}
|
||||
if (ok)
|
||||
ok = context_->Invoke(m_FnId, temp_params, numparams, result);
|
||||
|
||||
/* i should be equal to the last valid parameter + 1 */
|
||||
bool docopies = ok;
|
||||
while (i--) {
|
||||
if (!temp_info[i].marked)
|
||||
continue;
|
||||
@@ -299,17 +318,24 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
|
||||
}
|
||||
}
|
||||
|
||||
if ((err=ctx->HeapPop(temp_info[i].local_addr)) != SP_ERROR_NONE)
|
||||
return err;
|
||||
if (int err = context_->HeapPop(temp_info[i].local_addr))
|
||||
env_->ReportError(err);
|
||||
}
|
||||
|
||||
return err;
|
||||
return !env_->hasPendingException();
|
||||
}
|
||||
|
||||
int
|
||||
ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
|
||||
{
|
||||
Environment::get()->ReportError(SP_ERROR_ABORTED);
|
||||
return SP_ERROR_ABORTED;
|
||||
}
|
||||
|
||||
IPluginRuntime *
|
||||
ScriptedInvoker::GetParentRuntime()
|
||||
{
|
||||
return m_pRuntime;
|
||||
return context_->runtime();
|
||||
}
|
||||
|
||||
funcid_t
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
#define _INCLUDE_SOURCEMOD_BASEFUNCTION_H_
|
||||
|
||||
#include <sp_vm_api.h>
|
||||
#include <am-utility.h>
|
||||
|
||||
namespace sp {
|
||||
|
||||
using namespace SourcePawn;
|
||||
|
||||
class PluginRuntime;
|
||||
class PluginContext;
|
||||
class CompiledFunction;
|
||||
|
||||
struct ParamInfo
|
||||
@@ -43,17 +45,18 @@ class ScriptedInvoker : public IPluginFunction
|
||||
~ScriptedInvoker();
|
||||
|
||||
public:
|
||||
virtual int PushCell(cell_t cell);
|
||||
virtual int PushCellByRef(cell_t *cell, int flags);
|
||||
virtual int PushFloat(float number);
|
||||
virtual int PushFloatByRef(float *number, int flags);
|
||||
virtual int PushArray(cell_t *inarray, unsigned int cells, int copyback);
|
||||
virtual int PushString(const char *string);
|
||||
virtual int PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags);
|
||||
virtual int Execute(cell_t *result);
|
||||
virtual void Cancel();
|
||||
virtual int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result);
|
||||
virtual IPluginContext *GetParentContext();
|
||||
int PushCell(cell_t cell);
|
||||
int PushCellByRef(cell_t *cell, int flags);
|
||||
int PushFloat(float number);
|
||||
int PushFloatByRef(float *number, int flags);
|
||||
int PushArray(cell_t *inarray, unsigned int cells, int copyback);
|
||||
int PushString(const char *string);
|
||||
int PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags);
|
||||
int Execute(cell_t *result);
|
||||
void Cancel();
|
||||
int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result);
|
||||
IPluginContext *GetParentContext();
|
||||
bool Invoke(cell_t *result);
|
||||
bool IsRunnable();
|
||||
funcid_t GetFunctionID();
|
||||
int Execute2(IPluginContext *ctx, cell_t *result);
|
||||
@@ -83,13 +86,15 @@ class ScriptedInvoker : public IPluginFunction
|
||||
int SetError(int err);
|
||||
|
||||
private:
|
||||
Environment *env_;
|
||||
PluginRuntime *m_pRuntime;
|
||||
PluginContext *context_;
|
||||
cell_t m_params[SP_MAX_EXEC_PARAMS];
|
||||
ParamInfo m_info[SP_MAX_EXEC_PARAMS];
|
||||
unsigned int m_curparam;
|
||||
int m_errorstate;
|
||||
funcid_t m_FnId;
|
||||
char *full_name_;
|
||||
ke::AutoArray<char> full_name_;
|
||||
sp_public_t *public_;
|
||||
CompiledFunction *cc_function_;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
// vim: set sts=2 ts=8 sw=2 tw=99 et:
|
||||
//
|
||||
// Copyright (C) 2006-2015 AlliedModders LLC
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// SourcePawn. If not, see http://www.gnu.org/licenses/.
|
||||
//
|
||||
#include "environment.h"
|
||||
#include "plugin-runtime.h"
|
||||
#include "plugin-context.h"
|
||||
#include "stack-frames.h"
|
||||
#include "x86/frames-x86.h"
|
||||
#include "compiled-function.h"
|
||||
|
||||
using namespace ke;
|
||||
using namespace sp;
|
||||
using namespace SourcePawn;
|
||||
|
||||
InvokeFrame::InvokeFrame(PluginContext *cx, cell_t entry_cip)
|
||||
: prev_(Environment::get()->top()),
|
||||
cx_(cx),
|
||||
prev_exit_frame_(Environment::get()->exit_frame()),
|
||||
entry_cip_(0),
|
||||
entry_sp_(nullptr)
|
||||
{
|
||||
Environment::get()->enterInvoke(this);
|
||||
}
|
||||
|
||||
InvokeFrame::~InvokeFrame()
|
||||
{
|
||||
assert(Environment::get()->top() == this);
|
||||
Environment::get()->leaveInvoke();
|
||||
}
|
||||
|
||||
FrameIterator::FrameIterator()
|
||||
: ivk_(Environment::get()->top()),
|
||||
exit_frame_(Environment::get()->exit_frame()),
|
||||
runtime_(nullptr),
|
||||
sp_iter_(nullptr),
|
||||
sp_stop_(nullptr),
|
||||
function_cip_(kInvalidCip),
|
||||
cip_(kInvalidCip),
|
||||
pc_(nullptr)
|
||||
{
|
||||
if (!ivk_)
|
||||
return;
|
||||
|
||||
nextInvokeFrame();
|
||||
}
|
||||
|
||||
void
|
||||
FrameIterator::nextInvokeFrame()
|
||||
{
|
||||
assert(exit_frame_.exit_sp());
|
||||
sp_iter_ = exit_frame_.exit_sp();
|
||||
|
||||
// Inside an exit frame, the stack looks like this:
|
||||
// .. C++ ..
|
||||
// ----------- <-- entry_sp
|
||||
// return addr to C++
|
||||
// entry cip for frame #0
|
||||
// alignment
|
||||
// -----------
|
||||
// return addr to frame #0
|
||||
// entry cip for frame #1
|
||||
// alignment
|
||||
// ----------- <-- exit sp
|
||||
// saved regs
|
||||
// return addr
|
||||
// .. InvokeNativeBoundHelper() ..
|
||||
//
|
||||
// We are guaranteed to always have one frame. We subtract one frame from
|
||||
// the entry sp so we hit the stopping point correctly.
|
||||
assert(ivk_->entry_sp());
|
||||
assert(ke::IsAligned(sizeof(JitFrame), sizeof(intptr_t)));
|
||||
sp_stop_ = ivk_->entry_sp() - (sizeof(JitFrame) / sizeof(intptr_t));
|
||||
assert(sp_stop_ >= sp_iter_);
|
||||
|
||||
runtime_ = ivk_->cx()->runtime();
|
||||
function_cip_ = -1;
|
||||
pc_ = nullptr;
|
||||
cip_ = kInvalidCip;
|
||||
|
||||
if (!exit_frame_.has_exit_native()) {
|
||||
// We have an exit frame, but it's not for natives. automatically advance
|
||||
// to the most recent scripted frame.
|
||||
const JitExitFrameForHelper *exit =
|
||||
JitExitFrameForHelper::FromExitSp(exit_frame_.exit_sp());
|
||||
exit_frame_ = ExitFrame();
|
||||
|
||||
// If we haven't compiled the function yet, but threw an error, then the
|
||||
// return address will be null.
|
||||
pc_ = exit->return_address;
|
||||
assert(pc_ || exit->isCompileFunction());
|
||||
|
||||
// The function owning pc_ is in the previous frame.
|
||||
const JitFrame *frame = exit->prev();
|
||||
function_cip_ = frame->function_cip;
|
||||
sp_iter_ = reinterpret_cast<const intptr_t *>(frame);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FrameIterator::Next()
|
||||
{
|
||||
void *pc = nullptr;
|
||||
if (exit_frame_.has_exit_native()) {
|
||||
// If we're at an exit frame, the return address will yield the current pc.
|
||||
const JitExitFrameForNative *exit =
|
||||
JitExitFrameForNative::FromExitSp(exit_frame_.exit_sp());
|
||||
exit_frame_ = ExitFrame();
|
||||
|
||||
pc_ = exit->return_address;
|
||||
cip_ = kInvalidCip;
|
||||
|
||||
// The function owning pc_ is in the previous frame.
|
||||
const JitFrame *frame = JitFrame::FromSp(sp_iter_);
|
||||
function_cip_ = frame->function_cip;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sp_iter_ >= sp_stop_) {
|
||||
// Jump to the next invoke frame.
|
||||
exit_frame_ = ivk_->prev_exit_frame();
|
||||
ivk_ = ivk_->prev();
|
||||
if (!ivk_)
|
||||
return;
|
||||
|
||||
nextInvokeFrame();
|
||||
return;
|
||||
}
|
||||
|
||||
pc_ = JitFrame::FromSp(sp_iter_)->return_address;
|
||||
assert(pc_);
|
||||
|
||||
// Advance, and find the function cip the pc belongs to.
|
||||
sp_iter_ = reinterpret_cast<const intptr_t *>(JitFrame::FromSp(sp_iter_) + 1);
|
||||
function_cip_ = JitFrame::FromSp(sp_iter_)->function_cip;
|
||||
cip_ = kInvalidCip;
|
||||
}
|
||||
|
||||
void
|
||||
FrameIterator::Reset()
|
||||
{
|
||||
*this = FrameIterator();
|
||||
}
|
||||
|
||||
cell_t
|
||||
FrameIterator::findCip() const
|
||||
{
|
||||
CompiledFunction *fn = runtime_->GetJittedFunctionByOffset(function_cip_);
|
||||
if (!fn)
|
||||
return 0;
|
||||
|
||||
if (cip_ == kInvalidCip) {
|
||||
if (pc_)
|
||||
cip_ = fn->FindCipByPc(pc_);
|
||||
else
|
||||
cip_ = function_cip_;
|
||||
}
|
||||
return cip_;
|
||||
}
|
||||
|
||||
unsigned
|
||||
FrameIterator::LineNumber() const
|
||||
{
|
||||
cell_t cip = findCip();
|
||||
if (cip == kInvalidCip)
|
||||
return 0;
|
||||
|
||||
uint32_t line;
|
||||
if (!runtime_->image()->LookupLine(cip, &line))
|
||||
return 0;
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
const char *
|
||||
FrameIterator::FilePath() const
|
||||
{
|
||||
cell_t cip = findCip();
|
||||
if (cip == kInvalidCip)
|
||||
return runtime_->image()->LookupFile(function_cip_);
|
||||
|
||||
return runtime_->image()->LookupFile(cip);
|
||||
}
|
||||
|
||||
const char *
|
||||
FrameIterator::FunctionName() const
|
||||
{
|
||||
assert(ivk_);
|
||||
if (exit_frame_.has_exit_native()) {
|
||||
uint32_t native_index = exit_frame_.exit_native();
|
||||
const sp_native_t *native = runtime_->GetNative(native_index);
|
||||
if (!native)
|
||||
return nullptr;
|
||||
return native->name;
|
||||
}
|
||||
|
||||
return runtime_->image()->LookupFunction(function_cip_);
|
||||
}
|
||||
|
||||
bool
|
||||
FrameIterator::IsNativeFrame() const
|
||||
{
|
||||
return exit_frame_.has_exit_native();
|
||||
}
|
||||
|
||||
bool
|
||||
FrameIterator::IsScriptedFrame() const
|
||||
{
|
||||
return !IsNativeFrame() && ivk_;
|
||||
}
|
||||
|
||||
IPluginContext *
|
||||
FrameIterator::Context() const
|
||||
{
|
||||
if (!ivk_)
|
||||
return nullptr;
|
||||
return ivk_->cx();
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// vim: set sts=2 ts=8 sw=2 tw=99 et:
|
||||
//
|
||||
// Copyright (C) 2006-2015 AlliedModders LLC
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// SourcePawn. If not, see http://www.gnu.org/licenses/.
|
||||
//
|
||||
#ifndef _include_sourcepawn_vm_stack_frames_h_
|
||||
#define _include_sourcepawn_vm_stack_frames_h_
|
||||
|
||||
#include <sp_vm_api.h>
|
||||
#include <assert.h>
|
||||
|
||||
namespace sp {
|
||||
|
||||
using namespace SourcePawn;
|
||||
|
||||
class PluginContext;
|
||||
class PluginRuntime;
|
||||
|
||||
// An ExitFrame represents the state of the most recent exit from VM state to
|
||||
// the outside world. Because this transition is on a critical path, we declare
|
||||
// exactly one ExitFrame and save/restore it in InvokeFrame(). Anytime we're in
|
||||
// the VM, we are guaranteed to have an ExitFrame for each InvokeFrame().
|
||||
class ExitFrame
|
||||
{
|
||||
public:
|
||||
ExitFrame()
|
||||
: exit_sp_(nullptr),
|
||||
exit_native_(-1)
|
||||
{}
|
||||
|
||||
public:
|
||||
const intptr_t *exit_sp() const {
|
||||
return exit_sp_;
|
||||
}
|
||||
bool has_exit_native() const {
|
||||
return exit_native_ != -1;
|
||||
}
|
||||
uint32_t exit_native() const {
|
||||
assert(has_exit_native());
|
||||
return exit_native_;
|
||||
}
|
||||
|
||||
public:
|
||||
static inline size_t offsetOfExitSp() {
|
||||
return offsetof(ExitFrame, exit_sp_);
|
||||
}
|
||||
static inline size_t offsetOfExitNative() {
|
||||
return offsetof(ExitFrame, exit_native_);
|
||||
}
|
||||
|
||||
private:
|
||||
const intptr_t *exit_sp_;
|
||||
int exit_native_;
|
||||
};
|
||||
|
||||
// An InvokeFrame represents one activation of Execute2().
|
||||
class InvokeFrame
|
||||
{
|
||||
public:
|
||||
InvokeFrame(PluginContext *cx, cell_t cip);
|
||||
~InvokeFrame();
|
||||
|
||||
InvokeFrame *prev() const {
|
||||
return prev_;
|
||||
}
|
||||
PluginContext *cx() const {
|
||||
return cx_;
|
||||
}
|
||||
|
||||
const ExitFrame &prev_exit_frame() const {
|
||||
return prev_exit_frame_;
|
||||
}
|
||||
const intptr_t *entry_sp() const {
|
||||
return entry_sp_;
|
||||
}
|
||||
cell_t entry_cip() const {
|
||||
return entry_cip_;
|
||||
}
|
||||
|
||||
public:
|
||||
static inline size_t offsetOfEntrySp() {
|
||||
return offsetof(InvokeFrame, entry_sp_);
|
||||
}
|
||||
|
||||
private:
|
||||
InvokeFrame *prev_;
|
||||
PluginContext *cx_;
|
||||
ExitFrame prev_exit_frame_;
|
||||
cell_t entry_cip_;
|
||||
const intptr_t *entry_sp_;
|
||||
};
|
||||
|
||||
class FrameIterator : public SourcePawn::IFrameIterator
|
||||
{
|
||||
public:
|
||||
FrameIterator();
|
||||
|
||||
bool Done() const KE_OVERRIDE {
|
||||
return !ivk_;
|
||||
}
|
||||
void Next() KE_OVERRIDE;
|
||||
void Reset() KE_OVERRIDE;
|
||||
|
||||
bool IsNativeFrame() const KE_OVERRIDE;
|
||||
bool IsScriptedFrame() const KE_OVERRIDE;
|
||||
const char *FunctionName() const KE_OVERRIDE;
|
||||
const char *FilePath() const KE_OVERRIDE;
|
||||
unsigned LineNumber() const KE_OVERRIDE;
|
||||
IPluginContext *Context() const KE_OVERRIDE;
|
||||
|
||||
private:
|
||||
void nextInvokeFrame();
|
||||
cell_t findCip() const;
|
||||
|
||||
private:
|
||||
InvokeFrame *ivk_;
|
||||
ExitFrame exit_frame_;
|
||||
PluginRuntime *runtime_;
|
||||
const intptr_t *sp_iter_;
|
||||
const intptr_t *sp_stop_;
|
||||
cell_t function_cip_;
|
||||
mutable cell_t cip_;
|
||||
void *pc_;
|
||||
};
|
||||
|
||||
} // namespace sp
|
||||
|
||||
#endif // _include_sourcepawn_vm_stack_frames_h_
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "code-stubs.h"
|
||||
#include "x86-utils.h"
|
||||
#include "jit_x86.h"
|
||||
#include "environment.h"
|
||||
|
||||
using namespace sp;
|
||||
using namespace SourcePawn;
|
||||
@@ -64,6 +65,12 @@ CodeStubs::CompileInvokeStub()
|
||||
// Align the stack.
|
||||
__ andl(esp, 0xfffffff0);
|
||||
|
||||
// Set up the last piece of the invoke frame. This lets us find the bounds
|
||||
// of the call stack.
|
||||
__ movl(eax, intptr_t(Environment::get()));
|
||||
__ movl(eax, Operand(eax, Environment::offsetOfTopFrame()));
|
||||
__ movl(Operand(eax, InvokeFrame::offsetOfEntrySp()), esp);
|
||||
|
||||
// Call into plugin (align the stack first).
|
||||
__ call(ecx);
|
||||
|
||||
@@ -98,17 +105,11 @@ CodeStubs::CompileInvokeStub()
|
||||
__ movl(ecx, Operand(ebp, 8 + 4 * 0)); // ret-path expects ecx = ctx
|
||||
__ jmp(&ret);
|
||||
|
||||
Label timeout;
|
||||
__ bind(&timeout);
|
||||
__ movl(eax, SP_ERROR_TIMEOUT);
|
||||
__ jmp(&error);
|
||||
|
||||
invoke_stub_ = LinkCode(env_, masm);
|
||||
if (!invoke_stub_)
|
||||
return false;
|
||||
|
||||
return_stub_ = reinterpret_cast<uint8_t *>(invoke_stub_) + error.offset();
|
||||
timeout_stub_ = reinterpret_cast<uint8_t *>(invoke_stub_) + timeout.offset();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// 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/>.
|
||||
#ifndef _include_sourcepawn_jit_frames_x86_h_
|
||||
#define _include_sourcepawn_jit_frames_x86_h_
|
||||
|
||||
#include <sp_vm_types.h>
|
||||
|
||||
namespace sp {
|
||||
|
||||
using namespace SourcePawn;
|
||||
|
||||
class PluginContext;
|
||||
|
||||
// This is the layout of the stack in between each scripted function call.
|
||||
struct JitFrame
|
||||
{
|
||||
intptr_t align0;
|
||||
intptr_t align1;
|
||||
ucell_t function_cip;
|
||||
void *return_address;
|
||||
|
||||
static inline const JitFrame *FromSp(const intptr_t *sp) {
|
||||
return reinterpret_cast<const JitFrame *>(sp);
|
||||
}
|
||||
};
|
||||
|
||||
// When we're about to call a native, the stack pointer we store in the exit
|
||||
// frame is such that (sp + sizeof(JitExitFrameForNative)) conforms to this
|
||||
// structure.
|
||||
//
|
||||
// Note that it looks reversed compared to JitFrame because we capture the sp
|
||||
// before saving registers and pushing arguments.
|
||||
struct JitExitFrameForNative
|
||||
{
|
||||
void *return_address;
|
||||
PluginContext *cx;
|
||||
union {
|
||||
uint32_t native_index;
|
||||
SPVM_NATIVE_FUNC fn;
|
||||
} arg;
|
||||
const cell_t *params;
|
||||
cell_t saved_alt;
|
||||
|
||||
static inline const JitExitFrameForNative *FromExitSp(const intptr_t *exit_sp) {
|
||||
return reinterpret_cast<const JitExitFrameForNative *>(
|
||||
reinterpret_cast<const uint8_t *>(exit_sp) - sizeof(JitExitFrameForNative));
|
||||
}
|
||||
};
|
||||
|
||||
// Unlke native frames, the exit_sp for these is created at the base address.
|
||||
struct JitExitFrameForHelper
|
||||
{
|
||||
void *return_address;
|
||||
|
||||
static inline const JitExitFrameForHelper *FromExitSp(const intptr_t *exit_sp) {
|
||||
return reinterpret_cast<const JitExitFrameForHelper *>(exit_sp);
|
||||
}
|
||||
|
||||
bool isCompileFunction() const {
|
||||
return !!return_address;
|
||||
}
|
||||
const JitFrame *prev() const {
|
||||
return reinterpret_cast<const JitFrame *>(this + 1);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sp
|
||||
|
||||
#endif // _include_sourcepawn_jit_frames_x86_h_
|
||||
+215
-94
@@ -236,6 +236,9 @@ Compiler::emit(int *errp)
|
||||
// an opcode, we bind its corresponding label.
|
||||
__ bind(&jump_map_[cip_ - codeseg]);
|
||||
|
||||
// Save the start of the opcode for emitCipMap().
|
||||
op_cip_ = cip_;
|
||||
|
||||
OPCODE op = (OPCODE)readCell();
|
||||
if (!emitOp(op) || error_ != SP_ERROR_NONE) {
|
||||
*errp = (error_ == SP_ERROR_NONE) ? SP_ERROR_OUT_OF_MEMORY : error_;
|
||||
@@ -244,6 +247,17 @@ Compiler::emit(int *errp)
|
||||
}
|
||||
|
||||
emitCallThunks();
|
||||
|
||||
// For each backward jump, emit a little thunk so we can exit from a timeout.
|
||||
// Track the offset of where the thunk is, so the watchdog timer can patch it.
|
||||
for (size_t i = 0; i < backward_jumps_.length(); i++) {
|
||||
BackwardJump &jump = backward_jumps_[i];
|
||||
jump.timeout_offset = masm.pc();
|
||||
__ call(&throw_timeout_);
|
||||
emitCipMapping(jump.cip);
|
||||
}
|
||||
|
||||
// This has to come last.
|
||||
emitErrorPaths();
|
||||
|
||||
uint8_t *code = LinkCode(env_, masm);
|
||||
@@ -255,44 +269,69 @@ Compiler::emit(int *errp)
|
||||
AutoPtr<FixedArray<LoopEdge>> edges(
|
||||
new FixedArray<LoopEdge>(backward_jumps_.length()));
|
||||
for (size_t i = 0; i < backward_jumps_.length(); i++) {
|
||||
edges->at(i).offset = backward_jumps_[i];
|
||||
edges->at(i).disp32 = *reinterpret_cast<int32_t *>(code + edges->at(i).offset - 4);
|
||||
const BackwardJump &jump = backward_jumps_[i];
|
||||
edges->at(i).offset = jump.pc;
|
||||
edges->at(i).disp32 = int32_t(jump.timeout_offset) - int32_t(jump.pc);
|
||||
}
|
||||
|
||||
return new CompiledFunction(code, pcode_start_, edges.take());
|
||||
AutoPtr<FixedArray<CipMapEntry>> cipmap(
|
||||
new FixedArray<CipMapEntry>(cip_map_.length()));
|
||||
memcpy(cipmap->buffer(), cip_map_.buffer(), cip_map_.length() * sizeof(CipMapEntry));
|
||||
|
||||
return new CompiledFunction(code, masm.length(), pcode_start_, edges.take(), cipmap.take());
|
||||
}
|
||||
|
||||
// Helpers for invoking context members.
|
||||
// No exit frame - error code is returned directly.
|
||||
static int
|
||||
InvokePushTracker(PluginContext *cx, uint32_t amount)
|
||||
{
|
||||
return cx->pushTracker(amount);
|
||||
}
|
||||
|
||||
// No exit frame - error code is returned directly.
|
||||
static int
|
||||
InvokePopTrackerAndSetHeap(PluginContext *cx)
|
||||
{
|
||||
return cx->popTrackerAndSetHeap();
|
||||
}
|
||||
|
||||
// Error code must be checked in the environment.
|
||||
static cell_t
|
||||
InvokeNativeHelper(PluginContext *cx, ucell_t native_idx, cell_t *params)
|
||||
{
|
||||
return cx->invokeNative(native_idx, params);
|
||||
}
|
||||
|
||||
// Error code must be checked in the environment.
|
||||
static cell_t
|
||||
InvokeBoundNativeHelper(PluginContext *cx, SPVM_NATIVE_FUNC fn, cell_t *params)
|
||||
{
|
||||
return cx->invokeBoundNative(fn, params);
|
||||
}
|
||||
|
||||
// No exit frame - error code is returned directly.
|
||||
static int
|
||||
InvokeGenerateFullArray(PluginContext *cx, uint32_t argc, cell_t *argv, int autozero)
|
||||
{
|
||||
return cx->generateFullArray(argc, argv, autozero);
|
||||
}
|
||||
|
||||
// Exit frame is a JitExitFrameForHelper.
|
||||
static void
|
||||
InvokeReportError(int err)
|
||||
{
|
||||
Environment::get()->ReportError(err);
|
||||
}
|
||||
|
||||
// Exit frame is a JitExitFrameForHelper. This is a special function since we
|
||||
// have to notify the watchdog timer that we're unblocked.
|
||||
static void
|
||||
InvokeReportTimeout()
|
||||
{
|
||||
Environment::get()->watchdog()->NotifyTimeoutReceived();
|
||||
InvokeReportError(SP_ERROR_TIMEOUT);
|
||||
}
|
||||
|
||||
bool
|
||||
Compiler::emitOp(OPCODE op)
|
||||
{
|
||||
@@ -450,8 +489,16 @@ Compiler::emitOp(OPCODE op)
|
||||
__ subl(tmp, dat);
|
||||
__ movl(Operand(frmAddr()), tmp);
|
||||
|
||||
// Align the stack to 16-bytes (each call adds 4 bytes).
|
||||
__ subl(esp, 12);
|
||||
// Store the function cip for stack traces.
|
||||
__ push(pcode_start_);
|
||||
|
||||
// Align the stack to 16-bytes (each call adds 8 bytes).
|
||||
__ subl(esp, 8);
|
||||
#if defined(DEBUG)
|
||||
// Debug guards.
|
||||
__ movl(Operand(esp, 0), 0xffaaee00);
|
||||
__ movl(Operand(esp, 4), 0xffaaee04);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case OP_IDXADDR_B:
|
||||
@@ -769,14 +816,14 @@ Compiler::emitOp(OPCODE op)
|
||||
|
||||
// Guard against divide-by-zero.
|
||||
__ testl(divisor, divisor);
|
||||
__ j(zero, &error_divide_by_zero_);
|
||||
jumpOnError(zero, SP_ERROR_DIVIDE_BY_ZERO);
|
||||
|
||||
// A more subtle case; -INT_MIN / -1 yields an overflow exception.
|
||||
Label ok;
|
||||
__ cmpl(divisor, -1);
|
||||
__ j(not_equal, &ok);
|
||||
__ cmpl(dividend, 0x80000000);
|
||||
__ j(equal, &error_integer_overflow_);
|
||||
jumpOnError(equal, SP_ERROR_INTEGER_OVERFLOW);
|
||||
__ bind(&ok);
|
||||
|
||||
// Now we can actually perform the divide.
|
||||
@@ -1078,13 +1125,13 @@ Compiler::emitOp(OPCODE op)
|
||||
if (amount > 0) {
|
||||
// Check if the stack went beyond the stack top - usually a compiler error.
|
||||
__ cmpl(stk, intptr_t(context_->memory() + context_->HeapSize()));
|
||||
__ j(not_below, &error_stack_min_);
|
||||
jumpOnError(not_below, SP_ERROR_STACKMIN);
|
||||
} else {
|
||||
// Check if the stack is going to collide with the heap.
|
||||
__ movl(tmp, Operand(hpAddr()));
|
||||
__ lea(tmp, Operand(dat, ecx, NoScale, STACK_MARGIN));
|
||||
__ cmpl(stk, tmp);
|
||||
__ j(below, &error_stack_low_);
|
||||
jumpOnError(below, SP_ERROR_STACKLOW);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1097,12 +1144,12 @@ Compiler::emitOp(OPCODE op)
|
||||
|
||||
if (amount < 0) {
|
||||
__ cmpl(Operand(hpAddr()), context_->DataSize());
|
||||
__ j(below, &error_heap_min_);
|
||||
jumpOnError(below, SP_ERROR_HEAPMIN);
|
||||
} else {
|
||||
__ movl(tmp, Operand(hpAddr()));
|
||||
__ lea(tmp, Operand(dat, ecx, NoScale, STACK_MARGIN));
|
||||
__ cmpl(tmp, stk);
|
||||
__ j(above, &error_heap_low_);
|
||||
jumpOnError(above, SP_ERROR_HEAPLOW);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1114,7 +1161,7 @@ Compiler::emitOp(OPCODE op)
|
||||
return false;
|
||||
if (target->bound()) {
|
||||
__ jmp32(target);
|
||||
backward_jumps_.append(masm.pc());
|
||||
backward_jumps_.append(BackwardJump(masm.pc(), op_cip_));
|
||||
} else {
|
||||
__ jmp(target);
|
||||
}
|
||||
@@ -1131,7 +1178,7 @@ Compiler::emitOp(OPCODE op)
|
||||
__ testl(pri, pri);
|
||||
if (target->bound()) {
|
||||
__ j32(cc, target);
|
||||
backward_jumps_.append(masm.pc());
|
||||
backward_jumps_.append(BackwardJump(masm.pc(), op_cip_));
|
||||
} else {
|
||||
__ j(cc, target);
|
||||
}
|
||||
@@ -1152,7 +1199,7 @@ Compiler::emitOp(OPCODE op)
|
||||
__ cmpl(pri, alt);
|
||||
if (target->bound()) {
|
||||
__ j32(cc, target);
|
||||
backward_jumps_.append(masm.pc());
|
||||
backward_jumps_.append(BackwardJump(masm.pc(), op_cip_));
|
||||
} else {
|
||||
__ j(cc, target);
|
||||
}
|
||||
@@ -1171,7 +1218,7 @@ Compiler::emitOp(OPCODE op)
|
||||
__ call(ExternalAddress((void *)InvokePushTracker));
|
||||
__ addl(esp, 8);
|
||||
__ testl(eax, eax);
|
||||
__ j(not_zero, &extern_error_);
|
||||
jumpOnError(not_zero);
|
||||
|
||||
__ pop(alt);
|
||||
__ pop(pri);
|
||||
@@ -1189,31 +1236,32 @@ Compiler::emitOp(OPCODE op)
|
||||
__ call(ExternalAddress((void *)InvokePopTrackerAndSetHeap));
|
||||
__ addl(esp, 4);
|
||||
__ testl(eax, eax);
|
||||
__ j(not_zero, &extern_error_);
|
||||
jumpOnError(not_zero);
|
||||
|
||||
__ pop(alt);
|
||||
__ pop(pri);
|
||||
break;
|
||||
}
|
||||
|
||||
// This opcode is used to note where line breaks occur. We don't support
|
||||
// live debugging, and if we did, we could build this map from the lines
|
||||
// table. So we don't generate any code here.
|
||||
case OP_BREAK:
|
||||
{
|
||||
cell_t cip = uintptr_t(cip_ - 1) - uintptr_t(rt_->code().bytes);
|
||||
__ movl(Operand(cipAddr()), cip);
|
||||
break;
|
||||
}
|
||||
|
||||
// This should never be hit.
|
||||
case OP_HALT:
|
||||
__ align(16);
|
||||
__ movl(pri, readCell());
|
||||
__ jmp(&extern_error_);
|
||||
__ testl(eax, eax);
|
||||
jumpOnError(not_zero);
|
||||
break;
|
||||
|
||||
case OP_BOUNDS:
|
||||
{
|
||||
cell_t value = readCell();
|
||||
__ cmpl(eax, value);
|
||||
__ j(above, &error_bounds_);
|
||||
jumpOnError(above, SP_ERROR_ARRAY_BOUNDS);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1281,7 +1329,7 @@ Compiler::emitCheckAddress(Register reg)
|
||||
{
|
||||
// Check if we're in memory bounds.
|
||||
__ cmpl(reg, context_->HeapSize());
|
||||
__ j(not_below, &error_memaccess_);
|
||||
jumpOnError(not_below, SP_ERROR_MEMACCESS);
|
||||
|
||||
// Check if we're in the invalid region between hp and sp.
|
||||
Label done;
|
||||
@@ -1289,7 +1337,7 @@ Compiler::emitCheckAddress(Register reg)
|
||||
__ j(below, &done);
|
||||
__ lea(tmp, Operand(dat, reg, NoScale));
|
||||
__ cmpl(tmp, stk);
|
||||
__ j(below, &error_memaccess_);
|
||||
jumpOnError(below, SP_ERROR_MEMACCESS);
|
||||
__ bind(&done);
|
||||
}
|
||||
|
||||
@@ -1308,7 +1356,7 @@ Compiler::emitGenArray(bool autozero)
|
||||
__ movl(Operand(hpAddr()), alt);
|
||||
__ addl(alt, dat);
|
||||
__ cmpl(alt, stk);
|
||||
__ j(not_below, &error_heap_low_);
|
||||
jumpOnError(not_below, SP_ERROR_HEAPLOW);
|
||||
|
||||
__ shll(tmp, 2);
|
||||
__ push(tmp);
|
||||
@@ -1318,7 +1366,7 @@ Compiler::emitGenArray(bool autozero)
|
||||
__ pop(tmp);
|
||||
__ shrl(tmp, 2);
|
||||
__ testl(eax, eax);
|
||||
__ j(not_zero, &extern_error_);
|
||||
jumpOnError(not_zero);
|
||||
|
||||
if (autozero) {
|
||||
// Note - tmp is ecx and still intact.
|
||||
@@ -1347,7 +1395,7 @@ Compiler::emitGenArray(bool autozero)
|
||||
__ pop(tmp);
|
||||
|
||||
__ testl(eax, eax);
|
||||
__ j(not_zero, &extern_error_);
|
||||
jumpOnError(not_zero);
|
||||
|
||||
// Move tmp back to pri, remove pushed args.
|
||||
__ movl(pri, tmp);
|
||||
@@ -1367,25 +1415,6 @@ Compiler::emitCall()
|
||||
return false;
|
||||
}
|
||||
|
||||
// eax = context
|
||||
// ecx = rp
|
||||
__ movl(eax, intptr_t(rt_->GetBaseContext()));
|
||||
__ movl(ecx, Operand(eax, PluginContext::offsetOfRp()));
|
||||
|
||||
// Check if the return stack is used up.
|
||||
__ cmpl(ecx, SP_MAX_RETURN_STACK);
|
||||
__ j(not_below, &error_stack_low_);
|
||||
|
||||
// Add to the return stack.
|
||||
uintptr_t cip = uintptr_t(cip_ - 2) - uintptr_t(rt_->code().bytes);
|
||||
__ movl(Operand(eax, ecx, ScaleFour, PluginContext::offsetOfRstkCips()), cip);
|
||||
|
||||
// Increment the return stack pointer.
|
||||
__ addl(Operand(eax, PluginContext::offsetOfRp()), 1);
|
||||
|
||||
// Store the CIP of the function we're about to call.
|
||||
__ movl(Operand(cipAddr()), offset);
|
||||
|
||||
CompiledFunction *fun = rt_->GetJittedFunctionByOffset(offset);
|
||||
if (!fun) {
|
||||
// Need to emit a delayed thunk.
|
||||
@@ -1398,12 +1427,8 @@ Compiler::emitCall()
|
||||
__ call(ExternalAddress(fun->GetEntryAddress()));
|
||||
}
|
||||
|
||||
// Restore the last cip.
|
||||
__ movl(Operand(cipAddr()), cip);
|
||||
|
||||
// Mark us as leaving the last frame.
|
||||
__ movl(tmp, intptr_t(rt_->GetBaseContext()));
|
||||
__ subl(Operand(tmp, PluginContext::offsetOfRp()), 1);
|
||||
// Map the return address to the cip that started this call.
|
||||
emitCipMapping(op_cip_);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1415,15 +1440,27 @@ Compiler::emitCallThunks()
|
||||
|
||||
Label error;
|
||||
__ bind(&thunk->call);
|
||||
// Huge hack - get the return address, since that is the call that we
|
||||
// need to patch.
|
||||
|
||||
// Get the return address, since that is the call that we need to patch.
|
||||
__ movl(eax, Operand(esp, 0));
|
||||
|
||||
// Push an OP_PROC frame as if we already called the function. This helps
|
||||
// error reporting.
|
||||
__ push(thunk->pcode_offset);
|
||||
__ subl(esp, 8);
|
||||
|
||||
// Create the exit frame, then align the stack.
|
||||
__ push(0);
|
||||
__ movl(ecx, intptr_t(&Environment::get()->exit_frame()));
|
||||
__ movl(Operand(ecx, ExitFrame::offsetOfExitNative()), -1);
|
||||
__ movl(Operand(ecx, ExitFrame::offsetOfExitSp()), esp);
|
||||
|
||||
// We need to push 4 arguments, and one of them will need an extra word
|
||||
// on the stack. Allocate a big block so we're aligned, subtracting
|
||||
// 4 because we got here via a call.
|
||||
// on the stack. Allocate a big block so we're aligned.
|
||||
//
|
||||
// Note: we add 12 since the push above misaligned the stack.
|
||||
static const size_t kStackNeeded = 5 * sizeof(void *);
|
||||
static const size_t kStackReserve = ke::Align(kStackNeeded, 16) - sizeof(void *);
|
||||
static const size_t kStackReserve = ke::Align(kStackNeeded, 16) + 3 * sizeof(void *);
|
||||
__ subl(esp, kStackReserve);
|
||||
|
||||
// Set arguments.
|
||||
@@ -1435,14 +1472,10 @@ Compiler::emitCallThunks()
|
||||
|
||||
__ call(ExternalAddress((void *)CompileFromThunk));
|
||||
__ movl(edx, Operand(esp, 4 * sizeof(void *)));
|
||||
__ addl(esp, kStackReserve);
|
||||
__ addl(esp, kStackReserve + 4 * sizeof(void *)); // Drop the exit frame and fake frame.
|
||||
__ testl(eax, eax);
|
||||
__ j(not_zero, &error);
|
||||
jumpOnError(not_zero);
|
||||
__ jmp(edx);
|
||||
|
||||
__ bind(&error);
|
||||
__ movl(Operand(cipAddr()), thunk->pcode_offset);
|
||||
__ jmp(ExternalAddress(env_->stubs()->ReturnStub()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1482,18 +1515,23 @@ Compiler::emitNativeCall(OPCODE op)
|
||||
__ subl(stk, 4);
|
||||
}
|
||||
|
||||
// Create the exit frame. This is a JitExitFrameForNative, so everything we
|
||||
// push up to the return address of the call instruction is reflected in
|
||||
// that structure.
|
||||
__ movl(eax, intptr_t(&Environment::get()->exit_frame()));
|
||||
__ movl(Operand(eax, ExitFrame::offsetOfExitNative()), native_index);
|
||||
__ movl(Operand(eax, ExitFrame::offsetOfExitSp()), esp);
|
||||
|
||||
// Save registers.
|
||||
__ push(edx);
|
||||
|
||||
// Push the last parameter for the C++ function.
|
||||
__ push(stk);
|
||||
|
||||
__ movl(eax, intptr_t(rt_->GetBaseContext()));
|
||||
__ movl(Operand(eax, PluginContext::offsetOfLastNative()), native_index);
|
||||
|
||||
// Relocate our absolute stk to be dat-relative, and update the context's
|
||||
// view.
|
||||
__ subl(stk, dat);
|
||||
__ movl(eax, intptr_t(context_));
|
||||
__ movl(Operand(eax, PluginContext::offsetOfSp()), stk);
|
||||
|
||||
const sp_native_t *native = rt_->GetNative(native_index);
|
||||
@@ -1512,11 +1550,14 @@ Compiler::emitNativeCall(OPCODE op)
|
||||
__ call(ExternalAddress((void *)InvokeBoundNativeHelper));
|
||||
}
|
||||
|
||||
// Check for errors.
|
||||
__ movl(ecx, intptr_t(rt_->GetBaseContext()));
|
||||
__ movl(ecx, Operand(ecx, PluginContext::offsetOfNativeError()));
|
||||
__ testl(ecx, ecx);
|
||||
__ j(not_zero, &extern_error_);
|
||||
// Map the return address to the cip that initiated this call.
|
||||
emitCipMapping(op_cip_);
|
||||
|
||||
// Check for errors. Note we jump directly to the return stub since the
|
||||
// error has already been reported.
|
||||
__ movl(ecx, intptr_t(Environment::get()));
|
||||
__ cmpl(Operand(ecx, Environment::offsetOfExceptionCode()), 0);
|
||||
__ j(not_zero, &return_reported_error_);
|
||||
|
||||
// Restore local state.
|
||||
__ addl(stk, dat);
|
||||
@@ -1631,16 +1672,6 @@ Compiler::emitSwitch()
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
Compiler::emitErrorPath(Label *dest, int code)
|
||||
{
|
||||
if (dest->used()) {
|
||||
__ bind(dest);
|
||||
__ movl(eax, code);
|
||||
__ jmp(ExternalAddress(env_->stubs()->ReturnStub()));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Compiler::emitFloatCmp(ConditionCode cc)
|
||||
{
|
||||
@@ -1691,22 +1722,112 @@ Compiler::emitFloatCmp(ConditionCode cc)
|
||||
__ addl(stk, 8);
|
||||
}
|
||||
|
||||
void
|
||||
Compiler::jumpOnError(ConditionCode cc, int err)
|
||||
{
|
||||
// Note: we accept 0 for err. In this case we expect the error to be in eax.
|
||||
{
|
||||
ErrorPath path(op_cip_, err);
|
||||
error_paths_.append(path);
|
||||
}
|
||||
|
||||
ErrorPath &path = error_paths_.back();
|
||||
__ j(cc, &path.label);
|
||||
}
|
||||
|
||||
void
|
||||
Compiler::emitErrorPaths()
|
||||
{
|
||||
emitErrorPath(&error_divide_by_zero_, SP_ERROR_DIVIDE_BY_ZERO);
|
||||
emitErrorPath(&error_stack_low_, SP_ERROR_STACKLOW);
|
||||
emitErrorPath(&error_stack_min_, SP_ERROR_STACKMIN);
|
||||
emitErrorPath(&error_bounds_, SP_ERROR_ARRAY_BOUNDS);
|
||||
emitErrorPath(&error_memaccess_, SP_ERROR_MEMACCESS);
|
||||
emitErrorPath(&error_heap_low_, SP_ERROR_HEAPLOW);
|
||||
emitErrorPath(&error_heap_min_, SP_ERROR_HEAPMIN);
|
||||
emitErrorPath(&error_integer_overflow_, SP_ERROR_INTEGER_OVERFLOW);
|
||||
// For each path that had an error check, bind it to an error routine and
|
||||
// add it to the cip map. What we'll get is something like:
|
||||
//
|
||||
// cmp dividend, 0
|
||||
// jz error_thunk_0
|
||||
//
|
||||
// error_thunk_0:
|
||||
// call integer_overflow
|
||||
//
|
||||
// integer_overflow:
|
||||
// mov eax, SP_ERROR_DIVIDE_BY_ZERO
|
||||
// jmp report_error
|
||||
//
|
||||
// report_error:
|
||||
// create exit frame
|
||||
// push eax
|
||||
// call InvokeReportError(int err)
|
||||
//
|
||||
for (size_t i = 0; i < error_paths_.length(); i++) {
|
||||
ErrorPath &path = error_paths_[i];
|
||||
|
||||
if (extern_error_.used()) {
|
||||
__ bind(&extern_error_);
|
||||
__ movl(eax, intptr_t(rt_->GetBaseContext()));
|
||||
__ movl(eax, Operand(eax, PluginContext::offsetOfNativeError()));
|
||||
// If there's no error code, it should be in eax. Otherwise we'll jump to
|
||||
// a path that sets eax to a hardcoded value.
|
||||
__ bind(&path.label);
|
||||
if (path.err == 0)
|
||||
__ call(&report_error_);
|
||||
else
|
||||
__ call(&throw_error_code_[path.err]);
|
||||
|
||||
emitCipMapping(path.cip);
|
||||
}
|
||||
|
||||
emitThrowPathIfNeeded(SP_ERROR_DIVIDE_BY_ZERO);
|
||||
emitThrowPathIfNeeded(SP_ERROR_STACKLOW);
|
||||
emitThrowPathIfNeeded(SP_ERROR_STACKMIN);
|
||||
emitThrowPathIfNeeded(SP_ERROR_ARRAY_BOUNDS);
|
||||
emitThrowPathIfNeeded(SP_ERROR_MEMACCESS);
|
||||
emitThrowPathIfNeeded(SP_ERROR_HEAPLOW);
|
||||
emitThrowPathIfNeeded(SP_ERROR_HEAPMIN);
|
||||
emitThrowPathIfNeeded(SP_ERROR_INTEGER_OVERFLOW);
|
||||
|
||||
if (report_error_.used()) {
|
||||
__ bind(&report_error_);
|
||||
|
||||
// Create the exit frame. We always get here through a call from the opcode
|
||||
// (and always via an out-of-line thunk).
|
||||
__ movl(ecx, intptr_t(&Environment::get()->exit_frame()));
|
||||
__ movl(Operand(ecx, ExitFrame::offsetOfExitNative()), -1);
|
||||
__ movl(Operand(ecx, ExitFrame::offsetOfExitSp()), esp);
|
||||
|
||||
// Since the return stub wipes out the stack, we don't need to subl after
|
||||
// the call.
|
||||
__ push(eax);
|
||||
__ call(ExternalAddress((void *)InvokeReportError));
|
||||
__ jmp(ExternalAddress(env_->stubs()->ReturnStub()));
|
||||
}
|
||||
|
||||
// We get here if we know an exception is already pending.
|
||||
if (return_reported_error_.used()) {
|
||||
__ bind(&return_reported_error_);
|
||||
__ movl(eax, intptr_t(Environment::get()));
|
||||
__ movl(eax, Operand(eax, Environment::offsetOfExceptionCode()));
|
||||
__ jmp(ExternalAddress(env_->stubs()->ReturnStub()));
|
||||
}
|
||||
|
||||
// The timeout uses a special stub.
|
||||
if (throw_timeout_.used()) {
|
||||
__ bind(&throw_timeout_);
|
||||
|
||||
// Create the exit frame.
|
||||
__ movl(ecx, intptr_t(&Environment::get()->exit_frame()));
|
||||
__ movl(Operand(ecx, ExitFrame::offsetOfExitNative()), -1);
|
||||
__ movl(Operand(ecx, ExitFrame::offsetOfExitSp()), esp);
|
||||
|
||||
// Since the return stub wipes out the stack, we don't need to subl after
|
||||
// the call.
|
||||
__ call(ExternalAddress((void *)InvokeReportTimeout));
|
||||
__ jmp(ExternalAddress(env_->stubs()->ReturnStub()));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Compiler::emitThrowPathIfNeeded(int err)
|
||||
{
|
||||
assert(err < SP_MAX_ERROR_CODES);
|
||||
|
||||
if (!throw_error_code_[err].used())
|
||||
return;
|
||||
|
||||
__ bind(&throw_error_code_[err]);
|
||||
__ movl(eax, err);
|
||||
__ jmp(&report_error_);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,36 @@ class LegacyImage;
|
||||
class Environment;
|
||||
class CompiledFunction;
|
||||
|
||||
struct ErrorPath
|
||||
{
|
||||
SilentLabel label;
|
||||
const cell_t *cip;
|
||||
int err;
|
||||
|
||||
ErrorPath(const cell_t *cip, int err)
|
||||
: cip(cip),
|
||||
err(err)
|
||||
{}
|
||||
ErrorPath()
|
||||
{}
|
||||
};
|
||||
|
||||
struct BackwardJump {
|
||||
// The pc at the jump instruction (i.e. after it).
|
||||
uint32_t pc;
|
||||
// The cip of the jump.
|
||||
const cell_t *cip;
|
||||
// The offset of the timeout thunk. This is filled in at the end.
|
||||
uint32_t timeout_offset;
|
||||
|
||||
BackwardJump()
|
||||
{}
|
||||
BackwardJump(uint32_t pc, const cell_t *cip)
|
||||
: pc(pc),
|
||||
cip(cip)
|
||||
{}
|
||||
};
|
||||
|
||||
#define JIT_INLINE_ERRORCHECKS (1<<0)
|
||||
#define JIT_INLINE_NATIVES (1<<1)
|
||||
#define STACK_MARGIN 64 //8 parameters of safety, I guess
|
||||
@@ -42,21 +72,9 @@ class CompiledFunction;
|
||||
|
||||
#define sDIMEN_MAX 5 //this must mirror what the compiler has.
|
||||
|
||||
typedef struct funcinfo_s
|
||||
{
|
||||
unsigned int magic;
|
||||
unsigned int index;
|
||||
} funcinfo_t;
|
||||
|
||||
typedef struct functracker_s
|
||||
{
|
||||
unsigned int num_functions;
|
||||
unsigned int code_size;
|
||||
} functracker_t;
|
||||
|
||||
struct CallThunk
|
||||
{
|
||||
Label call;
|
||||
SilentLabel call;
|
||||
cell_t pcode_offset;
|
||||
|
||||
CallThunk(cell_t pcode_offset)
|
||||
@@ -89,10 +107,9 @@ class Compiler
|
||||
void emitErrorPath(Label *dest, int code);
|
||||
void emitErrorPaths();
|
||||
void emitFloatCmp(ConditionCode cc);
|
||||
void jumpOnError(ConditionCode cc, int err = 0);
|
||||
void emitThrowPathIfNeeded(int err);
|
||||
|
||||
ExternalAddress cipAddr() {
|
||||
return ExternalAddress(context_->addressOfCip());
|
||||
}
|
||||
ExternalAddress hpAddr() {
|
||||
return ExternalAddress(context_->addressOfHp());
|
||||
}
|
||||
@@ -100,6 +117,16 @@ class Compiler
|
||||
return ExternalAddress(context_->addressOfFrm());
|
||||
}
|
||||
|
||||
// Map a return address (i.e. an exit point from a function) to its source
|
||||
// cip. This lets us avoid tracking the cip during runtime. These are
|
||||
// sorted by definition since we assemble and emit in forward order.
|
||||
void emitCipMapping(const cell_t *cip) {
|
||||
CipMapEntry entry;
|
||||
entry.cipoffs = uintptr_t(cip) - uintptr_t(code_start_);
|
||||
entry.pcoffs = masm.pc();
|
||||
cip_map_.append(entry);
|
||||
}
|
||||
|
||||
private:
|
||||
AssemblerX86 masm;
|
||||
Environment *env_;
|
||||
@@ -110,20 +137,19 @@ class Compiler
|
||||
uint32_t pcode_start_;
|
||||
const cell_t *code_start_;
|
||||
const cell_t *cip_;
|
||||
const cell_t *op_cip_;
|
||||
const cell_t *code_end_;
|
||||
Label *jump_map_;
|
||||
ke::Vector<size_t> backward_jumps_;
|
||||
ke::Vector<BackwardJump> backward_jumps_;
|
||||
|
||||
// Errors
|
||||
Label error_bounds_;
|
||||
Label error_heap_low_;
|
||||
Label error_heap_min_;
|
||||
Label error_stack_low_;
|
||||
Label error_stack_min_;
|
||||
Label error_divide_by_zero_;
|
||||
Label error_memaccess_;
|
||||
Label error_integer_overflow_;
|
||||
Label extern_error_;
|
||||
ke::Vector<CipMapEntry> cip_map_;
|
||||
|
||||
// Errors.
|
||||
ke::Vector<ErrorPath> error_paths_;
|
||||
Label throw_timeout_;
|
||||
Label throw_error_code_[SP_MAX_ERROR_CODES];
|
||||
Label report_error_;
|
||||
Label return_reported_error_;
|
||||
|
||||
ke::Vector<CallThunk *> thunks_; //:TODO: free
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user