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:
parent
6daf03b353
commit
5919fc7877
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
//
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
}
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user