Implement a watchdog timer for scripts that take too long to execute (bug 5837, r=fyren).

--HG--
extra : rebase_source : ffacb38457eca581660ce8f15c444ad828b7fedd
This commit is contained in:
David Anderson 2013-08-14 23:54:25 -07:00
parent 74f1b800c2
commit 3ac43497b9
25 changed files with 1250 additions and 99 deletions

View File

@ -1,5 +1,5 @@
/** /**
* vim: set ts=4 sw=4 : * vim: set ts=4 sw=4 tw=99 noet:
* ============================================================================= * =============================================================================
* SourceMod * SourceMod
* Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved. * Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved.
@ -311,6 +311,16 @@ void SourceModBase::StartSourceMod(bool late)
{ {
extsys->LoadAutoExtension("updater.ext." PLATFORM_LIB_EXT); extsys->LoadAutoExtension("updater.ext." PLATFORM_LIB_EXT);
} }
const char *timeout = GetCoreConfigValue("SlowScriptTimeout");
if (timeout == NULL)
{
timeout = "8";
}
if (atoi(timeout) != 0)
{
g_pSourcePawn2->InstallWatchdogTimer(atoi(timeout) * 1000);
}
} }
static bool g_LevelEndBarrier = false; static bool g_LevelEndBarrier = false;

View File

@ -64,6 +64,11 @@ class Assembler
return pos_ - buffer_; return pos_ - buffer_;
} }
// Current offset into the code stream.
uint32_t pc() const {
return uint32_t(pos_ - buffer_);
}
protected: protected:
void writeByte(uint8_t byte) { void writeByte(uint8_t byte) {
write<uint8_t>(byte); write<uint8_t>(byte);
@ -125,11 +130,6 @@ class Assembler
return int32_t(pos_ - buffer_); return int32_t(pos_ - buffer_);
} }
// pc is the unsigned version of position().
uint32_t pc() const {
return uint32_t(pos_ - buffer_);
}
protected: protected:
void assertCanWrite(size_t bytes) { void assertCanWrite(size_t bytes) {
assert(pos_ + bytes <= end_); assert(pos_ + bytes <= end_);

View File

@ -640,6 +640,10 @@ class AssemblerX86 : public Assembler
emit2(0xdc, 0xc0 + src.code); emit2(0xdc, 0xc0 + src.code);
} }
void jmp32(Label *dest) {
emit1(0xe9);
emitJumpTarget(dest);
}
void jmp(Label *dest) { void jmp(Label *dest) {
int8_t d8; int8_t d8;
if (canEmitSmallJump(dest, &d8)) { if (canEmitSmallJump(dest, &d8)) {
@ -655,6 +659,10 @@ class AssemblerX86 : public Assembler
void jmp(const Operand &target) { void jmp(const Operand &target) {
emit1(0xff, 4, target); emit1(0xff, 4, target);
} }
void j32(ConditionCode cc, Label *dest) {
emit2(0x0f, 0x80 + uint8_t(cc));
emitJumpTarget(dest);
}
void j(ConditionCode cc, Label *dest) { void j(ConditionCode cc, Label *dest) {
int8_t d8; int8_t d8;
if (canEmitSmallJump(dest, &d8)) { if (canEmitSmallJump(dest, &d8)) {

View File

@ -0,0 +1,137 @@
/**
* vim: set sts=2 ts=8 sw=2 tw=99 noet :
* =============================================================================
* SourcePawn
* Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*/
#ifndef _include_sourcepawn_inline_list_h_
#define _include_sourcepawn_inline_list_h_
#include <stddef.h>
template <typename T> class InlineList;
template <typename T>
class InlineListNode
{
friend class InlineList<T>;
public:
InlineListNode()
: next_(NULL),
prev_(NULL)
{
}
InlineListNode(InlineListNode *next, InlineListNode *prev)
: next_(next),
prev_(prev)
{
}
protected:
InlineListNode *next_;
InlineListNode *prev_;
};
template <typename T>
class InlineList
{
typedef InlineListNode<T> Node;
Node head_;
public:
InlineList()
: head_(&head_, &head_)
{
}
public:
class iterator
{
friend class InlineList;
Node *iter_;
public:
iterator(Node *iter)
: iter_(iter)
{
}
iterator & operator ++() {
iter_ = iter_->next;
return *iter_;
}
iterator operator ++(int) {
iterator old(*this);
iter_ = iter_->next_;
return old;
}
T * operator *() {
return static_cast<T *>(iter_);
}
T * operator ->() {
return static_cast<T *>(iter_);
}
bool operator !=(const iterator &where) const {
return iter_ != where.iter_;
}
bool operator ==(const iterator &where) const {
return iter_ == where.iter_;
}
iterator prev() const {
iterator p(iter_->prev_);
return p;
}
iterator next() const {
iterator p(iter_->next_);
return p;
}
};
iterator begin() {
return iterator(head_.next_);
}
iterator end() {
return iterator(&head_);
}
void erase(Node *t) {
t->prev_->next_ = t->next_;
t->next_->prev_ = t->prev_;
}
void insert(Node *t) {
t->prev_ = head_.prev_;
t->next_ = &head_;
head_.prev_->next_ = t;
head_.prev_ = t;
}
};
#endif // _include_sourcepawn_inline_list_h_

View File

