diff --git a/core/sourcemod.cpp b/core/sourcemod.cpp index 9872a4d9..7e8cf3b2 100644 --- a/core/sourcemod.cpp +++ b/core/sourcemod.cpp @@ -1,5 +1,5 @@ /** - * vim: set ts=4 sw=4 : + * vim: set ts=4 sw=4 tw=99 noet: * ============================================================================= * SourceMod * 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); } + + 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; diff --git a/public/jit/assembler.h b/public/jit/assembler.h index d37a3ab7..c156cd00 100644 --- a/public/jit/assembler.h +++ b/public/jit/assembler.h @@ -64,6 +64,11 @@ class Assembler return pos_ - buffer_; } + // Current offset into the code stream. + uint32_t pc() const { + return uint32_t(pos_ - buffer_); + } + protected: void writeByte(uint8_t byte) { write(byte); @@ -125,11 +130,6 @@ class Assembler return int32_t(pos_ - buffer_); } - // pc is the unsigned version of position(). - uint32_t pc() const { - return uint32_t(pos_ - buffer_); - } - protected: void assertCanWrite(size_t bytes) { assert(pos_ + bytes <= end_); diff --git a/public/jit/x86/assembler-x86.h b/public/jit/x86/assembler-x86.h index f3bb7264..86f25205 100644 --- a/public/jit/x86/assembler-x86.h +++ b/public/jit/x86/assembler-x86.h @@ -640,6 +640,10 @@ class AssemblerX86 : public Assembler emit2(0xdc, 0xc0 + src.code); } + void jmp32(Label *dest) { + emit1(0xe9); + emitJumpTarget(dest); + } void jmp(Label *dest) { int8_t d8; if (canEmitSmallJump(dest, &d8)) { @@ -655,6 +659,10 @@ class AssemblerX86 : public Assembler void jmp(const Operand &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) { int8_t d8; if (canEmitSmallJump(dest, &d8)) { diff --git a/public/sourcepawn/ke_inline_list.h b/public/sourcepawn/ke_inline_list.h new file mode 100644 index 00000000..18b0399b --- /dev/null +++ b/public/sourcepawn/ke_inline_list.h @@ -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 . + * + * 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 . + */ +#ifndef _include_sourcepawn_inline_list_h_ +#define _include_sourcepawn_inline_list_h_ + +#include + +template class InlineList; + +template +class InlineListNode +{ + friend class InlineList; + + public: + InlineListNode() + : next_(NULL), + prev_(NULL) + { + } + + InlineListNode(InlineListNode *next, InlineListNode *prev) + : next_(next), + prev_(prev) + { + } + + protected: + InlineListNode *next_; + InlineListNode *prev_; +}; + +template +class InlineList +{ + typedef InlineListNode 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(iter_); + } + T * operator ->() { + return static_cast(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_ + diff --git a/public/sourcepawn/ke_thread_posix.h b/public/sourcepawn/ke_thread_posix.h new file mode 100644 index 00000000..75e8ed4a --- /dev/null +++ b/public/sourcepawn/ke_thread_posix.h @@ -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 . +#ifndef _include_sourcepawn_thread_posix_h_ +#define _include_sourcepawn_thread_posix_h_ + +#include +#include +#include +#include +#if defined(__linux__) +# include +#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 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_ + diff --git a/public/sourcepawn/ke_thread_utils.h b/public/sourcepawn/ke_thread_utils.h new file mode 100644 index 00000000..7ff3d06e --- /dev/null +++ b/public/sourcepawn/ke_thread_utils.h @@ -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 . +#ifndef _include_sourcepawn_threads_ +#define _include_sourcepawn_threads_ + +#include +#if defined(_MSC_VER) +# include +#else +# include +#endif +#include + +// 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_ + diff --git a/public/sourcepawn/ke_thread_windows.h b/public/sourcepawn/ke_thread_windows.h new file mode 100644 index 00000000..e82e951e --- /dev/null +++ b/public/sourcepawn/ke_thread_windows.h @@ -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 . +#ifndef _include_sourcepawn_thread_windows_h_ +#define _include_sourcepawn_thread_windows_h_ + +#include + +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_ diff --git a/public/sourcepawn/ke_utility.h b/public/sourcepawn/ke_utility.h index 0539f400..12f97050 100644 --- a/public/sourcepawn/ke_utility.h +++ b/public/sourcepawn/ke_utility.h @@ -48,34 +48,45 @@ static const size_t kKB = 1024; static const size_t kMB = 1024 * kKB; static const size_t kGB = 1024 * kMB; +template T +ReturnAndVoid(T &t) +{ + T saved = t; + t = T(); + return saved; +} + template -class AutoFree +class AutoPtr { T *t_; public: - AutoFree() + AutoPtr() : t_(NULL) { } - AutoFree(T *t) + explicit AutoPtr(T *t) : t_(t) { } - ~AutoFree() { - free(t_); + ~AutoPtr() { + delete t_; } T *take() { - T *t = t_; - t_ = NULL; - return t; + return ReturnAndVoid(t_); } T *operator *() const { return t_; } + T *operator ->() const { + return t_; + } + operator T *() const { + return t_; + } void operator =(T *t) { - if (t_) - free(t_); + delete t_; t_ = t; } }; @@ -289,14 +300,6 @@ Max(const T &t1, const T &t2) return t1 > t2 ? t1 : t2; } -template T -ReturnAndVoid(T &t) -{ - T saved = t; - t = T(); - return saved; -} - #define OFFSETOF(Class, Member) reinterpret_cast(&((Class *)NULL)->Member) #if defined(_MSC_VER) diff --git a/public/sourcepawn/sp_vm_api.h b/public/sourcepawn/sp_vm_api.h index 2f43b28c..f152b071 100644 --- a/public/sourcepawn/sp_vm_api.h +++ b/public/sourcepawn/sp_vm_api.h @@ -40,7 +40,7 @@ /** SourcePawn Engine API Version */ #define SOURCEPAWN_ENGINE_API_VERSION 4 -#define SOURCEPAWN_ENGINE2_API_VERSION 4 +#define SOURCEPAWN_ENGINE2_API_VERSION 5 #if !defined SOURCEMOD_BUILD #define SOURCEMOD_BUILD @@ -1286,6 +1286,15 @@ namespace SourcePawn * @return New runtime, or NULL if not enough memory. */ 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; }; }; diff --git a/public/sourcepawn/sp_vm_types.h b/public/sourcepawn/sp_vm_types.h index fc6c9729..22b0de07 100644 --- a/public/sourcepawn/sp_vm_types.h +++ b/public/sourcepawn/sp_vm_types.h @@ -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_OUT_OF_MEMORY 28 /**< Out of memory */ #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! */ /********************************************** diff --git a/sourcepawn/jit/AMBuilder b/sourcepawn/jit/AMBuilder index aa919c72..a1fa16ca 100644 --- a/sourcepawn/jit/AMBuilder +++ b/sourcepawn/jit/AMBuilder @@ -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, 'knight', 'shared')) +if AMBuild.target['platform'] == 'linux': + compiler['POSTLINKFLAGS'].append('-lpthread') + compiler['POSTLINKFLAGS'].append('-lrt') + extension = AMBuild.AddJob('sourcepawn.jit.x86') binary = Cpp.LibraryBuilder('sourcepawn.jit.x86', AMBuild, extension, compiler) binary.AddSourceFiles('sourcepawn/jit', [ @@ -23,6 +27,7 @@ binary.AddSourceFiles('sourcepawn/jit', [ 'sp_vm_engine.cpp', 'sp_vm_function.cpp', 'opcodes.cpp', + 'watchdog_timer.cpp', 'x86/jit_x86.cpp', 'zlib/adler32.c', 'zlib/compress.c', diff --git a/sourcepawn/jit/BaseRuntime.cpp b/sourcepawn/jit/BaseRuntime.cpp index ec18e44d..463fc146 100644 --- a/sourcepawn/jit/BaseRuntime.cpp +++ b/sourcepawn/jit/BaseRuntime.cpp @@ -37,10 +37,21 @@ BaseRuntime::BaseRuntime() memset(m_CodeHash, 0, sizeof(m_CodeHash)); memset(m_DataHash, 0, sizeof(m_DataHash)); + + ke::AutoLock lock(g_Jit.Mutex()); + g_Jit.RegisterRuntime(this); } 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++) delete m_PubFuncs[i]; delete [] m_PubFuncs; diff --git a/sourcepawn/jit/BaseRuntime.h b/sourcepawn/jit/BaseRuntime.h index 6e5b9dcc..4c2f4254 100644 --- a/sourcepawn/jit/BaseRuntime.h +++ b/sourcepawn/jit/BaseRuntime.h @@ -4,6 +4,7 @@ #include #include +#include #include "jit_shared.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 */ -class BaseRuntime : public SourcePawn::IPluginRuntime +class BaseRuntime + : public SourcePawn::IPluginRuntime, + public InlineListNode { public: BaseRuntime(); @@ -73,6 +76,13 @@ class BaseRuntime : public SourcePawn::IPluginRuntime return &m_plugin; } + size_t NumJitFunctions() const { + return m_JitFunctions.length(); + } + JitFunction *GetJitFunction(size_t i) const { + return m_JitFunctions[i]; + } + private: void SetupFloatNativeRemapping(); @@ -103,3 +113,4 @@ class BaseRuntime : public SourcePawn::IPluginRuntime }; #endif //_INCLUDE_SOURCEPAWN_JIT_RUNTIME_H_ + diff --git a/sourcepawn/jit/Makefile.shell b/sourcepawn/jit/Makefile.shell index 3e80d316..41a4aee7 100644 --- a/sourcepawn/jit/Makefile.shell +++ b/sourcepawn/jit/Makefile.shell @@ -19,6 +19,7 @@ OBJECTS = dll_exports.cpp \ BaseRuntime.cpp \ jit_function.cpp \ opcodes.cpp \ + watchdog_timer.cpp \ md5/md5.cpp \ zlib/adler32.c \ zlib/compress.c \ @@ -47,7 +48,7 @@ CXX_GCC4_FLAGS = -fvisibility-inlines-hidden CXX = c++ 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 \ -I$(SMSDK)/public/sourcepawn -I$(MMSOURCE17)/core/sourcehook -I$(SMSDK)/knight/shared -Ix86 diff --git a/sourcepawn/jit/dll_exports.cpp b/sourcepawn/jit/dll_exports.cpp index 6c53fcba..6e0e23f0 100644 --- a/sourcepawn/jit/dll_exports.cpp +++ b/sourcepawn/jit/dll_exports.cpp @@ -100,7 +100,7 @@ public: while (error->GetTraceInfo(&stk_info)) { fprintf(stderr, - " [%d] Line %d, %s::%s()", + " [%d] Line %d, %s::%s()\n", i++, stk_info.line, stk_info.filename, @@ -221,6 +221,7 @@ int main(int argc, char **argv) ShellDebugListener debug; g_engine1.SetDebugListener(&debug); + g_engine2.InstallWatchdogTimer(5000); int errcode = Execute(argv[1]); diff --git a/sourcepawn/jit/engine2.cpp b/sourcepawn/jit/engine2.cpp index fc691956..284256e9 100644 --- a/sourcepawn/jit/engine2.cpp +++ b/sourcepawn/jit/engine2.cpp @@ -1,3 +1,4 @@ +// vim: set ts=4 sw=4 tw=99 noet: #include #include #include @@ -7,6 +8,7 @@ #include "zlib/zlib.h" #include "BaseRuntime.h" #include "sp_vm_engine.h" +#include "watchdog_timer.h" using namespace SourcePawn; @@ -210,3 +212,9 @@ IPluginRuntime *SourcePawnEngine2::CreateEmptyRuntime(const char *name, uint32_t return rt; } + +bool SourcePawnEngine2::InstallWatchdogTimer(size_t timeout_ms) +{ + return g_WatchdogTimer.Initialize(timeout_ms); +} + diff --git a/sourcepawn/jit/engine2.h b/sourcepawn/jit/engine2.h index b35410d5..0d20975c 100644 --- a/sourcepawn/jit/engine2.h +++ b/sourcepawn/jit/engine2.h @@ -26,6 +26,7 @@ namespace SourcePawn bool Initialize(); void Shutdown(); IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory); + bool InstallWatchdogTimer(size_t timeout_ms); public: IProfiler *GetProfiler(); private: diff --git a/sourcepawn/jit/jit_function.cpp b/sourcepawn/jit/jit_function.cpp index d40b629d..ffba3124 100644 --- a/sourcepawn/jit/jit_function.cpp +++ b/sourcepawn/jit/jit_function.cpp @@ -1,23 +1,31 @@ +// vim: set ts=8 ts=2 sw=2 tw=99 et: #include "jit_function.h" #include "sp_vm_engine.h" #include "jit_x86.h" -JitFunction::JitFunction(void *entry_addr, cell_t pcode_offs) -: m_pEntryAddr(entry_addr), m_PcodeOffs(pcode_offs) +JitFunction::JitFunction(void *entry_addr, cell_t pcode_offs, LoopEdge *edges, uint32_t nedges) + : m_pEntryAddr(entry_addr), + m_PcodeOffs(pcode_offs), + edges_(edges), + nedges_(nedges) { } JitFunction::~JitFunction() { - g_Jit.FreeCode(m_pEntryAddr); + delete [] edges_; + 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; } + diff --git a/sourcepawn/jit/jit_function.h b/sourcepawn/jit/jit_function.h index d2630d38..5c6949c7 100644 --- a/sourcepawn/jit/jit_function.h +++ b/sourcepawn/jit/jit_function.h @@ -1,19 +1,38 @@ +// vim: set ts=8 sts=2 sw=2 tw=99 et: #ifndef _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_ #define _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_ #include +#include + +struct LoopEdge +{ + uint32_t offset; + int32_t disp32; +}; class JitFunction { -public: - JitFunction(void *entry_addr, cell_t pcode_offs); - ~JitFunction(); -public: - void *GetEntryAddress(); - cell_t GetPCodeAddress(); -private: - void *m_pEntryAddr; - cell_t m_PcodeOffs; + public: + JitFunction(void *entry_addr, cell_t pcode_offs, LoopEdge *edges, uint32_t nedges); + ~JitFunction(); + + public: + void *GetEntryAddress() const; + 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; + cell_t m_PcodeOffs; + LoopEdge *edges_; + uint32_t nedges_; }; #endif //_INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_ diff --git a/sourcepawn/jit/sp_vm_basecontext.cpp b/sourcepawn/jit/sp_vm_basecontext.cpp index 4d9f923e..82ee6bb7 100644 --- a/sourcepawn/jit/sp_vm_basecontext.cpp +++ b/sourcepawn/jit/sp_vm_basecontext.cpp @@ -36,6 +36,7 @@ #include "sp_vm_api.h" #include "sp_vm_basecontext.h" #include "sp_vm_engine.h" +#include "watchdog_timer.h" #include "x86/jit_x86.h" using namespace SourcePawn; @@ -554,6 +555,9 @@ int BaseContext::Execute2(IPluginFunction *function, const cell_t *params, unsig fnid = function->GetFunctionID(); + if (!g_WatchdogTimer.HandleInterrupt()) + return SP_ERROR_TIMEOUT; + if (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) { g_engine1.ReportError(m_pRuntime, ir, m_MsgCache, save_rp); diff --git a/sourcepawn/jit/sp_vm_engine.cpp b/sourcepawn/jit/sp_vm_engine.cpp index c3fd6ecf..05e5be15 100644 --- a/sourcepawn/jit/sp_vm_engine.cpp +++ b/sourcepawn/jit/sp_vm_engine.cpp @@ -1,5 +1,5 @@ /** - * vim: set ts=4 : + * vim: set ts=4 sw=4 tw=99 et: * ============================================================================= * SourcePawn * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. @@ -58,7 +58,7 @@ SourcePawnEngine g_engine1; using namespace SourcePawn; -#define ERROR_MESSAGE_MAX 29 +#define ERROR_MESSAGE_MAX 30 static const char *g_ErrorMsgTable[] = { NULL, @@ -90,7 +90,8 @@ static const char *g_ErrorMsgTable[] = "Plugin format is too old", "Plugin format is too new", "Out of memory", - "Integer overflow" + "Integer overflow", + "Script execution timed out" }; const char *SourcePawnEngine::GetErrorString(int error) diff --git a/sourcepawn/jit/watchdog_timer.cpp b/sourcepawn/jit/watchdog_timer.cpp new file mode 100644 index 00000000..64e52beb --- /dev/null +++ b/sourcepawn/jit/watchdog_timer.cpp @@ -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 . +#include "watchdog_timer.h" +#include "x86/jit_x86.h" +#include + +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; +} diff --git a/sourcepawn/jit/watchdog_timer.h b/sourcepawn/jit/watchdog_timer.h new file mode 100644 index 00000000..67cd587b --- /dev/null +++ b/sourcepawn/jit/watchdog_timer.h @@ -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 . +#ifndef _include_sourcepawn_watchdog_timer_posix_h_ +#define _include_sourcepawn_watchdog_timer_posix_h_ + +#include +#include +#include + +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 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_ diff --git a/sourcepawn/jit/x86/jit_x86.cpp b/sourcepawn/jit/x86/jit_x86.cpp index 65be88ee..1e94a7e9 100644 --- a/sourcepawn/jit/x86/jit_x86.cpp +++ b/sourcepawn/jit/x86/jit_x86.cpp @@ -37,6 +37,7 @@ #include "../engine2.h" #include "../BaseRuntime.h" #include "../sp_vm_basecontext.h" +#include "watchdog_timer.h" using namespace Knight; @@ -50,7 +51,7 @@ JITX86 g_Jit; KeCodeCache *g_pCodeCache = NULL; ISourcePawnEngine *engine = &g_engine1; -static inline void * +static inline uint8_t * LinkCode(AssemblerX86 &masm) { if (masm.outOfMemory()) @@ -61,7 +62,7 @@ LinkCode(AssemblerX86 &masm) return NULL; masm.emitToExecutableMemory(code); - return code; + return reinterpret_cast(code); } static inline ConditionCode @@ -249,7 +250,7 @@ GenerateArray(BaseRuntime *rt, uint32_t argc, cell_t *argv, int autozero) return err; cell_t *base = reinterpret_cast(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); argv[argc - 1] = ctx->hp; @@ -314,6 +315,12 @@ GetFunctionName(const sp_plugin_t *plugin, uint32_t offs) static int 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); if (!fn) { int err; @@ -452,13 +459,19 @@ Compiler::emit(int *errp) emitCallThunks(); emitErrorPaths(); - void *code = LinkCode(masm); + uint8_t *code = LinkCode(masm); if (!code) { *errp = SP_ERROR_OUT_OF_MEMORY; 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(code + edges[i].offset - 4); + } + + return new JitFunction(code, pcode_start_, edges, backward_jumps_.length()); } bool @@ -1228,7 +1241,12 @@ Compiler::emitOp(OPCODE op) Label *target = labelAt(readCell()); if (!target) return false; - __ jmp(target); + if (target->bound()) { + __ jmp32(target); + backward_jumps_.append(masm.pc()); + } else { + __ jmp(target); + } break; } @@ -1240,7 +1258,12 @@ Compiler::emitOp(OPCODE op) if (!target) return false; __ testl(pri, pri); - __ j(cc, target); + if (target->bound()) { + __ j32(cc, target); + backward_jumps_.append(masm.pc()); + } else { + __ j(cc, target); + } break; } @@ -1256,7 +1279,12 @@ Compiler::emitOp(OPCODE op) return false; ConditionCode cc = OpToCondition(op); __ cmpl(pri, alt); - __ j(cc, target); + if (target->bound()) { + __ j32(cc, target); + backward_jumps_.append(masm.pc()); + } else { + __ j(cc, target); + } break; } @@ -1755,7 +1783,7 @@ Compiler::emitErrorPaths() typedef int (*JIT_EXECUTE)(sp_context_t *ctx, uint8_t *memory, void *code); static void * -GenerateEntry(void **retp) +GenerateEntry(void **retp, void **timeoutp) { AssemblerX86 masm; @@ -1813,11 +1841,17 @@ GenerateEntry(void **retp) __ 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); + void *code = LinkCode(masm); if (!code) return NULL; *retp = reinterpret_cast(code) + error.offset(); + *timeoutp = reinterpret_cast(code) + timeout.offset(); return code; } @@ -1841,11 +1875,12 @@ JITX86::JITX86() m_pJitEntry = NULL; } -bool JITX86::InitializeJIT() +bool +JITX86::InitializeJIT() { g_pCodeCache = KE_CreateCodeCache(); - m_pJitEntry = GenerateEntry(&m_pJitReturn); + m_pJitEntry = GenerateEntry(&m_pJitReturn, &m_pJitTimeout); if (!m_pJitEntry) return false; @@ -1873,6 +1908,11 @@ JITX86::CompileFunction(BaseRuntime *prt, cell_t pcode_offs, int *err) JitFunction *fun = cc.emit(err); if (!fun) 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); return fun; } @@ -1919,33 +1959,39 @@ JITX86::CreateFakeNative(SPVM_FAKENATIVE_FUNC callback, void *pData) 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); } -ICompilation *JITX86::StartCompilation() +ICompilation * +JITX86::StartCompilation() { return new CompData; } -ICompilation *JITX86::StartCompilation(BaseRuntime *runtime) +ICompilation * +JITX86::StartCompilation(BaseRuntime *runtime) { return new CompData; } -void CompData::Abort() +void +CompData::Abort() { delete this; } -void JITX86::FreeContextVars(sp_context_t *ctx) +void +JITX86::FreeContextVars(sp_context_t *ctx) { free(((tracker_t *)(ctx->vm[JITVARS_TRACKER]))->pBase); 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) return true; @@ -1962,7 +2008,8 @@ bool CompData::SetOption(const char *key, const char *val) 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(); @@ -1970,18 +2017,75 @@ int JITX86::InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result ctx->cip = fn->GetPCodeAddress(); JIT_EXECUTE pfn = (JIT_EXECUTE)m_pJitEntry; + + if (level_++ == 0) + frame_id_++; int err = pfn(ctx, runtime->plugin()->memory, fn->GetEntryAddress()); + level_--; *result = ctx->rval; return err; } -void *JITX86::AllocCode(size_t size) +void * +JITX86::AllocCode(size_t size) { return Knight::KE_AllocCode(g_pCodeCache, size); } -void JITX86::FreeCode(void *code) +void +JITX86::FreeCode(void *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::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(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(base + e.offset - 4) = diff; + } + } + } +} + +void +JITX86::UnpatchAllJumpsFromTimeout() +{ + mutex_.AssertCurrentThreadOwns(); + for (InlineList::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(fun->GetEntryAddress()); + + for (size_t j = 0; j < fun->NumLoopEdges(); j++) { + const LoopEdge &e = fun->GetLoopEdge(j); + *reinterpret_cast(base + e.offset - 4) = e.disp32; + } + } + } +} diff --git a/sourcepawn/jit/x86/jit_x86.h b/sourcepawn/jit/x86/jit_x86.h index 3ec5274b..fec3c4b8 100644 --- a/sourcepawn/jit/x86/jit_x86.h +++ b/sourcepawn/jit/x86/jit_x86.h @@ -1,34 +1,19 @@ -/** - * vim: set ts=4 sw=4 tw=99 et: - * ============================================================================= - * SourceMod - * Copyright (C) 2004-2008 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 . - * - * 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 . - * - * Version: $Id$ - */ - +// vim: set ts=8 sts=2 sw=2 tw=99 et: +// +// This file is part of SourcePawn. +// +// SourcePawn is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SourcePawn is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SourcePawn. If not, see . #ifndef _INCLUDE_SOURCEPAWN_JIT_X86_H_ #define _INCLUDE_SOURCEPAWN_JIT_X86_H_ @@ -42,6 +27,7 @@ #include "sp_vm_basecontext.h" #include "jit_function.h" #include "opcodes.h" +#include using namespace SourcePawn; @@ -155,6 +141,7 @@ class Compiler cell_t *cip_; cell_t *code_end_; Label *jump_map_; + ke::Vector backward_jumps_; // Errors Label error_bounds_; @@ -188,16 +175,36 @@ class JITX86 ICompilation *ApplyOptions(ICompilation *_IN, ICompilation *_OUT); int InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result); + void RegisterRuntime(BaseRuntime *rt); + void DeregisterRuntime(BaseRuntime *rt); + void PatchAllJumpsForTimeout(); + void UnpatchAllJumpsFromTimeout(); + public: ExternalAddress GetUniversalReturn() { - return ExternalAddress(m_pJitReturn); + return ExternalAddress(m_pJitReturn); } void *AllocCode(size_t size); void FreeCode(void *code); + uintptr_t FrameId() const { + return frame_id_; + } + bool RunningCode() const { + return level_ != 0; + } + ke::Mutex *Mutex() { + return &mutex_; + } + private: void *m_pJitEntry; /* Entry function */ void *m_pJitReturn; /* Universal return address */ + void *m_pJitTimeout; /* Universal timeout address */ + InlineList runtimes_; + uintptr_t frame_id_; + uintptr_t level_; + ke::Mutex mutex_; }; const Register pri = eax; @@ -211,3 +218,4 @@ extern Knight::KeCodeCache *g_pCodeCache; extern JITX86 g_Jit; #endif //_INCLUDE_SOURCEPAWN_JIT_X86_H_ +