diff --git a/.vscode/settings.json b/.vscode/settings.json index 535e523..dbd0e77 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,6 +21,9 @@ "os.h": "c", "os_core.h": "c", "file.h": "c", - "xiosbase": "c" + "xiosbase": "c", + "iterator": "c", + "memory": "c", + "format": "c" } } \ No newline at end of file diff --git a/code/base/arena.c b/code/base/arena.c index dcee4be..d4c0138 100644 --- a/code/base/arena.c +++ b/code/base/arena.c @@ -11,134 +11,35 @@ //- rjf: arena creation/destruction -#if 0 -internal Arena* -arena_alloc_(ArenaParams *params) -{ - // rjf: round up reserve/commit sizes - U64 reserve_size = params->reserve_size; - U64 commit_size = params->commit_size; - if(params->flags & ArenaFlag_LargePages) - { - reserve_size = AlignPow2(reserve_size, os_get_system_info()->large_page_size); - commit_size = AlignPow2(commit_size, os_get_system_info()->large_page_size); - } - else - { - reserve_size = AlignPow2(reserve_size, os_get_system_info()->page_size); - commit_size = AlignPow2(commit_size, os_get_system_info()->page_size); - } - - // rjf: reserve/commit initial block - void *base = params->optional_backing_buffer; - if(base == 0) - { - if(params->flags & ArenaFlag_LargePages) - { - base = os_reserve_large(reserve_size); - os_commit_large(base, commit_size); - } - else - { - base = os_reserve(reserve_size); - os_commit(base, commit_size); - } - } - - // rjf: panic on arena creation failure -#if OS_FEATURE_GRAPHICAL - if(Unlikely(base == 0)) - { - os_graphical_message(1, str8_lit("Fatal Allocation Failure"), str8_lit("Unexpected memory allocation failure.")); - os_abort(1); - } -#endif - - // rjf: extract arena header & fill - Arena *arena = (Arena *)base; - arena->current = arena; - arena->flags = params->flags; - arena->cmt_size = (U32)params->commit_size; - arena->res_size = params->reserve_size; - arena->base_pos = 0; - arena->pos = ARENA_HEADER_SIZE; - arena->cmt = commit_size; - arena->res = reserve_size; - AsanPoisonMemoryRegion(base, commit_size); - AsanUnpoisonMemoryRegion(base, ARENA_HEADER_SIZE); - return arena; -} -#endif - Arena* arena_alloc_(ArenaParams* params) { - // rjf: round up reserve/commit sizes - U64 reserve_size = params->reserve_size; - U64 commit_size = params->commit_size; + B32 is_virtual = false; + // This composite arena has the ability to decide a default behavior for allocation - if(params->flags & ArenaFlag_LargePages) - { - reserve_size = align_pow2(reserve_size, os_get_system_info()->large_page_size); - commit_size = align_pow2(commit_size, os_get_system_info()->large_page_size); - } - else - { - reserve_size = align_pow2(reserve_size, os_get_system_info()->page_size); - commit_size = align_pow2(commit_size, os_get_system_info()->page_size); - } + void* base = alloc(params->backing, params->block_size); - // rjf: reserve/commit initial block - void *base = params->optional_backing_buffer; - if(base == 0) - { - if(params->flags & ArenaFlag_LargePages) - { - base = os_reserve_large(reserve_size); - os_commit_large(base, commit_size); - } - else - { - base = os_reserve(reserve_size); - os_commit(base, commit_size); - } - } - - // rjf: panic on arena creation failure + // rjf: panic on arena creation failure #if OS_FEATURE_GRAPHICAL - if(Unlikely(base == 0)) - { - os_graphical_message(1, str8_lit("Fatal Allocation Failure"), str8_lit("Unexpected memory allocation failure.")); - os_abort(1); - } + if(unlikely(base == 0)) + { + os_graphical_message(1, str8_lit("Fatal Allocation Failure"), str8_lit("Unexpected memory allocation failure.")); + os_abort(1); + } #endif - // rjf: extract arena header & fill - Arena *arena = (Arena *)base; - arena->current = arena; - arena->flags = params->flags; - arena->cmt_size = (U32)params->commit_size; - arena->res_size = params->reserve_size; - arena->base_pos = 0; - arena->pos = ARENA_HEADER_SIZE; - arena->cmt = commit_size; - arena->res = reserve_size; - AsanPoisonMemoryRegion(base, commit_size); - AsanUnpoisonMemoryRegion(base, ARENA_HEADER_SIZE); - return arena; -} + // rjf: extract arena header & fill + Arena* arena = (Arena*) base; + arena->current = arena; + arena->backing = params->backing; + arena->pos = size_of(Arena); + arena->block_size = params->block_size; + arena->flags = params->flags; -// Note(Ed): INLINED -// internal void -// arena_release(Arena *arena) -// { -// for(Arena *n = arena->current, *prev = 0; n != 0; n = prev) -// { -// prev = n->prev; -// os_release(n, n->res); -// } -// } + asan_unpoison_memory_region(base, sizeof(Arena)); + return arena; +} //- rjf: arena push/pop core functions @@ -247,16 +148,16 @@ arena_pop(Arena *arena, U64 amt) //- rjf: temporary arena scopes -internal Temp +internal TempArena temp_begin(Arena *arena) { - U64 pos = arena_pos(arena); - Temp temp = {arena, pos}; + U64 pos = arena_pos(arena); + TempArena temp = {arena, pos}; return temp; } internal void -temp_end(Temp temp) +temp_end(TempArena temp) { arena_pop_to(temp.arena, temp.pos); } diff --git a/code/base/arena.h b/code/base/arena.h index 7d357c8..96de5ec 100644 --- a/code/base/arena.h +++ b/code/base/arena.h @@ -19,17 +19,19 @@ typedef U32 ArenaFlags; enum { - ArenaFlag_NoChain = ( 1 << 0), - ArenaFlag_LargePages = ( 1 << 1), + // Don't chain this arena + ArenaFlag_NoChain = (1 << 0), + // Only works if backing is virtual memory, will allocate a new backing VArena when the current block exhausts + // Otherwise will assume backing can chain multiple block_size arenas until failure. + ArenaFlag_ChainVirtual = (1 << 1), }; typedef struct ArenaParams ArenaParams; struct ArenaParams { - ArenaFlags flags; - U64 reserve_size; - U64 commit_size; - void* optional_backing_buffer; + AllocatorInfo backing; + ArenaFlags flags; + U64 block_size; // If chaining VArenas set this to the reserve size }; /* NOTE(Ed): This is a combination of several concepts into a single interface: @@ -44,18 +46,14 @@ struct ArenaParams typedef struct Arena Arena; struct Arena { - AllocatorInfo backing; Arena* prev; // previous arena in chain Arena* current; // current arena in chain - ArenaFlags flags; - U32 cmt_size; - U64 res_size; - U64 base_pos; + AllocatorInfo backing; U64 pos; - U64 cmt; - U64 res; + U64 block_size; + ArenaFlags flags; }; -static_assert(sizeof(Arena) <= ARENA_HEADER_SIZE, "sizeof(Arena) <= MD_ARENA_HEADER_SIZE"); +static_assert(size_of(Arena) <= ARENA_HEADER_SIZE, "sizeof(Arena) <= ARENA_HEADER_SIZE"); typedef struct TempArena TempArena; struct TempArena @@ -67,12 +65,12 @@ struct TempArena //////////////////////////////// //~ rjf: Arena Functions -MD_API void* arena_allocator_proc(void* allocator_data, AllocType type, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags); +MD_API void* arena_allocator_proc(void* allocator_data, AllocatorMode mode, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags); //- rjf: arena creation/destruction MD_API Arena* arena_alloc_(ArenaParams *params); -#define arena_alloc(...) arena_alloc_( & ( ArenaParams) { .reserve_size = MB(64), .commit_size = KB(64), __VA_ARGS__ } ) +#define arena_alloc(...) arena_alloc_( & ( ArenaParams) { .backing = {}, .reserve_size = MB(64), .commit_size = KB(64), __VA_ARGS__ } ) void arena_release(Arena *arena); @@ -109,9 +107,6 @@ arena_release(Arena* arena) for (Arena* n = arena->current, *prev = 0; n != 0; n = prev) { prev = n->prev; - // os_release(n, n->res); alloc_free(arena->backing, n); } } - - diff --git a/code/base/memory.h b/code/base/memory.h index 1b3f4ce..f0e87ac 100644 --- a/code/base/memory.h +++ b/code/base/memory.h @@ -4,6 +4,7 @@ # include "linkage.h" # include "macros.h" # include "platform.h" +# include "base_types.h" #endif @@ -200,19 +201,19 @@ void* mem_move( void* destination, void const* source, SSIZE byte_count ) { if ( to_uptr(src_ptr) % size_of( SSIZE ) == to_uptr(dest_ptr) % size_of( SSIZE ) ) { - while ( pcast( UPTR, dest_ptr) % size_of( ssize ) ) + while ( pcast( UPTR, dest_ptr) % size_of( SSIZE ) ) { if ( ! byte_count-- ) return destination; *dest_ptr++ = *src_ptr++; } - while ( byte_count >= size_of( ssize ) ) + while ( byte_count >= size_of( SSIZE ) ) { * rcast(SSIZE*, dest_ptr) = * rcast(SSIZE const*, src_ptr); - byte_count -= size_of( ssize ); - dest_ptr += size_of( ssize ); - src_ptr += size_of( ssize ); + byte_count -= size_of( SSIZE ); + dest_ptr += size_of( SSIZE ); + src_ptr += size_of( SSIZE ); } } for ( ; byte_count; byte_count-- ) @@ -301,7 +302,7 @@ void* mem_set( void* destination, U8 fill_byte, SSIZE byte_count ) byte_count -= align_offset; { - u64 fill_doubleword = ( scast( U64, fill_word) << 32 ) | fill_word; + U64 fill_doubleword = ( scast( U64, fill_word) << 32 ) | fill_word; while ( byte_count > 31 ) { * rcast( U64*, dest_ptr + 0 ) = fill_doubleword; diff --git a/code/base/memory_substrate.c b/code/base/memory_substrate.c index a3d4a6f..7a88fd7 100644 --- a/code/base/memory_substrate.c +++ b/code/base/memory_substrate.c @@ -4,6 +4,7 @@ # include "../os/os.h" #endif +#ifdef MD_HEAP_ANALYSIS #define GEN_HEAP_STATS_MAGIC 0xDEADC0DE typedef struct _heap_stats _heap_stats; @@ -47,8 +48,9 @@ struct _heap_alloc_info SSIZE size; void* physical_start; }; +#endif -void* heap_allocator_proc( void* allocator_data, AllocType type, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags ) +void* heap_allocator_proc( void* allocator_data, AllocatorMode mode, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags ) { void* ptr = nullptr; // unused( allocator_data ); @@ -62,7 +64,7 @@ void* heap_allocator_proc( void* allocator_data, AllocType type, SSIZE size, SSI ssize track_size = max( alloc_info_size, alignment ) + alloc_info_remainder; switch ( type ) { - case EAllocation_FREE : + case EAllocatorMode_FREE : { if ( ! old_memory ) break; @@ -72,7 +74,7 @@ void* heap_allocator_proc( void* allocator_data, AllocType type, SSIZE size, SSI old_memory = alloc_info->physical_start; } break; - case EAllocation_ALLOC : + case EAllocatorMode_ALLOC : { size += track_size; } @@ -82,76 +84,83 @@ void* heap_allocator_proc( void* allocator_data, AllocType type, SSIZE size, SSI } #endif - switch ( type ) + switch ( mode ) { -#if defined( COMPILER_MSVC ) || ( defined( COMPILER_GCC ) && defined( OS_WINDOWS ) ) || ( defined( COMPILER_TINYC ) && defined( OS_WINDOWS ) ) - case EAllocType_ALLOC : + #if defined( COMPILER_MSVC ) || ( defined( COMPILER_GCC ) && defined( OS_WINDOWS ) ) || ( defined( COMPILER_TINYC ) && defined( OS_WINDOWS ) ) + case AllocatorMode_ALLOC: + { ptr = _aligned_malloc( size, alignment ); if ( flags & ALLOCATOR_FLAG_CLEAR_TO_ZERO ) zero_size( ptr, size ); - break; - case EAllocType_FREE : + } + break; + + case AllocatorMode_FREE: _aligned_free( old_memory ); - break; - case EAllocType_RESIZE : - { - AllocatorInfo a = heap(); - ptr = default_resize_align( a, old_memory, old_size, size, alignment ); - } - break; + break; -#elif defined( OS_LINUX ) && ! defined( CPU_ARM ) && ! defined( COMPILER_TINYC ) + case AllocatorMode_FREE_ALL: + { + AllocatorInfo a = heap(); + ptr = default_resize_align( a, old_memory, old_size, size, alignment ); + } + break; + + #elif defined( OS_LINUX ) && ! defined( CPU_ARM ) && ! defined( COMPILER_TINYC ) case EAllocation_ALLOC : - { - ptr = aligned_alloc( alignment, ( size + alignment - 1 ) & ~( alignment - 1 ) ); + { + ptr = aligned_alloc( alignment, ( size + alignment - 1 ) & ~( alignment - 1 ) ); - if ( flags & GEN_ALLOCATOR_FLAG_CLEAR_TO_ZERO ) - { - zero_size( ptr, size ); - } + if ( flags & GEN_ALLOCATOR_FLAG_CLEAR_TO_ZERO ) + { + zero_size( ptr, size ); } - break; + } + break; case EAllocation_FREE : - { - free( old_memory ); - } - break; + { + free( old_memory ); + } + break; case EAllocation_RESIZE : - { - AllocatorInfo a = heap(); - ptr = default_resize_align( a, old_memory, old_size, size, alignment ); - } - break; -#else + { + AllocatorInfo a = heap(); + ptr = default_resize_align( a, old_memory, old_size, size, alignment ); + } + break; + #else case EAllocType_ALLOC : - { - posix_memalign( &ptr, alignment, size ); + { + posix_memalign( &ptr, alignment, size ); - if ( flags & ALLOCATOR_FLAG_CLEAR_TO_ZERO ) - { - zero_size( ptr, size ); - } + if ( flags & ALLOCATOR_FLAG_CLEAR_TO_ZERO ) + { + zero_size( ptr, size ); } - break; + } + break; case EAllocType_FREE : - { - free( old_memory ); - } - break; + { + free( old_memory ); + } + break; case EAllocType_RESIZE : - { - AllocatorInfo a = heap(); - ptr = default_resize_align( a, old_memory, old_size, size, alignment ); - } - break; -#endif + { + AllocatorInfo a = heap(); + ptr = default_resize_align( a, old_memory, old_size, size, alignment ); + } + break; + #endif - case EAllocType_FREE_ALL : + case AllocatorMode_FREE_ALL: break; + + case AllocatorMode_QueryType: + return (void*) Heap } #ifdef GEN_HEAP_ANALYSIS @@ -172,6 +181,44 @@ void* heap_allocator_proc( void* allocator_data, AllocType type, SSIZE size, SSI void* vm_allocator_proc(void* allocator_data, AllocType type, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags) { + void* result = nullptr; + // if(params->flags & ArenaFlag_LargePages) + // { + // base = os_reserve_large(reserve_size); + // os_commit_large(base, commit_size); + // } + // else + // { + // base = os_reserve(reserve_size); + // os_commit(base, commit_size); + // } + + return result; } +VMemory vm_alloc(VMemoryParams params) +{ + // rjf: round up reserve/commit sizes + U64 reserve_size = params->reserve_size; + U64 commit_size = params->commit_size; + + if(params->flags & ArenaFlag_LargePages) + { + reserve_size = align_pow2(reserve_size, os_get_system_info()->large_page_size); + commit_size = align_pow2(commit_size, os_get_system_info()->large_page_size); + } + else + { + reserve_size = align_pow2(reserve_size, os_get_system_info()->page_size); + commit_size = align_pow2(commit_size, os_get_system_info()->page_size); + } + + // Allocate virtual memory + is_virtual = true; + + VMemory vm = ; + + // TODO(Ed): Move this to vmem? + asan_poison_memory_region(base, params->commit_size); +} diff --git a/code/base/memory_substrate.h b/code/base/memory_substrate.h index dd8a2ea..914c32d 100644 --- a/code/base/memory_substrate.h +++ b/code/base/memory_substrate.h @@ -19,18 +19,29 @@ #define MD__HAS_ZERO( x ) ( ( ( x ) - MD__ONES ) & ~( x ) & MD__HIGHS ) #endif -typedef U32 AllocType; -enum AllocType enum_underlying(U32) +// Return value of allocator_type +typedef U32 AllocatorType; +enum { - EAllocType_ALLOC, - EAllocType_FREE, - EAllocType_FREE_ALL, - EAllocType_RESIZE, + AllocatorType_Heap = 0, // Genreal heap allocator + AllocatorType_VArena = 1, // Arena allocator backed by virtual address space + AllocatorType_FArena = 2, // Fixed arena backed back by a fixed size block of memory (usually a byte slice) + AllocatorType_Arena = 3, // Composite arena used originally by RAD Debugger & Metadesk }; -typedef void*(AllocatorProc)( void* allocator_data, AllocType type, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags ); +typedef U32 AllocatorMode; +enum AllocatorMode enum_underlying(U32) +{ + AllocatorMode_ALLOC, + AllocatorMode_FREE, + AllocatorMode_FREE_ALL, + AllocatorMode_RESIZE, + AllocatorMode_QueryType, +}; -typedef struct AllocatorInfo; +typedef void*(AllocatorProc)( void* allocator_data, AllocatorMode type, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags ); + +typedef struct AllocatorInfo AllocatorInfo; struct AllocatorInfo { AllocatorProc* proc; @@ -50,6 +61,9 @@ enum AllocFlag # define MD_DEFAULT_ALLOCATOR_FLAGS ( ALLOCATOR_FLAG_CLEAR_TO_ZERO ) #endif +// Allows the user to retrieve which type of allocator is being used. +AllocatorType allocator_type(AllocatorInfo a); + //! Allocate memory with default alignment. void* alloc( AllocatorInfo a, SSIZE size ); @@ -83,6 +97,7 @@ void* resize_align( AllocatorInfo a, void* ptr, SSIZE old_size, SSIZE new_size, //! Use this if you don't need a "fancy" resize allocation void* default_resize_align( AllocatorInfo a, void* ptr, SSIZE old_size, SSIZE new_size, SSIZE alignment ); +#ifdef MD_HEAP_ANALYSIS /* heap memory analysis tools */ /* define GEN_HEAP_ANALYSIS to enable this feature */ /* call zpl_heap_stats_init at the beginning of the entry point */ @@ -91,8 +106,9 @@ MD_API void heap_stats_init( void ); MD_API SSIZE heap_stats_used_memory( void ); MD_API SSIZE heap_stats_alloc_count( void ); MD_API void heap_stats_check( void ); +#endif -MD_API void* heap_allocator_proc( void* allocator_data, AllocType type, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags ); +MD_API void* heap_allocator_proc( void* allocator_data, AllocatorMode mode, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags ); #ifndef heap //! The heap allocator backed by operating system's memory manager. @@ -109,44 +125,83 @@ MD_API void* heap_allocator_proc( void* allocator_data, AllocType type, SSIZE si #define mfree( ptr ) free( heap(), ptr ) #endif -typedef U32 VMemoryFlags; +/* Virtual Memory Arena + This is separate from the composite arena used by HMH/Casey Muratori/RJF + This arena stricly manages one reservation of the process's virtual address space. + + Segregating this from composite Arena style causes more moremoy to be used for "allocator headers", however it allows + users of a library to have greater control over the allocation strategy used from their side instead of the library itself. + + Like with the composite Arena, the VArena has its struct as the header of the reserve of memory. +*/ + +typedef U32 VArenaFlags; enum { - VMemoryFlag_NoChain = (1 << 0), - VMemoryFlag_LargePages = (1 << 1), + VArenaFlag_LargePages = (1 << 0), }; -typedef struct VMemory_Params; -struct VMemory_Params +typedef struct VArenaParams VArenaParams; +struct VArenaParams { - VMemoryFlags flags; - U64 reserve_size; - U64 commit_size; - // void* optional_backing_buffer; + U64 base_addr; + VArenaFlags flags; + U64 reserve_size; + U64 commit_size; }; -typedef struct VMemory; -struct VMemory +typedef struct VArena VArena; +struct VArena { - U32 cmt_size; - U32 res_size; + VArenaFlags flags; U64 base_pos; U64 cmt; U64 res; + U64 res_size; + U64 cmt_size; }; -AllocatorInfo vm_allocator(VMemory* vm) { - AllocatorInfo info = { vm_allocator_proc, vm } - return info +AllocatorInfo vm_allocator(VArena* vm) { + AllocatorInfo info = { vm_allocator_proc, vm }; + return info; } -MD_API void* vm_allocator_proc(void* allocator_data, AllocType type, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags); +VArena* varena_alloc (VArenaParams params); +void varena_commit (VArena vm, SSIZE commit_size); +VArenaParams varena_free (VArena vm); +SSIZE varena_page_size(SSIZE* alignment_out); + +MD_API void* varena_allocator_proc(void* allocator_data, AllocatorMode mode, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags); + +typedef struct ByteSlice ByteSlice; +struct ByteSlice +{ + U8* data; + SSIZE len; +}; + +// Fixed size arena +typedef struct FArena FArena; +struct FArena +{ + ByteSlice slice; + SSIZE pos; +}; + +FArena farena_from_byteslice(ByteSlice slice); + +MD_API void* farena_allocator_proc(void* allocator_data, AllocatorMode mode, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags); // Inlines +inline +AllocatorType allocator_type(AllocatorInfo a) { + return (AllocatorType) a.proc(a.data, AllocatorMode_QueryType, 0, 0, nullptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS); +} + inline void* alloc_align( AllocatorInfo a, SSIZE size, SSIZE alignment ) { - return a.Proc( a.Data, EAllocType_ALLOC, size, alignment, nullptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS ); + return a.proc( a.data, AllocatorMode_ALLOC, size, alignment, nullptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS ); } inline @@ -157,12 +212,12 @@ void* alloc( AllocatorInfo a, SSIZE size ) { inline void allocator_free( AllocatorInfo a, void* ptr ) { if ( ptr != nullptr ) - a.Proc( a.Data, EAllocType_FREE, 0, 0, ptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS ); + a.proc( a.data, AllocatorMode_FREE, 0, 0, ptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS ); } inline void free_all( AllocatorInfo a ) { - a.Proc( a.Data, EAllocType_FREE_ALL, 0, 0, nullptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS ); + a.proc( a.data, AllocatorMode_FREE_ALL, 0, 0, nullptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS ); } inline @@ -172,7 +227,7 @@ void* resize( AllocatorInfo a, void* ptr, SSIZE old_size, SSIZE new_size ) { inline void* resize_align( AllocatorInfo a, void* ptr, SSIZE old_size, SSIZE new_size, SSIZE alignment ) { - return a.Proc( a.Data, EAllocType_RESIZE, new_size, alignment, ptr, old_size, MD_DEFAULT_ALLOCATOR_FLAGS ); + return a.proc( a.data, AllocatorMode_RESIZE, new_size, alignment, ptr, old_size, MD_DEFAULT_ALLOCATOR_FLAGS ); } inline diff --git a/code/metadesk.h b/code/metadesk.h index f2e8d28..f17b2c1 100644 --- a/code/metadesk.h +++ b/code/metadesk.h @@ -36,4 +36,3 @@ MD_NS_END // metagen -