toyspp

toyspp.git
git clone git://git.lenczewski.org/toyspp.git
Log | Files | Refs | Submodules | README | LICENSE

commit 0e4b6d5999e3707863870f624c2bc4ea3ce41abb
Author: Mikołaj Lenczewski <mblenczewski@gmail.com>
Date:   Sun, 11 May 2025 12:16:34 +0000

Initial commit

Diffstat:
A.editorconfig | 21+++++++++++++++++++++
A.gitignore | 4++++
A.gitmodules | 3+++
ALICENSE | 18++++++++++++++++++
AREADME | 11+++++++++++
Abuild_linux.sh | 11+++++++++++
Abuild_win.sh | 13+++++++++++++
Aclean.sh | 5+++++
Adistclean.sh | 7+++++++
Amemory.cpp | 267+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amemory.hpp | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asetup_win.sh | 9+++++++++
Atest.cpp | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Athirdparty/msvc-wine | 1+
Autility.cpp | 1+
Autility.hpp | 41+++++++++++++++++++++++++++++++++++++++++
16 files changed, 642 insertions(+), 0 deletions(-)

diff --git a/.editorconfig b/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +guidelines = 80, 120, 160 + +[*.{c,h}] +indent_style = tab +indent_size = 8 + +[*.{sh}] +indent_style = tab +indent_size = 8 + +[*.{md,txt}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +bin/ +opt/ + +**/.*.swp diff --git a/.gitmodules b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thirdparty/msvc-wine"] + path = thirdparty/msvc-wine + url = https://github.com/mstorsjo/msvc-wine.git diff --git a/LICENSE b/LICENSE @@ -0,0 +1,18 @@ +The MIT-Zero License + +Copyright (c) 2025 Mikołaj Lenczewski <mikolaj@lenczewski.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README b/README @@ -0,0 +1,11 @@ +toyspp +============================================================================== +A small collection of C++ data structures, algorithms, and macros. Written in +(reasonably) portable C++20. Includes basic test programs. + +toyspp: Building +------------------------------------------------------------------------------ +To build for linux, simply run `build.sh`. To build for windows (from under +linux), run `setup_win.sh` and then run `build_win.sh`. To clean, simply run +`clean.sh`. To also clean the `opt/` directory created when setting up the +windows SDK, run `distclean.sh`. diff --git a/build_linux.sh b/build_linux.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +CC="clang++" +WARNINGS="-Wall -Wextra -Wpedantic ${WERROR:+-Werror} -Wno-format-pedantic" +FLAGS="-std=c++20 -Og -g -fuse-ld=lld" + +set -ex + +mkdir -p bin + +$CC -o bin/test test.cpp $WARNINGS $FLAGS diff --git a/build_win.sh b/build_win.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +BIN="$(realpath ./opt/bin/x64)" . ./thirdparty/msvc-wine/msvcenv-native.sh + +CC="clang++ --target=x86_64-pc-windows-msvc" +WARNINGS="-Wall -Wextra -Wpedantic ${WERROR:+-Werror} -Wno-format-pedantic" +FLAGS="-std=c++20 -Og -g -gcodeview -fuse-ld=lld -Wl,/debug:full,/pdb:" + +set -ex + +mkdir -p bin + +$CC -o bin/test.exe test.cpp $WARNINGS $FLAGS diff --git a/clean.sh b/clean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -ex + +rm -rf bin diff --git a/distclean.sh b/distclean.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -ex + +rm -rf opt + +exec clean.sh diff --git a/memory.cpp b/memory.cpp @@ -0,0 +1,267 @@ +#include "memory.hpp" + +#if defined(_WIN32) +# include <windows.h> +# include <memoryapi.h> +namespace { + uint8_t* Win32PageAlloc(size_t size, size_t alignment) + { + assert(Test::Utility::IsPowerOfTwo(alignment)); + assert(Test::Memory::MEM_ALIGN_4_KIB <= alignment); + + uintptr_t aligned_size = Test::Utility::AlignNext(size, alignment); + + DWORD prot = PAGE_READWRITE; + DWORD flags = MEM_RESERVE | MEM_COMMIT; + LPVOID ptr = VirtualAlloc(nullptr, aligned_size, flags, prot); + + return (uint8_t*) ptr; + } + + Test::Memory::MirrorAllocResult Win32MirrorAlloc(size_t size, size_t mirrors) + { + assert(Test::Utility::IsAlignedTo(size, Test::Memory::MEM_ALIGN_4_KIB)); + + Test::Memory::MirrorAllocResult result{}; + + (void) size; + (void) mirrors; + + return result; + } +} + +#elif defined(__linux__) +# define _GNU_SOURCE 1 +# include <sys/mman.h> +# include <unistd.h> +namespace { + uint8_t* LinuxPageAlloc(size_t size, size_t alignment) + { + assert(Test::Utility::IsPowerOfTwo(alignment)); + assert(Test::Memory::MEM_ALIGN_4_KIB <= alignment); + + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + + uintptr_t aligned_size = Test::Utility::AlignNext(size, alignment); + void *unaligned_ptr = mmap(nullptr, aligned_size, PROT_NONE, flags, -1, 0); + if (unaligned_ptr == MAP_FAILED) + return nullptr; + + uintptr_t aligned_base = Test::Utility::AlignNext((uintptr_t) unaligned_ptr, alignment); + void *aligned_ptr = mmap((void *) aligned_base, size, prot, flags | MAP_FIXED, -1, 0); + assert(aligned_ptr != MAP_FAILED); + + if (Test::Utility::IsAlignedTo(aligned_base, Test::Memory::MEM_ALIGN_2_MIB)) + madvise(aligned_ptr, size, MADV_HUGEPAGE); + + munmap(unaligned_ptr, aligned_base - (uintptr_t) aligned_ptr); + + return (uint8_t*) aligned_ptr; + } + + Test::Memory::MirrorAllocResult LinuxMirrorAlloc(size_t size, size_t mirrors) + { + assert(Test::Utility::IsAlignedTo(size, Test::Memory::MEM_ALIGN_4_KIB)); + + Test::Memory::MirrorAllocResult result{}; + + result.len = size; + result.cap = size * mirrors; + + int prot = PROT_READ | PROT_WRITE; + int buffer_flags = MAP_PRIVATE | MAP_ANONYMOUS; + int mirror_flags = MAP_SHARED | MAP_FIXED; + + result.memfd = memfd_create("linux mirror alloc", MFD_CLOEXEC); + if (result.memfd < 0) { + goto error; + } + + ftruncate(result.memfd, result.cap); + + result.ptr = (uint8_t*) mmap(nullptr, result.cap, PROT_NONE, buffer_flags, -1, 0); + if (result.ptr == MAP_FAILED) { + close(result.memfd); + goto error; + } + + for (size_t i = 0; i < mirrors; i++) { + uint8_t* base = result.ptr + (result.len * i); + void *mirror = mmap(base, result.len, prot, mirror_flags, result.memfd, 0); + if (mirror == MAP_FAILED) { + munmap(result.ptr, result.cap); + close(result.memfd); + goto error; + } + } + + return result; + +error: + result.memfd = -1; + result.ptr = nullptr; + result.len = result.cap = 0; + + return result; + } +} + +#else +# error "Unknown platform, must be one of: windows linux" +#endif + +namespace Test::Memory { + uint8_t *GenAlloc(size_t size, size_t alignment) { + assert(Utility::IsPowerOfTwo(alignment)); + + size_t upper_bound = size + (alignment - 1); + + uintptr_t ptr = (uintptr_t) malloc(upper_bound); + uintptr_t res = Utility::AlignNext(ptr, alignment); + + return (uint8_t *) res; + } + + uint8_t *PageAlloc(size_t size, size_t alignment) { + if (alignment < MEM_ALIGN_4_KIB) + alignment = MEM_ALIGN_4_KIB; + +#if defined(_WIN32) + return Win32PageAlloc(size, alignment); +#elif defined(__linux__) + return LinuxPageAlloc(size, alignment); +#else + return nullptr; +#endif + } + + MirrorAllocResult MirrorAlloc(size_t size, size_t mirrors) { +#if defined(_WIN32) + return Win32MirrorAlloc(size, mirrors); +#elif defined(__linux__) + return LinuxMirrorAlloc(size, mirrors); +#else + return nullptr; +#endif + } + + void LinearAlloc::Init(uint8_t *buf, size_t cap) + { + ptr = buf; + this->cap = cap; + len = 0; + } + + void LinearAlloc::Reset() + { + len = 0; + } + + uint8_t *LinearAlloc::AllocRaw(size_t size, size_t alignment) + { + assert(size); + assert(alignment); + assert(Utility::IsPowerOfTwo(alignment)); + + uintptr_t currentPtr = (uintptr_t) ptr + len; + uintptr_t endPtr = (uintptr_t) ptr + cap; + + uintptr_t alignedPtr = Utility::AlignNext(currentPtr, alignment); + if (endPtr < alignedPtr + size) + return nullptr; + + len = (alignedPtr + size) - (uintptr_t) ptr; + + return (uint8_t *) alignedPtr; + } + + void BlockAlloc::Init(uint8_t *buf, size_t cap, size_t block_size, size_t block_alignment) + { + assert(Utility::IsPowerOfTwo(block_alignment)); + assert(Utility::IsAlignedTo((uintptr_t) buf, block_alignment)); + + assert(sizeof(FreeListNode) <= block_size); + assert(alignof(FreeListNode) <= block_alignment); + + ptr = buf; + this->cap = cap; + this->block_size = block_size; + this->block_alignment = block_alignment; + + Reset(); + } + + void BlockAlloc::Reset() + { + freelist = nullptr; + + for (size_t i = 0; i < cap / block_size; i++) { + uint8_t *block = ptr + (block_size * i); + + FreeListNode *node = (FreeListNode *) block; + node->next = freelist; + freelist = node; + } + } + + uint8_t *BlockAlloc::AllocRaw() + { + if (freelist == nullptr) + return nullptr; + + FreeListNode *node = freelist; + freelist = node->next; + + return (uint8_t *) node; + } + + void BlockAlloc::Free(uint8_t *ptr) + { + FreeListNode *node = (FreeListNode *) ptr; + node->next = freelist; + freelist = node; + } + + void StackAlloc::Init(uint8_t *buf, size_t cap) + { + ptr = buf; + this->cap = cap; + len = 0; + } + + void StackAlloc::Reset() + { + len = 0; + } + + StackAlloc::Result StackAlloc::AllocRaw(size_t size, size_t alignment) + { + assert(size); + assert(alignment); + assert(Utility::IsPowerOfTwo(alignment)); + + uintptr_t currentPtr = (uintptr_t) ptr + len; + uintptr_t endPtr = (uintptr_t) ptr + cap; + + uintptr_t alignedPtr = Utility::AlignNext(currentPtr, alignment); + if (endPtr < alignedPtr + size) { + Result result{}; + return result; + } + + len = (alignedPtr + size) - (uintptr_t) ptr; + + Result result{}; + result.ptr = (uint8_t *) alignedPtr; + result.len = size; + result.saveptr = (uint8_t *) currentPtr; + return result; + } + + void StackAlloc::Free(Result region) + { + len = region.saveptr - ptr; + } +} diff --git a/memory.hpp b/memory.hpp @@ -0,0 +1,111 @@ +#ifndef MEMORY_HPP +#define MEMORY_HPP + +#include <cstdint> +#include <cstdlib> + +#include <new> +#include <utility> + +#include "utility.hpp" + +namespace Test::Memory { + constexpr size_t MEM_ALIGN_4_KIB = 4 * 1024; + constexpr size_t MEM_ALIGN_2_MIB = 2 * 1024 * 1024; + constexpr size_t MEM_ALIGN_1_GIB = 1 * 1024 * 1024 * 1024; + + uint8_t *GenAlloc(size_t size, size_t alignment); + uint8_t *PageAlloc(size_t size, size_t alignment); + + struct MirrorAllocResult { + int memfd; + uint8_t *ptr; + size_t len, cap; + }; + + MirrorAllocResult MirrorAlloc(size_t size, size_t mirrors); + + struct LinearAlloc { + void Init(uint8_t *buf, size_t cap); + + void Reset(); + + uint8_t *AllocRaw(size_t size, size_t alignment); + + template<typename T, size_t Count = 1, size_t Align = alignof(T)> + T* Alloc(auto&&... args) { + uint8_t *buffer = AllocRaw(sizeof(T) * Count, Align); + if (buffer == nullptr) + return nullptr; + + return new (buffer) T(std::forward<decltype(args)>(args)...); + } + + private: + uint8_t *ptr; + size_t cap, len; + }; + + struct BlockAlloc { + void Init(uint8_t *buf, size_t cap, size_t block_size, size_t block_alignment); + + template <typename T> + void Init(T *buf, size_t cap) { + Init((uint8_t *) buf, cap * sizeof(T), sizeof(T), alignof(T)); + } + + void Reset(); + + uint8_t *AllocRaw(); + + template <typename T> + T *Alloc(auto&&... args) { + assert(sizeof(T) <= block_size); + assert(alignof(T) <= block_alignment); + + uint8_t *block = AllocRaw(); + if (block == nullptr) + return nullptr; + + return new (block) T(std::forward<decltype(args)>(args)...); + } + + void Free(uint8_t *ptr); + + private: + uint8_t *ptr; + size_t cap, block_size, block_alignment; + + struct FreeListNode { + FreeListNode *next; + }; + + FreeListNode *freelist; + }; + + struct StackAlloc { + void Init(uint8_t *buf, size_t cap); + + void Reset(); + + struct Result { + uint8_t *ptr; + size_t len; + + private: + uint8_t *saveptr; + + friend StackAlloc; + }; + + Result AllocRaw(size_t size, size_t alignment); + + void Free(Result region); + + private: + uint8_t *ptr; + size_t cap, len; + }; +} + +#endif /* MEMORY_HPP */ diff --git a/setup_win.sh b/setup_win.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -ex + +git submodule update --init --recursive + +mkdir -p opt +python3 ./thirdparty/msvc-wine/vsdownload.py --dest opt +./thirdparty/msvc-wine/install.sh opt diff --git a/test.cpp b/test.cpp @@ -0,0 +1,119 @@ +#include "utility.hpp" +#include "memory.hpp" + +#include <cstdio> + +struct alignas(8) mystruct { + int a, b, c, d; +}; + +int +main(void) +{ + uint8_t *arr0 = Test::Memory::GenAlloc(sizeof(struct mystruct), alignof(struct mystruct)); + assert(arr0); + + struct mystruct *mystruct0 = (struct mystruct *) arr0; + mystruct0->a = 42; + + printf("arr0: %p, mystruct0.a: %d\n", arr0, mystruct0->a); + + uint8_t *arr1 = Test::Memory::PageAlloc(sizeof(struct mystruct), Test::Memory::MEM_ALIGN_2_MIB); + assert(arr1); + + struct mystruct *mystruct1 = (struct mystruct *) arr1; + mystruct1->a = 69; + + printf("arr1: %p, mystruct1.a: %d\n", + arr1, mystruct1->a); + + Test::Memory::MirrorAllocResult arr2 = Test::Memory::MirrorAlloc(Test::Memory::MEM_ALIGN_4_KIB, 2); + assert(arr2.ptr); + + struct mystruct *mystruct2a = (struct mystruct *) arr2.ptr; + struct mystruct *mystruct2b = (struct mystruct *) (arr2.ptr + arr2.len); + + mystruct2a->a = 100; + + printf("arr2: %p, mystruct2a.a: %d, mystruct2b.a: %d\n", + arr2.ptr, mystruct2a->a, mystruct2b->a); + + { + uint8_t buf[1024]; + Test::Memory::LinearAlloc arena{}; + arena.Init(buf, sizeof buf); + + arena.Reset(); + struct mystruct *mystruct = arena.Alloc<struct mystruct>(1, 2, 3, 4); + assert(mystruct); + + printf("LinearAlloc: mystruct: %p, %d, %d, %d, %d\n", + mystruct, mystruct->a, mystruct->b, mystruct->c, mystruct->d); + + struct mystruct *mystruct2 = arena.Alloc<struct mystruct>(0, 0, 0, 0); + assert(mystruct2); + assert(mystruct2 != mystruct); + + printf("LinearAlloc: mystruct2: %p, %d, %d, %d, %d\n", + mystruct2, mystruct2->a, mystruct2->b, mystruct2->c, mystruct2->d); + } + + { + uint8_t buf[1024]; + Test::Memory::BlockAlloc blocks{}; + blocks.Init(buf, sizeof buf, sizeof(mystruct), alignof(mystruct)); + + struct mystruct *mystruct = blocks.Alloc<struct mystruct>(1, 2, 3, 4); + assert(mystruct); + + printf("BlockAlloc: mystruct: %p, %d, %d, %d, %d\n", + mystruct, mystruct->a, mystruct->b, mystruct->c, mystruct->d); + + struct mystruct *mystruct2 = blocks.Alloc<struct mystruct>(0, 0, 0, 0); + assert(mystruct2); + assert(mystruct2 != mystruct); + + printf("BlockAlloc: mystruct2: %p, %d, %d, %d, %d\n", + mystruct2, mystruct2->a, mystruct2->b, mystruct2->c, mystruct2->d); + } + + { + uint8_t buf[1024]; + Test::Memory::StackAlloc stack{}; + stack.Init(buf, sizeof buf); + + Test::Memory::StackAlloc::Result region0 = stack.AllocRaw(256, 16); + assert(region0.ptr); + + printf("StackAlloc: region0: %p\n", region0.ptr); + + { + Test::Memory::StackAlloc::Result region1 = stack.AllocRaw(64, 64); + assert(region1.ptr); + + printf("StackAlloc: region1: %p\n", region1.ptr); + + stack.Free(region1); + } + + { + Test::Memory::StackAlloc::Result region2 = stack.AllocRaw(64, 64); + assert(region2.ptr); + + printf("StackAlloc: region2: %p\n", region2.ptr); + + } + + stack.Free(region0); + + Test::Memory::StackAlloc::Result region3 = stack.AllocRaw(64, 64); + assert(region3.ptr); + + printf("StackAlloc: region3: %p\n", region3.ptr); + } + + return 0; +} + +#include "utility.cpp" +#include "memory.cpp" diff --git a/thirdparty/msvc-wine b/thirdparty/msvc-wine @@ -0,0 +1 @@ +Subproject commit 44dc13b5e62ecc2373fbe7e4727a525001f403f4 diff --git a/utility.cpp b/utility.cpp @@ -0,0 +1 @@ +#include "utility.hpp" diff --git a/utility.hpp b/utility.hpp @@ -0,0 +1,41 @@ +#ifndef UTILITY_HPP +#define UTILITY_HPP + +#include <cassert> +#include <cstddef> +#include <cstdint> + +namespace Test::Utility { + constexpr bool IsPowerOfTwo(uintptr_t value) { + return (value & (value - 1)) == 0; + } + + constexpr bool IsAlignedTo(uintptr_t value, size_t alignment) { + assert(IsPowerOfTwo(alignment)); + + uintptr_t mask = (alignment - 1); + uintptr_t res = value & mask; + + return res == 0; + } + + constexpr uintptr_t AlignPrev(uintptr_t value, size_t alignment) { + assert(IsPowerOfTwo(alignment)); + + uintptr_t mask = ~(alignment - 1); + uintptr_t res = value & mask; + + return res; + } + + constexpr uintptr_t AlignNext(uintptr_t value, size_t alignment) { + assert(IsPowerOfTwo(alignment)); + + uintptr_t upper_bound = value + (alignment - 1); + uintptr_t res = AlignPrev(upper_bound, alignment); + + return res; + } +} + +#endif /* UTILITY_HPP */