Skip to content
This repository has been archived by the owner on Feb 8, 2024. It is now read-only.

Commit

Permalink
[Update]: Merge our memory allocator (vhmalloc) implementation into m…
Browse files Browse the repository at this point in the history
…ain (plus refactoring). (#34)

* [WIP]: Commit current allocator changes

* Update allocator.c

* [WIP}: Commit current allocator code

* Update allocator.c

* Update CASPAL.h

* Update allocator.c

* Update CASPAL.h

* Update CASPAL.h

* Update allocator.h

* Update allocator.c

* [Update]: Finish virtual heap allocator implementation

* Update CASPAL.h

* [Fix]: Remove unnecessary field initialization

* [Refactor]: Simplify the allocator code further

* [Update]: Add likely macro

* [Update/Fix]: Fix value parameter in the likely macro (and add more braces)

* [Update]: Improve likely macro, now using bool types (and casts)

* [Update]: Add unlikely macro with bool types and casts

* [Update]: Revert back to just likely

* [WIP]: Commit unfinished refactoring (will need this later)

* [Fix]: Use __attribute__((dllexport)) on Windows (visibility("default") doesn't work)

* [WIP]: Commit benchmark code
  • Loading branch information
KTSnowy authored Oct 26, 2023
1 parent f5e6c28 commit 12e9ca1
Show file tree
Hide file tree
Showing 13 changed files with 531 additions and 263 deletions.
Binary file modified Libraries/Otterkit.Native/build/nativelib.dll
Binary file not shown.
Binary file modified Libraries/Otterkit.Native/build/nativelib.dylib
Binary file not shown.
176 changes: 112 additions & 64 deletions Libraries/Otterkit.Native/nativelib/CASPAL.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,32 +39,45 @@
#error "Standard C99 (or later) support is required. Consider upgrading your compiler, or using GCC or Clang instead."
#endif

#if defined C11OrLater
#if defined C23OrLater
// Built-in Standard C23 static assert.
#define StaticAssert(condition, error) static_assert(condition, error);
#elif defined C11OrLater
// Built-in Standard C11 static assert.
#define StaticAssert(condition, error) _Static_assert(condition, #error);

#define StaticAssert(condition, error) _Static_assert(condition, error);
#else
// C99 doesn't have a built-in static assert, so we have to use this neat trick.
// If the condition is false, the compiler will complain that the array size is negative.
#define StaticAssert(condition, error) typedef int assert_ ## __LINE__ ## error[(condition) ? 5 : -5];
#define StaticAssert(condition, error) typedef int Assert ## __LINE__ ## Static[(condition) ? 5 : -5];

#endif

#if defined C23OrLater
// Built-in Standard C23 alignas.
#define aligned(x) alignas(x)

#elif defined C11OrLater
// Built-in Standard C11 alignas.
#define aligned(x) _Alignas(x)

#elif defined C99OrLater && defined __GNUC__ || defined __clang__
#elif defined C99OrLater && (defined __GNUC__ || defined __clang__)
// C99 doesn't have a built-in alignas, so we use compiler-specific attributes.
#define aligned(x) __attribute__((aligned(x)))

#else
#define aligned(x) // No alignas support, just ignore it.
// No alignas support, just ignore it.
#define aligned(x)
#endif

#if defined C23OrLater
// C23 already defines bool (equivalent to _Bool), true, and false as language keywords.
// There's (most likely) no need for us to do anything here.
// According to the C23 standard, when casting to a bool, the result is false if the
// value is a zero (for arithmetic types) or null (for pointers), otherwise the result is true.
#else
// C99 introducted _Bool, we'll typedef it as bool for the sake of readability.
// According to the C99 and C11 standards, when casting to a _Bool the result
// is 0 if the value compares equal to 0, otherwise the result is 1.
typedef _Bool bool;
#define true ((bool)1)
#define false ((bool)0)
#endif

// As defined by the C11 standard:
Expand Down Expand Up @@ -94,10 +107,10 @@ typedef unsigned int uint32;
typedef unsigned long long uint64;

// ...and make sure they are actually the correct size.
StaticAssert(sizeof(uint8) == 1, InvalidUint8Size);
StaticAssert(sizeof(uint16) == 2, InvalidUint16Size);
StaticAssert(sizeof(uint32) == 4, InvalidUint32Size);
StaticAssert(sizeof(uint64) == 8, InvalidUint64Size);
StaticAssert(sizeof(uint8) == 1, "Invalid uint8 size")
StaticAssert(sizeof(uint16) == 2, "Invalid uint16 size")
StaticAssert(sizeof(uint32) == 4, "Invalid uint32 size")
StaticAssert(sizeof(uint64) == 8, "Invalid uint64 size")

// Typedef signed integer types...
typedef signed char int8;
Expand All @@ -106,10 +119,10 @@ typedef signed int int32;
typedef signed long long int64;

// ...and make sure they are actually the correct size.
StaticAssert(sizeof(int8) == 1, InvalidInt8Size);
StaticAssert(sizeof(int16) == 2, InvalidInt16Size);
StaticAssert(sizeof(int32) == 4, InvalidInt32Size);
StaticAssert(sizeof(int64) == 8, InvalidInt64Size);
StaticAssert(sizeof(int8) == 1, "Invalid int8 size")
StaticAssert(sizeof(int16) == 2, "Invalid int16 size")
StaticAssert(sizeof(int32) == 4, "Invalid int32 size")
StaticAssert(sizeof(int64) == 8, "Invalid int64 size")

// Sizes were already checked above.
typedef uint64 uintptr;
Expand All @@ -121,28 +134,30 @@ typedef int64 intptr;

// Just to make things easier to read, double underscores everywhere is ugly and hard to read.
typedef __m128i vec128i;

#elif defined ARM64
#include <arm_neon.h>

// Same as above, but for Aarch64 with NEON.
typedef int8x16_t vec128i;

#endif

#define assembly __asm__

//╭──────────────────────────────────────────────────────────────────────────────────╮
//│ Platform detection and abstractions │
//╰──────────────────────────────────────────────────────────────────────────────────╯

#if defined _WIN64
#define PlatformWindows
// We need to include this, otherwise it won't compile.
// We need to include it, otherwise it won't compile (Why though?)
#include <windows.h>

#elif defined __linux__
// I hope we don't need to check for individual distributions, that would be a pain.
#define PlatformLinux

#elif defined __APPLE__
// Darwin is the name of the kernel used by both macOS and iOS (and others).
#define PlatformDarwin

#else
Expand All @@ -156,61 +171,65 @@ typedef int64 intptr;
#if defined PlatformWindows
#include <memoryapi.h>

// Windows virtual memory primitives.
#define sysVirtualAlloc(addr, size, prot, flags) VirtualAlloc(addr, size, flags, prot)
#define sysVirtualDealloc(addr, size, flags) VirtualFree(addr, size, flags)
#define SYS_READWRITE PAGE_READWRITE
#define SYS_PROTECTED PAGE_NOACCESS

#define memReadWrite PAGE_READWRITE
#define memProtected PAGE_NOACCESS
// On Windows, attempting to reserve an address that's already reserved will fail.
// This is contrary to mmap's behavior, which will just overwrite the existing mapping.
#define SYS_ALLOCATE MEM_COMMIT | MEM_RESERVE
#define SYS_RESERVE MEM_RESERVE

#define memReserve MEM_RESERVE
#define memCommit MEM_COMMIT
// Wish we had these on Unix, but we don't. Would make the intent clearer.
#define SYS_COMMIT MEM_COMMIT
#define SYS_DECOMMIT MEM_DECOMMIT

#define memDecommit MEM_DECOMMIT
#define memRelease MEM_RELEASE
// Only really needed on Windows, but we define it anyway for consistency.
#define SYS_RELEASE MEM_RELEASE

#elif defined PlatformLinux || defined PlatformDarwin
#include <sys/mman.h>
// Requests more virtual memory from the operating system (Windows Edition).
#define SystemAlloc(addr, size, prot, flags) VirtualAlloc(addr, size, flags, prot)

// Linux and macOS virtual memory primitives.
#define sysVirtualAlloc(addr, size, prot, flags) mmap(addr, size, prot, flags, -1, 0)
#define sysVirtualDealloc(addr, size, flags) munmap(addr, size)
// This also releases the address space, so it shouldn't be used to decommit virtual memory.
// Use both SystemCommit and SystemDecommit for that instead.
#define SystemDealloc(addr, size) VirtualFree(addr, size, SYS_RELEASE)

#define memReadWrite PROT_READ | PROT_WRITE
#define memProtected PROT_NONE
// Must be used with an address within a reserved address space (returned by SystemAlloc).
#define SystemCommit(addr, size) VirtualAlloc(addr, size, SYS_COMMIT, SYS_READWRITE)

// MAP_NORESERVE to avoid reserving swap space for reserved address space.
#define memReserve MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS
#define memCommit MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS
// On Windows, we decommit (only release physical memory) by calling VirtualFree with MEM_DECOMMIT.
// (according to the documentation, this is the correct way to do it)
#define SystemDecommit(addr, size) VirtualFree(addr, size, SYS_DECOMMIT)
#elif defined PlatformLinux || defined PlatformDarwin
#include <sys/mman.h>

#define SYS_READWRITE PROT_READ | PROT_WRITE
#define SYS_PROTECTED PROT_NONE

// MAP_NORESERVE to avoid reserving swap space for decommitted pages.
#define memDecommit MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS
// Not needed on Unix systems.
#define memRelease 0
// These 2 have duplicate flags, but it's easier to maintain this way.
// This makes the intent of the code using them clearer, and more portable.
#define SYS_ALLOCATE MAP_PRIVATE | MAP_ANONYMOUS
#define SYS_RESERVE MAP_PRIVATE | MAP_ANONYMOUS

#endif
// These 2 also have duplicate flags, same reason as above.
#define SYS_COMMIT MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS
#define SYS_DECOMMIT MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS

//╭──────────────────────────────────────────────────────────────────────────────────╮
//│ Additional virtual memory convenience wrappers │
//╰──────────────────────────────────────────────────────────────────────────────────╯
// Not needed on Linux and macOS, but we define it anyway for consistency.
#define SYS_RELEASE 0

// So we don't have to remember the order of the arguments and flags.
#define sysReserveAddressSpace(size) sysVirtualAlloc(nullptr, size, memProtected, memReserve)
#define sysReleaseAddressSpace(addr, size) sysVirtualDealloc(addr, size, memRelease)
// Requests more virtual memory from the operating system (Unix Edition).
#define SystemAlloc(addr, size, prot, flags) mmap(addr, size, prot, flags, -1, 0)

// Must be used with an address within a reserved address space (returned by sysReserveAddressSpace).
#define sysCommitMemory(addr, size) sysVirtualAlloc(addr, size, memReadWrite, memCommit)
// This also releases the address space, so it shouldn't be used to decommit virtual memory.
// Use both SystemCommit and SystemDecommit for that instead.
#define SystemDealloc(addr, size) munmap(addr, size)

#if defined PlatformWindows
// On Windows, we decommit (only release physical memory) by calling VirtualFree with MEM_DECOMMIT.
// (according to the documentation, this is the correct way to do it)
#define sysDecommitMemory(addr, size) sysVirtualDealloc(addr, size, memDecommit)

#elif defined PlatformLinux || defined PlatformDarwin
// Must be used with an address within a reserved address space (returned by SystemAlloc).
#define SystemCommit(addr, size) mmap(addr, size, SYS_READWRITE, SYS_COMMIT, -1, 0)

// On Linux and macOS, we decommit (only release physical memory) by calling mmap with PROT_NONE.
// This will overwrite the existing mapping, and the pages will be physically released.
#define sysDecommitMemory(addr, size) sysVirtualAlloc(addr, size, memProtected, memDecommit)

#define SystemDecommit(addr, size) mmap(addr, size, SYS_PROTECTED, SYS_DECOMMIT, -1, 0)
#endif

//╭──────────────────────────────────────────────────────────────────────────────────╮
Expand All @@ -219,16 +238,45 @@ typedef int64 intptr;

// Shared library, set visibility to export all "public" symbols.
// Should be used with `-fvisibility=hidden` compiler flag on GCC and Clang.
#if defined __GNUC__ || defined __clang__
#if (defined __GNUC__ || defined __clang__) && defined PlatformWindows
// `visibility("default")` doesn't seem to work on Windows. Fortunately both compilers
// support the below attribute as well, which doesn't require the `__declspec` MSVC syntax.
#define public __attribute__((dllexport))
#elif (defined __GNUC__ || defined __clang__)
// This should work on most Unix-like systems. It should also be used together with
// `-fvisibility=hidden` to avoid bloating the export table with unnecessary symbols.
#define public __attribute__((visibility("default")))

#else
// Not supported on other compilers, just ignore it.
#define public

#endif

// Shared library initializer and finalizer attributes.
#if defined __GNUC__ || defined __clang__
// Library initializer attribute, function will be called when the library is loaded.
#define initializer __attribute__((constructor))
// Library finalizer attribute, function will be called when the library is unloaded.
#define finalizer __attribute__((destructor))
#else
// Not supported on other compilers, just ignore it.
#define initializer
#define finalizer
#endif

// Branch prediction hints for performance optimizations.
#if defined __GNUC__ || defined __clang__
// According to the C99, C11 and C23 standards, casting to a bool here should be safe.
#define likely(expect, condition) (__builtin_expect((bool)(condition), (bool)(expect)))
#else
// Not supported on other compilers, just ignore it.
#define likely(expect, condition) (condition)
#endif

#if (defined __GNUC__ || defined __clang__) && !defined PlatformDarwin
#define alias(name) __attribute__((alias(#name), copy(name), visibility("default"), used));
#endif

// For the sake of readability, since static is used for multiple different things.
#define private static

#endif // CASPAL
#endif // CASPAL
Loading

0 comments on commit 12e9ca1

Please sign in to comment.