blob: f2bdb35eafaa0758facb619c5273913bc85e30f2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Alexander V. Astapchuk
*/
/**
* @file
* @brief 'micro-kernel' - contains kernel-like utilities - synchronization,
system info, etc.
*/
#if !defined(__MKERNEL_H_INCLUDED__)
#define __MKERNEL_H_INCLUDED__
#ifdef _WIN32
// windows.h include introduces min/max defines, which conflicts
// with STL's min/max used in many other places. This define prevents
// them to appear.
#define NOMINMAX 1
// windows.h enforces its own aligment, which may conflict with
// currently used default one.
// Note: EVERY inclusion of windows.h in Jitrino must be surrounded
// with \#pragma pack(push/pop)
#pragma pack(push)
// <winsock2.h> must be included before <windows.h> to avoid
// compile-time errors with winsock.h. For more details, see:
//www.microsoft.com/msdownload/platformsdk/ ..
// .. sdkupdate/2600.2180.7/contents.htm
#include <winsock2.h>
#include <windows.h>
#if _MSC_VER < 1500
#define vsnprintf _vsnprintf
#endif
#pragma pack(pop)
// ... well, just to be absolutely sure...
#undef min
#undef max
#else
#include <pthread.h>
#include <semaphore.h>
#endif
#include <assert.h>
#if defined(FREEBSD)
#define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE
#endif
namespace Jitrino {
/**
* @brief Exclusive lock.
*
* Class Mutex represents an exclusive lock. Interface is obvious.
*
* There are no 'try_lock' or 'is_locked' methods, this absence is
* intentional.
*
* @note On both Linux and Windows platforms, the mutex object can be
* recursively taken by the same thread (and, surely, must be released
* exactly the same number of times).
*
* @note On Linux platform, Mutex must be unlocked before destruction (that
* is, the Mutex object goes out of scope) - this is requirement of
* LinuxThreads implementation.
*
* @note Can be used only to synchronize inside an application. Cannot be
* used (and was not intended to do so) for inter-process
* synchronization.
*
* @note On Windows platform the underlying OS object is not Mutex, but
* CriticalSection instead, as it's a bit lighter and faster.
*
* @see AutoUnlock
*/
class Mutex {
#ifdef PLATFORM_POSIX
//
// *nix implementation
//
public:
/**
* @brief Frees resources associated with the mutex.
*/
Mutex()
{
pthread_mutexattr_t attrs;
pthread_mutexattr_init(&attrs);
pthread_mutexattr_settype(&attrs, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&m_handle, &attrs);
pthread_mutexattr_destroy(&attrs);
}
/**
* @brief Destructs the object.
*/
~Mutex() { pthread_mutex_destroy(&m_handle); }
/**
* @brief Acquires a lock.
*/
void lock(void) { pthread_mutex_lock(&m_handle); }
/**
* @brief Releases a lock.
*/
void unlock(void) { pthread_mutex_unlock(&m_handle); }
private:
pthread_mutex_t m_handle;
#else // not PLATFORM_POSIX
//
// Win* implementation
//
public:
Mutex() { InitializeCriticalSection(&m_cs); }
~Mutex() { DeleteCriticalSection(&m_cs); }
void lock(void) { EnterCriticalSection(&m_cs); }
void unlock(void) { LeaveCriticalSection(&m_cs); }
private:
CRITICAL_SECTION m_cs;
#endif // ~ifdef PLATFORM_POSIX
/**
* @brief Disallows copying.
*/
Mutex(const Mutex&);
/**
* @brief Disallows copying.
*/
const Mutex& operator=(const Mutex&);
public:
static Mutex appGlobal;
};
#define _LINE_VAR_CAT( name, line ) name##line
#define _LINE_VAR( name, line ) _LINE_VAR_CAT( name, line )
#define LINE_VAR(name) _LINE_VAR( name, __LINE__ )
#define SYNC_FIRST(Expression) \
static bool LINE_VAR(first) = true;\
bool LINE_VAR(locked) = false;\
if (LINE_VAR(first)) {\
Mutex::appGlobal.lock();\
LINE_VAR(locked) = true;\
}\
Expression;\
LINE_VAR(first) = false;\
if (LINE_VAR(locked)) {\
Mutex::appGlobal.unlock();\
}\
/**
* @brief Automatically unlocks a Mutex when AutoLock object goes out of
* scope.
*
* Class AutoUnlock is an utility class to handy acquire and [automatically]
* release Mutex lock.
*
* A trivial C++ trick, which, I believe, is used everywhere with Mutexes -
* holds the lock in the ctor and releases lock in dtor - when AutoUnlock
* object goes out of scope.
*
* Safely accepts NULL-s - and performs nothing in this case.
*/
class AutoUnlock {
public:
/**
* @brief Locks the mutex.
*/
AutoUnlock(Mutex& m) : m_mutex(&m)
{
m_mutex->lock();
}
/**
* @brief Locks the mutex, or does nothing if \c pm is \b NULL.
*/
AutoUnlock(Mutex * pm) : m_mutex(pm)
{
if(m_mutex) {
m_mutex->lock();
}
}
/**
* @brief Forces mutex (if any) to be unlocked.
*
* When called, clears internal pointer to a mutex object, so the
* following sequential calls to forceUnlock and destructor are noops.
*/
void forceUnlock(void)
{
if(m_mutex) {
m_mutex->unlock();
m_mutex = NULL;
}
}
/**
* @brief Unlocks mutex (if any).
*/
~AutoUnlock()
{
if(m_mutex) {
m_mutex->unlock();
}
}
private:
AutoUnlock(const AutoUnlock&);
AutoUnlock& operator=(const AutoUnlock&);
Mutex * m_mutex;
};
/**
* @brief Class Runtime provides an info about environment the application
* is currently running.
*/
class Runtime {
public:
/**
* @brief Returns number of CPUs available in the system.
*
* In theory, may be 0, if underlying system does not provide the info.
*
* Does not even try to distinguish HyperThreading and 'phisical CPU', so
* a CPU with HT enabled is seen as 2 CPUs.
*/
static unsigned cpus(void) { return num_cpus; }
/**
* @brief Tests whether we're running in single-CPU machine.
*/
static bool is_one_cpu(void) { return cpus() == 1; }
/**
* @brief Tests whether we're running in multi-CPU box.
*/
static bool is_smp(void) { return cpus() > 1; }
private:
/**
* @brief Number of CPUs.
*
* Initialized once at startup and never changes.
*/
static const unsigned num_cpus;
/**
* @brief Initializes \link #num_cpus number of CPUs\endlink.
*/
static unsigned init_num_cpus(void);
/**
* @brief Disallows creation, interface is only through static functions.
*/
Runtime();
/**
* @brief Disallows copying.
*/
Runtime(const Runtime&);
/**
* @brief Disallows copying.
*/
Runtime& operator=(const Runtime&);
};
/**
* @brief Key used to identify data in TLS.
*/
typedef unsigned long TlsKey;
/**
* @brief Basic thread local storage (TLS) functionality.
*
* The functionality is made similar to the POSIX one - when a TLS key
* allocated, user may specify a function (<i>release function</i>) which
* will be called on thread's death to release the data stored in TLS.
*
* With POSIX threads this functionality is by design. On Windows, this
* functionality is emulated by the Tls class.
*
* The emulation's behavior was made as much close to the one specified by
* POSIX, though some differences still exist.
*
* - the release function is called only if data currently stored in TLS
* is not NULL
* - NULL value is associated with TLS key before calling release function
* - (the difference) if release function re-associates a data with the TLS
* key, this data is ignored (in other words, release function is called
* no more than once for each thread)
*
* @note Emulation on Windows requires external support - currently special
* method Tls::threadEnd is called on DLL_THREAD_DETACH. As our code lives
* in dll it's enough for all our needs. If the code (and especially the
* release function behaviour) need to be reused for a threads in process,
* the Tls::threadEnd() must be called at the end of thread function.
*/
class Tls {
public:
/**
* @brief Allocates a key for TLS.
*
* Initial value associated with each key in NULL.
*
* @param free_func - a function will be called when thread is about to
* finish, so it can free up the data stored in TLS. See Tls class
* comments.
*/
static TlsKey allocKey(void (*free_func) (void *) = NULL);
/**
* @brief Releases the TlsKey.
*/
static void releaseKey(TlsKey key);
#ifdef _WIN32
/**
* @brief Special-purpose method - used to emulate POSIX-like behavior
* with release function that may free up a data stored in TLS.
*
* Must be called when thread is about to finish.
*/
static void threadDetach(void);
/**
* @brief Special-purpose method - used to cleanup dynamically
* allocated map.
*
* Must be called when thread is about to finish.
*/
static void processDetach(void);
#endif
/**
* @brief Puts \c data into TLS.
*/
static void put(TlsKey key, void* data);
/**
* @brief Gets \c data from TLS.
*
* If there were no preceding put() operations in the current thread,
* NULL returned.
*/
static void* get(TlsKey key);
};
/**
* A handy wrapper to organize TLS storage, with auto-<i>magic</i> release
* of allocated data.
*
* TlsStorage is used to manage a thread-local copy of data that exists
* in a <i>single copy per each thread</i>. Here it differs from TlsList
* and TlsStack that organize a thread local stack storage of several
* instances of a data.
*
* The TlsStore presumes that a data stored in it is dynamically allocated
* using <code>global operator new</code>. Upon a thread termination, a
* non-<b>NULL</b> data is deallocated using <code>global operator
* delete</code>.
*
*
* Usage example:
* <pre>
* <code>
* static TlsStore<SomeStruct> globalStore;
*
* foo()
* {
* SomeStruct* data = globalStore.get();
* if (data == NULL) {
* data = new SomeStru();
* data->init();
* globalStore.put(data);
* }
*
* }
* </code>
* </pre>
*/
template <class T> struct TlsStore {
/**
* @brief Allocates a TlsKey to be used for TLS manipulations.
*/
TlsStore()
{
m_key = Tls::allocKey(free);
}
/**
* @brief Releases TlsKey.
*/
~TlsStore()
{
Tls::releaseKey(m_key);
}
/**
* @brief Returns the stored data or NULL if there were no data stored.
*/
T* get(void) const
{
return (T*)Tls::get(m_key);
}
/**
* @brief Stores the data into TLS, \b NULL-s are ok.
*
* If there were a data stored before, the previous data is freed.
*/
void put(T* pt) const
{
T* prev = get();
if (prev != NULL) {
free(prev);
}
Tls::put(m_key, pt);
}
/**
* Releases the data (calls delete)
*
* The method is invoked upon a thread termination to deallocate
* data stored in TLS.
*/
static void free(void* p)
{
delete (T*)p;
}
private:
TlsKey m_key;
};
/**
* @brief Provides basic functionality to organize stack of infos in TLS.
*
* <code>
* <pre>
* TlsKey globalKey = Tls::allocKey(NULL);
* foo()
* {
* SomeStru fooLocalData;
* TlsList::push(globalKey, &fooLocalData);
* boo();
* TlsList::pop(globalKey);
* }
*
* boo()
* {
* SomeStru* data = (SomeStru*)TlsList::get(globalKey);
* // do something
* }
* </pre>
* </code>
*
* \c foo() may be called in the same thread several times, and every time
* the latest data will be available for \c boo().
*
* @note Number of \c pop()-s must be strictly balanced with the number of
* push()-es, or a memory leak occurs.
*
* @note TlsList really stores its special data into TLS, so never mix
* calls of Tls::put()/Tls::get() with TlsList's methods with the same
* key.
*
* Normally, the TlsList may be used to store a pointer to general data,
* while for a particular data structures the template class TlsStack may
* be more convinient.
*
* @see TlsStack
*/
class TlsList {
public:
public:
/**
* @brief Pushes the \c data onto TLS stack.
*/
static void push(TlsKey key, void* data);
/**
* @brief Pops out the \c data from TLS stack.
*/
static void* pop(TlsKey key);
/**
* @brief Returns top data item from TLS stack and leaves it on stack.
*/
static void* get(TlsKey key);
private:
/**
* Helper structure to organize linked list of datas in TLS.
*/
struct ListItem
{
ListItem(ListItem* _prev, void* _data)
{
prev = _prev;
data = _data;
#ifdef _DEBUG
magic = MAGIC;
#endif
}
#ifdef _DEBUG
/**
* @brief A signature, used to verify data integrity.
*/
unsigned magic;
static const unsigned MAGIC = 0x4C495354; // 'LIST' in hex
~ListItem()
{
assert(magic == MAGIC);
}
#endif
void * data;
ListItem* prev;
};
};
/**
* @brief Handy wrapper for TlsList.
*
* Use as following:
*
* <code>
* TlsStack<SomeStruct> globalSomeStructStack; // must be static global
* </code>
*
* @note Note the declaration in the example. It's the type itself, and not
* the pointer - methods os TlsStack do \b not copy data. Instead only
* pointer to the specified data is stored.
*
* The result is that the stored data must be have it's scope not less than
* appropriate push/pop sequence.
*/
template<class AType> class TlsStack {
public:
/**
* @brief Allocates a TlsKey to be used for TLS manipulations.
*/
TlsStack(void)
{
m_key = Tls::allocKey();
}
/**
* @brief Releases TlsKey.
*/
~TlsStack()
{
Tls::releaseKey(m_key);
}
/**
* @brief Pushes the \c item onto TLS stack.
*/
void push(AType& item)
{
push(&item);
}
/**
* @brief Pushes the \c pitem onto TLS stack.
*/
void push(AType* pitem)
{
TlsList::push(m_key, pitem);
}
/**
* @brief Returns top data item from TLS stack and leaves it on stack.
*/
AType* get(void)
{
return (AType*)TlsList::get(m_key);
}
/**
* @brief Pops out the an item from TLS stack.
*/
AType* pop(void)
{
return (AType*)TlsList::pop(m_key);
}
private:
TlsKey m_key;
};
/**
Set of utility methods to get features the current CPU supports.
The functionality of this class is rather scarce, but it covers all needs we have in JIT today
*/
class CPUID {
CPUID(){}
public:
#if defined(HYX86) || defined(HYX86_64)
/** SSE2 is an extension of the IA-32 architecture, since 2000. */
static bool isSSE2Supported();
#endif
};
}; // ~namespace Jitrino
#endif // ~ifndef __MKERNEL_H_INCLUDED__