@ -0,0 +1,188 @@
// 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_thread_posix_h_
#define _include_sourcepawn_thread_posix_h_
#include <pthread.h>
#include <sys/time.h>
#include <errno.h>
#include <stdio.h>
#if defined(__linux__)
# include <sys/prctl.h>
#endif
namespace ke {
class Mutex : public Lockable
{
public:
Mutex() {
#if !defined(NDEBUG)
int rv =
#endif
pthread_mutex_init(&mutex_, NULL);
assert(rv == 0);
}
~Mutex() {
pthread_mutex_destroy(&mutex_);
}
bool DoTryLock() {
return pthread_mutex_trylock(&mutex_) == 0;
}
void DoLock() {
pthread_mutex_lock(&mutex_);
}
void DoUnlock() {
pthread_mutex_unlock(&mutex_);
}
pthread_mutex_t *raw() {
return &mutex_;
}
private:
pthread_mutex_t mutex_;
};
// Currently, this class only supports single-listener CVs.
class ConditionVariable : public Lockable
{
public:
ConditionVariable() {
#if !defined(NDEBUG)
int rv =
#endif
pthread_cond_init(&cv_, NULL);
assert(rv == 0);
}
~ConditionVariable() {
pthread_cond_destroy(&cv_);
}
bool DoTryLock() {
return mutex_.DoTryLock();
}
void DoLock() {
mutex_.DoLock();
}
void DoUnlock() {
mutex_.DoUnlock();
}
void Notify() {
AssertCurrentThreadOwns();
pthread_cond_signal(&cv_);
}
WaitResult Wait(size_t timeout_ms) {
AssertCurrentThreadOwns();
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
return Wait_Error;
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
DebugSetUnlocked();
int rv = pthread_cond_timedwait(&cv_, mutex_.raw(), &ts);
DebugSetLocked();
if (rv == ETIMEDOUT)
return Wait_Timeout;
if (rv == 0)
return Wait_Signaled;
return Wait_Error;
}
WaitResult Wait() {
AssertCurrentThreadOwns();
DebugSetUnlocked();
int rv = pthread_cond_wait(&cv_, mutex_.raw());
DebugSetLocked();
if (rv == 0)
return Wait_Signaled;
return Wait_Error;
}
private:
Mutex mutex_;
pthread_cond_t cv_;
};
class Thread
{
struct ThreadData {
IRunnable *run;
char name[17];
};
public:
Thread(IRunnable *run, const char *name = NULL) {
ThreadData *data = new ThreadData;
data->run = run;
snprintf(data->name, sizeof(data->name), "%s", name ? name : "");
initialized_ = (pthread_create(&thread_, NULL, Main, data) == 0);
if (!initialized_)
delete data;
}
bool Succeeded() const {
return initialized_;
}
void Join() {
if (!Succeeded())
return;
pthread_join(thread_, NULL);
}
private:
static void *Main(void *arg) {
AutoPtr<ThreadData> data((ThreadData *)arg);
if (data->name[0]) {
#if defined(__linux__)
prctl(PR_SET_NAME, (unsigned long)data->name);
#elif defined(__APPLE__)
int (*fn)(const char *) = (int (*)(const char *))dlsym(RTLD_DEFAULT, "pthread_setname_np");
if (fn)
fn(data->name);
#endif
}
data->run->Run();
return NULL;
}
private:
bool initialized_;
pthread_t thread_;
};
} // namespace ke
#endif // _include_sourcepawn_thread_posix_h_

View File

@ -0,0 +1,241 @@
// 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_threads_
#define _include_sourcepawn_threads_
#include <assert.h>
#if defined(_MSC_VER)
# include <windows.h>
#else
# include <pthread.h>
#endif
#include <ke_utility.h>
// Thread primitives for SourcePawn.
//
// -- Mutexes --
//
// A Lockable is a mutual exclusion primitive. It can be owned by at most one
// thread at a time, and ownership blocks any other thread from taking taking
// ownership. Ownership must be acquired and released on the same thread.
// Lockables are not re-entrant.
//
// While a few classes support the Lockable interface, the simplest Lockable
// object that can be instantiated is a Mutex.
//
// -- Condition Variables --
//
// A ConditionVariable provides mutually exclusive access based on a
// condition ocurring. CVs provide two capabilities: Wait(), which will block
// until the condition is triggered, and Notify(), which signals any blocking
// thread that the condition has occurred.
//
// Condition variables have an underlying mutex lock. This lock must be
// acquired before calling Wait() or Notify(). It is automatically released
// once Wait begins blocking. This operation is atomic with respect to other
// threads and the mutex. For example, it is not possible for the lock to be
// acquired by another thread in between unlocking and blocking. Since Notify
// also requires the lock to be acquired, there is no risk of an event
// accidentally dissipating into thin air because it was sent before the other
// thread began blocking.
//
// When Wait() returns, the lock is automatically re-acquired. This operation
// is NOT atomic. In between waking up and re-acquiring the lock, another
// thread may steal the lock and issue another event. Applications must
// account for this. For example, a message pump should check that there are no
// messages left to process before blocking again.
//
// -- Threads --
//
// A Thread object, when created, spawns a new thread with the given callback
// (the callbacks must implement IRunnable). Threads have one method of
// interest, Join(), which will block until the thread's execution finishes.
// Deleting a thread object will free any operating system resources associated
// with that thread, if the thread has finished executing.
//
// Threads can fail to spawn; make sure to check Succeeded().
//
namespace ke {
// Abstraction for accessing the current thread.
#if defined(_MSC_VER)
typedef HANDLE ThreadId;
static inline ThreadId GetCurrentThreadId()
{
return GetCurrentThread();
}
#else
typedef pthread_t ThreadId;
static inline ThreadId GetCurrentThreadId()
{
return pthread_self();
}
#endif
// Classes which use non-reentrant, same-thread lock/unlock semantics should
// inherit from this and implement DoLock/DoUnlock.
class Lockable
{
public:
Lockable()
{
#if !defined(NDEBUG)
owner_ = 0;
#endif
}
virtual ~Lockable() {
}
bool TryLock() {
if (DoTryLock()) {
DebugSetLocked();
return true;
}
return false;
}
void Lock() {
assert(Owner() != GetCurrentThreadId());
DoLock();
DebugSetLocked();
}
void Unlock() {
assert(Owner() == GetCurrentThreadId());
DebugSetUnlocked();
DoUnlock();
}
void AssertCurrentThreadOwns() const {
assert(Owner() == GetCurrentThreadId());
}
#if !defined(NDEBUG)
bool Locked() const {
return owner_ != 0;
}
ThreadId Owner() const {
return owner_;
}
#endif
virtual bool DoTryLock() = 0;
virtual void DoLock() = 0;
virtual void DoUnlock() = 0;
protected:
void DebugSetUnlocked() {
#if !defined(NDEBUG)
owner_ = 0;
#endif
}
void DebugSetLocked() {
#if !defined(NDEBUG)
owner_ = GetCurrentThreadId();
#endif
}
protected:
#if !defined(NDEBUG)
ThreadId owner_;
#endif
};
// RAII for automatically locking and unlocking an object.
class AutoLock
{
public:
AutoLock(Lockable *lock)
: lock_(lock)
{
lock_->Lock();
}
~AutoLock() {
lock_->Unlock();
}
private:
Lockable *lock_;
};
class AutoTryLock
{
public:
AutoTryLock(Lockable *lock)
{
lock_ = lock->TryLock() ? lock : NULL;
}
~AutoTryLock() {
if (lock_)
lock_->Unlock();
}
private:
Lockable *lock_;
};
// RAII for automatically unlocking and relocking an object.
class AutoUnlock
{
public:
AutoUnlock(Lockable *lock)
: lock_(lock)
{
lock_->Unlock();
}
~AutoUnlock() {
lock_->Lock();
}
private:
Lockable *lock_;
};
enum WaitResult {
// Woke up because something happened.
Wait_Signaled,
// Woke up because nothing happened and a timeout was specified.
Wait_Timeout,
// Woke up, but because of an error.
Wait_Error
};
// This must be implemented in order to spawn a new thread.
class IRunnable
{
public:
virtual ~IRunnable() {
}
virtual void Run() = 0;
};
} // namespace ke
// Include the actual thread implementations.
#if defined(_MSC_VER)
# include "ke_thread_windows.h"
#else
# include "ke_thread_posix.h"
#endif
#endif // _include_sourcepawn_threads_

