Implement a watchdog timer for scripts that take too long to execute (bug 5837, r=fyren).
--HG-- extra : rebase_source : ffacb38457eca581660ce8f15c444ad828b7fedd
This commit is contained in:
parent
74f1b800c2
commit
3ac43497b9
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* vim: set ts=4 sw=4 :
|
* vim: set ts=4 sw=4 tw=99 noet:
|
||||||
* =============================================================================
|
* =============================================================================
|
||||||
* SourceMod
|
* SourceMod
|
||||||
* Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved.
|
* Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved.
|
||||||
@ -311,6 +311,16 @@ void SourceModBase::StartSourceMod(bool late)
|
|||||||
{
|
{
|
||||||
extsys->LoadAutoExtension("updater.ext." PLATFORM_LIB_EXT);
|
extsys->LoadAutoExtension("updater.ext." PLATFORM_LIB_EXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *timeout = GetCoreConfigValue("SlowScriptTimeout");
|
||||||
|
if (timeout == NULL)
|
||||||
|
{
|
||||||
|
timeout = "8";
|
||||||
|
}
|
||||||
|
if (atoi(timeout) != 0)
|
||||||
|
{
|
||||||
|
g_pSourcePawn2->InstallWatchdogTimer(atoi(timeout) * 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool g_LevelEndBarrier = false;
|
static bool g_LevelEndBarrier = false;
|
||||||
|
@ -64,6 +64,11 @@ class Assembler
|
|||||||
return pos_ - buffer_;
|
return pos_ - buffer_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Current offset into the code stream.
|
||||||
|
uint32_t pc() const {
|
||||||
|
return uint32_t(pos_ - buffer_);
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void writeByte(uint8_t byte) {
|
void writeByte(uint8_t byte) {
|
||||||
write<uint8_t>(byte);
|
write<uint8_t>(byte);
|
||||||
@ -125,11 +130,6 @@ class Assembler
|
|||||||
return int32_t(pos_ - buffer_);
|
return int32_t(pos_ - buffer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pc is the unsigned version of position().
|
|
||||||
uint32_t pc() const {
|
|
||||||
return uint32_t(pos_ - buffer_);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void assertCanWrite(size_t bytes) {
|
void assertCanWrite(size_t bytes) {
|
||||||
assert(pos_ + bytes <= end_);
|
assert(pos_ + bytes <= end_);
|
||||||
|
@ -640,6 +640,10 @@ class AssemblerX86 : public Assembler
|
|||||||
emit2(0xdc, 0xc0 + src.code);
|
emit2(0xdc, 0xc0 + src.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void jmp32(Label *dest) {
|
||||||
|
emit1(0xe9);
|
||||||
|
emitJumpTarget(dest);
|
||||||
|
}
|
||||||
void jmp(Label *dest) {
|
void jmp(Label *dest) {
|
||||||
int8_t d8;
|
int8_t d8;
|
||||||
if (canEmitSmallJump(dest, &d8)) {
|
if (canEmitSmallJump(dest, &d8)) {
|
||||||
@ -655,6 +659,10 @@ class AssemblerX86 : public Assembler
|
|||||||
void jmp(const Operand &target) {
|
void jmp(const Operand &target) {
|
||||||
emit1(0xff, 4, target);
|
emit1(0xff, 4, target);
|
||||||
}
|
}
|
||||||
|
void j32(ConditionCode cc, Label *dest) {
|
||||||
|
emit2(0x0f, 0x80 + uint8_t(cc));
|
||||||
|
emitJumpTarget(dest);
|
||||||
|
}
|
||||||
void j(ConditionCode cc, Label *dest) {
|
void j(ConditionCode cc, Label *dest) {
|
||||||
int8_t d8;
|
int8_t d8;
|
||||||
if (canEmitSmallJump(dest, &d8)) {
|
if (canEmitSmallJump(dest, &d8)) {
|
||||||
|
137
public/sourcepawn/ke_inline_list.h
Normal file
137
public/sourcepawn/ke_inline_list.h
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* vim: set sts=2 ts=8 sw=2 tw=99 noet :
|
||||||
|
* =============================================================================
|
||||||
|
* SourcePawn
|
||||||
|
* Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
|
||||||
|
* =============================================================================
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||||
|
* Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||||
|
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||||
|
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||||
|
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||||
|
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||||
|
* this exception to all derivative works. AlliedModders LLC defines further
|
||||||
|
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||||
|
* or <http://www.sourcemod.net/license.php>.
|
||||||
|
*/
|
||||||
|
#ifndef _include_sourcepawn_inline_list_h_
|
||||||
|
#define _include_sourcepawn_inline_list_h_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
template <typename T> class InlineList;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class InlineListNode
|
||||||
|
{
|
||||||
|
friend class InlineList<T>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
InlineListNode()
|
||||||
|
: next_(NULL),
|
||||||
|
prev_(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineListNode(InlineListNode *next, InlineListNode *prev)
|
||||||
|
: next_(next),
|
||||||
|
prev_(prev)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
InlineListNode *next_;
|
||||||
|
InlineListNode *prev_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class InlineList
|
||||||
|
{
|
||||||
|
typedef InlineListNode<T> Node;
|
||||||
|
|
||||||
|
Node head_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
InlineList()
|
||||||
|
: head_(&head_, &head_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
class iterator
|
||||||
|
{
|
||||||
|
friend class InlineList;
|
||||||
|
Node *iter_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
iterator(Node *iter)
|
||||||
|
: iter_(iter)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator & operator ++() {
|
||||||
|
iter_ = iter_->next;
|
||||||
|
return *iter_;
|
||||||
|
}
|
||||||
|
iterator operator ++(int) {
|
||||||
|
iterator old(*this);
|
||||||
|
iter_ = iter_->next_;
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
T * operator *() {
|
||||||
|
return static_cast<T *>(iter_);
|
||||||
|
}
|
||||||
|
T * operator ->() {
|
||||||
|
return static_cast<T *>(iter_);
|
||||||
|
}
|
||||||
|
bool operator !=(const iterator &where) const {
|
||||||
|
return iter_ != where.iter_;
|
||||||
|
}
|
||||||
|
bool operator ==(const iterator &where) const {
|
||||||
|
return iter_ == where.iter_;
|
||||||
|
}
|
||||||
|
iterator prev() const {
|
||||||
|
iterator p(iter_->prev_);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
iterator next() const {
|
||||||
|
iterator p(iter_->next_);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
iterator begin() {
|
||||||
|
return iterator(head_.next_);
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator end() {
|
||||||
|
return iterator(&head_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void erase(Node *t) {
|
||||||
|
t->prev_->next_ = t->next_;
|
||||||
|
t->next_->prev_ = t->prev_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(Node *t) {
|
||||||
|
t->prev_ = head_.prev_;
|
||||||
|
t->next_ = &head_;
|
||||||
|
head_.prev_->next_ = t;
|
||||||
|
head_.prev_ = t;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _include_sourcepawn_inline_list_h_
|
||||||
|
|
188
public/sourcepawn/ke_thread_posix.h
Normal file
188
public/sourcepawn/ke_thread_posix.h
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// vim: set ts=8 sts=2 sw=2 tw=99 et:
|
||||||
|
//
|
||||||
|
// This file is part of SourcePawn.
|
||||||
|
//
|
||||||
|
// SourcePawn is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// SourcePawn is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#ifndef _include_sourcepawn_thread_posix_h_
|
||||||
|
#define _include_sourcepawn_thread_posix_h_
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#if defined(__linux__)
|
||||||
|
# include <sys/prctl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace ke {
|
||||||
|
|
||||||
|
class Mutex : public Lockable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Mutex() {
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
int rv =
|
||||||
|
#endif
|
||||||
|
pthread_mutex_init(&mutex_, NULL);
|
||||||
|
assert(rv == 0);
|
||||||
|
}
|
||||||
|
~Mutex() {
|
||||||
|
pthread_mutex_destroy(&mutex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoTryLock() {
|
||||||
|
return pthread_mutex_trylock(&mutex_) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoLock() {
|
||||||
|
pthread_mutex_lock(&mutex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoUnlock() {
|
||||||
|
pthread_mutex_unlock(&mutex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_t *raw() {
|
||||||
|
return &mutex_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
pthread_mutex_t mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Currently, this class only supports single-listener CVs.
|
||||||
|
class ConditionVariable : public Lockable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ConditionVariable() {
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
int rv =
|
||||||
|
#endif
|
||||||
|
pthread_cond_init(&cv_, NULL);
|
||||||
|
assert(rv == 0);
|
||||||
|
}
|
||||||
|
~ConditionVariable() {
|
||||||
|
pthread_cond_destroy(&cv_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoTryLock() {
|
||||||
|
return mutex_.DoTryLock();
|
||||||
|
}
|
||||||
|
void DoLock() {
|
||||||
|
mutex_.DoLock();
|
||||||
|
}
|
||||||
|
void DoUnlock() {
|
||||||
|
mutex_.DoUnlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notify() {
|
||||||
|
AssertCurrentThreadOwns();
|
||||||
|
pthread_cond_signal(&cv_);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitResult Wait(size_t timeout_ms) {
|
||||||
|
AssertCurrentThreadOwns();
|
||||||
|
|
||||||
|
struct timespec ts;
|
||||||
|
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
|
||||||
|
return Wait_Error;
|
||||||
|
|
||||||
|
ts.tv_sec += timeout_ms / 1000;
|
||||||
|
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
|
||||||
|
if (ts.tv_nsec >= 1000000000) {
|
||||||
|
ts.tv_sec++;
|
||||||
|
ts.tv_nsec -= 1000000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugSetUnlocked();
|
||||||
|
int rv = pthread_cond_timedwait(&cv_, mutex_.raw(), &ts);
|
||||||
|
DebugSetLocked();
|
||||||
|
|
||||||
|
if (rv == ETIMEDOUT)
|
||||||
|
return Wait_Timeout;
|
||||||
|
if (rv == 0)
|
||||||
|
return Wait_Signaled;
|
||||||
|
return Wait_Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitResult Wait() {
|
||||||
|
AssertCurrentThreadOwns();
|
||||||
|
|
||||||
|
DebugSetUnlocked();
|
||||||
|
int rv = pthread_cond_wait(&cv_, mutex_.raw());
|
||||||
|
DebugSetLocked();
|
||||||
|
|
||||||
|
if (rv == 0)
|
||||||
|
return Wait_Signaled;
|
||||||
|
return Wait_Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Mutex mutex_;
|
||||||
|
pthread_cond_t cv_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Thread
|
||||||
|
{
|
||||||
|
struct ThreadData {
|
||||||
|
IRunnable *run;
|
||||||
|
char name[17];
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
Thread(IRunnable *run, const char *name = NULL) {
|
||||||
|
ThreadData *data = new ThreadData;
|
||||||
|
data->run = run;
|
||||||
|
snprintf(data->name, sizeof(data->name), "%s", name ? name : "");
|
||||||
|
|
||||||
|
initialized_ = (pthread_create(&thread_, NULL, Main, data) == 0);
|
||||||
|
if (!initialized_)
|
||||||
|
delete data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Succeeded() const {
|
||||||
|
return initialized_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Join() {
|
||||||
|
if (!Succeeded())
|
||||||
|
return;
|
||||||
|
pthread_join(thread_, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void *Main(void *arg) {
|
||||||
|
AutoPtr<ThreadData> data((ThreadData *)arg);
|
||||||
|
|
||||||
|
if (data->name[0]) {
|
||||||
|
#if defined(__linux__)
|
||||||
|
prctl(PR_SET_NAME, (unsigned long)data->name);
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
int (*fn)(const char *) = (int (*)(const char *))dlsym(RTLD_DEFAULT, "pthread_setname_np");
|
||||||
|
if (fn)
|
||||||
|
fn(data->name);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
data->run->Run();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool initialized_;
|
||||||
|
pthread_t thread_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ke
|
||||||
|
|
||||||
|
#endif // _include_sourcepawn_thread_posix_h_
|
||||||
|
|
241
public/sourcepawn/ke_thread_utils.h
Normal file
241
public/sourcepawn/ke_thread_utils.h
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
// vim: set ts=8 sts=2 sw=2 tw=99 et:
|
||||||
|
//
|
||||||
|
// This file is part of SourcePawn.
|
||||||
|
//
|
||||||
|
// SourcePawn is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// SourcePawn is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#ifndef _include_sourcepawn_threads_
|
||||||
|
#define _include_sourcepawn_threads_
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# include <windows.h>
|
||||||
|
#else
|
||||||
|
# include <pthread.h>
|
||||||
|
#endif
|
||||||
|
#include <ke_utility.h>
|
||||||
|
|
||||||
|
// Thread primitives for SourcePawn.
|
||||||
|
//
|
||||||
|
// -- Mutexes --
|
||||||
|
//
|
||||||
|
// A Lockable is a mutual exclusion primitive. It can be owned by at most one
|
||||||
|
// thread at a time, and ownership blocks any other thread from taking taking
|
||||||
|
// ownership. Ownership must be acquired and released on the same thread.
|
||||||
|
// Lockables are not re-entrant.
|
||||||
|
//
|
||||||
|
// While a few classes support the Lockable interface, the simplest Lockable
|
||||||
|
// object that can be instantiated is a Mutex.
|
||||||
|
//
|
||||||
|
// -- Condition Variables --
|
||||||
|
//
|
||||||
|
// A ConditionVariable provides mutually exclusive access based on a
|
||||||
|
// condition ocurring. CVs provide two capabilities: Wait(), which will block
|
||||||
|
// until the condition is triggered, and Notify(), which signals any blocking
|
||||||
|
// thread that the condition has occurred.
|
||||||
|
//
|
||||||
|
// Condition variables have an underlying mutex lock. This lock must be
|
||||||
|
// acquired before calling Wait() or Notify(). It is automatically released
|
||||||
|
// once Wait begins blocking. This operation is atomic with respect to other
|
||||||
|
// threads and the mutex. For example, it is not possible for the lock to be
|
||||||
|
// acquired by another thread in between unlocking and blocking. Since Notify
|
||||||
|
// also requires the lock to be acquired, there is no risk of an event
|
||||||
|
// accidentally dissipating into thin air because it was sent before the other
|
||||||
|
// thread began blocking.
|
||||||
|
//
|
||||||
|
// When Wait() returns, the lock is automatically re-acquired. This operation
|
||||||
|
// is NOT atomic. In between waking up and re-acquiring the lock, another
|
||||||
|
// thread may steal the lock and issue another event. Applications must
|
||||||
|
// account for this. For example, a message pump should check that there are no
|
||||||
|
// messages left to process before blocking again.
|
||||||
|
//
|
||||||
|
// -- Threads --
|
||||||
|
//
|
||||||
|
// A Thread object, when created, spawns a new thread with the given callback
|
||||||
|
// (the callbacks must implement IRunnable). Threads have one method of
|
||||||
|
// interest, Join(), which will block until the thread's execution finishes.
|
||||||
|
// Deleting a thread object will free any operating system resources associated
|
||||||
|
// with that thread, if the thread has finished executing.
|
||||||
|
//
|
||||||
|
// Threads can fail to spawn; make sure to check Succeeded().
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace ke {
|
||||||
|
|
||||||
|
// Abstraction for accessing the current thread.
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
typedef HANDLE ThreadId;
|
||||||
|
|
||||||
|
static inline ThreadId GetCurrentThreadId()
|
||||||
|
{
|
||||||
|
return GetCurrentThread();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
typedef pthread_t ThreadId;
|
||||||
|
|
||||||
|
static inline ThreadId GetCurrentThreadId()
|
||||||
|
{
|
||||||
|
return pthread_self();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Classes which use non-reentrant, same-thread lock/unlock semantics should
|
||||||
|
// inherit from this and implement DoLock/DoUnlock.
|
||||||
|
class Lockable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Lockable()
|
||||||
|
{
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
owner_ = 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
virtual ~Lockable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryLock() {
|
||||||
|
if (DoTryLock()) {
|
||||||
|
DebugSetLocked();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lock() {
|
||||||
|
assert(Owner() != GetCurrentThreadId());
|
||||||
|
DoLock();
|
||||||
|
DebugSetLocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unlock() {
|
||||||
|
assert(Owner() == GetCurrentThreadId());
|
||||||
|
DebugSetUnlocked();
|
||||||
|
DoUnlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssertCurrentThreadOwns() const {
|
||||||
|
assert(Owner() == GetCurrentThreadId());
|
||||||
|
}
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
bool Locked() const {
|
||||||
|
return owner_ != 0;
|
||||||
|
}
|
||||||
|
ThreadId Owner() const {
|
||||||
|
return owner_;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
virtual bool DoTryLock() = 0;
|
||||||
|
virtual void DoLock() = 0;
|
||||||
|
virtual void DoUnlock() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void DebugSetUnlocked() {
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
owner_ = 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
void DebugSetLocked() {
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
owner_ = GetCurrentThreadId();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
ThreadId owner_;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// RAII for automatically locking and unlocking an object.
|
||||||
|
class AutoLock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AutoLock(Lockable *lock)
|
||||||
|
: lock_(lock)
|
||||||
|
{
|
||||||
|
lock_->Lock();
|
||||||
|
}
|
||||||
|
~AutoLock() {
|
||||||
|
lock_->Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Lockable *lock_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AutoTryLock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AutoTryLock(Lockable *lock)
|
||||||
|
{
|
||||||
|
lock_ = lock->TryLock() ? lock : NULL;
|
||||||
|
}
|
||||||
|
~AutoTryLock() {
|
||||||
|
if (lock_)
|
||||||
|
lock_->Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Lockable *lock_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// RAII for automatically unlocking and relocking an object.
|
||||||
|
class AutoUnlock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AutoUnlock(Lockable *lock)
|
||||||
|
: lock_(lock)
|
||||||
|
{
|
||||||
|
lock_->Unlock();
|
||||||
|
}
|
||||||
|
~AutoUnlock() {
|
||||||
|
lock_->Lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Lockable *lock_;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum WaitResult {
|
||||||
|
// Woke up because something happened.
|
||||||
|
Wait_Signaled,
|
||||||
|
|
||||||
|
// Woke up because nothing happened and a timeout was specified.
|
||||||
|
Wait_Timeout,
|
||||||
|
|
||||||
|
// Woke up, but because of an error.
|
||||||
|
Wait_Error
|
||||||
|
};
|
||||||
|
|
||||||
|
// This must be implemented in order to spawn a new thread.
|
||||||
|
class IRunnable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IRunnable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Run() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ke
|
||||||
|
|
||||||
|
// Include the actual thread implementations.
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# include "ke_thread_windows.h"
|
||||||
|
#else
|
||||||
|
# include "ke_thread_posix.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // _include_sourcepawn_threads_
|
||||||
|
|
144
public/sourcepawn/ke_thread_windows.h
Normal file
144
public/sourcepawn/ke_thread_windows.h
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// vim: set ts=8 sts=2 sw=2 tw=99 et:
|
||||||
|
//
|
||||||
|
// This file is part of SourcePawn.
|
||||||
|
//
|
||||||
|
// SourcePawn is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// SourcePawn is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#ifndef _include_sourcepawn_thread_windows_h_
|
||||||
|
#define _include_sourcepawn_thread_windows_h_
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace ke {
|
||||||
|
|
||||||
|
class CriticalSection : public Lockable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CriticalSection() {
|
||||||
|
InitializeCriticalSection(&cs_);
|
||||||
|
}
|
||||||
|
~CriticalSection() {
|
||||||
|
DeleteCriticalSection(&cs_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoTryLock() {
|
||||||
|
return !!TryEnterCriticalSection(&cs_);
|
||||||
|
}
|
||||||
|
void DoLock() {
|
||||||
|
EnterCriticalSection(&cs_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoUnlock() {
|
||||||
|
LeaveCriticalSection(&cs_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
CRITICAL_SECTION cs_;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef CriticalSection Mutex;
|
||||||
|
|
||||||
|
// Currently, this class only supports single-listener CVs.
|
||||||
|
class ConditionVariable : public Lockable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ConditionVariable() {
|
||||||
|
event_ = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
}
|
||||||
|
~ConditionVariable() {
|
||||||
|
CloseHandle(event_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoTryLock() {
|
||||||
|
return cs_.DoTryLock();
|
||||||
|
}
|
||||||
|
void DoLock() {
|
||||||
|
cs_.DoLock();
|
||||||
|
}
|
||||||
|
void DoUnlock() {
|
||||||
|
cs_.DoUnlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notify() {
|
||||||
|
AssertCurrentThreadOwns();
|
||||||
|
SetEvent(event_);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitResult Wait(size_t timeout_ms) {
|
||||||
|
// This will assert if the lock has not been acquired. We don't need to be
|
||||||
|
// atomic here, like pthread_cond_wait, because the event bit will stick
|
||||||
|
// until reset by a wait function.
|
||||||
|
Unlock();
|
||||||
|
DWORD rv = WaitForSingleObject(event_, timeout_ms);
|
||||||
|
Lock();
|
||||||
|
|
||||||
|
if (rv == WAIT_TIMEOUT)
|
||||||
|
return Wait_Timeout;
|
||||||
|
if (rv == WAIT_FAILED)
|
||||||
|
return Wait_Error;
|
||||||
|
return Wait_Signaled;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitResult Wait() {
|
||||||
|
return Wait(INFINITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
CriticalSection cs_;
|
||||||
|
HANDLE event_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Thread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Thread(IRunnable *run, const char *name = NULL) {
|
||||||
|
thread_ = CreateThread(NULL, 0, Main, run, 0, NULL);
|
||||||
|
}
|
||||||
|
~Thread() {
|
||||||
|
if (!thread_)
|
||||||
|
return;
|
||||||
|
CloseHandle(thread_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Succeeded() const {
|
||||||
|
return !!thread_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Join() {
|
||||||
|
if (!Succeeded())
|
||||||
|
return;
|
||||||
|
WaitForSingleObject(thread_, INFINITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static DWORD WINAPI Main(LPVOID arg) {
|
||||||
|
((IRunnable *)arg)->Run();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma pack(push, 8)
|
||||||
|
struct ThreadNameInfo {
|
||||||
|
DWORD dwType;
|
||||||
|
LPCSTR szName;
|
||||||
|
DWORD dwThreadID;
|
||||||
|
DWORD dwFlags;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
private:
|
||||||
|
HANDLE thread_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ke
|
||||||
|
|
||||||
|
#endif // _include_sourcepawn_thread_windows_h_
|
@ -48,34 +48,45 @@ static const size_t kKB = 1024;
|
|||||||
static const size_t kMB = 1024 * kKB;
|
static const size_t kMB = 1024 * kKB;
|
||||||
static const size_t kGB = 1024 * kMB;
|
static const size_t kGB = 1024 * kMB;
|
||||||
|
|
||||||
|
template <typename T> T
|
||||||
|
ReturnAndVoid(T &t)
|
||||||
|
{
|
||||||
|
T saved = t;
|
||||||
|
t = T();
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class AutoFree
|
class AutoPtr
|
||||||
{
|
{
|
||||||
T *t_;
|
T *t_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AutoFree()
|
AutoPtr()
|
||||||
: t_(NULL)
|
: t_(NULL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
AutoFree(T *t)
|
explicit AutoPtr(T *t)
|
||||||
: t_(t)
|
: t_(t)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
~AutoFree() {
|
~AutoPtr() {
|
||||||
free(t_);
|
delete t_;
|
||||||
}
|
}
|
||||||
T *take() {
|
T *take() {
|
||||||
T *t = t_;
|
return ReturnAndVoid(t_);
|
||||||
t_ = NULL;
|
|
||||||
return t;
|
|
||||||
}
|
}
|
||||||
T *operator *() const {
|
T *operator *() const {
|
||||||
return t_;
|
return t_;
|
||||||
}
|
}
|
||||||
|
T *operator ->() const {
|
||||||
|
return t_;
|
||||||
|
}
|
||||||
|
operator T *() const {
|
||||||
|
return t_;
|
||||||
|
}
|
||||||
void operator =(T *t) {
|
void operator =(T *t) {
|
||||||
if (t_)
|
delete t_;
|
||||||
free(t_);
|
|
||||||
t_ = t;
|
t_ = t;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -289,14 +300,6 @@ Max(const T &t1, const T &t2)
|
|||||||
return t1 > t2 ? t1 : t2;
|
return t1 > t2 ? t1 : t2;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> T
|
|
||||||
ReturnAndVoid(T &t)
|
|
||||||
{
|
|
||||||
T saved = t;
|
|
||||||
t = T();
|
|
||||||
return saved;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define OFFSETOF(Class, Member) reinterpret_cast<size_t>(&((Class *)NULL)->Member)
|
#define OFFSETOF(Class, Member) reinterpret_cast<size_t>(&((Class *)NULL)->Member)
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
/** SourcePawn Engine API Version */
|
/** SourcePawn Engine API Version */
|
||||||
#define SOURCEPAWN_ENGINE_API_VERSION 4
|
#define SOURCEPAWN_ENGINE_API_VERSION 4
|
||||||
#define SOURCEPAWN_ENGINE2_API_VERSION 4
|
#define SOURCEPAWN_ENGINE2_API_VERSION 5
|
||||||
|
|
||||||
#if !defined SOURCEMOD_BUILD
|
#if !defined SOURCEMOD_BUILD
|
||||||
#define SOURCEMOD_BUILD
|
#define SOURCEMOD_BUILD
|
||||||
@ -1286,6 +1286,15 @@ namespace SourcePawn
|
|||||||
* @return New runtime, or NULL if not enough memory.
|
* @return New runtime, or NULL if not enough memory.
|
||||||
*/
|
*/
|
||||||
virtual IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory) =0;
|
virtual IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory) =0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initiates the watchdog timer with the specified timeout
|
||||||
|
* length. This cannot be called more than once.
|
||||||
|
*
|
||||||
|
* @param timeout Timeout, in ms.
|
||||||
|
* @return True on success, false on failure.
|
||||||
|
*/
|
||||||
|
virtual bool InstallWatchdogTimer(size_t timeout_ms) =0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,6 +87,7 @@ typedef uint32_t funcid_t; /**< Function index code */
|
|||||||
#define SP_ERROR_CODE_TOO_NEW 27 /**< Code is too new for this VM */
|
#define SP_ERROR_CODE_TOO_NEW 27 /**< Code is too new for this VM */
|
||||||
#define SP_ERROR_OUT_OF_MEMORY 28 /**< Out of memory */
|
#define SP_ERROR_OUT_OF_MEMORY 28 /**< Out of memory */
|
||||||
#define SP_ERROR_INTEGER_OVERFLOW 29 /**< Integer overflow (-INT_MIN / -1) */
|
#define SP_ERROR_INTEGER_OVERFLOW 29 /**< Integer overflow (-INT_MIN / -1) */
|
||||||
|
#define SP_ERROR_TIMEOUT 30 /**< Timeout */
|
||||||
//Hey you! Update the string table if you add to the end of me! */
|
//Hey you! Update the string table if you add to the end of me! */
|
||||||
|
|
||||||
/**********************************************
|
/**********************************************
|
||||||
|
@ -12,6 +12,10 @@ compiler['CXXINCLUDES'].append(os.path.join(base, 'public', 'jit'))
|
|||||||
compiler['CXXINCLUDES'].append(os.path.join(base, 'public', 'jit', 'x86'))
|
compiler['CXXINCLUDES'].append(os.path.join(base, 'public', 'jit', 'x86'))
|
||||||
compiler['CXXINCLUDES'].append(os.path.join(base, 'knight', 'shared'))
|
compiler['CXXINCLUDES'].append(os.path.join(base, 'knight', 'shared'))
|
||||||
|
|
||||||
|
if AMBuild.target['platform'] == 'linux':
|
||||||
|
compiler['POSTLINKFLAGS'].append('-lpthread')
|
||||||
|
compiler['POSTLINKFLAGS'].append('-lrt')
|
||||||
|
|
||||||
extension = AMBuild.AddJob('sourcepawn.jit.x86')
|
extension = AMBuild.AddJob('sourcepawn.jit.x86')
|
||||||
binary = Cpp.LibraryBuilder('sourcepawn.jit.x86', AMBuild, extension, compiler)
|
binary = Cpp.LibraryBuilder('sourcepawn.jit.x86', AMBuild, extension, compiler)
|
||||||
binary.AddSourceFiles('sourcepawn/jit', [
|
binary.AddSourceFiles('sourcepawn/jit', [
|
||||||
@ -23,6 +27,7 @@ binary.AddSourceFiles('sourcepawn/jit', [
|
|||||||
'sp_vm_engine.cpp',
|
'sp_vm_engine.cpp',
|
||||||
'sp_vm_function.cpp',
|
'sp_vm_function.cpp',
|
||||||
'opcodes.cpp',
|
'opcodes.cpp',
|
||||||
|
'watchdog_timer.cpp',
|
||||||
'x86/jit_x86.cpp',
|
'x86/jit_x86.cpp',
|
||||||
'zlib/adler32.c',
|
'zlib/adler32.c',
|
||||||
'zlib/compress.c',
|
'zlib/compress.c',
|
||||||
|
@ -37,10 +37,21 @@ BaseRuntime::BaseRuntime()
|
|||||||
|
|
||||||
memset(m_CodeHash, 0, sizeof(m_CodeHash));
|
memset(m_CodeHash, 0, sizeof(m_CodeHash));
|
||||||
memset(m_DataHash, 0, sizeof(m_DataHash));
|
memset(m_DataHash, 0, sizeof(m_DataHash));
|
||||||
|
|
||||||
|
ke::AutoLock lock(g_Jit.Mutex());
|
||||||
|
g_Jit.RegisterRuntime(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseRuntime::~BaseRuntime()
|
BaseRuntime::~BaseRuntime()
|
||||||
{
|
{
|
||||||
|
// The watchdog thread takes the global JIT lock while it patches all
|
||||||
|
// runtimes. It is not enough to ensure that the unlinking of the runtime is
|
||||||
|
// protected; we cannot delete functions or code while the watchdog might be
|
||||||
|
// executing. Therefore, the entire destructor is guarded.
|
||||||
|
ke::AutoLock lock(g_Jit.Mutex());
|
||||||
|
|
||||||
|
g_Jit.DeregisterRuntime(this);
|
||||||
|
|
||||||
for (uint32_t i = 0; i < m_plugin.num_publics; i++)
|
for (uint32_t i = 0; i < m_plugin.num_publics; i++)
|
||||||
delete m_PubFuncs[i];
|
delete m_PubFuncs[i];
|
||||||
delete [] m_PubFuncs;
|
delete [] m_PubFuncs;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <sp_vm_api.h>
|
#include <sp_vm_api.h>
|
||||||
#include <ke_vector.h>
|
#include <ke_vector.h>
|
||||||
|
#include <ke_inline_list.h>
|
||||||
#include "jit_shared.h"
|
#include "jit_shared.h"
|
||||||
#include "sp_vm_function.h"
|
#include "sp_vm_function.h"
|
||||||
|
|
||||||
@ -33,7 +34,9 @@ struct floattbl_t
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* Jit wants fast access to this so we expose things as public */
|
/* Jit wants fast access to this so we expose things as public */
|
||||||
class BaseRuntime : public SourcePawn::IPluginRuntime
|
class BaseRuntime
|
||||||
|
: public SourcePawn::IPluginRuntime,
|
||||||
|
public InlineListNode<BaseRuntime>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BaseRuntime();
|
BaseRuntime();
|
||||||
@ -73,6 +76,13 @@ class BaseRuntime : public SourcePawn::IPluginRuntime
|
|||||||
return &m_plugin;
|
return &m_plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t NumJitFunctions() const {
|
||||||
|
return m_JitFunctions.length();
|
||||||
|
}
|
||||||
|
JitFunction *GetJitFunction(size_t i) const {
|
||||||
|
return m_JitFunctions[i];
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetupFloatNativeRemapping();
|
void SetupFloatNativeRemapping();
|
||||||
|
|
||||||
@ -103,3 +113,4 @@ class BaseRuntime : public SourcePawn::IPluginRuntime
|
|||||||
};
|
};
|
||||||
|
|
||||||
#endif //_INCLUDE_SOURCEPAWN_JIT_RUNTIME_H_
|
#endif //_INCLUDE_SOURCEPAWN_JIT_RUNTIME_H_
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ OBJECTS = dll_exports.cpp \
|
|||||||
BaseRuntime.cpp \
|
BaseRuntime.cpp \
|
||||||
jit_function.cpp \
|
jit_function.cpp \
|
||||||
opcodes.cpp \
|
opcodes.cpp \
|
||||||
|
watchdog_timer.cpp \
|
||||||
md5/md5.cpp \
|
md5/md5.cpp \
|
||||||
zlib/adler32.c \
|
zlib/adler32.c \
|
||||||
zlib/compress.c \
|
zlib/compress.c \
|
||||||
@ -47,7 +48,7 @@ CXX_GCC4_FLAGS = -fvisibility-inlines-hidden
|
|||||||
CXX = c++
|
CXX = c++
|
||||||
CC = cc
|
CC = cc
|
||||||
|
|
||||||
LINK = -m32 -lm
|
LINK = -m32 -lm -lpthread -lrt
|
||||||
|
|
||||||
INCLUDE = -I. -I.. -I$(SMSDK)/public -I$(SMSDK)/public/jit -I$(SMSDK)/public/jit/x86 \
|
INCLUDE = -I. -I.. -I$(SMSDK)/public -I$(SMSDK)/public/jit -I$(SMSDK)/public/jit/x86 \
|
||||||
-I$(SMSDK)/public/sourcepawn -I$(MMSOURCE17)/core/sourcehook -I$(SMSDK)/knight/shared -Ix86
|
-I$(SMSDK)/public/sourcepawn -I$(MMSOURCE17)/core/sourcehook -I$(SMSDK)/knight/shared -Ix86
|
||||||
|
@ -100,7 +100,7 @@ public:
|
|||||||
while (error->GetTraceInfo(&stk_info))
|
while (error->GetTraceInfo(&stk_info))
|
||||||
{
|
{
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
" [%d] Line %d, %s::%s()",
|
" [%d] Line %d, %s::%s()\n",
|
||||||
i++,
|
i++,
|
||||||
stk_info.line,
|
stk_info.line,
|
||||||
stk_info.filename,
|
stk_info.filename,
|
||||||
@ -221,6 +221,7 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
ShellDebugListener debug;
|
ShellDebugListener debug;
|
||||||
g_engine1.SetDebugListener(&debug);
|
g_engine1.SetDebugListener(&debug);
|
||||||
|
g_engine2.InstallWatchdogTimer(5000);
|
||||||
|
|
||||||
int errcode = Execute(argv[1]);
|
int errcode = Execute(argv[1]);
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// vim: set ts=4 sw=4 tw=99 noet:
|
||||||
#include <sourcemod_version.h>
|
#include <sourcemod_version.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -7,6 +8,7 @@
|
|||||||
#include "zlib/zlib.h"
|
#include "zlib/zlib.h"
|
||||||
#include "BaseRuntime.h"
|
#include "BaseRuntime.h"
|
||||||
#include "sp_vm_engine.h"
|
#include "sp_vm_engine.h"
|
||||||
|
#include "watchdog_timer.h"
|
||||||
|
|
||||||
using namespace SourcePawn;
|
using namespace SourcePawn;
|
||||||
|
|
||||||
@ -210,3 +212,9 @@ IPluginRuntime *SourcePawnEngine2::CreateEmptyRuntime(const char *name, uint32_t
|
|||||||
|
|
||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SourcePawnEngine2::InstallWatchdogTimer(size_t timeout_ms)
|
||||||
|
{
|
||||||
|
return g_WatchdogTimer.Initialize(timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ namespace SourcePawn
|
|||||||
bool Initialize();
|
bool Initialize();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory);
|
IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory);
|
||||||
|
bool InstallWatchdogTimer(size_t timeout_ms);
|
||||||
public:
|
public:
|
||||||
IProfiler *GetProfiler();
|
IProfiler *GetProfiler();
|
||||||
private:
|
private:
|
||||||
|
@ -1,23 +1,31 @@
|
|||||||
|
// vim: set ts=8 ts=2 sw=2 tw=99 et:
|
||||||
#include "jit_function.h"
|
#include "jit_function.h"
|
||||||
#include "sp_vm_engine.h"
|
#include "sp_vm_engine.h"
|
||||||
#include "jit_x86.h"
|
#include "jit_x86.h"
|
||||||
|
|
||||||
JitFunction::JitFunction(void *entry_addr, cell_t pcode_offs)
|
JitFunction::JitFunction(void *entry_addr, cell_t pcode_offs, LoopEdge *edges, uint32_t nedges)
|
||||||
: m_pEntryAddr(entry_addr), m_PcodeOffs(pcode_offs)
|
: m_pEntryAddr(entry_addr),
|
||||||
|
m_PcodeOffs(pcode_offs),
|
||||||
|
edges_(edges),
|
||||||
|
nedges_(nedges)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
JitFunction::~JitFunction()
|
JitFunction::~JitFunction()
|
||||||
{
|
{
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,38 @@
|
|||||||
|
// vim: set ts=8 sts=2 sw=2 tw=99 et:
|
||||||
#ifndef _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_
|
#ifndef _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_
|
||||||
#define _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_
|
#define _INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_
|
||||||
|
|
||||||
#include <sp_vm_types.h>
|
#include <sp_vm_types.h>
|
||||||
|
#include <ke_vector.h>
|
||||||
|
|
||||||
|
struct LoopEdge
|
||||||
|
{
|
||||||
|
uint32_t offset;
|
||||||
|
int32_t disp32;
|
||||||
|
};
|
||||||
|
|
||||||
class JitFunction
|
class JitFunction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
JitFunction(void *entry_addr, cell_t pcode_offs);
|
JitFunction(void *entry_addr, cell_t pcode_offs, LoopEdge *edges, uint32_t nedges);
|
||||||
~JitFunction();
|
~JitFunction();
|
||||||
public:
|
|
||||||
void *GetEntryAddress();
|
public:
|
||||||
cell_t GetPCodeAddress();
|
void *GetEntryAddress() const;
|
||||||
private:
|
cell_t GetPCodeAddress() const;
|
||||||
void *m_pEntryAddr;
|
uint32_t NumLoopEdges() const {
|
||||||
cell_t m_PcodeOffs;
|
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_
|
#endif //_INCLUDE_SOURCEPAWN_JIT2_FUNCTION_H_
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include "sp_vm_api.h"
|
#include "sp_vm_api.h"
|
||||||
#include "sp_vm_basecontext.h"
|
#include "sp_vm_basecontext.h"
|
||||||
#include "sp_vm_engine.h"
|
#include "sp_vm_engine.h"
|
||||||
|
#include "watchdog_timer.h"
|
||||||
#include "x86/jit_x86.h"
|
#include "x86/jit_x86.h"
|
||||||
|
|
||||||
using namespace SourcePawn;
|
using namespace SourcePawn;
|
||||||
@ -554,6 +555,9 @@ int BaseContext::Execute2(IPluginFunction *function, const cell_t *params, unsig
|
|||||||
|
|
||||||
fnid = function->GetFunctionID();
|
fnid = function->GetFunctionID();
|
||||||
|
|
||||||
|
if (!g_WatchdogTimer.HandleInterrupt())
|
||||||
|
return SP_ERROR_TIMEOUT;
|
||||||
|
|
||||||
if (fnid & 1)
|
if (fnid & 1)
|
||||||
{
|
{
|
||||||
public_id = fnid >> 1;
|
public_id = fnid >> 1;
|
||||||
@ -674,6 +678,9 @@ int BaseContext::Execute2(IPluginFunction *function, const cell_t *params, unsig
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ir == SP_ERROR_TIMEOUT)
|
||||||
|
g_WatchdogTimer.NotifyTimeoutReceived();
|
||||||
|
|
||||||
if (ir != SP_ERROR_NONE)
|
if (ir != SP_ERROR_NONE)
|
||||||
{
|
{
|
||||||
g_engine1.ReportError(m_pRuntime, ir, m_MsgCache, save_rp);
|
g_engine1.ReportError(m_pRuntime, ir, m_MsgCache, save_rp);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* vim: set ts=4 :
|
* vim: set ts=4 sw=4 tw=99 et:
|
||||||
* =============================================================================
|
* =============================================================================
|
||||||
* SourcePawn
|
* SourcePawn
|
||||||
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
||||||
@ -58,7 +58,7 @@ SourcePawnEngine g_engine1;
|
|||||||
|
|
||||||
using namespace SourcePawn;
|
using namespace SourcePawn;
|
||||||
|
|
||||||
#define ERROR_MESSAGE_MAX 29
|
#define ERROR_MESSAGE_MAX 30
|
||||||
static const char *g_ErrorMsgTable[] =
|
static const char *g_ErrorMsgTable[] =
|
||||||
{
|
{
|
||||||
NULL,
|
NULL,
|
||||||
@ -90,7 +90,8 @@ static const char *g_ErrorMsgTable[] =
|
|||||||
"Plugin format is too old",
|
"Plugin format is too old",
|
||||||
"Plugin format is too new",
|
"Plugin format is too new",
|
||||||
"Out of memory",
|
"Out of memory",
|
||||||
"Integer overflow"
|
"Integer overflow",
|
||||||
|
"Script execution timed out"
|
||||||
};
|
};
|
||||||
|
|
||||||
const char *SourcePawnEngine::GetErrorString(int error)
|
const char *SourcePawnEngine::GetErrorString(int error)
|
||||||
|
164
sourcepawn/jit/watchdog_timer.cpp
Normal file
164
sourcepawn/jit/watchdog_timer.cpp
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
// vim: set ts=8 sts=2 sw=2 tw=99 et:
|
||||||
|
//
|
||||||
|
// This file is part of SourcePawn.
|
||||||
|
//
|
||||||
|
// SourcePawn is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// SourcePawn is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#include "watchdog_timer.h"
|
||||||
|
#include "x86/jit_x86.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
WatchdogTimer g_WatchdogTimer;
|
||||||
|
|
||||||
|
WatchdogTimer::WatchdogTimer()
|
||||||
|
: terminate_(false),
|
||||||
|
mainthread_(ke::GetCurrentThreadId()),
|
||||||
|
last_frame_id_(0),
|
||||||
|
second_timeout_(false),
|
||||||
|
timedout_(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchdogTimer::~WatchdogTimer()
|
||||||
|
{
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
WatchdogTimer::Initialize(size_t timeout_ms)
|
||||||
|
{
|
||||||
|
if (thread_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
timeout_ms_ = timeout_ms;
|
||||||
|
|
||||||
|
thread_ = new ke::Thread(this, "SM Watchdog");
|
||||||
|
if (!thread_->Succeeded())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
WatchdogTimer::Shutdown()
|
||||||
|
{
|
||||||
|
if (terminate_ || !thread_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
{
|
||||||
|
ke::AutoLock lock(&cv_);
|
||||||
|
terminate_ = true;
|
||||||
|
cv_.Notify();
|
||||||
|
}
|
||||||
|
thread_->Join();
|
||||||
|
thread_ = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
WatchdogTimer::Run()
|
||||||
|
{
|
||||||
|
ke::AutoLock lock(&cv_);
|
||||||
|
|
||||||
|
// Initialize the frame id, so we don't have to wait longer on startup.
|
||||||
|
last_frame_id_ = g_Jit.FrameId();
|
||||||
|
|
||||||
|
while (!terminate_) {
|
||||||
|
ke::WaitResult rv = cv_.Wait(timeout_ms_ / 2);
|
||||||
|
if (terminate_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (rv == ke::Wait_Error)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (rv != ke::Wait_Timeout)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// We reached a timeout. If the current frame is not equal to the last
|
||||||
|
// frame, then we assume the server is still moving enough to process
|
||||||
|
// frames. We also make sure JIT code is actually on the stack, since
|
||||||
|
// our concept of frames won't move if JIT code is not running.
|
||||||
|
//
|
||||||
|
// Note that it's okay if these two race: it's just a heuristic, and
|
||||||
|
// worst case, we'll miss something that might have timed out but
|
||||||
|
// ended up resuming.
|
||||||
|
uintptr_t frame_id = g_Jit.FrameId();
|
||||||
|
if (frame_id != last_frame_id_ || !g_Jit.RunningCode()) {
|
||||||
|
last_frame_id_ = frame_id;
|
||||||
|
second_timeout_ = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We woke up with the same frame. We might be timing out. Wait another
|
||||||
|
// time to see if so.
|
||||||
|
if (!second_timeout_) {
|
||||||
|
second_timeout_ = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Prevent the JIT from linking or destroying runtimes and functions.
|
||||||
|
ke::AutoLock lock(g_Jit.Mutex());
|
||||||
|
|
||||||
|
// Set the timeout notification bit. If this is detected before any patched
|
||||||
|
// JIT backedges are reached, the main thread will attempt to acquire the
|
||||||
|
// monitor lock, and block until we call Wait().
|
||||||
|
timedout_ = true;
|
||||||
|
|
||||||
|
// Patch all jumps. This can race with the main thread's execution since
|
||||||
|
// all code writes are 32-bit integer instruction operands, which are
|
||||||
|
// guaranteed to be atomic on x86.
|
||||||
|
g_Jit.PatchAllJumpsForTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The JIT will be free to compile new functions while we wait, but it will
|
||||||
|
// see the timeout bit set above and immediately bail out.
|
||||||
|
cv_.Wait();
|
||||||
|
|
||||||
|
second_timeout_ = false;
|
||||||
|
|
||||||
|
// Reset the last frame ID to something unlikely to be chosen again soon.
|
||||||
|
last_frame_id_--;
|
||||||
|
|
||||||
|
// It's possible that Shutdown() raced with the timeout, and if so, we
|
||||||
|
// must leave now to prevent a deadlock.
|
||||||
|
if (terminate_)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
WatchdogTimer::NotifyTimeoutReceived()
|
||||||
|
{
|
||||||
|
// We are guaranteed that the watchdog thread is waiting for our
|
||||||
|
// notification, and is therefore blocked. We take the JIT lock
|
||||||
|
// anyway for sanity.
|
||||||
|
{
|
||||||
|
ke::AutoLock lock(g_Jit.Mutex());
|
||||||
|
g_Jit.UnpatchAllJumpsFromTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
timedout_ = false;
|
||||||
|
|
||||||
|
// Wake up the watchdog thread, it's okay to keep processing now.
|
||||||
|
ke::AutoLock lock(&cv_);
|
||||||
|
cv_.Notify();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
WatchdogTimer::HandleInterrupt()
|
||||||
|
{
|
||||||
|
if (timedout_)
|
||||||
|
return NotifyTimeoutReceived();
|
||||||
|
return true;
|
||||||
|
}
|
61
sourcepawn/jit/watchdog_timer.h
Normal file
61
sourcepawn/jit/watchdog_timer.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// vim: set ts=8 sts=2 sw=2 tw=99 et:
|
||||||
|
//
|
||||||
|
// This file is part of SourcePawn.
|
||||||
|
//
|
||||||
|
// SourcePawn is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// SourcePawn is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#ifndef _include_sourcepawn_watchdog_timer_posix_h_
|
||||||
|
#define _include_sourcepawn_watchdog_timer_posix_h_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <ke_thread_utils.h>
|
||||||
|
|
||||||
|
typedef bool (*WatchdogCallback)();
|
||||||
|
|
||||||
|
class WatchdogTimer : public ke::IRunnable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WatchdogTimer();
|
||||||
|
~WatchdogTimer();
|
||||||
|
|
||||||
|
bool Initialize(size_t timeout_ms);
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
// Called from main thread.
|
||||||
|
bool NotifyTimeoutReceived();
|
||||||
|
bool HandleInterrupt();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Watchdog thread.
|
||||||
|
void Run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool terminate_;
|
||||||
|
size_t timeout_ms_;
|
||||||
|
ke::ThreadId mainthread_;
|
||||||
|
|
||||||
|
ke::AutoPtr<ke::Thread> thread_;
|
||||||
|
ke::ConditionVariable cv_;
|
||||||
|
|
||||||
|
// Accessed only on the watchdog thread.
|
||||||
|
uintptr_t last_frame_id_;
|
||||||
|
bool second_timeout_;
|
||||||
|
|
||||||
|
// Accessed only on the main thread.
|
||||||
|
bool timedout_;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern WatchdogTimer g_WatchdogTimer;
|
||||||
|
|
||||||
|
#endif // _include_sourcepawn_watchdog_timer_posix_h_
|
@ -37,6 +37,7 @@
|
|||||||
#include "../engine2.h"
|
#include "../engine2.h"
|
||||||
#include "../BaseRuntime.h"
|
#include "../BaseRuntime.h"
|
||||||
#include "../sp_vm_basecontext.h"
|
#include "../sp_vm_basecontext.h"
|
||||||
|
#include "watchdog_timer.h"
|
||||||
|
|
||||||
using namespace Knight;
|
using namespace Knight;
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ JITX86 g_Jit;
|
|||||||
KeCodeCache *g_pCodeCache = NULL;
|
KeCodeCache *g_pCodeCache = NULL;
|
||||||
ISourcePawnEngine *engine = &g_engine1;
|
ISourcePawnEngine *engine = &g_engine1;
|
||||||
|
|
||||||
static inline void *
|
static inline uint8_t *
|
||||||
LinkCode(AssemblerX86 &masm)
|
LinkCode(AssemblerX86 &masm)
|
||||||
{
|
{
|
||||||
if (masm.outOfMemory())
|
if (masm.outOfMemory())
|
||||||
@ -61,7 +62,7 @@ LinkCode(AssemblerX86 &masm)
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
masm.emitToExecutableMemory(code);
|
masm.emitToExecutableMemory(code);
|
||||||
return code;
|
return reinterpret_cast<uint8_t *>(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline ConditionCode
|
static inline ConditionCode
|
||||||
@ -249,7 +250,7 @@ GenerateArray(BaseRuntime *rt, uint32_t argc, cell_t *argv, int autozero)
|
|||||||
return err;
|
return err;
|
||||||
|
|
||||||
cell_t *base = reinterpret_cast<cell_t *>(rt->plugin()->memory + ctx->hp);
|
cell_t *base = reinterpret_cast<cell_t *>(rt->plugin()->memory + ctx->hp);
|
||||||
cell_t offs = GenerateArrayIndirectionVectors(base, argv, argc, autozero);
|
cell_t offs = GenerateArrayIndirectionVectors(base, argv, argc, !!autozero);
|
||||||
assert(size_t(offs) == cells);
|
assert(size_t(offs) == cells);
|
||||||
|
|
||||||
argv[argc - 1] = ctx->hp;
|
argv[argc - 1] = ctx->hp;
|
||||||
@ -314,6 +315,12 @@ GetFunctionName(const sp_plugin_t *plugin, uint32_t offs)
|
|||||||
static int
|
static int
|
||||||
CompileFromThunk(BaseRuntime *runtime, cell_t pcode_offs, void **addrp, char *pc)
|
CompileFromThunk(BaseRuntime *runtime, cell_t pcode_offs, void **addrp, char *pc)
|
||||||
{
|
{
|
||||||
|
// If the watchdog timer has declared a timeout, we must process it now,
|
||||||
|
// and possibly refuse to compile, since otherwise we will compile a
|
||||||
|
// function that is not patched for timeouts.
|
||||||
|
if (!g_WatchdogTimer.HandleInterrupt())
|
||||||
|
return SP_ERROR_TIMEOUT;
|
||||||
|
|
||||||
JitFunction *fn = runtime->GetJittedFunctionByOffset(pcode_offs);
|
JitFunction *fn = runtime->GetJittedFunctionByOffset(pcode_offs);
|
||||||
if (!fn) {
|
if (!fn) {
|
||||||
int err;
|
int err;
|
||||||
@ -452,13 +459,19 @@ Compiler::emit(int *errp)
|
|||||||
emitCallThunks();
|
emitCallThunks();
|
||||||
emitErrorPaths();
|
emitErrorPaths();
|
||||||
|
|
||||||
void *code = LinkCode(masm);
|
uint8_t *code = LinkCode(masm);
|
||||||
if (!code) {
|
if (!code) {
|
||||||
*errp = SP_ERROR_OUT_OF_MEMORY;
|
*errp = SP_ERROR_OUT_OF_MEMORY;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JitFunction(code, pcode_start_);
|
LoopEdge *edges = new LoopEdge[backward_jumps_.length()];
|
||||||
|
for (size_t i = 0; i < backward_jumps_.length(); i++) {
|
||||||
|
edges[i].offset = backward_jumps_[i];
|
||||||
|
edges[i].disp32 = *reinterpret_cast<int32_t *>(code + edges[i].offset - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JitFunction(code, pcode_start_, edges, backward_jumps_.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -1228,7 +1241,12 @@ Compiler::emitOp(OPCODE op)
|
|||||||
Label *target = labelAt(readCell());
|
Label *target = labelAt(readCell());
|
||||||
if (!target)
|
if (!target)
|
||||||
return false;
|
return false;
|
||||||
__ jmp(target);
|
if (target->bound()) {
|
||||||
|
__ jmp32(target);
|
||||||
|
backward_jumps_.append(masm.pc());
|
||||||
|
} else {
|
||||||
|
__ jmp(target);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1240,7 +1258,12 @@ Compiler::emitOp(OPCODE op)
|
|||||||
if (!target)
|
if (!target)
|
||||||
return false;
|
return false;
|
||||||
__ testl(pri, pri);
|
__ testl(pri, pri);
|
||||||
__ j(cc, target);
|
if (target->bound()) {
|
||||||
|
__ j32(cc, target);
|
||||||
|
backward_jumps_.append(masm.pc());
|
||||||
|
} else {
|
||||||
|
__ j(cc, target);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1256,7 +1279,12 @@ Compiler::emitOp(OPCODE op)
|
|||||||
return false;
|
return false;
|
||||||
ConditionCode cc = OpToCondition(op);
|
ConditionCode cc = OpToCondition(op);
|
||||||
__ cmpl(pri, alt);
|
__ cmpl(pri, alt);
|
||||||
__ j(cc, target);
|
if (target->bound()) {
|
||||||
|
__ j32(cc, target);
|
||||||
|
backward_jumps_.append(masm.pc());
|
||||||
|
} else {
|
||||||
|
__ j(cc, target);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1755,7 +1783,7 @@ Compiler::emitErrorPaths()
|
|||||||
typedef int (*JIT_EXECUTE)(sp_context_t *ctx, uint8_t *memory, void *code);
|
typedef int (*JIT_EXECUTE)(sp_context_t *ctx, uint8_t *memory, void *code);
|
||||||
|
|
||||||
static void *
|
static void *
|
||||||
GenerateEntry(void **retp)
|
GenerateEntry(void **retp, void **timeoutp)
|
||||||
{
|
{
|
||||||
AssemblerX86 masm;
|
AssemblerX86 masm;
|
||||||
|
|
||||||
@ -1813,11 +1841,17 @@ GenerateEntry(void **retp)
|
|||||||
__ movl(ecx, Operand(ebp, 8 + 4 * 0)); // ret-path expects ecx = ctx
|
__ movl(ecx, Operand(ebp, 8 + 4 * 0)); // ret-path expects ecx = ctx
|
||||||
__ jmp(&ret);
|
__ jmp(&ret);
|
||||||
|
|
||||||
|
Label timeout;
|
||||||
|
__ bind(&timeout);
|
||||||
|
__ movl(eax, SP_ERROR_TIMEOUT);
|
||||||
|
__ jmp(&error);
|
||||||
|
|
||||||
void *code = LinkCode(masm);
|
void *code = LinkCode(masm);
|
||||||
if (!code)
|
if (!code)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
*retp = reinterpret_cast<uint8_t *>(code) + error.offset();
|
*retp = reinterpret_cast<uint8_t *>(code) + error.offset();
|
||||||
|
*timeoutp = reinterpret_cast<uint8_t *>(code) + timeout.offset();
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1841,11 +1875,12 @@ JITX86::JITX86()
|
|||||||
m_pJitEntry = NULL;
|
m_pJitEntry = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JITX86::InitializeJIT()
|
bool
|
||||||
|
JITX86::InitializeJIT()
|
||||||
{
|
{
|
||||||
g_pCodeCache = KE_CreateCodeCache();
|
g_pCodeCache = KE_CreateCodeCache();
|
||||||
|
|
||||||
m_pJitEntry = GenerateEntry(&m_pJitReturn);
|
m_pJitEntry = GenerateEntry(&m_pJitReturn, &m_pJitTimeout);
|
||||||
if (!m_pJitEntry)
|
if (!m_pJitEntry)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -1873,6 +1908,11 @@ JITX86::CompileFunction(BaseRuntime *prt, cell_t pcode_offs, int *err)
|
|||||||
JitFunction *fun = cc.emit(err);
|
JitFunction *fun = cc.emit(err);
|
||||||
if (!fun)
|
if (!fun)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
// Grab the lock before linking code in, since the watchdog timer will look
|
||||||
|
// at this list on another thread.
|
||||||
|
ke::AutoLock lock(g_Jit.Mutex());
|
||||||
|
|
||||||
prt->AddJittedFunction(fun);
|
prt->AddJittedFunction(fun);
|
||||||
return fun;
|
return fun;
|
||||||
}
|
}
|
||||||
@ -1919,33 +1959,39 @@ JITX86::CreateFakeNative(SPVM_FAKENATIVE_FUNC callback, void *pData)
|
|||||||
return (SPVM_NATIVE_FUNC)LinkCode(masm);
|
return (SPVM_NATIVE_FUNC)LinkCode(masm);
|
||||||
}
|
}
|
||||||
|
|
||||||
void JITX86::DestroyFakeNative(SPVM_NATIVE_FUNC func)
|
void
|
||||||
|
JITX86::DestroyFakeNative(SPVM_NATIVE_FUNC func)
|
||||||
{
|
{
|
||||||
KE_FreeCode(g_pCodeCache, (void *)func);
|
KE_FreeCode(g_pCodeCache, (void *)func);
|
||||||
}
|
}
|
||||||
|
|
||||||
ICompilation *JITX86::StartCompilation()
|
ICompilation *
|
||||||
|
JITX86::StartCompilation()
|
||||||
{
|
{
|
||||||
return new CompData;
|
return new CompData;
|
||||||
}
|
}
|
||||||
|
|
||||||
ICompilation *JITX86::StartCompilation(BaseRuntime *runtime)
|
ICompilation *
|
||||||
|
JITX86::StartCompilation(BaseRuntime *runtime)
|
||||||
{
|
{
|
||||||
return new CompData;
|
return new CompData;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompData::Abort()
|
void
|
||||||
|
CompData::Abort()
|
||||||
{
|
{
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void JITX86::FreeContextVars(sp_context_t *ctx)
|
void
|
||||||
|
JITX86::FreeContextVars(sp_context_t *ctx)
|
||||||
{
|
{
|
||||||
free(((tracker_t *)(ctx->vm[JITVARS_TRACKER]))->pBase);
|
free(((tracker_t *)(ctx->vm[JITVARS_TRACKER]))->pBase);
|
||||||
delete (tracker_t *)ctx->vm[JITVARS_TRACKER];
|
delete (tracker_t *)ctx->vm[JITVARS_TRACKER];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CompData::SetOption(const char *key, const char *val)
|
bool
|
||||||
|
CompData::SetOption(const char *key, const char *val)
|
||||||
{
|
{
|
||||||
if (strcmp(key, SP_JITCONF_DEBUG) == 0)
|
if (strcmp(key, SP_JITCONF_DEBUG) == 0)
|
||||||
return true;
|
return true;
|
||||||
@ -1962,7 +2008,8 @@ bool CompData::SetOption(const char *key, const char *val)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int JITX86::InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result)
|
int
|
||||||
|
JITX86::InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result)
|
||||||
{
|
{
|
||||||
sp_context_t *ctx = runtime->GetBaseContext()->GetCtx();
|
sp_context_t *ctx = runtime->GetBaseContext()->GetCtx();
|
||||||
|
|
||||||
@ -1970,18 +2017,75 @@ int JITX86::InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result
|
|||||||
ctx->cip = fn->GetPCodeAddress();
|
ctx->cip = fn->GetPCodeAddress();
|
||||||
|
|
||||||
JIT_EXECUTE pfn = (JIT_EXECUTE)m_pJitEntry;
|
JIT_EXECUTE pfn = (JIT_EXECUTE)m_pJitEntry;
|
||||||
|
|
||||||
|
if (level_++ == 0)
|
||||||
|
frame_id_++;
|
||||||
int err = pfn(ctx, runtime->plugin()->memory, fn->GetEntryAddress());
|
int err = pfn(ctx, runtime->plugin()->memory, fn->GetEntryAddress());
|
||||||
|
level_--;
|
||||||
|
|
||||||
*result = ctx->rval;
|
*result = ctx->rval;
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *JITX86::AllocCode(size_t size)
|
void *
|
||||||
|
JITX86::AllocCode(size_t size)
|
||||||
{
|
{
|
||||||
return Knight::KE_AllocCode(g_pCodeCache, size);
|
return Knight::KE_AllocCode(g_pCodeCache, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void JITX86::FreeCode(void *code)
|
void
|
||||||
|
JITX86::FreeCode(void *code)
|
||||||
{
|
{
|
||||||
KE_FreeCode(g_pCodeCache, code);
|
KE_FreeCode(g_pCodeCache, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
JITX86::RegisterRuntime(BaseRuntime *rt)
|
||||||
|
{
|
||||||
|
mutex_.AssertCurrentThreadOwns();
|
||||||
|
runtimes_.insert(rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
JITX86::DeregisterRuntime(BaseRuntime *rt)
|
||||||
|
{
|
||||||
|
mutex_.AssertCurrentThreadOwns();
|
||||||
|
runtimes_.erase(rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
JITX86::PatchAllJumpsForTimeout()
|
||||||
|
{
|
||||||
|
mutex_.AssertCurrentThreadOwns();
|
||||||
|
for (InlineList<BaseRuntime>::iterator iter = runtimes_.begin(); iter != runtimes_.end(); iter++) {
|
||||||
|
BaseRuntime *rt = *iter;
|
||||||
|
for (size_t i = 0; i < rt->NumJitFunctions(); i++) {
|
||||||
|
JitFunction *fun = rt->GetJitFunction(i);
|
||||||
|
uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress());
|
||||||
|
|
||||||
|
for (size_t j = 0; j < fun->NumLoopEdges(); j++) {
|
||||||
|
const LoopEdge &e = fun->GetLoopEdge(j);
|
||||||
|
int32_t diff = intptr_t(m_pJitTimeout) - intptr_t(base + e.offset);
|
||||||
|
*reinterpret_cast<int32_t *>(base + e.offset - 4) = diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
JITX86::UnpatchAllJumpsFromTimeout()
|
||||||
|
{
|
||||||
|
mutex_.AssertCurrentThreadOwns();
|
||||||
|
for (InlineList<BaseRuntime>::iterator iter = runtimes_.begin(); iter != runtimes_.end(); iter++) {
|
||||||
|
BaseRuntime *rt = *iter;
|
||||||
|
for (size_t i = 0; i < rt->NumJitFunctions(); i++) {
|
||||||
|
JitFunction *fun = rt->GetJitFunction(i);
|
||||||
|
uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress());
|
||||||
|
|
||||||
|
for (size_t j = 0; j < fun->NumLoopEdges(); j++) {
|
||||||
|
const LoopEdge &e = fun->GetLoopEdge(j);
|
||||||
|
*reinterpret_cast<int32_t *>(base + e.offset - 4) = e.disp32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,34 +1,19 @@
|
|||||||
/**
|
// vim: set ts=8 sts=2 sw=2 tw=99 et:
|
||||||
* vim: set ts=4 sw=4 tw=99 et:
|
//
|
||||||
* =============================================================================
|
// This file is part of SourcePawn.
|
||||||
* SourceMod
|
//
|
||||||
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
// SourcePawn is free software: you can redistribute it and/or modify
|
||||||
* =============================================================================
|
// it under the terms of the GNU General Public License as published by
|
||||||
*
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
* This program is free software; you can redistribute it and/or modify it under
|
// (at your option) any later version.
|
||||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
//
|
||||||
* Free Software Foundation.
|
// SourcePawn is distributed in the hope that it will be useful,
|
||||||
*
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
// GNU General Public License for more details.
|
||||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
//
|
||||||
* details.
|
// You should have received a copy of the GNU General Public License
|
||||||
*
|
// along with SourcePawn. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
|
||||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
|
||||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
|
||||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
|
||||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
|
||||||
* this exception to all derivative works. AlliedModders LLC defines further
|
|
||||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
|
||||||
* or <http://www.sourcemod.net/license.php>.
|
|
||||||
*
|
|
||||||
* Version: $Id$
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _INCLUDE_SOURCEPAWN_JIT_X86_H_
|
#ifndef _INCLUDE_SOURCEPAWN_JIT_X86_H_
|
||||||
#define _INCLUDE_SOURCEPAWN_JIT_X86_H_
|
#define _INCLUDE_SOURCEPAWN_JIT_X86_H_
|
||||||
|
|
||||||
@ -42,6 +27,7 @@
|
|||||||
#include "sp_vm_basecontext.h"
|
#include "sp_vm_basecontext.h"
|
||||||
#include "jit_function.h"
|
#include "jit_function.h"
|
||||||
#include "opcodes.h"
|
#include "opcodes.h"
|
||||||
|
#include <ke_thread_utils.h>
|
||||||
|
|
||||||
using namespace SourcePawn;
|
using namespace SourcePawn;
|
||||||
|
|
||||||
@ -155,6 +141,7 @@ class Compiler
|
|||||||
cell_t *cip_;
|
cell_t *cip_;
|
||||||
cell_t *code_end_;
|
cell_t *code_end_;
|
||||||
Label *jump_map_;
|
Label *jump_map_;
|
||||||
|
ke::Vector<size_t> backward_jumps_;
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
Label error_bounds_;
|
Label error_bounds_;
|
||||||
@ -188,16 +175,36 @@ class JITX86
|
|||||||
ICompilation *ApplyOptions(ICompilation *_IN, ICompilation *_OUT);
|
ICompilation *ApplyOptions(ICompilation *_IN, ICompilation *_OUT);
|
||||||
int InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result);
|
int InvokeFunction(BaseRuntime *runtime, JitFunction *fn, cell_t *result);
|
||||||
|
|
||||||
|
void RegisterRuntime(BaseRuntime *rt);
|
||||||
|
void DeregisterRuntime(BaseRuntime *rt);
|
||||||
|
void PatchAllJumpsForTimeout();
|
||||||
|
void UnpatchAllJumpsFromTimeout();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ExternalAddress GetUniversalReturn() {
|
ExternalAddress GetUniversalReturn() {
|
||||||
return ExternalAddress(m_pJitReturn);
|
return ExternalAddress(m_pJitReturn);
|
||||||
}
|
}
|
||||||
void *AllocCode(size_t size);
|
void *AllocCode(size_t size);
|
||||||
void FreeCode(void *code);
|
void FreeCode(void *code);
|
||||||
|
|
||||||
|
uintptr_t FrameId() const {
|
||||||
|
return frame_id_;
|
||||||
|
}
|
||||||
|
bool RunningCode() const {
|
||||||
|
return level_ != 0;
|
||||||
|
}
|
||||||
|
ke::Mutex *Mutex() {
|
||||||
|
return &mutex_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void *m_pJitEntry; /* Entry function */
|
void *m_pJitEntry; /* Entry function */
|
||||||
void *m_pJitReturn; /* Universal return address */
|
void *m_pJitReturn; /* Universal return address */
|
||||||
|
void *m_pJitTimeout; /* Universal timeout address */
|
||||||
|
InlineList<BaseRuntime> runtimes_;
|
||||||
|
uintptr_t frame_id_;
|
||||||
|
uintptr_t level_;
|
||||||
|
ke::Mutex mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Register pri = eax;
|
const Register pri = eax;
|
||||||
@ -211,3 +218,4 @@ extern Knight::KeCodeCache *g_pCodeCache;
|
|||||||
extern JITX86 g_Jit;
|
extern JITX86 g_Jit;
|
||||||
|
|
||||||
#endif //_INCLUDE_SOURCEPAWN_JIT_X86_H_
|
#endif //_INCLUDE_SOURCEPAWN_JIT_X86_H_
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user