1
0
forked from 0ad/0ad

fixes, documentation and cleanup.

. allocators: add static_calloc. also: documention was duplicated in
header; merge it.
. wdir_watch: fix last remaining static object init issues
. add rationale and explanations to lib_errors, wsdl, qpc, whrt.
. cpu/ia32: remove throttling detection (hacky); will be taken care of
by tsc.
. ia32: expose vendor
. wdbg_sym: add its own critical section (safer, less contention)

This was SVN commit r5114.
This commit is contained in:
janwas 2007-05-29 16:28:34 +00:00
parent 6daf03b353
commit 5919fc7877
16 changed files with 396 additions and 512 deletions

View File

@ -18,13 +18,12 @@
#include "bits.h"
//-----------------------------------------------------------------------------
// helper routines
//-----------------------------------------------------------------------------
// makes sure page_size has been initialized by the time it is needed
// (otherwise, we are open to NLSO ctor order issues).
// latch page size in case we are called from static ctors (it's possible
// that they are called before our static initializers).
// pool_create is therefore now safe to call before main().
static size_t get_page_size()
{
@ -108,14 +107,6 @@ static LibError mem_protect(u8* p, size_t size, int prot)
// page aligned allocator
//-----------------------------------------------------------------------------
/**
* allocate memory starting at a page-aligned address.
* it defaults to read/writable; you can mprotect it if desired.
*
* @param unaligned_size minimum size [bytes] to allocate
* (will be rounded up to page size)
* @return void* allocated memory, or NULL if error / out of memory.
*/
void* page_aligned_alloc(size_t unaligned_size)
{
const size_t size_pa = round_up_to_page(unaligned_size);
@ -125,12 +116,7 @@ void* page_aligned_alloc(size_t unaligned_size)
return p;
}
/**
* Free a memory block that had been allocated by page_aligned_alloc.
*
* @param void* Exact pointer returned by page_aligned_alloc
* @param unaligned_size Exact size passed to page_aligned_alloc
*/
void page_aligned_free(void* p, size_t unaligned_size)
{
if(!p)
@ -181,16 +167,6 @@ static LibError validate_da(DynArray* da)
#define CHECK_DA(da) RETURN_ERR(validate_da(da))
/**
* ready the DynArray object for use.
*
* @param DynArray*
* @param max_size Max size [bytes] of the DynArray; this much
* (rounded up to next page multiple) virtual address space is reserved.
* no virtual memory is actually committed until calls to da_set_size.
* @return LibError
*/
LibError da_alloc(DynArray* da, size_t max_size)
{
const size_t max_size_pa = round_up_to_page(max_size);
@ -209,40 +185,6 @@ LibError da_alloc(DynArray* da, size_t max_size)
}
/**
* "wrap" (i.e. store information about) the given buffer in a
* DynArray object, preparing it for use with da_read or da_append.
*
* da_free should be called when the DynArray is no longer needed,
* even though it doesn't free this memory (but does zero the DynArray).
*
* @param DynArray*. Note: any future operations on it that would
* change the underlying memory (e.g. da_set_size) will fail.
* @param p Memory
* @param size Size [bytes]
* @return LibError
*/
LibError da_wrap_fixed(DynArray* da, u8* p, size_t size)
{
da->base = p;
da->max_size_pa = round_up_to_page(size);
da->cur_size = size;
da->cur_size_pa = da->max_size_pa;
da->prot = PROT_READ|PROT_WRITE|DA_NOT_OUR_MEM;
da->pos = 0;
CHECK_DA(da);
return INFO::OK;
}
/**
* free all memory (address space + physical) that constitutes the
* given array. use-after-free is impossible because the memory is
* marked not-present via MMU.
*
* @param DynArray* da; zeroed afterwards.
* @return LibError
*/
LibError da_free(DynArray* da)
{
CHECK_DA(da);
@ -264,15 +206,6 @@ LibError da_free(DynArray* da)
}
/**
* expand or shrink the array: changes the amount of currently committed
* (i.e. usable) memory pages.
*
* @param DynArray*
* @param new_size [bytes]. Pages are added/removed until this size
* (rounded up to the next page size multiple) is reached.
* @return LibError
*/
LibError da_set_size(DynArray* da, size_t new_size)
{
CHECK_DA(da);
@ -308,14 +241,6 @@ LibError da_set_size(DynArray* da, size_t new_size)
}
/**
* Make sure at least <size> bytes starting at da->pos are committed and
* ready for use.
*
* @param DynArray*
* @param size Minimum amount to guarantee [bytes]
* @return LibError
*/
LibError da_reserve(DynArray* da, size_t size)
{
if(da->pos+size > da->cur_size_pa)
@ -325,15 +250,6 @@ LibError da_reserve(DynArray* da, size_t size)
}
/**
* Change access rights of the array memory; used to implement
* write-protection. affects the currently committed pages as well as
* all subsequently added pages.
*
* @param DynArray*
* @param prot PROT_* protection flags as defined by POSIX mprotect()
* @return LibError
*/
LibError da_set_prot(DynArray* da, int prot)
{
CHECK_DA(da);
@ -351,15 +267,19 @@ LibError da_set_prot(DynArray* da, int prot)
}
/**
* "read" from array, i.e. copy into the given buffer.
* starts at offset DynArray.pos and advances this.
*
* @param DynArray*
* @param data Destination buffer
* @param size Amount to copy [bytes]
* @return LibError
*/
LibError da_wrap_fixed(DynArray* da, u8* p, size_t size)
{
da->base = p;
da->max_size_pa = round_up_to_page(size);
da->cur_size = size;
da->cur_size_pa = da->max_size_pa;
da->prot = PROT_READ|PROT_WRITE|DA_NOT_OUR_MEM;
da->pos = 0;
CHECK_DA(da);
return INFO::OK;
}
LibError da_read(DynArray* da, void* data, size_t size)
{
// make sure we have enough data to read
@ -372,15 +292,6 @@ LibError da_read(DynArray* da, void* data, size_t size)
}
/**
* "write" to array, i.e. copy from the given buffer.
* starts at offset DynArray.pos and advances this.
*
* @param DynArray*
* @param data Source buffer
* @param Amount to copy [bytes]
* @return LibError
*/
LibError da_append(DynArray* da, const void* data, size_t size)
{
RETURN_ERR(da_reserve(da, size));
@ -394,13 +305,6 @@ LibError da_append(DynArray* da, const void* data, size_t size)
// pool allocator
//-----------------------------------------------------------------------------
// design parameters:
// - O(1) alloc and free;
// - fixed- XOR variable-sized blocks;
// - doesn't preallocate the entire pool;
// - returns sequential addresses.
// "freelist" is a pointer to the first unused element (0 if there are none);
// its memory holds a pointer to the next free one in list.
@ -427,18 +331,6 @@ static void* freelist_pop(void** pfreelist)
static const size_t ALIGN = 8;
/**
* Ready Pool for use.
*
* @param Pool*
* @param max_size Max size [bytes] of the Pool; this much
* (rounded up to next page multiple) virtual address space is reserved.
* no virtual memory is actually committed until calls to pool_alloc.
* @param el_size Number of bytes that will be returned by each
* pool_alloc (whose size parameter is then ignored). Can be 0 to
* allow variable-sized allocations, but pool_free is then unusable.
* @return LibError
*/
LibError pool_create(Pool* p, size_t max_size, size_t el_size)
{
if(el_size == POOL_VARIABLE_ALLOCS)
@ -451,14 +343,6 @@ LibError pool_create(Pool* p, size_t max_size, size_t el_size)
}
/**
* Free all memory that ensued from the Pool. all elements are made unusable
* (it doesn't matter if they were "allocated" or in freelist or unused);
* future alloc and free calls on this pool will fail.
*
* @param Pool*
* @return LibError
*/
LibError pool_destroy(Pool* p)
{
// don't be picky and complain if the freelist isn't empty;
@ -469,14 +353,6 @@ LibError pool_destroy(Pool* p)
}
/**
* Indicate whether <el> was allocated from the given pool.
* this is useful for callers that use several types of allocators.
*
* @param Pool*
* @param el Address in question
* @return bool
*/
bool pool_contains(Pool* p, void* el)
{
// outside of our range
@ -489,15 +365,6 @@ bool pool_contains(Pool* p, void* el)
}
/**
* Dole out memory from the pool.
* exhausts the freelist before returning new entries to improve locality.
*
* @param Pool*
* @param size bytes to allocate; ignored if pool_create's el_size was not 0.
* @return allocated memory, or 0 if the Pool would have to be expanded and
* there isn't enough memory to do so.
*/
void* pool_alloc(Pool* p, size_t size)
{
// if pool allows variable sizes, go with the size parameter,
@ -526,16 +393,6 @@ have_el:
}
/**
* Make a fixed-size element available for reuse in the given Pool.
*
* this is not allowed if the Pool was created for variable-size elements.
* rationale: avoids having to pass el_size here and compare with size when
* allocating; also prevents fragmentation and leaking memory.
*
* @param Pool*
* @param el Element returned by pool_alloc.
*/
void pool_free(Pool* p, void* el)
{
// only allowed to free items if we were initialized with
@ -554,13 +411,6 @@ void pool_free(Pool* p, void* el)
}
/**
* "free" all allocations that ensued from the given Pool.
* this resets it as if freshly pool_create-d, but doesn't release the
* underlying reserved virtual memory.
*
* @param Pool*
*/
void pool_free_all(Pool* p)
{
p->freelist = 0;
@ -576,31 +426,10 @@ void pool_free_all(Pool* p)
// bucket allocator
//-----------------------------------------------------------------------------
// design goals:
// - fixed- XOR variable-sized blocks;
// - allow freeing individual blocks if they are all fixed-size;
// - never relocates;
// - no fixed limit.
// note: this type of allocator is called "region-based" in the literature.
// see "Reconsidering Custom Memory Allocation" (Berger, Zorn, McKinley).
// if individual elements must be freeable, consider "reaps":
// basically a combination of region and heap, where frees go to the heap and
// allocs exhaust that memory first and otherwise use the region.
// power-of-2 isn't required; value is arbitrary.
const size_t BUCKET_SIZE = 4000;
/**
* Ready Bucket for use.
*
* @param Bucket*
* @param el_size Number of bytes that will be returned by each
* bucket_alloc (whose size parameter is then ignored). Can be 0 to
* allow variable-sized allocations, but bucket_free is then unusable.
* @return LibError
*/
LibError bucket_create(Bucket* b, size_t el_size)
{
b->freelist = 0;
@ -624,12 +453,6 @@ LibError bucket_create(Bucket* b, size_t el_size)
}
/**
* Free all memory that ensued from the Bucket.
* future alloc and free calls on this Bucket will fail.
*
* @param Bucket*
*/
void bucket_destroy(Bucket* b)
{
while(b->bucket)
@ -648,15 +471,6 @@ void bucket_destroy(Bucket* b)
}
/**
* Dole out memory from the Bucket.
* exhausts the freelist before returning new entries to improve locality.
*
* @param Bucket*
* @param size bytes to allocate; ignored if bucket_create's el_size was not 0.
* @return allocated memory, or 0 if the Bucket would have to be expanded and
* there isn't enough memory to do so.
*/
void* bucket_alloc(Bucket* b, size_t size)
{
size_t el_size = b->el_size? b->el_size : round_up(size, ALIGN);
@ -689,16 +503,6 @@ void* bucket_alloc(Bucket* b, size_t size)
}
/**
* Make a fixed-size element available for reuse in the Bucket.
*
* this is not allowed if the Bucket was created for variable-size elements.
* rationale: avoids having to pass el_size here and compare with size when
* allocating; also prevents fragmentation and leaking memory.
*
* @param Bucket*
* @param el Element returned by bucket_alloc.
*/
void bucket_free(Bucket* b, void* el)
{
if(b->el_size == 0)
@ -719,20 +523,6 @@ void bucket_free(Bucket* b, void* el)
// matrix allocator
//-----------------------------------------------------------------------------
// takes care of the dirty work of allocating 2D matrices:
// - aligns data
// - only allocates one memory block, which is more efficient than
// malloc/new for each row.
/**
* allocate a 2D cols x rows matrix of <el_size> byte cells.
* this must be freed via matrix_free.
*
* @param cols, rows Matrix dimensions.
* @param el_size Size [bytes] of each matrix entry.
* @return void**: 0 if out of memory, or a pointer that should be cast to the
* target type (e.g. int**). it can then be accessed via matrix[col][row].
*/
void** matrix_alloc(uint cols, uint rows, size_t el_size)
{
const size_t initial_align = 64;
@ -769,12 +559,6 @@ void** matrix_alloc(uint cols, uint rows, size_t el_size)
}
/**
* Free a matrix allocated by matrix_alloc.
*
* @param void** matrix. Callers will likely want to pass it as another
* type, but C++ requires it be explicitly casted to void**.
*/
void matrix_free(void** matrix)
{
free(matrix);
@ -785,25 +569,6 @@ void matrix_free(void** matrix)
// allocator optimized for single instances
//-----------------------------------------------------------------------------
/**
* Allocate <size> bytes of zeroed memory.
*
* intended for applications that frequently alloc/free a single
* fixed-size object. caller provides static storage and an in-use flag;
* we use that memory if available and otherwise fall back to the heap.
* if the application only has one object in use at a time, malloc is
* avoided; this is faster and avoids heap fragmentation.
*
* note: thread-safe despite use of shared static data.
*
* @param storage Caller-allocated memory of at least <size> bytes
* (typically a static array of bytes)
* @param in_use_flag Pointer to a flag we set when <storage> is in-use.
* @param size [bytes] to allocate
* @return allocated memory; typically = <storage>, but falls back to
* malloc if that's in-use. can return 0 (with warning) if out of memory.
*/
void* single_calloc(void* storage, volatile uintptr_t* in_use_flag, size_t size)
{
// sanity check
@ -830,13 +595,6 @@ void* single_calloc(void* storage, volatile uintptr_t* in_use_flag, size_t size)
}
/**
* Free a memory block that had been allocated by single_calloc.
*
* @param storage Exact value passed to single_calloc.
* @param in_use_flag Exact value passed to single_calloc.
* @param Exact value returned by single_calloc.
*/
void single_free(void* storage, volatile uintptr_t* in_use_flag, void* p)
{
// sanity check
@ -860,3 +618,16 @@ void single_free(void* storage, volatile uintptr_t* in_use_flag, void* p)
free(p);
}
}
//-----------------------------------------------------------------------------
// static allocator
//-----------------------------------------------------------------------------
void* static_calloc(StaticStorage* ss, size_t size)
{
void* p = (void*)round_up((uintptr_t)ss->pos, 16);
ss->pos = (u8*)p+size;
debug_assert(ss->pos <= ss->end);
return p;
}

View File

@ -30,9 +30,11 @@
* note that this allocator is stateless and very litte error checking
* can be performed.
*
* the memory is initially writable and you can use mprotect to set other
* access permissions if desired.
*
* @param unaligned_size minimum size [bytes] to allocate.
* @return writable, page-aligned and -padded memory; you can use
* mprotect to set other access permissions if desired.
* @return page-aligned and -padded memory or 0 on error / out of memory.
**/
extern void* page_aligned_alloc(size_t unaligned_size);
@ -87,11 +89,12 @@ extern LibError da_alloc(DynArray* da, size_t max_size);
/**
* free all memory (address space + physical) that constitutes the
* given DynArray.
* given array.
*
* @param da DynArray. zeroed afterwards; continued use of the allocated
* memory is impossible because it is marked not-present via MMU.
* @return LibError.
* use-after-free is impossible because the memory is unmapped.
*
* @param DynArray* da; zeroed afterwards.
* @return LibError
**/
extern LibError da_free(DynArray* da);
@ -107,12 +110,12 @@ extern LibError da_free(DynArray* da);
extern LibError da_set_size(DynArray* da, size_t new_size);
/**
* make sure a given number of bytes starting from the current position
* (DynArray.pos) are committed and ready for use.
* Make sure at least <size> bytes starting at da->pos are committed and
* ready for use.
*
* @param da DynArray.
* @param size minimum size [bytes].
* @return LibError.
* @param DynArray*
* @param size Minimum amount to guarantee [bytes]
* @return LibError
**/
extern LibError da_reserve(DynArray* da, size_t size);
@ -135,7 +138,8 @@ extern LibError da_set_prot(DynArray* da, int prot);
* da_free should be called when the DynArray is no longer needed,
* even though it doesn't free this memory (but does zero the DynArray).
*
* @param da DynArray.
* @param da DynArray. Note: any future operations on it that would
* change the underlying memory (e.g. da_set_size) will fail.
* @param p target memory (no alignment/padding requirements)
* @param size maximum size (no alignment requirements)
* @return LibError.
@ -204,16 +208,16 @@ struct Pool
const size_t POOL_VARIABLE_ALLOCS = ~0u;
/**
* ready the Pool object for use.
* Ready Pool for use.
*
* @param p Pool.
* @param max_size size [bytes] of address space to reserve (*);
* the Pool can never expand beyond this.
* (* rounded up to next page size multiple)
* @param el_size 0 to allow variable-sized allocations (which cannot be
* freed individually); otherwise, it specifies the number of bytes that
* will be returned by pool_alloc (whose size parameter is then ignored).
* @return LibError.
* @param Pool*
* @param max_size Max size [bytes] of the Pool; this much
* (rounded up to next page multiple) virtual address space is reserved.
* no virtual memory is actually committed until calls to pool_alloc.
* @param el_size Number of bytes that will be returned by each
* pool_alloc (whose size parameter is then ignored). Can be 0 to
* allow variable-sized allocations, but pool_free is then unusable.
* @return LibError
**/
extern LibError pool_create(Pool* p, size_t max_size, size_t el_size);
@ -221,9 +225,12 @@ extern LibError pool_create(Pool* p, size_t max_size, size_t el_size);
* free all memory (address space + physical) that constitutes the
* given Pool.
*
* @param p Pool. continued use of the allocated memory (*) is
* future alloc and free calls on this pool will fail.
* continued use of the allocated memory (*) is
* impossible because it is marked not-present via MMU.
* (* no matter if in freelist or unused or "allocated" to user)
*
* @param Pool*
* @return LibError.
**/
extern LibError pool_destroy(Pool* p);
@ -233,33 +240,31 @@ extern LibError pool_destroy(Pool* p);
*
* this is useful for callers that use several types of allocators.
*
* @param p Pool.
* @param Pool*
* @return bool.
**/
extern bool pool_contains(Pool* p, void* el);
/**
* allocate memory from the pool.
*
* Dole out memory from the pool.
* exhausts the freelist before returning new entries to improve locality.
*
* @param p Pool.
* @param size [bytes] to allocated. ignored if pool was set up with
* fixed-size elements.
* @return 0 if the Pool would have to be expanded and there isn't enough
* memory to do so, otherwise the allocated memory.
* @param Pool*
* @param size bytes to allocate; ignored if pool_create's el_size was not 0.
* @return allocated memory, or 0 if the Pool would have to be expanded and
* there isn't enough memory to do so.
**/
extern void* pool_alloc(Pool* p, size_t size);
/**
* make an entry available for reuse in the given Pool.
* Make a fixed-size element available for reuse in the given Pool.
*
* this is not allowed if created for variable-size elements.
* this is not allowed if the Pool was created for variable-size elements.
* rationale: avoids having to pass el_size here and compare with size when
* allocating; also prevents fragmentation and leaking memory.
*
* @param p Pool.
* @param el entry allocated via pool_alloc.
* @param Pool*
* @param el Element returned by pool_alloc.
**/
extern void pool_free(Pool* p, void* el);
@ -267,9 +272,9 @@ extern void pool_free(Pool* p, void* el);
* "free" all user allocations that ensued from the given Pool.
*
* this resets it as if freshly pool_create-d, but doesn't release the
* underlying memory.
* underlying reserved virtual memory.
*
* @param p Pool.
* @param Pool*
**/
extern void pool_free_all(Pool* p);
@ -319,7 +324,7 @@ struct Bucket
/**
* ready the Bucket object for use.
*
* @param b Bucket.
* @param Bucket*
* @param el_size 0 to allow variable-sized allocations (which cannot be
* freed individually); otherwise, it specifies the number of bytes that
* will be returned by bucket_alloc (whose size parameter is then ignored).
@ -332,20 +337,18 @@ extern LibError bucket_create(Bucket* b, size_t el_size);
*
* future alloc and free calls on this Bucket will fail.
*
* @param b Bucket.
* @param Bucket*
**/
extern void bucket_destroy(Bucket* b);
/**
* allocate memory from the bucket.
*
* Dole out memory from the Bucket.
* exhausts the freelist before returning new entries to improve locality.
*
* @param b Bucket.
* @param size [bytes] to allocated. ignored if pool was set up with
* fixed-size elements.
* @return 0 if the Bucket would have to be expanded and there isn't enough
* memory to do so, otherwise the allocated memory.
* @param Bucket*
* @param size bytes to allocate; ignored if bucket_create's el_size was not 0.
* @return allocated memory, or 0 if the Bucket would have to be expanded and
* there isn't enough memory to do so.
**/
extern void* bucket_alloc(Bucket* b, size_t size);
@ -356,7 +359,7 @@ extern void* bucket_alloc(Bucket* b, size_t size);
* rationale: avoids having to pass el_size here and compare with size when
* allocating; also prevents fragmentation and leaking memory.
*
* @param b Bucket.
* @param Bucket*
* @param el entry allocated via bucket_alloc.
**/
extern void bucket_free(Bucket* b, void* el);
@ -396,25 +399,31 @@ extern void matrix_free(void** matrix);
//
/**
* allocator for applications that frequently alloc/free a single
* fixed-size object.
* Allocate <size> bytes of zeroed memory.
*
* if there is only one object in use at a time, malloc is avoided;
* this is faster and avoids heap fragmentation.
* intended for applications that frequently alloc/free a single
* fixed-size object. caller provides static storage and an in-use flag;
* we use that memory if available and otherwise fall back to the heap.
* if the application only has one object in use at a time, malloc is
* avoided; this is faster and avoids heap fragmentation.
*
* @param storage static storage; enough to fit one item.
* @param in_use_flag: indicates if storage is in use. manipulated via CAS,
* so this is thread-safe.
* @param size [bytes] of storage (we need to know this if falling back to
* heap allocation).
* @return pointer to storage if available, otherwise a heap allocation.
* note: thread-safe despite use of shared static data.
*
* @param storage Caller-allocated memory of at least <size> bytes
* (typically a static array of bytes)
* @param in_use_flag Pointer to a flag we set when <storage> is in-use.
* @param size [bytes] to allocate
* @return allocated memory (typically = <storage>, but falls back to
* malloc if that's in-use), or 0 (with warning) if out of memory.
**/
extern void* single_calloc(void* storage, volatile uintptr_t* in_use_flag, size_t size);
/**
* free memory allocated via single_calloc.
* Free a memory block that had been allocated by single_calloc.
*
* see description there.
* @param storage Exact value passed to single_calloc.
* @param in_use_flag Exact value passed to single_calloc.
* @param Exact value returned by single_calloc.
**/
extern void single_free(void* storage, volatile uintptr_t* in_use_flag, void* p);
@ -450,6 +459,58 @@ public:
#endif // #ifdef __cplusplus
//
// static allocator
//
// dole out chunks of memory from storage reserved in the BSS.
// freeing isn't necessary.
/**
* opaque; initialized by STATIC_STORAGE and used by static_calloc
**/
struct StaticStorage
{
void* pos;
void* end;
};
// define <size> bytes of storage and prepare <name> for use with
// static_calloc.
// must be invoked from file or function scope.
#define STATIC_STORAGE(name, size)\
static u8 storage[(size)];\
static StaticStorage name = { storage, storage+(size) }
/*
usage example:
static Object* pObject;
void InitObject()
{
STATIC_STORAGE(ss, 100); // includes padding
void* addr = static_calloc(ss, sizeof(Object));
pObject = new(addr) Object;
}
*/
/**
* dole out memory from static storage reserved in BSS.
*
* this is useful for static objects that are used before _cinit - callers
* define static storage for one or several objects, use this function to
* retrieve an aligned pointer, then construct there via placement new.
*
* @param ss - initialized via STATIC_STORAGE
* @param size [bytes] to allocate
* @return aligned (suitable for any type) pointer
*
* raises a warning if there's not enough room (indicates incorrect usage)
**/
extern void* static_calloc(StaticStorage* ss, size_t size);
// (no need to free static_calloc-ed memory since it's in the BSS)
//
// overrun protection
//

View File

@ -25,6 +25,12 @@
// linked list (most recent first)
// note: no memory is allocated, all nodes are static instances.
//
// rationale: don't use a std::map. we don't care about lookup speed,
// dynamic allocation would be ugly, and returning a local static object
// from a function doesn't work, either (the compiler generates calls to
// atexit, which leads to disaster since we're sometimes called by
// winit functions before the CRT has initialized)
static LibErrorAssociation* associations;
int error_AddAssociation(LibErrorAssociation* lea)

View File

@ -57,36 +57,6 @@ static void DetectClockFrequency()
}
static bool isThrottlingPossible = true;
bool cpu_IsThrottlingPossible()
{
debug_assert(clockFrequency > 0.0); // (can't verify isThrottlingPossible directly)
return isThrottlingPossible;
}
static void DetectIfThrottlingPossible()
{
#if CPU_IA32
if(ia32_IsThrottlingPossible() == 1)
{
isThrottlingPossible = true;
return;
}
#endif
#if OS_WIN
if(wcpu_IsThrottlingPossible() == 1)
{
isThrottlingPossible = true;
return;
}
#endif
isThrottlingPossible = false;
}
static size_t memoryTotalMib = 1;
size_t cpu_MemoryTotalMiB()
@ -188,7 +158,6 @@ void cpu_Init()
#endif
DetectMemory();
DetectIfThrottlingPossible();
DetectClockFrequency();
}

View File

@ -26,7 +26,6 @@ extern void cpu_Shutdown();
extern const char* cpu_IdentifierString();
extern double cpu_ClockFrequency();
extern bool cpu_IsThrottlingPossible();
extern uint cpu_NumPackages(); // i.e. sockets
extern uint cpu_CoresPerPackage();
extern uint cpu_LogicalPerCore();
@ -46,10 +45,10 @@ extern size_t cpu_MemoryTotalMiB();
extern bool cpu_CAS(volatile uintptr_t* location, uintptr_t expected, uintptr_t new_value);
// this is often used for pointers, so the macro coerces parameters to
// uinptr_t. invalid usage unfortunately also goes through without warnings.
// uintptr_t. invalid usage unfortunately also goes through without warnings.
// to catch cases where the caller has passed <expected> as <location> or
// similar mishaps, the implementation verifies <location> is a valid pointer.
#define CAS(l,o,n) cpu_CAS((uintptr_t*)l, (uintptr_t)o, (uintptr_t)n)
#define CAS(l,o,n) cpu_CAS((volatile uintptr_t*)l, (uintptr_t)o, (uintptr_t)n)
/**
* add a signed value to a variable without the possibility of interference

View File

@ -63,13 +63,12 @@ bool ia32_cap(IA32Cap cap)
}
// we only store enum Vendor rather than the string because that
// is easier to compare.
static enum Vendor
static Ia32Vendor vendor;
Ia32Vendor ia32_Vendor()
{
UNKNOWN, INTEL, AMD
return vendor;
}
vendor = UNKNOWN;
static void DetectVendor()
{
@ -87,9 +86,9 @@ static void DetectVendor()
vendor_str[12] = '\0'; // 0-terminate
if(!strcmp(vendor_str, "AuthenticAMD"))
vendor = AMD;
vendor = IA32_VENDOR_AMD;
else if(!strcmp(vendor_str, "GenuineIntel"))
vendor = INTEL;
vendor = IA32_VENDOR_INTEL;
else
DEBUG_WARN_ERR(ERR::CPU_UNKNOWN_VENDOR);
}
@ -168,15 +167,7 @@ void ia32_Serialize()
//-----------------------------------------------------------------------------
// CPU / feature detect
//-----------------------------------------------------------------------------
// returned in edx by CPUID 0x80000007.
enum AmdPowerNowFlags
{
POWERNOW_FREQ_ID_CTRL = 2
};
// identifier string
/// functor to remove substrings from the CPU identifier string
class StringStripper
@ -243,25 +234,25 @@ const char* ia32_IdentifierString()
// doesn't recognize.
if(!have_brand_string || strncmp(identifier_string, "Unknow", 6) == 0)
{
if(vendor == AMD)
if(vendor == IA32_VENDOR_AMD)
{
// everything else is either too old, or should have a brand string.
if(family == 6)
{
if(model == 3 || model == 7)
SAFE_STRCPY(identifier_string, "AMD Duron");
SAFE_STRCPY(identifier_string, "IA32_VENDOR_AMD Duron");
else if(model <= 5)
SAFE_STRCPY(identifier_string, "AMD Athlon");
SAFE_STRCPY(identifier_string, "IA32_VENDOR_AMD Athlon");
else
{
if(ia32_cap(IA32_CAP_AMD_MP))
SAFE_STRCPY(identifier_string, "AMD Athlon MP");
SAFE_STRCPY(identifier_string, "IA32_VENDOR_AMD Athlon MP");
else
SAFE_STRCPY(identifier_string, "AMD Athlon XP");
SAFE_STRCPY(identifier_string, "IA32_VENDOR_AMD Athlon XP");
}
}
}
else if(vendor == INTEL)
else if(vendor == IA32_VENDOR_INTEL)
{
// everything else is either too old, or should have a brand string.
if(family == 6)
@ -293,26 +284,8 @@ const char* ia32_IdentifierString()
}
int ia32_IsThrottlingPossible()
{
if(vendor == INTEL)
{
if(ia32_cap(IA32_CAP_EST))
return 1;
}
else if(vendor == AMD)
{
u32 regs[4];
if(ia32_asm_cpuid(0x80000007, regs))
{
if(regs[EDX] & POWERNOW_FREQ_ID_CTRL)
return 1;
}
}
return 0; // pretty much authoritative, so don't return -1.
}
//-----------------------------------------------------------------------------
// CPU frequency
// set scheduling priority and restore when going out of scope.
class ScopedSetPriority

View File

@ -24,6 +24,21 @@
extern void ia32_Init();
extern void ia32_Shutdown();
/**
* CPU vendor.
* (this is exposed because some CPUID functions are vendor-specific.)
* (an enum is easier to compare than the original string values.)
**/
enum Ia32Vendor
{
IA32_VENDOR_UNKNOWN,
IA32_VENDOR_INTEL,
IA32_VENDOR_AMD,
};
extern Ia32Vendor ia32_Vendor();
/**
* bit indices of CPU capability flags (128 bits).
* values are defined by IA-32 CPUID feature flags - do not change!
@ -66,12 +81,6 @@ extern bool ia32_cap(IA32Cap cap);
**/
extern const char* ia32_IdentifierString();
/**
* @return whether CPU frequency throttling is possible or
* may potentially happen (if so, using RDTSC is unsafe).
**/
extern int ia32_IsThrottlingPossible();
/**
* @return the cached result of a precise measurement of the
* CPU frequency.

View File

@ -49,12 +49,6 @@ static void unlock()
win_unlock(WDBG_CS);
}
// used in a desperate attempt to avoid deadlock in wdbg_exception_handler.
static bool is_locked()
{
return win_is_locked(WDBG_CS) == 1;
}
void debug_puts(const char* text)
{
@ -234,7 +228,7 @@ static LibError call_while_suspended(WhileSuspendedFunc func, void* user_arg)
// parameter passing to helper thread. currently static storage,
// but the struct simplifies switching to a queue later.
static struct BreakInfo
struct BreakInfo
{
uintptr_t addr;
DbgBreakType type;
@ -242,13 +236,14 @@ static struct BreakInfo
// determines what brk_thread_func will do.
// set/reset by debug_remove_all_breaks.
bool want_all_disabled;
}
brk_info;
};
static BreakInfo brk_info;
// Local Enable bits of all registers we enabled (used when restoring all).
static DWORD brk_all_local_enables;
// IA32 limit; will need to update brk_enable_in_ctx if this changes.
// IA-32 limit; will need to update brk_enable_in_ctx if this changes.
static const uint MAX_BREAKPOINTS = 4;
@ -668,7 +663,7 @@ LONG WINAPI wdbg_exception_filter(EXCEPTION_POINTERS* ep)
// someone is already holding the dbghelp lock - this is bad.
// we'll report this problem first and then try to display the
// exception info regardless (maybe dbghelp won't blow up).
if(is_locked())
if(win_is_locked(WDBG_SYM_CS) == 1)
DISPLAY_ERROR(L"Exception raised while critical section is held - may deadlock..");
// extract details from ExceptionRecord.

View File

@ -52,15 +52,15 @@ WINIT_REGISTER_FUNC(wdbg_sym_shutdown);
// nested stack traces are ignored and only the error is displayed.
// protects dbghelp (which isn't thread-safe) and
// parameter passing to the breakpoint helper thread.
// protects the non-reentrant dbghelp library.
static void lock()
{
win_lock(WDBG_CS);
win_lock(WDBG_SYM_CS);
}
static void unlock()
{
win_unlock(WDBG_CS);
win_unlock(WDBG_SYM_CS);
}

View File

@ -16,15 +16,19 @@
#include <list>
#include "lib/path_util.h"
#include "lib/allocators.h"
#include "lib/res/file/file.h" // path_is_subpath
#include "win.h"
#include "winit.h"
#include "wutil.h"
#pragma SECTION_INIT(5)
WINIT_REGISTER_FUNC(wdir_watch_Init);
#pragma FORCE_INCLUDE(wdir_watch_Init)
#pragma SECTION_SHUTDOWN(5)
WINIT_REGISTER_FUNC(wdir_watch_shutdown);
#pragma FORCE_INCLUDE(wdir_watch_shutdown)
WINIT_REGISTER_FUNC(wdir_watch_Shutdown);
#pragma FORCE_INCLUDE(wdir_watch_Shutdown)
#pragma SECTION_RESTORE
@ -62,23 +66,6 @@ WINIT_REGISTER_FUNC(wdir_watch_shutdown);
// the completion key is used to associate Watch with the directory handle.
// list of all active watches. required, since we have to be able to
// cancel watches; also makes detecting duplicates possible.
//
// only store pointer in container - they're not copy-equivalent
// (dtor would close hDir).
//
// key is intptr_t "reqnum"; they aren't reused to avoid problems
// with stale reqnums after cancelling;
// hence, map instead of vector and freelist.
struct Watch;
typedef std::map<intptr_t, Watch*> Watches;
typedef Watches::iterator WatchIt;
// list of all active watches to detect duplicates and for easier cleanup.
// only store pointer in container - they're not copy-equivalent.
static Watches watches;
// don't worry about size; heap-allocated.
struct Watch
{
@ -115,72 +102,110 @@ struct Watch
{
memset(&ovl, 0, sizeof(ovl));
// change_buf[] doesn't need init
watches[reqnum] = this;
}
~Watch()
{
CloseHandle(hDir);
hDir = INVALID_HANDLE_VALUE;
watches[reqnum] = 0;
}
};
// global state
static HANDLE hIOCP = 0;
// not INVALID_HANDLE_VALUE! (CreateIoCompletionPort requirement)
//-----------------------------------------------------------------------------
// list of active watches
static intptr_t last_reqnum = 1000;
// start value provides a little protection against passing in bogus reqnums
// (we don't bother using a tag for safety though - it isn't important)
// we need to be able to cancel watches, which requires a 'list' of them.
// this also makes detecting duplicates possible and simplifies cleanup.
//
// key is intptr_t "reqnum"; they aren't reused to avoid problems with
// stale reqnums after canceling; hence, use map instead of array.
//
// only store pointer in container - they're not copy-equivalent
// (dtor would close hDir).
typedef std::map<intptr_t, Watch*> Watches;
typedef Watches::iterator WatchIt;
static Watches* watches;
typedef std::list<std::string> Events;
static Events pending_events;
// rationale:
// we need a queue, instead of just taking events from the change_buf,
// because we need to re-issue the watch immediately after it returns
// data. of course we can't have the app read from the buffer while
// waiting for RDC to write to the buffer - race condition.
// an alternative to a queue would be to allocate another buffer,
// but that's more complicated, and this way is cleaner anyway.
static LibError wdir_watch_shutdown()
static void FreeAllWatches()
{
CloseHandle(hIOCP);
hIOCP = INVALID_HANDLE_VALUE;
// free all (dynamically allocated) Watch objects
/*si for(WatchIt it = watches.begin(); it != watches.end(); ++it)
delete it->second;
watches.clear();
*/
return INFO::OK;
for(WatchIt it = watches->begin(); it != watches->end(); ++it)
{
Watch* w = it->second;
delete w;
}
watches->clear();
}
static Watch* WatchFromReqnum(intptr_t reqnum)
{
return (*watches)[reqnum];
}
//-----------------------------------------------------------------------------
// event queue
// rationale:
// we need a queue, instead of just taking events from the change_buf,
// because we need to re-issue the watch immediately after it returns
// data. of course we can't have the app read from the buffer while
// waiting for RDC to write to the buffer - race condition.
// an alternative to a queue would be to allocate another buffer,
// but that's more complicated, and this way is cleaner anyway.
typedef std::list<std::string> Events;
static Events* pending_events;
//-----------------------------------------------------------------------------
// allocate static objects
// manual allocation and construction/destruction of static objects is
// required because winit calls us before static ctors and after dtors.
static void AllocStaticObjects()
{
STATIC_STORAGE(ss, 200);
#include "lib/nommgr.h"
void* addr1 = static_calloc(&ss, sizeof(Watches));
watches = new(addr1) Watches;
void* addr2 = static_calloc(&ss, sizeof(Events));
pending_events = new(addr2) Events;
#include "lib/mmgr.h"
}
static void FreeStaticObjects()
{
watches->~Watches();
pending_events->~Events();
}
//-----------------------------------------------------------------------------
// global state
static HANDLE hIOCP = 0; // CreateIoCompletionPort requires 0, not INVALID_HANDLE_VALUE
// note: the start value provides a little protection against bogus reqnums
// (we don't bother using a tag for safety though - it isn't important)
static intptr_t last_reqnum = 1000;
// HACK - see call site
static void get_packet();
static Watch* find_watch(intptr_t reqnum)
{
return watches[reqnum];
}
// path: portable and relative, must add current directory and convert to native
// better to use a cached string from rel_chdir - secure
LibError dir_add_watch(const char* dir, intptr_t* _reqnum)
LibError dir_add_watch(const char* dir, intptr_t* preqnum)
{
LibError err = ERR::FAIL;
LibError err = ERR::FAIL;
WIN_SAVE_LAST_ERROR; // Create*
intptr_t reqnum;
*_reqnum = 0;
*preqnum = 0;
{
const std::string dir_s(dir);
@ -188,7 +213,7 @@ LibError dir_add_watch(const char* dir, intptr_t* _reqnum)
// check if this is a subdirectory of an already watched dir tree
// (much faster than issuing a new watch for every subdir).
// this also prevents watching the same directory twice.
for(WatchIt it = watches.begin(); it != watches.end(); ++it)
for(WatchIt it = watches->begin(); it != watches->end(); ++it)
{
Watch* const w = it->second;
if(!w)
@ -237,6 +262,7 @@ LibError dir_add_watch(const char* dir, intptr_t* _reqnum)
try
{
Watch* w = new Watch(reqnum, dir_s, hDir);
(*watches)[reqnum] = w;
// add trailing \ if not already there
if(dir_s[dir_s.length()-1] != '\\')
@ -260,7 +286,7 @@ LibError dir_add_watch(const char* dir, intptr_t* _reqnum)
done:
err = INFO::OK;
*_reqnum = reqnum;
*preqnum = reqnum;
fail:
WIN_RESTORE_LAST_ERROR;
@ -273,7 +299,7 @@ LibError dir_cancel_watch(const intptr_t reqnum)
if(reqnum <= 0)
WARN_RETURN(ERR::INVALID_PARAM);
Watch* w = find_watch(reqnum);
Watch* w = WatchFromReqnum(reqnum);
// watches[reqnum] is invalid - big trouble
if(!w)
WARN_RETURN(ERR::FAIL);
@ -290,8 +316,8 @@ LibError dir_cancel_watch(const intptr_t reqnum)
// its reqnum isn't reused; if we receive a packet, it's ignored.
BOOL ok = CancelIo(w->hDir);
(*watches)[reqnum] = 0;
delete w;
watches[reqnum] = 0;
return LibError_from_win32(ok);
}
@ -315,7 +341,7 @@ static void extract_events(Watch* w)
for(int i = 0; i < (int)fni->FileNameLength/2; i++)
fn += (char)fni->FileName[i];
pending_events.push_back(fn);
pending_events->push_back(fn);
// advance to next entry in buffer (variable length)
const DWORD ofs = fni->NextEntryOffset;
@ -332,8 +358,7 @@ static void extract_events(Watch* w)
static void get_packet()
{
// poll for change notifications from all pending watches
DWORD bytes_transferred;
// used to determine if packet is valid or a kickoff
DWORD bytes_transferred; // used to determine if packet is valid or a kickoff
ULONG_PTR key;
OVERLAPPED* povl;
BOOL got_packet = GetQueuedCompletionStatus(hIOCP, &bytes_transferred, &key, &povl, 0);
@ -341,7 +366,7 @@ static void get_packet()
return;
const intptr_t reqnum = (intptr_t)key;
Watch* const w = find_watch(reqnum);
Watch* const w = WatchFromReqnum(reqnum);
// watch was subsequently removed - ignore the error.
if(!w)
return;
@ -375,12 +400,32 @@ LibError dir_get_changed_file(char* fn)
get_packet();
// nothing to return; call again later.
if(pending_events.empty())
if(pending_events->empty())
return ERR::AGAIN; // NOWARN
const std::string& fn_s = pending_events.front();
const std::string& fn_s = pending_events->front();
strcpy_s(fn, PATH_MAX, fn_s.c_str());
pending_events.pop_front();
pending_events->pop_front();
return INFO::OK;
}
//-----------------------------------------------------------------------------
static LibError wdir_watch_Init()
{
AllocStaticObjects();
return INFO::OK;
}
static LibError wdir_watch_Shutdown()
{
CloseHandle(hIOCP);
hIOCP = INVALID_HANDLE_VALUE;
FreeAllWatches();
FreeStaticObjects();
return INFO::OK;
}

View File

@ -44,13 +44,19 @@ bool CounterQPC::IsSafe() const
return true;
// note: we have separate modules that directly access some of the
// counters potentially used by QPC. marking them or QPC unsafe is
// risky because users can override either of those decisions.
// directly disabling them is ugly (increased coupling).
// instead, we'll make sure our implementations can coexist with QPC and
// verify the secondary reference timer has a different frequency.
// counters potentially used by QPC. disabling the redundant counters
// would be ugly (increased coupling). instead, we'll make sure our
// implementations can coexist with QPC and verify the secondary
// reference timer has a different frequency.
// the PMT is safe (see discussion in CounterPmt::IsSafe);
// the PMT is generally safe (see discussion in CounterPmt::IsSafe),
// but older QPC implementations had problems with 24-bit rollover.
// "System clock problem can inflate benchmark scores"
// (http://www.lionbridge.com/bi/cont2000/200012/perfcnt.asp ; no longer
// online, nor findable in Google Cache / archive.org) tells of
// incorrect values every 4.6 seconds (i.e. 24 bits @ 3.57 MHz) unless
// the timer is polled in the meantime. fortunately, this is guaranteed
// by our periodic updates (which come at least that often).
if(m_frequency == PIT_FREQ)
return true;

View File

@ -84,6 +84,36 @@ static LibError InitPerCpuTscStates(double cpuClockFrequency)
return INFO::OK;
}
//-----------------------------------------------------------------------------
/*
int ia32_IsThrottlingPossible()
{
// returned in edx by CPUID 0x80000007.
enum AmdPowerNowFlags
{
POWERNOW_FREQ_ID_CTRL = 2
};
if(vendor == IA32_VENDOR_INTEL)
{
if(ia32_cap(IA32_CAP_EST))
return 1;
}
else if(vendor == IA32_VENDOR_AMD)
{
u32 regs[4];
if(ia32_asm_cpuid(0x80000007, regs))
{
if(regs[EDX] & POWERNOW_FREQ_ID_CTRL)
return 1;
}
}
return 0; // pretty much authoritative, so don't return -1.
}
*/
//-----------------------------------------------------------------------------

View File

@ -16,6 +16,7 @@
#include "lib/sysdep/win/win.h"
#include "lib/sysdep/win/winit.h"
#include "lib/sysdep/win/wcpu.h"
#include "lib/sysdep/acpi.h"
#include "lib/adts.h"
#include "lib/bits.h"
@ -97,9 +98,12 @@ static ICounter* CreateCounter(uint id)
// unusual and thus bad, but there's also one advantage: we avoid
// using global operator new before the CRT is initialized (risky).
//
// note: we can't just define these classes as static because their
// ctors (necessary for vptr initialization) will run during _cinit,
// which is already after our use of them.
// alternatives:
// - defining as static doesn't work because the ctors (necessary for
// vptr initialization) run during _cinit, which comes after our
// first use of them.
// - using static_calloc isn't possible because we don't know the
// size until after the alloc / placement new.
static const size_t MEM_SIZE = 200; // checked below
static u8 mem[MEM_SIZE];
static u8* nextMem = mem;
@ -458,6 +462,12 @@ static inline void ShutdownUpdateThread()
static LibError whrt_Init()
{
// note: several counter implementations use acpi.cpp. if a counter is
// deemed unsafe, it is shut down, which releases the (possibly only)
// reference to the ACPI module. unloading and reloading it after trying
// each counter would be a waste of time, so we grab a reference here.
(void)acpi_Init();
InitCounter();
UpdateTimerState(); // must come before InitUpdateThread to avoid race
@ -474,5 +484,7 @@ static LibError whrt_Shutdown()
ShutdownCounter();
acpi_Shutdown();
return INFO::OK;
}

View File

@ -30,8 +30,15 @@ WINIT_REGISTER_FUNC(waio_shutdown);
#pragma SECTION_RESTORE
#define lock() win_lock(WAIO_CS)
#define unlock() win_unlock(WAIO_CS)
static void lock()
{
win_lock(WAIO_CS);
}
static void unlock()
{
win_unlock(WAIO_CS);
}
// return the largest sector size [bytes] of any storage medium

View File

@ -49,7 +49,7 @@ static bool fullscreen;
// the app is shutting down.
// if set, ignore further Windows messages for clean shutdown.
static bool is_shutdown;
static bool is_quitting;
// app instance.
// returned by GetModuleHandle and used in kbd hook and window creation.
@ -426,16 +426,21 @@ SDL_Surface* SDL_GetVideoSurface()
//----------------------------------------------------------------------------
// event queue
// note: we aren't using winit at all, so static objects are safe.
typedef std::queue<SDL_Event> Queue;
static Queue queue;
static void queue_event(const SDL_Event& ev)
{
debug_assert(!is_quitting);
queue.push(ev);
}
static bool dequeue_event(SDL_Event* ev)
{
debug_assert(!is_quitting);
if(queue.empty())
return false;
*ev = queue.front();
@ -444,18 +449,6 @@ static bool dequeue_event(SDL_Event* ev)
}
static void queue_quit_event()
{
SDL_Event ev;
ev.type = SDL_QUIT;
queue_event(ev);
}
//----------------------------------------------------------------------------
// app activation
@ -540,6 +533,15 @@ Uint8 SDL_GetAppState()
return app_state;
}
static void queue_quit_event()
{
SDL_Event ev;
ev.type = SDL_QUIT;
queue_event(ev);
}
//----------------------------------------------------------------------------
// keyboard
@ -556,7 +558,7 @@ static void queue_key_event(uint type, uint sdlk, WCHAR unicode_char)
static Uint8 keys[SDLK_LAST];
// winuser.h promises VK_0 and VK_A etc. match ASCII value.
// winuser.h promises VK_0..9 and VK_A..Z etc. match ASCII value.
#define VK_0 '0'
#define VK_A 'A'
@ -792,7 +794,7 @@ static void screen_to_client(int screen_x, int screen_y, int& client_x, int& cli
pt.x = (LONG) screen_x;
pt.y = (LONG) screen_y;
// this can fail for unknown reasons even if hWnd is still
// valid and !is_shutdown. don't WARN_IF_FALSE.
// valid and !is_quitting. don't WARN_IF_FALSE.
if(!ScreenToClient(hWnd, &pt))
pt.x = pt.y = 0;
client_x = (int)pt.x;
@ -987,7 +989,7 @@ int SDL_ShowCursor(int toggle)
static LRESULT CALLBACK wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if(is_shutdown)
if(is_quitting)
return DefWindowProc(hWnd, uMsg, wParam, lParam);
switch(uMsg)
@ -1338,7 +1340,7 @@ void SDL_Quit()
if(!ModuleShouldShutdown(&initState))
return;
is_shutdown = true;
is_quitting = true;
// redirected to stdout.txt in SDL_Init;
// close to avoid BoundsChecker warning.

View File

@ -34,10 +34,9 @@ extern void win_free(void* p);
enum
{
ONCE_CS,
WTIME_CS,
WAIO_CS,
WIN_CS,
WDBG_CS,
WDBG_SYM_CS,
NUM_CS
};