View File

@ -0,0 +1,144 @@
// 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_thread_windows_h_
#define _include_sourcepawn_thread_windows_h_
#include <windows.h>
namespace ke {
class CriticalSection : public Lockable
{
public:
CriticalSection() {
InitializeCriticalSection(&cs_);
}
~CriticalSection() {
DeleteCriticalSection(&cs_);
}
bool DoTryLock() {
return !!TryEnterCriticalSection(&cs_);
}
void DoLock() {
EnterCriticalSection(&cs_);
}
void DoUnlock() {
LeaveCriticalSection(&cs_);
}
private:
CRITICAL_SECTION cs_;
};
typedef CriticalSection Mutex;
// Currently, this class only supports single-listener CVs.
class ConditionVariable : public Lockable
{
public:
ConditionVariable() {
event_ = CreateEvent(NULL, FALSE, FALSE, NULL);
}
~ConditionVariable() {
CloseHandle(event_);
}
bool DoTryLock() {
return cs_.DoTryLock();
}
void DoLock() {
cs_.DoLock();
}
void DoUnlock() {
cs_.DoUnlock();
}
void Notify() {
AssertCurrentThreadOwns();
SetEvent(event_);
}
WaitResult Wait(size_t timeout_ms) {
// This will assert if the lock has not been acquired. We don't need to be
// atomic here, like pthread_cond_wait, because the event bit will stick
// until reset by a wait function.
Unlock();
DWORD rv = WaitForSingleObject(event_, timeout_ms);
Lock();
if (rv == WAIT_TIMEOUT)
return Wait_Timeout;
if (rv == WAIT_FAILED)
return Wait_Error;
return Wait_Signaled;
}
WaitResult Wait() {
return Wait(INFINITE);
}
private:
CriticalSection cs_;
HANDLE event_;
};
class Thread
{
public:
Thread(IRunnable *run, const char *name = NULL) {
thread_ = CreateThread(NULL, 0, Main, run, 0, NULL);
}
~Thread() {
if (!thread_)
return;
CloseHandle(thread_);
}
bool Succeeded() const {
return !!thread_;
}
void Join() {
if (!Succeeded())
return;
WaitForSingleObject(thread_, INFINITE);
}
private:
static DWORD WINAPI Main(LPVOID arg) {
((IRunnable *)arg)->Run();
return 0;
}
#pragma pack(push, 8)
struct ThreadNameInfo {
DWORD dwType;
LPCSTR szName;
DWORD dwThreadID;
DWORD dwFlags;
};
#pragma pack(pop)
private:
HANDLE thread_;
};
} // namespace ke
#endif // _include_sourcepawn_thread_windows_h_

View File

@ -48,34 +48,45 @@ static const size_t kKB = 1024;
static const size_t kMB = 1024 * kKB; static const size_t kMB = 1024 * kKB;
static const size_t kGB = 1024 * kMB; static const size_t kGB = 1024 * kMB;
template <typename T> T
ReturnAndVoid(T &t)
{
T saved = t;
t = T();
return saved;
}
template <typename T> template <typename T>
class AutoFree class AutoPtr
{ {
T *t_; T *t_;
public: public:
AutoFree() AutoPtr()
: t_(NULL) : t_(NULL)
{ {
} }
AutoFree(T *t) explicit AutoPtr(T *t)
: t_(t) : t_(t)
{ {
} }
~AutoFree() { ~AutoPtr() {
free(t_); delete t_;
} }
T *take() { T *take() {
T *t = t_; return ReturnAndVoid(t_);
t_ = NULL;
return t;
} }
T *operator *() const { T *operator *() const {
return t_; return t_;
} }
T *operator ->() const {
return t_;
}
operator T *() const {
return t_;
}
void operator =(T *t) { void operator =(T *t) {
if (t_) delete t_;
free(t_);
t_ = t; t_ = t;
} }
}; };
@ -289,14 +300,6 @@ Max(const T &t1, const T &t2)
return t1 > t2 ? t1 : t2; return t1 > t2 ? t1 : t2;
} }
template <typename T> T
ReturnAndVoid(T &t)
{
T saved = t;
t = T();
return saved;
}
#define OFFSETOF(Class, Member) reinterpret_cast<size_t>(&((Class *)NULL)->Member) #define OFFSETOF(Class, Member) reinterpret_cast<size_t>(&((Class *)NULL)->Member)
#if defined(_MSC_VER) #if defined(_MSC_VER)

View File

@ -40,7 +40,7 @@
/** SourcePawn Engine API Version */ /** SourcePawn Engine API Version */
#define SOURCEPAWN_ENGINE_API_VERSION 4 #define SOURCEPAWN_ENGINE_API_VERSION 4
#define SOURCEPAWN_ENGINE2_API_VERSION 4 #define SOURCEPAWN_ENGINE2_API_VERSION 5
#if !defined SOURCEMOD_BUILD #if !defined SOURCEMOD_BUILD
#define SOURCEMOD_BUILD #define SOURCEMOD_BUILD
@ -1286,6 +1286,15 @@ namespace SourcePawn
* @return New runtime, or NULL if not enough memory. * @return New runtime, or NULL if not enough memory.
*/ */
virtual IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory) =0; virtual IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory) =0;
/**
* @brief Initiates the watchdog timer with the specified timeout
* length. This cannot be called more than once.
*
* @param timeout Timeout, in ms.
* @return True on success, false on failure.
*/
virtual bool InstallWatchdogTimer(size_t timeout_ms) =0;
}; };
}; };

View File

