├── .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 |
3 |
4 |
5 | [](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 | }
--------------------------------------------------------------------------------