Implement a watchdog timer for scripts that take too long to execute (bug 5837, r=fyren).
1.1 --- a/core/sourcemod.cpp Wed Aug 14 17:18:02 2013 -0400
1.2 +++ b/core/sourcemod.cpp Wed Aug 14 23:54:25 2013 -0700
1.3 @@ -1,5 +1,5 @@
1.4 /**
1.5 - * vim: set ts=4 sw=4 :
1.6 + * vim: set ts=4 sw=4 tw=99 noet:
1.7 * =============================================================================
1.8 * SourceMod
1.9 * Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved.
1.10 @@ -311,6 +311,16 @@
1.11 {
1.12 extsys->LoadAutoExtension("updater.ext." PLATFORM_LIB_EXT);
1.13 }
1.14 +
1.15 + const char *timeout = GetCoreConfigValue("SlowScriptTimeout");
1.16 + if (timeout == NULL)
1.17 + {
1.18 + timeout = "8";
1.19 + }
1.20 + if (atoi(timeout) != 0)
1.21 + {
1.22 + g_pSourcePawn2->InstallWatchdogTimer(atoi(timeout) * 1000);
1.23 + }
1.24 }
1.25
1.26 static bool g_LevelEndBarrier = false;
2.1 --- a/public/jit/assembler.h Wed Aug 14 17:18:02 2013 -0400
2.2 +++ b/public/jit/assembler.h Wed Aug 14 23:54:25 2013 -0700
2.3 @@ -64,6 +64,11 @@
2.4 return pos_ - buffer_;
2.5 }
2.6
2.7 + // Current offset into the code stream.
2.8 + uint32_t pc() const {
2.9 + return uint32_t(pos_ - buffer_);
2.10 + }
2.11 +
2.12 protected:
2.13 void writeByte(uint8_t byte) {
2.14 write<uint8_t>(byte);
2.15 @@ -125,11 +130,6 @@
2.16 return int32_t(pos_ - buffer_);
2.17 }
2.18
2.19 - // pc is the unsigned version of position().
2.20 - uint32_t pc() const {
2.21 - return uint32_t(pos_ - buffer_);
2.22 - }
2.23 -
2.24 protected:
2.25 void assertCanWrite(size_t bytes) {
2.26 assert(pos_ + bytes <= end_);
3.1 --- a/public/jit/x86/assembler-x86.h Wed Aug 14 17:18:02 2013 -0400
3.2 +++ b/public/jit/x86/assembler-x86.h Wed Aug 14 23:54:25 2013 -0700
3.3 @@ -640,6 +640,10 @@
3.4 emit2(0xdc, 0xc0 + src.code);
3.5 }
3.6
3.7 + void jmp32(Label *dest) {
3.8 + emit1(0xe9);
3.9 + emitJumpTarget(dest);
3.10 + }
3.11 void jmp(Label *dest) {
3.12 int8_t d8;
3.13 if (canEmitSmallJump(dest, &d8)) {
3.14 @@ -655,6 +659,10 @@
3.15 void jmp(const Operand &target) {
3.16 emit1(0xff, 4, target);
3.17 }
3.18 + void j32(ConditionCode cc, Label *dest) {
3.19 + emit2(0x0f, 0x80 + uint8_t(cc));
3.20 + emitJumpTarget(dest);
3.21 + }
3.22 void j(ConditionCode cc, Label *dest) {
3.23 int8_t d8;
3.24 if (canEmitSmallJump(dest, &d8)) {
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/public/sourcepawn/ke_inline_list.h Wed Aug 14 23:54:25 2013 -0700
4.3 @@ -0,0 +1,137 @@
4.4 +/**
4.5 + * vim: set sts=2 ts=8 sw=2 tw=99 noet :
4.6 + * =============================================================================
4.7 + * SourcePawn
4.8 + * Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
4.9 + * =============================================================================
4.10 + *
4.11 + * This program is free software; you can redistribute it and/or modify it under
4.12 + * the terms of the GNU General Public License, version 3.0, as published by the
4.13 + * Free Software Foundation.
4.14 + *
4.15 + * This program is distributed in the hope that it will be useful, but WITHOUT
4.16 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
4.17 + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
4.18 + * details.
4.19 + *
4.20 + * You should have received a copy of the GNU General Public License along with
4.21 + * this program. If not, see <http://www.gnu.org/licenses/>.
4.22 + *
4.23 + * As a special exception, AlliedModders LLC gives you permission to link the
4.24 + * code of this program (as well as its derivative works) to "Half-Life 2," the
4.25 + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
4.26 + * by the Valve Corporation. You must obey the GNU General Public License in
4.27 + * all respects for all other code used. Additionally, AlliedModders LLC grants
4.28 + * this exception to all derivative works. AlliedModders LLC defines further
4.29 + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
4.30 + * or <http://www.sourcemod.net/license.php>.
4.31 + */
4.32 +#ifndef _include_sourcepawn_inline_list_h_
4.33 +#define _include_sourcepawn_inline_list_h_
4.34 +
4.35 +#include <stddef.h>
4.36 +
4.37 +template <typename T> class InlineList;
4.38 +
4.39 +template <typename T>
4.40 +class InlineListNode
4.41 +{
4.42 + friend class InlineList<T>;
4.43 +
4.44 + public:
4.45 + InlineListNode()
4.46 + : next_(NULL),
4.47 + prev_(NULL)
4.48 + {
4.49 + }
4.50 +
4.51 + InlineListNode(InlineListNode *next, InlineListNode *prev)
4.52 + : next_(next),
4.53 + prev_(prev)
4.54 + {
4.55 + }
4.56 +
4.57 + protected:
4.58 + InlineListNode *next_;
4.59 + InlineListNode *prev_;
4.60 +};
4.61 +
4.62 +template <typename T>
4.63 +class InlineList
4.64 +{
4.65 + typedef InlineListNode<T> Node;
4.66 +
4.67 + Node head_;
4.68 +
4.69 + public:
4.70 + InlineList()
4.71 + : head_(&head_, &head_)
4.72 + {
4.73 + }
4.74 +
4.75 + public:
4.76 + class iterator
4.77 + {
4.78 + friend class InlineList;
4.79 + Node *iter_;
4.80 +
4.81 + public:
4.82 + iterator(Node *iter)
4.83 + : iter_(iter)
4.84 + {
4.85 + }
4.86 +
4.87 + iterator & operator ++() {
4.88 + iter_ = iter_->next;
4.89 + return *iter_;
4.90 + }
4.91 + iterator operator ++(int) {
4.92 + iterator old(*this);
4.93 + iter_ = iter_->next_;
4.94 + return old;
4.95 + }
4.96 + T * operator *() {
4.97 + return static_cast<T *>(iter_);
4.98 + }
4.99 + T * operator ->() {
4.100 + return static_cast<T *>(iter_);
4.101 + }
4.102 + bool operator !=(const iterator &where) const {
4.103 + return iter_ != where.iter_;
4.104 + }
4.105 + bool operator ==(const iterator &where) const {
4.106 + return iter_ == where.iter_;
4.107 + }
4.108 + iterator prev() const {
4.109 + iterator p(iter_->prev_);
4.110 + return p;
4.111 + }
4.112 + iterator next() const {
4.113 + iterator p(iter_->next_);
4.114 + return p;
4.115 + }
4.116 + };
4.117 +
4.118 + iterator begin() {
4.119 + return iterator(head_.next_);
4.120 + }
4.121 +
4.122 + iterator end() {
4.123 + return iterator(&head_);
4.124 + }
4.125 +
4.126 + void erase(Node *t) {
4.127 + t->prev_->next_ = t->next_;
4.128 + t->next_->prev_ = t->prev_;
4.129 + }
4.130 +
4.131 + void insert(Node *t) {
4.132 + t->prev_ = head_.prev_;
4.133 + t->next_ = &head_;
4.134 + head_.prev_->next_ = t;
4.135 + head_.prev_ = t;
4.136 + }
4.137 +};
4.138 +
4.139 +#endif // _include_sourcepawn_inline_list_h_
4.140 +
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/public/sourcepawn/ke_thread_posix.h Wed Aug 14 23:54:25 2013 -0700
5.3 @@ -0,0 +1,188 @@
5.4 +// vim: set ts=8 sts=2 sw=2 tw=99 et:
5.5 +//
5.6 +// This file is part of SourcePawn.
5.7 +//
5.8 +// SourcePawn is free software: you can redistribute it and/or modify
5.9 +// it under the terms of the GNU General Public License as published by
5.10 +// the Free Software Foundation, either version 3 of the License, or
5.11 +// (at your option) any later version.
5.12 +//
5.13 +// SourcePawn is distributed in the hope that it will be useful,
5.14 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
5.15 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5.16 +// GNU General Public License for more details.
5.17 +//
5.18 +// You should have received a copy of the GNU General Public License
5.19 +// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
5.20 +#ifndef _include_sourcepawn_thread_posix_h_
5.21 +#define _include_sourcepawn_thread_posix_h_
5.22 +
5.23 +#include <pthread.h>
5.24 +#include <sys/time.h>
5.25 +#include <errno.h>
5.26 +#include <stdio.h>
5.27 +#if defined(__linux__)
5.28 +# include <sys/prctl.h>
5.29 +#endif
5.30 +
5.31 +namespace ke {
5.32 +
5.33 +class Mutex : public Lockable
5.34 +{
5.35 + public:
5.36 + Mutex() {
5.37 +#if !defined(NDEBUG)
5.38 + int rv =
5.39 +#endif
5.40 + pthread_mutex_init(&mutex_, NULL);
5.41 + assert(rv == 0);
5.42 + }
5.43 + ~Mutex() {
5.44 + pthread_mutex_destroy(&mutex_);
5.45 + }
5.46 +
5.47 + bool DoTryLock() {
5.48 + return pthread_mutex_trylock(&mutex_) == 0;
5.49 + }
5.50 +
5.51 + void DoLock() {
5.52 + pthread_mutex_lock(&mutex_);
5.53 + }
5.54 +
5.55 + void DoUnlock() {
5.56 + pthread_mutex_unlock(&mutex_);
5.57 + }
5.58 +
5.59 + pthread_mutex_t *raw() {
5.60 + return &mutex_;
5.61 + }
5.62 +
5.63 + private:
5.64 + pthread_mutex_t mutex_;
5.65 +};
5.66 +
5.67 +// Currently, this class only supports single-listener CVs.
5.68 +class ConditionVariable : public Lockable
5.69 +{
5.70 + public:
5.71 + ConditionVariable() {
5.72 +#if !defined(NDEBUG)
5.73 + int rv =
5.74 +#endif
5.75 + pthread_cond_init(&cv_, NULL);
5.76 + assert(rv == 0);
5.77 + }
5.78 + ~ConditionVariable() {
5.79 + pthread_cond_destroy(&cv_);
5.80 + }
5.81 +
5.82 + bool DoTryLock() {
5.83 + return mutex_.DoTryLock();
5.84 + }
5.85 + void DoLock() {
5.86 + mutex_.DoLock();
5.87 + }
5.88 + void DoUnlock() {
5.89 + mutex_.DoUnlock();
5.90 + }
5.91 +
5.92 + void Notify() {
5.93 + AssertCurrentThreadOwns();
5.94 + pthread_cond_signal(&cv_);
5.95 + }
5.96 +
5.97 + WaitResult Wait(size_t timeout_ms) {
5.98 + AssertCurrentThreadOwns();
5.99 +
5.100 + struct timespec ts;
5.101 + if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
5.102 + return Wait_Error;
5.103 +
5.104 + ts.tv_sec += timeout_ms / 1000;
5.105 + ts.tv_nsec += (timeout_ms % 1000) * 1000000;
5.106 + if (ts.tv_nsec >= 1000000000) {
5.107 + ts.tv_sec++;
5.108 + ts.tv_nsec -= 1000000000;
5.109 + }
5.110 +
5.111 + DebugSetUnlocked();
5.112 + int rv = pthread_cond_timedwait(&cv_, mutex_.raw(), &ts);
5.113 + DebugSetLocked();
5.114 +
5.115 + if (rv == ETIMEDOUT)
5.116 + return Wait_Timeout;
5.117 + if (rv == 0)
5.118 + return Wait_Signaled;
5.119 + return Wait_Error;
5.120 + }
5.121 +
5.122 + WaitResult Wait() {
5.123 + AssertCurrentThreadOwns();
5.124 +
5.125 + DebugSetUnlocked();
5.126 + int rv = pthread_cond_wait(&cv_, mutex_.raw());
5.127 + DebugSetLocked();
5.128 +
5.129 + if (rv == 0)
5.130 + return Wait_Signaled;
5.131 + return Wait_Error;
5.132 + }
5.133 +
5.134 + private:
5.135 + Mutex mutex_;
5.136 + pthread_cond_t cv_;
5.137 +};
5.138 +
5.139 +class Thread
5.140 +{
5.141 + struct ThreadData {
5.142 + IRunnable *run;
5.143 + char name[17];
5.144 + };
5.145 + public:
5.146 + Thread(IRunnable *run, const char *name = NULL) {
5.147 + ThreadData *data = new ThreadData;
5.148 + data->run = run;
5.149 + snprintf(data->name, sizeof(data->name), "%s", name ? name : "");
5.150 +
5.151 + initialized_ = (pthread_create(&thread_, NULL, Main, data) == 0);
5.152 + if (!initialized_)
5.153 + delete data;
5.154 + }
5.155 +
5.156 + bool Succeeded() const {
5.157 + return initialized_;
5.158 + }
5.159 +
5.160 + void Join() {
5.161 + if (!Succeeded())
5.162 + return;
5.163 + pthread_join(thread_, NULL);
5.164 + }
5.165 +
5.166 + private:
5.167 + static void *Main(void *arg) {
5.168 + AutoPtr<ThreadData> data((ThreadData *)arg);
5.169 +
5.170 + if (data->name[0]) {
5.171 +#if defined(__linux__)
5.172 + prctl(PR_SET_NAME, (unsigned long)data->name);
5.173 +#elif defined(__APPLE__)
5.174 + int (*fn)(const char *) = (int (*)(const char *))dlsym(RTLD_DEFAULT, "pthread_setname_np");
5.175 + if (fn)
5.176 + fn(data->name);
5.177 +#endif
5.178 + }
5.179 + data->run->Run();
5.180 + return NULL;
5.181 + }
5.182 +
5.183 + private:
5.184 + bool initialized_;
5.185 + pthread_t thread_;
5.186 +};
5.187 +
5.188 +} // namespace ke
5.189 +
5.190 +#endif // _include_sourcepawn_thread_posix_h_
5.191 +
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/public/sourcepawn/ke_thread_utils.h Wed Aug 14 23:54:25 2013 -0700
6.3 @@ -0,0 +1,241 @@
6.4 +// vim: set ts=8 sts=2 sw=2 tw=99 et:
6.5 +//
6.6 +// This file is part of SourcePawn.
6.7 +//
6.8 +// SourcePawn is free software: you can redistribute it and/or modify
6.9 +// it under the terms of the GNU General Public License as published by
6.10 +// the Free Software Foundation, either version 3 of the License, or
6.11 +// (at your option) any later version.
6.12 +//
6.13 +// SourcePawn is distributed in the hope that it will be useful,
6.14 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
6.15 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6.16 +// GNU General Public License for more details.
6.17 +//
6.18 +// You should have received a copy of the GNU General Public License
6.19 +// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
6.20 +#ifndef _include_sourcepawn_threads_
6.21 +#define _include_sourcepawn_threads_
6.22 +
6.23 +#include <assert.h>
6.24 +#if defined(_MSC_VER)
6.25 +# include <windows.h>
6.26 +#else
6.27 +# include <pthread.h>
6.28 +#endif
6.29 +#include <ke_utility.h>
6.30 +
6.31 +// Thread primitives for SourcePawn.
6.32 +//
6.33 +// -- Mutexes --
6.34 +//
6.35 +// A Lockable is a mutual exclusion primitive. It can be owned by at most one
6.36 +// thread at a time, and ownership blocks any other thread from taking taking
6.37 +// ownership. Ownership must be acquired and released on the same thread.
6.38 +// Lockables are not re-entrant.
6.39 +//
6.40 +// While a few classes support the Lockable interface, the simplest Lockable
6.41 +// object that can be instantiated is a Mutex.
6.42 +//
6.43 +// -- Condition Variables --
6.44 +//
6.45 +// A ConditionVariable provides mutually exclusive access based on a
6.46 +// condition ocurring. CVs provide two capabilities: Wait(), which will block
6.47 +// until the condition is triggered, and Notify(), which signals any blocking
6.48 +// thread that the condition has occurred.
6.49 +//
6.50 +// Condition variables have an underlying mutex lock. This lock must be
6.51 +// acquired before calling Wait() or Notify(). It is automatically released
6.52 +// once Wait begins blocking. This operation is atomic with respect to other
6.53 +// threads and the mutex. For example, it is not possible for the lock to be
6.54 +// acquired by another thread in between unlocking and blocking. Since Notify
6.55 +// also requires the lock to be acquired, there is no risk of an event
6.56 +// accidentally dissipating into thin air because it was sent before the other
6.57 +// thread began blocking.
6.58 +//
6.59 +// When Wait() returns, the lock is automatically re-acquired. This operation
6.60 +// is NOT atomic. In between waking up and re-acquiring the lock, another
6.61 +// thread may steal the lock and issue another event. Applications must
6.62 +// account for this. For example, a message pump should check that there are no
6.63 +// messages left to process before blocking again.
6.64 +//
6.65 +// -- Threads --
6.66 +//
6.67 +// A Thread object, when created, spawns a new thread with the given callback
6.68 +// (the callbacks must implement IRunnable). Threads have one method of
6.69 +// interest, Join(), which will block until the thread's execution finishes.
6.70 +// Deleting a thread object will free any operating system resources associated
6.71 +// with that thread, if the thread has finished executing.
6.72 +//
6.73 +// Threads can fail to spawn; make sure to check Succeeded().
6.74 +//
6.75 +
6.76 +namespace ke {
6.77 +
6.78 +// Abstraction for accessing the current thread.
6.79 +#if defined(_MSC_VER)
6.80 +typedef HANDLE ThreadId;
6.81 +
6.82 +static inline ThreadId GetCurrentThreadId()
6.83 +{
6.84 + return GetCurrentThread();
6.85 +}
6.86 +#else
6.87 +typedef pthread_t ThreadId;
6.88 +
6.89 +static inline ThreadId GetCurrentThreadId()
6.90 +{
6.91 + return pthread_self();
6.92 +}
6.93 +#endif
6.94 +
6.95 +// Classes which use non-reentrant, same-thread lock/unlock semantics should
6.96 +// inherit from this and implement DoLock/DoUnlock.
6.97 +class Lockable
6.98 +{
6.99 + public:
6.100 + Lockable()
6.101 + {
6.102 +#if !defined(NDEBUG)
6.103 + owner_ = 0;
6.104 +#endif
6.105 + }
6.106 + virtual ~Lockable() {
6.107 + }
6.108 +
6.109 + bool TryLock() {
6.110 + if (DoTryLock()) {
6.111 + DebugSetLocked();
6.112 + return true;
6.113 + }
6.114 + return false;
6.115 + }
6.116 +
6.117 + void Lock() {
6.118 + assert(Owner() != GetCurrentThreadId());
6.119 + DoLock();
6.120 + DebugSetLocked();
6.121 + }
6.122 +
6.123 + void Unlock() {
6.124 + assert(Owner() == GetCurrentThreadId());
6.125 + DebugSetUnlocked();
6.126 + DoUnlock();
6.127 + }
6.128 +
6.129 + void AssertCurrentThreadOwns() const {
6.130 + assert(Owner() == GetCurrentThreadId());
6.131 + }
6.132 +#if !defined(NDEBUG)
6.133 + bool Locked() const {
6.134 + return owner_ != 0;
6.135 + }
6.136 + ThreadId Owner() const {
6.137 + return owner_;
6.138 + }
6.139 +#endif
6.140 +
6.141 + virtual bool DoTryLock() = 0;
6.142 + virtual void DoLock() = 0;
6.143 + virtual void DoUnlock() = 0;
6.144 +
6.145 + protected:
6.146 + void DebugSetUnlocked() {
6.147 +#if !defined(NDEBUG)
6.148 + owner_ = 0;
6.149 +#endif
6.150 + }
6.151 + void DebugSetLocked() {
6.152 +#if !defined(NDEBUG)
6.153 + owner_ = GetCurrentThreadId();
6.154 +#endif
6.155 + }
6.156 +
6.157 + protected:
6.158 +#if !defined(NDEBUG)
6.159 + ThreadId owner_;
6.160 +#endif
6.161 +};
6.162 +
6.163 +// RAII for automatically locking and unlocking an object.
6.164 +class AutoLock
6.165 +{
6.166 + public:
6.167 + AutoLock(Lockable *lock)
6.168 + : lock_(lock)
6.169 + {
6.170 + lock_->Lock();
6.171 + }
6.172 + ~AutoLock() {
6.173 + lock_->Unlock();
6.174 + }
6.175 +
6.176 + private:
6.177 + Lockable *lock_;
6.178 +};
6.179 +
6.180 +class AutoTryLock
6.181 +{
6.182 + public:
6.183 + AutoTryLock(Lockable *lock)
6.184 + {
6.185 + lock_ = lock->TryLock() ? lock : NULL;
6.186 + }
6.187 + ~AutoTryLock() {
6.188 + if (lock_)
6.189 + lock_->Unlock();
6.190 + }
6.191 +
6.192 + private:
6.193 + Lockable *lock_;
6.194 +};
6.195 +
6.196 +// RAII for automatically unlocking and relocking an object.
6.197 +class AutoUnlock
6.198 +{
6.199 + public:
6.200 + AutoUnlock(Lockable *lock)
6.201 + : lock_(lock)
6.202 + {
6.203 + lock_->Unlock();
6.204 + }
6.205 + ~AutoUnlock() {
6.206 + lock_->Lock();
6.207 + }
6.208 +
6.209 + private:
6.210 + Lockable *lock_;
6.211 +};
6.212 +
6.213 +enum WaitResult {
6.214 + // Woke up because something happened.
6.215 + Wait_Signaled,
6.216 +
6.217 + // Woke up because nothing happened and a timeout was specified.
6.218 + Wait_Timeout,
6.219 +
6.220 + // Woke up, but because of an error.
6.221 + Wait_Error
6.222 +};
6.223 +
6.224 +// This must be implemented in order to spawn a new thread.
6.225 +class IRunnable
6.226 +{
6.227 + public:
6.228 + virtual ~IRunnable() {
6.229 + }
6.230 +
6.231 + virtual void Run() = 0;
6.232 +};
6.233 +
6.234 +} // namespace ke
6.235 +
6.236 +// Include the actual thread implementations.
6.237 +#if defined(_MSC_VER)
6.238 +# include "ke_thread_windows.h"
6.239 +#else
6.240 +# include "ke_thread_posix.h"
6.241 +#endif
6.242 +
6.243 +#endif // _include_sourcepawn_threads_
6.244 +
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/public/sourcepawn/ke_thread_windows.h Wed Aug 14 23:54:25 2013 -0700
7.3 @@ -0,0 +1,144 @@
7.4 +// vim: set ts=8 sts=2 sw=2 tw=99 et:
7.5 +//
7.6 +// This file is part of SourcePawn.
7.7 +//
7.8 +// SourcePawn is free software: you can redistribute it and/or modify
7.9 +// it under the terms of the GNU General Public License as published by
7.10 +// the Free Software Foundation, either version 3 of the License, or
7.11 +// (at your option) any later version.
7.12 +//
7.13 +// SourcePawn is distributed in the hope that it will be useful,
7.14 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
7.15 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
7.16 +// GNU General Public License for more details.
7.17 +//
7.18 +// You should have received a copy of the GNU General Public License
7.19 +// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
7.20 +#ifndef _include_sourcepawn_thread_windows_h_
7.21 +#define _include_sourcepawn_thread_windows_h_
7.22 +
7.23 +#include <windows.h>
7.24 +
7.25 +namespace ke {
7.26 +
7.27 +class CriticalSection : public Lockable
7.28 +{
7.29 + public:
7.30 + CriticalSection() {
7.31 + InitializeCriticalSection(&cs_);
7.32 + }
7.33 + ~CriticalSection() {
7.34 + DeleteCriticalSection(&cs_);
7.35 + }
7.36 +
7.37 + bool DoTryLock() {
7.38 + return !!TryEnterCriticalSection(&cs_);
7.39 + }
7.40 + void DoLock() {
7.41 + EnterCriticalSection(&cs_);
7.42 + }
7.43 +
7.44 + void DoUnlock() {
7.45 + LeaveCriticalSection(&cs_);
7.46 + }
7.47 +
7.48 + private:
7.49 + CRITICAL_SECTION cs_;
7.50 +};
7.51 +
7.52 +typedef CriticalSection Mutex;
7.53 +
7.54 +// Currently, this class only supports single-listener CVs.
7.55 +class ConditionVariable : public Lockable
7.56 +{
7.57 + public:
7.58 + ConditionVariable() {
7.59 + event_ = CreateEvent(NULL, FALSE, FALSE, NULL);
7.60 + }
7.61 + ~ConditionVariable() {
7.62 + CloseHandle(event_);
7.63 + }
7.64 +
7.65 + bool DoTryLock() {
7.66 + return cs_.DoTryLock();
7.67 + }
7.68 + void DoLock() {
7.69 + cs_.DoLock();
7.70 + }
7.71 + void DoUnlock() {
7.72 + cs_.DoUnlock();
7.73 + }
7.74 +
7.75 + void Notify() {
7.76 + AssertCurrentThreadOwns();
7.77 + SetEvent(event_);
7.78 + }
7.79 +
7.80 + WaitResult Wait(size_t timeout_ms) {
7.81 + // This will assert if the lock has not been acquired. We don't need to be
7.82 + // atomic here, like pthread_cond_wait, because the event bit will stick
7.83 + // until reset by a wait function.
7.84 + Unlock();
7.85 + DWORD rv = WaitForSingleObject(event_, timeout_ms);
7.86 + Lock();
7.87 +
7.88 + if (rv == WAIT_TIMEOUT)
7.89 + return Wait_Timeout;
7.90 + if (rv == WAIT_FAILED)
7.91 + return Wait_Error;
7.92 + return Wait_Signaled;
7.93 + }
7.94 +
7.95 + WaitResult Wait() {
7.96 + return Wait(INFINITE);
7.97 + }
7.98 +
7.99 + private:
7.100 + CriticalSection cs_;
7.101 + HANDLE event_;
7.102 +};
7.103 +
7.104 +class Thread
7.105 +{
7.106 + public:
7.107 + Thread(IRunnable *run, const char *name = NULL) {
7.108 + thread_ = CreateThread(NULL, 0, Main, run, 0, NULL);
7.109 + }
7.110 + ~Thread() {
7.111 + if (!thread_)
7.112 + return;
7.113 + CloseHandle(thread_);
7.114 + }
7.115 +
7.116 + bool Succeeded() const {
7.117 + return !!thread_;
7.118 + }
7.119 +
7.120 + void Join() {
7.121 + if (!Succeeded())
7.122 + return;
7.123 + WaitForSingleObject(thread_, INFINITE);
7.124 + }
7.125 +
7.126 + private:
7.127 + static DWORD WINAPI Main(LPVOID arg) {
7.128 + ((IRunnable *)arg)->Run();
7.129 + return 0;
7.130 + }
7.131 +
7.132 +#pragma pack(push, 8)
7.133 + struct ThreadNameInfo {
7.134 + DWORD dwType;
7.135 + LPCSTR szName;
7.136 + DWORD dwThreadID;
7.137 + DWORD dwFlags;
7.138 + };
7.139 +#pragma pack(pop)
7.140 +
7.141 + private:
7.142 + HANDLE thread_;
7.143 +};
7.144 +
7.145 +} // namespace ke
7.146 +
7.147 +#endif // _include_sourcepawn_thread_windows_h_
8.1 --- a/public/sourcepawn/ke_utility.h Wed Aug 14 17:18:02 2013 -0400
8.2 +++ b/public/sourcepawn/ke_utility.h Wed Aug 14 23:54:25 2013 -0700
8.3 @@ -48,34 +48,45 @@
8.4 static const size_t kMB = 1024 * kKB;
8.5 static const size_t kGB = 1024 * kMB;
8.6
8.7 +template <typename T> T
8.8 +ReturnAndVoid(T &t)
8.9 +{
8.10 + T saved = t;
8.11 + t = T();
8.12 + return saved;
8.13 +}
8.14 +
8.15 template <typename T>
8.16 -class AutoFree
8.17 +class AutoPtr
8.18 {
8.19 T *t_;
8.20
8.21 public:
8.22 - AutoFree()
8.23 + AutoPtr()
8.24 : t_(NULL)
8.25 {
8.26 }
8.27 - AutoFree(T *t)
8.28 + explicit AutoPtr(T *t)
8.29 : t_(t)
8.30 {
8.31 }
8.32 - ~AutoFree() {
8.33 - free(t_);
8.34 + ~AutoPtr() {
8.35 + delete t_;
8.36 }
8.37 T *take() {
8.38 - T *t = t_;
8.39 - t_ = NULL;
8.40 - return t;
8.41 + return ReturnAndVoid(t_);
8.42 }
8.43 T *operator *() const {
8.44 return t_;
8.45 }
8.46 + T *operator ->() const {
8.47 + return t_;
8.48 + }
8.49 + operator T *() const {
8.50 + return t_;
8.51 + }
8.52 void operator =(T *t) {
8.53 - if (t_)
8.54 - free(t_);
8.55 + delete t_;
8.56 t_ = t;
8.57 }
8.58 };
8.59 @@ -289,14 +300,6 @@
8.60 return t1 > t2 ? t1 : t2;
8.61 }
8.62
8.63 -template <typename T> T
8.64 -ReturnAndVoid(T &t)
8.65 -{
8.66 - T saved = t;
8.67 - t = T();
8.68 - return saved;
8.69 -}
8.70 -
8.71 #define OFFSETOF(Class, Member) reinterpret_cast<size_t>(&((Class *)NULL)->Member)
8.72
8.73 #if defined(_MSC_VER)
9.1 --- a/public/sourcepawn/sp_vm_api.h Wed Aug 14 17:18:02 2013 -0400
9.2 +++ b/public/sourcepawn/sp_vm_api.h Wed Aug 14 23:54:25 2013 -0700
9.3 @@ -40,7 +40,7 @@
9.4
9.5 /** SourcePawn Engine API Version */
9.6 #define SOURCEPAWN_ENGINE_API_VERSION 4
9.7 -#define SOURCEPAWN_ENGINE2_API_VERSION 4
9.8 +#define SOURCEPAWN_ENGINE2_API_VERSION 5
9.9
9.10 #if !defined SOURCEMOD_BUILD
9.11 #define SOURCEMOD_BUILD
9.12 @@ -1286,6 +1286,15 @@
9.13 * @return New runtime, or NULL if not enough memory.
9.14 */
9.15 virtual IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory) =0;
9.16 +
9.17 + /**
9.18 + * @brief Initiates the watchdog timer with the specified timeout
9.19 + * length. This cannot be called more than once.
9.20 + *
9.21 + * @param timeout Timeout, in ms.
9.22 + * @return True on success, false on failure.
9.23 + */
9.24 + virtual bool InstallWatchdogTimer(size_t timeout_ms) =0;
9.25 };
9.26 };
9.27
10.1 --- a/public/sourcepawn/sp_vm_types.h Wed Aug 14 17:18:02 2013 -0400
10.2 +++ b/public/sourcepawn/sp_vm_types.h Wed Aug 14 23:54:25 2013 -0700
10.3 @@ -87,6 +87,7 @@
10.4 #define SP_ERROR_CODE_TOO_NEW 27 /**< Code is too new for this VM */
10.5 #define SP_ERROR_OUT_OF_MEMORY 28 /**< Out of memory */
10.6 #define SP_ERROR_INTEGER_OVERFLOW 29 /**< Integer overflow (-INT_MIN / -1) */
10.7 +#define SP_ERROR_TIMEOUT 30 /**< Timeout */
10.8 //Hey you! Update the string table if you add to the end of me! */
10.9
10.10 /**********************************************
11.1 --- a/sourcepawn/jit/AMBuilder Wed Aug 14 17:18:02 2013 -0400
11.2 +++ b/sourcepawn/jit/AMBuilder Wed Aug 14 23:54:25 2013 -0700
11.3 @@ -12,6 +12,10 @@
11.4 compiler['CXXINCLUDES'].append(os.path.join(base, 'public', 'jit', 'x86'))
11.5 compiler['CXXINCLUDES'].append(os.path.join(base, 'knight', 'shared'))
11.6
11.7 +if AMBuild.target['platform'] == 'linux':
11.8 + compiler['POSTLINKFLAGS'].append('-lpthread')
11.9 + compiler['POSTLINKFLAGS'].append('-lrt')
11.10 +
11.11 extension = AMBuild.AddJob('sourcepawn.jit.x86')
11.12 binary = Cpp.LibraryBuilder('sourcepawn.jit.x86', AMBuild, extension, compiler)
11.13 binary.AddSourceFiles('sourcepawn/jit', [
11.14 @@ -23,6 +27,7 @@
11.15 'sp_vm_engine.cpp',
11.16 'sp_vm_function.cpp',
11.17 'opcodes.cpp',
11.18 + 'watchdog_timer.cpp',
11.19 'x86/jit_x86.cpp',
11.20 'zlib/adler32.c',
11.21 'zlib/compress.c',
12.1 --- a/sourcepawn/jit/BaseRuntime.cpp Wed Aug 14 17:18:02 2013 -0400
12.2 +++ b/sourcepawn/jit/BaseRuntime.cpp Wed Aug 14 23:54:25 2013 -0700
12.3 @@ -37,10 +37,21 @@
12.4
12.5 memset(m_CodeHash, 0, sizeof(m_CodeHash));
12.6 memset(m_DataHash, 0, sizeof(m_DataHash));
12.7 +
12.8 + ke::AutoLock lock(g_Jit.Mutex());
12.9 + g_Jit.RegisterRuntime(this);
12.10 }
12.11
12.12 BaseRuntime::~BaseRuntime()
12.13 {
12.14 + // The watchdog thread takes the global JIT lock while it patches all
12.15 + // runtimes. It is not enough to ensure that the unlinking of the runtime is
12.16 + // protected; we cannot delete functions or code while the watchdog might be
12.17 + // executing. Therefore, the entire destructor is guarded.
12.18 + ke::AutoLock lock(g_Jit.Mutex());
12.19 +
12.20 + g_Jit.DeregisterRuntime(this);
12.21 +
12.22 for (uint32_t i = 0; i < m_plugin.num_publics; i++)
12.23 delete m_PubFuncs[i];
12.24 delete [] m_PubFuncs;
13.1 --- a/sourcepawn/jit/BaseRuntime.h Wed Aug 14 17:18:02 2013 -0400
13.2 +++ b/sourcepawn/jit/BaseRuntime.h Wed Aug 14 23:54:25 2013 -0700
13.3 @@ -4,6 +4,7 @@
13.4
13.5 #include <sp_vm_api.h>
13.6 #include <ke_vector.h>
13.7 +#include <ke_inline_list.h>
13.8 #include "jit_shared.h"
13.9 #include "sp_vm_function.h"
13.10
13.11 @@ -33,7 +34,9 @@
13.12 };
13.13
13.14 /* Jit wants fast access to this so we expose things as public */
13.15 -class BaseRuntime : public SourcePawn::IPluginRuntime
13.16 +class BaseRuntime
13.17 + : public SourcePawn::IPluginRuntime,
13.18 + public InlineListNode<BaseRuntime>
13.19 {
13.20 public:
13.21 BaseRuntime();
13.22 @@ -73,6 +76,13 @@
13.23 return &m_plugin;
13.24 }
13.25
13.26 + size_t NumJitFunctions() const {
13.27 + return m_JitFunctions.length();
13.28 + }
13.29 + JitFunction *GetJitFunction(size_t i) const {
13.30 + return m_JitFunctions[i];
13.31 + }
13.32 +
13.33 private:
13.34 void SetupFloatNativeRemapping();
13.35
13.36 @@ -103,3 +113,4 @@
13.37 };
13.38
13.39 #endif //_INCLUDE_SOURCEPAWN_JIT_RUNTIME_H_
13.40 +
14.1 --- a/sourcepawn/jit/Makefile.shell Wed Aug 14 17:18:02 2013 -0400
14.2 +++ b/sourcepawn/jit/Makefile.shell Wed Aug 14 23:54:25 2013 -0700
14.3 @@ -19,6 +19,7 @@
14.4 BaseRuntime.cpp \
14.5 jit_function.cpp \
14.6 opcodes.cpp \
14.7 + watchdog_timer.cpp \
14.8 md5/md5.cpp \
14.9 zlib/adler32.c \
14.10 zlib/compress.c \
14.11 @@ -47,7 +48,7 @@
14.12 CXX = c++
14.13 CC = cc
14.14
14.15 -LINK = -m32 -lm
14.16 +LINK = -m32 -lm -lpthread -lrt
14.17
14.18 INCLUDE = -I. -I.. -I$(SMSDK)/public -I$(SMSDK)/public/jit -I$(SMSDK)/public/jit/x86 \
14.19 -I$(SMSDK)/public/sourcepawn -I$(MMSOURCE17)/core/sourcehook -I$(SMSDK)/knight/shared -Ix86
15.1 --- a/sourcepawn/jit/dll_exports.cpp Wed Aug 14 17:18:02 2013 -0400
15.2 +++ b/sourcepawn/jit/dll_exports.cpp Wed Aug 14 23:54:25 2013 -0700
15.3 @@ -100,7 +100,7 @@
15.4 while (error->GetTraceInfo(&stk_info))
15.5 {
15.6 fprintf(stderr,
15.7 - " [%d] Line %d, %s::%s()",
15.8 + " [%d] Line %d, %s::%s()\n",
15.9 i++,
15.10 stk_info.line,
15.11 stk_info.filename,
15.12 @@ -221,6 +221,7 @@
15.13
15.14 ShellDebugListener debug;
15.15 g_engine1.SetDebugListener(&debug);
15.16 + g_engine2.InstallWatchdogTimer(5000);
15.17
15.18 int errcode = Execute(argv[1]);
15.19
16.1 --- a/sourcepawn/jit/engine2.cpp Wed Aug 14 17:18:02 2013 -0400
16.2 +++ b/sourcepawn/jit/engine2.cpp Wed Aug 14 23:54:25 2013 -0700
16.3 @@ -1,3 +1,4 @@
16.4 +// vim: set ts=4 sw=4 tw=99 noet:
16.5 #include <sourcemod_version.h>
16.6 #include <stdio.h>
16.7 #include <stdlib.h>
16.8 @@ -7,6 +8,7 @@
16.9 #include "zlib/zlib.h"
16.10 #include "BaseRuntime.h"
16.11 #include "sp_vm_engine.h"
16.12 +#include "watchdog_timer.h"
16.13
16.14 using namespace SourcePawn;
16.15
16.16 @@ -210,3 +212,9 @@
16.17
16.18 return rt;
16.19 }
16.20 +
16.21 +bool SourcePawnEngine2::InstallWatchdogTimer(size_t timeout_ms)
16.22 +{
16.23 + return g_WatchdogTimer.Initialize(timeout_ms);
16.24 +}
16.25 +
17.1 --- a/sourcepawn/jit/engine2.h Wed Aug 14 17:18:02 2013 -0400
17.2 +++ b/sourcepawn/jit/engine2.h Wed Aug 14 23:54:25 2013 -0700
17.3 @@ -26,6 +26,7 @@
17.4 bool Initialize();
17.5 void Shutdown();
17.6 IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory);
17.7 + bool InstallWatchdogTimer(size_t timeout_ms);
17.8 public:
17.9 IProfiler *GetProfiler();
17.10 private:
18.1 --- a/sourcepawn/jit/jit_function.cpp Wed Aug 14 17:18:02 2013 -0400
18.2 +++ b/sourcepawn/jit/jit_function.cpp Wed Aug 14 23:54:25 2013 -0700
18.3 @@ -1,23 +1,31 @@
18.4 +// vim: set ts=8 ts=2 sw=2 tw=99 et:
18.5 #include "jit_function.h"
18.6 #include "sp_vm_engine.h"
18.7 #include "jit_x86.h"
18.8
18.9 -JitFunction::JitFunction(void *entry_addr, cell_t pcode_offs)
18.10 -: m_pEntryAddr(entry_addr), m_PcodeOffs(pcode_offs)
18.11 +JitFunction::JitFunction(void *entry_addr, cell_t pcode_offs, LoopEdge *edges, uint32_t nedges)
18.12 + : m_pEntryAddr(entry_addr),
18.13 + m_PcodeOffs(pcode_offs),
18.14 + edges_(edges),
18.15 + nedges_(nedges)
18.16 {
18.17 }
18.18
18.19 JitFunction::~JitFunction()
18.20 {
18.21 - g_Jit.FreeCode(m_pEntryAddr);
18.22 + delete [] edges_;
18.23 + g_Jit.FreeCode(m_pEntryAddr);
18.24 }
18.25
18.26 -void *JitFunction::GetEntryAddress()
18.27 +void *
18.28 +JitFunction::GetEntryAddress() const
18.29 {
18.30 - return m_pEntryAddr;
18.31 + return m_pEntryAddr;
18.32 }
18.33
18.34 -cell_t JitFunction::GetPCodeAddress()
18.35 +cell_t
18.36 +JitFunction::GetPCodeAddress() const
18.37 {
18.38 - return m_PcodeOffs;
18.39 + return m_PcodeOffs;
18.40 }
18.41 +
19.1 --- a/sourcepawn/jit/jit_function.h Wed Aug 14 17:18:02 2013 -0400
19.2 +++ b/sourcepawn/jit/jit_function.h Wed Aug 14 23:54:25 2013 -0700
19.3 @@ -1,19 +1,38 @@
19.4 +// vim: set ts=8 sts=2 sw=2 tw=99 et:
19.5 #ifndef _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_
19.6 #define _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_
19.7
19.8 #include <sp_vm_types.h>
19.9 +#include <ke_vector.h>
19.10 +
19.11 +struct LoopEdge
19.12 +{
19.13 + uint32_t offset;
19.14 + int32_t disp32;
19.15 +};
19.16
19.17 class JitFunction
19.18 {
19.19 -public:
19.20 - JitFunction(void *entry_addr, cell_t pcode_offs);
19.21 - ~JitFunction();
19.22 -public:
19.23 - void *GetEntryAddress();
19.24 - cell_t GetPCodeAddress();
19.25 -private:
19.26 - void *m_pEntryAddr;
19.27 - cell_t m_PcodeOffs;
19.28 + public:
19.29 + JitFunction(void *entry_addr, cell_t pcode_offs, LoopEdge *edges, uint32_t nedges);
19.30 + ~JitFunction();
19.31 +
19.32 + public:
19.33 + void *GetEntryAddress() const;
19.34 + cell_t GetPCodeAddress() const;
19.35 + uint32_t NumLoopEdges() const {
19.36 + return nedges_;
19.37 + }
19.38 + const LoopEdge &GetLoopEdge(size_t i) const {
19.39 + assert(i < nedges_);
19.40 + return edges_[i];
19.41 + }
19.42 +
19.43 + private:
19.44 + void *m_pEntryAddr;
19.45 + cell_t m_PcodeOffs;
19.46 + LoopEdge *edges_;
19.47 + uint32_t nedges_;
19.48 };
19.49
19.50 #endif //_INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_
20.1 --- a/sourcepawn/jit/sp_vm_basecontext.cpp Wed Aug 14 17:18:02 2013 -0400
20.2 +++ b/sourcepawn/jit/sp_vm_basecontext.cpp Wed Aug 14 23:54:25 2013 -0700
20.3 @@ -36,6 +36,7 @@
20.4 #include "sp_vm_api.h"
20.5 #include "sp_vm_basecontext.h"
20.6 #include "sp_vm_engine.h"
20.7 +#include "watchdog_timer.h"
20.8 #include "x86/jit_x86.h"
20.9
20.10 using namespace SourcePawn;
20.11 @@ -554,6 +555,9 @@
20.12
20.13 fnid = function->GetFunctionID();
20.14
20.15 + if (!g_WatchdogTimer.HandleInterrupt())
20.16 + return SP_ERROR_TIMEOUT;
20.17 +
20.18 if (fnid & 1)
20.19 {
20.20 public_id = fnid >> 1;
20.21 @@ -674,6 +678,9 @@
20.22 }
20.23 }
20.24
20.25 + if (ir == SP_ERROR_TIMEOUT)
20.26 + g_WatchdogTimer.NotifyTimeoutReceived();
20.27 +
20.28 if (ir != SP_ERROR_NONE)
20.29 {
20.30 g_engine1.ReportError(m_pRuntime, ir, m_MsgCache, save_rp);
21.1 --- a/sourcepawn/jit/sp_vm_engine.cpp Wed Aug 14 17:18:02 2013 -0400
21.2 +++ b/sourcepawn/jit/sp_vm_engine.cpp Wed Aug 14 23:54:25 2013 -0700
21.3 @@ -1,5 +1,5 @@
21.4 /**
21.5 - * vim: set ts=4 :
21.6 + * vim: set ts=4 sw=4 tw=99 et:
21.7 * =============================================================================
21.8 * SourcePawn
21.9 * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
21.10 @@ -58,7 +58,7 @@
21.11
21.12 using namespace SourcePawn;
21.13
21.14 -#define ERROR_MESSAGE_MAX 29
21.15 +#define ERROR_MESSAGE_MAX 30
21.16 static const char *g_ErrorMsgTable[] =
21.17 {
21.18 NULL,
21.19 @@ -90,7 +90,8 @@
21.20 "Plugin format is too old",
21.21 "Plugin format is too new",
21.22 "Out of memory",
21.23 - "Integer overflow"
21.24 + "Integer overflow",
21.25 + "Script execution timed out"
21.26 };
21.27
21.28 const char *SourcePawnEngine::GetErrorString(int error)
22.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
22.2 +++ b/sourcepawn/jit/watchdog_timer.cpp Wed Aug 14 23:54:25 2013 -0700
22.3 @@ -0,0 +1,164 @@
22.4 +// vim: set ts=8 sts=2 sw=2 tw=99 et:
22.5 +//
22.6 +// This file is part of SourcePawn.
22.7 +//
22.8 +// SourcePawn is free software: you can redistribute it and/or modify
22.9 +// it under the terms of the GNU General Public License as published by
22.10 +// the Free Software Foundation, either version 3 of the License, or
22.11 +// (at your option) any later version.
22.12 +//
22.13 +// SourcePawn is distributed in the hope that it will be useful,
22.14 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
22.15 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22.16 +// GNU General Public License for more details.
22.17 +//
22.18 +// You should have received a copy of the GNU General Public License
22.19 +// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
22.20 +#include "watchdog_timer.h"
22.21 +#include "x86/jit_x86.h"
22.22 +#include <string.h>
22.23 +
22.24 +WatchdogTimer g_WatchdogTimer;
22.25 +
22.26 +WatchdogTimer::WatchdogTimer()
22.27 + : terminate_(false),
22.28 + mainthread_(ke::GetCurrentThreadId()),
22.29 + last_frame_id_(0),
22.30 + second_timeout_(false),
22.31 + timedout_(false)
22.32 +{
22.33 +}
22.34 +
22.35 +WatchdogTimer::~WatchdogTimer()
22.36 +{
22.37 + Shutdown();
22.38 +}
22.39 +
22.40 +bool
22.41 +WatchdogTimer::Initialize(size_t timeout_ms)
22.42 +{
22.43 + if (thread_)
22.44 + return false;
22.45 +
22.46 + timeout_ms_ = timeout_ms;
22.47 +
22.48 + thread_ = new ke::Thread(this, "SM Watchdog");
22.49 + if (!thread_->Succeeded())
22.50 + return false;
22.51 +
22.52 + return true;
22.53 +}
22.54 +
22.55 +void
22.56 +WatchdogTimer::Shutdown()
22.57 +{
22.58 + if (terminate_ || !thread_)
22.59 + return;
22.60 +
22.61 + {
22.62 + ke::AutoLock lock(&cv_);
22.63 + terminate_ = true;
22.64 + cv_.Notify();
22.65 + }
22.66 + thread_->Join();
22.67 + thread_ = NULL;
22.68 +}
22.69 +
22.70 +void
22.71 +WatchdogTimer::Run()
22.72 +{
22.73 + ke::AutoLock lock(&cv_);
22.74 +
22.75 + // Initialize the frame id, so we don't have to wait longer on startup.
22.76 + last_frame_id_ = g_Jit.FrameId();
22.77 +
22.78 + while (!terminate_) {
22.79 + ke::WaitResult rv = cv_.Wait(timeout_ms_ / 2);
22.80 + if (terminate_)
22.81 + return;
22.82 +
22.83 + if (rv == ke::Wait_Error)
22.84 + return;
22.85 +
22.86 + if (rv != ke::Wait_Timeout)
22.87 + continue;
22.88 +
22.89 + // We reached a timeout. If the current frame is not equal to the last
22.90 + // frame, then we assume the server is still moving enough to process
22.91 + // frames. We also make sure JIT code is actually on the stack, since
22.92 + // our concept of frames won't move if JIT code is not running.
22.93 + //
22.94 + // Note that it's okay if these two race: it's just a heuristic, and
22.95 + // worst case, we'll miss something that might have timed out but
22.96 + // ended up resuming.
22.97 + uintptr_t frame_id = g_Jit.FrameId();
22.98 + if (frame_id != last_frame_id_ || !g_Jit.RunningCode()) {
22.99 + last_frame_id_ = frame_id;
22.100 + second_timeout_ = false;
22.101 + continue;
22.102 + }
22.103 +
22.104 + // We woke up with the same frame. We might be timing out. Wait another
22.105 + // time to see if so.
22.106 + if (!second_timeout_) {
22.107 + second_timeout_ = true;
22.108 + continue;
22.109 + }
22.110 +
22.111 + {
22.112 + // Prevent the JIT from linking or destroying runtimes and functions.
22.113 + ke::AutoLock lock(g_Jit.Mutex());
22.114 +
22.115 + // Set the timeout notification bit. If this is detected before any patched
22.116 + // JIT backedges are reached, the main thread will attempt to acquire the
22.117 + // monitor lock, and block until we call Wait().
22.118 + timedout_ = true;
22.119 +
22.120 + // Patch all jumps. This can race with the main thread's execution since
22.121 + // all code writes are 32-bit integer instruction operands, which are
22.122 + // guaranteed to be atomic on x86.
22.123 + g_Jit.PatchAllJumpsForTimeout();
22.124 + }
22.125 +
22.126 + // The JIT will be free to compile new functions while we wait, but it will
22.127 + // see the timeout bit set above and immediately bail out.
22.128 + cv_.Wait();
22.129 +
22.130 + second_timeout_ = false;
22.131 +
22.132 + // Reset the last frame ID to something unlikely to be chosen again soon.
22.133 + last_frame_id_--;
22.134 +
22.135 + // It's possible that Shutdown() raced with the timeout, and if so, we
22.136 + // must leave now to prevent a deadlock.
22.137 + if (terminate_)
22.138 + return;
22.139 + }
22.140 +}
22.141 +
22.142 +bool
22.143 +WatchdogTimer::NotifyTimeoutReceived()
22.144 +{
22.145 + // We are guaranteed that the watchdog thread is waiting for our
22.146 + // notification, and is therefore blocked. We take the JIT lock
22.147 + // anyway for sanity.
22.148 + {
22.149 + ke::AutoLock lock(g_Jit.Mutex());
22.150 + g_Jit.UnpatchAllJumpsFromTimeout();
22.151 + }
22.152 +
22.153 + timedout_ = false;
22.154 +
22.155 + // Wake up the watchdog thread, it's okay to keep processing now.
22.156 + ke::AutoLock lock(&cv_);
22.157 + cv_.Notify();
22.158 + return false;
22.159 +}
22.160 +
22.161 +bool
22.162 +WatchdogTimer::HandleInterrupt()
22.163 +{
22.164 + if (timedout_)
22.165 + return NotifyTimeoutReceived();
22.166 + return true;
22.167 +}
23.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
23.2 +++ b/sourcepawn/jit/watchdog_timer.h Wed Aug 14 23:54:25 2013 -0700
23.3 @@ -0,0 +1,61 @@
23.4 +// vim: set ts=8 sts=2 sw=2 tw=99 et:
23.5 +//
23.6 +// This file is part of SourcePawn.
23.7 +//
23.8 +// SourcePawn is free software: you can redistribute it and/or modify
23.9 +// it under the terms of the GNU General Public License as published by
23.10 +// the Free Software Foundation, either version 3 of the License, or
23.11 +// (at your option) any later version.
23.12 +//
23.13 +// SourcePawn is distributed in the hope that it will be useful,
23.14 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
23.15 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23.16 +// GNU General Public License for more details.
23.17 +//
23.18 +// You should have received a copy of the GNU General Public License
23.19 +// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
23.20 +#ifndef _include_sourcepawn_watchdog_timer_posix_h_
23.21 +#define _include_sourcepawn_watchdog_timer_posix_h_
23.22 +
23.23 +#include <stddef.h>
23.24 +#include <stdint.h>
23.25 +#include <ke_thread_utils.h>
23.26 +
23.27 +typedef bool (*WatchdogCallback)();
23.28 +
23.29 +class WatchdogTimer : public ke::IRunnable
23.30 +{
23.31 + public:
23.32 + WatchdogTimer();
23.33 + ~WatchdogTimer();
23.34 +
23.35 + bool Initialize(size_t timeout_ms);
23.36 + void Shutdown();
23.37 +
23.38 + // Called from main thread.
23.39 + bool NotifyTimeoutReceived();
23.40 + bool HandleInterrupt();
23.41 +
23.42 + private:
23.43 + // Watchdog thread.
23.44 + void Run();
23.45 +
23.46 + private:
23.47 + bool terminate_;
23.48 + size_t timeout_ms_;
23.49 + ke::ThreadId mainthread_;
23.50 +
23.51 + ke::AutoPtr<ke::Thread> thread_;
23.52 + ke::ConditionVariable cv_;
23.53 +
23.54 + // Accessed only on the watchdog thread.
23.55 + uintptr_t last_frame_id_;
23.56 + bool second_timeout_;
23.57 +
23.58 + // Accessed only on the main thread.
23.59 + bool timedout_;
23.60 +};
23.61 +
23.62 +extern WatchdogTimer g_WatchdogTimer;
23.63 +
23.64 +#endif // _include_sourcepawn_watchdog_timer_posix_h_
24.1 --- a/sourcepawn/jit/x86/jit_x86.cpp Wed Aug 14 17:18:02 2013 -0400
24.2 +++ b/sourcepawn/jit/x86/jit_x86.cpp Wed Aug 14 23:54:25 2013 -0700
24.3 @@ -37,6 +37,7 @@
24.4 #include "../engine2.h"
24.5 #include "../BaseRuntime.h"
24.6 #include "../sp_vm_basecontext.h"
24.7 +#include "watchdog_timer.h"
24.8
24.9 using namespace Knight;
24.10
24.11 @@ -50,7 +51,7 @@
24.12 KeCodeCache *g_pCodeCache = NULL;
24.13 ISourcePawnEngine *engine = &g_engine1;
24.14
24.15 -static inline void *
24.16 +static inline uint8_t *
24.17 LinkCode(AssemblerX86 &masm)
24.18 {
24.19 if (masm.outOfMemory())
24.20 @@ -61,7 +62,7 @@
24.21 return NULL;
24.22
24.23 masm.emitToExecutableMemory(code);
24.24 - return code;
24.25 + return reinterpret_cast<uint8_t *>(code);
24.26 }
24.27
24.28 static inline ConditionCode
24.29 @@ -249,7 +250,7 @@
24.30 return err;
24.31
24.32 cell_t *base = reinterpret_cast<cell_t *>(rt->plugin()->memory + ctx->hp);
24.33 - cell_t offs = GenerateArrayIndirectionVectors(base, argv, argc, autozero);
24.34 + cell_t offs = GenerateArrayIndirectionVectors(base, argv, argc, !!autozero);
24.35 assert(size_t(offs) == cells);
24.36
24.37 argv[argc - 1] = ctx->hp;
24.38 @@ -314,6 +315,12 @@
24.39 static int
24.40 CompileFromThunk(BaseRuntime *runtime, cell_t pcode_offs, void **addrp, char *pc)
24.41 {
24.42 + // If the watchdog timer has declared a timeout, we must process it now,
24.43 + // and possibly refuse to compile, since otherwise we will compile a
24.44 + // function that is not patched for timeouts.
24.45 + if (!g_WatchdogTimer.HandleInterrupt())
24.46 + return SP_ERROR_TIMEOUT;
24.47 +
24.48 JitFunction *fn = runtime->GetJittedFunctionByOffset(pcode_offs);
24.49 if (!fn) {
24.50 int err;
24.51 @@ -452,13 +459,19 @@
24.52 emitCallThunks();
24.53 emitErrorPaths();
24.54
24.55 - void *code = LinkCode(masm);
24.56 + uint8_t *code = LinkCode(masm);
24.57 if (!code) {
24.58 *errp = SP_ERROR_OUT_OF_MEMORY;
24.59 return NULL;
24.60 }
24.61
24.62 - return new JitFunction(code, pcode_start_);
24.63 + LoopEdge *edges = new LoopEdge[backward_jumps_.length()];
24.64 + for (size_t i = 0; i < backward_jumps_.length(); i++) {
24.65 + edges[i].offset = backward_jumps_[i];
24.66 + edges[i].disp32 = *reinterpret_cast<int32_t *>(code + edges[i].offset - 4);
24.67 + }
24.68 +
24.69 + return new JitFunction(code, pcode_start_, edges, backward_jumps_.length());
24.70 }
24.71
24.72 bool
24.73 @@ -1228,7 +1241,12 @@
24.74 Label *target = labelAt(readCell());
24.75 if (!target)
24.76 return false;
24.77 - __ jmp(target);
24.78 + if (target->bound()) {
24.79 + __ jmp32(target);
24.80 + backward_jumps_.append(masm.pc());
24.81 + } else {
24.82 + __ jmp(target);
24.83 + }
24.84 break;
24.85 }
24.86
24.87 @@ -1240,7 +1258,12 @@
24.88 if (!target)
24.89 return false;
24.90 __ testl(pri, pri);
24.91 - __ j(cc, target);
24.92 + if (target->bound()) {
24.93 + __ j32(cc, target);
24.94 + backward_jumps_.append(masm.pc());
24.95 + } else {
24.96 + __ j(cc, target);
24.97 + }
24.98 break;
24.99 }
24.100
24.101 @@ -1256,7 +1279,12 @@
24.102 return false;
24.103 ConditionCode cc = OpToCondition(op);
24.104 __ cmpl(pri, alt);
24.105 - __ j(cc, target);
24.106 + if (target->bound()) {
24.107 + __ j32(cc, target);
24.108 + backward_jumps_.append(masm.pc());
24.109 + } else {
24.110 + __ j(cc, target);
24.111 + }
24.112 break;
24.113 }
24.114
24.115 @@ -1755,7 +1783,7 @@
24.116 typedef int (*JIT_EXECUTE)(sp_context_t *ctx, uint8_t *memory, void *code);
24.117
24.118 static void *
24.119 -GenerateEntry(void **retp)
24.120 +GenerateEntry(void **retp, void **timeoutp)
24.121 {
24.122 AssemblerX86 masm;
24.123
24.124 @@ -1813,11 +1841,17 @@
24.125 __ movl(ecx, Operand(ebp, 8 + 4 * 0)); // ret-path expects ecx = ctx
24.126 __ jmp(&ret);
24.127
24.128 + Label timeout;
24.129 + __ bind(&timeout);
24.130 + __ movl(eax, SP_ERROR_TIMEOUT);
24.131 + __ jmp(&error);
24.132 +
24.133 void *code = LinkCode(masm);
24.134 if (!code)
24.135 return NULL;
24.136
24.137 *retp = reinterpret_cast<uint8_t *>(code) + error.offset();
24.138 + *timeoutp = reinterpret_cast<uint8_t *>(code) + timeout.offset();
24.139 return code;
24.140 }
24.141
24.142 @@ -1841,11 +1875,12 @@
24.143 m_pJitEntry = NULL;
24.144 }
24.145
24.146 -bool JITX86::InitializeJIT()
24.147 +bool
24.148 +JITX86::InitializeJIT()
24.149 {
24.150 g_pCodeCache = KE_CreateCodeCache();
24.151
24.152 - m_pJitEntry = GenerateEntry(&m_pJitReturn);
24.153 + m_pJitEntry = GenerateEntry(&m_pJitReturn, &m_pJitTimeout);
24.154 if (!m_pJitEntry)
24.155 return false;
24.156
24.157 @@ -1873,6 +1908,11 @@
24.158 JitFunction *fun = cc.emit(err);
24.159 if (!fun)
24.160 return NULL;
24.161 +
24.162 + // Grab the lock before linking code in, since the watchdog timer will look
24.163 + // at this list on another thread.
24.164 + ke::AutoLock lock(g_Jit.Mutex());
24.165 +
24.166 prt->AddJittedFunction(fun);
24.167 return fun;
24.168 }
24.169 @@ -1919,33 +1959,39 @@
24.170 return (SPVM_NATIVE_FUNC)LinkCode(masm);
24.171 }
24.172
24.173 -void JITX86::DestroyFakeNative(SPVM_NATIVE_FUNC func)
24.174 +void
24.175 +JITX86::DestroyFakeNative(SPVM_NATIVE_FUNC func)
24.176 {
24.177 KE_FreeCode(g_pCodeCache, (void *)func);
24.178 }
24.179
24.180 -ICompilation *JITX86::StartCompilation()
24.181 +ICompilation *
24.182 +JITX86::StartCompilation()
24.183 {
24.184 return new CompData;
24.185 }
24.186
24.187 -ICompilation *JITX86::StartCompilation(BaseRuntime *runtime)
24.188 +ICompilation *
24.189 +JITX86::StartCompilation(BaseRuntime *runtime)
24.190 {
24.191 return new CompData;
24.192 }
24.193
24.194 -void CompData::Abort()
24.195 +void
24.196 +CompData::Abort()
24.197 {
24.198 delete this;
24.199 }
24.200
24.201 -void JITX86::FreeContextVars(sp_context_t *ctx)
24.202 +void
24.203 +JITX86::FreeContextVars(sp_context_t *ctx)
24.204 {
24.205 free(((tracker_t *)(ctx->vm[JITVARS_TRACKER]))->pBase);
24.206 delete (tracker_t *)ctx->vm[JITVARS_TRACKER];
24.207 }
24.208
24.209 -bool CompData::SetOption(const char *key, const char *val)
24.210 +bool
24.211 +CompData::SetOption(const char *key, const char *val)
24.212 {
24.213 if (strcmp(key, SP_JITCONF_DEBUG) == 0)
24.214 return true;
24.215 @@ -1962,7 +2008,8 @@
24.216 return false;
24.217 }
24.218
24.219 -int JITX86::InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result)
24.220 +int
24.221 +JITX86::InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result)
24.222 {
24.223 sp_context_t *ctx = runtime->GetBaseContext()->GetCtx();
24.224
24.225 @@ -1970,18 +2017,75 @@
24.226 ctx->cip = fn->GetPCodeAddress();
24.227
24.228 JIT_EXECUTE pfn = (JIT_EXECUTE)m_pJitEntry;
24.229 +
24.230 + if (level_++ == 0)
24.231 + frame_id_++;
24.232 int err = pfn(ctx, runtime->plugin()->memory, fn->GetEntryAddress());
24.233 + level_--;
24.234
24.235 *result = ctx->rval;
24.236 return err;
24.237 }
24.238
24.239 -void *JITX86::AllocCode(size_t size)
24.240 +void *
24.241 +JITX86::AllocCode(size_t size)
24.242 {
24.243 return Knight::KE_AllocCode(g_pCodeCache, size);
24.244 }
24.245
24.246 -void JITX86::FreeCode(void *code)
24.247 +void
24.248 +JITX86::FreeCode(void *code)
24.249 {
24.250 KE_FreeCode(g_pCodeCache, code);
24.251 }
24.252 +
24.253 +void
24.254 +JITX86::RegisterRuntime(BaseRuntime *rt)
24.255 +{
24.256 + mutex_.AssertCurrentThreadOwns();
24.257 + runtimes_.insert(rt);
24.258 +}
24.259 +
24.260 +void
24.261 +JITX86::DeregisterRuntime(BaseRuntime *rt)
24.262 +{
24.263 + mutex_.AssertCurrentThreadOwns();
24.264 + runtimes_.erase(rt);
24.265 +}
24.266 +
24.267 +void
24.268 +JITX86::PatchAllJumpsForTimeout()
24.269 +{
24.270 + mutex_.AssertCurrentThreadOwns();
24.271 + for (InlineList<BaseRuntime>::iterator iter = runtimes_.begin(); iter != runtimes_.end(); iter++) {
24.272 + BaseRuntime *rt = *iter;
24.273 + for (size_t i = 0; i < rt->NumJitFunctions(); i++) {
24.274 + JitFunction *fun = rt->GetJitFunction(i);
24.275 + uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress());
24.276 +
24.277 + for (size_t j = 0; j < fun->NumLoopEdges(); j++) {
24.278 + const LoopEdge &e = fun->GetLoopEdge(j);
24.279 + int32_t diff = intptr_t(m_pJitTimeout) - intptr_t(base + e.offset);
24.280 + *reinterpret_cast<int32_t *>(base + e.offset - 4) = diff;
24.281 + }
24.282 + }
24.283 + }
24.284 +}
24.285 +
24.286 +void
24.287 +JITX86::UnpatchAllJumpsFromTimeout()
24.288 +{
24.289 + mutex_.AssertCurrentThreadOwns();
24.290 + for (InlineList<BaseRuntime>::iterator iter = runtimes_.begin(); iter != runtimes_.end(); iter++) {
24.291 + BaseRuntime *rt = *iter;
24.292 + for (size_t i = 0; i < rt->NumJitFunctions(); i++) {
24.293 + JitFunction *fun = rt->GetJitFunction(i);
24.294 + uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress());
24.295 +
24.296 + for (size_t j = 0; j < fun->NumLoopEdges(); j++) {
24.297 + const LoopEdge &e = fun->GetLoopEdge(j);
24.298 + *reinterpret_cast<int32_t *>(base + e.offset - 4) = e.disp32;
24.299 + }
24.300 + }
24.301 + }
24.302 +}
25.1 --- a/sourcepawn/jit/x86/jit_x86.h Wed Aug 14 17:18:02 2013 -0400
25.2 +++ b/sourcepawn/jit/x86/jit_x86.h Wed Aug 14 23:54:25 2013 -0700
25.3 @@ -1,34 +1,19 @@
25.4 -/**
25.5 - * vim: set ts=4 sw=4 tw=99 et:
25.6 - * =============================================================================
25.7 - * SourceMod
25.8 - * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
25.9 - * =============================================================================
25.10 - *
25.11 - * This program is free software; you can redistribute it and/or modify it under
25.12 - * the terms of the GNU General Public License, version 3.0, as published by the
25.13 - * Free Software Foundation.
25.14 - *
25.15 - * This program is distributed in the hope that it will be useful, but WITHOUT
25.16 - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
25.17 - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
25.18 - * details.
25.19 - *
25.20 - * You should have received a copy of the GNU General Public License along with
25.21 - * this program. If not, see <http://www.gnu.org/licenses/>.
25.22 - *
25.23 - * As a special exception, AlliedModders LLC gives you permission to link the
25.24 - * code of this program (as well as its derivative works) to "Half-Life 2," the
25.25 - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
25.26 - * by the Valve Corporation. You must obey the GNU General Public License in
25.27 - * all respects for all other code used. Additionally, AlliedModders LLC grants
25.28 - * this exception to all derivative works. AlliedModders LLC defines further
25.29 - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
25.30 - * or <http://www.sourcemod.net/license.php>.
25.31 - *
25.32 - * Version: $Id$
25.33 - */
25.34 -
25.35 +// vim: set ts=8 sts=2 sw=2 tw=99 et:
25.36 +//
25.37 +// This file is part of SourcePawn.
25.38 +//
25.39 +// SourcePawn is free software: you can redistribute it and/or modify
25.40 +// it under the terms of the GNU General Public License as published by
25.41 +// the Free Software Foundation, either version 3 of the License, or
25.42 +// (at your option) any later version.
25.43 +//
25.44 +// SourcePawn is distributed in the hope that it will be useful,
25.45 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
25.46 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25.47 +// GNU General Public License for more details.
25.48 +//
25.49 +// You should have received a copy of the GNU General Public License
25.50 +// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
25.51 #ifndef _INCLUDE_SOURCEPAWN_JIT_X86_H_
25.52 #define _INCLUDE_SOURCEPAWN_JIT_X86_H_
25.53
25.54 @@ -42,6 +27,7 @@
25.55 #include "sp_vm_basecontext.h"
25.56 #include "jit_function.h"
25.57 #include "opcodes.h"
25.58 +#include <ke_thread_utils.h>
25.59
25.60 using namespace SourcePawn;
25.61
25.62 @@ -155,6 +141,7 @@
25.63 cell_t *cip_;
25.64 cell_t *code_end_;
25.65 Label *jump_map_;
25.66 + ke::Vector<size_t> backward_jumps_;
25.67
25.68 // Errors
25.69 Label error_bounds_;
25.70 @@ -188,16 +175,36 @@
25.71 ICompilation *ApplyOptions(ICompilation *_IN, ICompilation *_OUT);
25.72 int InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result);
25.73
25.74 + void RegisterRuntime(BaseRuntime *rt);
25.75 + void DeregisterRuntime(BaseRuntime *rt);
25.76 + void PatchAllJumpsForTimeout();
25.77 + void UnpatchAllJumpsFromTimeout();
25.78 +
25.79 public:
25.80 ExternalAddress GetUniversalReturn() {
25.81 - return ExternalAddress(m_pJitReturn);
25.82 + return ExternalAddress(m_pJitReturn);
25.83 }
25.84 void *AllocCode(size_t size);
25.85 void FreeCode(void *code);
25.86
25.87 + uintptr_t FrameId() const {
25.88 + return frame_id_;
25.89 + }
25.90 + bool RunningCode() const {
25.91 + return level_ != 0;
25.92 + }
25.93 + ke::Mutex *Mutex() {
25.94 + return &mutex_;
25.95 + }
25.96 +
25.97 private:
25.98 void *m_pJitEntry; /* Entry function */
25.99 void *m_pJitReturn; /* Universal return address */
25.100 + void *m_pJitTimeout; /* Universal timeout address */
25.101 + InlineList<BaseRuntime> runtimes_;
25.102 + uintptr_t frame_id_;
25.103 + uintptr_t level_;
25.104 + ke::Mutex mutex_;
25.105 };
25.106
25.107 const Register pri = eax;
25.108 @@ -211,3 +218,4 @@
25.109 extern JITX86 g_Jit;
25.110
25.111 #endif //_INCLUDE_SOURCEPAWN_JIT_X86_H_
25.112 +