@ -87,6 +87,7 @@ typedef uint32_t funcid_t; /**< Function index code */
#define SP_ERROR_CODE_TOO_NEW 27 /**< Code is too new for this VM */ #define SP_ERROR_CODE_TOO_NEW 27 /**< Code is too new for this VM */
#define SP_ERROR_OUT_OF_MEMORY 28 /**< Out of memory */ #define SP_ERROR_OUT_OF_MEMORY 28 /**< Out of memory */
#define SP_ERROR_INTEGER_OVERFLOW 29 /**< Integer overflow (-INT_MIN / -1) */ #define SP_ERROR_INTEGER_OVERFLOW 29 /**< Integer overflow (-INT_MIN / -1) */
#define SP_ERROR_TIMEOUT 30 /**< Timeout */
//Hey you! Update the string table if you add to the end of me! */ //Hey you! Update the string table if you add to the end of me! */
/********************************************** /**********************************************

View File

@ -12,6 +12,10 @@ compiler['CXXINCLUDES'].append(os.path.join(base, 'public', 'jit'))
compiler['CXXINCLUDES'].append(os.path.join(base, 'public', 'jit', 'x86')) compiler['CXXINCLUDES'].append(os.path.join(base, 'public', 'jit', 'x86'))
compiler['CXXINCLUDES'].append(os.path.join(base, 'knight', 'shared')) compiler['CXXINCLUDES'].append(os.path.join(base, 'knight', 'shared'))
if AMBuild.target['platform'] == 'linux':
compiler['POSTLINKFLAGS'].append('-lpthread')
compiler['POSTLINKFLAGS'].append('-lrt')
extension = AMBuild.AddJob('sourcepawn.jit.x86') extension = AMBuild.AddJob('sourcepawn.jit.x86')
binary = Cpp.LibraryBuilder('sourcepawn.jit.x86', AMBuild, extension, compiler) binary = Cpp.LibraryBuilder('sourcepawn.jit.x86', AMBuild, extension, compiler)
binary.AddSourceFiles('sourcepawn/jit', [ binary.AddSourceFiles('sourcepawn/jit', [
@ -23,6 +27,7 @@ binary.AddSourceFiles('sourcepawn/jit', [
'sp_vm_engine.cpp', 'sp_vm_engine.cpp',
'sp_vm_function.cpp', 'sp_vm_function.cpp',
'opcodes.cpp', 'opcodes.cpp',
'watchdog_timer.cpp',
'x86/jit_x86.cpp', 'x86/jit_x86.cpp',
'zlib/adler32.c', 'zlib/adler32.c',
'zlib/compress.c', 'zlib/compress.c',

View File

@ -37,10 +37,21 @@ BaseRuntime::BaseRuntime()
memset(m_CodeHash, 0, sizeof(m_CodeHash)); memset(m_CodeHash, 0, sizeof(m_CodeHash));
memset(m_DataHash, 0, sizeof(m_DataHash)); memset(m_DataHash, 0, sizeof(m_DataHash));
ke::AutoLock lock(g_Jit.Mutex());
g_Jit.RegisterRuntime(this);
} }
BaseRuntime::~BaseRuntime() BaseRuntime::~BaseRuntime()
{ {
// The watchdog thread takes the global JIT lock while it patches all
// runtimes. It is not enough to ensure that the unlinking of the runtime is
// protected; we cannot delete functions or code while the watchdog might be
// executing. Therefore, the entire destructor is guarded.
ke::AutoLock lock(g_Jit.Mutex());
g_Jit.DeregisterRuntime(this);
for (uint32_t i = 0; i < m_plugin.num_publics; i++) for (uint32_t i = 0; i < m_plugin.num_publics; i++)
delete m_PubFuncs[i]; delete m_PubFuncs[i];
delete [] m_PubFuncs; delete [] m_PubFuncs;

View File

@ -4,6 +4,7 @@
#include <sp_vm_api.h> #include <sp_vm_api.h>
#include <ke_vector.h> #include <ke_vector.h>
#include <ke_inline_list.h>
#include "jit_shared.h" #include "jit_shared.h"
#include "sp_vm_function.h" #include "sp_vm_function.h"
@ -33,7 +34,9 @@ struct floattbl_t
}; };
/* Jit wants fast access to this so we expose things as public */ /* Jit wants fast access to this so we expose things as public */
class BaseRuntime : public SourcePawn::IPluginRuntime class BaseRuntime
: public SourcePawn::IPluginRuntime,
public InlineListNode<BaseRuntime>
{ {
public: public:
BaseRuntime(); BaseRuntime();
@ -73,6 +76,13 @@ class BaseRuntime : public SourcePawn::IPluginRuntime
return &m_plugin; return &m_plugin;
} }
size_t NumJitFunctions() const {
return m_JitFunctions.length();
}
JitFunction *GetJitFunction(size_t i) const {
return m_JitFunctions[i];
}
private: private:
void SetupFloatNativeRemapping(); void SetupFloatNativeRemapping();
@ -103,3 +113,4 @@ class BaseRuntime : public SourcePawn::IPluginRuntime
}; };
#endif //_INCLUDE_SOURCEPAWN_JIT_RUNTIME_H_ #endif //_INCLUDE_SOURCEPAWN_JIT_RUNTIME_H_

View File

@ -19,6 +19,7 @@ OBJECTS = dll_exports.cpp \
BaseRuntime.cpp \ BaseRuntime.cpp \
jit_function.cpp \ jit_function.cpp \
opcodes.cpp \ opcodes.cpp \
watchdog_timer.cpp \
md5/md5.cpp \ md5/md5.cpp \
zlib/adler32.c \ zlib/adler32.c \
zlib/compress.c \ zlib/compress.c \
@ -47,7 +48,7 @@ CXX_GCC4_FLAGS = -fvisibility-inlines-hidden
CXX = c++ CXX = c++
CC = cc CC = cc
LINK = -m32 -lm LINK = -m32 -lm -lpthread -lrt
INCLUDE = -I. -I.. -I$(SMSDK)/public -I$(SMSDK)/public/jit -I$(SMSDK)/public/jit/x86 \ INCLUDE = -I. -I.. -I$(SMSDK)/public -I$(SMSDK)/public/jit -I$(SMSDK)/public/jit/x86 \
-I$(SMSDK)/public/sourcepawn -I$(MMSOURCE17)/core/sourcehook -I$(SMSDK)/knight/shared -Ix86 -I$(SMSDK)/public/sourcepawn -I$(MMSOURCE17)/core/sourcehook -I$(SMSDK)/knight/shared -Ix86

View File

