├── .github └── workflows │ ├── dub_test_build.yml │ └── run_tests.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── dscanner.ini ├── dub.sdl ├── modules ├── hookset-libc │ ├── dub.sdl │ └── source │ │ ├── atomic │ │ ├── dummy.d │ │ ├── ldc.d │ │ └── package.d │ │ └── hookset.d └── hookset-wasm │ ├── LICENSE-walloc │ ├── dub.sdl │ └── source │ ├── hookset.d │ └── walloc.d ├── numem.png ├── source └── numem │ ├── casting.d │ ├── compiler.d │ ├── core │ ├── atomic.d │ ├── attributes.d │ ├── exception.d │ ├── hooks.d │ ├── lifetime.d │ ├── memory.d │ ├── meta.d │ ├── package.d │ ├── traits.d │ └── types.d │ ├── heap.d │ ├── lifetime.d │ ├── object.d │ ├── package.d │ └── volatile.d └── tests └── ut ├── casting.d ├── core ├── atomic.d ├── exception.d └── memory.d ├── lifetime.d └── object.d /.github/workflows/dub_test_build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | dep_config: 5 | description: 'Config to build' 6 | required: true 7 | type: string 8 | dep_version: 9 | description: 'Pinned dependency version' 10 | required: true 11 | type: string 12 | repo: 13 | description: 'The repository containing the code to build' 14 | required: true 15 | type: string 16 | repo_tag: 17 | description: 'The tag to attempt to build' 18 | required: true 19 | type: string 20 | may_fail: 21 | description: 'Whether it may fail' 22 | default: false 23 | required: false 24 | type: boolean 25 | 26 | name: 'Dub dependant build-check' 27 | run-name: 'Build-check ${{ inputs.repo }} ${{ inputs.repo_tag }}' 28 | 29 | jobs: 30 | try_build: 31 | name: '${{ matrix.dc }} on ${{ matrix.os }}' 32 | continue-on-error: ${{ inputs.may_fail }} 33 | strategy: 34 | fail-fast: true 35 | matrix: 36 | os: [macOS-latest, ubuntu-latest, windows-latest] 37 | dc: [ldc, ldc-beta, dmd, dmd-beta] 38 | 39 | runs-on: ${{ matrix.os }} 40 | steps: 41 | - name: 'Install ${{ matrix.dc }}' 42 | uses: dlang-community/setup-dlang@v2 43 | with: 44 | compiler: ${{ matrix.dc }} 45 | 46 | - name: 'Checkout ${{ github.repository }}' 47 | uses: actions/checkout@v4 48 | with: 49 | path: '${{ github.repository }}' 50 | 51 | - name: 'Pin local package to ${{ inputs.dep_version }}' 52 | run: | 53 | dub add-local ${{ github.repository }} "${{ inputs.dep_version }}" 54 | 55 | - name: 'Checkout ${{ inputs.repo }}' 56 | uses: actions/checkout@v4 57 | with: 58 | repository: '${{ inputs.repo }}' 59 | ref: '${{ inputs.repo_tag }}' 60 | path: '${{ inputs.repo }}' 61 | 62 | - name: 'Attempt build with specified config' 63 | working-directory: '${{ inputs.repo }}' 64 | if: ${{ inputs.dep_config != 'unittest' }} 65 | run: | 66 | dub build --config=${{ inputs.dep_config }} --build=debug 67 | dub build --config=${{ inputs.dep_config }} --build=release 68 | 69 | - name: 'Attempt unittest' 70 | working-directory: '${{ inputs.repo }}' 71 | if: ${{ inputs.dep_config == 'unittest' }} 72 | run: | 73 | dub test --build=debug 74 | dub test --build=release -------------------------------------------------------------------------------- /.github/workflows/run_tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | name: Run Tests 6 | 7 | on: 8 | push: 9 | branches: [ "main" ] 10 | pull_request: 11 | types: [ "opened", "synchronize", "edited" ] 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | test: 18 | name: 'Build and Test Library' 19 | strategy: 20 | matrix: 21 | os: [macOS-latest, ubuntu-latest, windows-latest] 22 | dc: [ldc-beta, ldc-1.30.0, dmd] 23 | 24 | runs-on: ${{ matrix.os }} 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - name: Install compiler 29 | uses: dlang-community/setup-dlang@v2 30 | with: 31 | compiler: ${{ matrix.dc }} 32 | 33 | - name: 'Build with ${{ matrix.dc }} (on ${{ matrix.os }})' 34 | run: | 35 | # Build and run tests, as defined by `unittest` configuration 36 | # In this mode, `mainSourceFile` is excluded and `version (unittest)` are included 37 | # See https://dub.pm/package-format-json.html#configurations 38 | dub test --config=unittest 39 | 40 | # Ditto, in release mode. 41 | # Sometimes D packages break in release mode, so this is important to test. 42 | dub test --config=unittest --build=release 43 | 44 | sanity_check: 45 | name: 'Run a sanity check' 46 | strategy: 47 | matrix: 48 | dc: [ldc-beta, ldc-1.30.0, dmd] 49 | 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v4 53 | 54 | - name: Install compiler 55 | uses: dlang-community/setup-dlang@v2 56 | with: 57 | compiler: ${{ matrix.dc }} 58 | 59 | - name: 'Run tests 50 times (${{ matrix.dc }})' 60 | run: | 61 | for i in `seq 1 50` 62 | do 63 | dub test --config=unittest 64 | dub test --config=unittest --build=release 65 | done 66 | 67 | test_build: 68 | name: 'Do buildchecks' 69 | strategy: 70 | matrix: 71 | to_build: 72 | - repo: Inochi2D/hairetsu 73 | tag: main 74 | config: unittest 75 | version: "99.0.0" 76 | may_fail: false 77 | - repo: Inochi2D/nulib 78 | tag: main 79 | config: unittest 80 | version: "99.0.0" 81 | may_fail: false 82 | 83 | needs: [test, sanity_check] 84 | uses: ./.github/workflows/dub_test_build.yml 85 | with: 86 | repo: ${{ matrix.to_build.repo }} 87 | repo_tag: ${{ matrix.to_build.tag }} 88 | dep_config: ${{ matrix.to_build.config }} 89 | dep_version: ${{ matrix.to_build.version }} 90 | may_fail: ${{ matrix.to_build.may_fail }} 91 | 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | __dummy_docs/ 6 | /numem 7 | numem.so 8 | numem.dylib 9 | numem.dll 10 | numem.a 11 | numem.lib 12 | numem-test-* 13 | *.exe 14 | *.o 15 | *.obj 16 | *.lst 17 | 18 | dub.selections.json 19 | out/ 20 | source/app.d 21 | 22 | .vscode 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Numem Contribution Guidelines 2 | 3 | Thank you for considering to contribute to numem! 4 | 5 | Numem, unlike other packages under the Inochi2D umbrella is far more strict in what should 6 | and should not be added to the codebase. This is to ensure API stability for consumers 7 | of the numem API. 8 | 9 | ## General Rules 10 | 11 | 1. Do not add any external dependencies. 12 | 2. Only include essential features for memory managment. 13 | 3. New features should have associated unittests under the tests directory. 14 | 4. Be sure to mark functions with attributes appropriately. 15 | * Fundamentally unsafe code should not be marked trusted! 16 | 5. Follow the code style of the rest of the project. 17 | 18 |   19 |   20 |   21 | 22 | # Creating Issues 23 | 24 | ## Reporting bugs 25 | Reported bugs should have clear steps for reproduction, bonus if you provide a minimal dustmite example. 26 | 27 | ## Requesting features 28 | Most features that may be requested will likely be rejected; you may want to submit them to nucore instead. 29 | numem is meant to be minimal in size to decrease the surface of how much may break. 30 | 31 |   32 |   33 |   34 | 35 | # Creating pull-requests 36 | 37 | If you'd like to make a feature pull-request please bring up a suggestion via Issues first, 38 | Bugfixes will only be merged if all of the tests pass. If a pull-request is a work-in-progress, 39 | please mark it as a Draft. 40 | 41 |   42 |   43 |   44 | 45 | # General style guide. 46 | 47 | Lower level primitives (that optionally are exported to C) should be named using `snake_case`, `SCREAMING_SNAKE_CASE` in the case of public constants and single-value enums. 48 | Higher level functionality should follow the phobos style guidelines. 49 | 50 | Symbols should be documented using DDOC syntax. Besides sections in the official ddoc documentation; we also use a couple of extra ones when needed: 51 | 52 | | Section | Meaning | 53 | | -------------- | ------------------------------------------------------------------------------------------------------------------------- | 54 | | `Memorysafety` | Describes how memory safety the feature is in question; safety is determined in terms of how safe it is for the end user. | 55 | | `Threadsafety` | Describes how safe it is to use a function in a multi-threaded context. | 56 | | `Notes` | Any additional notes the user of the symbol should be aware of. | 57 | 58 | Please appropriately document new functions added. 59 | 60 | **NOTE**: Some functions carried over from older versions of numem may not have gotten their documentation updated yet, they are a temporary exception 61 | and will be fixed with time. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023-2025, Kitsunebi Games 2 | Copyright (c) 2023-2025, Inochi2D Project 3 | 4 | Boost Software License - Version 1.0 - August 17th, 2003 5 | 6 | Permission is hereby granted, free of charge, to any person or organization 7 | obtaining a copy of the software and accompanying documentation covered by 8 | this license (the "Software") to use, reproduce, display, distribute, 9 | execute, and transmit the Software, and to prepare derivative works of the 10 | Software, and to permit third-parties to whom the Software is furnished to 11 | do so, all subject to the following: 12 | 13 | The copyright notices in the Software and this entire statement, including 14 | the above license grant, this restriction and the following disclaimer, 15 | must be included in all copies of the Software, in whole or in part, and 16 | all derivative works of the Software, unless such copies or derivative 17 | works are solely in the form of machine-executable object code generated by 18 | a source language processor. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | NuMem 3 |

