Merge pull request #285 from alliedmodders/frames

Implement a new stack and error handling model for the SourcePawn VM.
This commit is contained in:
David Anderson
2015-03-04 23:45:59 -08:00
51 changed files with 2085 additions and 1766 deletions
+1 -1
View File
@@ -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
View File
@@ -178,14 +178,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';
@@ -194,6 +190,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)
{
@@ -256,13 +264,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;
}
@@ -341,7 +349,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;
}
@@ -387,3 +397,9 @@ SourcePawnEngine2::SetProfilingTool(IProfilingTool *tool)
{
Environment::get()->SetProfiler(tool);
}
ISourcePawnEnvironment *
SourcePawnEngine2::Environment()
{
return Environment::get();
}
+5 -1
View File
@@ -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_
+1 -2
View File
@@ -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)
{
}
-4
View File
@@ -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_.
};
}
+44 -2
View File
@@ -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 -2
View File
@@ -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_;
};
}
-123
View File
@@ -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;
}
-51
View File
@@ -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_
+70 -74
View File
@@ -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
View File
@@ -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_;
}
+44 -7
View File
@@ -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
View File
@@ -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);
}
+15 -58
View File
@@ -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_;
+11 -10
View File
@@ -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
+8 -7
View File
@@ -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_;
+105 -79
View File
@@ -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
+17 -12
View File
@@ -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_;
};
+227
View File
@@ -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();
}
+135
View File
@@ -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_
+7 -6
View File
@@ -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;
}
+83
View File
@@ -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
View File
@@ -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_);
}
+53 -27
View File
@@ -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
};