@ -100,7 +100,7 @@ public:
while (error->GetTraceInfo(&stk_info)) while (error->GetTraceInfo(&stk_info))
{ {
fprintf(stderr, fprintf(stderr,
" [%d] Line %d, %s::%s()", " [%d] Line %d, %s::%s()\n",
i++, i++,
stk_info.line, stk_info.line,
stk_info.filename, stk_info.filename,
@ -221,6 +221,7 @@ int main(int argc, char **argv)
ShellDebugListener debug; ShellDebugListener debug;
g_engine1.SetDebugListener(&debug); g_engine1.SetDebugListener(&debug);
g_engine2.InstallWatchdogTimer(5000);
int errcode = Execute(argv[1]); int errcode = Execute(argv[1]);

View File

@ -1,3 +1,4 @@
// vim: set ts=4 sw=4 tw=99 noet:
#include <sourcemod_version.h> #include <sourcemod_version.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -7,6 +8,7 @@
#include "zlib/zlib.h" #include "zlib/zlib.h"
#include "BaseRuntime.h" #include "BaseRuntime.h"
#include "sp_vm_engine.h" #include "sp_vm_engine.h"
#include "watchdog_timer.h"
using namespace SourcePawn; using namespace SourcePawn;
@ -210,3 +212,9 @@ IPluginRuntime *SourcePawnEngine2::CreateEmptyRuntime(const char *name, uint32_t
return rt; return rt;
} }
bool SourcePawnEngine2::InstallWatchdogTimer(size_t timeout_ms)
{
return g_WatchdogTimer.Initialize(timeout_ms);
}

View File

@ -26,6 +26,7 @@ namespace SourcePawn
bool Initialize(); bool Initialize();
void Shutdown(); void Shutdown();
IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory); IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory);
bool InstallWatchdogTimer(size_t timeout_ms);
public: public:
IProfiler *GetProfiler(); IProfiler *GetProfiler();
private: private:

View File

@ -1,23 +1,31 @@
// vim: set ts=8 ts=2 sw=2 tw=99 et:
#include "jit_function.h" #include "jit_function.h"
#include "sp_vm_engine.h" #include "sp_vm_engine.h"
#include "jit_x86.h" #include "jit_x86.h"
JitFunction::JitFunction(void *entry_addr, cell_t pcode_offs) JitFunction::JitFunction(void *entry_addr, cell_t pcode_offs, LoopEdge *edges, uint32_t nedges)
: m_pEntryAddr(entry_addr), m_PcodeOffs(pcode_offs) : m_pEntryAddr(entry_addr),
m_PcodeOffs(pcode_offs),
edges_(edges),
nedges_(nedges)
{ {
} }
JitFunction::~JitFunction() JitFunction::~JitFunction()
{ {
delete [] edges_;
g_Jit.FreeCode(m_pEntryAddr); g_Jit.FreeCode(m_pEntryAddr);
} }
void *JitFunction::GetEntryAddress() void *
JitFunction::GetEntryAddress() const
{ {
return m_pEntryAddr; return m_pEntryAddr;
} }
cell_t JitFunction::GetPCodeAddress() cell_t
JitFunction::GetPCodeAddress() const
{ {
return m_PcodeOffs; return m_PcodeOffs;
} }

View File

@ -1,19 +1,38 @@
// vim: set ts=8 sts=2 sw=2 tw=99 et:
#ifndef _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_ #ifndef _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_
#define _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_ #define _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_
#include <sp_vm_types.h> #include <sp_vm_types.h>
#include <ke_vector.h>
struct LoopEdge
{
uint32_t offset;
int32_t disp32;
};
class JitFunction class JitFunction
{ {
public: public:
JitFunction(void *entry_addr, cell_t pcode_offs); JitFunction(void *entry_addr, cell_t pcode_offs, LoopEdge *edges, uint32_t nedges);
~JitFunction(); ~JitFunction();
public:
void *GetEntryAddress(); public:
cell_t GetPCodeAddress(); void *GetEntryAddress() const;
private: cell_t GetPCodeAddress() const;
uint32_t NumLoopEdges() const {
return nedges_;
}
const LoopEdge &GetLoopEdge(size_t i) const {
assert(i < nedges_);
return edges_[i];
}
private:
void *m_pEntryAddr; void *m_pEntryAddr;
cell_t m_PcodeOffs; cell_t m_PcodeOffs;
LoopEdge *edges_;
uint32_t nedges_;
}; };
#endif //_INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_ #endif //_INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_

View File

@ -36,6 +36,7 @@
#include "sp_vm_api.h" #include "sp_vm_api.h"
#include "sp_vm_basecontext.h" #include "sp_vm_basecontext.h"
#include "sp_vm_engine.h" #include "sp_vm_engine.h"
#include "watchdog_timer.h"
#include "x86/jit_x86.h" #include "x86/jit_x86.h"
using namespace SourcePawn; using namespace SourcePawn;
@ -554,6 +555,9 @@ int BaseContext::Execute2(IPluginFunction *function, const cell_t *params, unsig
fnid = function->GetFunctionID(); fnid = function->GetFunctionID();
if (!g_WatchdogTimer.HandleInterrupt())
return SP_ERROR_TIMEOUT;
if (fnid & 1) if (fnid & 1)
{ {
public_id = fnid >> 1; public_id = fnid >> 1;
@ -674,6 +678,9 @@ int BaseContext::Execute2(IPluginFunction *function, const cell_t *params, unsig
} }
} }
if (ir == SP_ERROR_TIMEOUT)
g_WatchdogTimer.NotifyTimeoutReceived();
if (ir != SP_ERROR_NONE) if (ir != SP_ERROR_NONE)
{ {
g_engine1.ReportError(m_pRuntime, ir, m_MsgCache, save_rp); g_engine1.ReportError(m_pRuntime, ir, m_MsgCache, save_rp);

View File

@ -1,5 +1,5 @@
/** /**
* vim: set ts=4 : * vim: set ts=4 sw=4 tw=99 et:
* ============================================================================= * =============================================================================
* SourcePawn * SourcePawn
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
@ -58,7 +58,7 @@ SourcePawnEngine g_engine1;
using namespace SourcePawn; using namespace SourcePawn;
#define ERROR_MESSAGE_MAX 29 #define ERROR_MESSAGE_MAX 30
static const char *g_ErrorMsgTable[] = static const char *g_ErrorMsgTable[] =
{ {
NULL, NULL,
@ -90,7 +90,8 @@ static const char *g_ErrorMsgTable[] =
"Plugin format is too old", "Plugin format is too old",
"Plugin format is too new", "Plugin format is too new",
"Out of memory", "Out of memory",
"Integer overflow" "Integer overflow",
"Script execution timed out"
}; };
const char *SourcePawnEngine::GetErrorString(int error) const char *SourcePawnEngine::GetErrorString(int error)

View File

@ -0,0 +1,164 @@
// vim: set ts=8 sts=2 sw=2 tw=99 et:
//
// This file is part of SourcePawn.
//
// SourcePawn is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// SourcePawn is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
#include "watchdog_timer.h"
#include "x86/jit_x86.h"
#include <string.h>
WatchdogTimer g_WatchdogTimer;
WatchdogTimer::WatchdogTimer()
: terminate_(false),
mainthread_(ke::GetCurrentThreadId()),
last_frame_id_(0),
second_timeout_(false),
timedout_(false)
{
}
WatchdogTimer::~WatchdogTimer()
{
Shutdown();
}
bool
WatchdogTimer::Initialize(size_t timeout_ms)
{
if (thread_)
return false;
timeout_ms_ = timeout_ms;
thread_ = new ke::Thread(this, "SM Watchdog");
if (!thread_->Succeeded())
return false;
return true;
}
void
WatchdogTimer::Shutdown()
{
if (terminate_ || !thread_)
return;
{
ke::AutoLock lock(&cv_);
terminate_ = true;
cv_.Notify();
}
thread_->Join();
thread_ = NULL;
}
void
WatchdogTimer::Run()
{
ke::AutoLock lock(&cv_);
// Initialize the frame id, so we don't have to wait longer on startup.
last_frame_id_ = g_Jit.FrameId();
while (!terminate_) {
ke::WaitResult rv = cv_.Wait(timeout_ms_ / 2);
if (terminate_)
return;
if (rv == ke::Wait_Error)
return;
if (rv != ke::Wait_Timeout)
continue;
// We reached a timeout. If the current frame is not equal to the last
// frame, then we assume the server is still moving enough to process
// frames. We also make sure JIT code is actually on the stack, since
// our concept of frames won't move if JIT code is not running.
//
// Note that it's okay if these two race: it's just a heuristic, and
// worst case, we'll miss something that might have timed out but
// ended up resuming.
uintptr_t frame_id = g_Jit.FrameId();
if (frame_id != last_frame_id_ || !g_Jit.RunningCode()) {
last_frame_id_ = frame_id;
second_timeout_ = false;
continue;
}
// We woke up with the same frame. We might be timing out. Wait another
// time to see if so.
if (!second_timeout_) {
second_timeout_ = true;
continue;
}
{
// Prevent the JIT from linking or destroying runtimes and functions.
ke::AutoLock lock(g_Jit.Mutex());
// Set the timeout notification bit. If this is detected before any patched
// JIT backedges are reached, the main thread will attempt to acquire the
// monitor lock, and block until we call Wait().
timedout_ = true;
// Patch all jumps. This can race with the main thread's execution since
// all code writes are 32-bit integer instruction operands, which are
// guaranteed to be atomic on x86.
g_Jit.PatchAllJumpsForTimeout();
}
// The JIT will be free to compile new functions while we wait, but it will
// see the timeout bit set above and immediately bail out.
cv_.Wait();
second_timeout_ = false;
// Reset the last frame ID to something unlikely to be chosen again soon.
last_frame_id_--;
// It's possible that Shutdown() raced with the timeout, and if so, we
// must leave now to prevent a deadlock.
if (terminate_)
return;
}
}
bool
WatchdogTimer::NotifyTimeoutReceived()
{
// We are guaranteed that the watchdog thread is waiting for our
// notification, and is therefore blocked. We take the JIT lock
// anyway for sanity.
{
ke::AutoLock lock(g_Jit.Mutex());
g_Jit.UnpatchAllJumpsFromTimeout();
}
timedout_ = false;
// Wake up the watchdog thread, it's okay to keep processing now.
ke::AutoLock lock(&cv_);
cv_.Notify();
return false;
}
bool
WatchdogTimer::HandleInterrupt()
{
if (timedout_)
return NotifyTimeoutReceived();
return true;
}

View File

@ -0,0 +1,61 @@
// 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_watchdog_timer_posix_h_
#define _include_sourcepawn_watchdog_timer_posix_h_
#include <stddef.h>
#include <stdint.h>
#include <ke_thread_utils.h>
typedef bool (*WatchdogCallback)();
class WatchdogTimer : public ke::IRunnable
{
public:
WatchdogTimer();
~WatchdogTimer();
bool Initialize(size_t timeout_ms);
void Shutdown();
// Called from main thread.
bool NotifyTimeoutReceived();
bool HandleInterrupt();
private:
// Watchdog thread.
void Run();
private:
bool terminate_;
size_t timeout_ms_;
ke::ThreadId mainthread_;
ke::AutoPtr<ke::Thread> thread_;
ke::ConditionVariable cv_;
// Accessed only on the watchdog thread.
uintptr_t last_frame_id_;
bool second_timeout_;
// Accessed only on the main thread.
bool timedout_;
};
extern WatchdogTimer g_WatchdogTimer;
#endif // _include_sourcepawn_watchdog_timer_posix_h_

View File

@ -37,6 +37,7 @@
#include "../engine2.h" #include "../engine2.h"
#include "../BaseRuntime.h" #include "../BaseRuntime.h"
#include "../sp_vm_basecontext.h" #include "../sp_vm_basecontext.h"
#include "watchdog_timer.h"
using namespace Knight; using namespace Knight;
@ -50,7 +51,7 @@ JITX86 g_Jit;
KeCodeCache *g_pCodeCache = NULL; KeCodeCache *g_pCodeCache = NULL;
ISourcePawnEngine *engine = &g_engine1; ISourcePawnEngine *engine = &g_engine1;
static inline void * static inline uint8_t *
LinkCode(AssemblerX86 &masm) LinkCode(AssemblerX86 &masm)
{ {
if (masm.outOfMemory()) if (masm.outOfMemory())
@ -61,7 +62,7 @@ LinkCode(AssemblerX86 &masm)
return NULL; return NULL;
masm.emitToExecutableMemory(code); masm.emitToExecutableMemory(code);
return code; return reinterpret_cast<uint8_t *>(code);
} }
static inline ConditionCode static inline ConditionCode
@ -249,7 +250,7 @@ GenerateArray(BaseRuntime *rt, uint32_t argc, cell_t *argv, int autozero)
return err; return err;
cell_t *base = reinterpret_cast<cell_t *>(rt->plugin()->memory + ctx->hp); cell_t *base = reinterpret_cast<cell_t *>(rt->plugin()->memory + ctx->hp);
cell_t offs = GenerateArrayIndirectionVectors(base, argv, argc, autozero); cell_t offs = GenerateArrayIndirectionVectors(base, argv, argc, !!autozero);
assert(size_t(offs) == cells); assert(size_t(offs) == cells);
argv[argc - 1] = ctx->hp; argv[argc - 1] = ctx->hp;
@ -314,6 +315,12 @@ GetFunctionName(const sp_plugin_t *plugin, uint32_t offs)
static int static int
CompileFromThunk(BaseRuntime *runtime, cell_t pcode_offs, void **addrp, char *pc) CompileFromThunk(BaseRuntime *runtime, cell_t pcode_offs, void **addrp, char *pc)
{ {
// If the watchdog timer has declared a timeout, we must process it now,
// and possibly refuse to compile, since otherwise we will compile a
// function that is not patched for timeouts.
if (!g_WatchdogTimer.HandleInterrupt())
return SP_ERROR_TIMEOUT;
JitFunction *fn = runtime->GetJittedFunctionByOffset(pcode_offs); JitFunction *fn = runtime->GetJittedFunctionByOffset(pcode_offs);
if (!fn) { if (!fn) {
int err; int err;
@ -452,13 +459,19 @@ Compiler::emit(int *errp)
emitCallThunks(); emitCallThunks();
emitErrorPaths(); emitErrorPaths();
void *code = LinkCode(masm); uint8_t *code = LinkCode(masm);
if (!code) { if (!code) {
*errp = SP_ERROR_OUT_OF_MEMORY; *errp = SP_ERROR_OUT_OF_MEMORY;
return NULL; return NULL;
} }
return new JitFunction(code, pcode_start_); LoopEdge *edges = new LoopEdge[backward_jumps_.length()];
for (size_t i = 0; i < backward_jumps_.length(); i++) {
edges[i].offset = backward_jumps_[i];
edges[i].disp32 = *reinterpret_cast<int32_t *>(code + edges[i].offset - 4);
}
return new JitFunction(code, pcode_start_, edges, backward_jumps_.length());
} }
bool bool
@ -1228,7 +1241,12 @@ Compiler::emitOp(OPCODE op)
Label *target = labelAt(readCell()); Label *target = labelAt(readCell());
if (!target) if (!target)
return false; return false;
if (target->bound()) {
__ jmp32(target);
backward_jumps_.append(masm.pc());
} else {
__ jmp(target); __ jmp(target);
}
break; break;
} }
@ -1240,7 +1258,12 @@ Compiler::emitOp(OPCODE op)
if (!target) if (!target)
return false; return false;
__ testl(pri, pri); __ testl(pri, pri);
if (target->bound()) {
__ j32(cc, target);
backward_jumps_.append(masm.pc());
} else {
__ j(cc, target); __ j(cc, target);
}
break; break;
} }
@ -1256,7 +1279,12 @@ Compiler::emitOp(OPCODE op)
return false; return false;
ConditionCode cc = OpToCondition(op); ConditionCode cc = OpToCondition(op);
__ cmpl(pri, alt); __ cmpl(pri, alt);
if (target->bound()) {
__ j32(cc, target);
backward_jumps_.append(masm.pc());
} else {
__ j(cc, target); __ j(cc, target);
}
break; break;
} }
@ -1755,7 +1783,7 @@ Compiler::emitErrorPaths()
typedef int (*JIT_EXECUTE)(sp_context_t *ctx, uint8_t *memory, void *code); typedef int (*JIT_EXECUTE)(sp_context_t *ctx, uint8_t *memory, void *code);
static void * static void *
GenerateEntry(void **retp) GenerateEntry(void **retp, void **timeoutp)
{ {
AssemblerX86 masm; AssemblerX86 masm;
@ -1813,11 +1841,17 @@ GenerateEntry(void **retp)
__ movl(ecx, Operand(ebp, 8 + 4 * 0)); // ret-path expects ecx = ctx __ movl(ecx, Operand(ebp, 8 + 4 * 0)); // ret-path expects ecx = ctx
__ jmp(&ret); __ jmp(&ret);
Label timeout;
__ bind(&timeout);
__ movl(eax, SP_ERROR_TIMEOUT);
__ jmp(&error);
void *code = LinkCode(masm); void *code = LinkCode(masm);
if (!code) if (!code)
return NULL; return NULL;
*retp = reinterpret_cast<uint8_t *>(code) + error.offset(); *retp = reinterpret_cast<uint8_t *>(code) + error.offset();
*timeoutp = reinterpret_cast<uint8_t *>(code) + timeout.offset();
return code; return code;
} }
@ -1841,11 +1875,12 @@ JITX86::JITX86()
m_pJitEntry = NULL; m_pJitEntry = NULL;
} }
bool JITX86::InitializeJIT() bool
JITX86::InitializeJIT()
{ {
g_pCodeCache = KE_CreateCodeCache(); g_pCodeCache = KE_CreateCodeCache();
m_pJitEntry = GenerateEntry(&m_pJitReturn); m_pJitEntry = GenerateEntry(&m_pJitReturn, &m_pJitTimeout);
if (!m_pJitEntry) if (!m_pJitEntry)
return false; return false;
@ -1873,6 +1908,11 @@ JITX86::CompileFunction(BaseRuntime *prt, cell_t pcode_offs, int *err)
JitFunction *fun = cc.emit(err); JitFunction *fun = cc.emit(err);
if (!fun) if (!fun)
return NULL; return NULL;
// Grab the lock before linking code in, since the watchdog timer will look
// at this list on another thread.
ke::AutoLock lock(g_Jit.Mutex());
prt->AddJittedFunction(fun); prt->AddJittedFunction(fun);
return fun; return fun;
} }
@ -1919,33 +1959,39 @@ JITX86::CreateFakeNative(SPVM_FAKENATIVE_FUNC callback, void *pData)
return (SPVM_NATIVE_FUNC)LinkCode(masm); return (SPVM_NATIVE_FUNC)LinkCode(masm);
} }
void JITX86::DestroyFakeNative(SPVM_NATIVE_FUNC func) void
JITX86::DestroyFakeNative(SPVM_NATIVE_FUNC func)
{ {
KE_FreeCode(g_pCodeCache, (void *)func); KE_FreeCode(g_pCodeCache, (void *)func);
} }
ICompilation *JITX86::StartCompilation() ICompilation *
JITX86::StartCompilation()
{ {
return new CompData; return new CompData;
} }
ICompilation *JITX86::StartCompilation(BaseRuntime *runtime) ICompilation *
JITX86::StartCompilation(BaseRuntime *runtime)
{ {
return new CompData; return new CompData;
} }
void CompData::Abort() void
CompData::Abort()
{ {
delete this; delete this;
} }
void JITX86::FreeContextVars(sp_context_t *ctx) void
JITX86::FreeContextVars(sp_context_t *ctx)
{ {
free(((tracker_t *)(ctx->vm[JITVARS_TRACKER]))->pBase); free(((tracker_t *)(ctx->vm[JITVARS_TRACKER]))->pBase);
delete (tracker_t *)ctx->vm[JITVARS_TRACKER]; delete (tracker_t *)ctx->vm[JITVARS_TRACKER];
} }
bool CompData::SetOption(const char *key, const char *val) bool
CompData::SetOption(const char *key, const char *val)
{ {
if (strcmp(key, SP_JITCONF_DEBUG) == 0) if (strcmp(key, SP_JITCONF_DEBUG) == 0)
return true; return true;
@ -1962,7 +2008,8 @@ bool CompData::SetOption(const char *key, const char *val)
return false; return false;
} }
int JITX86::InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result) int
JITX86::InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result)
{ {
sp_context_t *ctx = runtime->GetBaseContext()->GetCtx(); sp_context_t *ctx = runtime->GetBaseContext()->GetCtx();
@ -1970,18 +2017,75 @@ int JITX86::InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result
ctx->cip = fn->GetPCodeAddress(); ctx->cip = fn->GetPCodeAddress();
JIT_EXECUTE pfn = (JIT_EXECUTE)m_pJitEntry; JIT_EXECUTE pfn = (JIT_EXECUTE)m_pJitEntry;
if (level_++ == 0)
frame_id_++;
int err = pfn(ctx, runtime->plugin()->memory, fn->GetEntryAddress()); int err = pfn(ctx, runtime->plugin()->memory, fn->GetEntryAddress());
level_--;
*result = ctx->rval; *result = ctx->rval;
return err; return err;
} }
void *JITX86::AllocCode(size_t size) void *
JITX86::AllocCode(size_t size)
{ {
return Knight::KE_AllocCode(g_pCodeCache, size); return Knight::KE_AllocCode(g_pCodeCache, size);
} }
void JITX86::FreeCode(void *code) void
JITX86::FreeCode(void *code)
{ {
KE_FreeCode(g_pCodeCache, code); KE_FreeCode(g_pCodeCache, code);
} }
void
JITX86::RegisterRuntime(BaseRuntime *rt)
{
mutex_.AssertCurrentThreadOwns();
runtimes_.insert(rt);
}
void
JITX86::DeregisterRuntime(BaseRuntime *rt)
{
mutex_.AssertCurrentThreadOwns();
runtimes_.erase(rt);
}
void
JITX86::PatchAllJumpsForTimeout()
{
mutex_.AssertCurrentThreadOwns();
for (InlineList<BaseRuntime>::iterator iter = runtimes_.begin(); iter != runtimes_.end(); iter++) {
BaseRuntime *rt = *iter;
for (size_t i = 0; i < rt->NumJitFunctions(); i++) {
JitFunction *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(m_pJitTimeout) - intptr_t(base + e.offset);
*reinterpret_cast<int32_t *>(base + e.offset - 4) = diff;
}
}
}
}
void
JITX86::UnpatchAllJumpsFromTimeout()
{
mutex_.AssertCurrentThreadOwns();
for (InlineList<BaseRuntime>::iterator iter = runtimes_.begin(); iter != runtimes_.end(); iter++) {
BaseRuntime *rt = *iter;
for (size_t i = 0; i < rt->NumJitFunctions(); i++) {
JitFunction *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;
}
}
}
}

View File

@ -1,34 +1,19 @@
/** // vim: set ts=8 sts=2 sw=2 tw=99 et:
* vim: set ts=4 sw=4 tw=99 et: //
* ============================================================================= // This file is part of SourcePawn.
* SourceMod //
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. // 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
* This program is free software; you can redistribute it and/or modify it under // (at your option) any later version.
* the terms of the GNU General Public License, version 3.0, as published by the //
* Free Software Foundation. // SourcePawn is distributed in the hope that it will be useful,
* // but WITHOUT ANY WARRANTY; without even the implied warranty of
* This program is distributed in the hope that it will be useful, but WITHOUT // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // GNU General Public License for more details.
* 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/>.
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEPAWN_JIT_X86_H_ #ifndef _INCLUDE_SOURCEPAWN_JIT_X86_H_
#define _INCLUDE_SOURCEPAWN_JIT_X86_H_ #define _INCLUDE_SOURCEPAWN_JIT_X86_H_
@ -42,6 +27,7 @@
#include "sp_vm_basecontext.h" #include "sp_vm_basecontext.h"
#include "jit_function.h" #include "jit_function.h"
#include "opcodes.h" #include "opcodes.h"
#include <ke_thread_utils.h>
using namespace SourcePawn; using namespace SourcePawn;
@ -155,6 +141,7 @@ class Compiler
cell_t *cip_; cell_t *cip_;
cell_t *code_end_; cell_t *code_end_;
Label *jump_map_; Label *jump_map_;
ke::Vector<size_t> backward_jumps_;
// Errors // Errors
Label error_bounds_; Label error_bounds_;
@ -188,6 +175,11 @@ class JITX86
ICompilation *ApplyOptions(ICompilation *_IN, ICompilation *_OUT); ICompilation *ApplyOptions(ICompilation *_IN, ICompilation *_OUT);
int InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result); int InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result);
void RegisterRuntime(BaseRuntime *rt);
void DeregisterRuntime(BaseRuntime *rt);
void PatchAllJumpsForTimeout();
void UnpatchAllJumpsFromTimeout();
public: public:
ExternalAddress GetUniversalReturn() { ExternalAddress GetUniversalReturn() {
return ExternalAddress(m_pJitReturn); return ExternalAddress(m_pJitReturn);
@ -195,9 +187,24 @@ class JITX86
void *AllocCode(size_t size); void *AllocCode(size_t size);
void FreeCode(void *code); void FreeCode(void *code);
uintptr_t FrameId() const {
return frame_id_;
}
bool RunningCode() const {
return level_ != 0;
}
ke::Mutex *Mutex() {
return &mutex_;
}
private: private:
void *m_pJitEntry; /* Entry function */ void *m_pJitEntry; /* Entry function */
void *m_pJitReturn; /* Universal return address */ void *m_pJitReturn; /* Universal return address */
void *m_pJitTimeout; /* Universal timeout address */
InlineList<BaseRuntime> runtimes_;
uintptr_t frame_id_;
uintptr_t level_;
ke::Mutex mutex_;
}; };
const Register pri = eax; const Register pri = eax;
@ -211,3 +218,4 @@ extern Knight::KeCodeCache *g_pCodeCache;
extern JITX86 g_Jit; extern JITX86 g_Jit;
#endif //_INCLUDE_SOURCEPAWN_JIT_X86_H_ #endif //_INCLUDE_SOURCEPAWN_JIT_X86_H_