4 | 5 | [![Unit Test Status](https://github.com/Inochi2D/numem/actions/workflows/pushes.yml/badge.svg)](https://github.com/Inochi2D/numem/actions/workflows/pushes.yml) 6 | 7 | Nu:Mem is a package for D which implements various nogc memory managment tools, allowing classes, strings, and more to be handled safely in nogc mode. 8 | This library is still a work in progress, but is intended to be used within Inochi2D's nogc rewrite to allow the library to have good ergonomics, 9 | while allowing more seamless integration with other programming languages. 10 | 11 |   12 |   13 |   14 | 15 | # Configuration 16 | 17 | Numem provides a couple of version flags for configuring some base features of numem. 18 | Packages which intend to extend numem should appropriately implement these flags to handle 19 | parts of numem being non-functional. 20 | 21 | | Flag | Description | 22 | | :----------------- | ------------------------------------------------------------------------------------ | 23 | | `NUMEM_NO_ATOMICS` | Disables atomic operations, all atomic operations are replaced with dummy functions. | 24 | 25 |   26 |   27 |   28 | 29 | # Using numem 30 | Numem allows you to instantiate classes without the GC, it's highly recommended that you mark all functions in classes as @nogc to avoid GC conflicts. 31 | 32 | Using `nogc_new` you can instantiate types on the heap, and `nogc_delete` can destruct and free types. 33 | 34 | ## Hooksets 35 | 36 | Numem works on a concept of hooksets, all core functionality of numem is built on a small series of internal hook functions. 37 | without said functions numem will not link, as a recommendation you can start with `numem:hookset-libc` to get going, 38 | if you have special requirements for your target platform you can implement your own hookset by implementing the functions 39 | in `numem.core.hooks` in a seperate package and adding it as a dependency. 40 | 41 | ## Using Classes 42 | 43 | ```d 44 | import numem; 45 | 46 | class MyClass { 47 | @nogc: 48 | void doSomething() { 49 | import core.stdc.stdio : printf; 50 | printf("Hello, world!\n"); 51 | } 52 | } 53 | 54 | void main() { 55 | MyClass klass = nogc_new!MyClass(); 56 | klass.doSomething(); 57 | 58 | nogc_delete(klass); 59 | } 60 | ``` 61 | 62 | All features of classes are available without the GC, such as subclassing and interfaces. 63 | 64 | It is recommended that nogc classes extend `NuObject`. 65 | ```d 66 | import numem; 67 | 68 | class MyClass : NuObject { 69 | @nogc: 70 | // NuObject ensures all the derived functions 71 | // are nogc; as such you can easily override functions 72 | // like opEquals. 73 | } 74 | ``` 75 | 76 | ## Reference Counted Classes 77 | 78 | Numem features an extra base class, derived from `NuObject`, called `NuRefCounted`. 79 | This class implements manual reference counting using the `retain` and `release` functions. 80 | 81 | ```d 82 | import numem; 83 | import std.stdio; 84 | 85 | class MyRCClass : NuRefCounted { 86 | @nogc: 87 | private: 88 | int secret; 89 | 90 | public: 91 | ~this() { 92 | import core.stdc.stdio : printf; 93 | printf("Deleted!\n"); 94 | } 95 | 96 | int getSecret() { 97 | return secret; 98 | } 99 | } 100 | 101 | void main() { 102 | MyRCClass rcclass = nogc_new!MyRCClass(); 103 | 104 | // Add one refcount. 105 | rcclass.retain(); 106 | writeln(rcclass.retain().getSecret()); 107 | 108 | // Repeatedly release a refcount until rcclass is freed. 109 | while(rcclass) 110 | rcclass = rcclass.release(); 111 | 112 | assert(rcclass is null); 113 | } 114 | ``` 115 | 116 | ## Using slices 117 | 118 | Numem allows creating slice buffers, however these buffers are less safe than higher level 119 | alternatives available in [nulib](https://github.com/Inochi2D/nulib). 120 | 121 | ```d 122 | float[] myFloatSlice; 123 | myFloatSlice.nu_resize(42); 124 | foreach(i; 0..myFloatSlice.length) 125 | myFloatSlice[i] = 0.0; 126 | 127 | // Slices MUST be freed by resizing the slice to 0, 128 | // other functions will either fail or cause memory corruption. 129 | // as slices are using the aligned allocation functions. 130 | myFloatSlice.nu_resize(0); 131 | ``` -------------------------------------------------------------------------------- /dscanner.ini: -------------------------------------------------------------------------------- 1 | ; Numem does in some instances use function, variable and class names that do not conform to phobos 2 | ; We don't want linter warnings for those. 3 | [analysis.config.StaticAnalysisConfig] 4 | style_check="disabled" 5 | object_const_check="disabled" -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "numem" 2 | description "Memory managment utilities for D" 3 | copyright "Copyright © 2023, Inochi2D Project" 4 | authors "Kitsunebi Games" "Luna" "Inochi2D Project" 5 | license "BSL-1.0" 6 | targetPath "out/" 7 | 8 | subPackage "modules/hookset-libc" 9 | subPackage "modules/hookset-wasm" 10 | 11 | // Needed for ddox. 12 | dflags "-oq" platform="ldc" 13 | 14 | configuration "static" { 15 | targetType "staticLibrary" 16 | } 17 | 18 | configuration "dynamic" { 19 | targetType "dynamicLibrary" 20 | } 21 | 22 | configuration "unittest" { 23 | targetType "autodetect" 24 | 25 | dependency "numem:hookset-libc" version="*" 26 | 27 | dependency "silly" version="~>1.1.1" 28 | sourcePaths "source/" "tests/" 29 | importPaths "source/" "tests/" 30 | } -------------------------------------------------------------------------------- /modules/hookset-libc/dub.sdl: -------------------------------------------------------------------------------- 1 | name "hookset-libc" 2 | description "libc based hookset for numem." 3 | authors "Kitsunebi Games" "Luna" "Inochi2D Project" 4 | license "BSL-1.0" 5 | targetPath "out/" 6 | 7 | configuration "emscripten" { 8 | platforms "emscripten" 9 | targetType "staticLibrary" 10 | dflags "-mattr=+bulk-memory,+multivalue,+reference-types" 11 | } 12 | 13 | configuration "others" { 14 | targetType "staticLibrary" 15 | } -------------------------------------------------------------------------------- /modules/hookset-libc/source/atomic/dummy.d: -------------------------------------------------------------------------------- 1 | module atomic.dummy; 2 | import atomic; 3 | 4 | static if (ATOMIC_USE_DUMMY): 5 | 6 | /** 7 | Gets the status of atomics support in the current 8 | numem configuration. 9 | 10 | Notes: 11 | If atomics are not supported, these functions will 12 | act as non-atomic alternatives. 13 | 14 | Returns: 15 | Whether atomics are supported by the loaded 16 | hookset. 17 | */ 18 | export 19 | extern(C) 20 | bool nu_atomic_supported() @nogc nothrow { 21 | return false; 22 | } 23 | 24 | /** 25 | Inserts a memory acquire barrier. 26 | */ 27 | export 28 | extern(C) 29 | void nu_atomic_barrier_acquire() @nogc nothrow { 30 | return; 31 | } 32 | 33 | /** 34 | Inserts a memory release barrier. 35 | */ 36 | export 37 | extern(C) 38 | void nu_atomic_barrier_release() @nogc nothrow { 39 | return; 40 | } 41 | 42 | /** 43 | Loads a 32 bit value atomically. 44 | */ 45 | export 46 | extern(C) 47 | inout(uint) nu_atomic_load_32(ref inout(uint) src) @nogc nothrow { 48 | return src; 49 | } 50 | 51 | /** 52 | Stores a 32 bit value atomically. 53 | */ 54 | export 55 | extern(C) 56 | void nu_atomic_store_32(ref uint dst, uint value) @nogc nothrow { 57 | dst = value; 58 | } 59 | 60 | /** 61 | Adds a 32 bit value atomically. 62 | */ 63 | export 64 | extern(C) 65 | extern uint nu_atomic_add_32(ref uint dst, uint value) @nogc nothrow { 66 | uint oldval = dst; 67 | dst += value; 68 | return oldval; 69 | } 70 | 71 | /** 72 | Subtracts a 32 bit value atomically. 73 | */ 74 | export 75 | extern(C) 76 | extern uint nu_atomic_sub_32(ref uint dst, uint value) @nogc nothrow { 77 | uint oldval = dst; 78 | dst -= value; 79 | return oldval; 80 | } 81 | 82 | /** 83 | Loads a pointer value atomically. 84 | */ 85 | export 86 | extern(C) 87 | extern inout(void)* nu_atomic_load_ptr(inout(void)** src) @nogc nothrow { 88 | return *src; 89 | } 90 | 91 | /** 92 | Stores a pointer value atomically. 93 | */ 94 | export 95 | extern(C) 96 | extern void nu_atomic_store_ptr(void** dst, void* value) @nogc nothrow { 97 | *dst = value; 98 | } 99 | 100 | /** 101 | Compares variable at $(D dst) and swaps it if it contains $(D oldvalue). 102 | */ 103 | export 104 | extern(C) 105 | extern bool nu_atomic_cmpxhg_ptr(void** dst, void* oldvalue, void* value) @nogc nothrow { 106 | if (*dst is oldvalue) { 107 | *dst = value; 108 | return true; 109 | } 110 | 111 | return false; 112 | } -------------------------------------------------------------------------------- /modules/hookset-libc/source/atomic/ldc.d: -------------------------------------------------------------------------------- 1 | /** 2 | LDC atomic intrinsics. 3 | 4 | Copyright: 5 | Copyright © 2023-2025, Kitsunebi Games 6 | Copyright © 2023-2025, Inochi2D Project 7 | 8 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Authors: Luna Nielsen 10 | */ 11 | module atomic.ldc; 12 | version(LDC): 13 | 14 | import ldc.intrinsics; 15 | 16 | // 17 | // HOOK IMPLEMENTATION. 18 | // 19 | 20 | extern(C) 21 | export 22 | bool nu_atomic_supported() @nogc nothrow { 23 | return true; 24 | } 25 | 26 | /** 27 | Inserts a memory acquire barrier. 28 | */ 29 | extern(C) 30 | export 31 | void nu_atomic_barrier_acquire() @nogc nothrow { 32 | llvm_memory_fence(AtomicOrdering.Acquire, SynchronizationScope.CrossThread); 33 | } 34 | 35 | /** 36 | Inserts a memory release barrier. 37 | */ 38 | extern(C) 39 | export 40 | void nu_atomic_barrier_release() @nogc nothrow { 41 | llvm_memory_fence(AtomicOrdering.Release, SynchronizationScope.CrossThread); 42 | } 43 | 44 | /** 45 | Loads a 32 bit value atomically. 46 | */ 47 | extern(C) 48 | export 49 | inout(uint) nu_atomic_load_32(ref inout(uint) src) @nogc nothrow { 50 | return llvm_atomic_load!(inout(uint))(cast(shared(inout(uint)*))&src); 51 | } 52 | 53 | /** 54 | Stores a 32 bit value atomically. 55 | */ 56 | extern(C) 57 | export 58 | void nu_atomic_store_32(ref uint dst, uint value) @nogc nothrow { 59 | llvm_atomic_store!(uint)(value, cast(shared(uint*))&dst); 60 | } 61 | 62 | /** 63 | Adds a 32 bit value atomically. 64 | */ 65 | extern(C) 66 | export 67 | uint nu_atomic_add_32(ref uint dst, uint value) @nogc nothrow { 68 | return llvm_atomic_rmw_add!(uint)(cast(shared(uint*))&dst, value); 69 | } 70 | 71 | /** 72 | Subtracts a 32 bit value atomically. 73 | */ 74 | extern(C) 75 | export 76 | uint nu_atomic_sub_32(ref uint dst, uint value) @nogc nothrow { 77 | return llvm_atomic_rmw_sub!(uint)(cast(shared(uint*))&dst, value); 78 | } 79 | 80 | /** 81 | Loads a pointer value atomically. 82 | */ 83 | extern(C) 84 | export 85 | inout(void)* nu_atomic_load_ptr(inout(void)** src) @nogc nothrow { 86 | return llvm_atomic_load!(inout(void)*)(cast(shared(inout(void)**))src); 87 | } 88 | 89 | /** 90 | Stores a pointer value atomically. 91 | */ 92 | extern(C) 93 | export 94 | void nu_atomic_store_ptr(void** dst, void* value) @nogc nothrow { 95 | llvm_atomic_store!(void*)(value, cast(shared(void**))dst); 96 | } 97 | 98 | /** 99 | Compares variable at $(D dst) and swaps it if it contains $(D oldvalue). 100 | */ 101 | extern(C) 102 | export 103 | bool nu_atomic_cmpxhg_ptr(void** dst, void* oldvalue, void* value) @nogc nothrow { 104 | return llvm_atomic_cmp_xchg!(void*)(cast(shared(void**))dst, oldvalue, value).exchanged; 105 | } -------------------------------------------------------------------------------- /modules/hookset-libc/source/atomic/package.d: -------------------------------------------------------------------------------- 1 | module atomic; 2 | 3 | /* 4 | This more or less decides which method to use 5 | for atomics. 6 | */ 7 | version(LDC) enum ATOMIC_USE_DUMMY = false; 8 | else enum ATOMIC_USE_DUMMY = true; -------------------------------------------------------------------------------- /modules/hookset-libc/source/hookset.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2023-2025, Kitsunebi Games 3 | Copyright (c) 2023-2025, Inochi2D Project 4 | 5 | Distributed under the Boost Software License, Version 1.0. 6 | (See accompanying LICENSE file or copy at 7 | https://www.boost.org/LICENSE_1_0.txt) 8 | */ 9 | module hookset; 10 | public import atomic; 11 | 12 | private { 13 | 14 | extern(C) extern void* malloc(size_t) nothrow @nogc; 15 | extern(C) extern void* realloc(void*, size_t) nothrow @nogc; 16 | extern(C) extern void free(void*) nothrow @nogc; 17 | extern(C) extern void* memcpy(return scope void*, scope const void*, size_t) nothrow @nogc pure; 18 | extern(C) extern void* memmove(return scope void*, scope const void*, size_t) nothrow @nogc pure; 19 | extern(C) extern void* memset(return scope void*, int, size_t) nothrow @nogc pure; 20 | 21 | extern(C) extern void abort() nothrow @nogc; 22 | } 23 | 24 | export extern(C) @nogc nothrow @system: 25 | 26 | void* nu_malloc(size_t bytes) { 27 | return malloc(bytes); 28 | } 29 | 30 | void* nu_realloc(void* data, size_t newSize) { 31 | return realloc(data, newSize); 32 | } 33 | 34 | void nu_free(void* data) { 35 | free(data); 36 | } 37 | 38 | void* nu_memcpy(return scope void* dst, scope const void* src, size_t bytes) pure { 39 | return memcpy(dst, src, bytes); 40 | } 41 | 42 | void* nu_memmove(return scope void* dst, scope const void* src, size_t bytes) pure { 43 | return memmove(dst, src, bytes); 44 | } 45 | 46 | void* nu_memset(return scope void* dst, ubyte value, size_t bytes) pure { 47 | return memset(dst, value, bytes); 48 | } 49 | 50 | void nu_fatal(const(char)[] msg) { 51 | abort(); 52 | } -------------------------------------------------------------------------------- /modules/hookset-wasm/LICENSE-walloc: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Igalia, S.L. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /modules/hookset-wasm/dub.sdl: -------------------------------------------------------------------------------- 1 | name "hookset-wasm" 2 | description "wasm hookset for numem." 3 | authors "Kitsunebi Games" "Luna" "Inochi2D Project" 4 | license "MIT" 5 | targetPath "out/" 6 | 7 | toolchainRequirements "ldc2" 8 | dflags "-mattr=+bulk-memory,+multivalue,+reference-types" 9 | 10 | configuration "emscripten32" { 11 | platforms "emscripten-wasm32" 12 | dflags "--disable-linker-strip-dead" 13 | } 14 | 15 | configuration "emscripten64" { 16 | platforms "emscripten-wasm64" 17 | lflags "-mwasm64" 18 | dflags "--disable-linker-strip-dead" 19 | } 20 | 21 | configuration "wasm32" { 22 | platforms "wasm-wasm32" 23 | targetType "staticLibrary" 24 | } 25 | 26 | configuration "wasm64" { 27 | platforms "wasm-wasm64" 28 | targetType "staticLibrary" 29 | lflags "-mwasm64" 30 | } 31 | 32 | configuration "other" { 33 | targetType "none" 34 | } -------------------------------------------------------------------------------- /modules/hookset-wasm/source/hookset.d: -------------------------------------------------------------------------------- 1 | module hookset; 2 | import ldc.intrinsics; 3 | import walloc; 4 | 5 | export extern(C) @nogc nothrow @system: 6 | 7 | void* nu_malloc(size_t bytes) { 8 | return malloc(bytes); 9 | } 10 | 11 | void* nu_realloc(void* data, size_t newSize) { 12 | return realloc(data, newSize); 13 | } 14 | 15 | void nu_free(void* data) { 16 | free(data); 17 | } 18 | 19 | void* nu_memcpy(return scope void* dst, scope const void* src, size_t bytes) pure { 20 | llvm_memcpy(dst, src, bytes); 21 | return dst; 22 | } 23 | 24 | void* nu_memmove(return scope void* dst, scope const void* src, size_t bytes) pure { 25 | llvm_memmove(dst, src, bytes); 26 | return dst; 27 | } 28 | 29 | void* nu_memset(return scope void* dst, ubyte value, size_t bytes) pure { 30 | llvm_memset(dst, value, bytes); 31 | return dst; 32 | } 33 | 34 | void nu_fatal(const(char)[] msg) { 35 | llvm_trap(); 36 | } -------------------------------------------------------------------------------- /modules/hookset-wasm/source/walloc.d: -------------------------------------------------------------------------------- 1 | /** 2 | A small malloc implementation for use in WebAssembly targets 3 | 4 | Copyright (c) 2023-2025, Kitsunebi Games 5 | Copyright (c) 2023-2025, Inochi2D Project 6 | Copyright (c) 2020, Igalia, S.L. 7 | 8 | Distributed under an MIT-style License. 9 | (See accompanying LICENSE file or copy at 10 | https://github.com/wingo/walloc/blob/master/LICENSE.md) 11 | */ 12 | module walloc; 13 | import ldc.intrinsics : 14 | llvm_wasm_memory_grow, 15 | llvm_wasm_memory_size, 16 | llvm_memmove; 17 | 18 | extern(C) @nogc nothrow: 19 | 20 | void* malloc(size_t size) @nogc nothrow @system { 21 | if (size == 0) 22 | return null; 23 | 24 | size_t granules = size_to_granules(size); 25 | chunk_kind kind = granules_to_chunk_kind(granules); 26 | return (kind == chunk_kind.LARGE_OBJECT) ? allocate_large(size) : allocate_small(kind); 27 | } 28 | 29 | export 30 | void free(void *ptr) @nogc nothrow @system { 31 | if (!ptr) return; 32 | 33 | _page_t* page = get_page(ptr); 34 | size_t chunk = get_chunk_index(ptr); 35 | ubyte kind = page.header.chunk_kinds[chunk]; 36 | if (kind == chunk_kind.LARGE_OBJECT) { 37 | _large_object_t* obj = get_large_object(ptr); 38 | obj.next = large_objects; 39 | large_objects = obj; 40 | allocate_chunk(page, chunk, chunk_kind.FREE_LARGE_OBJECT); 41 | pending_large_object_compact = 1; 42 | } else { 43 | size_t granules = kind; 44 | _freelist_t** loc = get_small_object_freelist(cast(chunk_kind)granules); 45 | _freelist_t* obj = cast(_freelist_t*)ptr; 46 | obj.next = *loc; 47 | *loc = obj; 48 | } 49 | } 50 | 51 | export 52 | void* realloc(void* ptr, size_t newSize) @nogc nothrow @system { 53 | if (!ptr) 54 | return malloc(newSize); 55 | 56 | size_t oldSize = get_alloc_size(ptr); 57 | if (oldSize <= newSize) 58 | return ptr; 59 | 60 | // Size is bigger, realloc just to be sure. 61 | void* n_mem = malloc(newSize); 62 | llvm_memmove(n_mem, ptr, oldSize, true); 63 | free(ptr); 64 | return n_mem; 65 | } 66 | 67 | private: 68 | 69 | size_t get_alloc_size(void* ptr) { 70 | _page_t* page = get_page(ptr); 71 | size_t chunk = get_chunk_index(ptr); 72 | chunk_kind kind = cast(chunk_kind)page.header.chunk_kinds[chunk]; 73 | 74 | if (kind == chunk_kind.LARGE_OBJECT) { 75 | _large_object_t* obj = get_large_object(ptr); 76 | return obj.size; 77 | } 78 | 79 | ptrdiff_t granules = chunk_kind_to_granules(kind); 80 | if (granules <= chunk_kind.SMALL_OBJECT_CHUNK_KINDS) 81 | return granules * GRANULE_SIZE; 82 | 83 | return 0; 84 | } 85 | 86 | extern __gshared void* __heap_base; 87 | __gshared size_t walloc_heap_size; 88 | __gshared _freelist_t*[chunk_kind.SMALL_OBJECT_CHUNK_KINDS] small_object_freelists; 89 | __gshared _large_object_t* large_objects; 90 | 91 | 92 | pragma(inline, true) 93 | size_t _max(size_t a, size_t b) { return a < b ? b : a; } 94 | 95 | pragma(inline, true) 96 | size_t _alignv(size_t val, size_t alignment) { return (val + alignment - 1) & ~(alignment - 1); } 97 | 98 | pragma(inline, true) 99 | extern(D) 100 | void __assert_aligned(T, Y)(T x, Y y) { 101 | assert(cast(size_t)x == _alignv(cast(size_t)x, cast(size_t)y)); 102 | } 103 | 104 | enum size_t CHUNK_SIZE = 256; 105 | enum size_t CHUNK_SIZE_LOG_2 = 8; 106 | enum size_t CHUNK_MASK = (CHUNK_SIZE - 1); 107 | enum size_t PAGE_SIZE = 65536; 108 | enum size_t PAGE_SIZE_LOG_2 = 16; 109 | enum size_t PAGE_MASK = (PAGE_SIZE - 1); 110 | enum size_t CHUNKS_PER_PAGE = 256; 111 | enum size_t GRANULE_SIZE = 8; 112 | enum size_t GRANULE_SIZE_LOG_2 = 3; 113 | enum size_t LARGE_OBJECT_THRESHOLD = 256; 114 | enum size_t LARGE_OBJECT_GRANULE_THRESHOLD = 32; 115 | enum size_t FIRST_ALLOCATABLE_CHUNK = 1; 116 | enum size_t PAGE_HEADER_SIZE = _page_header_t.sizeof; 117 | enum size_t LARGE_OBJECT_HEADER_SIZE = _large_object_t.sizeof; 118 | 119 | static assert(PAGE_SIZE == CHUNK_SIZE * CHUNKS_PER_PAGE); 120 | static assert(CHUNK_SIZE == 1 << CHUNK_SIZE_LOG_2); 121 | static assert(PAGE_SIZE == 1 << PAGE_SIZE_LOG_2); 122 | static assert(GRANULE_SIZE == 1 << GRANULE_SIZE_LOG_2); 123 | static assert(LARGE_OBJECT_THRESHOLD == 124 | LARGE_OBJECT_GRANULE_THRESHOLD * GRANULE_SIZE); 125 | 126 | struct _chunk_t { 127 | void[CHUNK_SIZE] data; 128 | } 129 | 130 | enum chunk_kind : ubyte { 131 | GRANULES_1, 132 | GRANULES_2, 133 | GRANULES_3, 134 | GRANULES_4, 135 | GRANULES_5, 136 | GRANULES_6, 137 | GRANULES_8, 138 | GRANULES_10, 139 | GRANULES_16, 140 | GRANULES_32, 141 | 142 | SMALL_OBJECT_CHUNK_KINDS, 143 | FREE_LARGE_OBJECT = 254, 144 | LARGE_OBJECT = 255 145 | } 146 | 147 | __gshared const ubyte[] small_object_granule_sizes = [ 148 | 1, 2, 3, 4, 5, 6, 8, 10, 16, 32 149 | ]; 150 | 151 | pragma(inline, true) 152 | chunk_kind granules_to_chunk_kind(size_t granules) { 153 | static foreach(gsize; small_object_granule_sizes) { 154 | if (granules <= gsize) 155 | return mixin(q{chunk_kind.GRANULES_}, cast(int)gsize); 156 | } 157 | return chunk_kind.LARGE_OBJECT; 158 | } 159 | 160 | pragma(inline, true) 161 | ubyte chunk_kind_to_granules(chunk_kind kind) { 162 | static foreach(gsize; small_object_granule_sizes) { 163 | if (kind == mixin(q{chunk_kind.GRANULES_}, cast(int)gsize)) 164 | return gsize; 165 | } 166 | return cast(ubyte)-1; 167 | } 168 | 169 | struct _page_header_t { 170 | ubyte[CHUNKS_PER_PAGE] chunk_kinds; 171 | } 172 | 173 | struct _page_t { 174 | union { 175 | _page_header_t header; 176 | _chunk_t[CHUNKS_PER_PAGE] chunks; 177 | } 178 | } 179 | 180 | pragma(inline, true) 181 | _page_t* get_page(void *ptr) { 182 | return cast(_page_t*)cast(void*)((cast(size_t) ptr) & ~PAGE_MASK); 183 | } 184 | 185 | pragma(inline, true) 186 | static size_t get_chunk_index(void *ptr) { 187 | return ((cast(size_t) ptr) & PAGE_MASK) / CHUNK_SIZE; 188 | } 189 | 190 | struct _freelist_t { 191 | _freelist_t *next; 192 | } 193 | 194 | struct _large_object_t { 195 | _large_object_t* next; 196 | size_t size; 197 | } 198 | 199 | pragma(inline, true) 200 | void* get_large_object_payload(_large_object_t *obj) { 201 | return (cast(void*)obj) + LARGE_OBJECT_HEADER_SIZE; 202 | } 203 | 204 | pragma(inline, true) 205 | _large_object_t* get_large_object(void *ptr) { 206 | return cast(_large_object_t*)ptr - LARGE_OBJECT_HEADER_SIZE; 207 | } 208 | 209 | _page_t* allocate_pages(size_t payloadSize, size_t* allocated) { 210 | size_t needed = payloadSize + PAGE_HEADER_SIZE; 211 | size_t heap_size = llvm_wasm_memory_size(0) * PAGE_SIZE; 212 | size_t base = heap_size; 213 | size_t preallocated = 0, grow = 0; 214 | 215 | if (!walloc_heap_size) { 216 | // We are allocating the initial pages, if any. We skip the first 64 kB, 217 | // then take any additional space up to the memory size. 218 | size_t heap_base = _alignv(cast(size_t)&__heap_base, PAGE_SIZE); 219 | preallocated = heap_size - heap_base; // Preallocated pages. 220 | walloc_heap_size = preallocated; 221 | base -= preallocated; 222 | } 223 | 224 | if (preallocated < needed) { 225 | // Always grow the walloc heap at least by 50%. 226 | grow = _alignv(_max(walloc_heap_size / 2, needed - preallocated), 227 | PAGE_SIZE); 228 | 229 | assert(grow); 230 | if (llvm_wasm_memory_grow(0, cast(int)(grow >> PAGE_SIZE_LOG_2)) == -1) { 231 | return null; 232 | } 233 | 234 | walloc_heap_size += grow; 235 | } 236 | 237 | _page_t* ret = cast(_page_t*)base; 238 | size_t size = grow + preallocated; 239 | 240 | assert(size); 241 | assert(size == _alignv(size, PAGE_SIZE)); 242 | *allocated = size / PAGE_SIZE; 243 | return ret; 244 | } 245 | 246 | void* allocate_chunk(_page_t* page, size_t idx, chunk_kind kind) { 247 | page.header.chunk_kinds[idx] = kind; 248 | return page.chunks[idx].data.ptr; 249 | } 250 | 251 | // It's possible for splitting to produce a large object of size 248 (256 minus 252 | // the header size) -- i.e. spanning a single chunk. In that case, push the 253 | // chunk back on the GRANULES_32 small object freelist. 254 | void maybe_repurpose_single_chunk_large_objects_head() { 255 | if (large_objects.size < CHUNK_SIZE) { 256 | size_t idx = get_chunk_index(large_objects); 257 | void* ptr = allocate_chunk(get_page(large_objects), idx, chunk_kind.GRANULES_32); 258 | large_objects = large_objects.next; 259 | _freelist_t* head = cast(_freelist_t*)ptr; 260 | head.next = small_object_freelists[chunk_kind.GRANULES_32]; 261 | small_object_freelists[chunk_kind.GRANULES_32] = head; 262 | } 263 | } 264 | 265 | // If there have been any large-object frees since the last large object 266 | // allocation, go through the freelist and merge any adjacent objects. 267 | __gshared int pending_large_object_compact = 0; 268 | _large_object_t** maybe_merge_free_large_object(_large_object_t** prev) { 269 | _large_object_t* obj = *prev; 270 | 271 | while(true) { 272 | void* end = get_large_object_payload(obj) + obj.size; 273 | __assert_aligned(end, CHUNK_SIZE); 274 | 275 | size_t chunk = get_chunk_index(end); 276 | if (chunk < FIRST_ALLOCATABLE_CHUNK) { 277 | // Merging can't create a large object that newly spans the header chunk. 278 | // This check also catches the end-of-heap case. 279 | return prev; 280 | } 281 | _page_t* page = get_page(end); 282 | if (page.header.chunk_kinds[chunk] != chunk_kind.FREE_LARGE_OBJECT) { 283 | return prev; 284 | } 285 | _large_object_t* next = cast(_large_object_t*)end; 286 | 287 | _large_object_t** prev_prev = &large_objects; 288 | _large_object_t* walk = large_objects; 289 | while(true) { 290 | assert(walk); 291 | if (walk == next) { 292 | obj.size += LARGE_OBJECT_HEADER_SIZE + walk.size; 293 | *prev_prev = walk.next; 294 | if (prev == &walk.next) { 295 | prev = prev_prev; 296 | } 297 | break; 298 | } 299 | prev_prev = &walk.next; 300 | walk = walk.next; 301 | } 302 | } 303 | } 304 | 305 | void maybe_compact_free_large_objects() { 306 | if (pending_large_object_compact) { 307 | pending_large_object_compact = 0; 308 | _large_object_t** prev = &large_objects; 309 | while (*prev) { 310 | prev = &(*maybe_merge_free_large_object(prev)).next; 311 | } 312 | } 313 | } 314 | 315 | // Allocate a large object with enough space for SIZE payload bytes. Returns a 316 | // large object with a header, aligned on a chunk boundary, whose payload size 317 | // may be larger than SIZE, and whose total size (header included) is 318 | // chunk-aligned. Either a suitable allocation is found in the large object 319 | // freelist, or we ask the OS for some more pages and treat those pages as a 320 | // large object. If the allocation fits in that large object and there's more 321 | // than an aligned chunk's worth of data free at the end, the large object is 322 | // split. 323 | // 324 | // The return value's corresponding chunk in the page as starting a large 325 | // object. 326 | _large_object_t* allocate_large_object(size_t size) { 327 | maybe_compact_free_large_objects(); 328 | 329 | _large_object_t* best = null; 330 | _large_object_t** best_prev = &large_objects; 331 | size_t best_size = -1; 332 | 333 | _large_object_t** prev = &large_objects; 334 | _large_object_t* walk = large_objects; 335 | while (walk) { 336 | if (walk.size >= size && walk.size < best_size) { 337 | best_size = walk.size; 338 | best = walk; 339 | best_prev = prev; 340 | 341 | // Not going to do any better than this; just return it. 342 | if (best_size + LARGE_OBJECT_HEADER_SIZE == _alignv(size + LARGE_OBJECT_HEADER_SIZE, CHUNK_SIZE)) 343 | break; 344 | } 345 | 346 | prev = &walk.next; 347 | walk = walk.next; 348 | } 349 | 350 | if (!best) { 351 | // The large object freelist doesn't have an object big enough for this 352 | // allocation. Allocate one or more pages from the OS, and treat that new 353 | // sequence of pages as a fresh large object. It will be split if 354 | // necessary. 355 | size_t size_with_header = size + _large_object_t.sizeof; 356 | size_t n_allocated = 0; 357 | _page_t* page = allocate_pages(size_with_header, &n_allocated); 358 | if (!page) { 359 | return null; 360 | } 361 | 362 | void* ptr = allocate_chunk(page, FIRST_ALLOCATABLE_CHUNK, chunk_kind.LARGE_OBJECT); 363 | best = cast(_large_object_t*)ptr; 364 | size_t page_header = ptr - cast(void*)page; 365 | 366 | best.next = large_objects; 367 | best.size = best_size = n_allocated * PAGE_SIZE - page_header - LARGE_OBJECT_HEADER_SIZE; 368 | assert(best_size >= size_with_header); 369 | } 370 | 371 | allocate_chunk(get_page(best), get_chunk_index(best), chunk_kind.LARGE_OBJECT); 372 | 373 | _large_object_t* next = best.next; 374 | *best_prev = next; 375 | 376 | size_t tail_size = (best_size - size) & ~CHUNK_MASK; 377 | if (tail_size) { 378 | // The best-fitting object has 1 or more aligned chunks free after the 379 | // requested allocation; split the tail off into a fresh aligned object. 380 | _page_t* start_page = get_page(best); 381 | void* start = get_large_object_payload(best); 382 | void* end = start + best_size; 383 | 384 | if (start_page == get_page(end - tail_size - 1)) { 385 | 386 | // The allocation does not span a page boundary; yay. 387 | __assert_aligned(end, CHUNK_SIZE); 388 | } else if (size < PAGE_SIZE - LARGE_OBJECT_HEADER_SIZE - CHUNK_SIZE) { 389 | 390 | // If the allocation itself smaller than a page, split off the head, then 391 | // fall through to maybe split the tail. 392 | assert(cast(size_t)end == _alignv(cast(size_t)end, PAGE_SIZE)); 393 | 394 | size_t first_page_size = PAGE_SIZE - (cast(size_t)start & PAGE_MASK); 395 | _large_object_t* head = best; 396 | allocate_chunk(start_page, get_chunk_index(start), chunk_kind.FREE_LARGE_OBJECT); 397 | head.size = first_page_size; 398 | head.next = large_objects; 399 | large_objects = head; 400 | 401 | maybe_repurpose_single_chunk_large_objects_head(); 402 | 403 | _page_t* next_page = start_page + 1; 404 | void* ptr = allocate_chunk(next_page, FIRST_ALLOCATABLE_CHUNK, chunk_kind.LARGE_OBJECT); 405 | best = cast(_large_object_t*)ptr; 406 | best.size = best_size = best_size - first_page_size - CHUNK_SIZE - LARGE_OBJECT_HEADER_SIZE; 407 | assert(best_size >= size); 408 | 409 | start = get_large_object_payload(best); 410 | tail_size = (best_size - size) & ~CHUNK_MASK; 411 | } else { 412 | 413 | // A large object that spans more than one page will consume all of its 414 | // tail pages. Therefore if the split traverses a page boundary, round up 415 | // to page size. 416 | __assert_aligned(end, PAGE_SIZE); 417 | size_t first_page_size = PAGE_SIZE - (cast(size_t)start & PAGE_MASK); 418 | size_t tail_pages_size = _alignv(size - first_page_size, PAGE_SIZE); 419 | size = first_page_size + tail_pages_size; 420 | tail_size = best_size - size; 421 | } 422 | best.size -= tail_size; 423 | 424 | size_t tail_idx = get_chunk_index(end - tail_size); 425 | while (tail_idx < FIRST_ALLOCATABLE_CHUNK && tail_size) { 426 | 427 | // We would be splitting in a page header; don't do that. 428 | tail_size -= CHUNK_SIZE; 429 | tail_idx++; 430 | } 431 | 432 | if (tail_size) { 433 | _page_t *page = get_page(end - tail_size); 434 | void* tail_ptr = allocate_chunk(page, tail_idx, chunk_kind.FREE_LARGE_OBJECT); 435 | _large_object_t* tail = cast(_large_object_t*) tail_ptr; 436 | tail.next = large_objects; 437 | tail.size = tail_size - LARGE_OBJECT_HEADER_SIZE; 438 | 439 | debug { 440 | size_t payloadsz = cast(size_t)get_large_object_payload(tail) + tail.size; 441 | assert(payloadsz == _alignv(payloadsz, CHUNK_SIZE)); 442 | } 443 | 444 | large_objects = tail; 445 | maybe_repurpose_single_chunk_large_objects_head(); 446 | } 447 | } 448 | 449 | debug { 450 | size_t payloadsz = cast(size_t)get_large_object_payload(best) + best.size; 451 | assert(payloadsz == _alignv(payloadsz, CHUNK_SIZE)); 452 | } 453 | return best; 454 | } 455 | 456 | _freelist_t* obtain_small_objects(chunk_kind kind) { 457 | _freelist_t** whole_chunk_freelist = &small_object_freelists[chunk_kind.GRANULES_32]; 458 | void *chunk; 459 | if (*whole_chunk_freelist) { 460 | chunk = *whole_chunk_freelist; 461 | *whole_chunk_freelist = (*whole_chunk_freelist).next; 462 | } else { 463 | chunk = allocate_large_object(0); 464 | if (!chunk) { 465 | return null; 466 | } 467 | } 468 | 469 | void* ptr = allocate_chunk(get_page(chunk), get_chunk_index(chunk), kind); 470 | void* end = ptr + CHUNK_SIZE; 471 | _freelist_t* next = null; 472 | size_t size = chunk_kind_to_granules(kind) * GRANULE_SIZE; 473 | for (size_t i = size; i <= CHUNK_SIZE; i += size) { 474 | _freelist_t* head = cast(_freelist_t*)(end - i); 475 | head.next = next; 476 | next = head; 477 | } 478 | return next; 479 | } 480 | 481 | pragma(inline, true) 482 | size_t size_to_granules(size_t size) { 483 | return (size + GRANULE_SIZE - 1) >> GRANULE_SIZE_LOG_2; 484 | } 485 | 486 | pragma(inline, true) 487 | _freelist_t** get_small_object_freelist(chunk_kind kind) { 488 | assert(kind < chunk_kind.SMALL_OBJECT_CHUNK_KINDS); 489 | return &small_object_freelists[kind]; 490 | } 491 | 492 | void* allocate_small(chunk_kind kind) { 493 | _freelist_t** loc = get_small_object_freelist(kind); 494 | if (!*loc) { 495 | _freelist_t* freelist = obtain_small_objects(kind); 496 | if (!freelist) 497 | return null; 498 | 499 | *loc = freelist; 500 | } 501 | 502 | _freelist_t* ret = *loc; 503 | *loc = ret.next; 504 | return cast(void*)ret; 505 | } 506 | 507 | void* allocate_large(size_t size) { 508 | _large_object_t* obj = allocate_large_object(size); 509 | return obj ? get_large_object_payload(obj) : null; 510 | } -------------------------------------------------------------------------------- /numem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Inochi2D/numem/9c2fd655e3d47ff39090df1d83b4e61d8f989178/numem.png -------------------------------------------------------------------------------- /source/numem/casting.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem casting helpers 3 | 4 | Copyright: 5 | Copyright © 2023-2025, Kitsunebi Games 6 | Copyright © 2023-2025, Inochi2D Project 7 | 8 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Authors: Luna Nielsen 10 | */ 11 | module numem.casting; 12 | import numem.core.traits; 13 | 14 | nothrow @nogc: 15 | 16 | /** 17 | Safely casts between $(D T) and $(D U). 18 | */ 19 | auto ref T dynamic_cast(T, U)(auto ref U from) @trusted if(is(T : U)) { 20 | return cast(T)from; 21 | } 22 | 23 | /** 24 | Allows casting one type to another given that the types have the same 25 | size, reinterpreting the data. 26 | 27 | This will NOT call opCast of aggregate types! 28 | */ 29 | pragma(inline, true) 30 | auto ref T reinterpret_cast(T, U)(auto ref U from) @trusted if (T.sizeof == U.sizeof) { 31 | union tmp { U from; T to; } 32 | return tmp(from).to; 33 | } 34 | 35 | /** 36 | Allows casting between qualified versions of the input type. 37 | The unqualified version of the types need to be implicitly 38 | convertible in at least one direction. 39 | 40 | Examples: 41 | --- 42 | const(char)* myString = "Hello, world!"; 43 | char* myStringMut = const_cast!(char*)(myString); 44 | --- 45 | */ 46 | pragma(inline, true) 47 | auto ref T const_cast(T, U)(auto ref U from) @trusted if (isAnyCompatible!(T, U)) { 48 | return reinterpret_cast!(T, U)(from); 49 | } -------------------------------------------------------------------------------- /source/numem/compiler.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem Compiler Traits and Information. 3 | 4 | New definitions are added to this file over time to have a central place 5 | to query about compiler differences which may cause compilation to fail. 6 | 7 | Copyright: 8 | Copyright © 2023-2025, Kitsunebi Games 9 | Copyright © 2023-2025, Inochi2D Project 10 | 11 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 12 | Authors: 13 | Luna Nielsen 14 | */ 15 | module numem.compiler; 16 | 17 | version(D_Ddoc) { 18 | 19 | /** 20 | Whether the compiler being used for this compilation is strict 21 | about types and function definitions matching exactly. 22 | 23 | This flag allows libraries to detect this case and revert to 24 | using libdruntime or libphobos definitions for things like 25 | C symbols. 26 | */ 27 | enum bool NU_COMPILER_STRICT_TYPES = false; 28 | 29 | } else version(LDC) { 30 | import ldc.intrinsics; 31 | 32 | // NOTE: Before LLVM 17 typed pointers were provided by the IR. 33 | // These typed pointers can cause conflicts if symbols are redeclared, 34 | // as nulib and other libraries may end up redefining symbols 35 | // workarounds may need to be employed. This enum helps dependents 36 | // detect this case. 37 | enum bool NU_COMPILER_STRICT_TYPES = LLVM_version < 1700; 38 | } else version(GNU) { 39 | 40 | // NOTE: GDC is *always* strict about type matches, so this should always 41 | // be enabled there. 42 | enum bool NU_COMPILER_STRICT_TYPES = true; 43 | } else version(DMD) { 44 | 45 | // NOTE: DMD, to my understanding hasn't been too strict about types. 46 | enum bool NU_COMPILER_STRICT_TYPES = __VERSION__ < 2100; 47 | } else { 48 | 49 | // NOTE: For any other compiler it's better to be on the safe side. 50 | enum bool NU_COMPILER_STRICT_TYPES = true; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /source/numem/core/atomic.d: -------------------------------------------------------------------------------- 1 | /** 2 | Atomics Hooks 3 | 4 | Copyright: 5 | Copyright © 2023-2025, Kitsunebi Games 6 | Copyright © 2023-2025, Inochi2D Project 7 | 8 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Authors: Luna Nielsen 10 | */ 11 | module numem.core.atomic; 12 | 13 | // Allow disabling atomics. 14 | // This replaces atomics operations with dummies that are non-atomic. 15 | // much like hookset-libc does when it can't find an implementation. 16 | version(NUMEM_NO_ATOMICS) { 17 | /** 18 | Gets the status of atomics support in the current 19 | numem configuration. 20 | 21 | Notes: 22 | If atomics are not supported, these functions will 23 | act as non-atomic alternatives. 24 | 25 | Returns: 26 | Whether atomics are supported by the loaded 27 | hookset. 28 | */ 29 | export 30 | extern(C) 31 | bool nu_atomic_supported() @nogc nothrow { 32 | return false; 33 | } 34 | 35 | /** 36 | Inserts a memory acquire barrier. 37 | */ 38 | export 39 | extern(C) 40 | void nu_atomic_barrier_acquire() @nogc nothrow { 41 | return; 42 | } 43 | 44 | /** 45 | Inserts a memory release barrier. 46 | */ 47 | export 48 | extern(C) 49 | void nu_atomic_barrier_release() @nogc nothrow { 50 | return; 51 | } 52 | 53 | /** 54 | Loads a 32 bit value atomically. 55 | */ 56 | export 57 | extern(C) 58 | inout(uint) nu_atomic_load_32(ref inout(uint) src) @nogc nothrow { 59 | return src; 60 | } 61 | 62 | /** 63 | Stores a 32 bit value atomically. 64 | */ 65 | export 66 | extern(C) 67 | void nu_atomic_store_32(ref uint dst, uint value) @nogc nothrow { 68 | dst = value; 69 | } 70 | 71 | /** 72 | Adds a 32 bit value atomically. 73 | */ 74 | export 75 | extern(C) 76 | extern uint nu_atomic_add_32(ref uint dst, uint value) @nogc nothrow { 77 | uint oldval = dst; 78 | dst += value; 79 | return oldval; 80 | } 81 | 82 | /** 83 | Subtracts a 32 bit value atomically. 84 | */ 85 | export 86 | extern(C) 87 | extern uint nu_atomic_sub_32(ref uint dst, uint value) @nogc nothrow { 88 | uint oldval = dst; 89 | dst -= value; 90 | return oldval; 91 | } 92 | 93 | /** 94 | Loads a pointer value atomically. 95 | */ 96 | export 97 | extern(C) 98 | extern inout(void)* nu_atomic_load_ptr(inout(void)** src) @nogc nothrow { 99 | return *src; 100 | } 101 | 102 | /** 103 | Stores a pointer value atomically. 104 | */ 105 | export 106 | extern(C) 107 | extern void nu_atomic_store_ptr(void** dst, void* value) @nogc nothrow { 108 | *dst = value; 109 | } 110 | 111 | /** 112 | Compares variable at $(D dst) and swaps it if it contains $(D oldvalue). 113 | */ 114 | export 115 | extern(C) 116 | extern bool nu_atomic_cmpxhg_ptr(void** dst, void* oldvalue, void* value) @nogc nothrow { 117 | if (*dst is oldvalue) { 118 | *dst = value; 119 | return true; 120 | } 121 | 122 | return false; 123 | } 124 | } else: 125 | 126 | extern(C): 127 | 128 | /** 129 | Gets the status of atomics support in the current 130 | numem configuration. 131 | 132 | Notes: 133 | If atomics are not supported, these functions will 134 | act as non-atomic alternatives. 135 | 136 | Returns: 137 | Whether atomics are supported by the loaded 138 | hookset. 139 | */ 140 | extern bool nu_atomic_supported() @nogc nothrow; 141 | 142 | /** 143 | Inserts a memory acquire barrier. 144 | */ 145 | extern void nu_atomic_barrier_acquire() @nogc nothrow; 146 | 147 | /** 148 | Inserts a memory release barrier. 149 | */ 150 | extern void nu_atomic_barrier_release() @nogc nothrow; 151 | 152 | /** 153 | Loads a 32 bit value atomically. 154 | */ 155 | extern inout(uint) nu_atomic_load_32(ref inout(uint) src) @nogc nothrow; 156 | 157 | /** 158 | Stores a 32 bit value atomically. 159 | */ 160 | extern void nu_atomic_store_32(ref uint dst, uint value) @nogc nothrow; 161 | 162 | /** 163 | Adds a 32 bit value atomically. 164 | */ 165 | extern uint nu_atomic_add_32(ref uint dst, uint value) @nogc nothrow; 166 | 167 | /** 168 | Subtracts a 32 bit value atomically. 169 | */ 170 | extern uint nu_atomic_sub_32(ref uint dst, uint value) @nogc nothrow; 171 | 172 | /** 173 | Loads a pointer value atomically. 174 | */ 175 | extern inout(void)* nu_atomic_load_ptr(inout(void)** src) @nogc nothrow; 176 | 177 | /** 178 | Stores a pointer value atomically. 179 | */ 180 | extern void nu_atomic_store_ptr(void** dst, void* value) @nogc nothrow; 181 | 182 | /** 183 | Compares variable at $(D dst) and swaps it if it contains $(D oldvalue). 184 | */ 185 | extern bool nu_atomic_cmpxhg_ptr(void** dst, void* oldvalue, void* value) @nogc nothrow; -------------------------------------------------------------------------------- /source/numem/core/attributes.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem attribute hooks for various compilers. 3 | 4 | We implement them here to avoid relying on druntime, 5 | or phobos. 6 | 7 | Some of this code references druntime in dmd, ldc and gcc. 8 | 9 | Copyright: 10 | Copyright © 2023-2025, Kitsunebi Games 11 | Copyright © 2023-2025, Inochi2D Project 12 | 13 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 14 | Authors: Luna Nielsen 15 | */ 16 | module numem.core.attributes; 17 | 18 | // TODO: Ask GCC maintainers whether this is OK licensing wise. 19 | // Internal Helpers. 20 | version(GDC) { 21 | private struct Attribute(A...) { 22 | A arguments; 23 | } 24 | } 25 | 26 | /** 27 | When applied to a global symbol, specifies that the symbol should be emitted 28 | with weak linkage. An example use case is a library function that should be 29 | overridable by user code. 30 | 31 | Quote from the LLVM manual: "Note that weak linkage does not actually allow 32 | the optimizer to inline the body of this function into callers because it 33 | doesn’t know if this definition of the function is the definitive definition 34 | within the program or whether it will be overridden by a stronger 35 | definition." 36 | 37 | Examples: 38 | --- 39 | import numem.core.attributes; 40 | 41 | @weak int user_hook() { return 1; } 42 | --- 43 | */ 44 | version(LDC) { 45 | immutable weak = _weak(); 46 | private struct _weak { } 47 | } else version(GDC) { 48 | enum weak = Attribute!string("weak"); 49 | } else { 50 | // NOTE: Not used by other compilers. 51 | struct weak; 52 | } 53 | 54 | /** 55 | When applied to a global variable or function, causes it to be emitted to a 56 | non-standard object file/executable section. 57 | 58 | The target platform might impose certain restrictions on the format for 59 | section names. 60 | 61 | Examples: 62 | --- 63 | import numem.core.attributes; 64 | 65 | @section(".mySection") int myGlobal; 66 | --- 67 | */ 68 | version(LDC) { 69 | struct section { string name; } 70 | } else version(GDC) { 71 | auto section(string sectionName) { 72 | return Attribute!(string, string)("section", sectionName); 73 | } 74 | } else { 75 | 76 | // DMD doesn't support this, but whatever. 77 | struct section { string name; } 78 | } 79 | 80 | /** 81 | Use this attribute to attach an Objective-C selector to a method. 82 | 83 | Examples: 84 | --- 85 | extern (Objective-C) 86 | class NSObject 87 | { 88 | this() @selector("init"); 89 | static NSObject alloc() @selector("alloc"); 90 | NSObject initWithUTF8String(in char* str) @selector("initWithUTF8String:"); 91 | ObjcObject copyScriptingValue(ObjcObject value, NSString key, NSDictionary properties) 92 | @selector("copyScriptingValue:forKey:withProperties:"); 93 | } 94 | --- 95 | */ 96 | version(D_ObjectiveC) 97 | struct selector { 98 | string selector; 99 | } 100 | 101 | /** 102 | Use this attribute to make an Objective-C interface method optional. 103 | 104 | An optional method is a method that does **not** have to be implemented in 105 | the class that implements the interface. To safely call an optional method, 106 | a runtime check should be performed to make sure the receiver implements the 107 | method. 108 | */ 109 | version(D_ObjectiveC) 110 | enum optional; -------------------------------------------------------------------------------- /source/numem/core/exception.d: -------------------------------------------------------------------------------- 1 | /** 2 | Essential tools for nothrow. 3 | 4 | Copyright: 5 | Copyright © 2023-2025, Kitsunebi Games 6 | Copyright © 2023-2025, Inochi2D Project 7 | 8 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Authors: Luna Nielsen 10 | */ 11 | module numem.core.exception; 12 | import numem.core.memory; 13 | import numem.core.hooks; 14 | import numem.core.traits; 15 | import numem.lifetime : nogc_delete, nogc_new; 16 | 17 | /** 18 | Assumes that a given function or delegate does not throw. 19 | 20 | Params: 21 | expr = The expression to execute. 22 | args = Arguments to pass to the function. 23 | */ 24 | auto assumeNoThrow(T, Args...)(T expr, auto ref Args args) @nogc nothrow if (isSomeFunction!T) { 25 | try { 26 | return expr(args); 27 | } catch (Exception ex) { 28 | nu_fatal(ex.msg); 29 | assert(0); 30 | } 31 | } 32 | 33 | /** 34 | Assumes that a given function or delegate does not throw. 35 | 36 | Params: 37 | expr = The expression to execute. 38 | args = Arguments to pass to the function. 39 | */ 40 | auto assumeNoThrowNoGC(T, Args...)(T expr, auto ref Args args) @nogc nothrow if (isSomeFunction!T) { 41 | try { 42 | return assumeNoGC(expr, args); 43 | } catch (Exception ex) { 44 | nu_fatal(ex.msg); 45 | assert(0); 46 | } 47 | } 48 | 49 | /** 50 | Assumes that the provided function does not use 51 | the D garbage collector. 52 | 53 | Params: 54 | expr = The expression to execute. 55 | args = Arguments to pass to the function. 56 | */ 57 | auto assumeNoGC(T, Args...)(T expr, auto ref Args args) @nogc if (isSomeFunction!T) { 58 | static if (is(T Fptr : Fptr*) && is(Fptr == function)) 59 | alias ft = @nogc ReturnType!T function(Parameters!T); 60 | else static if (is(T Fdlg == delegate)) 61 | alias ft = @nogc ReturnType!T delegate(Parameters!T); 62 | else 63 | static assert(0); 64 | 65 | return (cast(ft)expr)(args); 66 | } 67 | 68 | /** 69 | An exception which can be thrown from numem 70 | */ 71 | class NuException : Exception { 72 | public: 73 | @nogc: 74 | 75 | ~this() { 76 | // Free message. 77 | msg.nu_resize(0); 78 | 79 | // Free next-in-chain 80 | if (Throwable t = this.next()) { 81 | nogc_delete(t); 82 | } 83 | } 84 | 85 | /** 86 | Constructs a nogc exception 87 | */ 88 | this(const(char)[] msg, Throwable nextInChain = null, string file = __FILE__, size_t line = __LINE__) { 89 | super(cast(string)msg.nu_dup(), nextInChain, file, line); 90 | } 91 | 92 | /** 93 | Returns the error message 94 | */ 95 | override 96 | const(char)[] message() const @safe nothrow { 97 | return this.msg; 98 | } 99 | 100 | /** 101 | Helper function to free this exception 102 | */ 103 | void free() @trusted { 104 | NuException ex = this; 105 | nogc_delete(ex); 106 | } 107 | 108 | /** 109 | Helper function to free this exception 110 | */ 111 | final 112 | void freeNoThrow() @trusted nothrow { 113 | assumeNoThrowNoGC((NuException self) { 114 | nogc_delete(self); 115 | }, this); 116 | } 117 | } 118 | 119 | /** 120 | Enforces the truthiness of $(D in_) 121 | 122 | If it evaluates to false, throws a $(D NuException). 123 | */ 124 | void enforce(T)(T in_, const(char)[] err) @nogc @trusted { 125 | if (!in_) { 126 | throw nogc_new!NuException(err); 127 | } 128 | } 129 | 130 | /** 131 | Enforces the truthiness of $(D in_) 132 | 133 | If it evaluates to false, throws a $(D NuException). 134 | */ 135 | void enforce(T)(T in_, NuException t) @nogc @trusted { 136 | if (!in_) { 137 | throw t; 138 | } 139 | } -------------------------------------------------------------------------------- /source/numem/core/hooks.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem Core Hooks. 3 | 4 | This file contains all the core hooks numem calls internally to handle memory. 5 | Given that some platforms may not have a C standard library, these hooks allow you 6 | to override how numem handles memory for such platforms from an external library. 7 | 8 | In this case, most of the hooks presented here will need to be implemented to cover 9 | all of the used internal hooks within numem. 10 | 11 | Hooks which are prefixed $(D nuopt_) are optional and may be omitted, they are 12 | usually for language-specific interoperability. 13 | 14 | Copyright: 15 | Copyright © 2023-2025, Kitsunebi Games 16 | Copyright © 2023-2025, Inochi2D Project 17 | 18 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 19 | Authors: Luna Nielsen 20 | */ 21 | module numem.core.hooks; 22 | 23 | /** 24 | Allocates $(D bytes) worth of memory. 25 | 26 | Params: 27 | bytes = How many bytes to allocate. 28 | 29 | Returns: 30 | Newly allocated memory or $(D null) on failure. 31 | To avoid a memory leak, free the memory with $(D nu_free). 32 | 33 | Notes: 34 | Given the implementation of $(D nu_malloc) and $(D nu_free) may be 35 | independent of the libc allocator, memory allocated with 36 | $(D nu_malloc) should $(B always) be freed with $(D nu_free)! 37 | */ 38 | extern(C) 39 | extern void* nu_malloc(size_t bytes) @nogc nothrow @system pure; 40 | 41 | /** 42 | Reallocates memory prior allocated with $(D nu_malloc) or 43 | $(D nu_alignedalloc). 44 | 45 | This function may re-allocate the memory if resizing the allocation 46 | to the new size is not possible. 47 | 48 | Params: 49 | data = Pointer to prior allocated memory. 50 | newSize = New size of the allocation, in bytes. 51 | 52 | Returns: 53 | The address of the reallocated memory or $(D null) on failure. 54 | To avoid a memory leak, free the memory with $(D nu_free). 55 | 56 | Notes: 57 | Given the implementation of $(D nu_realloc) and $(D nu_free) may be 58 | independent of the libc allocator, memory allocated with 59 | $(D nu_realloc) should $(B always) be freed with $(D nu_free)! 60 | */ 61 | extern(C) 62 | extern void* nu_realloc(void* data, size_t newSize) @nogc nothrow @system pure; 63 | 64 | /** 65 | Frees allocated memory. 66 | 67 | Params: 68 | data = Pointer to start of memory prior allocated. 69 | 70 | Notes: 71 | Given the implementation of the allocators and $(D nu_free) may be 72 | independent of the libc allocator, memory allocated with 73 | numem functions should $(B always) be freed with $(D nu_free)! 74 | */ 75 | extern(C) 76 | extern void nu_free(void* data) @nogc nothrow @system pure; 77 | 78 | /** 79 | Copies $(D bytes) worth of data from $(D src) into $(D dst). 80 | 81 | $(D src) and $(D dst) needs to be allocated and within range, 82 | additionally the source and destination may not overlap. 83 | 84 | Params: 85 | dst = Destination of the memory copy operation. 86 | src = Source of the memory copy operation 87 | bytes = The amount of bytes to copy. 88 | */ 89 | extern(C) 90 | extern void* nu_memcpy(return scope void* dst, scope const void* src, size_t bytes) @nogc nothrow @system pure; 91 | 92 | /** 93 | Moves $(D bytes) worth of data from $(D src) into $(D dst). 94 | 95 | $(D src) and $(D dst) needs to be allocated and within range. 96 | 97 | Params: 98 | dst = Destination of the memory copy operation. 99 | src = Source of the memory copy operation 100 | bytes = The amount of bytes to copy. 101 | 102 | Returns: 103 | Pointer to $(D dst) 104 | */ 105 | extern(C) 106 | extern void* nu_memmove(return scope void* dst, scope const void* src, size_t bytes) @nogc nothrow @system pure; 107 | 108 | /** 109 | Fills $(D dst) with $(D bytes) amount of $(D value)s. 110 | 111 | Params: 112 | dst = Destination of the memory copy operation. 113 | value = The byte to repeatedly copy to the memory starting at $(D dst) 114 | bytes = The amount of bytes to write. 115 | 116 | Returns: 117 | Pointer to $(D dst) 118 | */ 119 | extern(C) 120 | extern void* nu_memset(return scope void* dst, ubyte value, size_t bytes) @nogc nothrow @system pure; 121 | 122 | /** 123 | Called internally by numem if a fatal error occured. 124 | 125 | This function $(B should) exit the application and if possible, 126 | print an error. But it $(B may) not print an error. 127 | 128 | Params: 129 | errMsg = A D string containing the error in question. 130 | 131 | Returns: 132 | Never returns, the application should crash at this point. 133 | */ 134 | extern(C) 135 | extern void nu_fatal(const(char)[] errMsg) @nogc nothrow @system; 136 | 137 | 138 | /** 139 | Hooks for handling auto release pools. 140 | 141 | These $(I optional) pool callback functions allows implementers of 142 | numem to hook into the auto release pool system. 143 | 144 | Push should push a new context onto an internal stack, while pop should 145 | release an element from the stack. 146 | 147 | These functions are mainly useful for Objective-C interoperability. 148 | 149 | See_Also: 150 | $(LINK2 https://clang.llvm.org/docs/AutomaticReferenceCounting.html#autoreleasepool, ARC Documentation) 151 | */ 152 | extern(C) 153 | __gshared void* function() @nogc nothrow @system nuopt_autoreleasepool_push = null; 154 | 155 | /// ditto 156 | extern(C) 157 | __gshared void function(void*) @nogc nothrow @system nuopt_autoreleasepool_pop = null; -------------------------------------------------------------------------------- /source/numem/core/lifetime.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem Internal Lifetime Handling. 3 | 4 | This module implements the neccesary functionality to instantiate 5 | complex D types, including handling of copy constructors, 6 | destructors, moving, and copying. 7 | 8 | Copyright: 9 | Copyright © 2023-2025, Kitsunebi Games 10 | Copyright © 2023-2025, Inochi2D Project 11 | 12 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 13 | Authors: Luna Nielsen 14 | */ 15 | module numem.core.lifetime; 16 | import numem.core.hooks; 17 | import numem.core.traits; 18 | import numem.core.exception; 19 | import numem.core.memory; 20 | import numem.casting; 21 | import numem.lifetime : nogc_construct, nogc_initialize, nogc_delete; 22 | 23 | // Deletion function signature. 24 | private extern (D) alias fp_t = void function (Object) @nogc nothrow; 25 | 26 | // Helper which creates a destructor function that 27 | // D likes. 28 | private template xdtor(T) { 29 | void xdtor(ref T obj) { 30 | obj.__xdtor(); 31 | } 32 | } 33 | 34 | /** 35 | Initializes the memory at the specified chunk. 36 | */ 37 | void initializeAt(T)(scope ref T chunk) @nogc nothrow @trusted { 38 | static if (is(T == class)) { 39 | 40 | // NOTE: class counts as a pointer, so its normal init symbol 41 | // in general circumstances is null, we don't want this, so class check 42 | // should be first! Otherwise the chunk = T.init will mess us up. 43 | const void[] initSym = __traits(initSymbol, T); 44 | nu_memcpy(cast(void*)chunk, cast(void*)initSym.ptr, initSym.length); 45 | } else static if (__traits(isZeroInit, T)) { 46 | 47 | nu_memset(cast(void*)&chunk, 0, T.sizeof); 48 | } else static if (__traits(isScalar, T) || 49 | (T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, () { T chunk; chunk = T.init; }))) { 50 | 51 | // To avoid triggering postblits/move constructors we need to do a memcpy here as well. 52 | // If the user wants to postblit after initialization, they should call the relevant postblit function. 53 | T tmp = T.init; 54 | nu_memcpy(cast(void*)&chunk, &tmp, T.sizeof); 55 | } else static if (__traits(isStaticArray, T)) { 56 | 57 | foreach(i; 0..T.length) 58 | initializeAt(chunk[i]); 59 | } else { 60 | 61 | const void[] initSym = __traits(initSymbol, T); 62 | nu_memcpy(cast(void*)&chunk, initSym.ptr, initSym.length); 63 | } 64 | } 65 | 66 | /** 67 | Initializes the memory at the specified chunk, but ensures no 68 | context pointers are wiped. 69 | */ 70 | void initializeAtNoCtx(T)(scope ref T chunk) @nogc nothrow @trusted { 71 | static if (__traits(isZeroInit, T)) { 72 | 73 | nu_memset(cast(void*)&chunk, 0, T.sizeof); 74 | } else static if (__traits(isScalar, T) || 75 | (T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, () { T chunk; chunk = T.init; }))) { 76 | 77 | // To avoid triggering postblits/move constructors we need to do a memcpy here as well. 78 | // If the user wants to postblit after initialization, they should call the relevant postblit function. 79 | T tmp = T.init; 80 | nu_memcpy(cast(void*)&chunk, &tmp, T.sizeof); 81 | } else static if (__traits(isStaticArray, T)) { 82 | 83 | foreach(i; 0..T.length) 84 | initializeAt(chunk[i]); 85 | } else { 86 | 87 | const void[] initSym = __traits(initSymbol, T); 88 | nu_memcpy(cast(void*)&chunk, initSym.ptr, initSym.length); 89 | } 90 | } 91 | 92 | /** 93 | Destroy element with a destructor. 94 | */ 95 | @trusted 96 | void destruct(T, bool reInit=true)(ref T obj_) @nogc { 97 | alias RealT = Unref!T; 98 | 99 | // Handle custom destruction functions. 100 | alias destroyWith = nu_getdestroywith!RealT; 101 | static if (is(typeof(destroyWith))) { 102 | destroyWith(obj_); 103 | } else { 104 | static if (isHeapAllocated!T) { 105 | if (obj_ !is null) { 106 | static if (__traits(getLinkage, RealT) == "D") { 107 | auto cInfo = cast(ClassInfo)typeid(obj_); 108 | if (cInfo) { 109 | auto c = cInfo; 110 | 111 | // Call destructors in order of most specific 112 | // to least-specific 113 | do { 114 | if (c.destructor) 115 | (cast(fp_t)c.destructor)(cast(Object)obj_); 116 | } while((c = c.base) !is null); 117 | 118 | } else { 119 | 120 | // Item is a struct, we can destruct it directly. 121 | static if (__traits(hasMember, RealT, "__xdtor")) { 122 | assumeNoGC(&obj_.__xdtor); 123 | } else static if (__traits(hasMember, RealT, "__dtor")) { 124 | assumeNoGC(&obj_.__dtor); 125 | } 126 | } 127 | } else static if (__traits(getLinkage, Unref!T) == "C++") { 128 | 129 | // C++ and Objective-C types may have D destructors declared 130 | // with extern(D), in that case, just call those. 131 | 132 | static if (__traits(hasMember, RealT, "__xdtor")) { 133 | assumeNoGC(&xdtor!T, obj_); 134 | } 135 | } else static if (__traits(hasMember, RealT, "__xdtor")) { 136 | 137 | // Item is liekly a struct, we can destruct it directly. 138 | assumeNoGC(&xdtor!T, obj_); 139 | } 140 | } 141 | } else { 142 | 143 | // Item is a struct, we can destruct it directly. 144 | static if (__traits(hasMember, RealT, "__xdtor")) { 145 | assumeNoGC(&obj_.__xdtor); 146 | } else static if (__traits(hasMember, RealT, "__dtor")) { 147 | assumeNoGC(&obj_.__dtor); 148 | } 149 | } 150 | } 151 | 152 | static if (reInit) 153 | initializeAt(obj_); 154 | } 155 | 156 | /** 157 | Runs constructor for the memory at dst 158 | */ 159 | void emplace(T, UT, Args...)(ref UT dst, auto ref Args args) @nogc { 160 | static if(__VERSION__ >= 2111) { 161 | static if (is(T == class)) { 162 | 163 | // NOTE: Since we need to handle inner-classes 164 | // we need to initialize here instead of next to the ctor. 165 | initializeAt(dst); 166 | 167 | // Shove it in a mixin, that more or less prevents the compiler 168 | // complaining. 169 | mixin(q{ dst = new(nu_storageT(dst)) T(args); }); 170 | } else static if (args.length == 0) { 171 | static assert(is(typeof({static T i;})), 172 | "Cannot emplace a " ~ T.stringof ~ ", its constructor is marked with @disable."); 173 | 174 | initializeAt(dst); 175 | } else { 176 | 177 | T tmp; 178 | mixin(q{ dst = *(new(tmp) T(args)); }); 179 | } 180 | } else { 181 | enum isConstructibleOther = 182 | (!is(T == struct) && Args.length == 1) || // Primitives, enums, arrays. 183 | (Args.length == 1 && is(typeof({T t = forward!(args[0]); }))) || // Conversions 184 | is(typeof(T(forward!args))); // General constructors. 185 | 186 | static if (is(T == class)) { 187 | 188 | static assert(!__traits(isAbstractClass, T), 189 | T.stringof ~ " is abstract and can't be emplaced."); 190 | 191 | // NOTE: Since we need to handle inner-classes 192 | // we need to initialize here instead of next to the ctor. 193 | initializeAt(dst); 194 | 195 | static if (isInnerClass!T) { 196 | static assert(Args.length > 0, 197 | "Initializing an inner class requires a pointer to the outer class"); 198 | 199 | static assert(is(Args[0] : typeof(T.outer)), 200 | "The first argument must be a pointer to the outer class"); 201 | 202 | chunk.outer = args[0]; 203 | alias fargs = args[1..$]; 204 | alias fargsT = Args[1..$]; 205 | 206 | } else { 207 | alias fargs = args; 208 | alias fargsT = Args; 209 | } 210 | 211 | static if (is(typeof(dst.__ctor(forward!fargs)))) { 212 | dst.__ctor(forward!args); 213 | } else { 214 | static assert(fargs.length == 0 && !is(typeof(&T.__ctor)), 215 | "No constructor for " ~ T.stringof ~ " found matching arguments "~fargsT.stringof~"!"); 216 | } 217 | 218 | } else static if (args.length == 0) { 219 | 220 | static assert(is(typeof({static T i;})), 221 | "Cannot emplace a " ~ T.stringof ~ ", its constructor is marked with @disable."); 222 | initializeAt(dst); 223 | } else static if (isConstructibleOther) { 224 | 225 | // Handler struct which forwards construction 226 | // to the payload. 227 | static struct S { 228 | T payload; 229 | this()(auto ref Args args) { 230 | static if (__traits(compiles, payload = forward!args)) 231 | payload = forward!args; 232 | else 233 | payload = T(forward!args); 234 | } 235 | } 236 | 237 | if (__ctfe) { 238 | static if (__traits(compiles, dst = T(forward!args))) 239 | dst = T(forward!args); 240 | else static if(args.length == 1 && __traits(compiles, dst = forward!(args[0]))) 241 | dst = forward!(args[0]); 242 | else static assert(0, 243 | "Can't emplace " ~ T.stringof ~ " at compile-time using " ~ Args.stringof ~ "."); 244 | } else { 245 | S* p = cast(S*)cast(void*)&dst; 246 | static if (UT.sizeof > 0) 247 | initializeAt(*p); 248 | 249 | p.__ctor(forward!args); 250 | } 251 | } else static if (is(typeof(dst.__ctor(forward!args)))) { 252 | 253 | initializeAt(dst); 254 | chunk.__ctor(forward!args); 255 | } else { 256 | static assert(!(Args.length == 1 && is(Args[0] : T)), 257 | "Can't emplace a " ~ T.stringof ~ " because the postblit is disabled."); 258 | 259 | static assert(0, 260 | "No constructor for " ~ T.stringof ~ " found matching arguments "~fargs.stringof~"!"); 261 | } 262 | } 263 | } 264 | 265 | /// ditto 266 | void emplace(UT, Args...)(auto ref UT dst, auto ref Args args) @nogc { 267 | emplace!(UT, UT, Args)(dst, forward!args); 268 | } 269 | 270 | /** 271 | Copies source to target. 272 | */ 273 | void __copy(S, T)(ref S source, ref T target) @nogc @system { 274 | static if (is(T == struct)) { 275 | static if (!__traits(hasCopyConstructor, T)) 276 | __blit(target, source); 277 | 278 | static if (hasElaborateCopyConstructor!T) 279 | __copy_postblit(source, target); 280 | } else static if (is(T == E[n], E, size_t n)) { 281 | 282 | // Some kind of array or range. 283 | static if (hasElaborateCopyConstructor!E) { 284 | size_t i; 285 | try { 286 | for(i = 0; i < n; i++) 287 | __copy(source[i], target[i]); 288 | } catch(Exception ex) { 289 | while(i--) { 290 | auto ref_ = const_cast!(Unconst!(E)*)(&target[i]); 291 | destruct(ref_); 292 | nu_free(cast(void*)ref_); 293 | } 294 | throw e; 295 | } 296 | } else static if (!__traits(hasCopyConstructor, T)) 297 | __blit(target, source); 298 | } else { 299 | *(const_cast!(Unconst!(T)*)(&target)) = *const_cast!(Unconst!(T)*)(&source); 300 | } 301 | } 302 | 303 | /** 304 | Moves $(D source) to $(D target), via destructive copy if neccesary. 305 | 306 | $(D source) will be reset to its init state after the move. 307 | */ 308 | void __move(S, T)(ref S source, ref T target) @nogc @trusted { 309 | static if (is(T == struct) && hasElaborateDestructor!T) { 310 | if(&source is &target) 311 | return; 312 | 313 | destruct!(T, false)(target); 314 | } 315 | 316 | return __moveImpl(source, target); 317 | } 318 | 319 | /// ditto 320 | T __move(T)(ref return scope T source) @nogc @trusted { 321 | T target = void; 322 | __moveImpl(source, target); 323 | return target; 324 | } 325 | 326 | private 327 | pragma(inline, true) 328 | void __moveImpl(S, T)(ref S source, ref T target) @nogc @system { 329 | static if(is(T == struct)) { 330 | assert(&source !is &target, "Source and target must not be identical"); 331 | __blit(target, source); 332 | 333 | static if (hasElaborateMove!T) 334 | __move_postblit(target, source); 335 | 336 | // If the source defines a destructor or a postblit the type needs to be 337 | // obliterated to avoid double frees and undue aliasing. 338 | static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T) { 339 | initializeAtNoCtx(source); 340 | } 341 | } else static if (is(T == E[n], E, size_t n)) { 342 | static if (!hasElaborateMove!T && 343 | !hasElaborateDestructor!T && 344 | !hasElaborateCopyConstructor!T) { 345 | 346 | assert(source.ptr !is target.ptr, "Source and target must not be identical"); 347 | __blit(target, source); 348 | initializeAt(source); 349 | } else { 350 | foreach(i; 0..source.length) { 351 | __move(source[i], target[i]); 352 | } 353 | } 354 | } else { 355 | target = source; 356 | initializeAt(source); 357 | } 358 | } 359 | 360 | /** 361 | Blits instance $(D from) to location $(D to). 362 | 363 | Effectively this acts as a simple memory copy, 364 | a postblit needs to be run after to finalize the object. 365 | */ 366 | pragma(inline, true) 367 | void __blit(T)(ref T to, ref T from) @nogc @system nothrow { 368 | nu_memcpy(const_cast!(Unqual!T*)(&to), const_cast!(Unqual!T*)(&from), AllocSize!T); 369 | } 370 | 371 | /** 372 | Runs postblit operations for a copy operation. 373 | */ 374 | pragma(inline, true) 375 | void __copy_postblit(S, T)(ref S source, ref T target) @nogc @system { 376 | static if (__traits(hasPostblit, T)) { 377 | dst.__xpostblit(); 378 | } else static if (__traits(hasCopyConstructor, T)) { 379 | 380 | // https://issues.dlang.org/show_bug.cgi?id=22766 381 | initializeAt(target); 382 | 383 | // Copy context pointer if needed. 384 | static if (__traits(isNested, T)) 385 | *(cast(void**)&target.tupleof[$-1]) = cast(void*) source.tupleof[$-1]; 386 | 387 | // Invoke copy ctor. 388 | target.__ctor(source); 389 | } 390 | } 391 | 392 | /** 393 | Ported from D runtime, this function is released under the boost license. 394 | 395 | Recursively calls the $(D opPostMove) callbacks of a struct and its members if 396 | they're defined. 397 | 398 | When moving a struct instance, the compiler emits a call to this function 399 | after blitting the instance and before releasing the original instance's 400 | memory. 401 | 402 | Params: 403 | newLocation = reference to struct instance being moved into 404 | oldLocation = reference to the original instance 405 | 406 | Notes: 407 | $(D __move_postblit) will do nothing if the type does not support elaborate moves. 408 | */ 409 | pragma(inline, true) 410 | void __move_postblit(T)(ref T newLocation, ref T oldLocation) @nogc @system { 411 | static if (is(T == struct)) { 412 | 413 | // Call __move_postblit for all members which have move semantics. 414 | static foreach(i, M; typeof(T.tupleof)) { 415 | static if (hasElaborateMove!T) { 416 | __move_postblit(newLocation.tupleof[i], oldLocation.tupleof[i]); 417 | } 418 | } 419 | 420 | static if (__traits(hasMember, T, "opPostMove")) { 421 | static assert(is(typeof(T.init.opPostMove(lvalueOf!T))) && 422 | !is(typeof(T.init.opPostMove(rvalueOf!T))), 423 | "`" ~ T.stringof ~ ".opPostMove` must take exactly one argument of type `" ~ T.stringof ~ "` by reference"); 424 | 425 | newLocation.opPostMove(oldLocation); 426 | } 427 | } else static if (__traits(isStaticArray, T)) { 428 | static if (T.length && hasElaborateMove!(typeof(newLocation[0]))) { 429 | foreach(i; 0..T.length) 430 | __move_postblit(newLocation[i], oldLocation[i]); 431 | } 432 | } 433 | } 434 | 435 | /** 436 | Forwards function arguments while keeping $(D out), $(D ref), and $(D lazy) on 437 | the parameters. 438 | 439 | Params: 440 | args = a parameter list or an $(REF AliasSeq,std,meta). 441 | 442 | Returns: 443 | An $(D AliasSeq) of $(D args) with $(D out), $(D ref), and $(D lazy) saved. 444 | */ 445 | template forward(args...) 446 | { 447 | import core.internal.traits : AliasSeq; 448 | import numem.object; 449 | 450 | template fwd(alias arg) 451 | { 452 | // by ref || lazy || const/immutable 453 | static if (__traits(isRef, arg) || 454 | __traits(isOut, arg) || 455 | __traits(isLazy, arg) || 456 | !is(typeof(__move(arg)))) 457 | alias fwd = arg; 458 | // (r)value 459 | else 460 | @property auto fwd() 461 | { 462 | version (DigitalMars) { /* @@BUG 23890@@ */ } else pragma(inline, true); 463 | return __move(arg); 464 | } 465 | } 466 | 467 | alias Result = AliasSeq!(); 468 | static foreach (arg; args) 469 | Result = AliasSeq!(Result, fwd!arg); 470 | static if (Result.length == 1) 471 | alias forward = Result[0]; 472 | else 473 | alias forward = Result; 474 | } 475 | 476 | /** 477 | UDA which allows specifying which functions numem should call when 478 | destroying an object with $(D destruct). 479 | */ 480 | struct nu_destroywith(alias handlerFunc) { 481 | private: 482 | alias Handler = handlerFunc; 483 | } 484 | 485 | /** 486 | UDA which allows specifying which functions numem should call when 487 | autoreleasing an object with $(D nu_autorelease). 488 | */ 489 | struct nu_autoreleasewith(alias handlerFunc) { 490 | private: 491 | alias Handler = handlerFunc; 492 | } 493 | 494 | /** 495 | Adds the given item to the topmost auto release pool. 496 | 497 | Params: 498 | item = The item to automatically be destroyed when the pool 499 | goes out of scope. 500 | 501 | Returns: 502 | $(D true) if the item was successfully added to the pool, 503 | $(D false) otherwise. 504 | */ 505 | bool nu_autorelease(T)(T item) @trusted @nogc { 506 | static if (isValidObjectiveC!T) { 507 | item.autorelease(); 508 | } else { 509 | alias autoreleaseWith = nu_getautoreleasewith!T; 510 | 511 | if (nu_arpool_stack.length > 0) { 512 | nu_arpool_stack[$-1].push( 513 | nu_arpool_element( 514 | cast(void*)item, 515 | (void* obj) { 516 | T obj_ = cast(T)obj; 517 | static if (is(typeof(autoreleaseWith))) { 518 | autoreleaseWith(obj_); 519 | } else { 520 | nogc_delete!(T)(obj_); 521 | } 522 | } 523 | ) 524 | ); 525 | return true; 526 | } 527 | return false; 528 | } 529 | } 530 | 531 | /** 532 | Pushes an auto release pool onto the pool stack. 533 | 534 | Returns: 535 | A context pointer, meaning is arbitrary. 536 | 537 | Memorysafety: 538 | $(D nu_autoreleasepool_push) and $(D nu_autoreleasepool_pop) are internal 539 | API and are not safely used outside of the helpers. 540 | 541 | See_Also: 542 | $(D numem.lifetime.autoreleasepool_scope), 543 | $(D numem.lifetime.autoreleasepool) 544 | */ 545 | void* nu_autoreleasepool_push() @system @nogc { 546 | nu_arpool_stack.nu_resize(nu_arpool_stack.length+1); 547 | nogc_construct(nu_arpool_stack[$-1]); 548 | 549 | if (nuopt_autoreleasepool_push) 550 | nu_arpool_stack[$-1].fctx = nuopt_autoreleasepool_push(); 551 | 552 | 553 | return cast(void*)&nu_arpool_stack[$-1]; 554 | } 555 | 556 | /** 557 | Pops an auto release pool from the pool stack. 558 | 559 | Params: 560 | ctx = A context pointer. 561 | 562 | Memorysafety: 563 | $(D nu_autoreleasepool_push) and $(D nu_autoreleasepool_pop) are internal 564 | API and are not safely used outside of the helpers. 565 | 566 | See_Also: 567 | $(D numem.lifetime.autoreleasepool_scope), 568 | $(D numem.lifetime.autoreleasepool) 569 | */ 570 | void nu_autoreleasepool_pop(void* ctx) @system @nogc { 571 | if (nu_arpool_stack.length > 0) { 572 | assert(ctx == &nu_arpool_stack[$-1], "Misaligned auto release pool sequence!"); 573 | 574 | nu_arpool_stack.nu_resize(cast(ptrdiff_t)nu_arpool_stack.length-1); 575 | if (nuopt_autoreleasepool_pop) 576 | nuopt_autoreleasepool_pop(ctx); 577 | } 578 | } 579 | 580 | 581 | // 582 | // INTERNAL 583 | // 584 | 585 | private: 586 | import numem.object : NuRefCounted; 587 | 588 | // auto-release pool stack. 589 | __gshared nu_arpool_ctx[] nu_arpool_stack; 590 | 591 | // Handler function type. 592 | alias nu_arpool_handler_t = void function(void*) @nogc; 593 | 594 | // Handlers within an auto-release pool context. 595 | struct nu_arpool_element { 596 | void* ptr; 597 | nu_arpool_handler_t handler; 598 | } 599 | 600 | // An autorelease pool context. 601 | struct nu_arpool_ctx { 602 | @nogc: 603 | nu_arpool_element[] queue; 604 | void* fctx; 605 | 606 | ~this() { 607 | foreach_reverse(ref item; queue) { 608 | item.handler(item.ptr); 609 | nogc_initialize(item); 610 | } 611 | queue.nu_resize(0); 612 | } 613 | 614 | void push(nu_arpool_element element) { 615 | queue.nu_resize(queue.length+1); 616 | queue[$-1] = element; 617 | } 618 | } 619 | 620 | // DESTROY UDA 621 | 622 | template nu_getdestroywith(T, A...) { 623 | static if (is(typeof(__traits(getAttributes, Unref!T)))) { 624 | static if (A.length == 0) { 625 | alias attrs = __traits(getAttributes, Unref!T); 626 | 627 | static if (attrs.length > 0) 628 | alias nu_getdestroywith = nu_getdestroywith!(Unref!T, attrs); 629 | else 630 | alias nu_getdestroywith = void; 631 | } else static if (A.length == 1) { 632 | static if (nu_isdestroywith!(T, A[0])) 633 | alias nu_getdestroywith = A[0].Handler; 634 | else 635 | alias nu_getdestroywith = void; 636 | } else static if (nu_isdestroywith!(T, A[0])) 637 | alias nu_getdestroywith = A[0].Handler; 638 | else 639 | alias nu_getdestroywith = nu_getdestroywith!(T, A[1 .. $]); 640 | } else { 641 | alias nu_getdestroywith = void; 642 | } 643 | } 644 | 645 | enum nu_isdestroywith(T, alias H) = 646 | __traits(identifier, H) == __traits(identifier, nu_destroywith) && 647 | is(typeof(H.Handler)) && 648 | is(typeof((H.Handler(lvalueOf!T)))); 649 | 650 | // AUTORELEASE UDA 651 | 652 | template nu_getautoreleasewith(T, A...) { 653 | static if (is(typeof(__traits(getAttributes, Unref!T)))) { 654 | static if (A.length == 0) { 655 | alias attrs = __traits(getAttributes, Unref!T); 656 | 657 | static if (attrs.length > 0) 658 | alias nu_getautoreleasewith = nu_getautoreleasewith!(Unref!T, attrs); 659 | else 660 | alias nu_getautoreleasewith = void; 661 | } else static if (A.length == 1) { 662 | static if (nu_isautoreleasewith!(T, A[0])) 663 | alias nu_getautoreleasewith = A[0].Handler; 664 | else 665 | alias nu_getautoreleasewith = void; 666 | } else static if (nu_isautoreleasewith!(T, A[0])) 667 | alias nu_getautoreleasewith = A[0].Handler; 668 | else 669 | alias nu_getautoreleasewith = nu_getautoreleasewith!(T, A[1 .. $]); 670 | } else { 671 | alias nu_getautoreleasewith = void; 672 | } 673 | } 674 | 675 | enum nu_isautoreleasewith(T, alias H) = 676 | __traits(identifier, H) == __traits(identifier, nu_autoreleasewith) && 677 | is(typeof(H.Handler)) && 678 | is(typeof((H.Handler(lvalueOf!T)))); -------------------------------------------------------------------------------- /source/numem/core/memory.d: -------------------------------------------------------------------------------- 1 | /** 2 | Additional Memory Handling. 3 | 4 | Functions that build on the numem hooks to implement higher level memory 5 | managment functionality. 6 | 7 | Copyright: 8 | Copyright © 2023-2025, Kitsunebi Games 9 | Copyright © 2023-2025, Inochi2D Project 10 | 11 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 12 | Authors: Luna Nielsen, Guillaume Piolat 13 | */ 14 | module numem.core.memory; 15 | import numem.core.hooks; 16 | import numem.core.traits; 17 | import numem.core.atomic; 18 | import numem.core.traits : AllocSize, isPointer; 19 | 20 | /** 21 | System pointer size. 22 | */ 23 | enum size_t ALIGN_PTR_SIZE = (void*).sizeof; 24 | 25 | /** 26 | Allocates enough memory to contain Type T. 27 | 28 | Returns: 29 | Newly allocated memory or $(D null) on failure. 30 | To avoid a memory leak, free the memory with $(D nu_free). 31 | 32 | Notes: 33 | Given the implementation of $(D nu_malloc) and $(D nu_free) may be 34 | independent of the libc allocator, memory allocated with 35 | $(D nu_malloc) should $(B always) be freed with $(D nu_free)! 36 | */ 37 | ref void[AllocSize!T] nu_mallocT(T)() @nogc nothrow @trusted { 38 | return nu_malloc(AllocSize!T)[0..AllocSize!T]; 39 | } 40 | 41 | /** 42 | Gets the storage space used by $(D object). 43 | 44 | Params: 45 | object = The object to get the storage space of. 46 | 47 | Returns: 48 | The storage of the provided object; cast to a static 49 | void array reference. 50 | */ 51 | ref void[AllocSize!T] nu_storageT(T)(ref T object) @nogc nothrow @trusted { 52 | static if (AllocSize!T == T.sizeof) 53 | return object; 54 | else { 55 | return (cast(void*)object)[0..AllocSize!T]; 56 | } 57 | } 58 | 59 | /** 60 | Resizes a slice to be of the given size and alignment. 61 | If the slice is not yet allocated, it will be. 62 | 63 | When creating a slice with complex types you may wish to chain the resize 64 | operation with $(D numem.lifetime.nogc_initialize). 65 | 66 | Set $(D length) to $(D 0) to free the buffer. 67 | 68 | Params: 69 | buffer = The buffer to resize. 70 | length = The length of the buffer (in elements.) 71 | alignment = The alignment of the buffer (in bytes.) 72 | 73 | Notes: 74 | $(UL 75 | $(LI 76 | Resizing the buffer to be smaller than it was originally will 77 | cause the elements to be deleted; if, and only if the type 78 | is an aggregate value type (aka. $(D struct) or $(D union)) 79 | and said type has an elaborate destructor. 80 | ) 81 | $(LI 82 | Class pointers will NOT be deleted, this must be done 83 | manually. 84 | ) 85 | ) 86 | 87 | Threadsafety: 88 | The underlying data will, if possible be updated atomically, however 89 | this does $(B NOT) prevent you from accessing stale references 90 | elsewhere. If you wish to access a slice across threads, you should 91 | use synchronisation primitives such as a mutex. 92 | 93 | Returns: 94 | The resized buffer. 95 | */ 96 | ref T[] nu_resize(T)(ref T[] buffer, size_t length, int alignment = 1) @nogc { 97 | static if (!isObjectiveC!T && hasElaborateDestructor!T && !isHeapAllocated!T) { 98 | import numem.lifetime : nogc_delete; 99 | 100 | if (length < buffer.length) { 101 | 102 | // Handle destructor invocation. 103 | nogc_delete!(T, false)(buffer[length..buffer.length]); 104 | 105 | // Handle buffer deletion. 106 | if (length == 0) { 107 | if (buffer.length > 0) 108 | nu_aligned_free(cast(void*)buffer.ptr, alignment); 109 | 110 | buffer = null; 111 | return buffer; 112 | } 113 | } 114 | 115 | } else { 116 | 117 | // No destructors, just free normally. 118 | if (length == 0) { 119 | if (buffer.length > 0) 120 | nu_aligned_free(cast(void*)buffer.ptr, alignment); 121 | 122 | buffer = null; 123 | return buffer; 124 | } 125 | } 126 | 127 | T* ptr = cast(T*)nu_aligned_realloc(cast(void*)buffer.ptr, T.sizeof * length, alignment); 128 | buffer = ptr !is null ? ptr[0..length] : null; 129 | return buffer; 130 | } 131 | 132 | /** 133 | Creates a shallow duplicate of the given buffer. 134 | 135 | Params: 136 | buffer = Buffer to duplicate. 137 | 138 | Memorysafety: 139 | This function copies data out of the string into a new 140 | memory allocation; as such it has to be freed. 141 | It is otherwise safe, in that it won't modify 142 | the original memory provided. 143 | 144 | Returns: 145 | Duplicated slice, must be freed with $(D nu_resize) 146 | */ 147 | inout(T)[] nu_dup(T)(inout(T)[] buffer) @nogc @trusted { 148 | T[] buf; 149 | 150 | buf.nu_resize(buffer.length); 151 | nu_memcpy(cast(void*)buf.ptr, cast(void*)buffer.ptr, buf.length*T.sizeof); 152 | return cast(inout(T)[])buf; 153 | } 154 | 155 | /** 156 | Creates a shallow immutable duplicate of the given buffer. 157 | 158 | Params: 159 | buffer = Buffer to duplicate. 160 | 161 | Memorysafety: 162 | This function copies data out of the slice into a new 163 | memory allocation; as such it has to be freed. 164 | It is otherwise safe, in that it won't modify 165 | the original memory provided. 166 | 167 | Returns: 168 | Duplicated slice, must be freed with $(D nu_resize) 169 | */ 170 | immutable(T)[] nu_idup(T)(inout(T)[] buffer) @nogc @trusted { 171 | return cast(immutable(T)[])nu_dup(buffer); 172 | } 173 | 174 | /** 175 | Appends a null terminator at the end of the string, 176 | resizes the memory allocation if need be. 177 | 178 | Params: 179 | text = string to add a null-terminator to, in-place 180 | 181 | Memorysafety: 182 | This function is not memory safe, in that if you attempt 183 | to use it on string literals it may lead to memory corruption 184 | or crashes. This is meant to be used internally. It may reallocate 185 | the underlying memory of the provided string, as such all prior 186 | string references should be assumed to be invalid. 187 | 188 | Returns: 189 | Slice of the null-terminated string, the null terminator is hidden. 190 | */ 191 | inout(T)[] nu_terminate(T)(ref inout(T)[] text) @nogc @system 192 | if (is(T == char) || is(T == wchar) || is(T == dchar)) { 193 | 194 | // Early escape, empty string. 195 | if (text.length == 0) 196 | return text; 197 | 198 | // Early escape out, already terminated. 199 | if (text[$-1] == '\0') 200 | return text[0..$-1]; 201 | 202 | size_t termOffset = text.length; 203 | 204 | // Resize by 1, add null terminator. 205 | // Sometimes this won't be needed, if extra memory was 206 | // already allocated. 207 | text.nu_resize(text.length+1); 208 | (cast(T*)text.ptr)[termOffset] = '\0'; 209 | text = text[0..$-1]; 210 | 211 | // Return length _without_ null terminator by slicing it out. 212 | // The memory allocation is otherwise still the same. 213 | return text; 214 | } 215 | 216 | /** 217 | Gets whether 2 memory ranges are overlapping. 218 | 219 | Params: 220 | a = Start address of first range. 221 | aLength = Length of first range, in bytes. 222 | b = Start address of second range. 223 | bLength = Length of second range, in bytes. 224 | 225 | Returns: 226 | $(D true) if range $(D a) and $(D b) overlaps, $(D false) otherwise. 227 | It is assumed that start points at $(D null) and lengths of $(D 0) never 228 | overlap. 229 | 230 | Examples: 231 | --- 232 | int[] arr1 = [1, 2, 3, 4]; 233 | int[] arr2 = arr1[1..$-1]; 234 | 235 | size_t arr1len = arr1.length*int.sizeof; 236 | size_t arr2len = arr2.length*int.sizeof; 237 | 238 | // Test all iterations that are supported. 239 | assert(nu_is_overlapping(arr1.ptr, arr1len, arr2.ptr, arr2len)); 240 | assert(!nu_is_overlapping(arr1.ptr, arr1len, arr2.ptr, 0)); 241 | assert(!nu_is_overlapping(arr1.ptr, 0, arr2.ptr, arr2len)); 242 | assert(!nu_is_overlapping(null, arr1len, arr2.ptr, arr2len)); 243 | assert(!nu_is_overlapping(arr1.ptr, arr1len, null, arr2len)); 244 | --- 245 | 246 | */ 247 | export 248 | extern(C) 249 | bool nu_is_overlapping(void* a, size_t aLength, void* b, size_t bLength) @nogc nothrow { 250 | 251 | // Early exit, null don't overlap. 252 | if (a is null || b is null) 253 | return false; 254 | 255 | // Early exit, no length. 256 | if (aLength == 0 || bLength == 0) 257 | return false; 258 | 259 | void* aEnd = a+aLength; 260 | void* bEnd = b+bLength; 261 | 262 | // Overlap occurs if src is within [dst..dstEnd] 263 | // or dst is within [src..srcEnd] 264 | if (a >= b && a < bEnd) 265 | return true; 266 | 267 | if (b >= a && b < aEnd) 268 | return true; 269 | 270 | return false; 271 | } 272 | 273 | /** 274 | Gets the amount of bytes to allocate when requesting a specific amount of memory 275 | with a given alignment. 276 | 277 | Params: 278 | request = How many bytes to allocate 279 | alignment = The alignment of the requested allocation, 280 | in bytes. 281 | 282 | Returns: 283 | $(D request) aligned to $(D alignment), taking in to account 284 | pointer alignment requirements. 285 | */ 286 | export 287 | extern(C) 288 | size_t nu_aligned_size(size_t request, size_t alignment) nothrow @nogc @safe pure { 289 | return request + alignment - 1 + ALIGN_PTR_SIZE * 2; 290 | } 291 | 292 | /** 293 | Realigns $(D ptr) to the next increment of $(D alignment) 294 | 295 | Params: 296 | ptr = A pointer 297 | alignment = The alignment to adjust the pointer to. 298 | 299 | Returns: 300 | The next aligned pointer. 301 | */ 302 | export 303 | extern(C) 304 | void* nu_realign(void* ptr, size_t alignment) nothrow @nogc @trusted pure { 305 | return ptr+(cast(size_t)ptr % alignment); 306 | } 307 | 308 | /** 309 | Gets whether $(D ptr) is aligned to $(D alignment) 310 | 311 | Params: 312 | ptr = A pointer 313 | alignment = The alignment to compare the pointer to. 314 | 315 | Returns: 316 | Whether $(D ptr) is aligned to $(D alignment) 317 | */ 318 | export 319 | extern(C) 320 | bool nu_is_aligned(void* ptr, size_t alignment) nothrow @nogc @trusted pure { 321 | return (cast(size_t)ptr & (alignment-1)) == 0; 322 | } 323 | 324 | /** 325 | Allocates memory with a given alignment. 326 | 327 | Params: 328 | size = The size of the allocation, in bytes. 329 | alignment = The alignment of the allocation, in bytes. 330 | 331 | Returns: 332 | A new aligned pointer, $(D null) on failure. 333 | 334 | See_Also: 335 | $(D nu_aligned_realloc) 336 | $(D nu_aligned_free) 337 | */ 338 | export 339 | extern(C) 340 | void* nu_aligned_alloc(size_t size, size_t alignment) nothrow @nogc { 341 | assert(alignment != 0); 342 | 343 | // Shortcut for tight alignment. 344 | if (alignment == 1) 345 | return nu_malloc(size); 346 | 347 | size_t request = nu_aligned_size(size, alignment); 348 | void* raw = nu_malloc(request); 349 | 350 | return __nu_store_aligned_ptr(raw, size, alignment); 351 | } 352 | 353 | /** 354 | Reallocates memory with a given alignment. 355 | 356 | Params: 357 | ptr = Pointer to prior allocation made with $(D nu_aligned_alloc) 358 | size = The size of the allocation, in bytes. 359 | alignment = The alignment of the allocation, in bytes. 360 | 361 | Returns: 362 | The address of the pointer after the reallocation, $(D null) on failure. 363 | 364 | Notes: 365 | The alignment provided $(B HAS) to match the original alignment of $(D ptr) 366 | 367 | See_Also: 368 | $(D nu_aligned_alloc) 369 | $(D nu_aligned_free) 370 | */ 371 | export 372 | extern(C) 373 | void* nu_aligned_realloc(void* ptr, size_t size, size_t alignment) nothrow @nogc { 374 | return __nu_aligned_realloc!true(ptr, size, alignment); 375 | } 376 | 377 | /** 378 | Reallocates memory with a given alignment. 379 | 380 | Params: 381 | ptr = Pointer to prior allocation made with $(D nu_aligned_alloc) 382 | size = The size of the allocation, in bytes. 383 | alignment = The alignment of the allocation, in bytes. 384 | 385 | Returns: 386 | The address of the pointer after the reallocation, $(D null) on failure. 387 | 388 | Notes: 389 | The alignment provided $(B HAS) to match the original alignment of $(D ptr) 390 | 391 | See_Also: 392 | $(D nu_aligned_alloc) 393 | $(D nu_aligned_free) 394 | */ 395 | export 396 | extern(C) 397 | void* nu_aligned_realloc_destructive(void* ptr, size_t size, size_t alignment) nothrow @nogc { 398 | return __nu_aligned_realloc!false(ptr, size, alignment); 399 | } 400 | 401 | /** 402 | Frees aligned memory. 403 | 404 | Params: 405 | ptr = Pointer to prior allocation made with $(D nu_aligned_alloc) 406 | alignment = The alignment of the allocation, in bytes. 407 | 408 | See_Also: 409 | $(D nu_aligned_alloc) 410 | $(D nu_aligned_realloc) 411 | */ 412 | export 413 | extern(C) 414 | void nu_aligned_free(void* ptr, size_t alignment) nothrow @nogc { 415 | 416 | // Handle null case. 417 | if (!ptr) 418 | return; 419 | 420 | // Handle unaligned memory. 421 | if (alignment == 1) 422 | return nu_free(ptr); 423 | 424 | assert(alignment != 0); 425 | assert(nu_is_aligned(ptr, alignment)); 426 | 427 | void** rawLocation = cast(void**)(ptr - ALIGN_PTR_SIZE); 428 | nu_free(*rawLocation); 429 | } 430 | 431 | private 432 | void* __nu_store_aligned_ptr(void* ptr, size_t size, size_t alignment) nothrow @nogc { 433 | 434 | // Handle null case. 435 | if (!ptr) 436 | return null; 437 | 438 | void* start = ptr + ALIGN_PTR_SIZE * 2; 439 | void* aligned = nu_realign(start, alignment); 440 | 441 | // Update the location. 442 | void** rawLocation = cast(void**)(aligned - ALIGN_PTR_SIZE); 443 | nu_atomic_store_ptr(cast(void**)rawLocation, ptr); 444 | 445 | // Update the size. 446 | size_t* sizeLocation = cast(size_t*)(aligned - 2 * ALIGN_PTR_SIZE); 447 | nu_atomic_store_ptr(cast(void**)sizeLocation, cast(void*)size); 448 | 449 | assert(nu_is_aligned(aligned, alignment)); 450 | return aligned; 451 | } 452 | 453 | private 454 | void* __nu_aligned_realloc(bool preserveIfResized)(void* aligned, size_t size, size_t alignment) nothrow @nogc { 455 | 456 | // Use normal realloc if there's no alignment. 457 | if (alignment == 1) 458 | return nu_realloc(aligned, size); 459 | 460 | // Create if doesn't exist. 461 | if (aligned is null) 462 | return nu_aligned_alloc(size, alignment); 463 | 464 | assert(alignment != 0); 465 | assert(nu_is_aligned(aligned, alignment)); 466 | 467 | size_t prevSize = *cast(size_t*)(aligned - ALIGN_PTR_SIZE * 2); 468 | size_t prevRequest = nu_aligned_size(prevSize, alignment); 469 | size_t request = nu_aligned_size(size, alignment); 470 | 471 | // Ensure alignment matches. 472 | assert(prevRequest - request == prevSize - size); 473 | 474 | // Heuristic: if a requested size is within 50% to 100% of what is already allocated 475 | // then exit with the same pointer 476 | if ((prevRequest < request * 4) && (request <= prevRequest)) 477 | return aligned; 478 | 479 | void* newptr = nu_malloc(request); 480 | if (request > 0 && newptr is null) 481 | return null; 482 | 483 | void* newAligned = __nu_store_aligned_ptr(newptr, size, alignment); 484 | 485 | static if (preserveIfResized) { 486 | size_t minSize = size < prevSize ? size : prevSize; 487 | nu_memcpy(newAligned, aligned, minSize); 488 | } 489 | nu_aligned_free(aligned, alignment); 490 | 491 | assert(nu_is_aligned(newAligned, alignment)); 492 | return newAligned; 493 | } -------------------------------------------------------------------------------- /source/numem/core/meta.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem meta templates. 3 | 4 | Most of these are taken directly from the D runtime. 5 | 6 | Copyright: 7 | Copyright © 2005-2009, The D Language Foundation. 8 | Copyright © 2023-2025, Kitsunebi Games 9 | Copyright © 2023-2025, Inochi2D Project 10 | 11 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 12 | Authors: 13 | $(HTTP digitalmars.com, Walter Bright), 14 | $(HTTP klickverbot.at, David Nadlinger) 15 | Luna Nielsen 16 | */ 17 | module numem.core.meta; 18 | 19 | /** 20 | Equivalent to D runtime's AliasSeq. 21 | */ 22 | alias AliasSeq(AliasList...) = AliasList; 23 | 24 | /** 25 | A template which gets whether all the inputs satisfy the condition 26 | outlined in $(D F). 27 | */ 28 | template allSatisfy(alias F, T...) { 29 | static foreach(U; T) { 30 | static if (!is(typeof(allSatisfy) == bool) && is(F!U) && !F!(U)) 31 | enum allSatisfy = false; 32 | } 33 | 34 | static if (!is(typeof(allSatisfy) == bool)) 35 | enum allSatisfy = true; 36 | } 37 | 38 | /** 39 | A template which gets whether any of the inputs satisfy the 40 | condition outlined in $(D F). 41 | */ 42 | template anySatisfy(alias F, T...) { 43 | static foreach(U; T) { 44 | static if (!is(typeof(anySatisfy) == bool) && is(F!U) && F!U) 45 | enum anySatisfy = true; 46 | } 47 | 48 | static if (!is(typeof(anySatisfy) == bool)) 49 | enum anySatisfy = false; 50 | } 51 | 52 | /** 53 | Returns a sequence of F!(T[0]), F!(T[1]), ..., F!(T[$-1]) 54 | */ 55 | template staticMap(alias F, T...) { 56 | static if (T.length == 0) 57 | alias staticMap = AliasSeq!(); 58 | else static if (T.length == 1) 59 | alias staticMap = AliasSeq!(F!(T[0])); 60 | else static if (T.length == 2) 61 | alias staticMap = AliasSeq!(F!(T[0]), F!(T[1])); 62 | else static if (T.length == 3) 63 | alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2])); 64 | else static if (T.length == 4) 65 | alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3])); 66 | else static if (T.length == 5) 67 | alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4])); 68 | else static if (T.length == 6) 69 | alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5])); 70 | else static if (T.length == 7) 71 | alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6])); 72 | else static if (T.length == 8) 73 | alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6]), F!(T[7])); 74 | else { 75 | alias staticMap = 76 | AliasSeq!( 77 | staticMap!(F, T[ 0 .. $/2]), 78 | staticMap!(F, T[$/2 .. $ ])); 79 | } 80 | } 81 | 82 | /** 83 | Returns a sequence containing the provided sequence after filtering by $(D F). 84 | */ 85 | template Filter(alias F, T...) { 86 | alias Filter = AliasSeq!(); 87 | static foreach(Arg; T) { 88 | static if (F!Arg) 89 | Filter = AliasSeq!(Filter, Arg); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /source/numem/core/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem Core 3 | 4 | Copyright: 5 | Copyright © 2023-2025, Kitsunebi Games 6 | Copyright © 2023-2025, Inochi2D Project 7 | 8 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Authors: Luna Nielsen 10 | */ 11 | module numem.core; 12 | 13 | -------------------------------------------------------------------------------- /source/numem/core/traits.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem Traits 3 | 4 | Copyright: 5 | Copyright © 2005-2009, The D Language Foundation. 6 | Copyright © 2023-2025, Kitsunebi Games 7 | Copyright © 2023-2025, Inochi2D Project 8 | 9 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 10 | Authors: 11 | $(HTTP digitalmars.com, Walter Bright), 12 | Tomasz Stachowiak (isExpressions), 13 | $(HTTP erdani.org, Andrei Alexandrescu), 14 | Shin Fujishiro, 15 | $(HTTP octarineparrot.com, Robert Clipsham), 16 | $(HTTP klickverbot.at, David Nadlinger), 17 | Kenji Hara, 18 | Shoichi Kato 19 | Luna Nielsen 20 | */ 21 | module numem.core.traits; 22 | import numem.core.meta; 23 | 24 | /** 25 | Gets a sequence over all of the fields in type $(D T). 26 | 27 | If $(D T) is a type with no fields, returns a sequence containing the input. 28 | */ 29 | template Fields(T) { 30 | static if(is(T == struct) || is(T == union)) 31 | alias Fields = typeof(T.tupleof[0..$-__traits(isNested, T)]); 32 | else static if (is(T == class) || is(T == interface)) 33 | alias Fields = typeof(T.tupleof); 34 | else 35 | alias Fields = AliasSeq!T; 36 | } 37 | 38 | /** 39 | Gets the base element type of type $(D T). 40 | */ 41 | template BaseElemOf(T) { 42 | static if(is(OriginalType!T == E[N], E, size_t N)) 43 | alias BaseElemOf = BaseElemOf!E; 44 | else 45 | alias BaseElemOf = T; 46 | } 47 | 48 | /** 49 | Gets the original type of $(D T). 50 | */ 51 | template OriginalType(T) { 52 | template Impl(T) { 53 | static if(is(T U == enum)) alias Impl = OriginalType!U; 54 | else alias Impl = T; 55 | } 56 | 57 | alias OriginalType = ModifyTypePreservingTQ!(Impl, T); 58 | } 59 | 60 | /** 61 | Modifies type $(D T) to follow the predicate specified by $(D Modifier). 62 | */ 63 | template ModifyTypePreservingTQ(alias Modifier, T) { 64 | static if (is(T U == immutable U)) alias ModifyTypePreservingTQ = immutable Modifier!U; 65 | else static if (is(T U == shared inout const U)) alias ModifyTypePreservingTQ = shared inout const Modifier!U; 66 | else static if (is(T U == shared inout U)) alias ModifyTypePreservingTQ = shared inout Modifier!U; 67 | else static if (is(T U == shared const U)) alias ModifyTypePreservingTQ = shared const Modifier!U; 68 | else static if (is(T U == shared U)) alias ModifyTypePreservingTQ = shared Modifier!U; 69 | else static if (is(T U == inout const U)) alias ModifyTypePreservingTQ = inout const Modifier!U; 70 | else static if (is(T U == inout U)) alias ModifyTypePreservingTQ = inout Modifier!U; 71 | else static if (is(T U == const U)) alias ModifyTypePreservingTQ = const Modifier!U; 72 | else alias ModifyTypePreservingTQ = Modifier!T; 73 | } 74 | 75 | /** 76 | Removes const type qualifiers from $(D T). 77 | */ 78 | alias Unconst(T : const U, U) = U; 79 | 80 | /** 81 | Removes shared type qualifiers from $(D T). 82 | */ 83 | alias Unshared(T : shared U, U) = U; 84 | 85 | /** 86 | Removes all qualifiers from type T. 87 | */ 88 | template Unqual(T : const U, U) { 89 | static if(is(U == shared V, V)) 90 | alias Unqual = V; 91 | else 92 | alias Unqual = U; 93 | } 94 | 95 | /** 96 | Gets the reference type version of type $(D T). 97 | */ 98 | template Ref(T) { 99 | static if (is(T == class) || isHeapAllocated!T) 100 | alias Ref = T; 101 | else 102 | alias Ref = T*; 103 | } 104 | 105 | /** 106 | Gets the reference type version of type $(D T). 107 | */ 108 | template Unref(T) { 109 | static if (!isClasslike!T && isHeapAllocated!T) 110 | alias Unref = typeof(*T.init); 111 | else 112 | alias Unref = T; 113 | } 114 | 115 | /** 116 | Gets the amount of bytes needed to allocate an instance of type $(D T). 117 | */ 118 | template AllocSize(T) { 119 | static if (is(T == class) || is(T == interface)) 120 | enum AllocSize = __traits(classInstanceSize, T); 121 | else 122 | enum AllocSize = T.sizeof; 123 | } 124 | 125 | /** 126 | Gets the alignment of type $(D T) in bytes. 127 | */ 128 | template AllocAlign(T) { 129 | static if(is(T == class)) 130 | enum AllocAlign = __traits(classInstanceAlignment, T); 131 | else 132 | enum AllocAlign = T.alignof; 133 | } 134 | 135 | private struct __DummyStruct { } 136 | 137 | /** 138 | Returns the rvalue equivalent of T. 139 | */ 140 | @property T rvalueOf(T)(T val) { return val; } 141 | 142 | /** 143 | Returns the rvalue equivalent of $(D T). 144 | 145 | Can only be used at compile time for type checking. 146 | */ 147 | @property T rvalueOf(T)(inout __DummyStruct = __DummyStruct.init); 148 | 149 | /** 150 | Returns the lvalue equivalent of $(D T). 151 | 152 | Can only be used at compile time for type checking. 153 | */ 154 | @property ref T lvalueOf(T)(inout __DummyStruct = __DummyStruct.init); 155 | 156 | /** 157 | Gets whether $(D T) supports moving. 158 | */ 159 | enum isMovable(T) = 160 | (is(T == struct) || is(T == union)) || 161 | (__traits(isStaticArray, T) && hasElaborateMove!(T.init[0])); 162 | 163 | /** 164 | Gets whether $(D T) is an aggregate type (i.e. a type which contains other types) 165 | */ 166 | enum isAggregateType(T) = 167 | is(T == class) || is(T == interface) || 168 | is(T == struct) || is(T == union); 169 | 170 | /** 171 | Gets whether $(D T) is a class-like (i.e. class or interface) 172 | */ 173 | enum isClasslike(T) = 174 | is(T == class) || is(T == interface); 175 | 176 | /** 177 | Gets whether $(D T) is a struct-like (i.e. struct or union) 178 | */ 179 | enum isStructLike(T) = 180 | is(T == struct) || is(T == union); 181 | 182 | /** 183 | Gets whether the provided type is a scalar type. 184 | */ 185 | enum isScalarType(T) = __traits(isScalar, T) && is(T : real); 186 | 187 | /** 188 | Gets whether the provided type is a basic type. 189 | */ 190 | enum isBasicType(T) = isScalarType!T || is(immutable T == immutable void); 191 | 192 | /** 193 | Gets whether $(D T) is a pointer type. 194 | */ 195 | enum isPointer(T) = 196 | is(T == U*, U) && !isClasslike!T; 197 | 198 | /** 199 | Gets whether $(D T) is heap allocated. 200 | */ 201 | enum isHeapAllocated(T) = 202 | is(T == class) || is(T == U*, U); 203 | 204 | /** 205 | Gets whether type $(D T) is an array. 206 | */ 207 | enum isArray(T) = is(T == E[n], E, size_t n); 208 | 209 | /** 210 | Gets whether type T is a floating point type. 211 | */ 212 | enum isFloatingPoint(T) = __traits(isFloating, T); 213 | 214 | /** 215 | Gets whether type T is a integral point type. 216 | */ 217 | enum isIntegral(T) = __traits(isIntegral, T); 218 | 219 | /** 220 | Gets whether type T is a numeric type. 221 | */ 222 | enum isNumeric(T) = 223 | __traits(isFloating, T) && 224 | __traits(isIntegral, T); 225 | 226 | template FtoI(T) { 227 | static if (is(T == double)) 228 | alias FtoI = ulong; 229 | else static if (is(T == float)) 230 | alias FtoI = uint; 231 | else 232 | alias FtoI = size_t; 233 | } 234 | 235 | /** 236 | Gets whether $(D Lhs) can be assigned to $(D Rhs). 237 | */ 238 | template isAssignable(Lhs, Rhs = Lhs) { 239 | enum isAssignable = 240 | __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs) && 241 | __traits(compiles, lvalueOf!Lhs = lvalueOf!Rhs); 242 | } 243 | 244 | /** 245 | Gets whether $(D Lhs) can be assigned to $(D Rhs) or $(D Rhs) can be assigned to $(D Lhs). 246 | */ 247 | enum isAnyAssignable(Lhs, Rhs = Lhs) = 248 | isAssignable!(Lhs, Rhs) || isAssignable!(Rhs, Lhs); 249 | 250 | /** 251 | Gets whether the unqualified versions of $(D Lhs) and $(D Rhs) are in 252 | any way compatible in any direction. 253 | */ 254 | enum isAnyCompatible(Lhs, Rhs) = 255 | is(Unqual!Lhs : Unqual!Rhs) || is(Unqual!Rhs : Unqual!Lhs); 256 | 257 | 258 | /** 259 | Gets whether $(D symbol) has the user defined attribute $(D attrib). 260 | */ 261 | template hasUDA(alias symbol, alias attrib) { 262 | enum hasUDA = anySatisfy!(isDesiredAttr!attrib, __traits(getAttributes, symbol)); 263 | } 264 | 265 | /** 266 | Gets a sequence of all of the attributes within attrib. 267 | */ 268 | template getUDAs(alias symbol, alias attrib) { 269 | alias getUDAs = Filter!(isDesiredAttr!attrib, __traits(getAttributes, symbol)); 270 | } 271 | 272 | private 273 | template isDesiredAttr(alias attribute) { 274 | // Taken from phobos. 275 | 276 | template isDesiredAttr(alias toCheck) { 277 | static if (is(typeof(attribute)) && !__traits(isTemplate, attribute)) { 278 | static if (__traits(compiles, toCheck == attribute)) 279 | enum isDesiredAttr = toCheck == attribute; 280 | else 281 | enum isDesiredAttr = false; 282 | } else static if (is(typeof(toCheck))) { 283 | static if (__traits(isTemplate, attribute)) 284 | enum isDesiredAttr = isInstanceOf!(attribute, typeof(toCheck)); 285 | else 286 | enum isDesiredAttr = is(typeof(toCheck) == attribute); 287 | } else static if (__traits(isTemplate, attribute)) 288 | enum isDesiredAttr = isInstanceOf!(attribute, toCheck); 289 | else 290 | enum isDesiredAttr = is(toCheck == attribute); 291 | } 292 | } 293 | 294 | /** 295 | Gets whether $(D T) is an instance of template $(D S). 296 | 297 | Returns: 298 | $(D true) if $(D T) is an instance of template $(D S), 299 | $(D false) otherwise. 300 | */ 301 | enum bool isInstanceOf(alias S, T) = is(T == S!Args, Args...); 302 | template isInstanceOf(alias S, alias T) { 303 | enum impl(alias T : S!Args, Args...) = true; 304 | enum impl(alias T) = false; 305 | enum isInstanceOf = impl!T; 306 | } /// ditto 307 | 308 | /** 309 | Gets whether $(D T) is an Objective-C class or protocol. 310 | 311 | Additionally, said class or protocol needs to have the 312 | $(D retain) and $(D release) methods. 313 | */ 314 | enum isObjectiveC(T) = 315 | isClasslike!T && __traits(getLinkage, T) == "Objective-C"; 316 | 317 | /** 318 | Gets whether $(D T) is a *valid* NSObject derived 319 | Objective-C class or protocol. 320 | 321 | Said class or protocol needs to have the 322 | $(D retain), $(D release) and $(D autorelease) methods. 323 | 324 | See_Also: 325 | $(LINK2 https://github.com/Inochi2D/objective-d, Objective-D) 326 | */ 327 | enum isValidObjectiveC(T) = 328 | isClasslike!T && __traits(getLinkage, T) == "Objective-C" && 329 | is(typeof(T.retain)) && is(typeof(T.release)) && is(typeof(T.autorelease)); 330 | 331 | /** 332 | Gets whether T is an inner class in a nested class layout. 333 | */ 334 | template isInnerClass(T) if(is(T == class)) { 335 | static if (is(typeof(T.outer))) { 336 | template hasOuterMember(T...) { 337 | static if (T.length == 0) 338 | enum hasOuterMember = false; 339 | else 340 | enum hasOuterMember = T[0] == "outer" || hasOuterMember!(T[1..$]); 341 | } 342 | 343 | enum isInnerClass = __traits(isSame, typeof(T.outer), __traits(parent, T)) && !hasOuterMember!(__traits(allMembers, T)); 344 | } else enum isInnerClass = false; 345 | } 346 | 347 | /** 348 | Gets whether $(D T) or any of its children has an elaborate move. 349 | */ 350 | template hasElaborateMove(T) { 351 | static if (isObjectiveC!T) 352 | enum bool hasElaborateMove = false; 353 | else static if (__traits(isStaticArray, T)) 354 | enum bool hasElaborateMove = T.sizeof && hasElaborateMove!(BaseElemOf!T); 355 | else static if (is(T == struct)) 356 | enum hasElaborateMove = (is(typeof(S.init.opPostMove(lvalueOf!T))) && 357 | !is(typeof(S.init.opPostMove(rvalueOf!T)))) || 358 | anySatisfy!(.hasElaborateMove, Fields!T); 359 | else 360 | enum hasElaborateMove = false; 361 | 362 | } 363 | 364 | /** 365 | Gets whether type $(D T) has elaborate assign semantics 366 | (i.e. is $(D opAssign) declared for the type) 367 | */ 368 | template hasElaborateAssign(T) { 369 | static if (isObjectiveC!T) 370 | enum bool hasElaborateAssign = false; 371 | else static if (__traits(isStaticArray, T)) 372 | enum bool hasElaborateAssign = T.sizeof && hasElaborateAssign!(BaseElemOf!T); 373 | else static if (is(T == struct)) 374 | enum hasElaborateAssign = (is(typeof(S.init.opPostMove(opAssign!T))) && 375 | !is(typeof(S.init.opPostMove(opAssign!T)))) || 376 | anySatisfy!(.hasElaborateAssign, Fields!T); 377 | else 378 | enum hasElaborateAssign = false; 379 | 380 | } 381 | 382 | /** 383 | Gets whether type $(D T) has elaborate copy constructor semantics 384 | (i.e. is a copy constructor or postblit constructor declared.) 385 | */ 386 | template hasElaborateCopyConstructor(T) { 387 | static if (isObjectiveC!T) 388 | enum bool hasElaborateCopyConstructor = false; 389 | else static if (__traits(isStaticArray, T)) 390 | enum bool hasElaborateCopyConstructor = T.sizeof && hasElaborateCopyConstructor!(BaseElemOf!T); 391 | else static if (is(T == struct)) 392 | enum hasElaborateCopyConstructor = __traits(hasCopyConstructor, T) || __traits(hasPostblit, T); 393 | else 394 | enum hasElaborateCopyConstructor = false; 395 | } 396 | 397 | /** 398 | Gets whether type $(D T) has elaborate destructor semantics (is ~this() declared). 399 | */ 400 | template hasElaborateDestructor(T) { 401 | static if (isObjectiveC!T) 402 | enum hasElaborateDestructor = false; 403 | else static if (__traits(isStaticArray, T)) 404 | enum bool hasElaborateDestructor = T.sizeof && hasElaborateDestructor!(BaseElemOf!T); 405 | else static if (isAggregateType!T) 406 | enum hasElaborateDestructor = __traits(hasMember, T, "__dtor") || 407 | anySatisfy!(.hasElaborateDestructor, Fields!T); 408 | else 409 | enum hasElaborateDestructor = false; 410 | } 411 | 412 | /** 413 | Detect whether symbol or type $(D T) is a function, a function pointer or a delegate. 414 | 415 | Params: 416 | T = The type to check 417 | Returns: 418 | A $(D_KEYWORD bool) 419 | */ 420 | enum bool isSomeFunction(alias T) = 421 | is(T == return) || 422 | is(typeof(T) == return) || 423 | is(typeof(&T) == return); // @property 424 | 425 | /** 426 | Detect whether $(D T) is a callable object, which can be called with the 427 | function call operator `$(LPAREN)...$(RPAREN)`. 428 | */ 429 | template isCallable(alias callable) { 430 | static if (is(typeof(&callable.opCall) == delegate)) 431 | // T is a object which has a member function opCall(). 432 | enum bool isCallable = true; 433 | else static if (is(typeof(&callable.opCall) V : V*) && is(V == function)) 434 | // T is a type which has a static member function opCall(). 435 | enum bool isCallable = true; 436 | else static if (is(typeof(&callable.opCall!()) TemplateInstanceType)) 437 | { 438 | enum bool isCallable = isCallable!TemplateInstanceType; 439 | } 440 | else static if (is(typeof(&callable!()) TemplateInstanceType)) 441 | { 442 | enum bool isCallable = isCallable!TemplateInstanceType; 443 | } 444 | else 445 | { 446 | enum bool isCallable = isSomeFunction!callable; 447 | } 448 | } 449 | 450 | /** 451 | Get the function type from a callable object $(D func), or from a function pointer/delegate type. 452 | 453 | Using builtin $(D typeof) on a property function yields the types of the 454 | property value, not of the property function itself. Still, 455 | $(D FunctionTypeOf) is able to obtain function types of properties. 456 | 457 | Note: 458 | Do not confuse function types with function pointer types; function types are 459 | usually used for compile-time reflection purposes. 460 | */ 461 | template FunctionTypeOf(alias func) 462 | if (isCallable!func) { 463 | static if ((is(typeof(& func) Fsym : Fsym*) && is(Fsym == function)) || is(typeof(& func) Fsym == delegate)) 464 | { 465 | alias FunctionTypeOf = Fsym; // HIT: (nested) function symbol 466 | } 467 | else static if (is(typeof(& func.opCall) Fobj == delegate) || is(typeof(& func.opCall!()) Fobj == delegate)) 468 | { 469 | alias FunctionTypeOf = Fobj; // HIT: callable object 470 | } 471 | else static if ( 472 | (is(typeof(& func.opCall) Ftyp : Ftyp*) && is(Ftyp == function)) || 473 | (is(typeof(& func.opCall!()) Ftyp : Ftyp*) && is(Ftyp == function)) 474 | ) 475 | { 476 | alias FunctionTypeOf = Ftyp; // HIT: callable type 477 | } 478 | else static if (is(func T) || is(typeof(func) T)) 479 | { 480 | static if (is(T == function)) 481 | alias FunctionTypeOf = T; // HIT: function 482 | else static if (is(T Fptr : Fptr*) && is(Fptr == function)) 483 | alias FunctionTypeOf = Fptr; // HIT: function pointer 484 | else static if (is(T Fdlg == delegate)) 485 | alias FunctionTypeOf = Fdlg; // HIT: delegate 486 | else 487 | static assert(0); 488 | } 489 | else 490 | static assert(0); 491 | } 492 | 493 | /** 494 | Get the type of the return value from a function, 495 | a pointer to function, a delegate, a struct 496 | with an opCall, a pointer to a struct with an opCall, 497 | or a class with an $(D opCall). Please note that $(D_KEYWORD ref) 498 | is not part of a type, but the attribute of the function. 499 | 500 | Note: 501 | To reduce template instantiations, consider instead using 502 | $(D_INLINECODE typeof(() { return func(args); } ())) if the argument types are known or 503 | $(D_INLINECODE static if (is(typeof(func) Ret == return))) if only that basic test is needed. 504 | */ 505 | template ReturnType(alias func) 506 | if (isCallable!func) { 507 | static if (is(FunctionTypeOf!func R == return)) 508 | alias ReturnType = R; 509 | else 510 | static assert(0, "argument has no return type"); 511 | } 512 | 513 | /** 514 | Get, as a tuple, the types of the parameters to a function, a pointer 515 | to function, a delegate, a struct with an `opCall`, a pointer to a 516 | struct with an `opCall`, or a class with an `opCall`. 517 | */ 518 | template Parameters(alias func) 519 | if (isCallable!func) { 520 | static if (is(FunctionTypeOf!func P == function)) 521 | alias Parameters = P; 522 | else 523 | static assert(0, "argument has no parameters"); 524 | } 525 | -------------------------------------------------------------------------------- /source/numem/core/types.d: -------------------------------------------------------------------------------- 1 | /** 2 | Helpers for creating special types. 3 | 4 | Copyright: 5 | Copyright © 2023-2025, Kitsunebi Games 6 | Copyright © 2023-2025, Inochi2D Project 7 | 8 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Authors: Luna Nielsen 10 | */ 11 | module numem.core.types; 12 | 13 | /** 14 | Creates a new unique handle type. 15 | 16 | Handle types are pointers to opaque structs. 17 | 18 | Params: 19 | name = Unique name of the handle. 20 | 21 | Examples: 22 | --- 23 | alias VkInstance = OpaqueHandle!("VkInstance"); 24 | --- 25 | */ 26 | template OpaqueHandle(string name) { 27 | struct OpaqueHandleT(string name); 28 | alias OpaqueHandle = OpaqueHandleT!(name)*; 29 | } 30 | 31 | @("OpaqueHandle") 32 | unittest { 33 | alias HandleT1 = OpaqueHandle!("HandleT1"); 34 | alias HandleT2 = OpaqueHandle!("HandleT2"); 35 | 36 | assert(!is(HandleT1 == HandleT2)); 37 | assert(!is(HandleT1 : HandleT2)); 38 | } 39 | 40 | /** 41 | Creates a new type based on an existing type. 42 | 43 | Params: 44 | T = Base type of the typedef. 45 | name = An extra identifier for the type. 46 | init = Initializer value for this type. 47 | 48 | Examples: 49 | --- 50 | alias MyInt = TypeDef!(int, "MyInt"); 51 | assert(!is(MyInt == int)); 52 | --- 53 | */ 54 | template TypeDef(T, string name, T init = T.init) { 55 | import std.format : format; 56 | 57 | mixin(q{ 58 | struct %s { 59 | @nogc nothrow: 60 | private: 61 | T value = init; 62 | 63 | public: 64 | alias BaseType = T; 65 | alias value this; 66 | 67 | static if ((is(T == struct) || is(T == union)) && !is(typeof({T t;}))) { 68 | @disable this(); 69 | } 70 | this(T init) { this.value = init; } 71 | this(typeof(this) init) { this.value = init.value; } 72 | } 73 | }.format(name)); 74 | 75 | alias TypeDef = mixin(name); 76 | } 77 | 78 | /** 79 | Gets the base type of a typedef. 80 | */ 81 | template TypeDefBase(T) { 82 | static if (is(typeof({ T.BaseType t; }))) { 83 | alias TypeDefBase = T.BaseType; 84 | } else { 85 | static assert(0, "Not a TypeDef!"); 86 | } 87 | } 88 | 89 | @("TypeDef") 90 | unittest { 91 | alias MyInt = TypeDef!(int, "MyInt"); 92 | 93 | // They're not the same type, but they *are* implicitly convertible. 94 | assert(!is(MyInt == int)); 95 | assert(is(MyInt : int)); 96 | 97 | int i = 42; 98 | MyInt va = 42; 99 | 100 | assert(i == va); 101 | assert(TypeDefBase!MyInt.stringof == int.stringof); 102 | } -------------------------------------------------------------------------------- /source/numem/heap.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem Heaps. 3 | 4 | Copyright: 5 | Copyright © 2023-2025, Kitsunebi Games 6 | Copyright © 2023-2025, Inochi2D Project 7 | 8 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Authors: Luna Nielsen 10 | */ 11 | module numem.heap; 12 | import numem.core.hooks; 13 | 14 | /** 15 | A heap represents a destination for placement new operations. 16 | This allows classes to be instantiated into memory in developer 17 | specified configurations. 18 | */ 19 | abstract 20 | class NuHeap { 21 | @nogc: 22 | 23 | /** 24 | Allocates memory on the heap. 25 | 26 | Params: 27 | bytes = The amount of bytes to allocate from the heap. 28 | Returns: 29 | A pointer to the memory allocated on the heap. 30 | $(D null) if operation failed. 31 | */ 32 | abstract void* alloc(size_t bytes); 33 | 34 | /** 35 | Attempts to reallocate an existing memory allocation on the heap. 36 | 37 | Params: 38 | allocation = The original allocation 39 | bytes = The new size of the allocation, in bytes. 40 | Returns: 41 | A pointer to the memory allocated on the heap. 42 | $(D null) if the operation failed. 43 | */ 44 | abstract void* realloc(void* allocation, size_t bytes); 45 | 46 | /** 47 | Frees memory from the heap. 48 | Note: Only memory owned by the heap may be freed by it. 49 | 50 | Params: 51 | allocation = The allocation to free. 52 | */ 53 | abstract void free(void* allocation); 54 | } 55 | 56 | /** 57 | A heap which is a simple abstraction over nuAlloc, nuRealloc and nuFree. 58 | */ 59 | class NuMallocHeap : NuHeap { 60 | @nogc: 61 | 62 | /** 63 | Allocates memory on the heap. 64 | 65 | Params: 66 | bytes = The amount of bytes to allocate from the heap. 67 | Returns: 68 | A pointer to the memory allocated on the heap. 69 | $(D null) if operation failed. 70 | */ 71 | override 72 | void* alloc(size_t bytes) { 73 | return nu_malloc(bytes); 74 | } 75 | 76 | /** 77 | Attempts to reallocate an existing memory allocation on the heap. 78 | 79 | Params: 80 | allocation = The original allocation 81 | bytes = The new size of the allocation, in bytes. 82 | Returns: 83 | A pointer to the memory allocated on the heap. 84 | $(D null) if the operation failed. 85 | */ 86 | override 87 | void* realloc(void* allocation, size_t bytes) { 88 | return nu_realloc(allocation, bytes); 89 | } 90 | 91 | /** 92 | Frees memory from the heap. 93 | 94 | Params: 95 | allocation = The allocation to free. 96 | */ 97 | override 98 | void free(void* allocation) { 99 | return nu_free(allocation); 100 | } 101 | } -------------------------------------------------------------------------------- /source/numem/lifetime.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem Lifetime Handling. 3 | 4 | This module implements wrappers around $(D numem.core.lifetime) to provide 5 | an easy way to instantiate various D types and slices of D types. 6 | 7 | Copyright: 8 | Copyright © 2023-2025, Kitsunebi Games 9 | Copyright © 2023-2025, Inochi2D Project 10 | 11 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 12 | Authors: Luna Nielsen 13 | 14 | See_Also: 15 | $(D numem.core.memory.nu_resize) 16 | $(D numem.core.memory.nu_dup) 17 | $(D numem.core.memory.nu_idup) 18 | */ 19 | module numem.lifetime; 20 | import numem.core.exception; 21 | import numem.core.traits; 22 | import numem.core.lifetime; 23 | import numem.core.hooks; 24 | import numem.core.memory; 25 | import numem.casting; 26 | import numem.heap; 27 | 28 | // Utilities that are useful in the same regard from core. 29 | public import numem.core.memory : nu_dup, nu_idup, nu_resize; 30 | public import numem.core.lifetime : nu_destroywith, nu_autoreleasewith; 31 | 32 | @nogc: 33 | 34 | /** 35 | Creates a new auto release pool spanning the current scope. 36 | 37 | Returns: 38 | A stack allocated object with copying and moving disabled. 39 | */ 40 | auto autoreleasepool_scope() { 41 | static 42 | struct nu_arpool_stackctx { 43 | @nogc: 44 | private: 45 | void* ctx; 46 | 47 | public: 48 | ~this() { nu_autoreleasepool_pop(ctx); } 49 | this(void* ctx) { this.ctx = ctx; } 50 | 51 | @disable this(this); 52 | } 53 | 54 | return nu_arpool_stackctx(nu_autoreleasepool_push()); 55 | } 56 | 57 | /** 58 | Creates a scoped auto release pool. 59 | 60 | Params: 61 | scope_ = The scope in which the auto release pool acts. 62 | */ 63 | void autoreleasepool(void delegate() scope @nogc scope_) @trusted { 64 | void* ctx = nu_autoreleasepool_push(); 65 | scope_(); 66 | nu_autoreleasepool_pop(ctx); 67 | } 68 | 69 | /** 70 | Constructs the given object. 71 | 72 | Attempting to construct a non-initialized $(D object) is undefined behaviour. 73 | */ 74 | void nogc_construct(T, Args...)(ref T object, Args args) @trusted { 75 | static if (isPointer!T) 76 | emplace(*object, args); 77 | else 78 | emplace(object, args); 79 | } 80 | 81 | /** 82 | Allocates a new instance of $(D T). 83 | */ 84 | Ref!T nogc_new(T, Args...)(auto ref Args args) @trusted { 85 | if (Ref!T newobject = cast(Ref!T)nu_malloc(AllocSize!T)) { 86 | try { 87 | nogc_construct(newobject, args); 88 | return newobject; 89 | 90 | } catch(Exception ex) { 91 | nu_free(cast(void*)newobject); 92 | throw ex; 93 | } 94 | } 95 | return null; 96 | } 97 | 98 | /** 99 | Attempts to allocate a new instance of $(D T). 100 | 101 | Params: 102 | args = The arguments to pass to the type's constructor. 103 | 104 | Returns: 105 | A reference to the instantiated object or $(D null) if allocation 106 | failed. 107 | */ 108 | Ref!T nogc_trynew(T, Args...)(auto ref Args args) @trusted nothrow { 109 | if (Ref!T newobject = cast(Ref!T)nu_malloc(AllocSize!T)) { 110 | try { 111 | nogc_construct(newobject, args); 112 | return newobject; 113 | 114 | } catch(Exception ex) { 115 | 116 | nu_free(cast(void*)newobject); 117 | if (ex) 118 | assumeNoThrowNoGC((Exception ex) { nogc_delete(ex); }, ex); 119 | return null; 120 | } 121 | } 122 | return null; 123 | } 124 | 125 | /** 126 | Allocates a new instance of $(D T) on the specified heap. 127 | 128 | Params: 129 | heap = The heap to allocate the instance on. 130 | args = The arguments to pass to the type's constructor. 131 | */ 132 | Ref!T nogc_new(T, Args...)(NuHeap heap, auto ref Args args) @trusted { 133 | if (Ref!T newobject = cast(Ref!T)heap.alloc(AllocSize!T)) { 134 | try { 135 | nogc_construct(newobject, args); 136 | } catch(Exception ex) { 137 | nu_free(cast(void*)newobject); 138 | throw ex; 139 | } 140 | } 141 | return null; 142 | } 143 | 144 | /** 145 | Attempts to allocate a new instance of $(D T) on the specified heap. 146 | 147 | Params: 148 | heap = The heap to allocate the instance on. 149 | args = The arguments to pass to the type's constructor. 150 | 151 | Returns: 152 | A reference to the instantiated object or $(D null) if allocation 153 | failed. 154 | */ 155 | Ref!T nogc_trynew(T, Args...)(NuHeap heap, auto ref Args args) @trusted nothrow { 156 | if (Ref!T newobject = cast(Ref!T)heap.alloc(AllocSize!T)) { 157 | try { 158 | nogc_construct(newobject, args); 159 | return newobject; 160 | 161 | } catch(Exception ex) { 162 | 163 | nu_free(cast(void*)newobject); 164 | if (ex) 165 | assumeNoThrowNoGC((Exception ex) { nogc_delete(ex); }, ex); 166 | return null; 167 | } 168 | } 169 | return null; 170 | } 171 | 172 | /** 173 | Finalizes $(D obj_) by calling its destructor (if any). 174 | 175 | If $(D doFree) is $(D true), memory associated with obj_ will additionally be freed 176 | after finalizers have run; otherwise the object is reset to its original state. 177 | 178 | Params: 179 | obj_ = Instance to destroy and deallocate. 180 | */ 181 | void nogc_delete(T, bool doFree=true)(ref T obj_) @trusted { 182 | static if (isObjectiveC!T) { 183 | // Do nothing. 184 | } else static if (isHeapAllocated!T) { 185 | 186 | // Ensure type is not null. 187 | if (reinterpret_cast!(void*)(obj_) !is null) { 188 | 189 | destruct!(T, !doFree)(obj_); 190 | 191 | // Free memory if need be. 192 | static if (doFree) 193 | nu_free(cast(void*)obj_); 194 | 195 | obj_ = null; 196 | } 197 | } else { 198 | destruct!(T, !doFree)(obj_); 199 | } 200 | } 201 | 202 | /** 203 | Attempts to finalize $(D obj_) by calling its destructor (if any). 204 | 205 | If $(D doFree) is $(D true), memory associated with obj_ will additionally be freed 206 | after finalizers have run; otherwise the object is reset to its original state. 207 | 208 | Params: 209 | obj_ = Instance to destroy and deallocate. 210 | 211 | Returns: 212 | Whether the operation succeeded. 213 | */ 214 | bool nogc_trydelete(T, bool doFree=true)(ref T obj_) @trusted nothrow { 215 | try { 216 | nogc_delete(obj_); 217 | return true; 218 | 219 | } catch (Exception ex) { 220 | if (ex) 221 | assumeNoThrowNoGC((Exception ex) { nogc_delete(ex); }, ex); 222 | return false; 223 | 224 | } 225 | } 226 | 227 | /** 228 | Deallocates the specified instance of $(D T) from the specified heap. 229 | Finalizes $(D obj_) by calling its destructor (if any). 230 | 231 | If $(D doFree) is $(D true), memory associated with obj_ will additionally be freed 232 | after finalizers have run; otherwise the object is reset to its original state. 233 | 234 | Params: 235 | heap = The heap to allocate the instance on. 236 | obj_ = Instance to destroy and deallocate. 237 | */ 238 | void nogc_delete(T, bool doFree=true)(NuHeap heap, ref T obj_) @trusted 239 | if (isHeapAllocated!T) { 240 | if (reinterpret_cast!(void*)(obj_) !is null) { 241 | 242 | destruct!(T, !doFree)(obj_); 243 | 244 | // Free memory if need be. 245 | static if (doFree) 246 | heap.free(cast(void*)obj_); 247 | 248 | obj_ = null; 249 | } 250 | } 251 | 252 | /** 253 | Attempts to deallocate the specified instance of $(D T) from the specified heap. 254 | Finalizes $(D obj_) by calling its destructor (if any). 255 | 256 | If $(D doFree) is $(D true), memory associated with obj_ will additionally be freed 257 | after finalizers have run; otherwise the object is reset to its original state. 258 | 259 | Params: 260 | heap = The heap to allocate the instance on. 261 | obj_ = Instance to destroy and deallocate. 262 | 263 | Returns: 264 | Whether the operation succeeded. 265 | */ 266 | bool nogc_trydelete(T, bool doFree=true)(NuHeap heap, ref T obj_) @trusted nothrow 267 | if (isHeapAllocated!T) { 268 | try { 269 | 270 | nogc_delete(heap, obj_); 271 | return true; 272 | } catch (Exception ex) { 273 | 274 | if (ex) 275 | assumeNoThrowNoGC((Exception ex) { nogc_delete(ex); }, ex); 276 | return false; 277 | } 278 | } 279 | 280 | /** 281 | Finalizes the objects referenced by $(D objects) by calling the 282 | destructors of its members (if any). 283 | 284 | If $(D doFree) is $(D true), memory associated with each object 285 | will additionally be freed after finalizers have run; otherwise the object 286 | is reset to its original state. 287 | */ 288 | void nogc_delete(T, bool doFree=true)(T[] objects) @trusted { 289 | foreach(i; 0..objects.length) 290 | nogc_delete!(T, doFree)(objects[i]); 291 | } 292 | 293 | /** 294 | Attempts to finalize the objects referenced by $(D objects) by calling the 295 | destructors of its members (if any). 296 | 297 | If $(D doFree) is $(D true), memory associated with each object 298 | will additionally be freed after finalizers have run; otherwise the object 299 | is reset to its original state. 300 | 301 | Params: 302 | objects = The objects to try to delete. 303 | 304 | Returns: 305 | Whether the operation succeeded. 306 | */ 307 | bool nogc_trydelete(T, bool doFree=true)(T[] objects) @trusted { 308 | size_t failed = 0; 309 | foreach(i; 0..objects.length) 310 | failed += nogc_trydelete!(T, doFree)(objects[i]); 311 | 312 | return failed != 0; 313 | } 314 | 315 | /** 316 | Initializes the object at the memory in $(D dst), filling it out with 317 | its default state. 318 | 319 | This variant will also initialize class instances on the stack which 320 | can then be constructed with $(D nogc_construct). However you may 321 | still need to $(D nogc_delete) these instances, with $(D doFree) set 322 | to false. 323 | 324 | Params: 325 | dst = A memory range allocated, big enough to store $(D T) 326 | 327 | Returns: 328 | A reference to the initialized element, or $(D T.init) if it failed. 329 | */ 330 | T nogc_initialize(T)(void[] dst) @trusted { 331 | if (dst.length < AllocSize!T) 332 | return T.init; 333 | 334 | T tmp = cast(T)dst.ptr; 335 | initializeAt(tmp); 336 | return tmp; 337 | } 338 | 339 | /** 340 | Initializes the object at $(D element), filling it out with 341 | its default state. 342 | 343 | Params: 344 | element = A reference to the allocated memory to initialize. 345 | 346 | Returns: 347 | A reference to the initialized element. 348 | */ 349 | ref T nogc_initialize(T)(ref T element) @trusted { 350 | initializeAtNoCtx(element); 351 | return element; 352 | } 353 | 354 | /** 355 | Initializes the objects at $(D elements), filling them out with 356 | their default state. 357 | 358 | Params: 359 | elements = A slice of the elements to initialize 360 | 361 | Returns: 362 | The slice with now initialized contents. 363 | */ 364 | T[] nogc_initialize(T)(T[] elements) @trusted { 365 | foreach(i; 0..elements.length) 366 | initializeAtNoCtx(elements[i]); 367 | 368 | return elements; 369 | } 370 | 371 | /** 372 | Zero-fills an object 373 | */ 374 | void nogc_zeroinit(T)(ref T element) @trusted nothrow pure { 375 | nu_memset(&element, 0, element.sizeof); 376 | } 377 | 378 | /** 379 | Zero-fills an object 380 | */ 381 | void nogc_zeroinit(T)(T[] elements) @trusted nothrow pure { 382 | nu_memset(elements.ptr, 0, elements.length*T.sizeof); 383 | } 384 | 385 | /** 386 | Allocates a new class on the heap. 387 | Immediately exits the application if out of memory. 388 | */ 389 | void nogc_emplace(T, Args...)(auto ref T dest, Args args) @trusted { 390 | emplace!(T, T, Args)(dest, args); 391 | } 392 | 393 | /** 394 | Moves elements in $(D src) to $(D dst) via destructive copy. 395 | 396 | If an element in $(D src) is not a valid object, 397 | then accessing the moved element in $(D to) will be undefined behaviour. 398 | 399 | After the move operation, the original memory locations in $(D from) will be 400 | reset to their base initialized state before any constructors are run. 401 | */ 402 | void nogc_move(T)(T[] dst, T[] src) @trusted { 403 | import nulib.math : min; 404 | size_t toTx = min(dst.length, src.length); 405 | 406 | foreach(i; 0..toTx) 407 | __move(src[i], dst[i]); 408 | } 409 | 410 | /** 411 | Copies $(D src) to $(D dst) via a blit operation. 412 | 413 | Postblits and copy constructors will be called subsequently. 414 | */ 415 | void nogc_copy(T)(T[] dst, T[] src) @trusted { 416 | import nulib.math : min; 417 | size_t toTx = min(dst.length, src.length); 418 | 419 | foreach(i; 0..toTx) 420 | __copy(src[i], dst[i]); 421 | } 422 | 423 | /** 424 | Moves $(D from) to $(D to) via a destructive copy. 425 | 426 | If $(D from) is not a valid object, then accessing $(D to) will be undefined 427 | behaviour. 428 | 429 | After the move operation, the original memory location of $(D from) will be 430 | reset to its base initialized state before any constructors are run. 431 | 432 | Params: 433 | from = The source of the move operation. 434 | to = The destination of the move operation. 435 | */ 436 | void moveTo(T)(ref T from, ref T to) @trusted { 437 | __move(from, to); 438 | } 439 | 440 | /** 441 | Moves $(D from). 442 | 443 | Useful for moving stack allocated structs. 444 | 445 | Params: 446 | from = The source of the move operation. 447 | 448 | Returns: 449 | The moved value, $(D from) will be reset to its initial state. 450 | */ 451 | T move(T)(scope ref return T from) @trusted { 452 | return __move(from); 453 | } 454 | 455 | /** 456 | Copies $(D from) to $(D to) via a blit operation. 457 | 458 | Postblits and copy constructors will be called subsequently. 459 | 460 | Params: 461 | from = The source of the copy operation. 462 | to = The destination of the copy operation. 463 | */ 464 | void copyTo(T)(ref T from, ref T to) @trusted { 465 | __copy(from, to); 466 | } -------------------------------------------------------------------------------- /source/numem/object.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem Base Classes. 3 | 4 | Copyright: 5 | Copyright © 2000-2011, The D Language Foundation. 6 | Copyright © 2023-2025, Kitsunebi Games 7 | Copyright © 2023-2025, Inochi2D Project 8 | 9 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 10 | Authors: Luna Nielsen, Walter Bright, Sean Kelly 11 | 12 | See_Also: 13 | $(D numem.core.memory.nu_resize) 14 | $(D numem.core.memory.nu_dup) 15 | $(D numem.core.memory.nu_idup) 16 | */ 17 | module numem.object; 18 | import numem.core.atomic; 19 | import numem.lifetime; 20 | import numem.core.exception; 21 | import numem.core.lifetime : nu_autorelease; 22 | 23 | /** 24 | Numem base-class which allows using basic class functions without a garbage 25 | collector. 26 | */ 27 | class NuObject { 28 | @nogc: 29 | private: 30 | 31 | TypeInfo_Class getClassInfo() { 32 | return cast(TypeInfo_Class)typeid(this); 33 | } 34 | 35 | public: 36 | 37 | /** 38 | Gets the name of the class. 39 | 40 | Returns: 41 | Name of class based on its compile-time generated typeid. 42 | */ 43 | override 44 | string toString() { 45 | return getClassInfo().name; 46 | } 47 | 48 | /** 49 | Test whether $(D this) is equal to $(D other). 50 | 51 | Default implementation only checks whether they are stored at the same 52 | memory address. 53 | */ 54 | override 55 | bool opEquals(const Object other) const { 56 | return this is other; 57 | } 58 | 59 | /** 60 | Compare with another Object $(D other). 61 | 62 | Returns: 63 | $(TABLE 64 | $(TR $(TD this < obj) $(TD < 0)) 65 | $(TR $(TD this == obj) $(TD 0)) 66 | $(TR $(TD this > obj) $(TD > 0)) 67 | ) 68 | 69 | Notes: 70 | If you are combining this with a compacting garbage collector, 71 | it will prevent it from functioning properly. 72 | */ 73 | override 74 | int opCmp(const Object other) const { 75 | return cast(int)cast(void*)this - cast(int)cast(void*)other; 76 | } 77 | 78 | /** 79 | Compute a hash for Object. 80 | */ 81 | override 82 | size_t toHash() @trusted nothrow { 83 | 84 | // Address of ourselves. 85 | size_t addr = cast(size_t)cast(void*)this; 86 | 87 | // The bottom log2((void*).alignof) bits of the address will always 88 | // be 0. Moreover it is likely that each Object is allocated with a 89 | // separate call to malloc. The alignment of malloc differs from 90 | // platform to platform, but rather than having special cases for 91 | // each platform it is safe to use a shift of 4. To minimize 92 | // collisions in the low bits it is more important for the shift to 93 | // not be too small than for the shift to not be too big. 94 | return addr ^ (addr >>> 4); 95 | } 96 | 97 | } 98 | 99 | /** 100 | A reference counted class. 101 | 102 | Reference counted classes in numem are manually reference counted, 103 | this means that you are responsible for managing synchronising 104 | $(D retain) and $(D release) calls. 105 | 106 | Threadsafety: 107 | Threadsafety depends on whether the hookset used supports 108 | atomic operations; see $(D numem.core.atomic.nu_atomic_supported). 109 | If unsupported, retain and release will not be threadsafe on their own, 110 | and should be wrapped in another synchronisation primitive. 111 | 112 | Memorysafety: 113 | Once the reference count for a class reaches 0, it will be destructed 114 | and freed automatically. All references to the class after refcount 115 | reaches 0 will be invalid and should not be used. 116 | $(D NuRefCounted.release) returns a value which can be used to determine whether 117 | the destructor was invoked. 118 | */ 119 | @nu_autoreleasewith!((ref obj) { obj.release(); }) 120 | class NuRefCounted : NuObject { 121 | @nogc: 122 | private: 123 | uint refcount = 0; 124 | 125 | public: 126 | 127 | /** 128 | Base constructor, all subclasses *have* to invoke this constructor. 129 | Otherwise the instance will be invalid on instantiation. 130 | */ 131 | this() @safe { 132 | refcount = 1; 133 | } 134 | 135 | /** 136 | Retains a reference to a valid object. 137 | 138 | Notes: 139 | The object validity is determined by the refcount. 140 | Uninitialized refcounted classes will be invalid. 141 | As such, releasing an invalid object will not 142 | invoke the destructor of said object. 143 | */ 144 | final 145 | NuRefCounted retain() @trusted nothrow { 146 | if (isValid) 147 | nu_atomic_add_32(refcount, 1); 148 | 149 | return this; 150 | } 151 | 152 | /** 153 | Releases a reference from a valid object. 154 | 155 | Notes: 156 | The object validity is determined by the refcount. 157 | Uninitialized refcounted classes will be invalid. 158 | As such, releasing an invalid object will not 159 | invoke the destructor of said object. 160 | 161 | Returns: 162 | The class instance release was called on, $(D null) if 163 | the class was freed. 164 | */ 165 | final 166 | NuRefCounted release() @trusted { 167 | if (isValid) { 168 | nu_atomic_sub_32(refcount, 1); 169 | 170 | // Handle destruction. 171 | if (nu_atomic_load_32(refcount) == 0) { 172 | NuRefCounted self = this; 173 | nogc_delete(self); 174 | return null; 175 | } 176 | } 177 | 178 | return this; 179 | } 180 | 181 | /** 182 | Pushes this refcounted object to the topmost auto release pool. 183 | 184 | Returns: 185 | The class instance release was called on. 186 | */ 187 | final 188 | NuRefCounted autorelease() @trusted { 189 | nu_autorelease!NuRefCounted(this); 190 | return this; 191 | } 192 | 193 | /** 194 | Returns whether this object is valid. 195 | 196 | Notes: 197 | The object validity is determined by the refcount. 198 | Uninitialized refcounted classes will be invalid. 199 | As such, releasing an invalid object will not 200 | invoke the destructor of said object. 201 | 202 | Returns: 203 | Whether the object is valid (has a refcount higher than 0) 204 | */ 205 | final 206 | bool isValid() @trusted nothrow { 207 | return nu_atomic_load_32(refcount) != 0; 208 | } 209 | } 210 | 211 | /** 212 | Helper function which allows typed chaining of retain calls. 213 | 214 | Params: 215 | elem = The element to perform the operation on 216 | 217 | Returns: 218 | The element or $(D null) if it was freed as a result 219 | of the operation. 220 | */ 221 | T retained(T)(T elem) @nogc @trusted if (is(T : NuRefCounted)) { 222 | return cast(T)elem.retain(); 223 | } 224 | 225 | /// ditto 226 | T released(T)(T elem) @nogc @trusted if (is(T : NuRefCounted)) { 227 | return cast(T)elem.release(); 228 | } 229 | 230 | /// ditto 231 | T autoreleased(T)(T elem) @nogc @trusted if (is(T : NuRefCounted)) { 232 | return cast(T)elem.autorelease(); 233 | } -------------------------------------------------------------------------------- /source/numem/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Numem 3 | 4 | Copyright: 5 | Copyright © 2023-2025, Kitsunebi Games 6 | Copyright © 2023-2025, Inochi2D Project 7 | 8 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Authors: Luna Nielsen 10 | */ 11 | module numem; 12 | 13 | public import numem.core.exception; 14 | public import numem.core.hooks; 15 | public import numem.casting; 16 | public import numem.lifetime; 17 | public import numem.object; 18 | 19 | /** 20 | Container for numem version. 21 | */ 22 | struct nu_version { 23 | @nogc: 24 | public: 25 | uint major; /// Major 26 | uint minor; /// Minor 27 | uint patch; /// Patch 28 | } 29 | 30 | /** 31 | Numem version 32 | */ 33 | private 34 | const __gshared nu_version __nu_version = nu_version(1, 0, 0); 35 | 36 | /** 37 | Gets the current version of numem. 38 | 39 | Returns: 40 | The current version of numem as 3 tightly packed 32-bit 41 | unsigned integers. 42 | */ 43 | export 44 | extern(C) 45 | nu_version nu_get_version() @nogc nothrow @safe pure { 46 | return __nu_version; 47 | } -------------------------------------------------------------------------------- /source/numem/volatile.d: -------------------------------------------------------------------------------- 1 | /** 2 | Volatile load/store operations. 3 | 4 | Copyright: Copyright © 2019, The D Language Foundation 5 | License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | Authors: Walter Bright, Ernesto Castellotti 7 | */ 8 | module numem.volatile; 9 | nothrow @safe @nogc: 10 | 11 | /** 12 | Read/write value from/to the memory location indicated by ptr. 13 | 14 | These functions are recognized by the compiler, and calls to them are guaranteed 15 | to not be removed (as dead assignment elimination or presumed to have no effect) 16 | or reordered in the same thread. 17 | 18 | These reordering guarantees are only made with regards to other 19 | operations done through these functions; the compiler is free to reorder regular 20 | loads/stores with regards to loads/stores done through these functions. 21 | 22 | This is useful when dealing with memory-mapped I/O (MMIO) where a store can 23 | have an effect other than just writing a value, or where sequential loads 24 | with no intervening stores can retrieve 25 | different values from the same location due to external stores to the location. 26 | 27 | These functions will, when possible, do the load/store as a single operation. In 28 | general, this is possible when the size of the operation is less than or equal to 29 | $(D (void*).sizeof), although some targets may support larger operations. If the 30 | load/store cannot be done as a single operation, multiple smaller operations will be used. 31 | 32 | These are not to be conflated with atomic operations. They do not guarantee any 33 | atomicity. This may be provided by coincidence as a result of the instructions 34 | used on the target, but this should not be relied on for portable programs. 35 | Further, no memory fences are implied by these functions. 36 | They should not be used for communication between threads. 37 | They may be used to guarantee a write or read cycle occurs at a specified address. 38 | */ 39 | 40 | ubyte volatileLoad(ubyte * ptr); 41 | ushort volatileLoad(ushort* ptr); /// ditto 42 | uint volatileLoad(uint * ptr); /// ditto 43 | ulong volatileLoad(ulong * ptr); /// ditto 44 | 45 | void volatileStore(ubyte * ptr, ubyte value); /// ditto 46 | void volatileStore(ushort* ptr, ushort value); /// ditto 47 | void volatileStore(uint * ptr, uint value); /// ditto 48 | void volatileStore(ulong * ptr, ulong value); /// ditto -------------------------------------------------------------------------------- /tests/ut/casting.d: -------------------------------------------------------------------------------- 1 | module ut.casting; 2 | import numem.casting; 3 | 4 | private class Test { 5 | T opCast(T)() { 6 | static if (is(T : int)) 7 | return 42; 8 | else static if (is(T : void*)) 9 | return reinterpret_cast!T(this); 10 | else static if (is(Test : T)) 11 | return reinterpret_cast!T(this); 12 | else 13 | static assert(0, "Can't cast to type "~T.stringof~"!"); 14 | } 15 | } 16 | 17 | @("reinterpret_cast: opCast") 18 | unittest { 19 | import numem.lifetime; 20 | 21 | Test a = nogc_new!Test(); 22 | assert(cast(int)a == 42); 23 | assert(cast(void*)a); 24 | assert(cast(Object)a); 25 | } 26 | 27 | @("const_cast: const to non-const") 28 | unittest { 29 | const(char)* myString = "Hello, world!"; 30 | char* myStringMut = const_cast!(char*)(myString); 31 | myString = const_cast!(const(char)*)(myStringMut); 32 | } -------------------------------------------------------------------------------- /tests/ut/core/atomic.d: -------------------------------------------------------------------------------- 1 | module ut.core.atomic; 2 | import numem.core.atomic; 3 | 4 | @("load: i32") 5 | unittest { 6 | if (!nu_atomic_supported) 7 | return; 8 | 9 | uint value = 42; 10 | assert(nu_atomic_load_32(value) == 42); 11 | } 12 | 13 | @("store: i32") 14 | unittest { 15 | if (!nu_atomic_supported) 16 | return; 17 | 18 | uint value = 128; 19 | nu_atomic_store_32(value, 42); 20 | assert(nu_atomic_load_32(value) == 42); 21 | } 22 | 23 | @("add: i32") 24 | unittest { 25 | if (!nu_atomic_supported) 26 | return; 27 | 28 | uint value = 41; 29 | assert(nu_atomic_add_32(value, 1) == 41); 30 | assert(nu_atomic_load_32(value) == 42); 31 | } 32 | 33 | @("sub: i32") 34 | unittest { 35 | if (!nu_atomic_supported) 36 | return; 37 | 38 | uint value = 42; 39 | assert(nu_atomic_sub_32(value, 1) == 42); 40 | assert(nu_atomic_load_32(value) == 41); 41 | } 42 | 43 | @("load: ptr") 44 | unittest { 45 | if (!nu_atomic_supported) 46 | return; 47 | 48 | size_t value = 42; 49 | assert(cast(size_t)nu_atomic_load_ptr(cast(void**)&value) == 42); 50 | } 51 | 52 | @("store: ptr") 53 | unittest { 54 | if (!nu_atomic_supported) 55 | return; 56 | 57 | size_t value = 128; 58 | nu_atomic_store_ptr(cast(void**)&value, cast(void*)42); 59 | assert(cast(size_t)nu_atomic_load_ptr(cast(void**)&value) == 42); 60 | } 61 | 62 | @("cmpxhg: ptr") 63 | unittest { 64 | if (!nu_atomic_supported) 65 | return; 66 | 67 | size_t value = 128; 68 | 69 | nu_atomic_cmpxhg_ptr(cast(void**)&value, cast(void*)128, cast(void*)42); 70 | assert(cast(size_t)nu_atomic_load_ptr(cast(void**)&value) == 42); 71 | } 72 | 73 | shared static this() { 74 | import std.stdio : writeln; 75 | if (!nu_atomic_supported) { 76 | writeln("Atomics are disabled, skipping tests..."); 77 | } 78 | } -------------------------------------------------------------------------------- /tests/ut/core/exception.d: -------------------------------------------------------------------------------- 1 | module ut.core.exception; 2 | import numem.core.exception; 3 | 4 | @("catch & free NuException") 5 | @nogc 6 | unittest { 7 | try { 8 | enforce(false, "Ooops!"); 9 | } catch(NuException ex) { 10 | assert(ex.message() == "Ooops!"); 11 | ex.free(); 12 | } 13 | } -------------------------------------------------------------------------------- /tests/ut/core/memory.d: -------------------------------------------------------------------------------- 1 | module ut.core.memory; 2 | import numem.core.memory; 3 | 4 | @("nu_resize") 5 | unittest { 6 | uint[] arr; 7 | 8 | assert(arr.nu_resize(5).ptr); 9 | assert(!arr.nu_resize(0).ptr); 10 | } 11 | 12 | @("nu_dup") 13 | unittest { 14 | const(char)[] str1 = "Hello, world!".nu_dup(); 15 | immutable(char)[] str2 = "Hello, world!".nu_idup(); 16 | assert(str1 == "Hello, world!"); 17 | assert(str2 == "Hello, world!"); 18 | 19 | assert(!str1.nu_resize(0).ptr); 20 | assert(!str2.nu_resize(0).ptr); 21 | } 22 | 23 | @("nu_terminate") 24 | unittest { 25 | string str1 = "Hello, world!".nu_idup(); 26 | str1.nu_terminate(); 27 | assert(str1.ptr[str1.length] == '\0'); 28 | assert(!str1.nu_resize(0).ptr); 29 | } 30 | 31 | @("nu_is_overlapping") 32 | unittest { 33 | int[] arr1 = [1, 2, 3, 4]; 34 | int[] arr2 = arr1[1..$-1]; 35 | 36 | size_t arr1len = arr1.length*int.sizeof; 37 | size_t arr2len = arr2.length*int.sizeof; 38 | 39 | // Test all iterations that are supported. 40 | assert(nu_is_overlapping(arr1.ptr, arr1len, arr2.ptr, arr2len)); 41 | assert(!nu_is_overlapping(arr1.ptr, arr1len, arr2.ptr, 0)); 42 | assert(!nu_is_overlapping(arr1.ptr, 0, arr2.ptr, arr2len)); 43 | assert(!nu_is_overlapping(null, arr1len, arr2.ptr, arr2len)); 44 | assert(!nu_is_overlapping(arr1.ptr, arr1len, null, arr2len)); 45 | } -------------------------------------------------------------------------------- /tests/ut/lifetime.d: -------------------------------------------------------------------------------- 1 | module ut.lifetime; 2 | import numem.lifetime; 3 | 4 | class TestClass { 5 | @nogc: 6 | int value; 7 | 8 | this(int value) { 9 | this.value = value; 10 | } 11 | 12 | int func1() { return value; } 13 | } 14 | 15 | class SubClass : TestClass { 16 | @nogc: 17 | int value2; 18 | 19 | this(int value, int value2) { 20 | super(value); 21 | this.value2 = value2; 22 | } 23 | 24 | override 25 | int func1() { return value2; } 26 | } 27 | 28 | struct TestStruct { 29 | int value; 30 | } 31 | 32 | @("Construct class") 33 | @nogc 34 | unittest { 35 | TestClass a = nogc_new!TestClass(12); 36 | assert(a.func1() == 12); 37 | 38 | // also free. 39 | nogc_delete(a); 40 | } 41 | 42 | @("Construct subclass") 43 | @nogc 44 | unittest { 45 | TestClass klass1 = nogc_new!SubClass(1, 2); 46 | assert(klass1.func1() == 2); 47 | assert(cast(SubClass)klass1); 48 | } 49 | 50 | @("Construct class (nothrow)") 51 | nothrow @nogc 52 | unittest { 53 | import numem.core.exception : assumeNoThrow; 54 | 55 | TestClass a = assumeNoThrow(() => nogc_new!TestClass(12)); 56 | assert(a.value == 12); 57 | 58 | // also free. 59 | nogc_delete(a); 60 | } 61 | 62 | @("Construct struct") 63 | @nogc 64 | unittest { 65 | TestStruct* a = nogc_new!TestStruct(12); 66 | assert(a.value == 12); 67 | 68 | // also free. 69 | nogc_delete(a); 70 | } 71 | 72 | @("Free struct") 73 | @nogc 74 | unittest { 75 | TestStruct a = TestStruct(12); 76 | assert(a.value == 12); 77 | 78 | // also free. 79 | nogc_delete(a); 80 | } 81 | 82 | @("Construct class (Slice list)") 83 | @nogc 84 | unittest { 85 | TestClass[] classList; 86 | classList.nu_resize(5).nogc_initialize(); 87 | 88 | foreach(i; 0..classList.length) { 89 | classList[i] = nogc_new!TestClass(cast(int)i); 90 | } 91 | 92 | foreach(i; 0..classList.length) { 93 | assert(classList[i].value == i); 94 | } 95 | } 96 | 97 | @("nogc_zeroinit") 98 | unittest { 99 | uint var1 = 42; 100 | uint[8] var2 = [1, 2, 3, 4, 5, 6, 7, 8]; 101 | 102 | // Single var ref. 103 | nogc_zeroinit(var1); 104 | assert(var1 == 0); 105 | 106 | // Var range 107 | nogc_zeroinit(var2); 108 | foreach(i, value; var2) { 109 | assert(value == 0); 110 | } 111 | } 112 | 113 | @("nogc_initialize") 114 | unittest { 115 | import numem.casting : reinterpret_cast; 116 | 117 | // Pointers should be initialized to null. 118 | void* ptr = cast(void*)0xDEADBEEF; 119 | assert(nogc_initialize(ptr) is null); 120 | 121 | // NOTE: non-finite floats, even if the same bit pattern will never be equal. 122 | // as such we reinterpret it to a uint to convert it to a bit pattern. 123 | float f = 42.0; 124 | uint f_bitpattern = reinterpret_cast!uint(float.init); 125 | assert(reinterpret_cast!uint(nogc_initialize(f)) == f_bitpattern); 126 | 127 | // Basic scalars. 128 | ubyte u8 = ubyte.max; 129 | byte i8 = byte.max; 130 | ushort u16 = ushort.max; 131 | short i16 = short.max; 132 | uint u32 = uint.max; 133 | int i32 = int.max; 134 | ulong u64 = ulong.max; 135 | long i64 = long.max; 136 | assert(nogc_initialize(u8) == 0); 137 | assert(nogc_initialize(i8) == 0); 138 | assert(nogc_initialize(u16) == 0); 139 | assert(nogc_initialize(i16) == 0); 140 | assert(nogc_initialize(u32) == 0); 141 | assert(nogc_initialize(i32) == 0); 142 | assert(nogc_initialize(u64) == 0); 143 | assert(nogc_initialize(i64) == 0); 144 | 145 | // Class references 146 | TestClass tclass = reinterpret_cast!TestClass(cast(void*)0xDEADBEEF); 147 | assert(cast(void*)nogc_initialize(tclass) is null); 148 | 149 | // Create new un-constructed class, allocated on the stack. 150 | import numem.core.traits : AllocSize; 151 | ubyte[AllocSize!TestClass] allocSpace; 152 | auto klass = nogc_initialize!TestClass(cast(void[])allocSpace); 153 | 154 | // Attempt to use the class. 155 | // NOTE: In this case due to the simplicitly of the class, the constructor 156 | // is not neccesary to run; but eg. NuRefCounted *would* need it. 157 | klass.value = 42; 158 | assert(&klass.func1 !is null); 159 | assert(klass.func1() == 42); 160 | 161 | // Initializing struct should blit its initial state back in. 162 | TestStruct strukt = TestStruct(1000); 163 | assert(strukt.value == 1000); 164 | assert(nogc_initialize(strukt).value == 0); 165 | } 166 | 167 | // Test creating destroy-with 168 | @nu_destroywith!((ref value) { value.i = 42; }) 169 | struct ValueT { 170 | int i = 0; 171 | } 172 | 173 | @("nu_destroywith") 174 | unittest { 175 | ValueT myvalue; 176 | 177 | nogc_delete(myvalue); 178 | assert(myvalue.i == 42); 179 | } 180 | 181 | extern(C++) 182 | class TestCPPClass { 183 | @nogc: 184 | int value; 185 | 186 | this(int value) { this.value = value; } 187 | ~this() { } 188 | } 189 | 190 | @("C++ ctor-dtor") 191 | unittest { 192 | TestCPPClass myClass = nogc_new!TestCPPClass(42); 193 | 194 | assert(myClass.value == 42); 195 | nogc_delete(myClass); 196 | } 197 | 198 | extern(C++) 199 | struct TestCPPStruct { 200 | @nogc: 201 | int value; 202 | } 203 | 204 | @("C++ ctor-dtor") 205 | unittest { 206 | TestCPPStruct* myStruct = nogc_new!TestCPPStruct(42); 207 | 208 | assert(myStruct.value == 42); 209 | nogc_delete(myStruct); 210 | } 211 | 212 | @("basic types") 213 | unittest { 214 | import numem.core.memory : nu_dup; 215 | 216 | string myString = "Hello, world!".nu_dup(); 217 | nogc_delete(myString); 218 | } -------------------------------------------------------------------------------- /tests/ut/object.d: -------------------------------------------------------------------------------- 1 | module ut.object; 2 | import numem.object; 3 | import numem.lifetime; 4 | import numem.core.hooks; 5 | 6 | class MyRCClass : NuRefCounted { 7 | @nogc: 8 | private: 9 | int secret; 10 | 11 | public: 12 | ~this() { } 13 | 14 | this(int secret) { 15 | super(); 16 | this.secret = secret; 17 | } 18 | 19 | int getSecret() { 20 | return secret; 21 | } 22 | } 23 | 24 | class TrackedClass : NuRefCounted { 25 | @nogc: 26 | private: 27 | __gshared uint rcClassCount; 28 | 29 | public: 30 | ~this() { rcClassCount--; } 31 | this() { rcClassCount++; } 32 | } 33 | 34 | @("refcounted create-destroy.") 35 | unittest { 36 | MyRCClass rcclass = nogc_new!MyRCClass(42); 37 | assert(rcclass.getSecret() == 42); 38 | assert(rcclass.release() is null); 39 | } 40 | 41 | @("refcounted pool") 42 | unittest { 43 | autoreleasepool(() { 44 | foreach(i; 0..100) { 45 | nogc_new!TrackedClass().autoreleased(); 46 | } 47 | 48 | assert(TrackedClass.rcClassCount == 100); 49 | }); 50 | assert(TrackedClass.rcClassCount == 0); 51 | } 52 | 53 | @("numem nogc overloads") 54 | @nogc 55 | unittest { 56 | MyRCClass rcclass = nogc_new!MyRCClass(42); 57 | 58 | assert(rcclass.toString() == typeid(MyRCClass).name); 59 | assert(rcclass.toHash() == rcclass.toHash()); // Just to ensure they are properly nogc. 60 | assert(rcclass.opCmp(rcclass) == 0); 61 | assert(rcclass.opEquals(rcclass)); // Just to ensure they are properly nogc. 62 | rcclass.release(); 63 | } 64 | 65 | @nu_destroywith!((ref obj){ }) 66 | class CDestroyWith : NuObject { 67 | private: 68 | @nogc: 69 | __gshared uint fCount = 0; 70 | 71 | public: 72 | ~this() { fCount++; } 73 | } 74 | 75 | @("nu_destroywith") 76 | unittest { 77 | auto dwith = nogc_new!CDestroyWith(); 78 | 79 | assert(nogc_trydelete(dwith)); 80 | assert(CDestroyWith.fCount == 0); 81 | } --------------------------------------------------------------------------------