mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd8e280835 | ||
|
|
34c2d8ce77 | ||
|
|
28b9ff8016 | ||
|
|
76f226f536 | ||
|
|
1b0ac13d76 | ||
|
|
f134b8b67a | ||
|
|
33b05bcfeb | ||
|
|
6d3c1f5d2f | ||
|
|
96943ca66f | ||
|
|
374d73af12 | ||
|
|
81397f0726 | ||
|
|
0c33b78a2f | ||
|
|
ee5b9e5826 | ||
|
|
5d3c3781e4 | ||
|
|
c13c0d04b1 | ||
|
|
88f44f1eac | ||
|
|
50680d6893 | ||
|
|
31096531e1 | ||
|
|
062a67fe75 | ||
|
|
1dfc24822e | ||
|
|
e35c7f0b90 | ||
|
|
7083b2b8e5 | ||
|
|
135213388d | ||
|
|
ed62268997 | ||
|
|
38110b0269 | ||
|
|
e34d56327a | ||
|
|
b50e6bd0e4 | ||
|
|
3ba68f85fe | ||
|
|
9f5c5a9acf | ||
|
|
b6f5938eda | ||
|
|
87725a3a9e | ||
|
|
70029cc4b8 | ||
|
|
4f72bc4be9 | ||
|
|
3a1aa8bdf0 | ||
|
|
a986d053c0 | ||
|
|
43943c1f33 | ||
|
|
3da9f73338 | ||
|
|
855be92881 | ||
|
|
e674deb486 | ||
|
|
3ef094a3d3 | ||
|
|
9c60c2cb33 | ||
|
|
b54d994475 | ||
|
|
80e360d8dd | ||
|
|
9f165342e2 | ||
|
|
a2bfeb156d | ||
|
|
bb8c03777d | ||
|
|
8338888976 | ||
|
|
79db06ecd1 | ||
|
|
341a70bd5d | ||
|
|
8bf9ca89a1 | ||
|
|
5046608d1f | ||
|
|
535151a2a5 | ||
|
|
b45cb22950 | ||
|
|
d6485ca08b | ||
|
|
d9e5926d57 | ||
|
|
cbacd64987 | ||
|
|
168c11e006 | ||
|
|
26362d5068 | ||
|
|
e77d1fb646 | ||
|
|
c41d551ead |
28
.github/workflows/main.yml
vendored
28
.github/workflows/main.yml
vendored
@@ -52,9 +52,9 @@ jobs:
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
cd resources/testproject
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32 --trust=full
|
||||
dir build\llvm_ir
|
||||
dir build\llvm\windows-x64
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe clean
|
||||
dir build\llvm_ir
|
||||
dir build\llvm\windows-x64
|
||||
|
||||
|
||||
- name: Build testproject lib
|
||||
@@ -95,8 +95,7 @@ jobs:
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3.exe src/tester.py ..\build\${{ matrix.build_type }}\c3c.exe test_suite/
|
||||
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run -O1 src/test_suite_runner.c3 --stdlib ..\lib7 --enable-new-generics -- ..\build\${{ matrix.build_type }}\c3c.exe test_suite7/ --stdlib ../lib7 --no-terminal
|
||||
|
||||
- name: Test python script
|
||||
run: |
|
||||
@@ -144,6 +143,7 @@ jobs:
|
||||
run: |
|
||||
cd resources
|
||||
../build/c3c compile-run --print-linking examples/hello_world_many.c3
|
||||
../build/c3c compile-run --print-linking examples/process.c3
|
||||
../build/c3c compile-run --print-linking examples/time.c3
|
||||
../build/c3c compile-run --print-linking examples/fannkuch-redux.c3
|
||||
../build/c3c compile-run --print-linking examples/contextfree/boolerr.c3
|
||||
@@ -168,8 +168,8 @@ jobs:
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c.exe test_suite/
|
||||
|
||||
../build/c3c.exe compile --target windows-x64 -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics
|
||||
./test_suite_runner.exe ../build/c3c.exe test_suite7/ --stdlib ../lib7 --no-terminal
|
||||
|
||||
build-msys2-clang:
|
||||
runs-on: windows-latest
|
||||
@@ -220,7 +220,7 @@ jobs:
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c.exe test_suite/
|
||||
../build/c3c.exe compile-run -O1 --stdlib ../lib7 src/test_suite_runner.c3 --enable-new-generics -- ../build/c3c.exe test_suite7/ --stdlib ../lib7 --no-terminal
|
||||
|
||||
build-linux:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -381,7 +381,7 @@ jobs:
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
../build/c3c compile-run -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics -- ../build/c3c test_suite7/ --stdlib ../lib7
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
|
||||
@@ -502,7 +502,7 @@ jobs:
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
../build/c3c compile-run -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics -- ../build/c3c test_suite7/ --stdlib ../lib7
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
|
||||
@@ -606,7 +606,7 @@ jobs:
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
../build/c3c compile-run -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics -- ../build/c3c test_suite7/ --stdlib ../lib7
|
||||
|
||||
build-mac:
|
||||
runs-on: macos-latest
|
||||
@@ -686,7 +686,13 @@ jobs:
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
../build/c3c compile -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics
|
||||
./test_suite_runner ../build/c3c test_suite7/ --stdlib ../lib7
|
||||
|
||||
- name: run build test suite runner
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
|
||||
|
||||
@@ -44,8 +44,8 @@ if(MSVC)
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
|
||||
else()
|
||||
if (true)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O0 -fno-exceptions")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O0 -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
|
||||
else()
|
||||
|
||||
@@ -138,7 +138,7 @@ fn void main()
|
||||
|
||||
### Current status
|
||||
|
||||
The current stable version of the compiler is **version 0.6.6**.
|
||||
The current stable version of the compiler is **version 0.6.7**.
|
||||
|
||||
The upcoming 0.6.x releases will focus on expanding the standard library.
|
||||
Follow the issues [here](https://github.com/c3lang/c3c/issues).
|
||||
|
||||
@@ -21,7 +21,7 @@ struct AnyList (Printable)
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = null)
|
||||
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = null) @deprecated("Use init(mem)")
|
||||
{
|
||||
return self.init(allocator ?: allocator::heap(), initial_capacity) @inline;
|
||||
}
|
||||
@@ -52,7 +52,18 @@ fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16)
|
||||
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16) @deprecated("Use tinit")
|
||||
{
|
||||
return self.init(allocator::temp(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(allocator::temp(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
@@ -86,15 +86,30 @@ struct GrowableBitSet
|
||||
@param initial_capacity
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
*>
|
||||
fn GrowableBitSet* GrowableBitSet.new_init(&self, usz initial_capacity = 1, Allocator allocator = allocator::heap())
|
||||
fn GrowableBitSet* GrowableBitSet.new_init(&self, usz initial_capacity = 1, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
|
||||
{
|
||||
self.data.new_init(initial_capacity, allocator);
|
||||
self.data.init(allocator, initial_capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn GrowableBitSet* GrowableBitSet.temp_init(&self, usz initial_capacity = 1)
|
||||
<*
|
||||
@param initial_capacity
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
*>
|
||||
fn GrowableBitSet* GrowableBitSet.init(&self, Allocator allocator, usz initial_capacity = 1)
|
||||
{
|
||||
return self.new_init(initial_capacity, allocator::temp()) @inline;
|
||||
self.data.init(allocator, initial_capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn GrowableBitSet* GrowableBitSet.temp_init(&self, usz initial_capacity = 1) @deprecated("Use tinit()")
|
||||
{
|
||||
return self.init(allocator::temp(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn GrowableBitSet* GrowableBitSet.tinit(&self, usz initial_capacity = 1)
|
||||
{
|
||||
return self.init(allocator::temp(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.free(&self)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
<*
|
||||
@require $defined(Key{}.hash()) `No .hash function found on the key`
|
||||
@require $defined((Key){}.hash()) `No .hash function found on the key`
|
||||
*>
|
||||
module std::collections::map(<Key, Value>);
|
||||
import std::math;
|
||||
@@ -24,7 +24,7 @@ struct HashMap (Printable)
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.new_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = null)
|
||||
fn HashMap* HashMap.new_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = null) @deprecated("Use init(mem)")
|
||||
{
|
||||
return self.init(allocator ?: allocator::heap(), capacity, load_factor);
|
||||
}
|
||||
@@ -52,7 +52,18 @@ fn HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INI
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
fn HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) @deprecated("Use tinit()")
|
||||
{
|
||||
return self.init(allocator::temp(), capacity, load_factor) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init(allocator::temp(), capacity, load_factor) @inline;
|
||||
}
|
||||
@@ -65,9 +76,9 @@ fn HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, f
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashMap* HashMap.new_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
macro HashMap* HashMap.new_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap()) @deprecated("Use init_with_key_values(mem)")
|
||||
{
|
||||
self.new_init(capacity, load_factor, allocator);
|
||||
self.init(capacity, load_factor, allocator);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
self.set($vaarg[$i], $vaarg[$i+1]);
|
||||
$endfor
|
||||
@@ -84,10 +95,10 @@ macro HashMap* HashMap.new_init_with_key_values(&self, ..., uint capacity = DEFA
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.new_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
fn HashMap* HashMap.new_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap()) @deprecated("Use init_from_keys_and_values(mem)")
|
||||
{
|
||||
assert(keys.len == values.len);
|
||||
self.new_init(capacity, load_factor, allocator);
|
||||
self.init(allocator, capacity, load_factor);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
self.set(keys[i], values[i]);
|
||||
@@ -102,9 +113,25 @@ fn HashMap* HashMap.new_init_from_keys_and_values(&self, Key[] keys, Value[] val
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashMap* HashMap.temp_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
macro HashMap* HashMap.temp_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) @deprecated("Use tinit_with_key_values")
|
||||
{
|
||||
self.temp_init(capacity, load_factor);
|
||||
self.tinit(capacity, load_factor);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
self.set($vaarg[$i], $vaarg[$i+1]);
|
||||
$endfor
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashMap* HashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.tinit(capacity, load_factor);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
self.set($vaarg[$i], $vaarg[$i+1]);
|
||||
$endfor
|
||||
@@ -121,10 +148,31 @@ macro HashMap* HashMap.temp_init_with_key_values(&self, ..., uint capacity = DEF
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.temp_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
fn HashMap* HashMap.temp_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap()) @deprecated("Use tinit_from_keys_and_values")
|
||||
{
|
||||
assert(keys.len == values.len);
|
||||
self.temp_init(capacity, load_factor);
|
||||
self.tinit(capacity, load_factor);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
self.set(keys[i], values[i]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys "The keys for the HashMap entries"
|
||||
@param [in] values "The values for the HashMap entries"
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require keys.len == values.len "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
assert(keys.len == values.len);
|
||||
self.tinit(capacity, load_factor);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
self.set(keys[i], values[i]);
|
||||
@@ -146,18 +194,18 @@ fn bool HashMap.is_initialized(&map)
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn HashMap* HashMap.new_init_from_map(&self, HashMap* other_map)
|
||||
fn HashMap* HashMap.new_init_from_map(&self, HashMap* other_map) @deprecated("Use init_from_map(mem, map)")
|
||||
{
|
||||
return self.init_from_map(other_map, allocator::heap()) @inline;
|
||||
return self.init_from_map(allocator::heap(), other_map) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn HashMap* HashMap.init_from_map(&self, HashMap* other_map, Allocator allocator)
|
||||
fn HashMap* HashMap.init_from_map(&self, Allocator allocator, HashMap* other_map)
|
||||
{
|
||||
self.new_init(other_map.table.len, other_map.load_factor, allocator);
|
||||
self.init(allocator, other_map.table.len, other_map.load_factor);
|
||||
self.put_all_for_create(other_map);
|
||||
return self;
|
||||
}
|
||||
@@ -165,9 +213,17 @@ fn HashMap* HashMap.init_from_map(&self, HashMap* other_map, Allocator allocator
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map)
|
||||
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map) @deprecated("Use tinit_from_map")
|
||||
{
|
||||
return map.init_from_map(other_map, allocator::temp()) @inline;
|
||||
return map.init_from_map(allocator::temp(), other_map) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn HashMap* HashMap.tinit_from_map(&map, HashMap* other_map)
|
||||
{
|
||||
return map.init_from_map(allocator::temp(), other_map) @inline;
|
||||
}
|
||||
|
||||
fn bool HashMap.is_empty(&map) @inline
|
||||
@@ -240,7 +296,7 @@ fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
|
||||
// If the map isn't initialized, use the defaults to initialize it.
|
||||
if (!map.allocator)
|
||||
{
|
||||
map.new_init();
|
||||
map.init(allocator::heap());
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
|
||||
@@ -33,13 +33,18 @@ fn LinkedList* LinkedList.init(&self, Allocator allocator)
|
||||
<*
|
||||
@return "the initialized list"
|
||||
*>
|
||||
fn LinkedList* LinkedList.new_init(&self)
|
||||
fn LinkedList* LinkedList.new_init(&self) @deprecated("Use init(mem)")
|
||||
{
|
||||
return self.init(allocator::heap()) @inline;
|
||||
}
|
||||
|
||||
|
||||
fn LinkedList* LinkedList.temp_init(&self)
|
||||
fn LinkedList* LinkedList.temp_init(&self) @deprecated("Use tinit()")
|
||||
{
|
||||
return self.init(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
fn LinkedList* LinkedList.tinit(&self)
|
||||
{
|
||||
return self.init(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct List (Printable)
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
*>
|
||||
fn List* List.init(&self, usz initial_capacity = 16, Allocator allocator)
|
||||
fn List* List.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
@@ -37,7 +37,7 @@ fn List* List.init(&self, usz initial_capacity = 16, Allocator allocator)
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
*>
|
||||
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
|
||||
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
@@ -52,9 +52,19 @@ fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = a
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn List* List.temp_init(&self, usz initial_capacity = 16)
|
||||
fn List* List.temp_init(&self, usz initial_capacity = 16) @deprecated("Use tinit()")
|
||||
{
|
||||
return self.init(initial_capacity, allocator::temp()) @inline;
|
||||
return self.init(allocator::temp(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn List* List.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(allocator::temp(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -63,9 +73,20 @@ fn List* List.temp_init(&self, usz initial_capacity = 16)
|
||||
@param [in] values `The values to initialize the list with.`
|
||||
@require self.size == 0 "The List must be empty"
|
||||
*>
|
||||
fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = allocator::heap())
|
||||
fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = allocator::heap()) @deprecated("Use init_with_array(mem)")
|
||||
{
|
||||
self.new_init(values.len, allocator) @inline;
|
||||
return self.init_with_array(allocator, values);
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a new list with an array.
|
||||
|
||||
@param [in] values `The values to initialize the list with.`
|
||||
@require self.size == 0 "The List must be empty"
|
||||
*>
|
||||
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
|
||||
{
|
||||
self.init(allocator, values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
return self;
|
||||
}
|
||||
@@ -76,9 +97,22 @@ fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = al
|
||||
@param [in] values `The values to initialize the list with.`
|
||||
@require self.size == 0 "The List must be empty"
|
||||
*>
|
||||
fn List* List.temp_init_with_array(&self, Type[] values)
|
||||
fn List* List.temp_init_with_array(&self, Type[] values) @deprecated("Use tinit_with_array()")
|
||||
{
|
||||
self.temp_init(values.len) @inline;
|
||||
self.tinit(values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a temporary list with an array.
|
||||
|
||||
@param [in] values `The values to initialize the list with.`
|
||||
@require self.size == 0 "The List must be empty"
|
||||
*>
|
||||
fn List* List.tinit_with_array(&self, Type[] values)
|
||||
{
|
||||
self.tinit(values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ module std::collections::list_common;
|
||||
*>
|
||||
macro list_to_new_aligned_array($Type, self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return $Type[] {};
|
||||
if (!self.size) return ($Type[]){};
|
||||
$Type[] result = allocator::alloc_array_aligned(allocator, $Type, self.size);
|
||||
result[..] = self.entries[:self.size];
|
||||
return result;
|
||||
@@ -13,7 +13,7 @@ macro list_to_new_aligned_array($Type, self, Allocator allocator)
|
||||
|
||||
macro list_to_new_array($Type, self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return $Type[] {};
|
||||
if (!self.size) return ($Type[]){};
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, self.size);
|
||||
result[..] = self.entries[:self.size];
|
||||
return result;
|
||||
|
||||
@@ -26,7 +26,7 @@ struct MapImpl
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn Map new(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
fn Map new(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap()) @deprecated("Map is deprecated")
|
||||
{
|
||||
MapImpl* map = allocator::alloc(allocator, MapImpl);
|
||||
_init(map, capacity, load_factor, allocator);
|
||||
|
||||
@@ -28,12 +28,12 @@ fn Maybe value(Type val)
|
||||
return { .value = val, .has_value = true };
|
||||
}
|
||||
|
||||
fn Maybe Maybe.with_value(Type val) @operator(construct)
|
||||
fn Maybe Maybe.with_value(Type val) @deprecated("Use maybe::value instead.") @operator(construct)
|
||||
{
|
||||
return { .value = val, .has_value = true };
|
||||
}
|
||||
|
||||
fn Maybe Maybe.empty() @operator(construct)
|
||||
fn Maybe Maybe.empty() @deprecated("Use maybe::EMPTY instead.") @operator(construct)
|
||||
{
|
||||
return { };
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ fn void Object.init_map_if_needed(&self) @private
|
||||
if (self.is_empty())
|
||||
{
|
||||
self.type = ObjectInternalMap.typeid;
|
||||
self.map.new_init(allocator: self.allocator);
|
||||
self.map.init(self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ fn void Object.init_array_if_needed(&self) @private
|
||||
if (self.is_empty())
|
||||
{
|
||||
self.type = ObjectInternalList.typeid;
|
||||
self.array.new_init(allocator: self.allocator);
|
||||
self.array.init(self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ struct PrivatePriorityQueue (Printable)
|
||||
Heap heap;
|
||||
}
|
||||
|
||||
fn void PrivatePriorityQueue.init(&self, Allocator allocator, usz initial_capacity = 16, ) @inline
|
||||
{
|
||||
self.heap.new_init(initial_capacity, allocator);
|
||||
}
|
||||
|
||||
fn void PrivatePriorityQueue.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @inline
|
||||
{
|
||||
self.heap.new_init(initial_capacity, allocator);
|
||||
|
||||
@@ -33,7 +33,7 @@ struct ArenaAllocatorHeader @local
|
||||
|
||||
macro ArenaAllocator* wrap(char[] bytes)
|
||||
{
|
||||
return ArenaAllocator{}.init(bytes);
|
||||
return (ArenaAllocator){}.init(bytes);
|
||||
}
|
||||
|
||||
<*
|
||||
|
||||
@@ -164,14 +164,21 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
DynamicArenaPage* page = self.page;
|
||||
void* ptr = {|
|
||||
|
||||
void* ptr @noinit;
|
||||
do SET_DONE:
|
||||
{
|
||||
if (!page && self.unused_page)
|
||||
{
|
||||
self.page = page = self.unused_page;
|
||||
self.unused_page = page.prev_arena;
|
||||
page.prev_arena = null;
|
||||
}
|
||||
if (!page) return self._alloc_new(size, alignment);
|
||||
if (!page)
|
||||
{
|
||||
ptr = self._alloc_new(size, alignment)!;
|
||||
break SET_DONE;
|
||||
}
|
||||
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
|
||||
usz new_used = start - page.memory + size;
|
||||
if ALLOCATE_NEW: (new_used > page.total)
|
||||
@@ -188,15 +195,15 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
|
||||
break ALLOCATE_NEW;
|
||||
}
|
||||
}
|
||||
return self._alloc_new(size, alignment);
|
||||
ptr = self._alloc_new(size, alignment)!;
|
||||
break SET_DONE;
|
||||
}
|
||||
page.used = new_used;
|
||||
assert(start + size == page.memory + page.used);
|
||||
void* mem = start;
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
|
||||
ptr = start;
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)ptr - 1;
|
||||
chunk.size = size;
|
||||
return mem;
|
||||
|}!;
|
||||
};
|
||||
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ struct TrackingAllocator (Allocator)
|
||||
fn void TrackingAllocator.init(&self, Allocator allocator)
|
||||
{
|
||||
*self = { .inner_allocator = allocator };
|
||||
self.map.new_init(allocator: allocator);
|
||||
self.map.init(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
|
||||
@@ -146,7 +146,7 @@ fn void panicf(String fmt, String file, String function, uint line, args...)
|
||||
@stack_mem(512; Allocator allocator)
|
||||
{
|
||||
DString s;
|
||||
s.new_init(allocator: allocator);
|
||||
s.init(allocator);
|
||||
s.appendf(fmt, ...args);
|
||||
in_panic = false;
|
||||
panic(s.str_view(), file, function, line);
|
||||
@@ -238,7 +238,7 @@ macro enum_by_name($Type, String enum_name) @builtin
|
||||
@param $Type `The type of the enum`
|
||||
@require $Type.kindof == ENUM `Only enums may be used`
|
||||
@require $defined($Type.#value) `Expected '#value' to match an enum associated value`
|
||||
@require $assignable(value, $typeof($Type{}.#value)) `Expected the value to match the type of the associated value`
|
||||
@require $assignable(value, $typeof(($Type){}.#value)) `Expected the value to match the type of the associated value`
|
||||
@ensure @typeis(return, $Type)
|
||||
@return! SearchResult.MISSING
|
||||
*>
|
||||
@@ -351,7 +351,7 @@ macro swizzle2(v, v2, ...) @builtin
|
||||
macro anyfault @catch(#expr) @builtin
|
||||
{
|
||||
if (catch f = #expr) return f;
|
||||
return anyfault {};
|
||||
return {};
|
||||
}
|
||||
|
||||
<*
|
||||
|
||||
@@ -9,7 +9,7 @@ const usz MIN_CAPACITY @private = 16;
|
||||
<*
|
||||
@require !self.data() "String already initialized"
|
||||
*>
|
||||
fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap())
|
||||
fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
|
||||
{
|
||||
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
|
||||
StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
|
||||
@@ -22,15 +22,37 @@ fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator alloca
|
||||
<*
|
||||
@require !self.data() "String already initialized"
|
||||
*>
|
||||
fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY)
|
||||
fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY)
|
||||
{
|
||||
self.new_init(capacity, allocator::temp()) @inline;
|
||||
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
|
||||
StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
|
||||
data.allocator = allocator;
|
||||
data.len = 0;
|
||||
data.capacity = capacity;
|
||||
return *self = (DString)data;
|
||||
}
|
||||
|
||||
<*
|
||||
@require !self.data() "String already initialized"
|
||||
*>
|
||||
fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY) @deprecated("Use tinit()")
|
||||
{
|
||||
self.init(allocator::temp(), capacity) @inline;
|
||||
return *self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require !self.data() "String already initialized"
|
||||
*>
|
||||
fn DString DString.tinit(&self, usz capacity = MIN_CAPACITY)
|
||||
{
|
||||
self.init(allocator::temp(), capacity) @inline;
|
||||
return *self;
|
||||
}
|
||||
|
||||
fn DString new_with_capacity(usz capacity, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return DString{}.new_init(capacity, allocator);
|
||||
return (DString){}.init(allocator, capacity);
|
||||
}
|
||||
|
||||
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, allocator::temp()) @inline;
|
||||
@@ -99,16 +121,25 @@ fn void DString.replace(&self, String needle, String replacement)
|
||||
};
|
||||
}
|
||||
|
||||
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
|
||||
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap()) @deprecated("Use concat(mem)")
|
||||
{
|
||||
DString string;
|
||||
string.new_init(self.len() + b.len(), allocator);
|
||||
string.init(allocator, self.len() + b.len());
|
||||
string.append(self);
|
||||
string.append(b);
|
||||
return string;
|
||||
}
|
||||
|
||||
fn DString DString.temp_concat(self, DString b) => self.new_concat(b, allocator::temp());
|
||||
fn DString DString.concat(self, Allocator allocator, DString b)
|
||||
{
|
||||
DString string;
|
||||
string.init(allocator, self.len() + b.len());
|
||||
string.append(self);
|
||||
string.append(b);
|
||||
return string;
|
||||
}
|
||||
|
||||
fn DString DString.temp_concat(self, DString b) => self.concat(allocator::temp(), b);
|
||||
|
||||
fn ZString DString.zstr_view(&self)
|
||||
{
|
||||
@@ -543,7 +574,7 @@ macro void DString.insert_at(&self, usz index, value)
|
||||
|
||||
fn usz! DString.appendf(&self, String format, args...) @maydiscard
|
||||
{
|
||||
if (!self.data()) self.new_init(format.len + 20);
|
||||
if (!self.data()) self.init(mem, format.len + 20);
|
||||
@pool(self.data().allocator)
|
||||
{
|
||||
Formatter formatter;
|
||||
@@ -554,7 +585,7 @@ fn usz! DString.appendf(&self, String format, args...) @maydiscard
|
||||
|
||||
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
|
||||
{
|
||||
if (!self.data()) self.new_init(format.len + 20);
|
||||
if (!self.data()) self.init(mem, format.len + 20);
|
||||
@pool(self.data().allocator)
|
||||
{
|
||||
Formatter formatter;
|
||||
|
||||
@@ -360,6 +360,7 @@ macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
|
||||
// All allocators
|
||||
|
||||
|
||||
def mem = thread_allocator @builtin;
|
||||
tlocal Allocator thread_allocator @private = base_allocator();
|
||||
Allocator temp_base_allocator @private = base_allocator();
|
||||
|
||||
|
||||
@@ -150,14 +150,14 @@ fn void x86_initialize_cpu_features()
|
||||
{
|
||||
uint max_level = x86_cpuid(0).eax;
|
||||
CpuId feat = x86_cpuid(1);
|
||||
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : CpuId {};
|
||||
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : CpuId {};
|
||||
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : CpuId {};
|
||||
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : CpuId {};
|
||||
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : CpuId {};
|
||||
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : CpuId {};
|
||||
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : CpuId {};
|
||||
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : CpuId {};
|
||||
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : {};
|
||||
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : {};
|
||||
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : {};
|
||||
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : {};
|
||||
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : {};
|
||||
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : {};
|
||||
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : {};
|
||||
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : {};
|
||||
add_feature_if_bit(ADX, leaf7.ebx, 19);
|
||||
add_feature_if_bit(AES, feat.ecx, 25);
|
||||
add_feature_if_bit(AMX_BF16, leaf7.edx, 22);
|
||||
|
||||
@@ -101,7 +101,7 @@ macro find_segment_section_body(MachHeader* header, char* segname, char* sectnam
|
||||
Section64*! section = find_section(find_segment(header, segname), sectname);
|
||||
if (catch section)
|
||||
{
|
||||
return $Type[] {};
|
||||
return ($Type[]){};
|
||||
}
|
||||
$Type* ptr = (void*)header + section.offset;
|
||||
return ptr[:section.size / $Type.sizeof];
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::runtime;
|
||||
import std::core::test @public;
|
||||
import std::core::mem::allocator @public;
|
||||
import libc, std::time, std::io, std::sort;
|
||||
import std::os::env;
|
||||
|
||||
@@ -23,6 +24,8 @@ struct TestContext
|
||||
bool assert_print_backtrace;
|
||||
bool has_ansi_codes;
|
||||
bool is_in_panic;
|
||||
bool is_quiet_mode;
|
||||
bool is_no_capture;
|
||||
String current_test_name;
|
||||
TestFn setup_fn;
|
||||
TestFn teardown_fn;
|
||||
@@ -30,8 +33,12 @@ struct TestContext
|
||||
char* error_buffer;
|
||||
usz error_buffer_capacity;
|
||||
File fake_stdout;
|
||||
File orig_stdout;
|
||||
File orig_stderr;
|
||||
struct stored
|
||||
{
|
||||
File stdout;
|
||||
File stderr;
|
||||
Allocator allocator;
|
||||
}
|
||||
}
|
||||
|
||||
struct TestUnit
|
||||
@@ -68,7 +75,6 @@ fn int cmp_test_unit(TestUnit a, TestUnit b)
|
||||
|
||||
fn bool terminal_has_ansi_codes() @local => @pool()
|
||||
{
|
||||
|
||||
if (try v = env::get_var_temp("TERM"))
|
||||
{
|
||||
if (v.contains("xterm") || v.contains("vt100") || v.contains("screen")) return true;
|
||||
@@ -107,47 +113,34 @@ fn void test_panic(String message, String file, String function, uint line) @loc
|
||||
}
|
||||
|
||||
test_context.is_in_panic = false;
|
||||
allocator::thread_allocator = test_context.stored.allocator;
|
||||
libc::longjmp(&test_context.buf, 1);
|
||||
}
|
||||
|
||||
fn void mute_output() @local
|
||||
{
|
||||
if (!test_context.fake_stdout.file) return;
|
||||
assert(!test_context.orig_stderr.file);
|
||||
assert(!test_context.orig_stdout.file);
|
||||
|
||||
if (test_context.is_no_capture || !test_context.fake_stdout.file) return;
|
||||
File* stdout = io::stdout();
|
||||
File* stderr = io::stderr();
|
||||
|
||||
test_context.orig_stderr = *stderr;
|
||||
test_context.orig_stdout = *stdout;
|
||||
|
||||
File* stderr = io::stderr();
|
||||
*stderr = test_context.fake_stdout;
|
||||
*stdout = test_context.fake_stdout;
|
||||
|
||||
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
|
||||
}
|
||||
|
||||
fn void unmute_output(bool has_error) @local
|
||||
{
|
||||
if (!test_context.fake_stdout.file)
|
||||
{
|
||||
return;
|
||||
}
|
||||
assert(test_context.orig_stderr.file);
|
||||
assert(test_context.orig_stdout.file);
|
||||
if (test_context.is_no_capture || !test_context.fake_stdout.file) return;
|
||||
|
||||
File* stdout = io::stdout();
|
||||
File* stderr = io::stderr();
|
||||
|
||||
*stderr = test_context.orig_stderr;
|
||||
*stdout = test_context.orig_stdout;
|
||||
test_context.orig_stderr.file = null;
|
||||
test_context.orig_stdout.file = null;
|
||||
*stderr = test_context.stored.stderr;
|
||||
*stdout = test_context.stored.stdout;
|
||||
|
||||
usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
|
||||
if (has_error)
|
||||
{
|
||||
io::printf("\nTesting %s ", test_context.current_test_name);
|
||||
io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
|
||||
}
|
||||
|
||||
@@ -163,7 +156,7 @@ fn void unmute_output(bool has_error) @local
|
||||
{
|
||||
if (@unlikely(c == '\0'))
|
||||
{
|
||||
// ignore junk from previous tests
|
||||
// ignore junk from previous tests
|
||||
break;
|
||||
}
|
||||
libc::putchar(c);
|
||||
@@ -177,6 +170,7 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
{
|
||||
usz max_name;
|
||||
bool sort_tests = true;
|
||||
bool check_leaks = true;
|
||||
foreach (&unit : tests)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
@@ -187,20 +181,29 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
.breakpoint_on_assert = false,
|
||||
.test_filter = "",
|
||||
.has_ansi_codes = terminal_has_ansi_codes(),
|
||||
.stored.allocator = allocator::heap(),
|
||||
.stored.stderr = *io::stderr(),
|
||||
.stored.stdout = *io::stdout(),
|
||||
};
|
||||
for (int i = 1; i < args.len; i++)
|
||||
{
|
||||
switch (args[i])
|
||||
{
|
||||
case "breakpoint":
|
||||
case "--test-breakpoint":
|
||||
context.breakpoint_on_assert = true;
|
||||
case "nosort":
|
||||
case "--test-nosort":
|
||||
sort_tests = false;
|
||||
case "noansi":
|
||||
case "--test-noleak":
|
||||
check_leaks = false;
|
||||
case "--test-nocapture":
|
||||
context.is_no_capture = true;
|
||||
case "--noansi":
|
||||
context.has_ansi_codes = false;
|
||||
case "useansi":
|
||||
case "--useansi":
|
||||
context.has_ansi_codes = true;
|
||||
case "filter":
|
||||
case "--test-quiet":
|
||||
context.is_quiet_mode = true;
|
||||
case "--test-filter":
|
||||
if (i == args.len - 1)
|
||||
{
|
||||
io::printn("Invalid arguments to test runner.");
|
||||
@@ -221,9 +224,9 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
|
||||
// Buffer for hijacking the output
|
||||
$if (!env::NO_LIBC):
|
||||
test_context.fake_stdout.file = libc::tmpfile();
|
||||
context.fake_stdout.file = libc::tmpfile();
|
||||
$endif
|
||||
if (test_context.fake_stdout.file == null)
|
||||
if (context.fake_stdout.file == null)
|
||||
{
|
||||
io::print("Failed to hijack stdout, tests will print everything");
|
||||
}
|
||||
@@ -239,56 +242,73 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
name.append_repeat('-', len / 2);
|
||||
name.append(" TESTS ");
|
||||
name.append_repeat('-', len - len / 2);
|
||||
io::printn(name);
|
||||
if (!context.is_quiet_mode) io::printn(name);
|
||||
name.clear();
|
||||
TempState temp_state = mem::temp_push();
|
||||
defer mem::temp_pop(temp_state);
|
||||
foreach(unit : tests)
|
||||
{
|
||||
if (test_context.test_filter && !unit.name.contains(test_context.test_filter))
|
||||
mem::temp_pop(temp_state);
|
||||
if (context.test_filter && !unit.name.contains(context.test_filter))
|
||||
{
|
||||
tests_skipped++;
|
||||
continue;
|
||||
}
|
||||
test_context.setup_fn = null;
|
||||
test_context.teardown_fn = null;
|
||||
test_context.current_test_name = unit.name;
|
||||
context.setup_fn = null;
|
||||
context.teardown_fn = null;
|
||||
context.current_test_name = unit.name;
|
||||
|
||||
defer name.clear();
|
||||
name.appendf("Testing %s ", unit.name);
|
||||
name.append_repeat('.', max_name - unit.name.len + 2);
|
||||
io::printf("%s ", name.str_view());
|
||||
if (context.is_quiet_mode)
|
||||
{
|
||||
io::print(".");
|
||||
}
|
||||
else
|
||||
{
|
||||
io::printf("%s ", name.str_view());
|
||||
}
|
||||
(void)io::stdout().flush();
|
||||
TrackingAllocator mem;
|
||||
mem.init(allocator::heap());
|
||||
|
||||
mem.init(context.stored.allocator);
|
||||
if (libc::setjmp(&context.buf) == 0)
|
||||
{
|
||||
mute_output();
|
||||
mem.clear();
|
||||
mem::@scoped(&mem)
|
||||
if (check_leaks) allocator::thread_allocator = &mem;
|
||||
$if(!$$OLD_TEST):
|
||||
unit.func();
|
||||
$else
|
||||
if (catch err = unit.func())
|
||||
{
|
||||
io::printf("[FAIL] Failed due to: %s", err);
|
||||
continue;
|
||||
}
|
||||
$endif
|
||||
// track cleanup that may take place in teardown_fn
|
||||
if (context.teardown_fn)
|
||||
{
|
||||
$if(!$$OLD_TEST):
|
||||
unit.func();
|
||||
$else
|
||||
if (catch err = unit.func())
|
||||
{
|
||||
io::printf("[FAIL] Failed due to: %s", err);
|
||||
continue;
|
||||
}
|
||||
$endif
|
||||
};
|
||||
context.teardown_fn();
|
||||
}
|
||||
if (check_leaks) allocator::thread_allocator = context.stored.allocator;
|
||||
|
||||
unmute_output(false); // all good, discard output
|
||||
if (mem.has_leaks())
|
||||
{
|
||||
io::print(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
|
||||
io::printn(" LEAKS DETECTED!");
|
||||
mem.print_report();
|
||||
}
|
||||
else
|
||||
{
|
||||
io::printfn(test_context.has_ansi_codes ? "[\e[0;32mPASS\e[0m]" : "[PASS]");
|
||||
tests_passed++;
|
||||
}
|
||||
if (test_context.teardown_fn)
|
||||
{
|
||||
test_context.teardown_fn();
|
||||
if(context.is_quiet_mode) io::printf("\n%s ", context.current_test_name);
|
||||
io::print(context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
|
||||
io::printn(" LEAKS DETECTED!");
|
||||
mem.print_report();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!context.is_quiet_mode)
|
||||
{
|
||||
io::printfn(context.has_ansi_codes ? "[\e[0;32mPASS\e[0m]" : "[PASS]");
|
||||
}
|
||||
tests_passed++;
|
||||
}
|
||||
}
|
||||
mem.free();
|
||||
@@ -297,9 +317,9 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
|
||||
int n_failed = test_count - tests_passed - tests_skipped;
|
||||
io::printf("Test Result: %s%s%s: ",
|
||||
test_context.has_ansi_codes ? (n_failed ? "\e[0;31m" : "\e[0;32m") : "",
|
||||
context.has_ansi_codes ? (n_failed ? "\e[0;31m" : "\e[0;32m") : "",
|
||||
n_failed ? "FAILED" : "PASSED",
|
||||
test_context.has_ansi_codes ? "\e[0m" : "",
|
||||
context.has_ansi_codes ? "\e[0m" : "",
|
||||
);
|
||||
|
||||
io::printfn("%d passed, %d failed, %d skipped.",
|
||||
@@ -308,8 +328,8 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
tests_skipped);
|
||||
|
||||
// cleanup fake_stdout file
|
||||
if (test_context.fake_stdout.file) libc::fclose(test_context.fake_stdout.file);
|
||||
test_context.fake_stdout.file = null;
|
||||
if (context.fake_stdout.file) libc::fclose(context.fake_stdout.file);
|
||||
context.fake_stdout.file = null;
|
||||
|
||||
return n_failed == 0;
|
||||
}
|
||||
|
||||
@@ -780,26 +780,28 @@ macro String.to_integer(string, $Type, int base = 10)
|
||||
$Type value = 0;
|
||||
while (index != len)
|
||||
{
|
||||
char c = {|
|
||||
char ch = string[index++];
|
||||
if (base_used != 16 || ch < 'A') return (char)(ch - '0');
|
||||
if (ch <= 'F') return (char)(ch - 'A' + 10);
|
||||
if (ch < 'a') return NumberConversion.MALFORMED_INTEGER?;
|
||||
if (ch > 'f') return NumberConversion.MALFORMED_INTEGER?;
|
||||
return (char)(ch - 'a' + 10);
|
||||
|}!;
|
||||
char c = string[index++];
|
||||
switch
|
||||
{
|
||||
case base_used != 16 || c < 'A': c -= '0';
|
||||
case c <= 'F': c -= 'A' - 10;
|
||||
case c < 'a' || c > 'f': return NumberConversion.MALFORMED_INTEGER?;
|
||||
default: c -= 'a' - 10;
|
||||
}
|
||||
if (c >= base_used) return NumberConversion.MALFORMED_INTEGER?;
|
||||
value = {|
|
||||
do
|
||||
{
|
||||
if (is_negative)
|
||||
{
|
||||
$Type new_value = value * base_used - c;
|
||||
if (new_value > value) return NumberConversion.INTEGER_OVERFLOW?;
|
||||
return new_value;
|
||||
value = new_value;
|
||||
break;
|
||||
}
|
||||
$Type new_value = value * base_used + c;
|
||||
if (new_value < value) return NumberConversion.INTEGER_OVERFLOW?;
|
||||
return new_value;
|
||||
|}!;
|
||||
value = new_value;
|
||||
};
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -868,7 +870,7 @@ macro String new_struct_to_str(x, Allocator allocator = allocator::heap())
|
||||
DString s;
|
||||
@stack_mem(512; Allocator mem)
|
||||
{
|
||||
s.new_init(allocator: mem);
|
||||
s.init(mem);
|
||||
io::fprint(&s, x)!!;
|
||||
return s.copy_str(allocator);
|
||||
};
|
||||
|
||||
@@ -376,10 +376,7 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
else
|
||||
{
|
||||
got_digit = true;
|
||||
int d = {|
|
||||
if (c > '9') return (c | 32) + 10 - 'a';
|
||||
return c - '0';
|
||||
|};
|
||||
int d = c > '9' ? ((c | 32) + 10 - 'a') : (c - '0');
|
||||
switch
|
||||
{
|
||||
case dc < 8:
|
||||
|
||||
@@ -72,7 +72,7 @@ macro @check(#condition, String format = "", args...)
|
||||
@stack_mem(512; Allocator allocator)
|
||||
{
|
||||
DString s;
|
||||
s.new_init(allocator: allocator);
|
||||
s.init(allocator);
|
||||
s.appendf("check `%s` failed. ", $stringify(#condition));
|
||||
s.appendf(format, ...args);
|
||||
print_panicf(s.str_view());
|
||||
|
||||
@@ -162,12 +162,12 @@ macro bool is_unsigned($Type) @const
|
||||
|
||||
macro bool is_indexable($Type) @const
|
||||
{
|
||||
return $defined($Type{}[0]);
|
||||
return $defined(($Type){}[0]);
|
||||
}
|
||||
|
||||
macro bool is_ref_indexable($Type) @const
|
||||
{
|
||||
return $defined(&$Type{}[0]);
|
||||
return $defined(&($Type){}[0]);
|
||||
}
|
||||
|
||||
macro bool is_intlike($Type) @const
|
||||
@@ -251,6 +251,7 @@ macro bool @has_same(#a, #b, ...) @const
|
||||
macro bool may_load_atomic($Type) @const
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$case BOOL:
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
$case POINTER:
|
||||
|
||||
@@ -17,12 +17,12 @@ fault JsonParsingError
|
||||
|
||||
fn Object*! parse_string(String s, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return parse(ByteReader{}.init(s), allocator);
|
||||
return parse((ByteReader){}.init(s), allocator);
|
||||
}
|
||||
|
||||
fn Object*! temp_parse_string(String s)
|
||||
{
|
||||
return parse(ByteReader{}.init(s), allocator::temp());
|
||||
return parse((ByteReader){}.init(s), allocator::temp());
|
||||
}
|
||||
|
||||
fn Object*! parse(InStream s, Allocator allocator = allocator::heap())
|
||||
|
||||
94
lib/std/experimental/FrameScheduler.c3
Normal file
94
lib/std/experimental/FrameScheduler.c3
Normal file
@@ -0,0 +1,94 @@
|
||||
module std::experimental::scheduler(<Event>);
|
||||
import std::collections, std::thread, std::time;
|
||||
|
||||
struct DelayedSchedulerEvent @local
|
||||
{
|
||||
inline Event event;
|
||||
Clock execution_time;
|
||||
}
|
||||
|
||||
fn int DelayedSchedulerEvent.compare_to(self, DelayedSchedulerEvent other) @local
|
||||
{
|
||||
switch
|
||||
{
|
||||
case self.execution_time < other.execution_time: return -1;
|
||||
case self.execution_time > other.execution_time: return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct FrameScheduler
|
||||
{
|
||||
PriorityQueue(<DelayedSchedulerEvent>) delayed_events;
|
||||
List(<Event>) events;
|
||||
List(<Event>) pending_events;
|
||||
bool pending;
|
||||
Mutex mtx;
|
||||
}
|
||||
|
||||
fn void FrameScheduler.init(&self)
|
||||
{
|
||||
self.events.init(mem);
|
||||
self.pending_events.init(mem);
|
||||
self.delayed_events.init(mem);
|
||||
(void)self.mtx.init();
|
||||
bool pending;
|
||||
}
|
||||
|
||||
macro void FrameScheduler.@destroy(&self; @destruct(Event e))
|
||||
{
|
||||
foreach (e : self.events) @destruct(e);
|
||||
foreach (e : self.pending_events) @destruct(e);
|
||||
foreach (e : self.delayed_events.heap) @destruct(e.event);
|
||||
self.events.free();
|
||||
self.pending_events.free();
|
||||
self.delayed_events.free();
|
||||
(void)self.mtx.destroy();
|
||||
}
|
||||
|
||||
fn void FrameScheduler.queue_delayed_event(&self, Event event, Duration delay)
|
||||
{
|
||||
self.mtx.@in_lock()
|
||||
{
|
||||
self.delayed_events.push({ event, clock::now().add_duration(delay)});
|
||||
@atomic_store(self.pending, true);
|
||||
};
|
||||
}
|
||||
|
||||
fn bool FrameScheduler.has_delayed(&self)
|
||||
{
|
||||
self.mtx.@in_lock()
|
||||
{
|
||||
return @ok(self.delayed_events.first());
|
||||
};
|
||||
}
|
||||
|
||||
fn void FrameScheduler.queue_event(&self, Event event)
|
||||
{
|
||||
self.mtx.@in_lock()
|
||||
{
|
||||
self.pending_events.push(event);
|
||||
@atomic_store(self.pending, true);
|
||||
};
|
||||
}
|
||||
fn Event! FrameScheduler.pop_event(&self)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (try event = self.events.pop()) return event;
|
||||
if (!@atomic_load(self.pending)) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.mtx.@in_lock()
|
||||
{
|
||||
self.events.add_all(&self.pending_events);
|
||||
self.pending_events.clear();
|
||||
Clock c = clock::now();
|
||||
while (try top = self.delayed_events.first())
|
||||
{
|
||||
if (top.execution_time > c) break;
|
||||
self.events.push(self.delayed_events.pop()!!);
|
||||
}
|
||||
@atomic_store(self.pending, self.delayed_events.len() > 0);
|
||||
if (!self.events.len()) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -78,10 +78,10 @@ fn char[HASH_BYTES] Sha1.final(&self)
|
||||
{
|
||||
finalcount[i] = (char)((self.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
|
||||
}
|
||||
self.update(char[] { 0o200 });
|
||||
self.update((char[]){ 0o200 });
|
||||
while ((self.count[0] & 504) != 448)
|
||||
{
|
||||
self.update(char[] { 0 });
|
||||
self.update((char[]){ 0 });
|
||||
}
|
||||
|
||||
self.update(&finalcount);
|
||||
|
||||
@@ -19,6 +19,11 @@ fn File! open_path(Path path, String mode)
|
||||
return from_handle(os::native_fopen(path.str_view(), mode));
|
||||
}
|
||||
|
||||
fn bool exists(String file) => @pool()
|
||||
{
|
||||
return path::exists(path::temp_new(file)) ?? false;
|
||||
}
|
||||
|
||||
fn File from_handle(CFile file)
|
||||
{
|
||||
return { .file = file };
|
||||
@@ -170,6 +175,8 @@ fn char[]! load_buffer(String filename, char[] buffer)
|
||||
|
||||
}
|
||||
|
||||
fn char[]! load(Allocator allocator, String filename) => load_new(filename, allocator);
|
||||
|
||||
fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
|
||||
{
|
||||
File file = open(filename, "rb")!;
|
||||
@@ -186,11 +193,15 @@ fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
|
||||
return data[:len];
|
||||
}
|
||||
|
||||
fn char[]! load_path_new(Path path, Allocator allocator = allocator::heap()) => load_new(path.str_view(), allocator);
|
||||
|
||||
fn char[]! load_temp(String filename)
|
||||
{
|
||||
return load_new(filename, allocator::temp());
|
||||
}
|
||||
|
||||
fn char[]! load_path_temp(Path path) => load_temp(path.str_view());
|
||||
|
||||
fn void! save(String filename, char[] data)
|
||||
{
|
||||
File file = open(filename, "wb")!;
|
||||
|
||||
@@ -145,7 +145,10 @@ fn usz! Formatter.print_with_function(&self, Printable arg)
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
|
||||
fn usz! Formatter.out_unknown(&self, String category, any arg) @private
|
||||
{
|
||||
return self.out_substr("[") + self.out_substr(category) + self.out_substr(" type:") + self.ntoa((iptr)arg.type, false, 16) + self.out_substr(", addr:") + self.ntoa((iptr)arg.ptr, false, 16) + self.out_substr("]");
|
||||
}
|
||||
fn usz! Formatter.out_str(&self, any arg) @private
|
||||
{
|
||||
switch (arg.type.kindof)
|
||||
@@ -199,13 +202,13 @@ fn usz! Formatter.out_str(&self, any arg) @private
|
||||
assert(i < arg.type.names.len, "Illegal enum value found, numerical value was %d.", i);
|
||||
return self.out_substr(arg.type.names[i]);
|
||||
case STRUCT:
|
||||
return self.out_substr("<struct>");
|
||||
return self.out_unknown("struct", arg);
|
||||
case UNION:
|
||||
return self.out_substr("<union>");
|
||||
return self.out_unknown("union", arg);
|
||||
case BITSTRUCT:
|
||||
return self.out_substr("<bitstruct>");
|
||||
return self.out_unknown("bitstruct", arg);
|
||||
case FUNC:
|
||||
return self.out_substr("<function>");
|
||||
return self.out_unknown("function", arg);
|
||||
case DISTINCT:
|
||||
if (arg.type == String.typeid)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ import std::io, std::os;
|
||||
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
|
||||
{
|
||||
PathList list;
|
||||
list.new_init(allocator: allocator);
|
||||
list.init(allocator);
|
||||
DIRPtr directory = posix::opendir(dir.str_view() ? dir.as_zstr() : (ZString)".");
|
||||
defer if (directory) posix::closedir(directory);
|
||||
if (!directory) return (path::is_dir(dir) ? IoError.CANNOT_READ_DIR : IoError.FILE_NOT_DIR)?;
|
||||
@@ -27,7 +27,7 @@ import std::time, std::os, std::io;
|
||||
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
|
||||
{
|
||||
PathList list;
|
||||
list.new_init(allocator: allocator);
|
||||
list.init(allocator);
|
||||
|
||||
@pool(allocator)
|
||||
{
|
||||
|
||||
@@ -274,7 +274,7 @@ fn Path! Path.new_absolute(self, Allocator allocator = allocator::heap())
|
||||
};
|
||||
$else
|
||||
String cwd = os::getcwd(allocator::temp())!;
|
||||
return Path { cwd, self.env }.new_append(path_str, allocator)!;
|
||||
return (Path){ cwd, self.env }.new_append(path_str, allocator)!;
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,23 @@ macro usz! read_all(stream, char[] buffer)
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
macro char[]! read_new_fully(stream, Allocator allocator = allocator::heap())
|
||||
macro char[]! read_new_fully(stream, Allocator allocator = allocator::heap()) @deprecated("Use read_fully(mem)")
|
||||
{
|
||||
usz len = available(stream)!;
|
||||
char* data = allocator::malloc_try(allocator, len)!;
|
||||
defer catch allocator::free(allocator, data);
|
||||
usz read = 0;
|
||||
while (read < len)
|
||||
{
|
||||
read += stream.read(data[read:len - read])!;
|
||||
}
|
||||
return data[:len];
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
macro char[]! read_fully(Allocator allocator, stream)
|
||||
{
|
||||
usz len = available(stream)!;
|
||||
char* data = allocator::malloc_try(allocator, len)!;
|
||||
@@ -181,12 +197,12 @@ fn usz! copy_to(InStream in, OutStream dst, char[] buffer = {})
|
||||
if (&dst.read_to) return dst.read_to(in);
|
||||
$switch (env::MEMORY_ENV)
|
||||
$case NORMAL:
|
||||
return copy_through_buffer(in, dst, &&char[4096]{});
|
||||
return copy_through_buffer(in, dst, &&(char[4096]){});
|
||||
$case SMALL:
|
||||
return copy_through_buffer(in, dst, &&char[1024]{});
|
||||
return copy_through_buffer(in, dst, &&(char[1024]){});
|
||||
$case TINY:
|
||||
$case NONE:
|
||||
return copy_through_buffer(in, dst, &&(char[256]{}));
|
||||
return copy_through_buffer(in, dst, &&(char[256]){});
|
||||
$endswitch
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ struct ByteBuffer (InStream, OutStream)
|
||||
max_read defines how many bytes might be kept before its internal buffer is shrinked.
|
||||
@require self.bytes.len == 0 "Buffer already initialized."
|
||||
*>
|
||||
fn ByteBuffer* ByteBuffer.new_init(&self, usz max_read, usz initial_capacity = 16, Allocator allocator = allocator::heap())
|
||||
fn ByteBuffer* ByteBuffer.init(&self, Allocator allocator, usz max_read, usz initial_capacity = 16)
|
||||
{
|
||||
*self = { .allocator = allocator, .max_read = max_read };
|
||||
initial_capacity = max(initial_capacity, 16);
|
||||
@@ -24,9 +24,27 @@ fn ByteBuffer* ByteBuffer.new_init(&self, usz max_read, usz initial_capacity = 1
|
||||
return self;
|
||||
}
|
||||
|
||||
fn ByteBuffer* ByteBuffer.temp_init(&self, usz max_read, usz initial_capacity = 16)
|
||||
<*
|
||||
ByteBuffer provides a streamable read/write buffer.
|
||||
max_read defines how many bytes might be kept before its internal buffer is shrinked.
|
||||
@require self.bytes.len == 0 "Buffer already initialized."
|
||||
*>
|
||||
fn ByteBuffer* ByteBuffer.new_init(&self, usz max_read, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
|
||||
{
|
||||
return self.new_init(max_read, initial_capacity, allocator::temp());
|
||||
*self = { .allocator = allocator, .max_read = max_read };
|
||||
initial_capacity = max(initial_capacity, 16);
|
||||
self.grow(initial_capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn ByteBuffer* ByteBuffer.tinit(&self, usz max_read, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(allocator::temp(), max_read, initial_capacity);
|
||||
}
|
||||
|
||||
fn ByteBuffer* ByteBuffer.temp_init(&self, usz max_read, usz initial_capacity = 16) @deprecated("Use tinit()")
|
||||
{
|
||||
return self.init(allocator::temp(), max_read, initial_capacity);
|
||||
}
|
||||
|
||||
<*
|
||||
|
||||
@@ -14,7 +14,19 @@ struct ByteWriter (OutStream)
|
||||
@require self.bytes.len == 0 "Init may not run on already initialized data"
|
||||
@ensure (bool)allocator, self.index == 0
|
||||
*>
|
||||
fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap())
|
||||
fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
|
||||
{
|
||||
*self = { .bytes = {}, .allocator = allocator };
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self
|
||||
@param [&inout] allocator
|
||||
@require self.bytes.len == 0 "Init may not run on already initialized data"
|
||||
@ensure (bool)allocator, self.index == 0
|
||||
*>
|
||||
fn ByteWriter* ByteWriter.init(&self, Allocator allocator)
|
||||
{
|
||||
*self = { .bytes = {}, .allocator = allocator };
|
||||
return self;
|
||||
@@ -25,9 +37,19 @@ fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap(
|
||||
@require self.bytes.len == 0 "Init may not run on already initialized data"
|
||||
@ensure self.index == 0
|
||||
*>
|
||||
fn ByteWriter* ByteWriter.temp_init(&self)
|
||||
fn ByteWriter* ByteWriter.tinit(&self)
|
||||
{
|
||||
return self.new_init(allocator::temp()) @inline;
|
||||
return self.init(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self
|
||||
@require self.bytes.len == 0 "Init may not run on already initialized data"
|
||||
@ensure self.index == 0
|
||||
*>
|
||||
fn ByteWriter* ByteWriter.temp_init(&self) @deprecated("Use tinit")
|
||||
{
|
||||
return self.init(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
|
||||
|
||||
@@ -18,7 +18,21 @@ struct MultiReader (InStream)
|
||||
@require self.readers.len == 0 "Init may not run on already initialized data"
|
||||
@ensure self.index == 0
|
||||
*>
|
||||
fn MultiReader* MultiReader.new_init(&self, InStream... readers, Allocator allocator = allocator::heap())
|
||||
fn MultiReader* MultiReader.new_init(&self, InStream... readers, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
|
||||
{
|
||||
InStream []copy = allocator::new_array(allocator, InStream, readers.len);
|
||||
copy[..] = readers[..];
|
||||
*self = { .readers = copy, .allocator = allocator };
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self
|
||||
@param [&inout] allocator
|
||||
@require self.readers.len == 0 "Init may not run on already initialized data"
|
||||
@ensure self.index == 0
|
||||
*>
|
||||
fn MultiReader* MultiReader.init(&self, Allocator allocator, InStream... readers)
|
||||
{
|
||||
InStream []copy = allocator::new_array(allocator, InStream, readers.len);
|
||||
copy[..] = readers[..];
|
||||
@@ -31,9 +45,19 @@ fn MultiReader* MultiReader.new_init(&self, InStream... readers, Allocator alloc
|
||||
@require self.readers.len == 0 "Init may not run on already initialized data"
|
||||
@ensure self.index == 0
|
||||
*>
|
||||
fn MultiReader* MultiReader.temp_init(&self, InStream... readers)
|
||||
fn MultiReader* MultiReader.temp_init(&self, InStream... readers) @deprecated("Use tinit()")
|
||||
{
|
||||
return self.new_init(...readers, allocator: allocator::temp());
|
||||
return self.init(allocator::temp(), ...readers);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self
|
||||
@require self.readers.len == 0 "Init may not run on already initialized data"
|
||||
@ensure self.index == 0
|
||||
*>
|
||||
fn MultiReader* MultiReader.tinit(&self, InStream... readers)
|
||||
{
|
||||
return self.init(allocator::temp(), ...readers);
|
||||
}
|
||||
|
||||
fn void MultiReader.free(&self)
|
||||
|
||||
@@ -15,7 +15,21 @@ struct MultiWriter (OutStream)
|
||||
@require writers.len > 0
|
||||
@require self.writers.len == 0 "Init may not run on already initialized data"
|
||||
*>
|
||||
fn MultiWriter* MultiWriter.new_init(&self, OutStream... writers, Allocator allocator = allocator::heap())
|
||||
fn MultiWriter* MultiWriter.init(&self, Allocator allocator, OutStream... writers)
|
||||
{
|
||||
OutStream[] copy = allocator::new_array(allocator, OutStream, writers.len);
|
||||
copy[..] = writers[..];
|
||||
*self = { .writers = copy, .allocator = allocator };
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self
|
||||
@param [&inout] allocator
|
||||
@require writers.len > 0
|
||||
@require self.writers.len == 0 "Init may not run on already initialized data"
|
||||
*>
|
||||
fn MultiWriter* MultiWriter.new_init(&self, OutStream... writers, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
|
||||
{
|
||||
OutStream[] copy = allocator::new_array(allocator, OutStream, writers.len);
|
||||
copy[..] = writers[..];
|
||||
@@ -28,9 +42,19 @@ fn MultiWriter* MultiWriter.new_init(&self, OutStream... writers, Allocator allo
|
||||
@require writers.len > 0
|
||||
@require self.writers.len == 0 "Init may not run on already initialized data"
|
||||
*>
|
||||
fn MultiWriter* MultiWriter.temp_init(&self, OutStream... writers)
|
||||
fn MultiWriter* MultiWriter.temp_init(&self, OutStream... writers) @deprecated("Use tinit")
|
||||
{
|
||||
return self.new_init(...writers, allocator: allocator::temp());
|
||||
return self.init(allocator::temp(), ...writers);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self
|
||||
@require writers.len > 0
|
||||
@require self.writers.len == 0 "Init may not run on already initialized data"
|
||||
*>
|
||||
fn MultiWriter* MultiWriter.tinit(&self, OutStream... writers)
|
||||
{
|
||||
return self.init(allocator::temp(), ...writers);
|
||||
}
|
||||
|
||||
fn void MultiWriter.free(&self)
|
||||
|
||||
@@ -44,16 +44,15 @@ fn BigInt* BigInt.init(&self, int128 value)
|
||||
fn BigInt* BigInt.init_with_u128(&self, uint128 value)
|
||||
{
|
||||
self.data[..] = 0;
|
||||
int128 tmp = value;
|
||||
uint128 tmp = value;
|
||||
uint len = 0;
|
||||
while (tmp != 0 && len < MAX_LEN)
|
||||
while (tmp != 0)
|
||||
{
|
||||
self.data[len] = (uint)(tmp & 0xFFFFFFFF);
|
||||
tmp >>= 32;
|
||||
len++;
|
||||
}
|
||||
self.len = len;
|
||||
assert(value == 0 || tmp == 0 || !self.is_negative());
|
||||
assert(!self.is_negative());
|
||||
self.len = max(len, 1);
|
||||
return self;
|
||||
}
|
||||
@@ -64,11 +63,18 @@ fn BigInt* BigInt.init_with_u128(&self, uint128 value)
|
||||
fn BigInt* BigInt.init_with_array(&self, uint[] values)
|
||||
{
|
||||
self.data[..] = 0;
|
||||
|
||||
if (values.len == 0)
|
||||
{
|
||||
self.len = 1;
|
||||
return self;
|
||||
}
|
||||
|
||||
self.len = values.len;
|
||||
|
||||
foreach_r(i, val : values)
|
||||
{
|
||||
values[values.len - 1 - i] = val;
|
||||
self.data[values.len - 1 - i] = val;
|
||||
}
|
||||
while (self.len > 1 && self.data[self.len - 1] == 0)
|
||||
{
|
||||
@@ -525,7 +531,7 @@ fn String BigInt.to_string_with_radix(&self, int radix, Allocator allocator)
|
||||
{
|
||||
BigInt a = *self;
|
||||
DString str;
|
||||
str.new_init(4096, allocator: mem);
|
||||
str.init(mem, 4096);
|
||||
bool negative = self.is_negative();
|
||||
if (negative)
|
||||
{
|
||||
|
||||
@@ -92,13 +92,13 @@ fault MatrixError
|
||||
|
||||
def Complexf = Complex(<float>);
|
||||
def Complex = Complex(<double>);
|
||||
def COMPLEX_IDENTITY = complex::IDENTITY(<double>);
|
||||
def COMPLEXF_IDENTITY = complex::IDENTITY(<float>);
|
||||
def COMPLEX_IDENTITY = complex::IDENTITY(<double>) @builtin;
|
||||
def COMPLEXF_IDENTITY = complex::IDENTITY(<float>) @builtin;
|
||||
|
||||
def Quaternionf = Quaternion(<float>);
|
||||
def Quaternion = Quaternion(<double>);
|
||||
def QUATERNION_IDENTITY = quaternion::IDENTITY(<double>);
|
||||
def QUATERNIONF_IDENTITY = quaternion::IDENTITY(<float>);
|
||||
def QUATERNION_IDENTITY = quaternion::IDENTITY(<double>) @builtin;
|
||||
def QUATERNIONF_IDENTITY = quaternion::IDENTITY(<float>) @builtin;
|
||||
|
||||
def Matrix2f = Matrix2x2(<float>);
|
||||
def Matrix2 = Matrix2x2(<double>);
|
||||
@@ -106,17 +106,17 @@ def Matrix3f = Matrix3x3(<float>);
|
||||
def Matrix3 = Matrix3x3(<double>);
|
||||
def Matrix4f = Matrix4x4(<float>);
|
||||
def Matrix4 = Matrix4x4(<double>);
|
||||
def matrix4_ortho = matrix::ortho(<double>);
|
||||
def matrix4_perspective = matrix::perspective(<double>);
|
||||
def matrix4f_ortho = matrix::ortho(<float>);
|
||||
def matrix4f_perspective = matrix::perspective(<float>);
|
||||
def matrix4_ortho = matrix::ortho(<double>) @builtin;
|
||||
def matrix4_perspective = matrix::perspective(<double>) @builtin;
|
||||
def matrix4f_ortho = matrix::ortho(<float>) @builtin;
|
||||
def matrix4f_perspective = matrix::perspective(<float>) @builtin;
|
||||
|
||||
def MATRIX2_IDENTITY = matrix::IDENTITY2(<double>);
|
||||
def MATRIX2F_IDENTITY = matrix::IDENTITY2(<float>);
|
||||
def MATRIX3_IDENTITY = matrix::IDENTITY3(<double>);
|
||||
def MATRIX3F_IDENTITY = matrix::IDENTITY3(<float>);
|
||||
def MATRIX4_IDENTITY = matrix::IDENTITY4(<double>);
|
||||
def MATRIX4F_IDENTITY = matrix::IDENTITY4(<float>);
|
||||
def MATRIX2_IDENTITY = matrix::IDENTITY2(<double>) @builtin;
|
||||
def MATRIX2F_IDENTITY = matrix::IDENTITY2(<float>) @builtin;
|
||||
def MATRIX3_IDENTITY = matrix::IDENTITY3(<double>) @builtin;
|
||||
def MATRIX3F_IDENTITY = matrix::IDENTITY3(<float>) @builtin;
|
||||
def MATRIX4_IDENTITY = matrix::IDENTITY4(<double>) @builtin;
|
||||
def MATRIX4F_IDENTITY = matrix::IDENTITY4(<float>) @builtin;
|
||||
|
||||
|
||||
<*
|
||||
|
||||
@@ -11,21 +11,21 @@ union Complex
|
||||
|
||||
const Complex IDENTITY = { 1, 0 };
|
||||
const Complex IMAGINARY = { 0, 1 };
|
||||
macro Complex Complex.add(self, Complex b) => Complex { .v = self.v + b.v };
|
||||
macro Complex Complex.add_each(self, Real b) => Complex { .v = self.v + b };
|
||||
macro Complex Complex.sub(self, Complex b) => Complex { .v = self.v - b.v };
|
||||
macro Complex Complex.sub_each(self, Real b) => Complex { .v = self.v - b };
|
||||
macro Complex Complex.scale(self, Real s) => Complex { .v = self.v * s };
|
||||
macro Complex Complex.add(self, Complex b) => { .v = self.v + b.v };
|
||||
macro Complex Complex.add_each(self, Real b) => { .v = self.v + b };
|
||||
macro Complex Complex.sub(self, Complex b) => { .v = self.v - b.v };
|
||||
macro Complex Complex.sub_each(self, Real b) => { .v = self.v - b };
|
||||
macro Complex Complex.scale(self, Real s) => { .v = self.v * s };
|
||||
macro Complex Complex.mul(self, Complex b) => { self.r * b.r - self.c * b.c, self.r * b.c + b.r * self.c };
|
||||
macro Complex Complex.div(self, Complex b)
|
||||
{
|
||||
Real div = b.v.dot(b.v);
|
||||
return Complex{ (self.r * b.r + self.c * b.c) / div, (self.c * b.r - self.r * b.c) / div };
|
||||
return { (self.r * b.r + self.c * b.c) / div, (self.c * b.r - self.r * b.c) / div };
|
||||
}
|
||||
macro Complex Complex.inverse(self)
|
||||
{
|
||||
Real sqr = self.v.dot(self.v);
|
||||
return Complex{ self.r / sqr, -self.c / sqr };
|
||||
return { self.r / sqr, -self.c / sqr };
|
||||
}
|
||||
macro Complex Complex.conjugate(self) => Complex { .r = self.r, .c = -self.c };
|
||||
macro Complex Complex.conjugate(self) => { .r = self.r, .c = -self.c };
|
||||
macro bool Complex.equals(self, Complex b) => self.v == b.v;
|
||||
|
||||
@@ -420,19 +420,19 @@ const Matrix4x4 IDENTITY4 = { .m = { [0] = 1, [5] = 1, [10] = 1, [15] = 1 } };
|
||||
macro matrix_component_mul(mat, val) @private
|
||||
{
|
||||
var $Type = Real[<$typeof(mat.m).len>];
|
||||
return $typeof(*mat) { .m = val * ($Type)mat.m };
|
||||
return ($typeof(*mat)) { .m = val * ($Type)mat.m };
|
||||
}
|
||||
|
||||
macro matrix_add(mat, mat2) @private
|
||||
{
|
||||
var $Type = Real[<$typeof(mat.m).len>];
|
||||
return $typeof(*mat) { .m = ($Type)mat.m + ($Type)mat2.m };
|
||||
return ($typeof(*mat)) { .m = ($Type)mat.m + ($Type)mat2.m };
|
||||
}
|
||||
|
||||
macro matrix_sub(mat, mat2) @private
|
||||
{
|
||||
var $Type = Real[<$typeof(mat.m).len>];
|
||||
return $typeof(*mat) { .m = ($Type)mat.m - ($Type)mat2.m };
|
||||
return ($typeof(*mat)) { .m = ($Type)mat.m - ($Type)mat2.m };
|
||||
}
|
||||
|
||||
macro matrix_look_at($Type, eye, target, up) @private
|
||||
@@ -441,7 +441,7 @@ macro matrix_look_at($Type, eye, target, up) @private
|
||||
var vx = up.cross(vz).normalize();
|
||||
var vy = vz.cross(vx);
|
||||
|
||||
return $Type {
|
||||
return ($Type){
|
||||
vx[0], vx[1], vx[2], - (Real)vx.dot(eye),
|
||||
vy[0], vy[1], vy[2], - (Real)vy.dot(eye),
|
||||
vz[0], vz[1], vz[2], - (Real)vz.dot(eye),
|
||||
|
||||
@@ -11,11 +11,11 @@ union Quaternion
|
||||
|
||||
const Quaternion IDENTITY = { 0, 0, 0, 1 };
|
||||
|
||||
macro Quaternion Quaternion.add(Quaternion a, Quaternion b) => Quaternion { .v = a.v + b.v };
|
||||
macro Quaternion Quaternion.add_each(Quaternion a, Real b) => Quaternion { .v = a.v + b };
|
||||
macro Quaternion Quaternion.sub(Quaternion a, Quaternion b) => Quaternion { .v = a.v - b.v };
|
||||
macro Quaternion Quaternion.sub_each(Quaternion a, Real b) => Quaternion { .v = a.v - b };
|
||||
macro Quaternion Quaternion.scale(Quaternion a, Real s) => Quaternion { .v = a.v * s };
|
||||
macro Quaternion Quaternion.add(Quaternion a, Quaternion b) => { .v = a.v + b.v };
|
||||
macro Quaternion Quaternion.add_each(Quaternion a, Real b) => { .v = a.v + b };
|
||||
macro Quaternion Quaternion.sub(Quaternion a, Quaternion b) => { .v = a.v - b.v };
|
||||
macro Quaternion Quaternion.sub_each(Quaternion a, Real b) => { .v = a.v - b };
|
||||
macro Quaternion Quaternion.scale(Quaternion a, Real s) => { .v = a.v * s };
|
||||
macro Quaternion Quaternion.normalize(Quaternion q) => { .v = q.v.normalize() };
|
||||
macro Real Quaternion.length(Quaternion q) => q.v.length();
|
||||
macro Quaternion Quaternion.lerp(Quaternion q1, Quaternion q2, Real amount) => { .v = q1.v.lerp(q2.v, amount) };
|
||||
@@ -76,7 +76,7 @@ macro into_matrix(Quaternion* q, $Type) @private
|
||||
var z = rotation.k;
|
||||
var w = rotation.l;
|
||||
|
||||
return $Type {
|
||||
return ($Type) {
|
||||
1 - 2*y*y - 2*z*z, 2*x*y - 2*z*w, 2*x*z + 2*y*w, 0,
|
||||
2*x*y + 2*z*w, 1 - 2*x*x - 2*z*z, 2*y*z - 2*x*w, 0,
|
||||
2*x*z - 2*y*w, 2*y*z + 2*x*w , 1 - 2*x*x - 2*y*y, 0,
|
||||
|
||||
@@ -145,7 +145,7 @@ macro transform2(v, mat) @private
|
||||
|
||||
macro transform3(v, mat) @private
|
||||
{
|
||||
return $typeof(v) {
|
||||
return ($typeof(v)){
|
||||
mat.m00 * v[0] + mat.m10 * v[1] + mat.m20 * v[2] + mat.m30,
|
||||
mat.m01 * v[0] + mat.m11 * v[1] + mat.m21 * v[2] + mat.m31,
|
||||
mat.m02 * v[0] + mat.m12 * v[1] + mat.m22 * v[2] + mat.m32
|
||||
@@ -169,7 +169,7 @@ macro void ortho_normalize3(v1, v2) @private
|
||||
|
||||
macro rotate_by_quat3(v, q) @private
|
||||
{
|
||||
return $typeof(v) {
|
||||
return ($typeof(v)){
|
||||
v[0] * (q.i * q.i + q.l * q.l - q.j * q.j - q.k * q.k)
|
||||
+ v[1] * (2 * q.i * q.j - 2 * q.l * q.k)
|
||||
+ v[2] * (2 * q.i * q.k - 2 * q.l * q.j),
|
||||
@@ -233,7 +233,7 @@ macro barycenter3(p, a, b, c) @private
|
||||
var denom = d00 * d11 - d01 * d01;
|
||||
var y = (d11 * d20 - d01 * d21) / denom;
|
||||
var z = (d00 * d21 - d01 * d20) / denom;
|
||||
return $typeof(p) { 1 - y - z, y, z };
|
||||
return ($typeof(p)){ 1 - y - z, y, z };
|
||||
}
|
||||
|
||||
macro refract3(v, n, r) @private
|
||||
|
||||
@@ -86,7 +86,7 @@ fn char[8 * 4] entropy() @if(!env::WASM_NOLIBC)
|
||||
hash(&entropy),
|
||||
random_int,
|
||||
hash(clock::now()),
|
||||
hash(&DString.new_init),
|
||||
hash(&DString.init),
|
||||
hash(allocator::heap())
|
||||
};
|
||||
return bitcast(entropy_data, char[8 * 4]);
|
||||
|
||||
@@ -275,7 +275,7 @@ fn UrlQueryValues parse_query(String query, Allocator allocator)
|
||||
{
|
||||
UrlQueryValues vals;
|
||||
vals.map.init(allocator);
|
||||
vals.key_order.new_init(allocator: allocator);
|
||||
vals.key_order.init(allocator);
|
||||
|
||||
Splitter raw_vals = query.tokenize("&");
|
||||
while (try String rv = raw_vals.next())
|
||||
@@ -309,7 +309,7 @@ fn UrlQueryValues* UrlQueryValues.add(&self, String key, String value)
|
||||
else
|
||||
{
|
||||
UrlQueryValueList new_list;
|
||||
new_list.new_init_with_array({ value_copy }, self.allocator);
|
||||
new_list.init_with_array(self.allocator, { value_copy });
|
||||
(*self)[key] = new_list;
|
||||
self.key_order.push(key.copy(self.allocator));
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ fn ulong! elf_module_image_base(String path) @local
|
||||
defer (void)file.close();
|
||||
char[4] buffer;
|
||||
io::read_all(&file, &buffer)!;
|
||||
if (buffer != char[4]{ 0x7f, 'E', 'L', 'F'}) return BacktraceFault.IMAGE_NOT_FOUND?;
|
||||
if (buffer != { 0x7f, 'E', 'L', 'F'}) return BacktraceFault.IMAGE_NOT_FOUND?;
|
||||
bool is_64 = file.read_byte()! == 2;
|
||||
bool is_little_endian = file.read_byte()! == 1;
|
||||
// Actually, not supported.
|
||||
@@ -218,7 +218,7 @@ fn void! backtrace_add_element(BacktraceList *list, void* addr, Allocator alloca
|
||||
fn BacktraceList! symbolize_backtrace(void*[] backtrace, Allocator allocator)
|
||||
{
|
||||
BacktraceList list;
|
||||
list.new_init(backtrace.len, allocator);
|
||||
list.init(allocator, backtrace.len);
|
||||
defer catch
|
||||
{
|
||||
foreach (trace : list)
|
||||
|
||||
@@ -136,7 +136,7 @@ fn BacktraceList! symbolize_backtrace(void*[] backtrace, Allocator allocator)
|
||||
{
|
||||
void *load_addr = (void *)load_address()!;
|
||||
BacktraceList list;
|
||||
list.new_init(backtrace.len, allocator);
|
||||
list.init(allocator, backtrace.len);
|
||||
defer catch
|
||||
{
|
||||
foreach (trace : list)
|
||||
|
||||
@@ -75,10 +75,12 @@ fn void! create_named_pipe_helper(void** rd, void **wr) @local @if(env::WIN32)
|
||||
fn WString convert_command_line_win32(String[] command_line) @inline @if(env::WIN32) @local
|
||||
{
|
||||
DString str = dstring::temp_with_capacity(512);
|
||||
foreach (i, s : command_line)
|
||||
foreach LINE: (i, s : command_line)
|
||||
{
|
||||
if (i != 0) str.append(' ');
|
||||
bool needs_escape = {|
|
||||
|
||||
do CHECK_WS:
|
||||
{
|
||||
foreach (c : s)
|
||||
{
|
||||
switch (c)
|
||||
@@ -86,16 +88,12 @@ fn WString convert_command_line_win32(String[] command_line) @inline @if(env::WI
|
||||
case '\t':
|
||||
case ' ':
|
||||
case '\v':
|
||||
return true;
|
||||
break CHECK_WS;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|};
|
||||
if (!needs_escape)
|
||||
{
|
||||
str.append(s);
|
||||
continue;
|
||||
}
|
||||
continue LINE;
|
||||
};
|
||||
str.append('"');
|
||||
foreach (j, c : s)
|
||||
{
|
||||
@@ -178,12 +176,13 @@ fn SubProcess! create(String[] command_line, SubProcessOptions options = {}, Str
|
||||
|
||||
start_info.hStdOutput = wr;
|
||||
|
||||
{|
|
||||
do
|
||||
{
|
||||
if (options.combined_stdout_stderr)
|
||||
{
|
||||
stderr = stdout;
|
||||
start_info.hStdError = start_info.hStdOutput;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
if (options.read_async)
|
||||
{
|
||||
@@ -202,8 +201,7 @@ fn SubProcess! create(String[] command_line, SubProcessOptions options = {}, Str
|
||||
if (!stderr) return SubProcessResult.FAILED_TO_OPEN_STDERR?;
|
||||
}
|
||||
start_info.hStdError = wr;
|
||||
|
||||
|}!;
|
||||
};
|
||||
void *event_output;
|
||||
void *event_error;
|
||||
if (options.read_async)
|
||||
@@ -323,11 +321,17 @@ fn SubProcess! create(String[] command_line, SubProcessOptions options = {}, Str
|
||||
CFile stdin = libc::fdopen(stdinfd[1], "wb");
|
||||
libc::close(stdoutfd[1]);
|
||||
CFile stdout = libc::fdopen(stdoutfd[0], "rb");
|
||||
CFile stderr = {|
|
||||
if (options.combined_stdout_stderr) return stdout;
|
||||
CFile stderr @noinit;
|
||||
do
|
||||
{
|
||||
if (options.combined_stdout_stderr)
|
||||
{
|
||||
stderr = stdout;
|
||||
break;
|
||||
}
|
||||
libc::close(stderrfd[1]);
|
||||
return libc::fdopen(stderrfd[0], "rb");
|
||||
|};
|
||||
stderr = libc::fdopen(stderrfd[0], "rb");
|
||||
};
|
||||
return {
|
||||
.stdin_file = stdin,
|
||||
.stdout_file = stdout,
|
||||
@@ -358,6 +362,11 @@ fn File SubProcess.stdout(&self)
|
||||
return file::from_handle(self.stdout_file);
|
||||
}
|
||||
|
||||
fn File SubProcess.stderr(&self)
|
||||
{
|
||||
return file::from_handle(self.stderr_file);
|
||||
}
|
||||
|
||||
fn CInt! SubProcess.join(&self) @if(env::WIN32)
|
||||
{
|
||||
if (self.stdin_file)
|
||||
|
||||
@@ -157,7 +157,7 @@ Win32_DWORD64 displacement;
|
||||
fn BacktraceList! symbolize_backtrace(void*[] backtrace, Allocator allocator)
|
||||
{
|
||||
BacktraceList list;
|
||||
list.new_init(backtrace.len, allocator);
|
||||
list.init(allocator, backtrace.len);
|
||||
Win32_HANDLE process = getCurrentProcess();
|
||||
symInitialize(process, null, 1);
|
||||
defer symCleanup(process);
|
||||
|
||||
@@ -29,11 +29,11 @@ module std::sort::cs(<Type, KeyFn>);
|
||||
def Counts = usz[256] @private;
|
||||
def Ranges = usz[257] @private;
|
||||
def Indexs = char[256] @private;
|
||||
def ElementType = $typeof(Type{}[0]);
|
||||
def ElementType = $typeof((Type){}[0]);
|
||||
|
||||
const bool NO_KEY_FN @private = types::is_same(KeyFn, EmptySlot);
|
||||
const bool KEY_BY_VALUE @private = NO_KEY_FN ||| $assignable(Type{}[0], $typefrom(KeyFn.paramsof[0].type));
|
||||
const bool LIST_HAS_REF @private = $defined(&Type{}[0]);
|
||||
const bool KEY_BY_VALUE @private = NO_KEY_FN ||| $assignable((Type){}[0], $typefrom(KeyFn.paramsof[0].type));
|
||||
const bool LIST_HAS_REF @private = $defined(&(Type){}[0]);
|
||||
|
||||
def KeyFnReturnType = $typefrom(KeyFn.returns) @if(!NO_KEY_FN);
|
||||
def KeyFnReturnType = ElementType @if(NO_KEY_FN);
|
||||
|
||||
@@ -19,7 +19,7 @@ macro insertionsort(list, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SLOT) @b
|
||||
|
||||
module std::sort::is(<Type, CmpFn, Context>);
|
||||
|
||||
def ElementType = $typeof(Type{}[0]);
|
||||
def ElementType = $typeof(((Type){})[0]);
|
||||
|
||||
fn void isort(Type list, usz low, usz high, CmpFn comp, Context context)
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ macro quickselect(list, isz k, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SLO
|
||||
|
||||
module std::sort::qs(<Type, CmpFn, Context>);
|
||||
|
||||
def ElementType = $typeof(Type{}[0]);
|
||||
def ElementType = $typeof(((Type){})[0]);
|
||||
|
||||
struct StackElementItem @private
|
||||
{
|
||||
|
||||
@@ -21,12 +21,12 @@ struct BufferedChannelImpl @private
|
||||
Type[?] buf;
|
||||
}
|
||||
|
||||
fn void! BufferedChannel.new_init(&self, usz size = 1)
|
||||
fn void! BufferedChannel.new_init(&self, usz size = 1) @deprecated("Use init(mem)")
|
||||
{
|
||||
return self.init(size, allocator::heap());
|
||||
return self.init(mem, size);
|
||||
}
|
||||
|
||||
fn void! BufferedChannel.init(&self, usz size = 1, Allocator allocator)
|
||||
fn void! BufferedChannel.init(&self, Allocator allocator, usz size = 1)
|
||||
{
|
||||
BufferedChannelImpl* channel = allocator::new_with_padding(allocator, BufferedChannelImpl, Type.sizeof * size)!;
|
||||
defer catch allocator::free(allocator, channel);
|
||||
|
||||
@@ -8,7 +8,14 @@ struct NativeMutex
|
||||
}
|
||||
|
||||
def NativeConditionVariable = Pthread_cond_t;
|
||||
def NativeThread = Pthread_t;
|
||||
|
||||
struct NativeThread
|
||||
{
|
||||
inline Pthread_t pthread;
|
||||
ThreadFn thread_fn;
|
||||
void* arg;
|
||||
}
|
||||
|
||||
def NativeOnceFlag = Pthread_once_t;
|
||||
|
||||
<*
|
||||
@@ -148,59 +155,49 @@ fn void! NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms)
|
||||
}
|
||||
}
|
||||
|
||||
tlocal PosixThreadData *_thread_data @private;
|
||||
tlocal NativeThread current_thread @private;
|
||||
|
||||
fn void free_thread_data() @private
|
||||
{
|
||||
if (_thread_data)
|
||||
{
|
||||
allocator::free(_thread_data.allocator, _thread_data);
|
||||
_thread_data = null;
|
||||
}
|
||||
}
|
||||
fn void* callback(void* arg) @private
|
||||
{
|
||||
_thread_data = arg;
|
||||
defer free_thread_data();
|
||||
return (void*)(iptr)_thread_data.thread_fn(_thread_data.arg);
|
||||
NativeThread* thread = arg;
|
||||
current_thread = *thread;
|
||||
return (void*)(iptr)thread.thread_fn(thread.arg);
|
||||
}
|
||||
|
||||
fn void! NativeThread.create(&thread, ThreadFn thread_fn, void* arg)
|
||||
{
|
||||
PosixThreadData *thread_data = mem::new(PosixThreadData, { .thread_fn = thread_fn, .arg = arg, .allocator = allocator::heap() });
|
||||
if (posix::pthread_create(thread, null, &callback, thread_data) != 0)
|
||||
thread.thread_fn = thread_fn;
|
||||
thread.arg = arg;
|
||||
if (posix::pthread_create(&thread.pthread, null, &callback, thread) != 0)
|
||||
{
|
||||
*thread = null;
|
||||
free(thread_data);
|
||||
return ThreadFault.INIT_FAILED?;
|
||||
}
|
||||
}
|
||||
|
||||
fn void! NativeThread.detach(thread)
|
||||
{
|
||||
if (posix::pthread_detach(thread)) return ThreadFault.DETACH_FAILED?;
|
||||
if (posix::pthread_detach(thread.pthread)) return ThreadFault.DETACH_FAILED?;
|
||||
}
|
||||
|
||||
fn void native_thread_exit(int result)
|
||||
{
|
||||
free_thread_data();
|
||||
posix::pthread_exit((void*)(iptr)result);
|
||||
}
|
||||
|
||||
fn NativeThread native_thread_current()
|
||||
{
|
||||
return (NativeThread)posix::pthread_self();
|
||||
return current_thread;
|
||||
}
|
||||
|
||||
fn bool NativeThread.equals(thread, NativeThread other)
|
||||
{
|
||||
return (bool)posix::pthread_equal(thread, other);
|
||||
return (bool)posix::pthread_equal(thread.pthread, other.pthread);
|
||||
}
|
||||
|
||||
fn int! NativeThread.join(thread)
|
||||
{
|
||||
void *pres;
|
||||
if (posix::pthread_join(thread, &pres)) return ThreadFault.JOIN_FAILED?;
|
||||
if (posix::pthread_join(thread.pthread, &pres)) return ThreadFault.JOIN_FAILED?;
|
||||
return (int)(iptr)pres;
|
||||
}
|
||||
|
||||
@@ -214,13 +211,6 @@ fn void native_thread_yield()
|
||||
posix::sched_yield();
|
||||
}
|
||||
|
||||
struct PosixThreadData @private
|
||||
{
|
||||
ThreadFn thread_fn;
|
||||
void* arg;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
fn void! native_sleep_nano(NanoDuration nano)
|
||||
{
|
||||
if (nano <= 0) return;
|
||||
|
||||
@@ -13,7 +13,7 @@ distinct TimedMutex = inline Mutex;
|
||||
distinct RecursiveMutex = inline Mutex;
|
||||
distinct TimedRecursiveMutex = inline Mutex;
|
||||
distinct ConditionVariable = NativeConditionVariable;
|
||||
distinct Thread = NativeThread;
|
||||
distinct Thread = inline NativeThread;
|
||||
distinct OnceFlag = NativeOnceFlag;
|
||||
def OnceFn = fn void();
|
||||
|
||||
@@ -45,6 +45,12 @@ macro void! TimedMutex.lock_timeout(&mutex, ulong ms) => NativeMutex.lock_timeou
|
||||
macro void! TimedRecursiveMutex.lock_timeout(&mutex, ulong ms) => NativeMutex.lock_timeout((NativeMutex*)mutex, ms);
|
||||
macro bool Mutex.try_lock(&mutex) => NativeMutex.try_lock((NativeMutex*)mutex);
|
||||
macro void! Mutex.unlock(&mutex) => NativeMutex.unlock((NativeMutex*)mutex);
|
||||
macro void Mutex.@in_lock(&mutex; @body)
|
||||
{
|
||||
(void)mutex.lock();
|
||||
defer (void)mutex.unlock();
|
||||
@body();
|
||||
}
|
||||
|
||||
macro void! ConditionVariable.init(&cond) => NativeConditionVariable.init((NativeConditionVariable*)cond);
|
||||
macro void! ConditionVariable.destroy(&cond) => NativeConditionVariable.destroy((NativeConditionVariable*)cond);
|
||||
@@ -59,11 +65,10 @@ macro void! ConditionVariable.wait_timeout(&cond, Mutex* mutex, ulong ms)
|
||||
return NativeConditionVariable.wait_timeout((NativeConditionVariable*)cond, (NativeMutex*)mutex, ms);
|
||||
}
|
||||
|
||||
|
||||
macro void! Thread.create(&thread, ThreadFn thread_fn, void* arg) => NativeThread.create((NativeThread*)thread, thread_fn, arg);
|
||||
macro void! Thread.detach(thread) => NativeThread.detach((NativeThread)thread);
|
||||
macro int! Thread.join(thread) => NativeThread.join((NativeThread)thread);
|
||||
macro bool Thread.equals(thread, Thread other) => NativeThread.equals((NativeThread)thread, (NativeThread)other);
|
||||
macro void! Thread.create(&thread, ThreadFn thread_fn, void* arg) => NativeThread.create(thread, thread_fn, arg);
|
||||
macro void! Thread.detach(thread) => NativeThread.detach(thread);
|
||||
macro int! Thread.join(thread) => NativeThread.join(thread);
|
||||
macro bool Thread.equals(thread, Thread other) => NativeThread.equals(thread, other);
|
||||
|
||||
macro void OnceFlag.call(&flag, OnceFn func) => NativeOnceFlag.call_once((NativeOnceFlag*)flag, func);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ struct UnbufferedChannelImpl @private
|
||||
ConditionVariable read_cond;
|
||||
}
|
||||
|
||||
fn void! UnbufferedChannel.new_init(&self) => self.init(allocator::heap());
|
||||
fn void! UnbufferedChannel.new_init(&self) @deprecated("Use init") => self.init(allocator::heap());
|
||||
|
||||
fn void! UnbufferedChannel.init(&self, Allocator allocator)
|
||||
{
|
||||
|
||||
@@ -18,6 +18,16 @@ fn NanoDuration Clock.mark(&self)
|
||||
return diff;
|
||||
}
|
||||
|
||||
fn Clock Clock.add_nano_duration(self, NanoDuration nano)
|
||||
{
|
||||
return (Clock)((NanoDuration)self + nano);
|
||||
}
|
||||
|
||||
fn Clock Clock.add_duration(self, Duration duration)
|
||||
{
|
||||
return self.add_nano_duration(duration.to_nano());
|
||||
}
|
||||
|
||||
fn NanoDuration Clock.to_now(self)
|
||||
{
|
||||
return (NanoDuration)(now() - self);
|
||||
|
||||
77
lib7/std/ascii.c3
Normal file
77
lib7/std/ascii.c3
Normal file
@@ -0,0 +1,77 @@
|
||||
module std::ascii;
|
||||
|
||||
macro bool in_range_m(c, start, len) => (uint)(c - start) < len;
|
||||
macro bool is_lower_m(c) => in_range_m(c, 0x61, 26);
|
||||
macro bool is_upper_m(c) => in_range_m(c, 0x41, 26);
|
||||
macro bool is_digit_m(c) => in_range_m(c, 0x30, 10);
|
||||
macro bool is_bdigit_m(c) => in_range_m(c, 0x30, 2);
|
||||
macro bool is_odigit_m(c) => in_range_m(c, 0x30, 8);
|
||||
macro bool is_xdigit_m(c) => in_range_m(c | 32, 0x61, 6) || is_digit_m(c);
|
||||
macro bool is_alpha_m(c) => in_range_m(c | 32, 0x61, 26);
|
||||
macro bool is_print_m(c) => in_range_m(c, 0x20, 95);
|
||||
macro bool is_graph_m(c) => in_range_m(c, 0x21, 94);
|
||||
macro bool is_space_m(c) => in_range_m(c, 0x9, 5) || c == 0x20;
|
||||
macro bool is_alnum_m(c) => is_alpha_m(c) || is_digit_m(c);
|
||||
macro bool is_punct_m(c) => !is_alnum_m(c) && is_graph_m(c);
|
||||
macro bool is_blank_m(c) => c == 0x20 || c == 0x9;
|
||||
macro bool is_cntrl_m(c) => c < 0x20 || c == 0x7f;
|
||||
macro to_lower_m(c) => is_upper_m(c) ? c + 0x20 : c;
|
||||
macro to_upper_m(c) => is_lower_m(c) ? c - 0x20 : c;
|
||||
|
||||
fn bool in_range(char c, char start, char len) => in_range_m(c, start, len);
|
||||
fn bool is_lower(char c) => is_lower_m(c);
|
||||
fn bool is_upper(char c) => is_upper_m(c);
|
||||
fn bool is_digit(char c) => is_digit_m(c);
|
||||
fn bool is_bdigit(char c) => is_bdigit_m(c);
|
||||
fn bool is_odigit(char c) => is_odigit_m(c);
|
||||
fn bool is_xdigit(char c) => is_xdigit_m(c);
|
||||
fn bool is_alpha(char c) => is_alpha_m(c);
|
||||
fn bool is_print(char c) => is_print_m(c);
|
||||
fn bool is_graph(char c) => is_graph_m(c);
|
||||
fn bool is_space(char c) => is_space_m(c);
|
||||
fn bool is_alnum(char c) => is_alnum_m(c);
|
||||
fn bool is_punct(char c) => is_punct_m(c);
|
||||
fn bool is_blank(char c) => is_blank_m(c);
|
||||
fn bool is_cntrl(char c) => is_cntrl_m(c);
|
||||
fn char to_lower(char c) => (char)to_lower_m(c);
|
||||
fn char to_upper(char c) => (char)to_upper_m(c);
|
||||
|
||||
fn bool char.in_range(char c, char start, char len) => in_range_m(c, start, len);
|
||||
fn bool char.is_lower(char c) => is_lower_m(c);
|
||||
fn bool char.is_upper(char c) => is_upper_m(c);
|
||||
fn bool char.is_digit(char c) => is_digit_m(c);
|
||||
fn bool char.is_bdigit(char c) => is_bdigit_m(c);
|
||||
fn bool char.is_odigit(char c) => is_odigit_m(c);
|
||||
fn bool char.is_xdigit(char c) => is_xdigit_m(c);
|
||||
fn bool char.is_alpha(char c) => is_alpha_m(c);
|
||||
fn bool char.is_print(char c) => is_print_m(c);
|
||||
fn bool char.is_graph(char c) => is_graph_m(c);
|
||||
fn bool char.is_space(char c) => is_space_m(c);
|
||||
fn bool char.is_alnum(char c) => is_alnum_m(c);
|
||||
fn bool char.is_punct(char c) => is_punct_m(c);
|
||||
fn bool char.is_blank(char c) => is_blank_m(c);
|
||||
fn bool char.is_cntrl(char c) => is_cntrl_m(c);
|
||||
fn char char.to_lower(char c) => (char)to_lower_m(c);
|
||||
fn char char.to_upper(char c) => (char)to_upper_m(c);
|
||||
<*
|
||||
@require c.is_xdigit()
|
||||
*>
|
||||
fn char char.from_hex(char c) => c.is_digit() ? c - '0' : 10 + (c | 0x20) - 'a';
|
||||
|
||||
fn bool uint.in_range(uint c, uint start, uint len) => in_range_m(c, start, len);
|
||||
fn bool uint.is_lower(uint c) => is_lower_m(c);
|
||||
fn bool uint.is_upper(uint c) => is_upper_m(c);
|
||||
fn bool uint.is_digit(uint c) => is_digit_m(c);
|
||||
fn bool uint.is_bdigit(uint c) => is_bdigit_m(c);
|
||||
fn bool uint.is_odigit(uint c) => is_odigit_m(c);
|
||||
fn bool uint.is_xdigit(uint c) => is_xdigit_m(c);
|
||||
fn bool uint.is_alpha(uint c) => is_alpha_m(c);
|
||||
fn bool uint.is_print(uint c) => is_print_m(c);
|
||||
fn bool uint.is_graph(uint c) => is_graph_m(c);
|
||||
fn bool uint.is_space(uint c) => is_space_m(c);
|
||||
fn bool uint.is_alnum(uint c) => is_alnum_m(c);
|
||||
fn bool uint.is_punct(uint c) => is_punct_m(c);
|
||||
fn bool uint.is_blank(uint c) => is_blank_m(c);
|
||||
fn bool uint.is_cntrl(uint c) => is_cntrl_m(c);
|
||||
fn uint uint.to_lower(uint c) => (uint)to_lower_m(c);
|
||||
fn uint uint.to_upper(uint c) => (uint)to_upper_m(c);
|
||||
520
lib7/std/atomic.c3
Normal file
520
lib7/std/atomic.c3
Normal file
@@ -0,0 +1,520 @@
|
||||
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::atomic::types{Type};
|
||||
|
||||
struct Atomic
|
||||
{
|
||||
Type data;
|
||||
}
|
||||
|
||||
<*
|
||||
Loads data atomically, by default this uses SEQ_CONSISTENT ordering.
|
||||
|
||||
@param ordering "The ordering, cannot be release or acquire-release."
|
||||
@require ordering != RELEASE && ordering != ACQUIRE_RELEASE : "Release and acquire-release are not valid for load"
|
||||
*>
|
||||
macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
switch(ordering)
|
||||
{
|
||||
case NOT_ATOMIC: return $$atomic_load(data, false, AtomicOrdering.NOT_ATOMIC.ordinal);
|
||||
case UNORDERED: return $$atomic_load(data, false, AtomicOrdering.UNORDERED.ordinal);
|
||||
case RELAXED: return $$atomic_load(data, false, AtomicOrdering.RELAXED.ordinal);
|
||||
case ACQUIRE: return $$atomic_load(data, false, AtomicOrdering.ACQUIRE.ordinal);
|
||||
case SEQ_CONSISTENT: return $$atomic_load(data, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
|
||||
case ACQUIRE_RELEASE:
|
||||
case RELEASE: unreachable("Invalid ordering.");
|
||||
}
|
||||
}
|
||||
<*
|
||||
Stores data atomically, by default this uses SEQ_CONSISTENT ordering.
|
||||
|
||||
@param ordering "The ordering, cannot be acquire or acquire-release."
|
||||
@require ordering != ACQUIRE && ordering != ACQUIRE_RELEASE : "Acquire and acquire-release are not valid for store"
|
||||
*>
|
||||
macro void Atomic.store(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
switch(ordering)
|
||||
{
|
||||
case NOT_ATOMIC: $$atomic_store(data, value, false, AtomicOrdering.NOT_ATOMIC.ordinal);
|
||||
case UNORDERED: $$atomic_store(data, value, false, AtomicOrdering.UNORDERED.ordinal);
|
||||
case RELAXED: $$atomic_store(data, value, false, AtomicOrdering.RELAXED.ordinal);
|
||||
case RELEASE: $$atomic_store(data, value, false, AtomicOrdering.RELEASE.ordinal);
|
||||
case SEQ_CONSISTENT: $$atomic_store(data, value, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
|
||||
case ACQUIRE_RELEASE:
|
||||
case ACQUIRE: unreachable("Invalid ordering.");
|
||||
}
|
||||
}
|
||||
|
||||
macro Type Atomic.add(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_add, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.sub(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_sub, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.mul(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_mul, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_div, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_div, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_min, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.or(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_or, data, value, ordering);
|
||||
}
|
||||
|
||||
fn Type Atomic.xor(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.and(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_and, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.shift_right(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.shift_left(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
|
||||
}
|
||||
|
||||
macro @atomic_exec(#func, data, value, ordering) @local
|
||||
{
|
||||
switch(ordering)
|
||||
{
|
||||
case RELAXED: return #func(data, value, RELAXED);
|
||||
case ACQUIRE: return #func(data, value, ACQUIRE);
|
||||
case RELEASE: return #func(data, value, RELEASE);
|
||||
case ACQUIRE_RELEASE: return #func(data, value, ACQUIRE_RELEASE);
|
||||
case SEQ_CONSISTENT: return #func(data, value, SEQ_CONSISTENT);
|
||||
default: unreachable("Ordering may not be non-atomic or unordered.");
|
||||
}
|
||||
}
|
||||
|
||||
module std::atomic;
|
||||
import std::math;
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
return $$atomic_fetch_add(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
return $$atomic_fetch_sub(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = old_value * y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = old_value / y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value | storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value ^ storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value & storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value >> storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value << storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value = true;
|
||||
|
||||
do {
|
||||
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
|
||||
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value = false;
|
||||
|
||||
do {
|
||||
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
|
||||
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
return $$atomic_fetch_max(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
return $$atomic_fetch_min(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
63
lib7/std/atomic_nolibc.c3
Normal file
63
lib7/std/atomic_nolibc.c3
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::atomic;
|
||||
|
||||
macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment) {
|
||||
switch(failure)
|
||||
{
|
||||
case AtomicOrdering.RELAXED.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.RELAXED.ordinal, $alignment);
|
||||
case AtomicOrdering.ACQUIRE.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.ACQUIRE.ordinal, $alignment);
|
||||
case AtomicOrdering.SEQ_CONSISTENT.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.SEQ_CONSISTENT.ordinal, $alignment);
|
||||
default: unreachable("Unrecognized failure ordering");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, success, failure, $alignment)
|
||||
{
|
||||
switch(success)
|
||||
{
|
||||
case AtomicOrdering.RELAXED.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELAXED.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.ACQUIRE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELEASE.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.ACQUIRE_RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE_RELEASE.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.SEQ_CONSISTENT.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.SEQ_CONSISTENT.ordinal, failure, $alignment);
|
||||
default: unreachable("Unrecognized success ordering");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, CInt success, CInt failure) @extern("__atomic_compare_exchange") @export
|
||||
{
|
||||
switch (size)
|
||||
{
|
||||
case 1:
|
||||
char* pt = (char*)ptr;
|
||||
char ex = *(char*)expected;
|
||||
char de = *(char*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 1)) return 1;
|
||||
case 2:
|
||||
short* pt = (short*)ptr;
|
||||
short ex = *(short*)expected;
|
||||
short de = *(short*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 2)) return 1;
|
||||
case 4:
|
||||
int* pt = (int*)ptr;
|
||||
int ex = *(int*)expected;
|
||||
int de = *(int*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 4)) return 1;
|
||||
case 8:
|
||||
$if iptr.sizeof >= 8:
|
||||
long* pt = (long*)ptr;
|
||||
long ex = *(long*)expected;
|
||||
long de = *(long*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 8)) return 1;
|
||||
$else
|
||||
nextcase;
|
||||
$endif
|
||||
default:
|
||||
unreachable("Unsuported size (%d) for atomic_compare_exchange", size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
171
lib7/std/bits.c3
Normal file
171
lib7/std/bits.c3
Normal file
@@ -0,0 +1,171 @@
|
||||
module std::bits;
|
||||
|
||||
<*
|
||||
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
|
||||
*>
|
||||
macro reverse(i) => $$bitreverse(i);
|
||||
|
||||
<*
|
||||
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
|
||||
*>
|
||||
macro bswap(i) @builtin => $$bswap(i);
|
||||
|
||||
macro uint[<?>].popcount(self) => $$popcount(self);
|
||||
macro uint[<?>].ctz(self) => $$ctz(self);
|
||||
macro uint[<?>].clz(self) => $$clz(self);
|
||||
macro uint[<?>] uint[<?>].fshl(hi, uint[<?>] lo, uint[<?>] shift) => $$fshl(hi, lo, shift);
|
||||
macro uint[<?>] uint[<?>].fshr(hi, uint[<?>] lo, uint[<?>] shift) => $$fshr(hi, lo, shift);
|
||||
macro uint[<?>] uint[<?>].rotl(self, uint[<?>] shift) => $$fshl(self, self, shift);
|
||||
macro uint[<?>] uint[<?>].rotr(self, uint[<?>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro int[<?>].popcount(self) => $$popcount(self);
|
||||
macro int[<?>].ctz(self) => $$ctz(self);
|
||||
macro int[<?>].clz(self) => $$clz(self);
|
||||
macro int[<?>] int[<?>].fshl(hi, int[<?>] lo, int[<?>] shift) => $$fshl(hi, lo, shift);
|
||||
macro int[<?>] int[<?>].fshr(hi, int[<?>] lo, int[<?>] shift) => $$fshr(hi, lo, shift);
|
||||
macro int[<?>] int[<?>].rotl(self, int[<?>] shift) => $$fshl(self, self, shift);
|
||||
macro int[<?>] int[<?>].rotr(self, int[<?>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ushort[<?>].popcount(self) => $$popcount(self);
|
||||
macro ushort[<?>].ctz(self) => $$ctz(self);
|
||||
macro ushort[<?>].clz(self) => $$clz(self);
|
||||
macro ushort[<?>] ushort[<?>].fshl(hi, ushort[<?>] lo, ushort[<?>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ushort[<?>] ushort[<?>].fshr(hi, ushort[<?>] lo, ushort[<?>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ushort[<?>] ushort[<?>].rotl(self, ushort[<?>] shift) => $$fshl(self, self, shift);
|
||||
macro ushort[<?>] ushort[<?>].rotr(self, ushort[<?>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro short[<?>].popcount(self) => $$popcount(self);
|
||||
macro short[<?>].ctz(self) => $$ctz(self);
|
||||
macro short[<?>].clz(self) => $$clz(self);
|
||||
macro short[<?>] short[<?>].fshl(hi, short[<?>] lo, short[<?>] shift) => $$fshl(hi, lo, shift);
|
||||
macro short[<?>] short[<?>].fshr(hi, short[<?>] lo, short[<?>] shift) => $$fshr(hi, lo, shift);
|
||||
macro short[<?>] short[<?>].rotl(self, short[<?>] shift) => $$fshl(self, self, shift);
|
||||
macro short[<?>] short[<?>].rotr(self, short[<?>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro char[<?>].popcount(self) => $$popcount(self);
|
||||
macro char[<?>].ctz(self) => $$ctz(self);
|
||||
macro char[<?>].clz(self) => $$clz(self);
|
||||
macro char[<?>] char[<?>].fshl(hi, char[<?>] lo, char[<?>] shift) => $$fshl(hi, lo, shift);
|
||||
macro char[<?>] char[<?>].fshr(hi, char[<?>] lo, char[<?>] shift) => $$fshr(hi, lo, shift);
|
||||
macro char[<?>] char[<?>].rotl(self, char[<?>] shift) => $$fshl(self, self, shift);
|
||||
macro char[<?>] char[<?>].rotr(self, char[<?>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ichar[<?>].popcount(self) => $$popcount(self);
|
||||
macro ichar[<?>].ctz(self) => $$ctz(self);
|
||||
macro ichar[<?>].clz(self) => $$clz(self);
|
||||
macro ichar[<?>] ichar[<?>].fshl(hi, ichar[<?>] lo, ichar[<?>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ichar[<?>] ichar[<?>].fshr(hi, ichar[<?>] lo, ichar[<?>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ichar[<?>] ichar[<?>].rotl(self, ichar[<?>] shift) => $$fshl(self, self, shift);
|
||||
macro ichar[<?>] ichar[<?>].rotr(self, ichar[<?>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ulong[<?>].popcount(self) => $$popcount(self);
|
||||
macro ulong[<?>].ctz(self) => $$ctz(self);
|
||||
macro ulong[<?>].clz(self) => $$clz(self);
|
||||
macro ulong[<?>] ulong[<?>].fshl(hi, ulong[<?>] lo, ulong[<?>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ulong[<?>] ulong[<?>].fshr(hi, ulong[<?>] lo, ulong[<?>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ulong[<?>] ulong[<?>].rotl(self, ulong[<?>] shift) => $$fshl(self, self, shift);
|
||||
macro ulong[<?>] ulong[<?>].rotr(self, ulong[<?>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro long[<?>].popcount(self) => $$popcount(self);
|
||||
macro long[<?>].ctz(self) => $$ctz(self);
|
||||
macro long[<?>].clz(self) => $$clz(self);
|
||||
macro long[<?>] long[<?>].fshl(hi, long[<?>] lo, long[<?>] shift) => $$fshl(hi, lo, shift);
|
||||
macro long[<?>] long[<?>].fshr(hi, long[<?>] lo, long[<?>] shift) => $$fshr(hi, lo, shift);
|
||||
macro long[<?>] long[<?>].rotl(self, long[<?>] shift) => $$fshl(self, self, shift);
|
||||
macro long[<?>] long[<?>].rotr(self, long[<?>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro uint128[<?>].popcount(self) => $$popcount(self);
|
||||
macro uint128[<?>].ctz(self) => $$ctz(self);
|
||||
macro uint128[<?>].clz(self) => $$clz(self);
|
||||
macro uint128[<?>] uint128[<?>].fshl(hi, uint128[<?>] lo, uint128[<?>] shift) => $$fshl(hi, lo, shift);
|
||||
macro uint128[<?>] uint128[<?>].fshr(hi, uint128[<?>] lo, uint128[<?>] shift) => $$fshr(hi, lo, shift);
|
||||
macro uint128[<?>] uint128[<?>].rotl(self, uint128[<?>] shift) => $$fshl(self, self, shift);
|
||||
macro uint128[<?>] uint128[<?>].rotr(self, uint128[<?>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro int128[<?>].popcount(self) => $$popcount(self);
|
||||
macro int128[<?>].ctz(self) => $$ctz(self);
|
||||
macro int128[<?>].clz(self) => $$clz(self);
|
||||
macro int128[<?>] int128[<?>].fshl(hi, int128[<?>] lo, int128[<?>] shift) => $$fshl(hi, lo, shift);
|
||||
macro int128[<?>] int128[<?>].fshr(hi, int128[<?>] lo, int128[<?>] shift) => $$fshr(hi, lo, shift);
|
||||
macro int128[<?>] int128[<?>].rotl(self, int128[<?>] shift) => $$fshl(self, self, shift);
|
||||
macro int128[<?>] int128[<?>].rotr(self, int128[<?>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro uint.popcount(self) => $$popcount(self);
|
||||
macro uint.ctz(self) => $$ctz(self);
|
||||
macro uint.clz(self) => $$clz(self);
|
||||
macro uint uint.fshl(hi, uint lo, uint shift) => $$fshl(hi, lo, shift);
|
||||
macro uint uint.fshr(hi, uint lo, uint shift) => $$fshr(hi, lo, shift);
|
||||
macro uint uint.rotl(self, uint shift) => $$fshl(self, self, shift);
|
||||
macro uint uint.rotr(self, uint shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro int.popcount(self) => $$popcount(self);
|
||||
macro int.ctz(self) => $$ctz(self);
|
||||
macro int.clz(self) => $$clz(self);
|
||||
macro int int.fshl(hi, int lo, int shift) => $$fshl(hi, lo, shift);
|
||||
macro int int.fshr(hi, int lo, int shift) => $$fshr(hi, lo, shift);
|
||||
macro int int.rotl(self, int shift) => $$fshl(self, self, shift);
|
||||
macro int int.rotr(self, int shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ushort.popcount(self) => $$popcount(self);
|
||||
macro ushort.ctz(self) => $$ctz(self);
|
||||
macro ushort.clz(self) => $$clz(self);
|
||||
macro ushort ushort.fshl(hi, ushort lo, ushort shift) => $$fshl(hi, lo, shift);
|
||||
macro ushort ushort.fshr(hi, ushort lo, ushort shift) => $$fshr(hi, lo, shift);
|
||||
macro ushort ushort.rotl(self, ushort shift) => $$fshl(self, self, shift);
|
||||
macro ushort ushort.rotr(self, ushort shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro short.popcount(self) => $$popcount(self);
|
||||
macro short.ctz(self) => $$ctz(self);
|
||||
macro short.clz(self) => $$clz(self);
|
||||
macro short short.fshl(hi, short lo, short shift) => $$fshl(hi, lo, shift);
|
||||
macro short short.fshr(hi, short lo, short shift) => $$fshr(hi, lo, shift);
|
||||
macro short short.rotl(self, short shift) => $$fshl(self, self, shift);
|
||||
macro short short.rotr(self, short shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro char.popcount(self) => $$popcount(self);
|
||||
macro char.ctz(self) => $$ctz(self);
|
||||
macro char.clz(self) => $$clz(self);
|
||||
macro char char.fshl(hi, char lo, char shift) => $$fshl(hi, lo, shift);
|
||||
macro char char.fshr(hi, char lo, char shift) => $$fshr(hi, lo, shift);
|
||||
macro char char.rotl(self, char shift) => $$fshl(self, self, shift);
|
||||
macro char char.rotr(self, char shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ichar.popcount(self) => $$popcount(self);
|
||||
macro ichar.ctz(self) => $$ctz(self);
|
||||
macro ichar.clz(self) => $$clz(self);
|
||||
macro ichar ichar.fshl(hi, ichar lo, ichar shift) => $$fshl(hi, lo, shift);
|
||||
macro ichar ichar.fshr(hi, ichar lo, ichar shift) => $$fshr(hi, lo, shift);
|
||||
macro ichar ichar.rotl(self, ichar shift) => $$fshl(self, self, shift);
|
||||
macro ichar ichar.rotr(self, ichar shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ulong.popcount(self) => $$popcount(self);
|
||||
macro ulong.ctz(self) => $$ctz(self);
|
||||
macro ulong.clz(self) => $$clz(self);
|
||||
macro ulong ulong.fshl(hi, ulong lo, ulong shift) => $$fshl(hi, lo, shift);
|
||||
macro ulong ulong.fshr(hi, ulong lo, ulong shift) => $$fshr(hi, lo, shift);
|
||||
macro ulong ulong.rotl(self, ulong shift) => $$fshl(self, self, shift);
|
||||
macro ulong ulong.rotr(self, ulong shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro long.popcount(self) => $$popcount(self);
|
||||
macro long.ctz(self) => $$ctz(self);
|
||||
macro long.clz(self) => $$clz(self);
|
||||
macro long long.fshl(hi, long lo, long shift) => $$fshl(hi, lo, shift);
|
||||
macro long long.fshr(hi, long lo, long shift) => $$fshr(hi, lo, shift);
|
||||
macro long long.rotl(self, long shift) => $$fshl(self, self, shift);
|
||||
macro long long.rotr(self, long shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro uint128.popcount(self) => $$popcount(self);
|
||||
macro uint128.ctz(self) => $$ctz(self);
|
||||
macro uint128.clz(self) => $$clz(self);
|
||||
macro uint128 uint128.fshl(hi, uint128 lo, uint128 shift) => $$fshl(hi, lo, shift);
|
||||
macro uint128 uint128.fshr(hi, uint128 lo, uint128 shift) => $$fshr(hi, lo, shift);
|
||||
macro uint128 uint128.rotl(self, uint128 shift) => $$fshl(self, self, shift);
|
||||
macro uint128 uint128.rotr(self, uint128 shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro int128.popcount(self) => $$popcount(self);
|
||||
macro int128.ctz(self) => $$ctz(self);
|
||||
macro int128.clz(self) => $$clz(self);
|
||||
macro int128 int128.fshl(hi, int128 lo, int128 shift) => $$fshl(hi, lo, shift);
|
||||
macro int128 int128.fshr(hi, int128 lo, int128 shift) => $$fshr(hi, lo, shift);
|
||||
macro int128 int128.rotl(self, int128 shift) => $$fshl(self, self, shift);
|
||||
macro int128 int128.rotr(self, int128 shift) => $$fshr(self, self, shift);
|
||||
450
lib7/std/collections/anylist.c3
Normal file
450
lib7/std/collections/anylist.c3
Normal file
@@ -0,0 +1,450 @@
|
||||
// Copyright (c) 2024-2025 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::anylist;
|
||||
import std::io,std::math;
|
||||
|
||||
def AnyPredicate = fn bool(any value);
|
||||
def AnyTest = fn bool(any type, any context);
|
||||
|
||||
struct AnyList (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
any* entries;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
if (initial_capacity > 0)
|
||||
{
|
||||
initial_capacity = math::next_power_of_2(initial_capacity);
|
||||
self.entries = allocator::alloc_array(allocator, any, initial_capacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.entries = null;
|
||||
}
|
||||
self.capacity = initial_capacity;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(tmem(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
case 0:
|
||||
return formatter.print("[]")!;
|
||||
case 1:
|
||||
return formatter.printf("[%s]", self.entries[0])!;
|
||||
default:
|
||||
usz n = formatter.print("[")!;
|
||||
foreach (i, element : self.entries[:self.size])
|
||||
{
|
||||
if (i != 0) formatter.print(", ")!;
|
||||
n += formatter.printf("%s", element)!;
|
||||
}
|
||||
n += formatter.print("]")!;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Push an element on the list by cloning it.
|
||||
*>
|
||||
macro void AnyList.push(&self, element)
|
||||
{
|
||||
if (!self.allocator) self.allocator = allocator::heap();
|
||||
self.append_internal(allocator::clone(self.allocator, element));
|
||||
}
|
||||
|
||||
fn void AnyList.append_internal(&self, any element) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
Free a retained element removed using *_retained.
|
||||
*>
|
||||
fn void AnyList.free_element(&self, any element) @inline
|
||||
{
|
||||
allocator::free(self.allocator, element.ptr);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@return! CastResult.TYPE_MISMATCH, IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.pop(&self, $Type)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return *anycast(self.entries[--self.size], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the last value and allocate the copy using the given allocator.
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any! AnyList.copy_pop(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return allocator::clone_any(allocator, self.entries[--self.size]);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Pop the last value and allocate the copy using the temp allocator
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any! AnyList.tcopy_pop(&self) => self.copy_pop(tmem());
|
||||
|
||||
<*
|
||||
Pop the last value. It must later be released using list.free_element()
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any! AnyList.pop_retained(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
fn void AnyList.clear(&self)
|
||||
{
|
||||
for (usz i = 0; i < self.size; i++)
|
||||
{
|
||||
self.free_element(self.entries[i]);
|
||||
}
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Same as pop() but pops the first value instead.
|
||||
*>
|
||||
macro AnyList.pop_first(&self, $Type)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return *anycast(self.entries[0], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Same as pop_retained() but pops the first value instead.
|
||||
*>
|
||||
fn any! AnyList.pop_first_retained(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Same as copy_pop() but pops the first value instead.
|
||||
*>
|
||||
fn any! AnyList.copy_pop_first(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
defer self.remove_at(0);
|
||||
return allocator::clone_any(allocator, self.entries[0]);
|
||||
}
|
||||
|
||||
<*
|
||||
Same as temp_pop() but pops the first value instead.
|
||||
*>
|
||||
fn any! AnyList.tcopy_pop_first(&self) => self.copy_pop_first(tmem());
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void AnyList.remove_at(&self, usz index)
|
||||
{
|
||||
if (!--self.size || index == self.size) return;
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
}
|
||||
|
||||
fn void AnyList.add_all(&self, AnyList* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
self.reserve(other_list.size);
|
||||
foreach (value : other_list)
|
||||
{
|
||||
self.entries[self.size++] = allocator::clone_any(self.allocator, value);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the elements in a list.
|
||||
*>
|
||||
fn void AnyList.reverse(&self)
|
||||
{
|
||||
if (self.size < 2) return;
|
||||
usz half = self.size / 2U;
|
||||
usz end = self.size - 1;
|
||||
for (usz i = 0; i < half; i++)
|
||||
{
|
||||
self.swap(i, end - i);
|
||||
}
|
||||
}
|
||||
|
||||
fn any[] AnyList.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Push an element to the front of the list.
|
||||
*>
|
||||
macro void AnyList.push_front(&self, type)
|
||||
{
|
||||
self.insert_at(0, type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
macro void AnyList.insert_at(&self, usz index, type) @local
|
||||
{
|
||||
any value = allocator::copy(self.allocator, type);
|
||||
self.insert_at_internal(self, index, value);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void AnyList.insert_at_internal(&self, usz index, any value) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
for (usz i = self.size; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.size++;
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn void AnyList.remove_last(&self)
|
||||
{
|
||||
self.free_element(self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn void AnyList.remove_first(&self)
|
||||
{
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
macro AnyList.first(&self, $Type)
|
||||
{
|
||||
return *anycast(self.first_any(), $Type);
|
||||
}
|
||||
|
||||
fn any! AnyList.first_any(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[0] : IteratorResult.NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
macro AnyList.last(&self, $Type)
|
||||
{
|
||||
return *anycast(self.last_any(), $Type);
|
||||
}
|
||||
|
||||
fn any! AnyList.last_any(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[self.size - 1] : IteratorResult.NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
fn bool AnyList.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
fn usz AnyList.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size "Index out of range"
|
||||
*>
|
||||
macro AnyList.get(&self, usz index, $Type)
|
||||
{
|
||||
return *anycast(self.entries[index], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size "Index out of range"
|
||||
*>
|
||||
fn any AnyList.get_any(&self, usz index) @inline
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn void AnyList.free(&self)
|
||||
{
|
||||
if (!self.allocator) return;
|
||||
self.clear();
|
||||
allocator::free(self.allocator, self.entries);
|
||||
self.capacity = 0;
|
||||
self.entries = null;
|
||||
}
|
||||
|
||||
fn void AnyList.swap(&self, usz i, usz j)
|
||||
{
|
||||
any temp = self.entries[i];
|
||||
self.entries[i] = self.entries[j];
|
||||
self.entries[j] = temp;
|
||||
}
|
||||
|
||||
<*
|
||||
@param filter "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.remove_if(&self, AnyPredicate filter)
|
||||
{
|
||||
return self._remove_if(filter, false);
|
||||
}
|
||||
|
||||
<*
|
||||
@param selection "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.retain_if(&self, AnyPredicate selection)
|
||||
{
|
||||
return self._remove_if(selection, true);
|
||||
}
|
||||
|
||||
macro usz AnyList._remove_if(&self, AnyPredicate filter, bool $invert) @local
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(&self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && filter(&self.entries[i - 1])) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(&self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(&self.entries[i - 1])) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
|
||||
{
|
||||
return self._remove_using_test(filter, false, context);
|
||||
}
|
||||
|
||||
fn usz AnyList.retain_using_test(&self, AnyTest filter, any context)
|
||||
{
|
||||
return self._remove_using_test(filter, true, context);
|
||||
}
|
||||
|
||||
macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @local
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Reserve at least min_capacity
|
||||
*>
|
||||
fn void AnyList.reserve(&self, usz min_capacity)
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = tmem();
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
self.entries = allocator::realloc(self.allocator, self.entries, any.sizeof * min_capacity);
|
||||
self.capacity = min_capacity;
|
||||
}
|
||||
|
||||
macro any AnyList.@item_at(&self, usz index) @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size "Index out of range"
|
||||
*>
|
||||
macro void AnyList.set(&self, usz index, value)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(value);
|
||||
return;
|
||||
}
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index] = allocator::copy(self.allocator, value);
|
||||
}
|
||||
|
||||
fn void AnyList.ensure_capacity(&self, usz added = 1) @inline @private
|
||||
{
|
||||
usz new_size = self.size + added;
|
||||
if (self.capacity >= new_size) return;
|
||||
|
||||
assert(new_size < usz.max / 2U);
|
||||
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
|
||||
while (new_capacity < new_size) new_capacity *= 2U;
|
||||
self.reserve(new_capacity);
|
||||
}
|
||||
155
lib7/std/collections/bitset.c3
Normal file
155
lib7/std/collections/bitset.c3
Normal file
@@ -0,0 +1,155 @@
|
||||
<*
|
||||
@require SIZE > 0
|
||||
*>
|
||||
module std::collections::bitset{SIZE};
|
||||
|
||||
def Type = uint;
|
||||
|
||||
const BITS = Type.sizeof * 8;
|
||||
const SZ = (SIZE + BITS - 1) / BITS;
|
||||
|
||||
struct BitSet
|
||||
{
|
||||
Type[SZ] data;
|
||||
}
|
||||
|
||||
fn usz BitSet.cardinality(&self)
|
||||
{
|
||||
usz n;
|
||||
foreach (x : self.data)
|
||||
{
|
||||
n += x.popcount();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
*>
|
||||
fn void BitSet.set(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
self.data[q] |= 1 << r;
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
*>
|
||||
fn void BitSet.unset(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
self.data[q] &= ~(1 << r);
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
*>
|
||||
fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
return self.data[q] & (1 << r) != 0;
|
||||
}
|
||||
|
||||
fn usz BitSet.len(&self) @operator(len) @inline
|
||||
{
|
||||
return SZ * BITS;
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
*>
|
||||
fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
|
||||
{
|
||||
if (value) return self.set(i);
|
||||
self.unset(i);
|
||||
}
|
||||
|
||||
<*
|
||||
@require Type.kindof == UNSIGNED_INT
|
||||
*>
|
||||
module std::collections::growablebitset{Type};
|
||||
import std::collections::list;
|
||||
|
||||
const BITS = Type.sizeof * 8;
|
||||
|
||||
def GrowableBitSetList = List{Type};
|
||||
|
||||
struct GrowableBitSet
|
||||
{
|
||||
GrowableBitSetList data;
|
||||
}
|
||||
|
||||
<*
|
||||
@param initial_capacity
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
*>
|
||||
fn GrowableBitSet* GrowableBitSet.init(&self, Allocator allocator, usz initial_capacity = 1)
|
||||
{
|
||||
self.data.init(allocator, initial_capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn GrowableBitSet* GrowableBitSet.tinit(&self, usz initial_capacity = 1)
|
||||
{
|
||||
return self.init(tmem(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.free(&self)
|
||||
{
|
||||
self.data.free();
|
||||
}
|
||||
|
||||
fn usz GrowableBitSet.cardinality(&self)
|
||||
{
|
||||
usz n;
|
||||
foreach (x : self.data)
|
||||
{
|
||||
n += x.popcount();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.set(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
usz current_len = self.data.len();
|
||||
while (q >= current_len)
|
||||
{
|
||||
self.data.push(0);
|
||||
current_len++;
|
||||
}
|
||||
self.data.set(q, self.data[q] | (1 << r));
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.unset(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
if (q >= self.data.len()) return;
|
||||
self.data.set(q, self.data[q] &~ (1 << r));
|
||||
}
|
||||
|
||||
fn bool GrowableBitSet.get(&self, usz i) @operator([]) @inline
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
if (q >= self.data.len()) return false;
|
||||
return self.data[q] & (1 << r) != 0;
|
||||
}
|
||||
|
||||
fn usz GrowableBitSet.len(&self) @operator(len)
|
||||
{
|
||||
usz n = self.data.len() * BITS;
|
||||
if (n > 0) n -= (usz)self.data[^1].clz();
|
||||
return n;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
|
||||
{
|
||||
if (value) return self.set(i);
|
||||
self.unset(i);
|
||||
}
|
||||
429
lib7/std/collections/elastic_array.c3
Normal file
429
lib7/std/collections/elastic_array.c3
Normal file
@@ -0,0 +1,429 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
<*
|
||||
@require MAX_SIZE >= 1 `The size must be at least 1 element big.`
|
||||
*>
|
||||
module std::collections::elastic_array{Type, MAX_SIZE};
|
||||
import std::io, std::math, std::collections::list_common;
|
||||
|
||||
def ElementPredicate = fn bool(Type *type);
|
||||
def ElementTest = fn bool(Type *type, any context);
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
|
||||
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct ElasticArray (Printable)
|
||||
{
|
||||
usz size;
|
||||
Type[MAX_SIZE] entries;
|
||||
}
|
||||
|
||||
fn usz! ElasticArray.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
case 0:
|
||||
return formatter.print("[]")!;
|
||||
case 1:
|
||||
return formatter.printf("[%s]", self.entries[0])!;
|
||||
default:
|
||||
usz n = formatter.print("[")!;
|
||||
foreach (i, element : self.entries[:self.size])
|
||||
{
|
||||
if (i != 0) formatter.print(", ")!;
|
||||
n += formatter.printf("%s", element)!;
|
||||
}
|
||||
n += formatter.print("]")!;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
fn String ElasticArray.to_tstring(&self)
|
||||
{
|
||||
return string::tformat("%s", *self);
|
||||
}
|
||||
|
||||
fn void! ElasticArray.push_try(&self, Type element) @inline
|
||||
{
|
||||
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `Tried to exceed the max size`
|
||||
*>
|
||||
fn void ElasticArray.push(&self, Type element) @inline
|
||||
{
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
fn Type! ElasticArray.pop(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
fn void ElasticArray.clear(&self)
|
||||
{
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn Type! ElasticArray.pop_first(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void ElasticArray.remove_at(&self, usz index)
|
||||
{
|
||||
if (!--self.size || index == self.size) return;
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
@require other_list.size + self.size <= MAX_SIZE
|
||||
*>
|
||||
fn void ElasticArray.add_all(&self, ElasticArray* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
foreach (&value : other_list)
|
||||
{
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Add as many elements as possible to the new array,
|
||||
returning the number of elements that didn't fit.
|
||||
*>
|
||||
fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
|
||||
{
|
||||
if (!other_list.size) return 0;
|
||||
foreach (i, &value : other_list)
|
||||
{
|
||||
if (self.size == MAX_SIZE) return other_list.size - i;
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Add as many values from this array as possible, returning the
|
||||
number of elements that didn't fit.
|
||||
|
||||
@param [in] array
|
||||
*>
|
||||
fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return 0;
|
||||
foreach (i, &value : array)
|
||||
{
|
||||
if (self.size == MAX_SIZE) return array.len - i;
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Add the values of an array to this list.
|
||||
|
||||
@param [in] array
|
||||
@require array.len + self.size <= MAX_SIZE `Size would exceed max.`
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void ElasticArray.add_array(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return;
|
||||
foreach (&value : array)
|
||||
{
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
fn Type[] ElasticArray.to_aligned_array(&self, Allocator allocator)
|
||||
{
|
||||
return list_common::list_to_aligned_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
@require !type_is_overaligned() : "This function is not available on overaligned types"
|
||||
*>
|
||||
macro Type[] ElasticArray.to_array(&self, Allocator allocator)
|
||||
{
|
||||
return list_common::list_to_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
fn Type[] ElasticArray.to_tarray(&self)
|
||||
{
|
||||
$if type_is_overaligned():
|
||||
return self.to_aligned_array(tmem());
|
||||
$else
|
||||
return self.to_array(tmem());
|
||||
$endif;
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the elements in a list.
|
||||
*>
|
||||
fn void ElasticArray.reverse(&self)
|
||||
{
|
||||
list_common::list_reverse(self);
|
||||
}
|
||||
|
||||
fn Type[] ElasticArray.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `List would exceed max size`
|
||||
*>
|
||||
fn void ElasticArray.push_front(&self, Type type) @inline
|
||||
{
|
||||
self.insert_at(0, type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `List would exceed max size`
|
||||
*>
|
||||
fn void! ElasticArray.push_front_try(&self, Type type) @inline
|
||||
{
|
||||
return self.insert_at_try(0, type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size
|
||||
*>
|
||||
fn void! ElasticArray.insert_at_try(&self, usz index, Type value)
|
||||
{
|
||||
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.insert_at(index, value);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `List would exceed max size`
|
||||
@require index <= self.size
|
||||
*>
|
||||
fn void ElasticArray.insert_at(&self, usz index, Type type)
|
||||
{
|
||||
for (usz i = self.size; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.size++;
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void ElasticArray.set_at(&self, usz index, Type type)
|
||||
{
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
fn void! ElasticArray.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.size--;
|
||||
}
|
||||
|
||||
fn void! ElasticArray.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type! ElasticArray.first(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
fn Type! ElasticArray.last(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
fn bool ElasticArray.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
fn usz ElasticArray.byte_size(&self) @inline
|
||||
{
|
||||
return Type.sizeof * self.size;
|
||||
}
|
||||
|
||||
fn usz ElasticArray.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
fn Type ElasticArray.get(&self, usz index) @inline
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn void ElasticArray.swap(&self, usz i, usz j)
|
||||
{
|
||||
@swap(self.entries[i], self.entries[j]);
|
||||
}
|
||||
|
||||
<*
|
||||
@param filter "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz ElasticArray.remove_if(&self, ElementPredicate filter)
|
||||
{
|
||||
return list_common::list_remove_if(self, filter, false);
|
||||
}
|
||||
|
||||
<*
|
||||
@param selection "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz ElasticArray.retain_if(&self, ElementPredicate selection)
|
||||
{
|
||||
return list_common::list_remove_if(self, selection, true);
|
||||
}
|
||||
|
||||
fn usz ElasticArray.remove_using_test(&self, ElementTest filter, any context)
|
||||
{
|
||||
return list_common::list_remove_using_test(self, filter, false, context);
|
||||
}
|
||||
|
||||
fn usz ElasticArray.retain_using_test(&self, ElementTest filter, any context)
|
||||
{
|
||||
return list_common::list_remove_using_test(self, filter, true, context);
|
||||
}
|
||||
|
||||
|
||||
macro Type ElasticArray.@item_at(&self, usz index) @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn Type* ElasticArray.get_ref(&self, usz index) @operator(&[]) @inline
|
||||
{
|
||||
return &self.entries[index];
|
||||
}
|
||||
|
||||
fn void ElasticArray.set(&self, usz index, Type value) @operator([]=)
|
||||
{
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
|
||||
// Functions for equatable types
|
||||
fn usz! ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn usz! ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach_r (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (self.size != other_list.size) return false;
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (!equals(v, other_list.entries[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Check for presence of a value in a list.
|
||||
|
||||
@param [&in] self "the list to find elements in"
|
||||
@param value "The value to search for"
|
||||
@return "True if the value is found, false otherwise"
|
||||
*>
|
||||
fn bool ElasticArray.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (equals(v, value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool ElasticArray.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
return @ok(self.remove_at(self.rindex_of(value)));
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool ElasticArray.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
return @ok(self.remove_at(self.index_of(value)));
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "the number of deleted elements."
|
||||
*>
|
||||
fn usz ElasticArray.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
return list_common::list_remove_item(self, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn void ElasticArray.remove_all_from(&self, ElasticArray* other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
foreach (v : other_list) self.remove_item(v);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] self
|
||||
@return "The number non-null values in the list"
|
||||
*>
|
||||
fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
usz vals = 0;
|
||||
foreach (v : self) if (v) vals++;
|
||||
return vals;
|
||||
}
|
||||
|
||||
fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
return list_common::list_compact(self);
|
||||
}
|
||||
56
lib7/std/collections/enummap.c3
Normal file
56
lib7/std/collections/enummap.c3
Normal file
@@ -0,0 +1,56 @@
|
||||
<*
|
||||
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap"
|
||||
*>
|
||||
module std::collections::enummap{Enum, ValueType};
|
||||
import std::io;
|
||||
struct EnumMap (Printable)
|
||||
{
|
||||
ValueType[Enum.len] values;
|
||||
}
|
||||
|
||||
fn void EnumMap.init(&self, ValueType init_value)
|
||||
{
|
||||
foreach (&a : self.values)
|
||||
{
|
||||
*a = init_value;
|
||||
}
|
||||
}
|
||||
|
||||
fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
usz n = formatter.print("{ ")!;
|
||||
foreach (i, &value : self.values)
|
||||
{
|
||||
if (i != 0) formatter.print(", ")!;
|
||||
n += formatter.printf("%s: %s", Enum.from_ordinal(i), *value)!;
|
||||
}
|
||||
n += formatter.print(" }")!;
|
||||
return n;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "The total size of this map, which is the same as the number of enum values"
|
||||
@pure
|
||||
*>
|
||||
fn usz EnumMap.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.values.len;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "Retrieve a value given the underlying enum, if there is no entry, then the zero value for the value is returned."
|
||||
*>
|
||||
fn ValueType EnumMap.get(&self, Enum key) @operator([]) @inline
|
||||
{
|
||||
return self.values[key.ordinal];
|
||||
}
|
||||
|
||||
fn ValueType* EnumMap.get_ref(&self, Enum key) @operator(&[]) @inline
|
||||
{
|
||||
return &self.values[key.ordinal];
|
||||
}
|
||||
|
||||
fn void EnumMap.set(&self, Enum key, ValueType value) @operator([]=) @inline
|
||||
{
|
||||
self.values[key.ordinal] = value;
|
||||
}
|
||||
160
lib7/std/collections/enumset.c3
Normal file
160
lib7/std/collections/enumset.c3
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
<*
|
||||
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enumset"
|
||||
*>
|
||||
module std::collections::enumset{Enum};
|
||||
import std::io;
|
||||
|
||||
def EnumSetType = $typefrom(type_for_enum_elements(Enum.elements)) @private;
|
||||
|
||||
const IS_CHAR_ARRAY = Enum.elements > 128;
|
||||
distinct EnumSet (Printable) = EnumSetType;
|
||||
|
||||
fn void EnumSet.add(&self, Enum v)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
(*self)[(usz)v.ordinal / 8] |= (char)(1u << ((usz)v.ordinal % 8));
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v.ordinal);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.clear(&self)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
*self = {};
|
||||
$else
|
||||
*self = 0;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn bool EnumSet.remove(&self, Enum v)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
if (!self.has(v) @inline) return false;
|
||||
(*self)[(usz)v.ordinal / 8] &= (char)~(1u << ((usz)v.ordinal % 8));
|
||||
return true;
|
||||
$else
|
||||
EnumSetType old = (EnumSetType)*self;
|
||||
EnumSetType new = old & ~(1u << (EnumSetType)v.ordinal);
|
||||
*self = (EnumSet)new;
|
||||
return old != new;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn bool EnumSet.has(&self, Enum v)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
return (bool)(((*self)[(usz)v.ordinal / 8] << ((usz)v.ordinal % 8)) & 0x01);
|
||||
$else
|
||||
return ((EnumSetType)*self & (1u << (EnumSetType)v.ordinal)) != 0;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.add_all(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*self)[i] |= c;
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self | (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.retain_all(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*self)[i] &= c;
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self & (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.remove_all(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*self)[i] &= ~c;
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self & ~(EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn EnumSet EnumSet.and_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
copy.retain_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self & (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn EnumSet EnumSet.or_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
copy.add_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self | (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
fn EnumSet EnumSet.diff_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
copy.remove_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self & ~(EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn EnumSet EnumSet.xor_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
foreach (i, c : s) copy[i] ^= c;
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self ^ (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
|
||||
{
|
||||
usz n = formatter.print("[")!;
|
||||
bool found;
|
||||
foreach (value : Enum.values)
|
||||
{
|
||||
if (!set.has(value)) continue;
|
||||
if (found) n += formatter.print(", ")!;
|
||||
found = true;
|
||||
n += formatter.printf("%s", value)!;
|
||||
}
|
||||
n += formatter.print("]")!;
|
||||
return n;
|
||||
}
|
||||
|
||||
macro typeid type_for_enum_elements(usz $elements) @local
|
||||
{
|
||||
$switch
|
||||
$case ($elements > 128):
|
||||
return char[($elements + 7) / 8].typeid;
|
||||
$case ($elements > 64):
|
||||
return uint128.typeid;
|
||||
$case ($elements > 32 || $$C_INT_SIZE > 32):
|
||||
return ulong.typeid;
|
||||
$case ($elements > 16 || $$C_INT_SIZE > 16):
|
||||
return uint.typeid;
|
||||
$case ($elements > 8 || $$C_INT_SIZE > 8):
|
||||
return ushort.typeid;
|
||||
$default:
|
||||
return char.typeid;
|
||||
$endswitch
|
||||
}
|
||||
580
lib7/std/collections/hashmap.c3
Normal file
580
lib7/std/collections/hashmap.c3
Normal file
@@ -0,0 +1,580 @@
|
||||
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
<*
|
||||
@require $defined((Key){}.hash()) `No .hash function found on the key`
|
||||
*>
|
||||
module std::collections::map{Key, Value};
|
||||
import std::math;
|
||||
import std::io @norecurse;
|
||||
|
||||
const uint DEFAULT_INITIAL_CAPACITY = 16;
|
||||
const uint MAXIMUM_CAPACITY = 1u << 31;
|
||||
const float DEFAULT_LOAD_FACTOR = 0.75;
|
||||
const VALUE_IS_EQUATABLE = Value.is_eq;
|
||||
const bool COPY_KEYS = types::implements_copy(Key);
|
||||
|
||||
|
||||
struct Entry
|
||||
{
|
||||
uint hash;
|
||||
Key key;
|
||||
Value value;
|
||||
Entry* next;
|
||||
}
|
||||
|
||||
struct HashMap (Printable)
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator allocator;
|
||||
uint count; // Number of elements
|
||||
uint threshold; // Resize limit
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
self.allocator = allocator;
|
||||
self.load_factor = load_factor;
|
||||
self.threshold = (uint)(capacity * load_factor);
|
||||
self.table = allocator::new_array(allocator, Entry*, capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init(tmem(), capacity, load_factor) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashMap* HashMap.init_with_key_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
self.set($vaarg[$i], $vaarg[$i + 1]);
|
||||
$endfor
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashMap* HashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.tinit_with_key_values(tmem(), capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys "The keys for the HashMap entries"
|
||||
@param [in] values "The values for the HashMap entries"
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require keys.len == values.len "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
assert(keys.len == values.len);
|
||||
self.init(allocator, capacity, load_factor);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
self.set(keys[i], values[i]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [in] keys "The keys for the HashMap entries"
|
||||
@param [in] values "The values for the HashMap entries"
|
||||
@require keys.len == values.len "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_from_keys_and_values(tmem(), keys, values, capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
Has this hash map been initialized yet?
|
||||
|
||||
@param [&in] map "The hash map we are testing"
|
||||
@return "Returns true if it has been initialized, false otherwise"
|
||||
*>
|
||||
fn bool HashMap.is_initialized(&map)
|
||||
{
|
||||
return (bool)map.allocator;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn HashMap* HashMap.init_from_map(&self, Allocator allocator, HashMap* other_map)
|
||||
{
|
||||
self.init(allocator, other_map.table.len, other_map.load_factor);
|
||||
self.put_all_for_create(other_map);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn HashMap* HashMap.tinit_from_map(&map, HashMap* other_map)
|
||||
{
|
||||
return map.init_from_map(tmem(), other_map) @inline;
|
||||
}
|
||||
|
||||
fn bool HashMap.is_empty(&map) @inline
|
||||
{
|
||||
return !map.count;
|
||||
}
|
||||
|
||||
fn usz HashMap.len(&map) @inline
|
||||
{
|
||||
return map.count;
|
||||
}
|
||||
|
||||
fn Value*! HashMap.get_ref(&map, Key key)
|
||||
{
|
||||
if (!map.count) return SearchResult.MISSING?;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return &e.value;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn Entry*! HashMap.get_entry(&map, Key key)
|
||||
{
|
||||
if (!map.count) return SearchResult.MISSING?;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value or update and
|
||||
@require $assignable(#expr, Value)
|
||||
*>
|
||||
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
{
|
||||
if (!map.count)
|
||||
{
|
||||
Value val = #expr;
|
||||
map.set(key, val);
|
||||
return val;
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e.value;
|
||||
}
|
||||
Value val = #expr;
|
||||
map.add_entry(hash, key, val, index);
|
||||
return val;
|
||||
}
|
||||
|
||||
fn Value! HashMap.get(&map, Key key) @operator([])
|
||||
{
|
||||
return *map.get_ref(key) @inline;
|
||||
}
|
||||
|
||||
fn bool HashMap.has_key(&map, Key key)
|
||||
{
|
||||
return @ok(map.get_ref(key));
|
||||
}
|
||||
|
||||
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
|
||||
{
|
||||
// If the map isn't initialized, use the defaults to initialize it.
|
||||
if (!map.allocator)
|
||||
{
|
||||
map.tinit();
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
e.value = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
map.add_entry(hash, key, value, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void! HashMap.remove(&map, Key key) @maydiscard
|
||||
{
|
||||
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn void HashMap.clear(&map)
|
||||
{
|
||||
if (!map.count) return;
|
||||
foreach (Entry** &entry_ref : map.table)
|
||||
{
|
||||
Entry* entry = *entry_ref;
|
||||
if (!entry) continue;
|
||||
Entry *next = entry.next;
|
||||
while (next)
|
||||
{
|
||||
Entry *to_delete = next;
|
||||
next = next.next;
|
||||
map.free_entry(to_delete);
|
||||
}
|
||||
map.free_entry(entry);
|
||||
*entry_ref = null;
|
||||
}
|
||||
map.count = 0;
|
||||
}
|
||||
|
||||
fn void HashMap.free(&map)
|
||||
{
|
||||
if (!map.allocator) return;
|
||||
map.clear();
|
||||
map.free_internal(map.table.ptr);
|
||||
map.table = {};
|
||||
}
|
||||
|
||||
fn Key[] HashMap.tkeys(&self)
|
||||
{
|
||||
return self.keys(tmem()) @inline;
|
||||
}
|
||||
|
||||
fn Key[] HashMap.keys(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
|
||||
Key[] list = allocator::alloc_array(allocator, Key, self.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : self.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
list[index++] = entry.key.copy(allocator);
|
||||
$else
|
||||
list[index++] = entry.key;
|
||||
$endif
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
macro HashMap.@each(map; @body(key, value))
|
||||
{
|
||||
map.@each_entry(; Entry* entry)
|
||||
{
|
||||
@body(entry.key, entry.value);
|
||||
};
|
||||
}
|
||||
|
||||
macro HashMap.@each_entry(map; @body(entry))
|
||||
{
|
||||
if (!map.count) return;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@body(entry);
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] HashMap.tvalues(&map)
|
||||
{
|
||||
return map.values(tmem()) @inline;
|
||||
}
|
||||
|
||||
fn Value[] HashMap.values(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, self.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : self.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
list[index++] = entry.value;
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
|
||||
{
|
||||
if (!map.count) return false;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
if (equals(v, entry.value)) return true;
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn HashMapIterator HashMap.iter(&self)
|
||||
{
|
||||
return { .map = self, .index = -1 };
|
||||
}
|
||||
|
||||
fn HashMapValueIterator HashMap.value_iter(&self)
|
||||
{
|
||||
return { .map = self, .index = -1 };
|
||||
}
|
||||
|
||||
fn HashMapKeyIterator HashMap.key_iter(&self)
|
||||
{
|
||||
return { .map = self, .index = -1 };
|
||||
}
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
|
||||
map.table[bucket_index] = entry;
|
||||
if (map.count++ >= map.threshold)
|
||||
{
|
||||
map.resize(map.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.resize(&map, uint new_capacity) @private
|
||||
{
|
||||
Entry*[] old_table = map.table;
|
||||
uint old_capacity = old_table.len;
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
map.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
|
||||
map.transfer(new_table);
|
||||
map.table = new_table;
|
||||
map.free_internal(old_table.ptr);
|
||||
map.threshold = (uint)(new_capacity * map.load_factor);
|
||||
}
|
||||
|
||||
fn usz! HashMap.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len;
|
||||
len += f.print("{ ")!;
|
||||
self.@each_entry(; Entry* entry)
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s: %s", entry.key, entry.value)!;
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
fn void HashMap.transfer(&map, Entry*[] new_table) @private
|
||||
{
|
||||
Entry*[] src = map.table;
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, Entry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
Entry* next = e.next;
|
||||
uint i = index_for(e.hash, new_capacity);
|
||||
e.next = new_table[i];
|
||||
new_table[i] = e;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
|
||||
{
|
||||
if (!other_map.count) return;
|
||||
foreach (Entry *e : other_map.table)
|
||||
{
|
||||
while (e)
|
||||
{
|
||||
map.put_for_create(e.key, e.value);
|
||||
e = e.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.put_for_create(&map, Key key, Value value) @private
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
e.value = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
map.create_entry(hash, key, value, i);
|
||||
}
|
||||
|
||||
fn void HashMap.free_internal(&map, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(map.allocator, ptr);
|
||||
}
|
||||
|
||||
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
if (!map.count) return false;
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
Entry* prev = map.table[i];
|
||||
Entry* e = prev;
|
||||
while (e)
|
||||
{
|
||||
Entry *next = e.next;
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
map.count--;
|
||||
if (prev == e)
|
||||
{
|
||||
map.table[i] = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
map.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
|
||||
{
|
||||
Entry *e = map.table[bucket_index];
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
|
||||
map.table[bucket_index] = entry;
|
||||
map.count++;
|
||||
}
|
||||
|
||||
fn void HashMap.free_entry(&self, Entry *entry) @local
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
allocator::free(self.allocator, entry.key);
|
||||
$endif
|
||||
self.free_internal(entry);
|
||||
}
|
||||
|
||||
|
||||
struct HashMapIterator
|
||||
{
|
||||
HashMap* map;
|
||||
int top_index;
|
||||
int index;
|
||||
Entry* current_entry;
|
||||
}
|
||||
|
||||
distinct HashMapValueIterator = HashMapIterator;
|
||||
distinct HashMapKeyIterator = HashMapIterator;
|
||||
|
||||
|
||||
<*
|
||||
@require idx < self.map.count
|
||||
*>
|
||||
fn Entry HashMapIterator.get(&self, usz idx) @operator([])
|
||||
{
|
||||
if (idx < self.index)
|
||||
{
|
||||
self.top_index = 0;
|
||||
self.current_entry = null;
|
||||
self.index = -1;
|
||||
}
|
||||
while (self.index != idx)
|
||||
{
|
||||
if (self.current_entry)
|
||||
{
|
||||
self.current_entry = self.current_entry.next;
|
||||
if (self.current_entry) self.index++;
|
||||
continue;
|
||||
}
|
||||
self.current_entry = self.map.table[self.top_index++];
|
||||
if (self.current_entry) self.index++;
|
||||
}
|
||||
return *self.current_entry;
|
||||
}
|
||||
|
||||
fn Value HashMapValueIterator.get(&self, usz idx) @operator([])
|
||||
{
|
||||
return ((HashMapIterator*)self).get(idx).value;
|
||||
}
|
||||
|
||||
fn Key HashMapKeyIterator.get(&self, usz idx) @operator([])
|
||||
{
|
||||
return ((HashMapIterator*)self).get(idx).key;
|
||||
}
|
||||
|
||||
fn usz HashMapValueIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz HashMapKeyIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz HashMapIterator.len(self) @operator(len) => self.map.count;
|
||||
|
||||
fn uint rehash(uint hash) @inline @private
|
||||
{
|
||||
hash ^= (hash >> 20) ^ (hash >> 12);
|
||||
return hash ^ ((hash >> 7) ^ (hash >> 4));
|
||||
}
|
||||
|
||||
macro uint index_for(uint hash, uint capacity) @private
|
||||
{
|
||||
return hash & (capacity - 1);
|
||||
}
|
||||
334
lib7/std/collections/linkedlist.c3
Normal file
334
lib7/std/collections/linkedlist.c3
Normal file
@@ -0,0 +1,334 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::linkedlist{Type};
|
||||
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
|
||||
struct Node @private
|
||||
{
|
||||
Node *next;
|
||||
Node *prev;
|
||||
Type value;
|
||||
}
|
||||
|
||||
struct LinkedList
|
||||
{
|
||||
Allocator allocator;
|
||||
usz size;
|
||||
Node *_first;
|
||||
Node *_last;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
@return "the initialized list"
|
||||
*>
|
||||
fn LinkedList* LinkedList.init(&self, Allocator allocator)
|
||||
{
|
||||
*self = { .allocator = allocator };
|
||||
return self;
|
||||
}
|
||||
|
||||
fn LinkedList* LinkedList.tinit(&self)
|
||||
{
|
||||
return self.init(tmem()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.allocator != null
|
||||
*>
|
||||
macro void LinkedList.free_node(&self, Node* node) @private
|
||||
{
|
||||
allocator::free(self.allocator, node);
|
||||
}
|
||||
|
||||
macro Node* LinkedList.alloc_node(&self) @private
|
||||
{
|
||||
if (!self.allocator) self.allocator = tmem();
|
||||
return allocator::alloc(self.allocator, Node);
|
||||
}
|
||||
|
||||
fn void LinkedList.push_front(&self, Type value)
|
||||
{
|
||||
Node *first = self._first;
|
||||
Node *new_node = self.alloc_node();
|
||||
*new_node = { .next = first, .value = value };
|
||||
self._first = new_node;
|
||||
if (!first)
|
||||
{
|
||||
self._last = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
first.prev = new_node;
|
||||
}
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn void LinkedList.push(&self, Type value)
|
||||
{
|
||||
Node *last = self._last;
|
||||
Node *new_node = self.alloc_node();
|
||||
*new_node = { .prev = last, .value = value };
|
||||
self._last = new_node;
|
||||
if (!last)
|
||||
{
|
||||
self._first = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
last.next = new_node;
|
||||
}
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.peek(&self) => self.first() @inline;
|
||||
fn Type! LinkedList.peek_last(&self) => self.last() @inline;
|
||||
|
||||
fn Type! LinkedList.first(&self)
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.last(&self)
|
||||
{
|
||||
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self._last.value;
|
||||
}
|
||||
|
||||
fn void LinkedList.free(&self) => self.clear() @inline;
|
||||
|
||||
fn void LinkedList.clear(&self)
|
||||
{
|
||||
for (Node* node = self._first; node != null;)
|
||||
{
|
||||
Node* next = node.next;
|
||||
self.free_node(node);
|
||||
node = next;
|
||||
}
|
||||
self._first = null;
|
||||
self._last = null;
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
fn usz LinkedList.len(&self) @inline => self.size;
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
macro Node* LinkedList.node_at_index(&self, usz index)
|
||||
{
|
||||
if (index * 2 >= self.size)
|
||||
{
|
||||
Node* node = self._last;
|
||||
index = self.size - index - 1;
|
||||
while (index--) node = node.prev;
|
||||
return node;
|
||||
}
|
||||
Node* node = self._first;
|
||||
while (index--) node = node.next;
|
||||
return node;
|
||||
}
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn Type LinkedList.get(&self, usz index)
|
||||
{
|
||||
return self.node_at_index(index).value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void LinkedList.set(&self, usz index, Type element)
|
||||
{
|
||||
self.node_at_index(index).value = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void LinkedList.remove_at(&self, usz index)
|
||||
{
|
||||
self.unlink(self.node_at_index(index));
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size
|
||||
*>
|
||||
fn void LinkedList.insert_at(&self, usz index, Type element)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
self.push_front(element);
|
||||
case self.size:
|
||||
self.push(element);
|
||||
default:
|
||||
self.link_before(self.node_at_index(index), element);
|
||||
}
|
||||
}
|
||||
<*
|
||||
@require succ != null
|
||||
*>
|
||||
fn void LinkedList.link_before(&self, Node *succ, Type value) @private
|
||||
{
|
||||
Node* pred = succ.prev;
|
||||
Node* new_node = self.alloc_node();
|
||||
*new_node = { .prev = pred, .next = succ, .value = value };
|
||||
succ.prev = new_node;
|
||||
if (!pred)
|
||||
{
|
||||
self._first = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
pred.next = new_node;
|
||||
}
|
||||
self.size++;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self._first != null
|
||||
*>
|
||||
fn void LinkedList.unlink_first(&self) @private
|
||||
{
|
||||
Node* f = self._first;
|
||||
Node* next = f.next;
|
||||
self.free_node(f);
|
||||
self._first = next;
|
||||
if (!next)
|
||||
{
|
||||
self._last = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
next.prev = null;
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
|
||||
fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
usz start = self.size;
|
||||
Node* node = self._first;
|
||||
while (node)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case equals(node.value, t):
|
||||
Node* next = node.next;
|
||||
self.unlink(node);
|
||||
node = next;
|
||||
default:
|
||||
node = node.next;
|
||||
}
|
||||
}
|
||||
return start - self.size;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.pop(&self)
|
||||
{
|
||||
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.unlink_last();
|
||||
return self._last.value;
|
||||
}
|
||||
|
||||
fn bool LinkedList.is_empty(&self)
|
||||
{
|
||||
return !self._first;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.pop_front(&self)
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.unlink_first();
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn void! LinkedList.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.unlink_last();
|
||||
}
|
||||
|
||||
fn void! LinkedList.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.unlink_first();
|
||||
}
|
||||
|
||||
|
||||
fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = self._first; node != null; node = node.next)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
self.unlink(node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = self._last; node != null; node = node.prev)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
self.unlink(node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
<*
|
||||
@require self._last != null
|
||||
*>
|
||||
fn void LinkedList.unlink_last(&self) @inline @private
|
||||
{
|
||||
Node* l = self._last;
|
||||
Node* prev = l.prev;
|
||||
self._last = prev;
|
||||
self.free_node(l);
|
||||
if (!prev)
|
||||
{
|
||||
self._first = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = null;
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
|
||||
<*
|
||||
@require x != null
|
||||
*>
|
||||
fn void LinkedList.unlink(&self, Node* x) @private
|
||||
{
|
||||
Node* next = x.next;
|
||||
Node* prev = x.prev;
|
||||
if (!prev)
|
||||
{
|
||||
self._first = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
if (!next)
|
||||
{
|
||||
self._last = prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
next.prev = prev;
|
||||
}
|
||||
self.free_node(x);
|
||||
self.size--;
|
||||
}
|
||||
570
lib7/std/collections/list.c3
Normal file
570
lib7/std/collections/list.c3
Normal file
@@ -0,0 +1,570 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::list{Type};
|
||||
import std::io, std::math, std::collections::list_common;
|
||||
|
||||
def ElementPredicate = fn bool(Type *type);
|
||||
def ElementTest = fn bool(Type *type, any context);
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
|
||||
|
||||
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct List (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
Type *entries;
|
||||
}
|
||||
|
||||
<*
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
*>
|
||||
fn List* List.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
self.capacity = 0;
|
||||
self.entries = null;
|
||||
self.reserve(initial_capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn List* List.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(tmem(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a new list with an array.
|
||||
|
||||
@param [in] values `The values to initialize the list with.`
|
||||
@require self.size == 0 "The List must be empty"
|
||||
*>
|
||||
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
|
||||
{
|
||||
self.init(allocator, values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a temporary list with an array.
|
||||
|
||||
@param [in] values `The values to initialize the list with.`
|
||||
@require self.size == 0 "The List must be empty"
|
||||
*>
|
||||
fn List* List.tinit_with_array(&self, Type[] values)
|
||||
{
|
||||
self.tinit(values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.capacity == 0 "The List must not be allocated"
|
||||
*>
|
||||
fn void List.init_wrapping_array(&self, Allocator allocator, Type[] types)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.capacity = types.len;
|
||||
self.entries = types.ptr;
|
||||
self.set_size(types.len);
|
||||
}
|
||||
|
||||
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
case 0:
|
||||
return formatter.print("[]")!;
|
||||
case 1:
|
||||
return formatter.printf("[%s]", self.entries[0])!;
|
||||
default:
|
||||
usz n = formatter.print("[")!;
|
||||
foreach (i, element : self.entries[:self.size])
|
||||
{
|
||||
if (i != 0) formatter.print(", ")!;
|
||||
n += formatter.printf("%s", element)!;
|
||||
}
|
||||
n += formatter.print("]")!;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
fn void List.push(&self, Type element) @inline
|
||||
{
|
||||
self.reserve(1);
|
||||
self.entries[self.set_size(self.size + 1)] = element;
|
||||
}
|
||||
|
||||
fn Type! List.pop(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.set_size(self.size - 1);
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
fn void List.clear(&self)
|
||||
{
|
||||
self.set_size(0);
|
||||
}
|
||||
|
||||
fn Type! List.pop_first(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size `Removed element out of bounds`
|
||||
*>
|
||||
fn void List.remove_at(&self, usz index)
|
||||
{
|
||||
self.set_size(self.size - 1);
|
||||
if (!self.size || index == self.size) return;
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
}
|
||||
|
||||
fn void List.add_all(&self, List* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
self.reserve(other_list.size);
|
||||
usz index = self.set_size(self.size + other_list.size);
|
||||
foreach (&value : other_list)
|
||||
{
|
||||
self.entries[index++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
fn Type[] List.to_aligned_array(&self, Allocator allocator)
|
||||
{
|
||||
return list_common::list_to_aligned_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
@require !type_is_overaligned() : "This function is not available on overaligned types"
|
||||
*>
|
||||
macro Type[] List.to_array(&self, Allocator allocator)
|
||||
{
|
||||
return list_common::list_to_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
fn Type[] List.to_tarray(&self)
|
||||
{
|
||||
$if type_is_overaligned():
|
||||
return self.to_aligned_array(tmem());
|
||||
$else
|
||||
return self.to_array(tmem());
|
||||
$endif;
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the elements in a list.
|
||||
*>
|
||||
fn void List.reverse(&self)
|
||||
{
|
||||
list_common::list_reverse(self);
|
||||
}
|
||||
|
||||
fn Type[] List.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Add the values of an array to this list.
|
||||
|
||||
@param [in] array
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void List.add_array(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return;
|
||||
self.reserve(array.len);
|
||||
usz index = self.set_size(self.size + array.len);
|
||||
self.entries[index : array.len] = array[..];
|
||||
}
|
||||
|
||||
fn void List.push_front(&self, Type type) @inline
|
||||
{
|
||||
self.insert_at(0, type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size `Insert was out of bounds`
|
||||
*>
|
||||
fn void List.insert_at(&self, usz index, Type type)
|
||||
{
|
||||
self.reserve(1);
|
||||
self.set_size(self.size + 1);
|
||||
for (isz i = self.size - 1; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void List.set_at(&self, usz index, Type type)
|
||||
{
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
fn void! List.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.set_size(self.size - 1);
|
||||
}
|
||||
|
||||
fn void! List.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type! List.first(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
fn Type! List.last(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
fn bool List.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
fn usz List.byte_size(&self) @inline
|
||||
{
|
||||
return Type.sizeof * self.size;
|
||||
}
|
||||
|
||||
fn usz List.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
*>
|
||||
fn Type List.get(&self, usz index) @inline
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn void List.free(&self)
|
||||
{
|
||||
if (!self.allocator || !self.capacity) return;
|
||||
|
||||
self.pre_free(); // Remove sanitizer annotation
|
||||
|
||||
$if type_is_overaligned():
|
||||
allocator::free_aligned(self.allocator, self.entries);
|
||||
$else
|
||||
allocator::free(self.allocator, self.entries);
|
||||
$endif;
|
||||
self.capacity = 0;
|
||||
self.size = 0;
|
||||
self.entries = null;
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < self.size && j < self.size `Access out of bounds`
|
||||
*>
|
||||
fn void List.swap(&self, usz i, usz j)
|
||||
{
|
||||
@swap(self.entries[i], self.entries[j]);
|
||||
}
|
||||
|
||||
<*
|
||||
@param filter "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz List.remove_if(&self, ElementPredicate filter)
|
||||
{
|
||||
return list_common::list_remove_if(self, filter, false);
|
||||
}
|
||||
|
||||
<*
|
||||
@param selection "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz List.retain_if(&self, ElementPredicate selection)
|
||||
{
|
||||
return list_common::list_remove_if(self, selection, true);
|
||||
}
|
||||
|
||||
fn usz List.remove_using_test(&self, ElementTest filter, any context)
|
||||
{
|
||||
usz old_size = self.size;
|
||||
defer
|
||||
{
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
return list_common::list_remove_using_test(self, filter, false, context);
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn usz List.retain_using_test(&self, ElementTest filter, any context)
|
||||
{
|
||||
usz old_size = self.size;
|
||||
defer {
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
return list_common::list_remove_using_test(self, filter, true, context);
|
||||
}
|
||||
|
||||
fn void List.ensure_capacity(&self, usz min_capacity) @local
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = tmem();
|
||||
|
||||
self.pre_free(); // Remove sanitizer annotation
|
||||
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
$if type_is_overaligned():
|
||||
self.entries = allocator::realloc_aligned(self.allocator, self.entries, Type.sizeof * min_capacity, alignment: Type[1].alignof)!!;
|
||||
$else
|
||||
self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity);
|
||||
$endif;
|
||||
self.capacity = min_capacity;
|
||||
|
||||
self.post_alloc(); // Add sanitizer annotation
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
*>
|
||||
macro Type List.@item_at(&self, usz index) @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
*>
|
||||
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
|
||||
{
|
||||
return &self.entries[index];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
*>
|
||||
fn void List.set(&self, usz index, Type value) @operator([]=)
|
||||
{
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
fn void List.reserve(&self, usz added)
|
||||
{
|
||||
usz new_size = self.size + added;
|
||||
if (self.capacity >= new_size) return;
|
||||
|
||||
assert(new_size < usz.max / 2U);
|
||||
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
|
||||
while (new_capacity < new_size) new_capacity *= 2U;
|
||||
self.ensure_capacity(new_capacity);
|
||||
}
|
||||
|
||||
fn void List._update_size_change(&self,usz old_size, usz new_size)
|
||||
{
|
||||
if (old_size == new_size) return;
|
||||
sanitizer::annotate_contiguous_container(self.entries,
|
||||
&self.entries[self.capacity],
|
||||
&self.entries[old_size],
|
||||
&self.entries[new_size]);
|
||||
}
|
||||
<*
|
||||
@require new_size == 0 || self.capacity != 0
|
||||
*>
|
||||
fn usz List.set_size(&self, usz new_size) @inline @private
|
||||
{
|
||||
usz old_size = self.size;
|
||||
self._update_size_change(old_size, new_size);
|
||||
self.size = new_size;
|
||||
return old_size;
|
||||
}
|
||||
|
||||
macro void List.pre_free(&self) @private
|
||||
{
|
||||
if (!self.capacity) return;
|
||||
self._update_size_change(self.size, self.capacity);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.capacity > 0
|
||||
*>
|
||||
macro void List.post_alloc(&self) @private
|
||||
{
|
||||
self._update_size_change(self.capacity, self.size);
|
||||
}
|
||||
|
||||
// Functions for equatable types
|
||||
|
||||
|
||||
fn usz! List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn usz! List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach_r (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (self.size != other_list.size) return false;
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (!equals(v, other_list.entries[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Check for presence of a value in a list.
|
||||
|
||||
@param [&in] self "the list to find elements in"
|
||||
@param value "The value to search for"
|
||||
@return "True if the value is found, false otherwise"
|
||||
*>
|
||||
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (equals(v, value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
return @ok(self.remove_at(self.rindex_of(value)));
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
return @ok(self.remove_at(self.index_of(value)));
|
||||
}
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "the number of deleted elements."
|
||||
*>
|
||||
fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
usz old_size = self.size;
|
||||
defer {
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
return list_common::list_remove_item(self, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
usz old_size = self.size;
|
||||
defer {
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
foreach (v : other_list) self.remove_item(v);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] self
|
||||
@return "The number non-null values in the list"
|
||||
*>
|
||||
fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
usz vals = 0;
|
||||
foreach (v : self) if (v) vals++;
|
||||
return vals;
|
||||
}
|
||||
|
||||
fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
usz old_size = self.size;
|
||||
defer {
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
return list_common::list_compact(self);
|
||||
}
|
||||
|
||||
// --> Deprecated
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_last_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
|
||||
{
|
||||
return self.remove_last_item(value) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_first_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
|
||||
{
|
||||
return self.remove_first_item(value) @inline;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "the number of deleted elements."
|
||||
*>
|
||||
fn usz List.remove_all_matches(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
|
||||
{
|
||||
return self.remove_item(value) @inline;
|
||||
}
|
||||
112
lib7/std/collections/list_common.c3
Normal file
112
lib7/std/collections/list_common.c3
Normal file
@@ -0,0 +1,112 @@
|
||||
module std::collections::list_common;
|
||||
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
macro list_to_aligned_array($Type, self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return ($Type[]){};
|
||||
$Type[] result = allocator::alloc_array_aligned(allocator, $Type, self.size);
|
||||
result[..] = self.entries[:self.size];
|
||||
return result;
|
||||
}
|
||||
|
||||
macro list_to_array($Type, self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return ($Type[]){};
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, self.size);
|
||||
result[..] = self.entries[:self.size];
|
||||
return result;
|
||||
}
|
||||
|
||||
macro void list_reverse(self)
|
||||
{
|
||||
if (self.size < 2) return;
|
||||
usz half = self.size / 2U;
|
||||
usz end = self.size - 1;
|
||||
for (usz i = 0; i < half; i++)
|
||||
{
|
||||
@swap(self.entries[i], self.entries[end - i]);
|
||||
}
|
||||
}
|
||||
|
||||
macro usz list_remove_using_test(self, filter, bool $invert, ctx)
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
macro usz list_compact(self)
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (self.entries[i - 1]) continue;
|
||||
for (usz j = i; j < size; j++)
|
||||
{
|
||||
self.entries[j - 1] = self.entries[j];
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
macro usz list_remove_item(self, value)
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (!equals(self.entries[i - 1], value)) continue;
|
||||
for (usz j = i; j < self.size; j++)
|
||||
{
|
||||
self.entries[j - 1] = self.entries[j];
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
|
||||
macro usz list_remove_if(self, filter, bool $invert)
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(&self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && filter(&self.entries[i - 1])) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(&self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(&self.entries[i - 1])) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
36
lib7/std/collections/maybe.c3
Normal file
36
lib7/std/collections/maybe.c3
Normal file
@@ -0,0 +1,36 @@
|
||||
module std::collections::maybe{Type};
|
||||
import std::io;
|
||||
|
||||
struct Maybe (Printable)
|
||||
{
|
||||
Type value;
|
||||
bool has_value;
|
||||
}
|
||||
|
||||
fn usz! Maybe.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
if (self.has_value) return f.printf("[%s]", self.value);
|
||||
return f.printf("[EMPTY]");
|
||||
}
|
||||
|
||||
fn void Maybe.set(&self, Type val)
|
||||
{
|
||||
*self = { .value = val, .has_value = true };
|
||||
}
|
||||
|
||||
fn void Maybe.reset(&self)
|
||||
{
|
||||
*self = {};
|
||||
}
|
||||
|
||||
fn Maybe value(Type val)
|
||||
{
|
||||
return { .value = val, .has_value = true };
|
||||
}
|
||||
|
||||
const Maybe EMPTY = { };
|
||||
|
||||
macro Type! Maybe.get(self)
|
||||
{
|
||||
return self.has_value ? self.value : SearchResult.MISSING?;
|
||||
}
|
||||
468
lib7/std/collections/object.c3
Normal file
468
lib7/std/collections/object.c3
Normal file
@@ -0,0 +1,468 @@
|
||||
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::object;
|
||||
import std::collections::map, std::collections::list, std::io;
|
||||
|
||||
const Object TRUE_OBJECT = { .b = true, .type = bool.typeid };
|
||||
const Object FALSE_OBJECT = { .b = false, .type = bool.typeid };
|
||||
const Object NULL_OBJECT = { .type = void*.typeid };
|
||||
|
||||
struct Object (Printable)
|
||||
{
|
||||
typeid type;
|
||||
Allocator allocator;
|
||||
union
|
||||
{
|
||||
uint128 i;
|
||||
double f;
|
||||
bool b;
|
||||
String s;
|
||||
void* other;
|
||||
ObjectInternalList array;
|
||||
ObjectInternalMap map;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.type)
|
||||
{
|
||||
case void:
|
||||
return formatter.printf("{}")!;
|
||||
case void*:
|
||||
return formatter.printf("null")!;
|
||||
case String:
|
||||
return formatter.printf(`"%s"`, self.s)!;
|
||||
case bool:
|
||||
return formatter.printf(self.b ? "true" : "false")!;
|
||||
case ObjectInternalList:
|
||||
usz n = formatter.printf("[")!;
|
||||
foreach (i, ol : self.array)
|
||||
{
|
||||
if (i > 0) n += formatter.printf(",")!;
|
||||
n += ol.to_format(formatter)!;
|
||||
}
|
||||
n += formatter.printf("]")!;
|
||||
return n;
|
||||
case ObjectInternalMap:
|
||||
usz n = formatter.printf("{")!;
|
||||
@stack_mem(1024; Allocator mem)
|
||||
{
|
||||
foreach (i, key : self.map.keys(mem))
|
||||
{
|
||||
if (i > 0) n += formatter.printf(",")!;
|
||||
n += formatter.printf(`"%s":`, key)!;
|
||||
n += self.map.get(key).to_format(formatter)!;
|
||||
}
|
||||
};
|
||||
n += formatter.printf("}")!;
|
||||
return n;
|
||||
default:
|
||||
switch (self.type.kindof)
|
||||
{
|
||||
case SIGNED_INT:
|
||||
return formatter.printf("%d", (int128)self.i)!;
|
||||
case UNSIGNED_INT:
|
||||
return formatter.printf("%d", (uint128)self.i)!;
|
||||
case FLOAT:
|
||||
return formatter.printf("%g", self.f)!;
|
||||
case ENUM:
|
||||
return formatter.printf("%d", self.i)!;
|
||||
default:
|
||||
return formatter.printf("<>")!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn Object* new_obj(Allocator allocator)
|
||||
{
|
||||
return allocator::new(allocator, Object, { .allocator = allocator, .type = void.typeid });
|
||||
}
|
||||
|
||||
fn Object* new_null()
|
||||
{
|
||||
return &NULL_OBJECT;
|
||||
}
|
||||
|
||||
fn Object* new_int(int128 i, Allocator allocator)
|
||||
{
|
||||
return allocator::new(allocator, Object, { .i = i, .allocator = allocator, .type = int128.typeid });
|
||||
}
|
||||
|
||||
macro Object* new_enum(e, Allocator allocator)
|
||||
{
|
||||
return allocator::new(allocator, Object, { .i = (int128)e, .allocator = allocator, .type = @typeid(e) });
|
||||
}
|
||||
|
||||
fn Object* new_float(double f, Allocator allocator)
|
||||
{
|
||||
return allocator::new(allocator, Object, { .f = f, .allocator = allocator, .type = double.typeid });
|
||||
}
|
||||
|
||||
fn Object* new_string(String s, Allocator allocator)
|
||||
{
|
||||
return allocator::new(allocator, Object, { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid });
|
||||
}
|
||||
|
||||
|
||||
fn Object* new_bool(bool b)
|
||||
{
|
||||
return b ? &TRUE_OBJECT : &FALSE_OBJECT;
|
||||
}
|
||||
|
||||
fn void Object.free(&self)
|
||||
{
|
||||
switch (self.type)
|
||||
{
|
||||
case void:
|
||||
break;
|
||||
case String:
|
||||
allocator::free(self.allocator, self.s);
|
||||
case ObjectInternalList:
|
||||
foreach (ol : self.array)
|
||||
{
|
||||
ol.free();
|
||||
}
|
||||
self.array.free();
|
||||
case ObjectInternalMap:
|
||||
self.map.@each_entry(; ObjectInternalMapEntry* entry) {
|
||||
entry.value.free();
|
||||
};
|
||||
self.map.free();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (self.allocator) allocator::free(self.allocator, self);
|
||||
}
|
||||
|
||||
fn bool Object.is_null(&self) @inline => self == &NULL_OBJECT;
|
||||
fn bool Object.is_empty(&self) @inline => self.type == void.typeid;
|
||||
fn bool Object.is_map(&self) @inline => self.type == ObjectInternalMap.typeid;
|
||||
fn bool Object.is_array(&self) @inline => self.type == ObjectInternalList.typeid;
|
||||
fn bool Object.is_bool(&self) @inline => self.type == bool.typeid;
|
||||
fn bool Object.is_string(&self) @inline => self.type == String.typeid;
|
||||
fn bool Object.is_float(&self) @inline => self.type == double.typeid;
|
||||
fn bool Object.is_int(&self) @inline => self.type == int128.typeid;
|
||||
fn bool Object.is_keyable(&self) => self.is_empty() || self.is_map();
|
||||
fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array();
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn void Object.init_map_if_needed(&self) @private
|
||||
{
|
||||
if (self.is_empty())
|
||||
{
|
||||
self.type = ObjectInternalMap.typeid;
|
||||
self.map.init(self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn void Object.init_array_if_needed(&self) @private
|
||||
{
|
||||
if (self.is_empty())
|
||||
{
|
||||
self.type = ObjectInternalList.typeid;
|
||||
self.array.init(self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn void Object.set_object(&self, String key, Object* new_object) @private
|
||||
{
|
||||
self.init_map_if_needed();
|
||||
ObjectInternalMapEntry*! entry = self.map.get_entry(key);
|
||||
defer
|
||||
{
|
||||
(void)entry.value.free();
|
||||
}
|
||||
self.map.set(key, new_object);
|
||||
}
|
||||
|
||||
macro Object* Object.object_from_value(&self, value) @private
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
$switch
|
||||
$case types::is_int($Type):
|
||||
return new_int(value, self.allocator);
|
||||
$case types::is_float($Type):
|
||||
return new_float(value, self.allocator);
|
||||
$case $Type.typeid == String.typeid:
|
||||
return new_string(value, self.allocator);
|
||||
$case $Type.typeid == bool.typeid:
|
||||
return new_bool(value);
|
||||
$case $Type.typeid == Object*.typeid:
|
||||
return value;
|
||||
$case $Type.typeid == void*.typeid:
|
||||
if (value != null) return CastResult.TYPE_MISMATCH?;
|
||||
return &NULL_OBJECT;
|
||||
$case $assignable(value, String):
|
||||
return new_string(value, self.allocator);
|
||||
$default:
|
||||
$error "Unsupported object type.";
|
||||
$endswitch
|
||||
|
||||
}
|
||||
|
||||
macro Object* Object.set(&self, String key, value)
|
||||
{
|
||||
Object* val = self.object_from_value(value);
|
||||
self.set_object(key, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
macro Object* Object.set_at(&self, usz index, String key, value)
|
||||
{
|
||||
Object* val = self.object_from_value(value);
|
||||
self.set_object_at(key, index, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
@ensure return != null
|
||||
*>
|
||||
macro Object* Object.push(&self, value)
|
||||
{
|
||||
Object* val = self.object_from_value(value);
|
||||
self.push_object(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn Object*! Object.get(&self, String key) => self.is_empty() ? SearchResult.MISSING? : self.map.get(key);
|
||||
|
||||
|
||||
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn Object* Object.get_at(&self, usz index)
|
||||
{
|
||||
return self.array.get(index);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn usz Object.get_len(&self)
|
||||
{
|
||||
return self.array.len();
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn void Object.push_object(&self, Object* to_append)
|
||||
{
|
||||
self.init_array_if_needed();
|
||||
self.array.push(to_append);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn void Object.set_object_at(&self, usz index, Object* to_set)
|
||||
{
|
||||
self.init_array_if_needed();
|
||||
while (self.array.len() < index)
|
||||
{
|
||||
self.array.push(&NULL_OBJECT);
|
||||
}
|
||||
if (self.array.len() == index)
|
||||
{
|
||||
self.array.push(to_set);
|
||||
return;
|
||||
}
|
||||
self.array.get(index).free();
|
||||
self.array.set_at(index, to_set);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof.is_int() "Expected an integer type."
|
||||
*>
|
||||
macro get_integer_value(Object* value, $Type)
|
||||
{
|
||||
if (value.is_float())
|
||||
{
|
||||
return ($Type)value.f;
|
||||
}
|
||||
if (value.is_string())
|
||||
{
|
||||
$if $Type.kindof == TypeKind.SIGNED_INT:
|
||||
return ($Type)value.s.to_int128();
|
||||
$else
|
||||
return ($Type)value.s.to_uint128();
|
||||
$endif
|
||||
}
|
||||
if (!value.is_int()) return NumberConversion.MALFORMED_INTEGER?;
|
||||
return ($Type)value.i;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
@require $Type.kindof.is_int() : "Expected an integer type"
|
||||
*>
|
||||
macro Object.get_integer_at(&self, $Type, usz index) @private
|
||||
{
|
||||
return get_integer_value(self.get_at(index), $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
@require $Type.kindof.is_int() : "Expected an integer type"
|
||||
*>
|
||||
macro Object.get_integer(&self, $Type, String key) @private
|
||||
{
|
||||
return get_integer_value(self.get(key), $Type);
|
||||
}
|
||||
|
||||
fn ichar! Object.get_ichar(&self, String key) => self.get_integer(ichar, key);
|
||||
fn short! Object.get_short(&self, String key) => self.get_integer(short, key);
|
||||
fn int! Object.get_int(&self, String key) => self.get_integer(int, key);
|
||||
fn long! Object.get_long(&self, String key) => self.get_integer(long, key);
|
||||
fn int128! Object.get_int128(&self, String key) => self.get_integer(int128, key);
|
||||
|
||||
fn ichar! Object.get_ichar_at(&self, usz index) => self.get_integer_at(ichar, index);
|
||||
fn short! Object.get_short_at(&self, usz index) => self.get_integer_at(short, index);
|
||||
fn int! Object.get_int_at(&self, usz index) => self.get_integer_at(int, index);
|
||||
fn long! Object.get_long_at(&self, usz index) => self.get_integer_at(long, index);
|
||||
fn int128! Object.get_int128_at(&self, usz index) => self.get_integer_at(int128, index);
|
||||
|
||||
fn char! Object.get_char(&self, String key) => self.get_integer(ichar, key);
|
||||
fn short! Object.get_ushort(&self, String key) => self.get_integer(ushort, key);
|
||||
fn uint! Object.get_uint(&self, String key) => self.get_integer(uint, key);
|
||||
fn ulong! Object.get_ulong(&self, String key) => self.get_integer(ulong, key);
|
||||
fn uint128! Object.get_uint128(&self, String key) => self.get_integer(uint128, key);
|
||||
|
||||
fn char! Object.get_char_at(&self, usz index) => self.get_integer_at(char, index);
|
||||
fn ushort! Object.get_ushort_at(&self, usz index) => self.get_integer_at(ushort, index);
|
||||
fn uint! Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index);
|
||||
fn ulong! Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index);
|
||||
fn uint128! Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn String! Object.get_string(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn String! Object.get_string_at(&self, usz index)
|
||||
{
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
macro String! Object.get_enum(&self, $EnumType, String key)
|
||||
{
|
||||
Object value = self.get(key)!;
|
||||
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
macro String! Object.get_enum_at(&self, $EnumType, usz index)
|
||||
{
|
||||
Object value = self.get_at(index);
|
||||
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn bool! Object.get_bool(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn bool! Object.get_bool_at(&self, usz index)
|
||||
{
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn double! Object.get_float(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
switch (value.type.kindof)
|
||||
{
|
||||
case SIGNED_INT:
|
||||
return (double)value.i;
|
||||
case UNSIGNED_INT:
|
||||
return (double)(uint128)value.i;
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
return CastResult.TYPE_MISMATCH?;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn double! Object.get_float_at(&self, usz index)
|
||||
{
|
||||
Object* value = self.get_at(index);
|
||||
switch (value.type.kindof)
|
||||
{
|
||||
case SIGNED_INT:
|
||||
return (double)value.i;
|
||||
case UNSIGNED_INT:
|
||||
return (double)(uint128)value.i;
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
return CastResult.TYPE_MISMATCH?;
|
||||
}
|
||||
}
|
||||
|
||||
fn Object* Object.get_or_create_obj(&self, String key)
|
||||
{
|
||||
if (try obj = self.get(key) && !obj.is_null()) return obj;
|
||||
Object* container = new_obj(self.allocator);
|
||||
self.set(key, container);
|
||||
return container;
|
||||
}
|
||||
|
||||
def ObjectInternalMap = HashMap{String, Object*} @private;
|
||||
def ObjectInternalList = List{Object*} @private;
|
||||
def ObjectInternalMapEntry = Entry{String, Object*} @private;
|
||||
|
||||
155
lib7/std/collections/priorityqueue.c3
Normal file
155
lib7/std/collections/priorityqueue.c3
Normal file
@@ -0,0 +1,155 @@
|
||||
// priorityqueue.c3
|
||||
// A priority queue using a classic binary heap for C3.
|
||||
//
|
||||
// Copyright (c) 2022-2025 David Kopec
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
module std::collections::priorityqueue{Type};
|
||||
import std::collections::priorityqueue::private;
|
||||
|
||||
distinct PriorityQueue = inline PrivatePriorityQueue{Type, false};
|
||||
distinct PriorityQueueMax = inline PrivatePriorityQueue{Type, true};
|
||||
|
||||
module std::collections::priorityqueue::private{Type, MAX};
|
||||
import std::collections::list, std::io;
|
||||
|
||||
struct PrivatePriorityQueue (Printable)
|
||||
{
|
||||
List{Type} heap;
|
||||
}
|
||||
|
||||
fn PrivatePriorityQueue* PrivatePriorityQueue.init(&self, Allocator allocator, usz initial_capacity = 16, ) @inline
|
||||
{
|
||||
self.heap.init(allocator, initial_capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn PrivatePriorityQueue* PrivatePriorityQueue.tinit(&self, usz initial_capacity = 16) @inline
|
||||
{
|
||||
self.init(tmem(), initial_capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
fn void PrivatePriorityQueue.push(&self, Type element)
|
||||
{
|
||||
self.heap.push(element);
|
||||
usz i = self.heap.len() - 1;
|
||||
while (i > 0)
|
||||
{
|
||||
usz parent = (i - 1) / 2;
|
||||
Type item = self.heap[i];
|
||||
Type parent_item = self.heap[parent];
|
||||
$if MAX:
|
||||
bool ok = greater(item, parent_item);
|
||||
$else
|
||||
bool ok = less(item, parent_item);
|
||||
$endif
|
||||
if (!ok) break;
|
||||
self.heap.swap(i, parent);
|
||||
i = parent;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len() : "Index out of range"
|
||||
*>
|
||||
fn void PrivatePriorityQueue.remove_at(&self, usz index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
self.pop()!!;
|
||||
return;
|
||||
}
|
||||
self.heap.remove_at(index);
|
||||
}
|
||||
<*
|
||||
@require self != null
|
||||
*>
|
||||
fn Type! PrivatePriorityQueue.pop(&self)
|
||||
{
|
||||
usz i = 0;
|
||||
usz len = self.heap.len();
|
||||
if (!len) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
usz new_count = len - 1;
|
||||
self.heap.swap(0, new_count);
|
||||
while OUTER: ((2 * i + 1) < new_count)
|
||||
{
|
||||
usz j = 2 * i + 1;
|
||||
Type left = self.heap[j];
|
||||
Type item = self.heap[i];
|
||||
switch
|
||||
{
|
||||
case j + 1 < new_count:
|
||||
Type right = self.heap[j + 1];
|
||||
$if MAX:
|
||||
if (!greater(right, left)) nextcase;
|
||||
if (!greater(right, item)) break OUTER;
|
||||
$else
|
||||
if (!greater(left, right)) nextcase;
|
||||
if (!greater(item, right)) break OUTER;
|
||||
$endif
|
||||
j++;
|
||||
default:
|
||||
$if MAX:
|
||||
if (!greater(left, item)) break OUTER;
|
||||
$else
|
||||
if (!greater(item, left)) break OUTER;
|
||||
$endif
|
||||
}
|
||||
self.heap.swap(i, j);
|
||||
i = j;
|
||||
}
|
||||
|
||||
return self.heap.pop();
|
||||
}
|
||||
|
||||
fn Type! PrivatePriorityQueue.first(&self)
|
||||
{
|
||||
return self.heap.first();
|
||||
}
|
||||
|
||||
fn void PrivatePriorityQueue.free(&self)
|
||||
{
|
||||
self.heap.free();
|
||||
}
|
||||
|
||||
fn usz PrivatePriorityQueue.len(&self) @operator(len)
|
||||
{
|
||||
return self.heap.len() @inline;
|
||||
}
|
||||
|
||||
fn bool PrivatePriorityQueue.is_empty(&self)
|
||||
{
|
||||
return self.heap.is_empty() @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len()
|
||||
*>
|
||||
fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
|
||||
{
|
||||
return self.heap[index];
|
||||
}
|
||||
|
||||
fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return self.heap.to_format(formatter);
|
||||
}
|
||||
|
||||
65
lib7/std/collections/range.c3
Normal file
65
lib7/std/collections/range.c3
Normal file
@@ -0,0 +1,65 @@
|
||||
<*
|
||||
@require Type.is_ordered : "The type must be ordered"
|
||||
*>
|
||||
module std::collections::range{Type};
|
||||
import std::io;
|
||||
|
||||
struct Range (Printable)
|
||||
{
|
||||
Type start;
|
||||
Type end;
|
||||
}
|
||||
|
||||
fn usz Range.len(&self) @operator(len)
|
||||
{
|
||||
if (self.end < self.start) return 0;
|
||||
return (usz)(self.end - self.start) + 1;
|
||||
}
|
||||
|
||||
fn bool Range.contains(&self, Type value) @inline
|
||||
{
|
||||
return value >= self.start && value <= self.end;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len() : "Can't index into an empty range"
|
||||
*>
|
||||
fn Type Range.get(&self, usz index) @operator([])
|
||||
{
|
||||
return (Type)(self.start + (usz)index);
|
||||
}
|
||||
|
||||
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return formatter.printf("[%s..%s]", self.start, self.end)!;
|
||||
}
|
||||
|
||||
struct ExclusiveRange (Printable)
|
||||
{
|
||||
Type start;
|
||||
Type end;
|
||||
}
|
||||
|
||||
fn usz ExclusiveRange.len(&self) @operator(len)
|
||||
{
|
||||
if (self.end < self.start) return 0;
|
||||
return (usz)(self.end - self.start);
|
||||
}
|
||||
|
||||
fn bool ExclusiveRange.contains(&self, Type value) @inline
|
||||
{
|
||||
return value >= self.start && value < self.end;
|
||||
}
|
||||
|
||||
fn usz! ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return formatter.printf("[%s..<%s]", self.start, self.end)!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len() : "Can't index into an empty range"
|
||||
*>
|
||||
fn Type ExclusiveRange.get(&self, usz index) @operator([])
|
||||
{
|
||||
return (Type)(self.start + index);
|
||||
}
|
||||
115
lib7/std/collections/ringbuffer.c3
Normal file
115
lib7/std/collections/ringbuffer.c3
Normal file
@@ -0,0 +1,115 @@
|
||||
<*
|
||||
@require Type.kindof == ARRAY : "Required an array type"
|
||||
*>
|
||||
module std::collections::ringbuffer{Type};
|
||||
import std::io;
|
||||
|
||||
def Element = $typeof((Type){}[0]);
|
||||
|
||||
struct RingBuffer (Printable)
|
||||
{
|
||||
Type buf;
|
||||
usz written;
|
||||
usz head;
|
||||
}
|
||||
|
||||
fn void RingBuffer.init(&self) @inline
|
||||
{
|
||||
*self = {};
|
||||
}
|
||||
|
||||
fn void RingBuffer.push(&self, Element c)
|
||||
{
|
||||
if (self.written < self.buf.len)
|
||||
{
|
||||
self.buf[self.written] = c;
|
||||
self.written++;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.buf[self.head] = c;
|
||||
self.head = (self.head + 1) % self.buf.len;
|
||||
}
|
||||
}
|
||||
|
||||
fn Element RingBuffer.get(&self, usz index) @operator([])
|
||||
{
|
||||
index %= self.buf.len;
|
||||
usz avail = self.buf.len - self.head;
|
||||
if (index < avail)
|
||||
{
|
||||
return self.buf[self.head + index];
|
||||
}
|
||||
return self.buf[index - avail];
|
||||
}
|
||||
|
||||
fn Element! RingBuffer.pop(&self)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case self.written == 0:
|
||||
return SearchResult.MISSING?;
|
||||
case self.written < self.buf.len:
|
||||
self.written--;
|
||||
return self.buf[self.written];
|
||||
default:
|
||||
self.head = (self.head - 1) % self.buf.len;
|
||||
return self.buf[self.head];
|
||||
}
|
||||
}
|
||||
|
||||
fn usz! RingBuffer.to_format(&self, Formatter* format) @dynamic
|
||||
{
|
||||
// Improve this?
|
||||
return format.printf("%s", self.buf);
|
||||
}
|
||||
|
||||
fn usz RingBuffer.read(&self, usz index, Element[] buffer)
|
||||
{
|
||||
index %= self.buf.len;
|
||||
if (self.written < self.buf.len)
|
||||
{
|
||||
if (index >= self.written) return 0;
|
||||
usz end = self.written - index;
|
||||
usz n = min(end, buffer.len);
|
||||
buffer[:n] = self.buf[index:n];
|
||||
return n;
|
||||
}
|
||||
usz end = self.buf.len - self.head;
|
||||
if (index >= end)
|
||||
{
|
||||
index -= end;
|
||||
if (index >= self.head) return 0;
|
||||
usz n = min(self.head - index, buffer.len);
|
||||
buffer[:n] = self.buf[index:n];
|
||||
return n;
|
||||
}
|
||||
if (buffer.len <= self.buf.len - index)
|
||||
{
|
||||
usz n = buffer.len;
|
||||
buffer[:n] = self.buf[self.head + index:n];
|
||||
return n;
|
||||
}
|
||||
usz n1 = self.buf.len - index;
|
||||
buffer[:n1] = self.buf[self.head + index:n1];
|
||||
buffer = buffer[n1..];
|
||||
index -= n1;
|
||||
usz n2 = min(self.head - index, buffer.len);
|
||||
buffer[:n2] = self.buf[index:n2];
|
||||
return n1 + n2;
|
||||
}
|
||||
|
||||
fn void RingBuffer.write(&self, Element[] buffer)
|
||||
{
|
||||
usz i;
|
||||
while (self.written < self.buf.len && i < buffer.len)
|
||||
{
|
||||
self.buf[self.written] = buffer[i++];
|
||||
self.written++;
|
||||
}
|
||||
foreach (c : buffer[i..])
|
||||
{
|
||||
self.buf[self.head] = c;
|
||||
self.head = (self.head + 1) % self.buf.len;
|
||||
}
|
||||
}
|
||||
16
lib7/std/collections/tuple.c3
Normal file
16
lib7/std/collections/tuple.c3
Normal file
@@ -0,0 +1,16 @@
|
||||
module std::collections::tuple{Type1, Type2};
|
||||
|
||||
struct Tuple
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
}
|
||||
|
||||
module std::collections::triple{Type1, Type2, Type3};
|
||||
|
||||
struct Triple
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
Type3 third;
|
||||
}
|
||||
474
lib7/std/compression/qoi.c3
Normal file
474
lib7/std/compression/qoi.c3
Normal file
@@ -0,0 +1,474 @@
|
||||
module std::compression::qoi;
|
||||
|
||||
const uint PIXELS_MAX = 400000000;
|
||||
|
||||
<*
|
||||
Colorspace.
|
||||
Purely informative. It will be saved to the file header,
|
||||
but does not affect how chunks are en-/decoded.
|
||||
*>
|
||||
enum QOIColorspace : char (char id)
|
||||
{
|
||||
SRGB = 0, // sRGB with linear alpha
|
||||
LINEAR = 1 // all channels linear
|
||||
}
|
||||
|
||||
<*
|
||||
Channels.
|
||||
The channels used in an image.
|
||||
AUTO can be used when decoding to automatically determine
|
||||
the channels from the file's header.
|
||||
*>
|
||||
enum QOIChannels : char (char id)
|
||||
{
|
||||
AUTO = 0,
|
||||
RGB = 3,
|
||||
RGBA = 4
|
||||
}
|
||||
|
||||
<*
|
||||
Descriptor.
|
||||
Contains information about an image.
|
||||
*>
|
||||
struct QOIDesc
|
||||
{
|
||||
uint width;
|
||||
uint height;
|
||||
QOIChannels channels;
|
||||
QOIColorspace colorspace;
|
||||
}
|
||||
|
||||
<*
|
||||
QOI Errors.
|
||||
These are all the possible bad outcomes.
|
||||
*>
|
||||
fault QOIError
|
||||
{
|
||||
INVALID_PARAMETERS,
|
||||
FILE_OPEN_FAILED,
|
||||
FILE_WRITE_FAILED,
|
||||
INVALID_DATA,
|
||||
TOO_MANY_PIXELS
|
||||
}
|
||||
|
||||
|
||||
// Let the user decide if they want to use std::io
|
||||
module std::compression::qoi @if(!$feature(QOI_NO_STDIO));
|
||||
import std::io;
|
||||
|
||||
<*
|
||||
Encode raw RGB or RGBA pixels into a QOI image and write it to the
|
||||
file system.
|
||||
|
||||
The desc struct must be filled with the image width, height, the
|
||||
used channels (QOIChannels.RGB or RGBA) and the colorspace
|
||||
(QOIColorspace.SRGB or LINEAR).
|
||||
|
||||
The function returns an optional, which can either be a QOIError
|
||||
or the number of bytes written on success.
|
||||
|
||||
@param [in] filename `The file's name to write the image to`
|
||||
@param [in] input `The raw RGB or RGBA pixels to encode`
|
||||
@param [&in] desc `The descriptor of the image`
|
||||
*>
|
||||
fn usz! write(String filename, char[] input, QOIDesc* desc) => @pool()
|
||||
{
|
||||
// encode data
|
||||
char[] output = encode(tmem(), input, desc)!;
|
||||
|
||||
file::save(filename, output)!;
|
||||
return output.len;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Read and decode a QOI image from the file system.
|
||||
|
||||
If channels is set to QOIChannels.AUTO, the function will
|
||||
automatically determine the channels from the file's header.
|
||||
However, if channels is RGB or RGBA, the output format will be
|
||||
forced into this number of channels.
|
||||
|
||||
The desc struct will be filled with the width, height,
|
||||
channels and colorspace of the image.
|
||||
|
||||
The function returns an optional, which can either be a QOIError
|
||||
or a char[] pointing to the decoded pixels on success.
|
||||
|
||||
The returned pixel data should be free()d after use, or the decoding
|
||||
and use of the data should be wrapped in a @pool() { ... }; block.
|
||||
|
||||
@param [in] filename `The file's name to read the image from`
|
||||
@param [&out] desc `The descriptor to fill with the image's info`
|
||||
@param channels `The channels to be used`
|
||||
*>
|
||||
fn char[]! read(Allocator allocator, String filename, QOIDesc* desc, QOIChannels channels = AUTO) => @pool(allocator)
|
||||
{
|
||||
// read file
|
||||
char[] data = file::load_temp(filename) ?? QOIError.FILE_OPEN_FAILED?!;
|
||||
// pass data to decode function
|
||||
return decode(allocator, data, desc, channels);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Back to basic non-stdio mode
|
||||
module std::compression::qoi;
|
||||
import std::bits;
|
||||
|
||||
<*
|
||||
Encode raw RGB or RGBA pixels into a QOI image in memory.
|
||||
|
||||
The function returns an optional, which can either be a QOIError
|
||||
or a char[] pointing to the encoded data on success.
|
||||
|
||||
The returned qoi data should be free()d after use, or the encoding
|
||||
and use of the data should be wrapped in a @pool() { ... }; block.
|
||||
See the write() function for an example.
|
||||
|
||||
@param [in] input `The raw RGB or RGBA pixels to encode`
|
||||
@param [&in] desc `The descriptor of the image`
|
||||
*>
|
||||
fn char[]! encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
{
|
||||
// check info in desc
|
||||
if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_PARAMETERS?;
|
||||
if (desc.channels == AUTO) return QOIError.INVALID_PARAMETERS?;
|
||||
uint pixels = desc.width * desc.height;
|
||||
if (pixels > PIXELS_MAX) return QOIError.TOO_MANY_PIXELS?;
|
||||
|
||||
// check input data size
|
||||
uint image_size = pixels * desc.channels.id;
|
||||
if (image_size != input.len) return QOIError.INVALID_DATA?;
|
||||
|
||||
// allocate memory for encoded data (output)
|
||||
// header + chunk tag and RGB(A) data for each pixel + end of stream
|
||||
uint max_size = Header.sizeof + pixels + image_size + END_OF_STREAM.len;
|
||||
char[] output = allocator::alloc_array(allocator, char, max_size); // no need to init
|
||||
defer catch allocator::free(allocator, output);
|
||||
|
||||
// write header
|
||||
*(Header*)output.ptr = {
|
||||
.be_magic = bswap('qoif'),
|
||||
.be_width = bswap(desc.width),
|
||||
.be_height = bswap(desc.height),
|
||||
.channels = desc.channels.id,
|
||||
.colorspace = desc.colorspace.id
|
||||
};
|
||||
|
||||
uint pos = Header.sizeof; // Current position in output
|
||||
uint loc; // Current position in image (top-left corner)
|
||||
uint loc_end = image_size - desc.channels.id; // End of image data
|
||||
char run_length = 0; // Length of the current run
|
||||
|
||||
Pixel[64] palette; // Zero-initialized by default
|
||||
Pixel prev = { 0, 0, 0, 255 };
|
||||
Pixel p = { 0, 0, 0, 255 };
|
||||
|
||||
ichar[<3>] diff; // pre-allocate for diff
|
||||
ichar[<3>] luma; // ...and luma
|
||||
|
||||
// write chunks
|
||||
for (loc = 0; loc < image_size; loc += desc.channels.id)
|
||||
{
|
||||
// set previous pixel
|
||||
prev = p;
|
||||
|
||||
// get current pixel
|
||||
p[:3] = input[loc:3]; // cutesy slices :3
|
||||
if (desc.channels == RGBA) p.a = input[loc + 3];
|
||||
|
||||
// check if we can run the previous pixel
|
||||
if (prev == p)
|
||||
{
|
||||
run_length++;
|
||||
if (run_length == 62 || loc == loc_end)
|
||||
{
|
||||
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
|
||||
run_length = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// end last run if there was one
|
||||
if (run_length > 0)
|
||||
{
|
||||
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
|
||||
run_length = 0;
|
||||
}
|
||||
|
||||
switch
|
||||
{
|
||||
// check if we can index the palette
|
||||
case (palette[p.hash()] == p):
|
||||
*@extract(OpIndex, output, &pos) = {
|
||||
OP_INDEX,
|
||||
p.hash()
|
||||
};
|
||||
|
||||
// check if we can use diff or luma
|
||||
case (prev != p && prev.a == p.a):
|
||||
// diff the pixels
|
||||
diff = p.rgb - prev.rgb;
|
||||
if (diff.r > -3 && diff.r < 2
|
||||
&& diff.g > -3 && diff.g < 2
|
||||
&& diff.b > -3 && diff.b < 2)
|
||||
{
|
||||
*@extract(OpDiff, output, &pos) = {
|
||||
OP_DIFF,
|
||||
(char)diff.r + 2,
|
||||
(char)diff.g + 2,
|
||||
(char)diff.b + 2
|
||||
};
|
||||
palette[p.hash()] = p;
|
||||
break;
|
||||
}
|
||||
// check luma eligibility
|
||||
luma = { diff.r - diff.g, diff.g, diff.b - diff.g };
|
||||
if (luma.r >= -8 && luma.r <= 7
|
||||
&& luma.g >= -32 && luma.g <= 31
|
||||
&& luma.b >= -8 && luma.b <= 7)
|
||||
{
|
||||
*@extract(OpLuma, output, &pos) = {
|
||||
OP_LUMA,
|
||||
(char)luma.g + 32,
|
||||
(char)luma.r + 8,
|
||||
(char)luma.b + 8
|
||||
};
|
||||
palette[p.hash()] = p;
|
||||
break;
|
||||
}
|
||||
nextcase;
|
||||
|
||||
// worst case scenario: just encode the raw pixel
|
||||
default:
|
||||
if (prev.a != p.a)
|
||||
{
|
||||
*@extract(OpRGBA, output, &pos) = { OP_RGBA, p.r, p.g, p.b, p.a };
|
||||
}
|
||||
else
|
||||
{
|
||||
*@extract(OpRGB, output, &pos) = { OP_RGB, p.r, p.g, p.b };
|
||||
}
|
||||
palette[p.hash()] = p;
|
||||
}
|
||||
}
|
||||
|
||||
// write end of stream
|
||||
output[pos:END_OF_STREAM.len] = END_OF_STREAM;
|
||||
pos += END_OF_STREAM.len;
|
||||
|
||||
return output[:pos];
|
||||
}
|
||||
|
||||
|
||||
|
||||
<*
|
||||
Decode a QOI image from memory.
|
||||
|
||||
If channels is set to QOIChannels.AUTO, the function will
|
||||
automatically determine the channels from the file's header.
|
||||
However, if channels is RGB or RGBA, the output format will be
|
||||
forced into this number of channels.
|
||||
|
||||
The desc struct will be filled with the width, height,
|
||||
channels and colorspace of the image.
|
||||
|
||||
The function returns an optional, which can either be a QOIError
|
||||
or a char[] pointing to the decoded pixels on success.
|
||||
|
||||
The returned pixel data should be free()d after use, or the decoding
|
||||
and use of the data should be wrapped in a @pool() { ... }; block.
|
||||
|
||||
@param [in] data `The QOI image data to decode`
|
||||
@param [&out] desc `The descriptor to fill with the image's info`
|
||||
@param channels `The channels to be used`
|
||||
*>
|
||||
fn char[]! decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels channels = AUTO) @nodiscard
|
||||
{
|
||||
// check input data
|
||||
if (data.len < Header.sizeof + END_OF_STREAM.len) return QOIError.INVALID_DATA?;
|
||||
|
||||
// get header
|
||||
Header* header = (Header*)data.ptr;
|
||||
|
||||
// check magic bytes (FourCC)
|
||||
if (bswap(header.be_magic) != 'qoif') return QOIError.INVALID_DATA?;
|
||||
|
||||
// copy header data to desc
|
||||
desc.width = bswap(header.be_width);
|
||||
desc.height = bswap(header.be_height);
|
||||
desc.channels = @enumcast(QOIChannels, header.channels)!; // Rethrow if invalid
|
||||
desc.colorspace = @enumcast(QOIColorspace, header.colorspace)!; // Rethrow if invalid
|
||||
if (desc.channels == AUTO) return QOIError.INVALID_DATA?; // Channels must be specified in the header
|
||||
|
||||
// check width and height
|
||||
if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_DATA?;
|
||||
|
||||
// check pixel count
|
||||
ulong pixels = (ulong)desc.width * (ulong)desc.height;
|
||||
if (pixels > PIXELS_MAX) return QOIError.TOO_MANY_PIXELS?;
|
||||
|
||||
uint pos = Header.sizeof; // Current position in data
|
||||
uint loc; // Current position in image (top-left corner)
|
||||
char run_length = 0; // Length of the current run
|
||||
char tag; // Current chunk tag
|
||||
|
||||
Pixel[64] palette; // Zero-initialized by default
|
||||
Pixel p = { 0, 0, 0, 255 };
|
||||
|
||||
if (channels == AUTO) channels = desc.channels;
|
||||
|
||||
// allocate memory for image data
|
||||
usz image_size = (usz)pixels * channels.id;
|
||||
char[] image = allocator::alloc_array(allocator, char, image_size);
|
||||
defer catch allocator::free(allocator, image);
|
||||
|
||||
for (loc = 0; loc < image_size; loc += channels.id)
|
||||
{
|
||||
// get chunk tag
|
||||
tag = data[pos];
|
||||
|
||||
// check for chunk type
|
||||
switch
|
||||
{
|
||||
case run_length > 0:
|
||||
run_length--;
|
||||
|
||||
case tag == OP_RGB:
|
||||
OpRGB* op = @extract(OpRGB, data, &pos);
|
||||
p = { op.red, op.green, op.blue, p.a };
|
||||
palette[p.hash()] = p;
|
||||
|
||||
case tag == OP_RGBA:
|
||||
OpRGBA* op = @extract(OpRGBA, data, &pos);
|
||||
p = { op.red, op.green, op.blue, op.alpha };
|
||||
palette[p.hash()] = p;
|
||||
|
||||
case tag >> 6 == OP_INDEX:
|
||||
OpIndex* op = @extract(OpIndex, data, &pos);
|
||||
p = palette[op.index];
|
||||
|
||||
case tag >> 6 == OP_DIFF:
|
||||
OpDiff* op = @extract(OpDiff, data, &pos);
|
||||
p.r += op.diff_red - 2;
|
||||
p.g += op.diff_green - 2;
|
||||
p.b += op.diff_blue - 2;
|
||||
palette[p.hash()] = p;
|
||||
|
||||
case tag >> 6 == OP_LUMA:
|
||||
OpLuma* op = @extract(OpLuma, data, &pos);
|
||||
int diff_green = op.diff_green - 32;
|
||||
p.r += (char)(op.diff_red_minus_green - 8 + diff_green);
|
||||
p.g += (char)(diff_green);
|
||||
p.b += (char)(op.diff_blue_minus_green - 8 + diff_green);
|
||||
palette[p.hash()] = p;
|
||||
|
||||
case tag >> 6 == OP_RUN:
|
||||
OpRun* op = @extract(OpRun, data, &pos);
|
||||
run_length = op.run;
|
||||
}
|
||||
|
||||
// draw the pixel
|
||||
if (channels == RGBA) { image[loc:4] = p.rgba; } else { image[loc:3] = p.rgb; }
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ***************************************************************************
|
||||
// *** ***
|
||||
// *** Main functions are at the top to make the file more readable. ***
|
||||
// *** From here on, helper functions and types are defined. ***
|
||||
// *** ***
|
||||
// ***************************************************************************
|
||||
module std::compression::qoi @private;
|
||||
|
||||
// 8-bit opcodes
|
||||
const OP_RGB = 0b11111110;
|
||||
const OP_RGBA = 0b11111111;
|
||||
// 2-bit opcodes
|
||||
const OP_INDEX = 0b00;
|
||||
const OP_DIFF = 0b01;
|
||||
const OP_LUMA = 0b10;
|
||||
const OP_RUN = 0b11;
|
||||
|
||||
struct Header @packed
|
||||
{
|
||||
uint be_magic; // magic bytes "qoif"
|
||||
uint be_width; // image width in pixels (BE)
|
||||
uint be_height; // image height in pixels (BE)
|
||||
|
||||
// informative fields
|
||||
char channels; // 3 = RGB, 4 = RGB
|
||||
char colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
|
||||
}
|
||||
|
||||
const char[?] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1};
|
||||
|
||||
// inefficient, but it's only run once at a time
|
||||
macro @enumcast($Type, raw)
|
||||
{
|
||||
foreach (value : $Type.values)
|
||||
{
|
||||
if (value.id == raw) return value;
|
||||
}
|
||||
return QOIError.INVALID_DATA?;
|
||||
}
|
||||
|
||||
distinct Pixel = inline char[<4>];
|
||||
macro char Pixel.hash(Pixel p)
|
||||
{
|
||||
return (p.r * 3 + p.g * 5 + p.b * 7 + p.a * 11) % 64;
|
||||
}
|
||||
|
||||
struct OpRGB // No need to use @packed here, the alignment is 1 anyways.
|
||||
{
|
||||
char tag;
|
||||
char red;
|
||||
char green;
|
||||
char blue;
|
||||
}
|
||||
struct OpRGBA @packed
|
||||
{
|
||||
char tag;
|
||||
char red;
|
||||
char green;
|
||||
char blue;
|
||||
char alpha;
|
||||
}
|
||||
bitstruct OpIndex : char
|
||||
{
|
||||
char tag : 6..7;
|
||||
char index : 0..5;
|
||||
}
|
||||
bitstruct OpDiff : char
|
||||
{
|
||||
char tag : 6..7;
|
||||
char diff_red : 4..5;
|
||||
char diff_green : 2..3;
|
||||
char diff_blue : 0..1;
|
||||
}
|
||||
bitstruct OpLuma : ushort @align(1)
|
||||
{
|
||||
char tag : 6..7;
|
||||
char diff_green : 0..5;
|
||||
char diff_red_minus_green : 12..15;
|
||||
char diff_blue_minus_green : 8..11;
|
||||
}
|
||||
bitstruct OpRun : char
|
||||
{
|
||||
char tag : 6..7;
|
||||
char run : 0..5;
|
||||
}
|
||||
|
||||
// Macro used to locate chunks in data buffers.
|
||||
// Can be used both for reading and writing.
|
||||
macro @extract($Type, char[] data, uint* pos)
|
||||
{
|
||||
// slice data, then double cast
|
||||
$Type* chunk = ($Type*)data[*pos : $Type.sizeof].ptr;
|
||||
*pos += $Type.sizeof;
|
||||
return chunk;
|
||||
}
|
||||
112
lib7/std/core/allocators/arena_allocator.c3
Normal file
112
lib7/std/core/allocators/arena_allocator.c3
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
struct ArenaAllocator (Allocator)
|
||||
{
|
||||
char[] data;
|
||||
usz used;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a memory arena for use using the provided bytes.
|
||||
*>
|
||||
fn ArenaAllocator* ArenaAllocator.init(&self, char[] data)
|
||||
{
|
||||
self.data = data;
|
||||
self.used = 0;
|
||||
return self;
|
||||
}
|
||||
|
||||
fn void ArenaAllocator.clear(&self)
|
||||
{
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
struct ArenaAllocatorHeader @local
|
||||
{
|
||||
usz size;
|
||||
char[?] data;
|
||||
}
|
||||
|
||||
macro ArenaAllocator* wrap(char[] bytes)
|
||||
{
|
||||
return (ArenaAllocator){}.init(bytes);
|
||||
}
|
||||
|
||||
<*
|
||||
@require ptr != null
|
||||
*>
|
||||
fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
{
|
||||
assert((uptr)ptr >= (uptr)self.data.ptr, "Pointer originates from a different allocator.");
|
||||
ArenaAllocatorHeader* header = ptr - ArenaAllocatorHeader.sizeof;
|
||||
// Reclaim memory if it's the last element.
|
||||
if (ptr + header.size == &self.data[self.used])
|
||||
{
|
||||
self.used -= header.size + ArenaAllocatorHeader.sizeof;
|
||||
}
|
||||
}
|
||||
|
||||
fn usz ArenaAllocator.mark(&self) @dynamic => self.used;
|
||||
fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark;
|
||||
|
||||
<*
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require size > 0
|
||||
*>
|
||||
fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
|
||||
void* start_mem = self.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof;
|
||||
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(mem - self.data.ptr) + size;
|
||||
if (end > total_len) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.used = end;
|
||||
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
|
||||
header.size = size;
|
||||
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
<*
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require old_pointer != null
|
||||
@require size > 0
|
||||
*>
|
||||
fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
|
||||
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
// Do last allocation and alignment match?
|
||||
if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
if (old_size >= size)
|
||||
{
|
||||
self.used -= old_size - size;
|
||||
}
|
||||
else
|
||||
{
|
||||
usz new_used = self.used + size - old_size;
|
||||
if (new_used > total_len) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.used = new_used;
|
||||
}
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
}
|
||||
// Otherwise just allocate new memory.
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
209
lib7/std/core/allocators/dynamic_arena.c3
Normal file
209
lib7/std/core/allocators/dynamic_arena.c3
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
struct DynamicArenaAllocator (Allocator)
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
DynamicArenaPage* page;
|
||||
DynamicArenaPage* unused_page;
|
||||
usz page_size;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator
|
||||
@require page_size >= 128
|
||||
*>
|
||||
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator allocator)
|
||||
{
|
||||
self.page = null;
|
||||
self.unused_page = null;
|
||||
self.page_size = page_size;
|
||||
self.backing_allocator = allocator;
|
||||
}
|
||||
|
||||
fn void DynamicArenaAllocator.free(&self)
|
||||
{
|
||||
DynamicArenaPage* page = self.page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
allocator::free(self.backing_allocator, page.memory);
|
||||
allocator::free(self.backing_allocator, page);
|
||||
page = next_page;
|
||||
}
|
||||
page = self.unused_page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
allocator::free(self.backing_allocator, page.memory);
|
||||
allocator::free(self.backing_allocator, page);
|
||||
page = next_page;
|
||||
}
|
||||
self.page = null;
|
||||
self.unused_page = null;
|
||||
}
|
||||
|
||||
struct DynamicArenaPage @local
|
||||
{
|
||||
void* memory;
|
||||
void* prev_arena;
|
||||
usz total;
|
||||
usz used;
|
||||
void* current_stack_ptr;
|
||||
}
|
||||
|
||||
struct DynamicArenaChunk @local
|
||||
{
|
||||
usz size;
|
||||
}
|
||||
|
||||
<*
|
||||
@require ptr != null
|
||||
@require self.page != null `tried to free pointer on invalid allocator`
|
||||
*>
|
||||
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
{
|
||||
DynamicArenaPage* current_page = self.page;
|
||||
if (ptr == current_page.current_stack_ptr)
|
||||
{
|
||||
current_page.used = (usz)((ptr - DEFAULT_SIZE_PREFIX) - current_page.memory);
|
||||
}
|
||||
current_page.current_stack_ptr = null;
|
||||
}
|
||||
|
||||
<*
|
||||
@require size > 0 `Resize doesn't support zeroing`
|
||||
@require old_pointer != null `Resize doesn't handle null pointers`
|
||||
@require self.page != null `tried to realloc pointer on invalid allocator`
|
||||
*>
|
||||
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
DynamicArenaPage* current_page = self.page;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
|
||||
usz old_size = *old_size_ptr;
|
||||
// We have the old pointer and it's correctly aligned.
|
||||
if (old_size >= size && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
*old_size_ptr = size;
|
||||
if (current_page.current_stack_ptr == old_pointer)
|
||||
{
|
||||
current_page.used = (usz)((old_pointer - DEFAULT_SIZE_PREFIX) - current_page.memory);
|
||||
}
|
||||
return old_pointer;
|
||||
}
|
||||
if REUSE: (current_page.current_stack_ptr == old_pointer && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
assert(size > old_size);
|
||||
usz add_size = size - old_size;
|
||||
if (add_size + current_page.used > current_page.total) break REUSE;
|
||||
*old_size_ptr = size;
|
||||
current_page.used += add_size;
|
||||
return old_pointer;
|
||||
}
|
||||
void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return new_mem;
|
||||
}
|
||||
|
||||
fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
|
||||
{
|
||||
assert(mark == 0, "Unexpectedly reset dynamic arena allocator with mark %d", mark);
|
||||
DynamicArenaPage* page = self.page;
|
||||
DynamicArenaPage** unused_page_ptr = &self.unused_page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
page.used = 0;
|
||||
DynamicArenaPage* prev_unused = *unused_page_ptr;
|
||||
*unused_page_ptr = page;
|
||||
page.prev_arena = prev_unused;
|
||||
page = next_page;
|
||||
}
|
||||
self.page = page;
|
||||
}
|
||||
|
||||
<*
|
||||
@require math::is_power_of_2(alignment)
|
||||
@require size > 0
|
||||
*>
|
||||
fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @local
|
||||
{
|
||||
// First, make sure that we can align it, extending the page size if needed.
|
||||
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + alignment, alignment));
|
||||
assert(page_size > size + DynamicArenaChunk.sizeof);
|
||||
// Grab the page without alignment (we do it ourselves)
|
||||
void* mem = allocator::malloc_try(self.backing_allocator, page_size)!;
|
||||
DynamicArenaPage*! page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
|
||||
if (catch err = page)
|
||||
{
|
||||
allocator::free(self.backing_allocator, mem);
|
||||
return err?;
|
||||
}
|
||||
page.memory = mem;
|
||||
void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment);
|
||||
assert(mem_start + size < mem + page_size);
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
|
||||
chunk.size = size;
|
||||
page.prev_arena = self.page;
|
||||
page.total = page_size;
|
||||
page.used = mem_start + size - page.memory;
|
||||
self.page = page;
|
||||
page.current_stack_ptr = mem_start;
|
||||
return mem_start;
|
||||
}
|
||||
|
||||
<*
|
||||
@require size > 0 `acquire expects size > 0`
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
*>
|
||||
fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
DynamicArenaPage* page = self.page;
|
||||
|
||||
void* ptr @noinit;
|
||||
do SET_DONE:
|
||||
{
|
||||
if (!page && self.unused_page)
|
||||
{
|
||||
self.page = page = self.unused_page;
|
||||
self.unused_page = page.prev_arena;
|
||||
page.prev_arena = null;
|
||||
}
|
||||
if (!page)
|
||||
{
|
||||
ptr = self._alloc_new(size, alignment)!;
|
||||
break SET_DONE;
|
||||
}
|
||||
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
|
||||
usz new_used = start - page.memory + size;
|
||||
if ALLOCATE_NEW: (new_used > page.total)
|
||||
{
|
||||
if ((page = self.unused_page))
|
||||
{
|
||||
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
|
||||
new_used = start + size - page.memory;
|
||||
if (page.total >= new_used)
|
||||
{
|
||||
self.unused_page = page.prev_arena;
|
||||
page.prev_arena = self.page;
|
||||
self.page = page;
|
||||
break ALLOCATE_NEW;
|
||||
}
|
||||
}
|
||||
ptr = self._alloc_new(size, alignment)!;
|
||||
break SET_DONE;
|
||||
}
|
||||
page.used = new_used;
|
||||
assert(start + size == page.memory + page.used);
|
||||
ptr = start;
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)ptr - 1;
|
||||
chunk.size = size;
|
||||
};
|
||||
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return ptr;
|
||||
}
|
||||
209
lib7/std/core/allocators/heap_allocator.c3
Normal file
209
lib7/std/core/allocators/heap_allocator.c3
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
struct SimpleHeapAllocator (Allocator)
|
||||
{
|
||||
MemoryAllocFn alloc_fn;
|
||||
Header* free_list;
|
||||
}
|
||||
|
||||
<*
|
||||
@require allocator != null "An underlying memory provider must be given"
|
||||
@require !self.free_list "The allocator may not be already initialized"
|
||||
*>
|
||||
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
|
||||
{
|
||||
self.alloc_fn = allocator;
|
||||
self.free_list = null;
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
return alignment > 0 ? @aligned_alloc(self._calloc, size, alignment) : self._calloc(size);
|
||||
}
|
||||
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size);
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
return alignment > 0
|
||||
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment)
|
||||
: self._realloc(old_pointer, size);
|
||||
}
|
||||
|
||||
fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
|
||||
{
|
||||
if (aligned)
|
||||
{
|
||||
@aligned_free(self._free, old_pointer)!!;
|
||||
}
|
||||
else
|
||||
{
|
||||
self._free(old_pointer);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require old_pointer && bytes > 0
|
||||
*>
|
||||
fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
|
||||
{
|
||||
// Find the block header.
|
||||
Header* block = (Header*)old_pointer - 1;
|
||||
if (block.size >= bytes) return old_pointer;
|
||||
void* new = self._alloc(bytes)!;
|
||||
usz max_to_copy = math::min(block.size, bytes);
|
||||
mem::copy(new, old_pointer, max_to_copy);
|
||||
self._free(old_pointer);
|
||||
return new;
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator._calloc(&self, usz bytes) @local
|
||||
{
|
||||
void* data = self._alloc(bytes)!;
|
||||
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
|
||||
{
|
||||
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
if (!self.free_list)
|
||||
{
|
||||
self.add_block(aligned_bytes)!;
|
||||
}
|
||||
|
||||
Header* current = self.free_list;
|
||||
Header* previous = current;
|
||||
while (current)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case current.size >= aligned_bytes && current.size <= aligned_bytes + Header.sizeof + 64:
|
||||
if (current == previous)
|
||||
{
|
||||
self.free_list = current.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
previous.next = current.next;
|
||||
}
|
||||
current.next = null;
|
||||
return current + 1;
|
||||
case current.size > aligned_bytes:
|
||||
Header* unallocated = (Header*)((char*)current + aligned_bytes + Header.sizeof);
|
||||
unallocated.size = current.size - aligned_bytes - Header.sizeof;
|
||||
unallocated.next = current.next;
|
||||
if (current == self.free_list)
|
||||
{
|
||||
self.free_list = unallocated;
|
||||
}
|
||||
else
|
||||
{
|
||||
previous.next = unallocated;
|
||||
}
|
||||
current.size = aligned_bytes;
|
||||
current.next = null;
|
||||
return current + 1;
|
||||
default:
|
||||
previous = current;
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
self.add_block(aligned_bytes)!;
|
||||
return self._alloc(aligned_bytes);
|
||||
}
|
||||
|
||||
fn void! SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
|
||||
{
|
||||
assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes);
|
||||
char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!;
|
||||
Header* new_block = (Header*)result.ptr;
|
||||
new_block.size = result.len - Header.sizeof;
|
||||
new_block.next = null;
|
||||
self._free(new_block + 1);
|
||||
}
|
||||
|
||||
|
||||
fn void SimpleHeapAllocator._free(&self, void* ptr) @local
|
||||
{
|
||||
// Empty ptr -> do nothing.
|
||||
if (!ptr) return;
|
||||
|
||||
// Find the block header.
|
||||
Header* block = (Header*)ptr - 1;
|
||||
|
||||
// No free list? Then just return self.
|
||||
if (!self.free_list)
|
||||
{
|
||||
self.free_list = block;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find where in the list it should be inserted.
|
||||
Header* current = self.free_list;
|
||||
Header* prev = current;
|
||||
while (current)
|
||||
{
|
||||
if (block < current)
|
||||
{
|
||||
// Between prev and current
|
||||
if (block > prev) break;
|
||||
// Before current
|
||||
if (current == prev) break;
|
||||
}
|
||||
prev = current;
|
||||
current = prev.next;
|
||||
}
|
||||
if (current)
|
||||
{
|
||||
// Insert after the current block.
|
||||
// Are the blocks adjacent?
|
||||
if (current == (Header*)((char*)(block + 1) + block.size))
|
||||
{
|
||||
// Merge
|
||||
block.size += current.size + Header.sizeof;
|
||||
block.next = current.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chain to current
|
||||
block.next = current;
|
||||
}
|
||||
}
|
||||
if (prev == current)
|
||||
{
|
||||
// Swap new start of free list
|
||||
self.free_list = block;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prev adjacent?
|
||||
if (block == (Header*)((char*)(prev + 1) + prev.size))
|
||||
{
|
||||
prev.size += block.size + Header.sizeof;
|
||||
prev.next = block.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Link prev to block
|
||||
prev.next = block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
union Header @local
|
||||
{
|
||||
struct
|
||||
{
|
||||
Header* next;
|
||||
usz size;
|
||||
}
|
||||
usz align;
|
||||
}
|
||||
161
lib7/std/core/allocators/libc_allocator.c3
Normal file
161
lib7/std/core/allocators/libc_allocator.c3
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator @if(env::LIBC);
|
||||
import std::io;
|
||||
import libc;
|
||||
|
||||
const LibcAllocator LIBC_ALLOCATOR = {};
|
||||
distinct LibcAllocator (Allocator, Printable) = uptr;
|
||||
|
||||
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
|
||||
fn usz! LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
|
||||
|
||||
module std::core::mem::allocator @if(env::POSIX);
|
||||
import std::os;
|
||||
import libc;
|
||||
|
||||
|
||||
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
void* data @noinit;
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
}
|
||||
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
else
|
||||
{
|
||||
void* data @noinit;
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(data = libc::malloc(bytes))) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
void* new_ptr;
|
||||
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
|
||||
$switch
|
||||
$case env::DARWIN:
|
||||
usz old_usable_size = darwin::malloc_size(old_ptr);
|
||||
$case env::LINUX:
|
||||
usz old_usable_size = linux::malloc_usable_size(old_ptr);
|
||||
$default:
|
||||
usz old_usable_size = new_bytes;
|
||||
$endswitch
|
||||
|
||||
usz copy_size = new_bytes < old_usable_size ? new_bytes : old_usable_size;
|
||||
mem::copy(new_ptr, old_ptr, copy_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
libc::free(old_ptr);
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
{
|
||||
libc::free(old_ptr);
|
||||
}
|
||||
|
||||
module std::core::mem::allocator @if(env::WIN32);
|
||||
import std::os::win32;
|
||||
import libc;
|
||||
|
||||
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
if (alignment > 0)
|
||||
{
|
||||
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
|
||||
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
if (alignment)
|
||||
{
|
||||
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
{
|
||||
if (aligned)
|
||||
{
|
||||
win32::_aligned_free(old_ptr);
|
||||
return;
|
||||
}
|
||||
libc::free(old_ptr);
|
||||
}
|
||||
|
||||
module std::core::mem::allocator @if(!env::WIN32 && !env::POSIX && env::LIBC);
|
||||
import libc;
|
||||
|
||||
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1);
|
||||
return data ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
else
|
||||
{
|
||||
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
|
||||
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
if (alignment)
|
||||
{
|
||||
void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!;
|
||||
return data ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
|
||||
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
{
|
||||
if (aligned)
|
||||
{
|
||||
@aligned_free(libc::free, old_ptr)!!;
|
||||
}
|
||||
else
|
||||
{
|
||||
libc::free(old_ptr);
|
||||
}
|
||||
}
|
||||
150
lib7/std/core/allocators/on_stack_allocator.c3
Normal file
150
lib7/std/core/allocators/on_stack_allocator.c3
Normal file
@@ -0,0 +1,150 @@
|
||||
module std::core::mem::allocator;
|
||||
|
||||
struct OnStackAllocator (Allocator)
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
char[] data;
|
||||
usz used;
|
||||
OnStackAllocatorExtraChunk* chunk;
|
||||
}
|
||||
|
||||
|
||||
struct OnStackAllocatorExtraChunk @local
|
||||
{
|
||||
bool is_aligned;
|
||||
OnStackAllocatorExtraChunk* prev;
|
||||
void* data;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a memory arena for use using the provided bytes.
|
||||
|
||||
@param [&inout] allocator
|
||||
*>
|
||||
fn void OnStackAllocator.init(&self, char[] data, Allocator allocator)
|
||||
{
|
||||
self.data = data;
|
||||
self.backing_allocator = allocator;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
fn void OnStackAllocator.free(&self)
|
||||
{
|
||||
OnStackAllocatorExtraChunk* chunk = self.chunk;
|
||||
while (chunk)
|
||||
{
|
||||
if (chunk.is_aligned)
|
||||
{
|
||||
allocator::free_aligned(self.backing_allocator, chunk.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator::free(self.backing_allocator, chunk.data);
|
||||
}
|
||||
void* old = chunk;
|
||||
chunk = chunk.prev;
|
||||
allocator::free(self.backing_allocator, old);
|
||||
}
|
||||
self.chunk = null;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
struct OnStackAllocatorHeader
|
||||
{
|
||||
usz size;
|
||||
char[?] data;
|
||||
}
|
||||
|
||||
<*
|
||||
@require old_pointer != null
|
||||
*>
|
||||
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
|
||||
{
|
||||
if (allocation_in_stack_mem(self, old_pointer)) return;
|
||||
on_stack_allocator_remove_chunk(self, old_pointer);
|
||||
self.backing_allocator.release(old_pointer, aligned);
|
||||
}
|
||||
|
||||
fn bool allocation_in_stack_mem(OnStackAllocator* a, void* ptr) @local
|
||||
{
|
||||
return ptr >= a.data.ptr && ptr <= &a.data[^1];
|
||||
}
|
||||
|
||||
fn void on_stack_allocator_remove_chunk(OnStackAllocator* a, void* ptr) @local
|
||||
{
|
||||
OnStackAllocatorExtraChunk* chunk = a.chunk;
|
||||
OnStackAllocatorExtraChunk** addr = &a.chunk;
|
||||
while (chunk)
|
||||
{
|
||||
if (chunk.data == ptr)
|
||||
{
|
||||
*addr = chunk.prev;
|
||||
allocator::free(a.backing_allocator, chunk);
|
||||
return;
|
||||
}
|
||||
addr = &chunk.prev;
|
||||
chunk = *addr;
|
||||
}
|
||||
unreachable("Missing chunk");
|
||||
}
|
||||
|
||||
fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a, void* ptr) @local
|
||||
{
|
||||
OnStackAllocatorExtraChunk* chunk = a.chunk;
|
||||
while (chunk)
|
||||
{
|
||||
if (chunk.data == ptr) return chunk;
|
||||
chunk = chunk.prev;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
<*
|
||||
@require size > 0
|
||||
@require old_pointer != null
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
*>
|
||||
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
if (!allocation_in_stack_mem(self, old_pointer))
|
||||
{
|
||||
OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(self, old_pointer);
|
||||
assert(chunk, "Tried to realloc pointer not belonging to the allocator");
|
||||
return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment)!;
|
||||
}
|
||||
|
||||
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
<*
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require size > 0
|
||||
*>
|
||||
fn void*! OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
bool aligned = alignment > 0;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = self.data.len;
|
||||
void* start_mem = self.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof ;
|
||||
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(mem - self.data.ptr) + size;
|
||||
Allocator backing_allocator = self.backing_allocator;
|
||||
|
||||
if (end > total_len)
|
||||
{
|
||||
OnStackAllocatorExtraChunk* chunk = allocator::alloc_try(backing_allocator, OnStackAllocatorExtraChunk)!;
|
||||
defer catch allocator::free(backing_allocator, chunk);
|
||||
defer try self.chunk = chunk;
|
||||
*chunk = { .prev = self.chunk, .is_aligned = aligned };
|
||||
return chunk.data = backing_allocator.acquire(size, init_type, aligned ? alignment : 0)!;
|
||||
}
|
||||
self.used = end;
|
||||
OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader.sizeof;
|
||||
header.size = size;
|
||||
return mem;
|
||||
}
|
||||
230
lib7/std/core/allocators/temp_allocator.c3
Normal file
230
lib7/std/core/allocators/temp_allocator.c3
Normal file
@@ -0,0 +1,230 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::io, std::math;
|
||||
|
||||
struct TempAllocatorChunk @local
|
||||
{
|
||||
usz size;
|
||||
char[?] data;
|
||||
}
|
||||
|
||||
struct TempAllocator (Allocator)
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
TempAllocatorPage* last_page;
|
||||
usz used;
|
||||
usz capacity;
|
||||
char[?] data;
|
||||
}
|
||||
|
||||
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
|
||||
|
||||
|
||||
struct TempAllocatorPage
|
||||
{
|
||||
TempAllocatorPage* prev_page;
|
||||
void* start;
|
||||
usz mark;
|
||||
usz size;
|
||||
usz ident;
|
||||
char[?] data;
|
||||
}
|
||||
|
||||
macro usz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
|
||||
macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
|
||||
|
||||
<*
|
||||
@require size >= 16
|
||||
*>
|
||||
fn TempAllocator*! new_temp_allocator(usz size, Allocator allocator)
|
||||
{
|
||||
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
|
||||
temp.last_page = null;
|
||||
temp.backing_allocator = allocator;
|
||||
temp.used = 0;
|
||||
temp.capacity = size;
|
||||
return temp;
|
||||
}
|
||||
|
||||
fn void TempAllocator.destroy(&self)
|
||||
{
|
||||
self.reset(0);
|
||||
if (self.last_page) (void)self._free_page(self.last_page);
|
||||
allocator::free(self.backing_allocator, self);
|
||||
}
|
||||
|
||||
fn usz TempAllocator.mark(&self) @dynamic => self.used;
|
||||
|
||||
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
|
||||
{
|
||||
usz old_size = *(usz*)(old_pointer - DEFAULT_SIZE_PREFIX);
|
||||
if (old_pointer + old_size == &self.data[self.used])
|
||||
{
|
||||
self.used -= old_size;
|
||||
asan::poison_memory_region(&self.data[self.used], old_size);
|
||||
}
|
||||
}
|
||||
fn void TempAllocator.reset(&self, usz mark) @dynamic
|
||||
{
|
||||
TempAllocatorPage *last_page = self.last_page;
|
||||
while (last_page && last_page.mark > mark)
|
||||
{
|
||||
self.used = last_page.mark;
|
||||
TempAllocatorPage *to_free = last_page;
|
||||
last_page = last_page.prev_page;
|
||||
self._free_page(to_free)!!;
|
||||
}
|
||||
self.last_page = last_page;
|
||||
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
|
||||
if (!last_page)
|
||||
{
|
||||
usz cleaned = self.used - mark;
|
||||
if (cleaned > 0)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
|
||||
self.data[mark : cleaned] = 0xAA;
|
||||
$endif
|
||||
asan::poison_memory_region(&self.data[mark], cleaned);
|
||||
}
|
||||
}
|
||||
$endif
|
||||
self.used = mark;
|
||||
}
|
||||
|
||||
fn void! TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
|
||||
{
|
||||
void* mem = page.start;
|
||||
return self.backing_allocator.release(mem, page.is_aligned());
|
||||
}
|
||||
|
||||
fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
|
||||
{
|
||||
// Then the actual start pointer:
|
||||
void* real_pointer = page.start;
|
||||
|
||||
// Walk backwards to find the pointer to this page.
|
||||
TempAllocatorPage **pointer_to_prev = &self.last_page;
|
||||
// Remove the page from the list
|
||||
while (*pointer_to_prev != page)
|
||||
{
|
||||
pointer_to_prev = &((*pointer_to_prev).prev_page);
|
||||
}
|
||||
*pointer_to_prev = page.prev_page;
|
||||
usz page_size = page.pagesize();
|
||||
// Clear on size > original size.
|
||||
void* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
self.backing_allocator.release(real_pointer, page.is_aligned());
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof;
|
||||
if (chunk.size == (usz)-1)
|
||||
{
|
||||
assert(self.last_page, "Realloc of non temp pointer");
|
||||
// First grab the page
|
||||
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
|
||||
return self._realloc_page(page, size, alignment);
|
||||
}
|
||||
|
||||
TempAllocatorChunk* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
<*
|
||||
@require size > 0
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
*>
|
||||
fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
void* start_mem = &self.data;
|
||||
void* starting_ptr = start_mem + self.used;
|
||||
void* aligned_header_start = mem::aligned_pointer(starting_ptr, TempAllocatorChunk.alignof);
|
||||
void* mem = aligned_header_start + TempAllocatorChunk.sizeof;
|
||||
if (alignment > TempAllocatorChunk.alignof)
|
||||
{
|
||||
mem = mem::aligned_pointer(mem, alignment);
|
||||
}
|
||||
usz new_usage = (usz)(mem - start_mem) + size;
|
||||
|
||||
// Arena allocation, simple!
|
||||
if (new_usage <= self.capacity)
|
||||
{
|
||||
asan::unpoison_memory_region(starting_ptr, new_usage - self.used);
|
||||
TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof;
|
||||
chunk_start.size = size;
|
||||
self.used = new_usage;
|
||||
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
// Fallback to backing allocator
|
||||
TempAllocatorPage* page;
|
||||
|
||||
// We have something we need to align.
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
// This is actually simpler, since it will create the offset for us.
|
||||
usz total_alloc_size = mem::aligned_offset(TempAllocatorPage.sizeof + size, alignment);
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
|
||||
}
|
||||
void* start = mem;
|
||||
mem += mem::aligned_offset(TempAllocatorPage.sizeof, alignment);
|
||||
page = (TempAllocatorPage*)mem - 1;
|
||||
page.start = start;
|
||||
page.size = size | PAGE_IS_ALIGNED;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here we might need to pad
|
||||
usz padded_header_size = mem::aligned_offset(TempAllocatorPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
usz total_alloc_size = padded_header_size + size;
|
||||
void* alloc = self.backing_allocator.acquire(total_alloc_size, init_type, 0)!;
|
||||
|
||||
// Find the page.
|
||||
page = alloc + padded_header_size - TempAllocatorPage.sizeof;
|
||||
assert(mem::ptr_is_aligned(page, TempAllocator.alignof));
|
||||
assert(mem::ptr_is_aligned(&page.data[0], mem::DEFAULT_MEM_ALIGNMENT));
|
||||
page.start = alloc;
|
||||
page.size = size;
|
||||
}
|
||||
|
||||
// Mark it as a page
|
||||
page.ident = ~(usz)0;
|
||||
// Store when it was created
|
||||
page.mark = ++self.used;
|
||||
// Hook up the page.
|
||||
page.prev_page = self.last_page;
|
||||
self.last_page = page;
|
||||
return &page.data[0];
|
||||
}
|
||||
|
||||
fn void! TempAllocator.print_pages(&self, File* f)
|
||||
{
|
||||
TempAllocatorPage *last_page = self.last_page;
|
||||
if (!last_page)
|
||||
{
|
||||
io::fprintf(f, "No pages.\n")!;
|
||||
return;
|
||||
}
|
||||
io::fprintf(f, "---Pages----\n")!;
|
||||
uint index = 0;
|
||||
while (last_page)
|
||||
{
|
||||
bool is_not_aligned = !(last_page.size & (1u64 << 63));
|
||||
io::fprintf(f, "%d. Alloc: %d %d at %p%s\n", ++index,
|
||||
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]")!;
|
||||
last_page = last_page.prev_page;
|
||||
}
|
||||
}
|
||||
215
lib7/std/core/allocators/tracking_allocator.c3
Normal file
215
lib7/std/core/allocators/tracking_allocator.c3
Normal file
@@ -0,0 +1,215 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator;
|
||||
import std::collections, std::io, std::os::backtrace;
|
||||
|
||||
const MAX_BACKTRACE = 16;
|
||||
struct Allocation
|
||||
{
|
||||
void* ptr;
|
||||
usz size;
|
||||
void*[MAX_BACKTRACE] backtrace;
|
||||
}
|
||||
|
||||
def AllocMap = HashMap { uptr, Allocation };
|
||||
|
||||
// A simple tracking allocator.
|
||||
// It tracks allocations using a hash map but
|
||||
// is not compatible with allocators that uses mark()
|
||||
struct TrackingAllocator (Allocator)
|
||||
{
|
||||
Allocator inner_allocator;
|
||||
AllocMap map;
|
||||
usz mem_total;
|
||||
usz allocs_total;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a tracking allocator to wrap (and track) another allocator.
|
||||
|
||||
@param [&inout] allocator "The allocator to track"
|
||||
*>
|
||||
fn void TrackingAllocator.init(&self, Allocator allocator)
|
||||
{
|
||||
*self = { .inner_allocator = allocator };
|
||||
self.map.init(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
Free this tracking allocator.
|
||||
*>
|
||||
fn void TrackingAllocator.free(&self)
|
||||
{
|
||||
self.map.free();
|
||||
*self = {};
|
||||
}
|
||||
|
||||
<*
|
||||
@return "the total allocated memory not yet freed."
|
||||
*>
|
||||
fn usz TrackingAllocator.allocated(&self) => @pool()
|
||||
{
|
||||
usz allocated = 0;
|
||||
foreach (&allocation : self.map.tvalues()) allocated += allocation.size;
|
||||
return allocated;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "the total memory allocated (freed or not)."
|
||||
*>
|
||||
fn usz TrackingAllocator.total_allocated(&self) => self.mem_total;
|
||||
|
||||
<*
|
||||
@return "the total number of allocations (freed or not)."
|
||||
*>
|
||||
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
|
||||
|
||||
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
|
||||
{
|
||||
return self.map.tvalues();
|
||||
}
|
||||
|
||||
<*
|
||||
@return "the number of non-freed allocations."
|
||||
*>
|
||||
fn usz TrackingAllocator.allocation_count(&self) => self.map.count;
|
||||
|
||||
fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
void* data = self.inner_allocator.acquire(size, init_type, alignment)!;
|
||||
self.allocs_total++;
|
||||
void*[MAX_BACKTRACE] bt;
|
||||
backtrace::capture_current(&bt);
|
||||
self.map.set((uptr)data, { data, size, bt });
|
||||
self.mem_total += size;
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
void* data = self.inner_allocator.resize(old_pointer, size, alignment)!;
|
||||
self.map.remove((uptr)old_pointer);
|
||||
void*[MAX_BACKTRACE] bt;
|
||||
backtrace::capture_current(&bt);
|
||||
self.map.set((uptr)data, { data, size, bt });
|
||||
self.mem_total += size;
|
||||
self.allocs_total++;
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
|
||||
{
|
||||
if (catch self.map.remove((uptr)old_pointer))
|
||||
{
|
||||
unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
|
||||
}
|
||||
self.inner_allocator.release(old_pointer, is_aligned);
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.clear(&self)
|
||||
{
|
||||
self.map.clear();
|
||||
}
|
||||
|
||||
fn bool TrackingAllocator.has_leaks(&self)
|
||||
{
|
||||
return self.map.len() > 0;
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.print_report(&self) => self.fprint_report(io::stdout())!!;
|
||||
|
||||
|
||||
fn void! TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
|
||||
{
|
||||
usz total = 0;
|
||||
usz entries = 0;
|
||||
bool leaks = false;
|
||||
|
||||
Allocation[] allocs = self.map.tvalues();
|
||||
if (allocs.len)
|
||||
{
|
||||
if (!allocs[0].backtrace[0])
|
||||
{
|
||||
io::fprintn(out, "======== Memory Report ========")!;
|
||||
io::fprintn(out, "Size in bytes Address")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
entries++;
|
||||
total += allocation.size;
|
||||
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
|
||||
}
|
||||
io::fprintn(out, "===============================")!;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
io::fprintn(out, "================================== Memory Report ==================================")!;
|
||||
io::fprintn(out, "Size in bytes Address Function ")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
entries++;
|
||||
total += allocation.size;
|
||||
BacktraceList backtraces = {};
|
||||
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
|
||||
if (allocation.backtrace[3])
|
||||
{
|
||||
trace = backtrace::symbolize_backtrace(tmem(), allocation.backtrace[3:1]).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
|
||||
}
|
||||
if (trace.function.len) leaks = true;
|
||||
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
|
||||
allocation.ptr, trace.function.len ? trace.function : "???",
|
||||
trace.line ? trace.line : 0)!;
|
||||
}
|
||||
io::fprintn(out, "===================================================================================")!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
|
||||
}
|
||||
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
|
||||
io::fprintfn(out, "- Total current allocations: %d", entries)!;
|
||||
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
|
||||
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
|
||||
if (leaks)
|
||||
{
|
||||
io::fprintn(out)!;
|
||||
io::fprintn(out, "Full leak report:")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
if (!allocation.backtrace[3])
|
||||
{
|
||||
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
|
||||
continue;
|
||||
}
|
||||
BacktraceList backtraces = {};
|
||||
usz end = MAX_BACKTRACE;
|
||||
foreach (j, val : allocation.backtrace)
|
||||
{
|
||||
if (!val)
|
||||
{
|
||||
end = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
BacktraceList list = backtrace::symbolize_backtrace(tmem(), allocation.backtrace[3..(end - 1)])!;
|
||||
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
|
||||
foreach (trace : list)
|
||||
{
|
||||
if (trace.has_file())
|
||||
{
|
||||
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
|
||||
continue;
|
||||
}
|
||||
if (trace.is_unknown())
|
||||
{
|
||||
io::fprintfn(out, " ??? (in unknown)");
|
||||
continue;
|
||||
}
|
||||
io::fprintfn(out, " %s (source unavailable)", trace.function);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
177
lib7/std/core/array.c3
Normal file
177
lib7/std/core/array.c3
Normal file
@@ -0,0 +1,177 @@
|
||||
module std::core::array;
|
||||
import std::core::array::slice;
|
||||
|
||||
<*
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@return "the first index of the element"
|
||||
@return! SearchResult.MISSING
|
||||
*>
|
||||
macro index_of(array, element)
|
||||
{
|
||||
foreach (i, &e : array)
|
||||
{
|
||||
if (*e == element) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @typekind(array_ptr) == POINTER
|
||||
@require @typekind(*array_ptr) == VECTOR || @typekind(*array_ptr) == ARRAY
|
||||
@require @typekind((*array_ptr)[0]) == VECTOR || @typekind((*array_ptr)[0]) == ARRAY
|
||||
*>
|
||||
macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
|
||||
{
|
||||
if (xlen < 1) xlen = $typeof((*array_ptr)[0]).len + xlen;
|
||||
if (ylen < 1) ylen = $typeof((*array_ptr)).len + ylen;
|
||||
var $ElementType = $typeof((*array_ptr)[0][0]);
|
||||
return Slice2d{$ElementType} { ($ElementType*)array_ptr, $typeof((*array_ptr)[0]).len, y, ylen, x, xlen };
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@return "the last index of the element"
|
||||
@return! SearchResult.MISSING
|
||||
*>
|
||||
macro rindex_of(array, element)
|
||||
{
|
||||
foreach_r (i, &e : array)
|
||||
{
|
||||
if (*e == element) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
|
||||
|
||||
@param [in] arr1
|
||||
@param [in] arr2
|
||||
@param [&inout] allocator "The allocator to use, default is the heap allocator"
|
||||
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
|
||||
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
|
||||
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
|
||||
@ensure result.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro concat(Allocator allocator, arr1, arr2) @nodiscard
|
||||
{
|
||||
var $Type = $typeof(arr1[0]);
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len);
|
||||
if (arr1.len > 0)
|
||||
{
|
||||
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $Type.alignof, $Type.alignof);
|
||||
}
|
||||
if (arr2.len > 0)
|
||||
{
|
||||
mem::copy(&result[arr1.len], &arr2[0], arr2.len * $Type.sizeof, $Type.alignof, $Type.alignof);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice containing the concatenation of them,
|
||||
allocated using the temp allocator.
|
||||
|
||||
@param [in] arr1
|
||||
@param [in] arr2
|
||||
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
|
||||
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
|
||||
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
|
||||
@ensure return.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro tconcat(arr1, arr2) @nodiscard => concat(allocator::temp(), arr1, arr2);
|
||||
|
||||
module std::core::array::slice{Type};
|
||||
|
||||
struct Slice2d
|
||||
{
|
||||
Type* ptr;
|
||||
usz inner_len;
|
||||
usz ystart;
|
||||
usz ylen;
|
||||
usz xstart;
|
||||
usz xlen;
|
||||
}
|
||||
|
||||
fn usz Slice2d.len(&self) @operator(len)
|
||||
{
|
||||
return self.ylen;
|
||||
}
|
||||
|
||||
fn usz Slice2d.count(&self)
|
||||
{
|
||||
return self.ylen * self.xlen;
|
||||
}
|
||||
|
||||
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
|
||||
{
|
||||
foreach (y, line : *self)
|
||||
{
|
||||
foreach (x, val : line)
|
||||
{
|
||||
@body({ x, y }, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
|
||||
{
|
||||
foreach (y, line : *self)
|
||||
{
|
||||
foreach (x, &val : line)
|
||||
{
|
||||
@body({ x, y }, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require idy >= 0 && idy < self.ylen
|
||||
*>
|
||||
macro Type[] Slice2d.get_row(self, usz idy) @operator([])
|
||||
{
|
||||
return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen];
|
||||
}
|
||||
|
||||
macro Type Slice2d.get_coord(self, usz[<2>] coord)
|
||||
{
|
||||
return *self.get_coord_ref(coord);
|
||||
}
|
||||
|
||||
macro Type Slice2d.get_xy(self, x, y)
|
||||
{
|
||||
return *self.get_xy_ref(x, y);
|
||||
}
|
||||
|
||||
macro Type* Slice2d.get_xy_ref(self, x, y)
|
||||
{
|
||||
return self.ptr + self.inner_len * (y + self.ystart) + self.xstart + x;
|
||||
}
|
||||
|
||||
macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord)
|
||||
{
|
||||
return self.get_xy_ref(coord.x, coord.y);
|
||||
}
|
||||
|
||||
macro void Slice2d.set_coord(self, usz[<2>] coord, Type value)
|
||||
{
|
||||
*self.get_coord_ref(coord) = value;
|
||||
}
|
||||
|
||||
macro void Slice2d.set_xy(self, x, y, Type value)
|
||||
{
|
||||
*self.get_xy_ref(x, y) = value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require y >= 0 && y < self.ylen
|
||||
@require x >= 0 && x < self.xlen
|
||||
*>
|
||||
fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0)
|
||||
{
|
||||
if (xlen < 1) xlen = self.xlen + xlen;
|
||||
if (ylen < 1) ylen = self.ylen + ylen;
|
||||
return { self.ptr, self.inner_len, y + self.ystart, ylen, x + self.xstart, xlen };
|
||||
}
|
||||
180
lib7/std/core/bitorder.c3
Normal file
180
lib7/std/core/bitorder.c3
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright (c) 2023 Christoffer Lerno and contributors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::bitorder;
|
||||
|
||||
// This module contains types of different endianness.
|
||||
// *BE types represent big-endian types
|
||||
// *LE types represent little-endian types.
|
||||
|
||||
bitstruct ShortBE : short @bigendian
|
||||
{
|
||||
short val : 0..15;
|
||||
}
|
||||
|
||||
bitstruct UShortBE : ushort @bigendian
|
||||
{
|
||||
ushort val : 0..15;
|
||||
}
|
||||
|
||||
bitstruct IntBE : int @bigendian
|
||||
{
|
||||
int val : 0..31;
|
||||
}
|
||||
|
||||
bitstruct UIntBE : int @bigendian
|
||||
{
|
||||
uint val : 0..31;
|
||||
}
|
||||
|
||||
bitstruct LongBE : long @bigendian
|
||||
{
|
||||
long val : 0..63;
|
||||
}
|
||||
|
||||
bitstruct ULongBE : ulong @bigendian
|
||||
{
|
||||
ulong val : 0..63;
|
||||
}
|
||||
|
||||
bitstruct Int128BE : int128 @bigendian
|
||||
{
|
||||
int128 val : 0..127;
|
||||
}
|
||||
|
||||
bitstruct UInt128BE : uint128 @bigendian
|
||||
{
|
||||
uint128 val : 0..127;
|
||||
}
|
||||
|
||||
bitstruct ShortLE : short @littleendian
|
||||
{
|
||||
short val : 0..15;
|
||||
}
|
||||
|
||||
bitstruct UShortLE : ushort @littleendian
|
||||
{
|
||||
ushort val : 0..15;
|
||||
}
|
||||
|
||||
bitstruct IntLE : int @littleendian
|
||||
{
|
||||
int val : 0..31;
|
||||
}
|
||||
|
||||
bitstruct UIntLE : int @littleendian
|
||||
{
|
||||
uint val : 0..31;
|
||||
}
|
||||
|
||||
bitstruct LongLE : long @littleendian
|
||||
{
|
||||
long val : 0..63;
|
||||
}
|
||||
|
||||
bitstruct ULongLE : ulong @littleendian
|
||||
{
|
||||
ulong val : 0..63;
|
||||
}
|
||||
|
||||
bitstruct Int128LE : int128 @littleendian
|
||||
{
|
||||
int128 val : 0..127;
|
||||
}
|
||||
|
||||
bitstruct UInt128LE : uint128 @littleendian
|
||||
{
|
||||
uint128 val : 0..127;
|
||||
}
|
||||
|
||||
<*
|
||||
@require is_array_or_slice_of_char(bytes) "argument must be an array, a pointer to an array or a slice of char"
|
||||
@require is_bitorder($Type) "type must be a bitorder integer"
|
||||
*>
|
||||
macro read(bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$endswitch
|
||||
return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
|
||||
}
|
||||
|
||||
<*
|
||||
@require is_arrayptr_or_slice_of_char(bytes) "argument must be a pointer to an array or a slice of char"
|
||||
@require is_bitorder($Type) "type must be a bitorder integer"
|
||||
*>
|
||||
macro write(x, bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$endswitch
|
||||
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
|
||||
}
|
||||
|
||||
macro is_bitorder($Type)
|
||||
{
|
||||
$switch ($Type)
|
||||
$case UShortLE:
|
||||
$case ShortLE:
|
||||
$case UIntLE:
|
||||
$case IntLE:
|
||||
$case ULongLE:
|
||||
$case LongLE:
|
||||
$case UInt128LE:
|
||||
$case Int128LE:
|
||||
$case UShortBE:
|
||||
$case ShortBE:
|
||||
$case UIntBE:
|
||||
$case IntBE:
|
||||
$case ULongBE:
|
||||
$case LongBE:
|
||||
$case UInt128BE:
|
||||
$case Int128BE:
|
||||
return true;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_array_or_slice_of_char(bytes)
|
||||
{
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
$if $Inner.kindof == ARRAY:
|
||||
var $Inner2 = $typefrom($Inner.inner);
|
||||
return $Inner2.typeid == char.typeid;
|
||||
$endif
|
||||
$case ARRAY:
|
||||
$case SLICE:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
return $Inner.typeid == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_arrayptr_or_slice_of_char(bytes)
|
||||
{
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
$if $Inner.kindof == ARRAY:
|
||||
var $Inner2 = $typefrom($Inner.inner);
|
||||
return $Inner2.typeid == char.typeid;
|
||||
$endif
|
||||
$case SLICE:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
return $Inner.typeid == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
779
lib7/std/core/builtin.c3
Normal file
779
lib7/std/core/builtin.c3
Normal file
@@ -0,0 +1,779 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno and contributors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::builtin;
|
||||
import libc, std::hash, std::io, std::os::backtrace;
|
||||
|
||||
<*
|
||||
EMPTY_MACRO_SLOT is a value used for implementing optional arguments for macros in an efficient
|
||||
way. It relies on the fact that distinct types are not implicitly convertable.
|
||||
|
||||
You can use `@is_empty_macro_slot()` and `@is_valid_macro_slot()` to figure out whether
|
||||
the argument has been used or not.
|
||||
|
||||
An example:
|
||||
|
||||
```c3
|
||||
macro foo(a, #b = EMPTY_MACRO_SLOT)
|
||||
{
|
||||
$if @is_valid_macro_slot(#b):
|
||||
return invoke_foo2(a, #b);
|
||||
$else
|
||||
return invoke_foo1(a);
|
||||
$endif
|
||||
}
|
||||
*>
|
||||
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
|
||||
|
||||
distinct EmptySlot = void*;
|
||||
macro @is_empty_macro_slot(#arg) @const @builtin => @typeis(#arg, EmptySlot);
|
||||
macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
|
||||
|
||||
/*
|
||||
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
|
||||
*/
|
||||
fault IteratorResult { NO_MORE_ELEMENT }
|
||||
|
||||
/*
|
||||
Use `SearchResult` when trying to return a value from some collection but the element is missing.
|
||||
*/
|
||||
fault SearchResult { MISSING }
|
||||
|
||||
/*
|
||||
Use `CastResult` when an attempt at conversion fails.
|
||||
*/
|
||||
fault CastResult { TYPE_MISMATCH }
|
||||
|
||||
|
||||
def VoidFn = fn void();
|
||||
|
||||
<*
|
||||
Stores a variable on the stack, then restores it at the end of the
|
||||
macro scope.
|
||||
|
||||
@param #variable `the variable to store and restore`
|
||||
@require values::@is_lvalue(#variable)
|
||||
*>
|
||||
macro void @scope(#variable; @body) @builtin
|
||||
{
|
||||
var temp = #variable;
|
||||
defer #variable = temp;
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
Swap two variables
|
||||
@require $defined(#a = #b, #b = #a) `The values must be mutually assignable`
|
||||
*>
|
||||
macro void @swap(#a, #b) @builtin
|
||||
{
|
||||
var temp = #a;
|
||||
#a = #b;
|
||||
#b = temp;
|
||||
}
|
||||
|
||||
<*
|
||||
Convert an `any` type to a type, returning an failure if there is a type mismatch.
|
||||
|
||||
@param v `the any to convert to the given type.`
|
||||
@param $Type `the type to convert to`
|
||||
@return `The any.ptr converted to its type.`
|
||||
@ensure @typeis(return, $Type*)
|
||||
@return! CastResult.TYPE_MISMATCH
|
||||
*>
|
||||
macro anycast(any v, $Type) @builtin
|
||||
{
|
||||
if (v.type != $Type.typeid) return CastResult.TYPE_MISMATCH?;
|
||||
return ($Type*)v.ptr;
|
||||
}
|
||||
|
||||
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE) => @pool()
|
||||
{
|
||||
void*[256] buffer;
|
||||
void*[] backtraces = backtrace::capture_current(&buffer);
|
||||
backtraces_to_ignore++;
|
||||
BacktraceList! backtrace = backtrace::symbolize_backtrace(tmem(), backtraces);
|
||||
if (catch backtrace) return false;
|
||||
if (backtrace.len() <= backtraces_to_ignore) return false;
|
||||
io::eprint("\nERROR: '");
|
||||
io::eprint(message);
|
||||
io::eprintn("'");
|
||||
foreach (i, &trace : backtrace)
|
||||
{
|
||||
if (i < backtraces_to_ignore) continue;
|
||||
String inline_suffix = trace.is_inline ? " [inline]" : "";
|
||||
if (trace.is_unknown())
|
||||
{
|
||||
io::eprintfn(" in ???%s", inline_suffix);
|
||||
continue;
|
||||
}
|
||||
if (trace.has_file())
|
||||
{
|
||||
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
|
||||
continue;
|
||||
}
|
||||
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
|
||||
{
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace(message, 2))
|
||||
{
|
||||
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
|
||||
}
|
||||
$endif
|
||||
$$trap();
|
||||
|
||||
}
|
||||
|
||||
macro void abort(String string = "Unrecoverable error reached", ...) @builtin @noreturn
|
||||
{
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
|
||||
$$trap();
|
||||
}
|
||||
|
||||
bool in_panic @local = false;
|
||||
|
||||
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
|
||||
{
|
||||
if (in_panic)
|
||||
{
|
||||
io::eprintn("Panic inside of panic.");
|
||||
return;
|
||||
}
|
||||
in_panic = true;
|
||||
$if $defined(io::stderr):
|
||||
io::eprint("\nERROR: '");
|
||||
io::eprint(message);
|
||||
io::eprintfn("', in %s (%s:%d)", function, file, line);
|
||||
$endif
|
||||
in_panic = false;
|
||||
$$trap();
|
||||
}
|
||||
|
||||
def PanicFn = fn void(String message, String file, String function, uint line);
|
||||
|
||||
PanicFn panic = &default_panic;
|
||||
|
||||
fn void panicf(String fmt, String file, String function, uint line, args...)
|
||||
{
|
||||
if (in_panic)
|
||||
{
|
||||
io::eprint("Panic inside of panic: ");
|
||||
io::eprintn(fmt);
|
||||
return;
|
||||
}
|
||||
in_panic = true;
|
||||
@stack_mem(512; Allocator allocator)
|
||||
{
|
||||
DString s;
|
||||
s.init(allocator);
|
||||
s.appendf(fmt, ...args);
|
||||
in_panic = false;
|
||||
panic(s.str_view(), file, function, line);
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Marks the path as unreachable. This will panic in safe mode, and in fast will simply be assumed
|
||||
never happens.
|
||||
@param [in] string "The panic message or format string"
|
||||
*>
|
||||
macro void unreachable(String string = "Unreachable statement reached.", ...) @builtin @noreturn
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE:
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
|
||||
$endif;
|
||||
$$unreachable();
|
||||
}
|
||||
|
||||
<*
|
||||
Marks the path as unsupported, this is similar to unreachable.
|
||||
@param [in] string "The error message"
|
||||
*>
|
||||
macro void unsupported(String string = "Unsupported function invoked") @builtin @noreturn
|
||||
{
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
|
||||
$$unreachable();
|
||||
}
|
||||
|
||||
<*
|
||||
Unconditionally break into an attached debugger when reached.
|
||||
*>
|
||||
macro void breakpoint() @builtin
|
||||
{
|
||||
$$breakpoint();
|
||||
}
|
||||
|
||||
macro any_make(void* ptr, typeid type) @builtin
|
||||
{
|
||||
return $$any_make(ptr, type);
|
||||
}
|
||||
|
||||
macro any.retype_to(&self, typeid type)
|
||||
{
|
||||
return $$any_make(self.ptr, type);
|
||||
}
|
||||
|
||||
macro any.as_inner(&self)
|
||||
{
|
||||
return $$any_make(self.ptr, self.type.inner);
|
||||
}
|
||||
|
||||
<*
|
||||
@param expr "the expression to cast"
|
||||
@param $Type "the type to cast to"
|
||||
|
||||
@require $sizeof(expr) == $Type.sizeof "Cannot bitcast between types of different size."
|
||||
@ensure @typeis(return, $Type)
|
||||
*>
|
||||
macro bitcast(expr, $Type) @builtin
|
||||
{
|
||||
$if $Type.alignof <= $alignof(expr):
|
||||
return *($Type*)&expr;
|
||||
$else
|
||||
$Type x @noinit;
|
||||
$$memcpy(&x, &expr, $sizeof(expr), false, $Type.alignof, $alignof(expr));
|
||||
return x;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param $Type `The type of the enum`
|
||||
@param [in] enum_name `The name of the enum to search for`
|
||||
@require $Type.kindof == ENUM `Only enums may be used`
|
||||
@ensure @typeis(return, $Type)
|
||||
@return! SearchResult.MISSING
|
||||
*>
|
||||
macro enum_by_name($Type, String enum_name) @builtin
|
||||
{
|
||||
typeid x = $Type.typeid;
|
||||
foreach (i, name : x.names)
|
||||
{
|
||||
if (name == enum_name) return $Type.from_ordinal(i);
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
@param $Type `The type of the enum`
|
||||
@require $Type.kindof == ENUM `Only enums may be used`
|
||||
@require $defined($Type.#value) `Expected '#value' to match an enum associated value`
|
||||
@require $assignable(value, $typeof(($Type){}.#value)) `Expected the value to match the type of the associated value`
|
||||
@ensure @typeis(return, $Type)
|
||||
@return! SearchResult.MISSING
|
||||
*>
|
||||
macro @enum_from_value($Type, #value, value) @builtin
|
||||
{
|
||||
usz elements = $Type.elements;
|
||||
foreach (e : $Type.values)
|
||||
{
|
||||
if (e.#value == value) return e;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
Mark an expression as likely to be true
|
||||
|
||||
@param #value "expression to be marked likely"
|
||||
@param $probability "in the range 0 - 1"
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro bool @likely(bool #value, $probability = 1.0) @builtin
|
||||
{
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value;
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, true);
|
||||
$default:
|
||||
return $$expect_with_probability(#value, true, $probability);
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Mark an expression as unlikely to be true
|
||||
|
||||
@param #value "expression to be marked unlikely"
|
||||
@param $probability "in the range 0 - 1"
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro bool @unlikely(bool #value, $probability = 1.0) @builtin
|
||||
{
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value;
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, false);
|
||||
$default:
|
||||
return $$expect_with_probability(#value, false, $probability);
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require values::@is_int(#value) || values::@is_bool(#value)
|
||||
@require $assignable(expected, $typeof(#value))
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro @expect(#value, expected, $probability = 1.0) @builtin
|
||||
{
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value == expected;
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, ($typeof(#value))expected);
|
||||
$default:
|
||||
return $$expect_with_probability(#value, expected, $probability);
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Locality for prefetch, levels 0 - 3, corresponding
|
||||
to "extremely local" to "no locality"
|
||||
*>
|
||||
enum PrefetchLocality
|
||||
{
|
||||
NO_LOCALITY,
|
||||
FAR,
|
||||
NEAR,
|
||||
VERY_NEAR,
|
||||
}
|
||||
|
||||
<*
|
||||
Prefetch a pointer.
|
||||
|
||||
@param [in] ptr `Pointer to prefetch`
|
||||
@param $locality `Locality ranging from none to extremely local`
|
||||
@param $write `Prefetch for write, otherwise prefetch for read.`
|
||||
*>
|
||||
macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
|
||||
{
|
||||
$if !env::BUILTIN_PREFETCH_IS_DISABLED:
|
||||
$$prefetch(ptr, $write ? 1 : 0, $locality.ordinal);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro swizzle(v, ...) @builtin
|
||||
{
|
||||
return $$swizzle(v, $vasplat);
|
||||
}
|
||||
|
||||
macro swizzle2(v, v2, ...) @builtin
|
||||
{
|
||||
return $$swizzle2(v, v2, $vasplat);
|
||||
}
|
||||
<*
|
||||
Return the excuse in the Optional if it is Empty, otherwise
|
||||
return a null fault.
|
||||
|
||||
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
|
||||
*>
|
||||
macro anyfault @catch(#expr) @builtin
|
||||
{
|
||||
if (catch f = #expr) return f;
|
||||
return {};
|
||||
}
|
||||
|
||||
<*
|
||||
Check if an Optional expression holds a value or is empty, returning true
|
||||
if it has a value.
|
||||
|
||||
@require @typekind(#expr) == OPTIONAL : `@ok expects an Optional value`
|
||||
*>
|
||||
macro bool @ok(#expr) @builtin
|
||||
{
|
||||
if (catch #expr) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(&#value, (char*)&#value) "This must be a value that can be viewed as a char array"
|
||||
*>
|
||||
macro char[] @as_char_view(#value) @builtin
|
||||
{
|
||||
return ((char*)&#value)[:$sizeof(#value)];
|
||||
}
|
||||
|
||||
macro isz @str_find(String $string, String $needle) @builtin => $$str_find($string, $needle);
|
||||
macro String @str_upper(String $str) @builtin => $$str_upper($str);
|
||||
macro String @str_lower(String $str) @builtin => $$str_lower($str);
|
||||
macro uint @str_hash(String $str) @builtin => $$str_hash($str);
|
||||
|
||||
macro @generic_hash_core(h, value)
|
||||
{
|
||||
h ^= (uint)value; // insert lowest 32 bits
|
||||
h *= 0x96f59e5b; // diffuse them up
|
||||
h ^= h >> 16; // diffuse them down
|
||||
return h;
|
||||
}
|
||||
|
||||
macro @generic_hash(value)
|
||||
{
|
||||
uint h = @generic_hash_core((uint)0x3efd4391, value);
|
||||
$for (var $cnt = 4; $cnt < $sizeof(value); $cnt += 4)
|
||||
value >>= 32; // reduce value
|
||||
h = @generic_hash_core(h, value);
|
||||
$endfor
|
||||
return h;
|
||||
}
|
||||
|
||||
macro uint int.hash(int i) => @generic_hash(i);
|
||||
macro uint uint.hash(uint i) => @generic_hash(i);
|
||||
macro uint short.hash(short s) => @generic_hash(s);
|
||||
macro uint ushort.hash(ushort s) => @generic_hash(s);
|
||||
macro uint char.hash(char c) => @generic_hash(c);
|
||||
macro uint ichar.hash(ichar c) => @generic_hash(c);
|
||||
macro uint long.hash(long i) => @generic_hash(i);
|
||||
macro uint ulong.hash(ulong i) => @generic_hash(i);
|
||||
macro uint int128.hash(int128 i) => @generic_hash(i);
|
||||
macro uint uint128.hash(uint128 i) => @generic_hash(i);
|
||||
macro uint bool.hash(bool b) => @generic_hash(b);
|
||||
macro uint typeid.hash(typeid t) => @generic_hash(((ulong)(uptr)t));
|
||||
macro uint String.hash(String c) => (uint)fnv32a::encode(c);
|
||||
macro uint char[].hash(char[] c) => (uint)fnv32a::encode(c);
|
||||
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
|
||||
|
||||
|
||||
const MAX_FRAMEADDRESS = 128;
|
||||
<*
|
||||
@require n >= 0
|
||||
*>
|
||||
macro void* get_frameaddress(int n)
|
||||
{
|
||||
if (n > MAX_FRAMEADDRESS) return null;
|
||||
switch (n)
|
||||
{
|
||||
case 0: return $$frameaddress(0);
|
||||
case 1: return $$frameaddress(1);
|
||||
case 2: return $$frameaddress(2);
|
||||
case 3: return $$frameaddress(3);
|
||||
case 4: return $$frameaddress(4);
|
||||
case 5: return $$frameaddress(5);
|
||||
case 6: return $$frameaddress(6);
|
||||
case 7: return $$frameaddress(7);
|
||||
case 8: return $$frameaddress(8);
|
||||
case 9: return $$frameaddress(9);
|
||||
case 10: return $$frameaddress(10);
|
||||
case 11: return $$frameaddress(11);
|
||||
case 12: return $$frameaddress(12);
|
||||
case 13: return $$frameaddress(13);
|
||||
case 14: return $$frameaddress(14);
|
||||
case 15: return $$frameaddress(15);
|
||||
case 16: return $$frameaddress(16);
|
||||
case 17: return $$frameaddress(17);
|
||||
case 18: return $$frameaddress(18);
|
||||
case 19: return $$frameaddress(19);
|
||||
case 20: return $$frameaddress(20);
|
||||
case 21: return $$frameaddress(21);
|
||||
case 22: return $$frameaddress(22);
|
||||
case 23: return $$frameaddress(23);
|
||||
case 24: return $$frameaddress(24);
|
||||
case 25: return $$frameaddress(25);
|
||||
case 26: return $$frameaddress(26);
|
||||
case 27: return $$frameaddress(27);
|
||||
case 28: return $$frameaddress(28);
|
||||
case 29: return $$frameaddress(29);
|
||||
case 30: return $$frameaddress(30);
|
||||
case 31: return $$frameaddress(31);
|
||||
case 32: return $$frameaddress(32);
|
||||
case 33: return $$frameaddress(33);
|
||||
case 34: return $$frameaddress(34);
|
||||
case 35: return $$frameaddress(35);
|
||||
case 36: return $$frameaddress(36);
|
||||
case 37: return $$frameaddress(37);
|
||||
case 38: return $$frameaddress(38);
|
||||
case 39: return $$frameaddress(39);
|
||||
case 40: return $$frameaddress(40);
|
||||
case 41: return $$frameaddress(41);
|
||||
case 42: return $$frameaddress(42);
|
||||
case 43: return $$frameaddress(43);
|
||||
case 44: return $$frameaddress(44);
|
||||
case 45: return $$frameaddress(45);
|
||||
case 46: return $$frameaddress(46);
|
||||
case 47: return $$frameaddress(47);
|
||||
case 48: return $$frameaddress(48);
|
||||
case 49: return $$frameaddress(49);
|
||||
case 50: return $$frameaddress(50);
|
||||
case 51: return $$frameaddress(51);
|
||||
case 52: return $$frameaddress(52);
|
||||
case 53: return $$frameaddress(53);
|
||||
case 54: return $$frameaddress(54);
|
||||
case 55: return $$frameaddress(55);
|
||||
case 56: return $$frameaddress(56);
|
||||
case 57: return $$frameaddress(57);
|
||||
case 58: return $$frameaddress(58);
|
||||
case 59: return $$frameaddress(59);
|
||||
case 60: return $$frameaddress(60);
|
||||
case 61: return $$frameaddress(61);
|
||||
case 62: return $$frameaddress(62);
|
||||
case 63: return $$frameaddress(63);
|
||||
case 64: return $$frameaddress(64);
|
||||
case 65: return $$frameaddress(65);
|
||||
case 66: return $$frameaddress(66);
|
||||
case 67: return $$frameaddress(67);
|
||||
case 68: return $$frameaddress(68);
|
||||
case 69: return $$frameaddress(69);
|
||||
case 70: return $$frameaddress(70);
|
||||
case 71: return $$frameaddress(71);
|
||||
case 72: return $$frameaddress(72);
|
||||
case 73: return $$frameaddress(73);
|
||||
case 74: return $$frameaddress(74);
|
||||
case 75: return $$frameaddress(75);
|
||||
case 76: return $$frameaddress(76);
|
||||
case 77: return $$frameaddress(77);
|
||||
case 78: return $$frameaddress(78);
|
||||
case 79: return $$frameaddress(79);
|
||||
case 80: return $$frameaddress(80);
|
||||
case 81: return $$frameaddress(81);
|
||||
case 82: return $$frameaddress(82);
|
||||
case 83: return $$frameaddress(83);
|
||||
case 84: return $$frameaddress(84);
|
||||
case 85: return $$frameaddress(85);
|
||||
case 86: return $$frameaddress(86);
|
||||
case 87: return $$frameaddress(87);
|
||||
case 88: return $$frameaddress(88);
|
||||
case 89: return $$frameaddress(89);
|
||||
case 90: return $$frameaddress(90);
|
||||
case 91: return $$frameaddress(91);
|
||||
case 92: return $$frameaddress(92);
|
||||
case 93: return $$frameaddress(93);
|
||||
case 94: return $$frameaddress(94);
|
||||
case 95: return $$frameaddress(95);
|
||||
case 96: return $$frameaddress(96);
|
||||
case 97: return $$frameaddress(97);
|
||||
case 98: return $$frameaddress(98);
|
||||
case 99: return $$frameaddress(99);
|
||||
case 100: return $$frameaddress(100);
|
||||
case 101: return $$frameaddress(101);
|
||||
case 102: return $$frameaddress(102);
|
||||
case 103: return $$frameaddress(103);
|
||||
case 104: return $$frameaddress(104);
|
||||
case 105: return $$frameaddress(105);
|
||||
case 106: return $$frameaddress(106);
|
||||
case 107: return $$frameaddress(107);
|
||||
case 108: return $$frameaddress(108);
|
||||
case 109: return $$frameaddress(109);
|
||||
case 110: return $$frameaddress(110);
|
||||
case 111: return $$frameaddress(111);
|
||||
case 112: return $$frameaddress(112);
|
||||
case 113: return $$frameaddress(113);
|
||||
case 114: return $$frameaddress(114);
|
||||
case 115: return $$frameaddress(115);
|
||||
case 116: return $$frameaddress(116);
|
||||
case 117: return $$frameaddress(117);
|
||||
case 118: return $$frameaddress(118);
|
||||
case 119: return $$frameaddress(119);
|
||||
case 120: return $$frameaddress(120);
|
||||
case 121: return $$frameaddress(121);
|
||||
case 122: return $$frameaddress(122);
|
||||
case 123: return $$frameaddress(123);
|
||||
case 124: return $$frameaddress(124);
|
||||
case 125: return $$frameaddress(125);
|
||||
case 126: return $$frameaddress(126);
|
||||
case 127: return $$frameaddress(127);
|
||||
case 128: return $$frameaddress(128);
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require n >= 0
|
||||
*>
|
||||
macro void* get_returnaddress(int n)
|
||||
{
|
||||
if (n > MAX_FRAMEADDRESS) return null;
|
||||
switch (n)
|
||||
{
|
||||
case 0: return $$returnaddress(0);
|
||||
case 1: return $$returnaddress(1);
|
||||
case 2: return $$returnaddress(2);
|
||||
case 3: return $$returnaddress(3);
|
||||
case 4: return $$returnaddress(4);
|
||||
case 5: return $$returnaddress(5);
|
||||
case 6: return $$returnaddress(6);
|
||||
case 7: return $$returnaddress(7);
|
||||
case 8: return $$returnaddress(8);
|
||||
case 9: return $$returnaddress(9);
|
||||
case 10: return $$returnaddress(10);
|
||||
case 11: return $$returnaddress(11);
|
||||
case 12: return $$returnaddress(12);
|
||||
case 13: return $$returnaddress(13);
|
||||
case 14: return $$returnaddress(14);
|
||||
case 15: return $$returnaddress(15);
|
||||
case 16: return $$returnaddress(16);
|
||||
case 17: return $$returnaddress(17);
|
||||
case 18: return $$returnaddress(18);
|
||||
case 19: return $$returnaddress(19);
|
||||
case 20: return $$returnaddress(20);
|
||||
case 21: return $$returnaddress(21);
|
||||
case 22: return $$returnaddress(22);
|
||||
case 23: return $$returnaddress(23);
|
||||
case 24: return $$returnaddress(24);
|
||||
case 25: return $$returnaddress(25);
|
||||
case 26: return $$returnaddress(26);
|
||||
case 27: return $$returnaddress(27);
|
||||
case 28: return $$returnaddress(28);
|
||||
case 29: return $$returnaddress(29);
|
||||
case 30: return $$returnaddress(30);
|
||||
case 31: return $$returnaddress(31);
|
||||
case 32: return $$returnaddress(32);
|
||||
case 33: return $$returnaddress(33);
|
||||
case 34: return $$returnaddress(34);
|
||||
case 35: return $$returnaddress(35);
|
||||
case 36: return $$returnaddress(36);
|
||||
case 37: return $$returnaddress(37);
|
||||
case 38: return $$returnaddress(38);
|
||||
case 39: return $$returnaddress(39);
|
||||
case 40: return $$returnaddress(40);
|
||||
case 41: return $$returnaddress(41);
|
||||
case 42: return $$returnaddress(42);
|
||||
case 43: return $$returnaddress(43);
|
||||
case 44: return $$returnaddress(44);
|
||||
case 45: return $$returnaddress(45);
|
||||
case 46: return $$returnaddress(46);
|
||||
case 47: return $$returnaddress(47);
|
||||
case 48: return $$returnaddress(48);
|
||||
case 49: return $$returnaddress(49);
|
||||
case 50: return $$returnaddress(50);
|
||||
case 51: return $$returnaddress(51);
|
||||
case 52: return $$returnaddress(52);
|
||||
case 53: return $$returnaddress(53);
|
||||
case 54: return $$returnaddress(54);
|
||||
case 55: return $$returnaddress(55);
|
||||
case 56: return $$returnaddress(56);
|
||||
case 57: return $$returnaddress(57);
|
||||
case 58: return $$returnaddress(58);
|
||||
case 59: return $$returnaddress(59);
|
||||
case 60: return $$returnaddress(60);
|
||||
case 61: return $$returnaddress(61);
|
||||
case 62: return $$returnaddress(62);
|
||||
case 63: return $$returnaddress(63);
|
||||
case 64: return $$returnaddress(64);
|
||||
case 65: return $$returnaddress(65);
|
||||
case 66: return $$returnaddress(66);
|
||||
case 67: return $$returnaddress(67);
|
||||
case 68: return $$returnaddress(68);
|
||||
case 69: return $$returnaddress(69);
|
||||
case 70: return $$returnaddress(70);
|
||||
case 71: return $$returnaddress(71);
|
||||
case 72: return $$returnaddress(72);
|
||||
case 73: return $$returnaddress(73);
|
||||
case 74: return $$returnaddress(74);
|
||||
case 75: return $$returnaddress(75);
|
||||
case 76: return $$returnaddress(76);
|
||||
case 77: return $$returnaddress(77);
|
||||
case 78: return $$returnaddress(78);
|
||||
case 79: return $$returnaddress(79);
|
||||
case 80: return $$returnaddress(80);
|
||||
case 81: return $$returnaddress(81);
|
||||
case 82: return $$returnaddress(82);
|
||||
case 83: return $$returnaddress(83);
|
||||
case 84: return $$returnaddress(84);
|
||||
case 85: return $$returnaddress(85);
|
||||
case 86: return $$returnaddress(86);
|
||||
case 87: return $$returnaddress(87);
|
||||
case 88: return $$returnaddress(88);
|
||||
case 89: return $$returnaddress(89);
|
||||
case 90: return $$returnaddress(90);
|
||||
case 91: return $$returnaddress(91);
|
||||
case 92: return $$returnaddress(92);
|
||||
case 93: return $$returnaddress(93);
|
||||
case 94: return $$returnaddress(94);
|
||||
case 95: return $$returnaddress(95);
|
||||
case 96: return $$returnaddress(96);
|
||||
case 97: return $$returnaddress(97);
|
||||
case 98: return $$returnaddress(98);
|
||||
case 99: return $$returnaddress(99);
|
||||
case 100: return $$returnaddress(100);
|
||||
case 101: return $$returnaddress(101);
|
||||
case 102: return $$returnaddress(102);
|
||||
case 103: return $$returnaddress(103);
|
||||
case 104: return $$returnaddress(104);
|
||||
case 105: return $$returnaddress(105);
|
||||
case 106: return $$returnaddress(106);
|
||||
case 107: return $$returnaddress(107);
|
||||
case 108: return $$returnaddress(108);
|
||||
case 109: return $$returnaddress(109);
|
||||
case 110: return $$returnaddress(110);
|
||||
case 111: return $$returnaddress(111);
|
||||
case 112: return $$returnaddress(112);
|
||||
case 113: return $$returnaddress(113);
|
||||
case 114: return $$returnaddress(114);
|
||||
case 115: return $$returnaddress(115);
|
||||
case 116: return $$returnaddress(116);
|
||||
case 117: return $$returnaddress(117);
|
||||
case 118: return $$returnaddress(118);
|
||||
case 119: return $$returnaddress(119);
|
||||
case 120: return $$returnaddress(120);
|
||||
case 121: return $$returnaddress(121);
|
||||
case 122: return $$returnaddress(122);
|
||||
case 123: return $$returnaddress(123);
|
||||
case 124: return $$returnaddress(124);
|
||||
case 125: return $$returnaddress(125);
|
||||
case 126: return $$returnaddress(126);
|
||||
case 127: return $$returnaddress(127);
|
||||
case 128: return $$returnaddress(128);
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
|
||||
import libc, std::io;
|
||||
|
||||
fn void sig_panic(String message)
|
||||
{
|
||||
default_panic(message, "???", "???", 0);
|
||||
}
|
||||
|
||||
SignalFunction old_bus_error;
|
||||
SignalFunction old_segmentation_fault;
|
||||
|
||||
fn void sig_bus_error(CInt i)
|
||||
{
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Illegal memory access.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Illegal memory access.", 1))
|
||||
{
|
||||
io::eprintn("\nERROR: 'Illegal memory access'.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
$$trap();
|
||||
}
|
||||
|
||||
fn void sig_segmentation_fault(CInt i)
|
||||
{
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Out of bounds memory access.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Out of bounds memory access.", 1))
|
||||
{
|
||||
io::eprintn("\nERROR: Memory error without backtrace, possible stack overflow.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
$$trap();
|
||||
}
|
||||
|
||||
fn void install_signal_handler(CInt signal, SignalFunction func) @local
|
||||
{
|
||||
SignalFunction old = libc::signal(signal, func);
|
||||
// Restore
|
||||
if ((iptr)old > 1024) libc::signal(signal, old);
|
||||
}
|
||||
|
||||
// Clean this up
|
||||
fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE)
|
||||
{
|
||||
install_signal_handler(libc::SIGBUS, &sig_bus_error);
|
||||
install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
|
||||
}
|
||||
|
||||
128
lib7/std/core/builtin_comparison.c3
Normal file
128
lib7/std/core/builtin_comparison.c3
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno and contributors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::builtin;
|
||||
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro less(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return a.less(b);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) < 0;
|
||||
$default:
|
||||
return a < b;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro less_eq(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return !b.less(a);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) <= 0;
|
||||
$default:
|
||||
return a <= b;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro greater(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return b.less(a);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) > 0;
|
||||
$default:
|
||||
return a > b;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro int compare_to(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b);
|
||||
$case $defined(a.less):
|
||||
return (int)b.less(a) - (int)a.less(b);
|
||||
$default:
|
||||
return (int)(a > b) - (int)(a < b);
|
||||
$endswitch
|
||||
}
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro greater_eq(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return !a.less(b);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) >= 0;
|
||||
$default:
|
||||
return a >= b;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::@equatable_value(a) && types::@equatable_value(b) `values must be equatable`
|
||||
*>
|
||||
macro bool equals(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$case $defined(a.equals, a.equals(b)):
|
||||
return a.equals(b);
|
||||
$case $defined(a.compare_to, a.compare_to(b)):
|
||||
return a.compare_to(b) == 0;
|
||||
$case $defined(a.less):
|
||||
return !a.less(b) && !b.less(a);
|
||||
$default:
|
||||
return a == b;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro min(x, ...) @builtin
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return less(x, $vaarg[0]) ? x : $vaarg[0];
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (less($vaarg[$i], result))
|
||||
{
|
||||
result = $vaarg[$i];
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro max(x, ...) @builtin
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return greater(x, $vaarg[0]) ? x : $vaarg[0];
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (greater($vaarg[$i], result))
|
||||
{
|
||||
result = $vaarg[$i];
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
$endif
|
||||
}
|
||||
|
||||
61
lib7/std/core/cinterop.c3
Normal file
61
lib7/std/core/cinterop.c3
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::cinterop;
|
||||
|
||||
const C_INT_SIZE = $$C_INT_SIZE;
|
||||
const C_LONG_SIZE = $$C_LONG_SIZE;
|
||||
const C_SHORT_SIZE = $$C_SHORT_SIZE;
|
||||
const C_LONG_LONG_SIZE = $$C_LONG_LONG_SIZE;
|
||||
|
||||
$assert C_SHORT_SIZE < 32;
|
||||
$assert C_INT_SIZE < 128;
|
||||
$assert C_LONG_SIZE < 128;
|
||||
$assert C_LONG_LONG_SIZE <= 128;
|
||||
$assert C_SHORT_SIZE <= C_INT_SIZE;
|
||||
$assert C_INT_SIZE <= C_LONG_SIZE;
|
||||
$assert C_LONG_SIZE <= C_LONG_LONG_SIZE;
|
||||
|
||||
def CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
|
||||
def CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
|
||||
def CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
|
||||
def CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
|
||||
def CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
|
||||
def CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
|
||||
def CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
def CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
def CSChar = ichar;
|
||||
def CUChar = char;
|
||||
|
||||
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
|
||||
|
||||
enum CBool : char
|
||||
{
|
||||
FALSE,
|
||||
TRUE
|
||||
}
|
||||
|
||||
// Helper macros
|
||||
macro typeid signed_int_from_bitsize(usz $bitsize) @private
|
||||
{
|
||||
$switch ($bitsize)
|
||||
$case 128: return int128.typeid;
|
||||
$case 64: return long.typeid;
|
||||
$case 32: return int.typeid;
|
||||
$case 16: return short.typeid;
|
||||
$case 8: return ichar.typeid;
|
||||
$default: $error("Invalid bitsize");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
|
||||
{
|
||||
$switch ($bitsize)
|
||||
$case 128: return uint128.typeid;
|
||||
$case 64: return ulong.typeid;
|
||||
$case 32: return uint.typeid;
|
||||
$case 16: return ushort.typeid;
|
||||
$case 8: return char.typeid;
|
||||
$default: $error("Invalid bitsize");
|
||||
$endswitch
|
||||
}
|
||||
416
lib7/std/core/conv.c3
Normal file
416
lib7/std/core/conv.c3
Normal file
@@ -0,0 +1,416 @@
|
||||
module std::core::string::conv;
|
||||
|
||||
const uint UTF16_SURROGATE_OFFSET @private = 0x10000;
|
||||
const uint UTF16_SURROGATE_GENERIC_MASK @private = 0xF800;
|
||||
const uint UTF16_SURROGATE_GENERIC_VALUE @private = 0xD800;
|
||||
const uint UTF16_SURROGATE_MASK @private = 0xFC00;
|
||||
const uint UTF16_SURROGATE_CODEPOINT_MASK @private = 0x03FF;
|
||||
const uint UTF16_SURROGATE_BITS @private = 10;
|
||||
const uint UTF16_SURROGATE_LOW_VALUE @private = 0xDC00;
|
||||
const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
|
||||
|
||||
<*
|
||||
@param c `The utf32 codepoint to convert`
|
||||
@param [out] output `the resulting buffer`
|
||||
*>
|
||||
fn usz! char32_to_utf8(Char32 c, char[] output)
|
||||
{
|
||||
if (!output.len) return UnicodeResult.CONVERSION_FAILED?;
|
||||
switch (true)
|
||||
{
|
||||
case c <= 0x7f:
|
||||
output[0] = (char)c;
|
||||
return 1;
|
||||
case c <= 0x7ff:
|
||||
if (output.len < 2) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xC0 | c >> 6);
|
||||
output[1] = (char)(0x80 | (c & 0x3F));
|
||||
return 2;
|
||||
case c <= 0xffff:
|
||||
if (output.len < 3) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xE0 | c >> 12);
|
||||
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c & 0x3F));
|
||||
return 3;
|
||||
case c <= 0x10ffff:
|
||||
if (output.len < 4) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xF0 | c >> 18);
|
||||
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[3] = (char)(0x80 | (c & 0x3F));
|
||||
return 4;
|
||||
default:
|
||||
// 0x10FFFF and above is not defined.
|
||||
return UnicodeResult.CONVERSION_FAILED?;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Convert a code pointer into 1-2 UTF16 characters.
|
||||
|
||||
@param c `The character to convert.`
|
||||
@param [inout] output `the resulting UTF16 buffer to write to.`
|
||||
*>
|
||||
fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
|
||||
{
|
||||
if (c < UTF16_SURROGATE_OFFSET)
|
||||
{
|
||||
(*output)++[0] = (Char16)c;
|
||||
return;
|
||||
}
|
||||
c -= UTF16_SURROGATE_OFFSET;
|
||||
Char16 low = (Char16)(UTF16_SURROGATE_LOW_VALUE | (c & UTF16_SURROGATE_CODEPOINT_MASK));
|
||||
c >>= UTF16_SURROGATE_BITS;
|
||||
Char16 high = (Char16)(UTF16_SURROGATE_HIGH_VALUE | (c & UTF16_SURROGATE_CODEPOINT_MASK));
|
||||
(*output)++[0] = (Char16)high;
|
||||
(*output)++[0] = (Char16)low;
|
||||
}
|
||||
|
||||
<*
|
||||
Convert 1-2 UTF16 data points into UTF8.
|
||||
|
||||
@param [in] ptr `The UTF16 data to convert.`
|
||||
@param [inout] available `amount of UTF16 data available.`
|
||||
@param [inout] output `the resulting utf8 buffer to write to.`
|
||||
*>
|
||||
fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
{
|
||||
Char16 high = *ptr;
|
||||
if (high & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
|
||||
{
|
||||
char32_to_utf8_unsafe(high, output);
|
||||
*available = 1;
|
||||
return;
|
||||
}
|
||||
// Low surrogate first is an error
|
||||
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return UnicodeResult.INVALID_UTF16?;
|
||||
|
||||
// Unmatched high surrogate is an error
|
||||
if (*available == 1) return UnicodeResult.INVALID_UTF16?;
|
||||
|
||||
Char16 low = ptr[1];
|
||||
|
||||
// Unmatched high surrogate, invalid
|
||||
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return UnicodeResult.INVALID_UTF16?;
|
||||
|
||||
// The high bits of the codepoint are the value bits of the high surrogate
|
||||
// The low bits of the codepoint are the value bits of the low surrogate
|
||||
Char32 uc = (high & UTF16_SURROGATE_CODEPOINT_MASK) << UTF16_SURROGATE_BITS
|
||||
| (low & UTF16_SURROGATE_CODEPOINT_MASK) + UTF16_SURROGATE_OFFSET;
|
||||
char32_to_utf8_unsafe(uc, output);
|
||||
*available = 2;
|
||||
}
|
||||
<*
|
||||
@param c `The utf32 codepoint to convert`
|
||||
@param [inout] output `the resulting buffer`
|
||||
*>
|
||||
fn usz char32_to_utf8_unsafe(Char32 c, char** output)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case c <= 0x7f:
|
||||
(*output)++[0] = (char)c;
|
||||
return 1;
|
||||
case c <= 0x7ff:
|
||||
(*output)++[0] = (char)(0xC0 | c >> 6);
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
return 2;
|
||||
case c <= 0xffff:
|
||||
(*output)++[0] = (char)(0xE0 | c >> 12);
|
||||
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
return 3;
|
||||
default:
|
||||
(*output)++[0] = (char)(0xF0 | c >> 18);
|
||||
(*output)++[0] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] ptr `pointer to the first character to parse`
|
||||
@param [inout] size `Set to max characters to read, set to characters read`
|
||||
@return `the parsed 32 bit codepoint`
|
||||
*>
|
||||
fn Char32! utf8_to_char32(char* ptr, usz* size)
|
||||
{
|
||||
usz max_size = *size;
|
||||
if (max_size < 1) return UnicodeResult.INVALID_UTF8?;
|
||||
char c = (ptr++)[0];
|
||||
|
||||
if ((c & 0x80) == 0)
|
||||
{
|
||||
*size = 1;
|
||||
return c;
|
||||
}
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
if (max_size < 2) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 2;
|
||||
Char32 uc = (c & 0x1F) << 6;
|
||||
c = *ptr;
|
||||
// Overlong sequence or invalid second.
|
||||
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
if (max_size < 3) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 3;
|
||||
Char32 uc = (c & 0x0F) << 12;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
uc += (c & 0x3F) << 6;
|
||||
c = ptr++[0];
|
||||
// Overlong sequence or invalid last
|
||||
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if (max_size < 4) return UnicodeResult.INVALID_UTF8?;
|
||||
if ((c & 0xF8) != 0xF0) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 4;
|
||||
Char32 uc = (c & 0x07) << 18;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
uc += (c & 0x3F) << 12;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
uc += (c & 0x3F) << 6;
|
||||
c = ptr++[0];
|
||||
// Overlong sequence or invalid last
|
||||
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
|
||||
<*
|
||||
@param utf8 `An UTF-8 encoded slice of bytes`
|
||||
@return `the number of encoded code points`
|
||||
*>
|
||||
fn usz utf8_codepoints(String utf8)
|
||||
{
|
||||
usz len = 0;
|
||||
foreach (char c : utf8)
|
||||
{
|
||||
if (c & 0xC0 != 0x80) len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the UTF8 length required to encode an UTF32 array.
|
||||
@param [in] utf32 `the utf32 data to calculate from`
|
||||
@return `the length of the resulting UTF8 array`
|
||||
*>
|
||||
fn usz utf8len_for_utf32(Char32[] utf32)
|
||||
{
|
||||
usz len = 0;
|
||||
foreach (Char32 uc : utf32)
|
||||
{
|
||||
switch (true)
|
||||
{
|
||||
case uc <= 0x7f:
|
||||
len++;
|
||||
case uc <= 0x7ff:
|
||||
len += 2;
|
||||
case uc <= 0xffff:
|
||||
len += 3;
|
||||
default:
|
||||
len += 4;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the UTF8 length required to encode an UTF16 array.
|
||||
@param [in] utf16 `the utf16 data to calculate from`
|
||||
@return `the length of the resulting UTF8 array`
|
||||
*>
|
||||
fn usz utf8len_for_utf16(Char16[] utf16)
|
||||
{
|
||||
usz len = 0;
|
||||
usz len16 = utf16.len;
|
||||
for (usz i = 0; i < len16; i++)
|
||||
{
|
||||
Char16 c = utf16[i];
|
||||
if (c & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
|
||||
{
|
||||
if (c <= 0x7f)
|
||||
{
|
||||
len++;
|
||||
continue;
|
||||
}
|
||||
if (c <= 0x7ff)
|
||||
{
|
||||
len += 2;
|
||||
continue;
|
||||
}
|
||||
len += 3;
|
||||
continue;
|
||||
}
|
||||
len += 4;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the UTF16 length required to encode a UTF8 array.
|
||||
@param utf8 `the utf8 data to calculate from`
|
||||
@return `the length of the resulting UTF16 array`
|
||||
*>
|
||||
fn usz utf16len_for_utf8(String utf8)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
usz len16 = 0;
|
||||
for (usz i = 0; i < len; i++)
|
||||
{
|
||||
len16++;
|
||||
char c = utf8[i];
|
||||
if (c & 0x80 == 0) continue;
|
||||
i++;
|
||||
if (c & 0xE0 == 0xC0) continue;
|
||||
i++;
|
||||
if (c & 0xF0 == 0xE0) continue;
|
||||
i++;
|
||||
len16++;
|
||||
}
|
||||
return len16;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] utf32 `the UTF32 array to check the length for`
|
||||
@return `the required length of an UTF16 array to hold the UTF32 data.`
|
||||
*>
|
||||
fn usz utf16len_for_utf32(Char32[] utf32)
|
||||
{
|
||||
usz len = utf32.len;
|
||||
foreach (Char32 uc : utf32)
|
||||
{
|
||||
if (uc >= UTF16_SURROGATE_OFFSET) len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
<*
|
||||
Convert an UTF32 array to an UTF8 array.
|
||||
|
||||
@param [in] utf32
|
||||
@param [out] utf8_buffer
|
||||
@return `the number of bytes written.`
|
||||
*>
|
||||
fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
|
||||
{
|
||||
char[] buffer = utf8_buffer;
|
||||
foreach (uc : utf32)
|
||||
{
|
||||
usz used = char32_to_utf8(uc, buffer) @inline!;
|
||||
buffer = buffer[used..];
|
||||
}
|
||||
// Zero terminate if there is space.
|
||||
if (buffer.len > 0) buffer[0] = 0;
|
||||
return utf8_buffer.len - buffer.len;
|
||||
}
|
||||
|
||||
<*
|
||||
Convert an UTF8 array to an UTF32 array.
|
||||
|
||||
@param [in] utf8
|
||||
@param [out] utf32_buffer
|
||||
@return `the number of Char32s written.`
|
||||
*>
|
||||
fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
Char32* ptr = utf32_buffer.ptr;
|
||||
usz len32 = 0;
|
||||
usz buf_len = utf32_buffer.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED?;
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
ptr[len32++] = uc;
|
||||
}
|
||||
// Zero terminate if possible
|
||||
if (len32 + 1 < buf_len) ptr[len32] = 0;
|
||||
return len32;
|
||||
}
|
||||
|
||||
<*
|
||||
Copy an array of UTF16 data into an UTF8 buffer without bounds
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf16 `The UTF16 array containing the data to convert.`
|
||||
@param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF16 data.`
|
||||
*>
|
||||
fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
|
||||
{
|
||||
usz len16 = utf16.len;
|
||||
for (usz i = 0; i < len16;)
|
||||
{
|
||||
usz available = len16 - i;
|
||||
char16_to_utf8_unsafe(&utf16[i], &available, &utf8_buffer) @inline!;
|
||||
i += available;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Copy an array of UTF8 data into an UTF32 buffer without bounds
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf8 `The UTF8 buffer containing the data to convert.`
|
||||
@param [out] utf32_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
*>
|
||||
fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
(utf32_buffer++)[0] = uc;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Copy an array of UTF8 data into an UTF16 buffer without bounds
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf8 `The UTF8 buffer containing the data to convert.`
|
||||
@param [out] utf16_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
*>
|
||||
fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
char32_to_utf16_unsafe(uc, &utf16_buffer) @inline;
|
||||
i += width;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Copy an array of UTF32 code points into an UTF8 buffer without bounds
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf32 `The UTF32 buffer containing the data to convert.`
|
||||
@param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
*>
|
||||
fn void utf32to8_unsafe(Char32[] utf32, char* utf8_buffer)
|
||||
{
|
||||
char* start = utf8_buffer;
|
||||
foreach (Char32 uc : utf32)
|
||||
{
|
||||
char32_to_utf8_unsafe(uc, &utf8_buffer) @inline;
|
||||
}
|
||||
}
|
||||
658
lib7/std/core/dstring.c3
Normal file
658
lib7/std/core/dstring.c3
Normal file
@@ -0,0 +1,658 @@
|
||||
module std::core::dstring;
|
||||
import std::io;
|
||||
|
||||
distinct DString (OutStream) = DStringOpaque*;
|
||||
distinct DStringOpaque = void;
|
||||
|
||||
const usz MIN_CAPACITY @private = 16;
|
||||
|
||||
<*
|
||||
@require !self.data() "String already initialized"
|
||||
*>
|
||||
fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY)
|
||||
{
|
||||
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
|
||||
StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
|
||||
data.allocator = allocator;
|
||||
data.len = 0;
|
||||
data.capacity = capacity;
|
||||
return *self = (DString)data;
|
||||
}
|
||||
|
||||
<*
|
||||
@require !self.data() "String already initialized"
|
||||
*>
|
||||
fn DString DString.tinit(&self, usz capacity = MIN_CAPACITY)
|
||||
{
|
||||
return self.init(tmem(), capacity) @inline;
|
||||
}
|
||||
|
||||
fn DString new_with_capacity(Allocator allocator, usz capacity)
|
||||
{
|
||||
return (DString){}.init(allocator, capacity);
|
||||
}
|
||||
|
||||
fn DString temp_with_capacity(usz capacity) => new_with_capacity(tmem(), capacity) @inline;
|
||||
|
||||
fn DString new(Allocator allocator, String c = "")
|
||||
{
|
||||
usz len = c.len;
|
||||
StringData* data = (StringData*)new_with_capacity(allocator, len);
|
||||
if (len)
|
||||
{
|
||||
data.len = len;
|
||||
mem::copy(&data.chars, c.ptr, len);
|
||||
}
|
||||
return (DString)data;
|
||||
}
|
||||
|
||||
fn DString temp(String s = "") => new(tmem(), s) @inline;
|
||||
|
||||
|
||||
fn void DString.replace_char(self, char ch, char replacement)
|
||||
{
|
||||
StringData* data = self.data();
|
||||
foreach (&c : data.chars[:data.len])
|
||||
{
|
||||
if (*c == ch) *c = replacement;
|
||||
}
|
||||
}
|
||||
|
||||
fn void DString.replace(&self, String needle, String replacement)
|
||||
{
|
||||
StringData* data = self.data();
|
||||
usz needle_len = needle.len;
|
||||
if (!data || data.len < needle_len) return;
|
||||
usz replace_len = replacement.len;
|
||||
if (needle_len == 1 && replace_len == 1)
|
||||
{
|
||||
self.replace_char(needle[0], replacement[0]);
|
||||
return;
|
||||
}
|
||||
@pool(data.allocator) {
|
||||
String str = self.tcopy_str();
|
||||
self.clear();
|
||||
usz len = str.len;
|
||||
usz match = 0;
|
||||
foreach (i, c : str)
|
||||
{
|
||||
if (c == needle[match])
|
||||
{
|
||||
match++;
|
||||
if (match == needle_len)
|
||||
{
|
||||
self.append_chars(replacement);
|
||||
match = 0;
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (match > 0)
|
||||
{
|
||||
self.append_chars(str[i - match:match]);
|
||||
match = 0;
|
||||
}
|
||||
self.append_char(c);
|
||||
}
|
||||
if (match > 0) self.append_chars(str[^match:match]);
|
||||
};
|
||||
}
|
||||
|
||||
fn DString DString.concat(self, Allocator allocator, DString b)
|
||||
{
|
||||
DString string;
|
||||
string.init(allocator, self.len() + b.len());
|
||||
string.append(self);
|
||||
string.append(b);
|
||||
return string;
|
||||
}
|
||||
|
||||
fn DString DString.tconcat(self, DString b) => self.concat(tmem(), b);
|
||||
|
||||
fn ZString DString.zstr_view(&self)
|
||||
{
|
||||
StringData* data = self.data();
|
||||
if (!data) return "";
|
||||
if (data.capacity == data.len)
|
||||
{
|
||||
self.reserve(1);
|
||||
data = self.data();
|
||||
data.chars[data.len] = 0;
|
||||
}
|
||||
else if (data.chars[data.len] != 0)
|
||||
{
|
||||
data.chars[data.len] = 0;
|
||||
}
|
||||
return (ZString)&data.chars[0];
|
||||
}
|
||||
|
||||
fn usz DString.capacity(self)
|
||||
{
|
||||
if (!self) return 0;
|
||||
return self.data().capacity;
|
||||
}
|
||||
|
||||
fn usz DString.len(&self) @dynamic @operator(len)
|
||||
{
|
||||
if (!*self) return 0;
|
||||
return self.data().len;
|
||||
}
|
||||
|
||||
<*
|
||||
@require new_size <= self.len()
|
||||
*>
|
||||
fn void DString.chop(self, usz new_size)
|
||||
{
|
||||
if (!self) return;
|
||||
self.data().len = new_size;
|
||||
}
|
||||
|
||||
fn String DString.str_view(self)
|
||||
{
|
||||
StringData* data = self.data();
|
||||
if (!data) return "";
|
||||
return (String)data.chars[:data.len];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len()
|
||||
@require self.data() != null "Empty string"
|
||||
*>
|
||||
fn char DString.char_at(self, usz index) @operator([])
|
||||
{
|
||||
return self.data().chars[index];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len()
|
||||
@require self.data() != null "Empty string"
|
||||
*>
|
||||
fn char* DString.char_ref(&self, usz index) @operator(&[])
|
||||
{
|
||||
return &self.data().chars[index];
|
||||
}
|
||||
|
||||
fn usz DString.append_utf32(&self, Char32[] chars)
|
||||
{
|
||||
self.reserve(chars.len);
|
||||
usz end = self.len();
|
||||
foreach (Char32 c : chars)
|
||||
{
|
||||
self.append_char32(c);
|
||||
}
|
||||
return self.data().len - end;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len()
|
||||
*>
|
||||
fn void DString.set(self, usz index, char c) @operator([]=)
|
||||
{
|
||||
self.data().chars[index] = c;
|
||||
}
|
||||
|
||||
fn void DString.append_repeat(&self, char c, usz times)
|
||||
{
|
||||
if (times == 0) return;
|
||||
self.reserve(times);
|
||||
StringData* data = self.data();
|
||||
for (usz i = 0; i < times; i++)
|
||||
{
|
||||
data.chars[data.len++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require c <= 0x10ffff
|
||||
*>
|
||||
fn usz DString.append_char32(&self, Char32 c)
|
||||
{
|
||||
char[4] buffer @noinit;
|
||||
char* p = &buffer;
|
||||
usz n = conv::char32_to_utf8_unsafe(c, &p);
|
||||
self.reserve(n);
|
||||
StringData* data = self.data();
|
||||
data.chars[data.len:n] = buffer[:n];
|
||||
data.len += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn DString DString.tcopy(&self) => self.copy(tmem());
|
||||
|
||||
fn DString DString.copy(self, Allocator allocator)
|
||||
{
|
||||
if (!self) return new(allocator);
|
||||
StringData* data = self.data();
|
||||
DString new_string = new_with_capacity(allocator, data.capacity);
|
||||
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
|
||||
return new_string;
|
||||
}
|
||||
|
||||
fn ZString DString.copy_zstr(self, Allocator allocator)
|
||||
{
|
||||
usz str_len = self.len();
|
||||
if (!str_len)
|
||||
{
|
||||
return (ZString)allocator::calloc(allocator, 1);
|
||||
}
|
||||
char* zstr = allocator::malloc(allocator, str_len + 1);
|
||||
StringData* data = self.data();
|
||||
mem::copy(zstr, &data.chars, str_len);
|
||||
zstr[str_len] = 0;
|
||||
return (ZString)zstr;
|
||||
}
|
||||
|
||||
fn String DString.copy_str(self, Allocator allocator)
|
||||
{
|
||||
return (String)self.copy_zstr(allocator)[:self.len()];
|
||||
}
|
||||
|
||||
fn String DString.tcopy_str(self) => self.copy_str(tmem()) @inline;
|
||||
|
||||
fn bool DString.equals(self, DString other_string)
|
||||
{
|
||||
StringData *str1 = self.data();
|
||||
StringData *str2 = other_string.data();
|
||||
if (str1 == str2) return true;
|
||||
if (!str1) return str2.len == 0;
|
||||
if (!str2) return str1.len == 0;
|
||||
usz str1_len = str1.len;
|
||||
if (str1_len != str2.len) return false;
|
||||
for (int i = 0; i < str1_len; i++)
|
||||
{
|
||||
if (str1.chars[i] != str2.chars[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void DString.free(&self)
|
||||
{
|
||||
if (!*self) return;
|
||||
StringData* data = self.data();
|
||||
if (!data) return;
|
||||
allocator::free(data.allocator, data);
|
||||
*self = (DString)null;
|
||||
}
|
||||
|
||||
fn bool DString.less(self, DString other_string)
|
||||
{
|
||||
StringData* str1 = self.data();
|
||||
StringData* str2 = other_string.data();
|
||||
if (str1 == str2) return false;
|
||||
if (!str1) return str2.len != 0;
|
||||
if (!str2) return str1.len == 0;
|
||||
usz str1_len = str1.len;
|
||||
usz str2_len = str2.len;
|
||||
if (str1_len != str2_len) return str1_len < str2_len;
|
||||
for (int i = 0; i < str1_len; i++)
|
||||
{
|
||||
if (str1.chars[i] >= str2.chars[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void DString.append_chars(&self, String str)
|
||||
{
|
||||
usz other_len = str.len;
|
||||
if (!other_len) return;
|
||||
if (!*self)
|
||||
{
|
||||
*self = temp(str);
|
||||
return;
|
||||
}
|
||||
self.reserve(other_len);
|
||||
StringData* data = self.data();
|
||||
mem::copy(&data.chars[data.len], str.ptr, other_len);
|
||||
data.len += other_len;
|
||||
}
|
||||
|
||||
fn Char32[] DString.copy_utf32(&self, Allocator allocator)
|
||||
{
|
||||
return self.str_view().to_utf32_copy(allocator) @inline!!;
|
||||
}
|
||||
|
||||
fn void DString.append_string(&self, DString str)
|
||||
{
|
||||
StringData* other = str.data();
|
||||
if (!other) return;
|
||||
self.append(str.str_view());
|
||||
}
|
||||
|
||||
fn void DString.clear(self)
|
||||
{
|
||||
if (!self) return;
|
||||
self.data().len = 0;
|
||||
}
|
||||
|
||||
fn usz! DString.write(&self, char[] buffer) @dynamic
|
||||
{
|
||||
self.append_chars((String)buffer);
|
||||
return buffer.len;
|
||||
}
|
||||
|
||||
fn void! DString.write_byte(&self, char c) @dynamic
|
||||
{
|
||||
self.append_char(c);
|
||||
}
|
||||
|
||||
fn void DString.append_char(&self, char c)
|
||||
{
|
||||
if (!*self)
|
||||
{
|
||||
*self = temp_with_capacity(MIN_CAPACITY);
|
||||
}
|
||||
self.reserve(1);
|
||||
StringData* data = self.data();
|
||||
data.chars[data.len++] = c;
|
||||
}
|
||||
|
||||
<*
|
||||
@require start < self.len()
|
||||
@require end < self.len()
|
||||
@require end >= start "End must be same or equal to the start"
|
||||
*>
|
||||
fn void DString.delete_range(&self, usz start, usz end)
|
||||
{
|
||||
self.delete(start, end - start + 1);
|
||||
}
|
||||
|
||||
<*
|
||||
@require start < self.len()
|
||||
@require start + len <= self.len()
|
||||
*>
|
||||
fn void DString.delete(&self, usz start, usz len = 1)
|
||||
{
|
||||
if (!len) return;
|
||||
StringData* data = self.data();
|
||||
usz new_len = data.len - len;
|
||||
if (new_len == 0)
|
||||
{
|
||||
data.len = 0;
|
||||
return;
|
||||
}
|
||||
usz len_after = data.len - start - len;
|
||||
if (len_after > 0)
|
||||
{
|
||||
data.chars[start:len_after] = data.chars[start + len:len_after];
|
||||
}
|
||||
data.len = new_len;
|
||||
}
|
||||
|
||||
macro void DString.append(&self, value)
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
$switch ($Type)
|
||||
$case char:
|
||||
$case ichar:
|
||||
self.append_char(value);
|
||||
$case DString:
|
||||
self.append_string(value);
|
||||
$case String:
|
||||
self.append_chars(value);
|
||||
$case Char32:
|
||||
self.append_char32(value);
|
||||
$default:
|
||||
$switch
|
||||
$case $defined((Char32)value):
|
||||
self.append_char32((Char32)value);
|
||||
$case $defined((String)value):
|
||||
self.append_chars((String)value);
|
||||
$default:
|
||||
$error "Unsupported type for append – use appendf instead.";
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.len()
|
||||
*>
|
||||
fn void DString.insert_chars_at(&self, usz index, String s)
|
||||
{
|
||||
if (s.len == 0) return;
|
||||
self.reserve(s.len);
|
||||
StringData* data = self.data();
|
||||
usz len = self.len();
|
||||
if (data.chars[:len].ptr == s.ptr)
|
||||
{
|
||||
// Source and destination are the same: nothing to do.
|
||||
return;
|
||||
}
|
||||
index = min(index, len);
|
||||
data.len += s.len;
|
||||
|
||||
char* start = data.chars[index:s.len].ptr; // area to insert into
|
||||
mem::move(start + s.len, start, len - index); // move existing data
|
||||
switch
|
||||
{
|
||||
case s.ptr <= start && start < s.ptr + s.len:
|
||||
// Overlapping areas.
|
||||
foreach_r (i, c : s)
|
||||
{
|
||||
data.chars[index + i] = c;
|
||||
}
|
||||
case start <= s.ptr && s.ptr < start + len:
|
||||
// Source has moved.
|
||||
mem::move(start, s.ptr + s.len, s.len);
|
||||
default:
|
||||
mem::move(start, s, s.len);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.len()
|
||||
*>
|
||||
fn void DString.insert_string_at(&self, usz index, DString str)
|
||||
{
|
||||
StringData* other = str.data();
|
||||
if (!other) return;
|
||||
self.insert_at(index, str.str_view());
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.len()
|
||||
*>
|
||||
fn void DString.insert_char_at(&self, usz index, char c)
|
||||
{
|
||||
self.reserve(1);
|
||||
StringData* data = self.data();
|
||||
|
||||
char* start = &data.chars[index];
|
||||
mem::move(start + 1, start, self.len() - index);
|
||||
data.chars[index] = c;
|
||||
data.len++;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.len()
|
||||
*>
|
||||
fn usz DString.insert_char32_at(&self, usz index, Char32 c)
|
||||
{
|
||||
char[4] buffer @noinit;
|
||||
char* p = &buffer;
|
||||
usz n = conv::char32_to_utf8_unsafe(c, &p);
|
||||
|
||||
self.reserve(n);
|
||||
StringData* data = self.data();
|
||||
|
||||
char* start = &data.chars[index];
|
||||
mem::move(start + n, start, self.len() - index);
|
||||
data.chars[index:n] = buffer[:n];
|
||||
data.len += n;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.len()
|
||||
*>
|
||||
fn usz DString.insert_utf32_at(&self, usz index, Char32[] chars)
|
||||
{
|
||||
usz n = conv::utf8len_for_utf32(chars);
|
||||
|
||||
self.reserve(n);
|
||||
StringData* data = self.data();
|
||||
|
||||
char* start = &data.chars[index];
|
||||
mem::move(start + n, start, self.len() - index);
|
||||
|
||||
char[4] buffer @noinit;
|
||||
|
||||
foreach(c : chars)
|
||||
{
|
||||
char* p = &buffer;
|
||||
usz m = conv::char32_to_utf8_unsafe(c, &p);
|
||||
data.chars[index:m] = buffer[:m];
|
||||
index += m;
|
||||
}
|
||||
|
||||
data.len += n;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
macro void DString.insert_at(&self, usz index, value)
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
$switch ($Type)
|
||||
$case char:
|
||||
$case ichar:
|
||||
self.insert_char_at(index, value);
|
||||
$case DString:
|
||||
self.insert_string_at(index, value);
|
||||
$case String:
|
||||
self.insert_chars_at(index, value);
|
||||
$case Char32:
|
||||
self.insert_char32_at(index, value);
|
||||
$default:
|
||||
$switch
|
||||
$case $defined((Char32)value):
|
||||
self.insert_char32_at(index, (Char32)value);
|
||||
$case $defined((String)value):
|
||||
self.insert_chars_at(index, (String)value);
|
||||
$default:
|
||||
$error "Unsupported type for insert";
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn usz! DString.appendf(&self, String format, args...) @maydiscard
|
||||
{
|
||||
if (!self.data()) self.tinit(format.len + 20);
|
||||
@pool(self.data().allocator)
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_string_append_fn, self);
|
||||
return formatter.vprintf(format, args);
|
||||
};
|
||||
}
|
||||
|
||||
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
|
||||
{
|
||||
if (!self.data()) self.tinit(format.len + 20);
|
||||
@pool(self.data().allocator)
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_string_append_fn, self);
|
||||
usz len = formatter.vprintf(format, args)!;
|
||||
self.append('\n');
|
||||
return len + 1;
|
||||
};
|
||||
}
|
||||
|
||||
fn DString join(Allocator allocator, String[] s, String joiner)
|
||||
{
|
||||
if (!s.len) return new(allocator);
|
||||
usz total_size = joiner.len * s.len;
|
||||
foreach (String* &str : s)
|
||||
{
|
||||
total_size += str.len;
|
||||
}
|
||||
DString res = new_with_capacity(allocator, total_size);
|
||||
res.append(s[0]);
|
||||
foreach (String* &str : s[1..])
|
||||
{
|
||||
res.append(joiner);
|
||||
res.append(*str);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
fn void! out_string_append_fn(void* data, char c) @private
|
||||
{
|
||||
DString* s = data;
|
||||
s.append_char(c);
|
||||
}
|
||||
|
||||
fn void DString.reverse(self)
|
||||
{
|
||||
StringData *data = self.data();
|
||||
if (!data) return;
|
||||
isz mid = data.len / 2;
|
||||
for (isz i = 0; i < mid; i++)
|
||||
{
|
||||
char temp = data.chars[i];
|
||||
isz reverse_index = data.len - 1 - i;
|
||||
data.chars[i] = data.chars[reverse_index];
|
||||
data.chars[reverse_index] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
fn StringData* DString.data(self) @inline @private
|
||||
{
|
||||
return (StringData*)self;
|
||||
}
|
||||
|
||||
fn void DString.reserve(&self, usz addition)
|
||||
{
|
||||
StringData* data = self.data();
|
||||
if (!data)
|
||||
{
|
||||
*self = dstring::temp_with_capacity(addition);
|
||||
return;
|
||||
}
|
||||
usz len = data.len + addition;
|
||||
if (data.capacity >= len) return;
|
||||
usz new_capacity = data.capacity * 2;
|
||||
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
|
||||
while (new_capacity < len) new_capacity *= 2;
|
||||
data.capacity = new_capacity;
|
||||
*self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity);
|
||||
}
|
||||
|
||||
fn usz! DString.read_from_stream(&self, InStream reader)
|
||||
{
|
||||
if (&reader.available)
|
||||
{
|
||||
usz total_read = 0;
|
||||
while (usz available = reader.available()!)
|
||||
{
|
||||
self.reserve(available);
|
||||
StringData* data = self.data();
|
||||
usz len = reader.read(data.chars[data.len..(data.capacity - 1)])!;
|
||||
total_read += len;
|
||||
data.len += len;
|
||||
}
|
||||
return total_read;
|
||||
}
|
||||
usz total_read = 0;
|
||||
while (true)
|
||||
{
|
||||
// Reserve at least 16 bytes
|
||||
self.reserve(16);
|
||||
StringData* data = self.data();
|
||||
// Read into the rest of the buffer
|
||||
usz read = reader.read(data.chars[data.len..(data.capacity - 1)])!;
|
||||
data.len += read;
|
||||
// Ok, we reached the end.
|
||||
if (read < 16) return total_read;
|
||||
// Otherwise go another round
|
||||
}
|
||||
}
|
||||
|
||||
struct StringData @private
|
||||
{
|
||||
Allocator allocator;
|
||||
usz len;
|
||||
usz capacity;
|
||||
char[?] chars;
|
||||
}
|
||||
197
lib7/std/core/env.c3
Normal file
197
lib7/std/core/env.c3
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::env;
|
||||
|
||||
enum CompilerOptLevel
|
||||
{
|
||||
O0,
|
||||
O1,
|
||||
O2,
|
||||
O3
|
||||
}
|
||||
|
||||
enum MemoryEnvironment
|
||||
{
|
||||
NORMAL,
|
||||
SMALL,
|
||||
TINY,
|
||||
NONE
|
||||
}
|
||||
|
||||
enum OsType
|
||||
{
|
||||
UNKNOWN,
|
||||
NONE,
|
||||
ANANAS,
|
||||
CLOUD_ABI,
|
||||
DRAGON_FLY,
|
||||
FREEBSD,
|
||||
FUCHSIA,
|
||||
IOS,
|
||||
KFREEBSD,
|
||||
LINUX,
|
||||
PS3,
|
||||
MACOS,
|
||||
NETBSD,
|
||||
OPENBSD,
|
||||
SOLARIS,
|
||||
WIN32,
|
||||
HAIKU,
|
||||
MINIX,
|
||||
RTEMS,
|
||||
NACL, // Native Client
|
||||
CNK, // BG/P Compute-Node Kernel
|
||||
AIX,
|
||||
CUDA,
|
||||
NVOPENCL,
|
||||
AMDHSA,
|
||||
PS4,
|
||||
ELFIAMCU,
|
||||
TVOS,
|
||||
WATCHOS,
|
||||
MESA3D,
|
||||
CONTIKI,
|
||||
AMDPAL,
|
||||
HERMITCORE,
|
||||
HURD,
|
||||
WASI,
|
||||
EMSCRIPTEN,
|
||||
}
|
||||
|
||||
enum ArchType
|
||||
{
|
||||
UNKNOWN,
|
||||
ARM, // ARM (little endian): arm, armv.*, xscale
|
||||
ARMB, // ARM (big endian): armeb
|
||||
AARCH64, // AArch64 (little endian): aarch64
|
||||
AARCH64_BE, // AArch64 (big endian): aarch64_be
|
||||
AARCH64_32, // AArch64 (little endian) ILP32: aarch64_32
|
||||
ARC, // ARC: Synopsys ARC
|
||||
AVR, // AVR: Atmel AVR microcontroller
|
||||
BPFEL, // eBPF or extended BPF or 64-bit BPF (little endian)
|
||||
BPFEB, // eBPF or extended BPF or 64-bit BPF (big endian)
|
||||
HEXAGON, // Hexagon: hexagon
|
||||
MIPS, // MIPS: mips, mipsallegrex, mipsr6
|
||||
MIPSEL, // MIPSEL: mipsel, mipsallegrexe, mipsr6el
|
||||
MIPS64, // MIPS64: mips64, mips64r6, mipsn32, mipsn32r6
|
||||
MIPS64EL, // MIPS64EL: mips64el, mips64r6el, mipsn32el, mipsn32r6el
|
||||
MSP430, // MSP430: msp430
|
||||
PPC, // PPC: powerpc
|
||||
PPC64, // PPC64: powerpc64, ppu
|
||||
PPC64LE, // PPC64LE: powerpc64le
|
||||
R600, // R600: AMD GPUs HD2XXX - HD6XXX
|
||||
AMDGCN, // AMDGCN: AMD GCN GPUs
|
||||
RISCV32, // RISC-V (32-bit): riscv32
|
||||
RISCV64, // RISC-V (64-bit): riscv64
|
||||
SPARC, // Sparc: sparc
|
||||
SPARCV9, // Sparcv9: Sparcv9
|
||||
SPARCEL, // Sparc: (endianness = little). NB: 'Sparcle' is a CPU variant
|
||||
SYSTEMZ, // SystemZ: s390x
|
||||
TCE, // TCE (http://tce.cs.tut.fi/): tce
|
||||
TCELE, // TCE little endian (http://tce.cs.tut.fi/): tcele
|
||||
THUMB, // Thumb (little endian): thumb, thumbv.*
|
||||
THUMBEB, // Thumb (big endian): thumbeb
|
||||
X86, // X86: i[3-9]86
|
||||
X86_64, // X86-64: amd64, x86_64
|
||||
XCORE, // XCore: xcore
|
||||
NVPTX, // NVPTX: 32-bit
|
||||
NVPTX64, // NVPTX: 64-bit
|
||||
LE32, // le32: generic little-endian 32-bit CPU (PNaCl)
|
||||
LE64, // le64: generic little-endian 64-bit CPU (PNaCl)
|
||||
AMDIL, // AMDIL
|
||||
AMDIL64, // AMDIL with 64-bit pointers
|
||||
HSAIL, // AMD HSAIL
|
||||
HSAIL64, // AMD HSAIL with 64-bit pointers
|
||||
SPIR, // SPIR: standard portable IR for OpenCL 32-bit version
|
||||
SPIR64, // SPIR: standard portable IR for OpenCL 64-bit version
|
||||
KALIMBA, // Kalimba: generic kalimba
|
||||
SHAVE, // SHAVE: Movidius vector VLIW processors
|
||||
LANAI, // Lanai: Lanai 32-bit
|
||||
WASM32, // WebAssembly with 32-bit pointers
|
||||
WASM64, // WebAssembly with 64-bit pointers
|
||||
RSCRIPT32, // 32-bit RenderScript
|
||||
RSCRIPT64, // 64-bit RenderScript
|
||||
XTENSA, // Xtensa
|
||||
}
|
||||
|
||||
const String COMPILER_BUILD_HASH = $$BUILD_HASH;
|
||||
const String COMPILER_BUILD_DATE = $$BUILD_DATE;
|
||||
const OsType OS_TYPE = OsType.from_ordinal($$OS_TYPE);
|
||||
const ArchType ARCH_TYPE = ArchType.from_ordinal($$ARCH_TYPE);
|
||||
const bool ARCH_32_BIT = $$REGISTER_SIZE == 32;
|
||||
const bool ARCH_64_BIT = $$REGISTER_SIZE == 64;
|
||||
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
|
||||
const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
|
||||
const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel.from_ordinal($$COMPILER_OPT_LEVEL);
|
||||
const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
|
||||
const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED;
|
||||
const bool F16_SUPPORT = $$PLATFORM_F16_SUPPORTED;
|
||||
const bool F128_SUPPORT = $$PLATFORM_F128_SUPPORTED;
|
||||
const REGISTER_SIZE = $$REGISTER_SIZE;
|
||||
const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE;
|
||||
const bool DEBUG_SYMBOLS = $$DEBUG_SYMBOLS;
|
||||
const bool BACKTRACE = $$BACKTRACE;
|
||||
const usz LLVM_VERSION = $$LLVM_VERSION;
|
||||
const bool BENCHMARKING = $$BENCHMARKING;
|
||||
const bool TESTING = $$TESTING;
|
||||
const MemoryEnvironment MEMORY_ENV = MemoryEnvironment.from_ordinal($$MEMORY_ENVIRONMENT);
|
||||
const bool TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING);
|
||||
const bool X86_64 = ARCH_TYPE == X86_64;
|
||||
const bool X86 = ARCH_TYPE == X86;
|
||||
const bool AARCH64 = ARCH_TYPE == AARCH64;
|
||||
const bool NATIVE_STACKTRACE = LINUX || DARWIN || WIN32;
|
||||
const bool LINUX = LIBC && OS_TYPE == LINUX;
|
||||
const bool DARWIN = LIBC && os_is_darwin();
|
||||
const bool WIN32 = LIBC && OS_TYPE == WIN32;
|
||||
const bool POSIX = LIBC && os_is_posix();
|
||||
const bool OPENBSD = LIBC && OS_TYPE == OPENBSD;
|
||||
const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
|
||||
const bool NETBSD = LIBC && OS_TYPE == NETBSD;
|
||||
const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD;
|
||||
const bool WASI = LIBC && OS_TYPE == WASI;
|
||||
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
|
||||
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
|
||||
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
|
||||
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
|
||||
|
||||
macro bool os_is_darwin() @const
|
||||
{
|
||||
$switch (OS_TYPE)
|
||||
$case IOS:
|
||||
$case MACOS:
|
||||
$case TVOS:
|
||||
$case WATCHOS:
|
||||
return true;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool os_is_posix() @const
|
||||
{
|
||||
$switch (OS_TYPE)
|
||||
$case IOS:
|
||||
$case MACOS:
|
||||
$case NETBSD:
|
||||
$case LINUX:
|
||||
$case KFREEBSD:
|
||||
$case FREEBSD:
|
||||
$case OPENBSD:
|
||||
$case SOLARIS:
|
||||
$case TVOS:
|
||||
$case WATCHOS:
|
||||
return true;
|
||||
$case WIN32:
|
||||
$case WASI:
|
||||
$case EMSCRIPTEN:
|
||||
return false;
|
||||
$default:
|
||||
$echo("Assuming non-Posix environment");
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
|
||||
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);
|
||||
911
lib7/std/core/mem.c3
Normal file
911
lib7/std/core/mem.c3
Normal file
@@ -0,0 +1,911 @@
|
||||
// Copyright (c) 2021-2023 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::mem;
|
||||
import std::core::mem::allocator @public;
|
||||
import std::math;
|
||||
|
||||
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
|
||||
const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
|
||||
|
||||
macro bool @constant_is_power_of_2($x) @const @private
|
||||
{
|
||||
return $x != 0 && ($x & ($x - 1)) == 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Load a vector from memory according to a mask assuming default alignment.
|
||||
|
||||
@param ptr "The pointer address to load from."
|
||||
@param mask "The mask for the load"
|
||||
@param passthru "The value to use for non masked values"
|
||||
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
|
||||
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
|
||||
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
*>
|
||||
macro masked_load(ptr, bool[<?>] mask, passthru)
|
||||
{
|
||||
return $$masked_load(ptr, mask, passthru, 0);
|
||||
}
|
||||
|
||||
<*
|
||||
Load a vector from memory according to a mask.
|
||||
|
||||
@param ptr "The pointer address to load from."
|
||||
@param mask "The mask for the load"
|
||||
@param passthru "The value to use for non masked values"
|
||||
@param $alignment "The alignment to assume for the pointer"
|
||||
|
||||
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
|
||||
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
|
||||
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
*>
|
||||
macro @masked_load_aligned(ptr, bool[<?>] mask, passthru, usz $alignment)
|
||||
{
|
||||
return $$masked_load(ptr, mask, passthru, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
Load values from a pointer vector, assuming default alignment.
|
||||
|
||||
@param ptrvec "The vector of pointers to load from."
|
||||
@param mask "The mask for the load"
|
||||
@param passthru "The value to use for non masked values"
|
||||
|
||||
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
|
||||
@require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
|
||||
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
*>
|
||||
macro gather(ptrvec, bool[<?>] mask, passthru)
|
||||
{
|
||||
return $$gather(ptrvec, mask, passthru, 0);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Load values from a pointer vector.
|
||||
|
||||
@param ptrvec "The vector of pointers to load from."
|
||||
@param mask "The mask for the load"
|
||||
@param passthru "The value to use for non masked values"
|
||||
@param $alignment "The alignment to assume for the pointers"
|
||||
|
||||
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
|
||||
@require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
|
||||
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
*>
|
||||
macro @gather_aligned(ptrvec, bool[<?>] mask, passthru, usz $alignment)
|
||||
{
|
||||
return $$gather(ptrvec, mask, passthru, $alignment);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Store parts of a vector according to the mask, assuming default alignment.
|
||||
|
||||
@param ptr "The pointer address to store to."
|
||||
@param value "The value to store masked"
|
||||
@param mask "The mask for the store"
|
||||
|
||||
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
|
||||
@require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require value.len == mask.len : "Mask and value must have the same length"
|
||||
*>
|
||||
macro masked_store(ptr, value, bool[<?>] mask)
|
||||
{
|
||||
return $$masked_store(ptr, value, mask, 0);
|
||||
}
|
||||
|
||||
<*
|
||||
@param ptr "The pointer address to store to."
|
||||
@param value "The value to store masked"
|
||||
@param mask "The mask for the store"
|
||||
@param $alignment "The alignment of the pointer"
|
||||
|
||||
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
|
||||
@require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require value.len == mask.len : "Mask and value must have the same length"
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
|
||||
*>
|
||||
macro @masked_store_aligned(ptr, value, bool[<?>] mask, usz $alignment)
|
||||
{
|
||||
return $$masked_store(ptr, value, mask, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param ptrvec "The vector pointer containing the addresses to store to."
|
||||
@param value "The value to store masked"
|
||||
@param mask "The mask for the store"
|
||||
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
|
||||
@require value.len == mask.len : "Mask and value must have the same length"
|
||||
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
|
||||
*>
|
||||
macro scatter(ptrvec, value, bool[<?>] mask)
|
||||
{
|
||||
return $$scatter(ptrvec, value, mask, 0);
|
||||
}
|
||||
|
||||
<*
|
||||
@param ptrvec "The vector pointer containing the addresses to store to."
|
||||
@param value "The value to store masked"
|
||||
@param mask "The mask for the store"
|
||||
@param $alignment "The alignment of the load"
|
||||
|
||||
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
|
||||
@require value.len == mask.len : "Mask and value must have the same length"
|
||||
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
*>
|
||||
macro @scatter_aligned(ptrvec, value, bool[<?>] mask, usz $alignment)
|
||||
{
|
||||
return $$scatter(ptrvec, value, mask, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "The variable or dereferenced pointer to load."
|
||||
@param $alignment "The alignment to assume for the load"
|
||||
@return "The value of the variable"
|
||||
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
|
||||
*>
|
||||
macro @unaligned_load(#x, usz $alignment) @builtin
|
||||
{
|
||||
return $$unaligned_load(&#x, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "The variable or dereferenced pointer to store to."
|
||||
@param value "The value to store."
|
||||
@param $alignment "The alignment to assume for the store"
|
||||
@return "The value stored"
|
||||
|
||||
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
|
||||
@require $defined(#x = value) : "The value doesn't match the variable"
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
*>
|
||||
macro @unaligned_store(#x, value, usz $alignment) @builtin
|
||||
{
|
||||
return $$unaligned_store(&#x, ($typeof(#x))value, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "The variable or dereferenced pointer to load."
|
||||
@return "The value of the variable"
|
||||
|
||||
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
|
||||
*>
|
||||
macro @volatile_load(#x) @builtin
|
||||
{
|
||||
return $$volatile_load(&#x);
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "The variable or dereferenced pointer to store to."
|
||||
@param value "The value to store."
|
||||
@return "The value stored"
|
||||
|
||||
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
|
||||
@require $defined(#x = value) : "The value doesn't match the variable"
|
||||
*>
|
||||
macro @volatile_store(#x, value) @builtin
|
||||
{
|
||||
return $$volatile_store(&#x, ($typeof(#x))value);
|
||||
}
|
||||
|
||||
enum AtomicOrdering : int
|
||||
{
|
||||
NOT_ATOMIC, // Not atomic
|
||||
UNORDERED, // No lock
|
||||
RELAXED, // Consistent ordering
|
||||
ACQUIRE, // Barrier locking load/store
|
||||
RELEASE, // Barrier releasing load/store
|
||||
ACQUIRE_RELEASE, // Barrier fence to load/store
|
||||
SEQ_CONSISTENT, // Acquire semantics, ordered with other seq_consistent
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "the variable or dereferenced pointer to load."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@param $volatile "whether the load should be volatile, defaults to 'false'"
|
||||
@return "returns the value of x"
|
||||
|
||||
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
|
||||
@require $ordering != AtomicOrdering.RELEASE "Release ordering is not valid for load."
|
||||
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid for load."
|
||||
@require types::may_load_atomic($typeof(#x)) "Only integer, float and pointers may be used."
|
||||
*>
|
||||
macro @atomic_load(#x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
|
||||
{
|
||||
return $$atomic_load(&#x, $volatile, $ordering.ordinal);
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "the variable or dereferenced pointer to store to."
|
||||
@param value "the value to store."
|
||||
@param $ordering "the atomic ordering of the store, defaults to SEQ_CONSISTENT"
|
||||
@param $volatile "whether the store should be volatile, defaults to 'false'"
|
||||
|
||||
@require $ordering != AtomicOrdering.ACQUIRE "Acquire ordering is not valid for store."
|
||||
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid for store."
|
||||
@require types::may_load_atomic($typeof(#x)) "Only integer, float and pointers may be used."
|
||||
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
|
||||
@require $defined(#x = value) : "The value doesn't match the variable"
|
||||
*>
|
||||
macro void @atomic_store(#x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
|
||||
{
|
||||
$$atomic_store(&#x, value, $volatile, $ordering.ordinal);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
|
||||
*>
|
||||
macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT, bool $volatile = true, bool $weak = false, usz $alignment = 0)
|
||||
{
|
||||
return $$compare_exchange(ptr, compare, value, $volatile, $weak, $success.ordinal, $failure.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
|
||||
*>
|
||||
macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT)
|
||||
{
|
||||
return compare_exchange(ptr, compare, value, $success, $failure, true);
|
||||
}
|
||||
|
||||
<*
|
||||
@require math::is_power_of_2(alignment)
|
||||
*>
|
||||
fn usz aligned_offset(usz offset, usz alignment)
|
||||
{
|
||||
return alignment * ((offset + alignment - 1) / alignment);
|
||||
}
|
||||
|
||||
macro void* aligned_pointer(void* ptr, usz alignment)
|
||||
{
|
||||
return (void*)(uptr)aligned_offset((uptr)ptr, alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@require math::is_power_of_2(alignment)
|
||||
*>
|
||||
fn bool ptr_is_aligned(void* ptr, usz alignment) @inline
|
||||
{
|
||||
return (uptr)ptr & ((uptr)alignment - 1) == 0;
|
||||
}
|
||||
|
||||
macro void zero_volatile(char[] data)
|
||||
{
|
||||
$$memset(data.ptr, (char)0, data.len, true, (usz)1);
|
||||
}
|
||||
|
||||
macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false)
|
||||
{
|
||||
$$memset(dst, (char)0, len, $is_volatile, $dst_align);
|
||||
}
|
||||
|
||||
macro void clear_inline(void* dst, usz $len, usz $dst_align = 0, bool $is_volatile = false)
|
||||
{
|
||||
$$memset_inline(dst, (char)0, $len, $is_volatile, $dst_align);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy memory from src to dst efficiently, assuming the memory ranges do not overlap.
|
||||
|
||||
@param [&out] dst "The destination to copy to"
|
||||
@param [&in] src "The source to copy from"
|
||||
@param len "The number of bytes to copy"
|
||||
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
|
||||
|
||||
@require len == 0 || dst + len <= src || src + len <= dst : "Ranges may not overlap"
|
||||
*>
|
||||
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
|
||||
{
|
||||
$$memcpy(dst, src, len, $is_volatile, $dst_align, $src_align);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy memory from src to dst efficiently, assuming the memory ranges do not overlap, it
|
||||
will always be inlined and never call memcopy
|
||||
|
||||
@param [&out] dst "The destination to copy to"
|
||||
@param [&in] src "The source to copy from"
|
||||
@param $len "The number of bytes to copy"
|
||||
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
|
||||
|
||||
@require $len == 0 || dst + $len <= src || src + $len <= dst : "Ranges may not overlap"
|
||||
*>
|
||||
macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
|
||||
{
|
||||
$$memcpy_inline(dst, src, $len, $is_volatile, $dst_align, $src_align);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy memory from src to dst but correctly handle the possibility of overlapping ranges.
|
||||
|
||||
@param [&out] dst "The destination to copy to"
|
||||
@param [&in] src "The source to copy from"
|
||||
@param len "The number of bytes to copy"
|
||||
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
|
||||
*>
|
||||
macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
|
||||
{
|
||||
$$memmove(dst, src, len, $is_volatile, $dst_align, $src_align);
|
||||
}
|
||||
|
||||
<*
|
||||
Sets all memory in a region to that of the provided byte.
|
||||
|
||||
@param [&out] dst "The destination to copy to"
|
||||
@param val "The value to copy into memory"
|
||||
@param len "The number of bytes to copy"
|
||||
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
|
||||
|
||||
@ensure !len || (dst[0] == val && dst[len - 1] == val)
|
||||
*>
|
||||
macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false)
|
||||
{
|
||||
$$memset(dst, val, len, $is_volatile, $dst_align);
|
||||
}
|
||||
|
||||
<*
|
||||
Sets all memory in a region to that of the provided byte. Never calls OS memset.
|
||||
|
||||
@param [&out] dst "The destination to copy to"
|
||||
@param val "The value to copy into memory"
|
||||
@param $len "The number of bytes to copy"
|
||||
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
|
||||
|
||||
@ensure !$len || (dst[0] == val && dst[$len - 1] == val)
|
||||
*>
|
||||
macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $is_volatile = false)
|
||||
{
|
||||
$$memset_inline(dst, val, $len, $is_volatile, $dst_align);
|
||||
}
|
||||
<*
|
||||
Test if n elements are equal in a slice, pointed to by a pointer etc.
|
||||
|
||||
@require values::@inner_kind(a) == TypeKind.SLICE || values::@inner_kind(a) == TypeKind.POINTER
|
||||
@require values::@inner_kind(b) == TypeKind.SLICE || values::@inner_kind(b) == TypeKind.POINTER
|
||||
@require values::@inner_kind(a) != TypeKind.SLICE || len == -1
|
||||
@require values::@inner_kind(a) != TypeKind.POINTER || len > -1
|
||||
@require values::@assign_to(a, b) && values::@assign_to(b, a)
|
||||
*>
|
||||
macro bool equals(a, b, isz len = -1, usz $align = 0)
|
||||
{
|
||||
$if !$align:
|
||||
$align = $typeof(a[0]).alignof;
|
||||
$endif
|
||||
void* x @noinit;
|
||||
void* y @noinit;
|
||||
$if values::@inner_kind(a) == TypeKind.SLICE:
|
||||
len = a.len;
|
||||
if (len != b.len) return false;
|
||||
x = a.ptr;
|
||||
y = b.ptr;
|
||||
$else
|
||||
x = a;
|
||||
y = b;
|
||||
assert(len >= 0, "A zero or positive length must be given when comparing pointers.");
|
||||
$endif
|
||||
|
||||
if (!len) return true;
|
||||
var $Type;
|
||||
$switch ($align)
|
||||
$case 1:
|
||||
$Type = char;
|
||||
$case 2:
|
||||
$Type = ushort;
|
||||
$case 4:
|
||||
$Type = uint;
|
||||
$case 8:
|
||||
$default:
|
||||
$Type = ulong;
|
||||
$endswitch
|
||||
var $step = $Type.sizeof;
|
||||
usz end = len / $step;
|
||||
for (usz i = 0; i < end; i++)
|
||||
{
|
||||
if ((($Type*)x)[i] != (($Type*)y)[i]) return false;
|
||||
}
|
||||
usz last = len % $align;
|
||||
for (usz i = len - last; i < len; i++)
|
||||
{
|
||||
if (((char*)x)[i] != ((char*)y)[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Check if an allocation must be aligned given the type.
|
||||
|
||||
@return `true if the alignment of the type exceeds DEFAULT_MEM_ALIGNMENT.`
|
||||
*>
|
||||
macro bool type_alloc_must_be_aligned($Type)
|
||||
{
|
||||
return $Type.alignof > DEFAULT_MEM_ALIGNMENT;
|
||||
}
|
||||
|
||||
<*
|
||||
Run with a specific allocator inside of the macro body.
|
||||
*>
|
||||
macro void @scoped(Allocator allocator; @body())
|
||||
{
|
||||
Allocator old_allocator = allocator::thread_allocator;
|
||||
allocator::thread_allocator = allocator;
|
||||
defer allocator::thread_allocator = old_allocator;
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
Run the tracking allocator in the scope, then
|
||||
print out stats.
|
||||
|
||||
@param $enabled "Set to false to disable tracking"
|
||||
*>
|
||||
macro void @report_heap_allocs_in_scope($enabled = true; @body())
|
||||
{
|
||||
$if $enabled:
|
||||
TrackingAllocator tracker;
|
||||
tracker.init(allocator::thread_allocator);
|
||||
Allocator old_allocator = allocator::thread_allocator;
|
||||
allocator::thread_allocator = &tracker;
|
||||
defer
|
||||
{
|
||||
allocator::thread_allocator = old_allocator;
|
||||
tracker.print_report();
|
||||
tracker.free();
|
||||
}
|
||||
$endif
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
Assert on memory leak in the scope of the macro body.
|
||||
|
||||
@param $report "Set to false to disable memory report"
|
||||
*>
|
||||
macro void @assert_leak($report = true; @body()) @builtin
|
||||
{
|
||||
$if env::DEBUG_SYMBOLS || $feature(MEMORY_ASSERTS):
|
||||
TrackingAllocator tracker;
|
||||
tracker.init(allocator::thread_allocator);
|
||||
Allocator old_allocator = allocator::thread_allocator;
|
||||
allocator::thread_allocator = &tracker;
|
||||
defer
|
||||
{
|
||||
allocator::thread_allocator = old_allocator;
|
||||
defer tracker.free();
|
||||
usz allocated = tracker.allocated();
|
||||
if (allocated)
|
||||
{
|
||||
DString report = dstring::new();
|
||||
defer report.free();
|
||||
$if $report:
|
||||
report.append_char('\n');
|
||||
(void)tracker.fprint_report(&report);
|
||||
$endif
|
||||
assert(allocated == 0, "Memory leak detected"
|
||||
" (%d bytes allocated).%s",
|
||||
allocated, report.str_view());
|
||||
}
|
||||
}
|
||||
$endif
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate [size] bytes on the stack to use for allocation,
|
||||
with the heap allocator as the backing allocator.
|
||||
|
||||
Release everything on scope exit.
|
||||
|
||||
@param $size `the size of the buffer`
|
||||
*>
|
||||
macro void @stack_mem(usz $size; @body(Allocator mem)) @builtin
|
||||
{
|
||||
char[$size] buffer;
|
||||
OnStackAllocator allocator;
|
||||
allocator.init(&buffer, allocator::heap());
|
||||
defer allocator.free();
|
||||
@body(&allocator);
|
||||
}
|
||||
|
||||
macro void @stack_pool(usz $size; @body) @builtin
|
||||
{
|
||||
char[$size] buffer;
|
||||
OnStackAllocator allocator;
|
||||
allocator.init(&buffer, allocator::heap());
|
||||
defer allocator.free();
|
||||
mem::@scoped(&allocator)
|
||||
{
|
||||
@body();
|
||||
};
|
||||
}
|
||||
|
||||
struct TempState
|
||||
{
|
||||
TempAllocator* old;
|
||||
TempAllocator* current;
|
||||
usz mark;
|
||||
}
|
||||
<*
|
||||
Push the current temp allocator. A push must always be balanced with a pop using the current state.
|
||||
*>
|
||||
fn TempState temp_push(TempAllocator* other = null)
|
||||
{
|
||||
TempAllocator* current = allocator::temp();
|
||||
TempAllocator* old = current;
|
||||
if (other == current)
|
||||
{
|
||||
current = allocator::temp_allocator_next();
|
||||
}
|
||||
return { old, current, current.used };
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the current temp allocator. A pop must always be balanced with a push.
|
||||
*>
|
||||
fn void temp_pop(TempState old_state)
|
||||
{
|
||||
assert(allocator::thread_temp_allocator == old_state.current, "Tried to pop temp allocators out of order.");
|
||||
assert(old_state.current.used >= old_state.mark, "Tried to pop temp allocators out of order.");
|
||||
old_state.current.reset(old_state.mark);
|
||||
allocator::thread_temp_allocator = old_state.old;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_empty_macro_slot(#other_temp) ||| $assignable(#other_temp, Allocator) "Must be an allocator"
|
||||
*>
|
||||
macro void @pool(#other_temp = EMPTY_MACRO_SLOT; @body) @builtin
|
||||
{
|
||||
TempAllocator* current = allocator::temp();
|
||||
$if @is_valid_macro_slot(#other_temp):
|
||||
TempAllocator* original = current;
|
||||
if (current == #other_temp.ptr) current = allocator::temp_allocator_next();
|
||||
$endif
|
||||
usz mark = current.used;
|
||||
defer
|
||||
{
|
||||
current.reset(mark);
|
||||
$if @is_valid_macro_slot(#other_temp):
|
||||
allocator::thread_temp_allocator = original;
|
||||
$endif;
|
||||
}
|
||||
@body();
|
||||
}
|
||||
|
||||
import libc;
|
||||
|
||||
|
||||
module std::core::mem @if(WASM_NOLIBC);
|
||||
import std::core::mem::allocator @public;
|
||||
SimpleHeapAllocator wasm_allocator @private;
|
||||
extern int __heap_base;
|
||||
|
||||
fn void initialize_wasm_mem() @init(1024) @private
|
||||
{
|
||||
allocator::wasm_memory.allocate_block(mem::DEFAULT_MEM_ALIGNMENT)!!; // Give us a valid null.
|
||||
// Check if we need to move the heap.
|
||||
uptr start = (uptr)&__heap_base;
|
||||
if (start > mem::DEFAULT_MEM_ALIGNMENT) allocator::wasm_memory.use = start;
|
||||
wasm_allocator.init(fn (x) => allocator::wasm_memory.allocate_block(x));
|
||||
allocator::thread_allocator = &wasm_allocator;
|
||||
allocator::temp_base_allocator = &wasm_allocator;
|
||||
allocator::init_default_temp_allocators();
|
||||
}
|
||||
|
||||
module std::core::mem;
|
||||
|
||||
|
||||
macro TrackingEnv* get_tracking_env()
|
||||
{
|
||||
$if env::TRACK_MEMORY:
|
||||
return &&TrackingEnv { $$FILE, $$FUNC, $$LINE };
|
||||
$else
|
||||
return null;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro @clone(value) @builtin @nodiscard
|
||||
{
|
||||
return allocator::clone(allocator::heap(), value);
|
||||
}
|
||||
|
||||
macro @tclone(value) @builtin @nodiscard
|
||||
{
|
||||
return temp_new($typeof(value), value);
|
||||
}
|
||||
|
||||
fn void* malloc(usz size) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::malloc(allocator::heap(), size);
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
fn void* malloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::malloc_aligned(allocator::heap(), size, alignment)!!;
|
||||
}
|
||||
|
||||
fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator::temp().acquire(size, NO_ZERO, alignment)!!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
*>
|
||||
macro new($Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc($Type.sizeof);
|
||||
$else
|
||||
$Type* val = malloc($Type.sizeof);
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
*>
|
||||
macro new_with_padding($Type, usz padding, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc($Type.sizeof + padding);
|
||||
$else
|
||||
$Type* val = malloc($Type.sizeof + padding);
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_aligned($Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
|
||||
$else
|
||||
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
*>
|
||||
macro alloc($Type) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc($Type.sizeof);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
*>
|
||||
macro alloc_with_padding($Type, usz padding) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc($Type.sizeof + padding);
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
macro alloc_aligned($Type) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc_aligned($Type.sizeof, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro temp_new($Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)tcalloc($Type.sizeof) @inline;
|
||||
$else
|
||||
$Type* val = tmalloc($Type.sizeof) @inline;
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro temp_new_with_padding($Type, usz padding, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)tcalloc($Type.sizeof + padding) @inline;
|
||||
$else
|
||||
$Type* val = tmalloc($Type.sizeof + padding) @inline;
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro temp_alloc($Type) @nodiscard
|
||||
{
|
||||
return tmalloc($Type.sizeof);
|
||||
}
|
||||
|
||||
macro temp_alloc_with_padding($Type, usz padding) @nodiscard
|
||||
{
|
||||
return tmalloc($Type.sizeof + padding);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
|
||||
*>
|
||||
macro new_array($Type, usz elements) @nodiscard
|
||||
{
|
||||
return allocator::new_array(allocator::heap(), $Type, elements);
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
macro new_array_aligned($Type, usz elements) @nodiscard
|
||||
{
|
||||
return allocator::new_array_aligned(allocator::heap(), $Type, elements);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
|
||||
*>
|
||||
macro alloc_array($Type, usz elements) @nodiscard
|
||||
{
|
||||
return allocator::alloc_array(allocator::heap(), $Type, elements);
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
macro alloc_array_aligned($Type, usz elements) @nodiscard
|
||||
{
|
||||
return allocator::alloc_array_aligned(allocator::heap(), $Type, elements);
|
||||
}
|
||||
|
||||
macro temp_alloc_array($Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)tmalloc($Type.sizeof * elements, $Type.alignof))[:elements];
|
||||
}
|
||||
|
||||
macro temp_new_array($Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)tcalloc($Type.sizeof * elements, $Type.alignof))[:elements];
|
||||
}
|
||||
|
||||
fn void* calloc(usz size) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::calloc(allocator::heap(), size);
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
fn void* calloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::calloc_aligned(allocator::heap(), size, alignment)!!;
|
||||
}
|
||||
|
||||
fn void* tcalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator::temp().acquire(size, ZERO, alignment)!!;
|
||||
}
|
||||
|
||||
fn void* realloc(void *ptr, usz new_size) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::realloc(allocator::heap(), ptr, new_size);
|
||||
}
|
||||
|
||||
fn void* realloc_aligned(void *ptr, usz new_size, usz alignment) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::realloc_aligned(allocator::heap(), ptr, new_size, alignment)!!;
|
||||
}
|
||||
|
||||
fn void free(void* ptr) @builtin @inline
|
||||
{
|
||||
return allocator::free(allocator::heap(), ptr);
|
||||
}
|
||||
|
||||
fn void free_aligned(void* ptr) @builtin @inline
|
||||
{
|
||||
return allocator::free_aligned(allocator::heap(), ptr);
|
||||
}
|
||||
|
||||
fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
if (!ptr) return tmalloc(size, alignment);
|
||||
return allocator::temp().resize(ptr, size, alignment)!!;
|
||||
}
|
||||
|
||||
module std::core::mem @if(env::NO_LIBC);
|
||||
|
||||
fn CInt __memcmp(void* s1, void* s2, usz n) @weak @export("memcmp")
|
||||
{
|
||||
char* p1 = s1;
|
||||
char* p2 = s2;
|
||||
for (usz i = 0; i < n; i++, p1++, p2++)
|
||||
{
|
||||
char c1 = *p1;
|
||||
char c2 = *p2;
|
||||
if (c1 < c2) return -1;
|
||||
if (c1 > c2) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn void* __memset(void* str, CInt c, usz n) @weak @export("memset")
|
||||
{
|
||||
char* p = str;
|
||||
char cc = (char)c;
|
||||
for (usz i = 0; i < n; i++, p++)
|
||||
{
|
||||
*p = cc;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
fn void* __memcpy(void* dst, void* src, usz n) @weak @export("memcpy")
|
||||
{
|
||||
char* d = dst;
|
||||
char* s = src;
|
||||
for (usz i = 0; i < n; i++, d++, s++)
|
||||
{
|
||||
*d = *s;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
464
lib7/std/core/mem_allocator.c3
Normal file
464
lib7/std/core/mem_allocator.c3
Normal file
@@ -0,0 +1,464 @@
|
||||
module std::core::mem::allocator;
|
||||
|
||||
const DEFAULT_SIZE_PREFIX = usz.sizeof;
|
||||
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
|
||||
|
||||
struct TrackingEnv
|
||||
{
|
||||
String file;
|
||||
String function;
|
||||
uint line;
|
||||
}
|
||||
|
||||
enum AllocInitType
|
||||
{
|
||||
NO_ZERO,
|
||||
ZERO
|
||||
}
|
||||
|
||||
interface Allocator
|
||||
{
|
||||
fn void reset(usz mark) @optional;
|
||||
fn usz mark() @optional;
|
||||
<*
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require size > 0
|
||||
*>
|
||||
fn void*! acquire(usz size, AllocInitType init_type, usz alignment = 0);
|
||||
<*
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require ptr != null
|
||||
@require new_size > 0
|
||||
*>
|
||||
fn void*! resize(void* ptr, usz new_size, usz alignment = 0);
|
||||
<*
|
||||
@require ptr != null
|
||||
*>
|
||||
fn void release(void* ptr, bool aligned);
|
||||
}
|
||||
|
||||
def MemoryAllocFn = fn char[]!(usz);
|
||||
|
||||
fault AllocationFailure
|
||||
{
|
||||
OUT_OF_MEMORY,
|
||||
CHUNK_TOO_LARGE,
|
||||
}
|
||||
|
||||
fn usz alignment_for_allocation(usz alignment) @inline @private
|
||||
{
|
||||
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
|
||||
}
|
||||
|
||||
macro void* malloc(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
return malloc_try(allocator, size)!!;
|
||||
}
|
||||
|
||||
macro void*! malloc_try(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
$if env::TESTING:
|
||||
char* data = allocator.acquire(size, NO_ZERO)!;
|
||||
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
$else
|
||||
return allocator.acquire(size, NO_ZERO);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void* calloc(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
return calloc_try(allocator, size)!!;
|
||||
}
|
||||
|
||||
macro void*! calloc_try(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator.acquire(size, ZERO);
|
||||
}
|
||||
|
||||
macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard
|
||||
{
|
||||
return realloc_try(allocator, ptr, new_size)!!;
|
||||
}
|
||||
|
||||
macro void*! realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
|
||||
{
|
||||
if (!new_size)
|
||||
{
|
||||
free(allocator, ptr);
|
||||
return null;
|
||||
}
|
||||
if (!ptr) return allocator.acquire(new_size, NO_ZERO);
|
||||
return allocator.resize(ptr, new_size);
|
||||
}
|
||||
|
||||
macro void free(Allocator allocator, void* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
$if env::TESTING:
|
||||
((char*)ptr)[0] = 0xBA;
|
||||
$endif
|
||||
allocator.release(ptr, false);
|
||||
}
|
||||
|
||||
macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
$if env::TESTING:
|
||||
char* data = allocator.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
$else
|
||||
return allocator.acquire(size, NO_ZERO, alignment);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void*! calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator.acquire(size, ZERO, alignment);
|
||||
}
|
||||
|
||||
macro void*! realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!new_size)
|
||||
{
|
||||
free_aligned(allocator, ptr);
|
||||
return null;
|
||||
}
|
||||
if (!ptr)
|
||||
{
|
||||
return malloc_aligned(allocator, new_size, alignment);
|
||||
}
|
||||
return allocator.resize(ptr, new_size, alignment);
|
||||
}
|
||||
|
||||
macro void free_aligned(Allocator allocator, void* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
$if env::TESTING:
|
||||
((char*)ptr)[0] = 0xBA;
|
||||
$endif
|
||||
allocator.release(ptr, aligned: true);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc(allocator, $Type.sizeof);
|
||||
$else
|
||||
$Type* val = malloc(allocator, $Type.sizeof);
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_try(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc_try(allocator, $Type.sizeof);
|
||||
$else
|
||||
$Type* val = malloc_try(allocator, $Type.sizeof)!;
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc_aligned(allocator, $Type.sizeof, $Type.alignof);
|
||||
$else
|
||||
$Type* val = malloc_aligned(allocator, $Type.sizeof, $Type.alignof)!;
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT
|
||||
*>
|
||||
macro new_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
|
||||
{
|
||||
return ($Type*)calloc_try(allocator, $Type.sizeof + padding);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
*>
|
||||
macro alloc(Allocator allocator, $Type) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc(allocator, $Type.sizeof);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
*>
|
||||
macro alloc_try(Allocator allocator, $Type) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc_try(allocator, $Type.sizeof);
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
macro alloc_aligned(Allocator allocator, $Type) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc_aligned(allocator, $Type.sizeof, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT
|
||||
*>
|
||||
macro alloc_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc_try(allocator, $Type.sizeof + padding);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
|
||||
*>
|
||||
macro new_array(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return new_array_try(allocator, $Type, elements)!!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
|
||||
*>
|
||||
macro new_array_try(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)calloc_try(allocator, $Type.sizeof * elements))[:elements];
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
macro new_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)calloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
|
||||
*>
|
||||
macro alloc_array(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return alloc_array_try(allocator, $Type, elements)!!;
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
macro alloc_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)malloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
|
||||
*>
|
||||
macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
|
||||
}
|
||||
|
||||
macro clone(Allocator allocator, value) @nodiscard
|
||||
{
|
||||
return new(allocator, $typeof(value), value);
|
||||
}
|
||||
|
||||
fn any clone_any(Allocator allocator, any value) @nodiscard
|
||||
{
|
||||
usz size = value.type.sizeof;
|
||||
void* data = malloc(allocator, size);
|
||||
mem::copy(data, value.ptr, size);
|
||||
return any_make(data, value.type);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require bytes > 0
|
||||
@require alignment > 0
|
||||
@require bytes <= isz.max
|
||||
*>
|
||||
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
|
||||
{
|
||||
if (alignment < void*.alignof) alignment = void*.alignof;
|
||||
usz header = AlignedBlock.sizeof + alignment;
|
||||
usz alignsize = bytes + header;
|
||||
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
|
||||
void* data = #alloc_fn(alignsize)!;
|
||||
$else
|
||||
void* data = #alloc_fn(alignsize);
|
||||
$endif
|
||||
void* mem = mem::aligned_pointer(data + AlignedBlock.sizeof, alignment);
|
||||
AlignedBlock* desc = (AlignedBlock*)mem - 1;
|
||||
assert(mem > data);
|
||||
*desc = { bytes, data };
|
||||
return mem;
|
||||
}
|
||||
|
||||
struct AlignedBlock
|
||||
{
|
||||
usz len;
|
||||
void* start;
|
||||
}
|
||||
|
||||
macro void! @aligned_free(#free_fn, void* old_pointer)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
|
||||
#free_fn(desc.start)!;
|
||||
$else
|
||||
#free_fn(desc.start);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require bytes > 0
|
||||
@require alignment > 0
|
||||
*>
|
||||
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
void* data_start = desc.start;
|
||||
void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!;
|
||||
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
|
||||
$if @typekind(#free_fn(data_start)) == OPTIONAL:
|
||||
#free_fn(data_start)!;
|
||||
$else
|
||||
#free_fn(data_start);
|
||||
$endif
|
||||
return new_data;
|
||||
}
|
||||
|
||||
|
||||
// All allocators
|
||||
|
||||
|
||||
def mem = thread_allocator @builtin;
|
||||
tlocal Allocator thread_allocator @private = base_allocator();
|
||||
Allocator temp_base_allocator @private = base_allocator();
|
||||
|
||||
tlocal TempAllocator* thread_temp_allocator @private = null;
|
||||
tlocal TempAllocator*[2] temp_allocator_pair @private;
|
||||
|
||||
macro Allocator base_allocator() @private
|
||||
{
|
||||
$if env::LIBC:
|
||||
return &allocator::LIBC_ALLOCATOR;
|
||||
$else
|
||||
return &allocator::NULL_ALLOCATOR;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro TempAllocator* create_default_sized_temp_allocator(Allocator allocator) @local
|
||||
{
|
||||
$switch (env::MEMORY_ENV)
|
||||
$case NORMAL:
|
||||
return new_temp_allocator(1024 * 256, allocator)!!;
|
||||
$case SMALL:
|
||||
return new_temp_allocator(1024 * 16, allocator)!!;
|
||||
$case TINY:
|
||||
return new_temp_allocator(1024 * 2, allocator)!!;
|
||||
$case NONE:
|
||||
unreachable("Temp allocator must explicitly created when memory-env is set to 'none'.");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro Allocator heap() => thread_allocator;
|
||||
|
||||
macro TempAllocator* temp()
|
||||
{
|
||||
if (!thread_temp_allocator)
|
||||
{
|
||||
init_default_temp_allocators();
|
||||
}
|
||||
return thread_temp_allocator;
|
||||
}
|
||||
|
||||
macro TempAllocator* tmem() @builtin
|
||||
{
|
||||
if (!thread_temp_allocator)
|
||||
{
|
||||
init_default_temp_allocators();
|
||||
}
|
||||
return thread_temp_allocator;
|
||||
}
|
||||
|
||||
fn void init_default_temp_allocators() @private
|
||||
{
|
||||
temp_allocator_pair[0] = create_default_sized_temp_allocator(temp_base_allocator);
|
||||
temp_allocator_pair[1] = create_default_sized_temp_allocator(temp_base_allocator);
|
||||
thread_temp_allocator = temp_allocator_pair[0];
|
||||
}
|
||||
|
||||
fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::LIBC)
|
||||
{
|
||||
destroy_temp_allocators();
|
||||
}
|
||||
|
||||
<*
|
||||
Call this to destroy any memory used by the temp allocators. This will invalidate all temp memory.
|
||||
*>
|
||||
fn void destroy_temp_allocators()
|
||||
{
|
||||
if (!thread_temp_allocator) return;
|
||||
temp_allocator_pair[0].destroy();
|
||||
temp_allocator_pair[1].destroy();
|
||||
temp_allocator_pair[..] = null;
|
||||
thread_temp_allocator = null;
|
||||
}
|
||||
|
||||
fn TempAllocator* temp_allocator_next() @private
|
||||
{
|
||||
if (!thread_temp_allocator)
|
||||
{
|
||||
init_default_temp_allocators();
|
||||
return thread_temp_allocator;
|
||||
}
|
||||
usz index = thread_temp_allocator == temp_allocator_pair[0] ? 1 : 0;
|
||||
return thread_temp_allocator = temp_allocator_pair[index];
|
||||
}
|
||||
|
||||
const NullAllocator NULL_ALLOCATOR = {};
|
||||
distinct NullAllocator (Allocator) = uptr;
|
||||
|
||||
fn void*! NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
return AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
fn void*! NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
return AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
{
|
||||
}
|
||||
|
||||
32
lib7/std/core/os/wasm_memory.c3
Normal file
32
lib7/std/core/os/wasm_memory.c3
Normal file
@@ -0,0 +1,32 @@
|
||||
module std::core::mem::allocator;
|
||||
|
||||
|
||||
const usz WASM_BLOCK_SIZE = 65536;
|
||||
|
||||
WasmMemory wasm_memory;
|
||||
|
||||
struct WasmMemory
|
||||
{
|
||||
usz allocation;
|
||||
uptr use;
|
||||
}
|
||||
|
||||
fn char[]! WasmMemory.allocate_block(&self, usz bytes)
|
||||
{
|
||||
if (!self.allocation)
|
||||
{
|
||||
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
}
|
||||
isz bytes_required = bytes + self.use - self.allocation;
|
||||
if (bytes_required <= 0)
|
||||
{
|
||||
defer self.use += bytes;
|
||||
return ((char*)self.use)[:bytes];
|
||||
}
|
||||
|
||||
usz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE;
|
||||
if ($$wasm_memory_grow(0, blocks_required) == -1) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
defer self.use += bytes;
|
||||
return ((char*)self.use)[:bytes];
|
||||
}
|
||||
268
lib7/std/core/private/cpu_detect.c3
Normal file
268
lib7/std/core/private/cpu_detect.c3
Normal file
@@ -0,0 +1,268 @@
|
||||
module std::core::cpudetect @if(env::X86 || env::X86_64);
|
||||
|
||||
struct CpuId
|
||||
{
|
||||
uint eax, ebx, ecx, edx;
|
||||
}
|
||||
fn CpuId x86_cpuid(uint eax, uint ecx = 0)
|
||||
{
|
||||
int edx;
|
||||
int ebx;
|
||||
asm
|
||||
{
|
||||
movl $eax, eax;
|
||||
movl $ecx, ecx;
|
||||
cpuid;
|
||||
movl eax, $eax;
|
||||
movl ebx, $ebx;
|
||||
movl ecx, $ecx;
|
||||
movl edx, $edx;
|
||||
}
|
||||
return { eax, ebx, ecx, edx };
|
||||
}
|
||||
|
||||
enum X86Feature
|
||||
{
|
||||
ADX,
|
||||
AES,
|
||||
AMX_AVX512,
|
||||
AMX_FP8,
|
||||
AMX_MOVRS,
|
||||
AMX_TF32,
|
||||
AMX_TRANSPOSE,
|
||||
AMX_BF16,
|
||||
AMX_COMPLEX,
|
||||
AMX_FP16,
|
||||
AMX_INT8,
|
||||
AMX_TILE,
|
||||
APXF,
|
||||
AVX,
|
||||
AVX10_1_256,
|
||||
AVX10_1_512,
|
||||
AVX10_2_256,
|
||||
AVX10_2_512,
|
||||
AVX2,
|
||||
AVX5124FMAPS,
|
||||
AVX5124VNNIW,
|
||||
AVX512BF16,
|
||||
AVX512BITALG,
|
||||
AVX512BW,
|
||||
AVX512CD,
|
||||
AVX512DQ,
|
||||
AVX512ER,
|
||||
AVX512F,
|
||||
AVX512FP16,
|
||||
AVX512IFMA,
|
||||
AVX512PF,
|
||||
AVX512VBMI,
|
||||
AVX512VBMI2,
|
||||
AVX512VL,
|
||||
AVX512VNNI,
|
||||
AVX512VP2INTERSECT,
|
||||
AVX512VPOPCNTDQ,
|
||||
AVXIFMA,
|
||||
AVXNECONVERT,
|
||||
AVXVNNI,
|
||||
AVXVNNIINT16,
|
||||
AVXVNNIINT8,
|
||||
BMI,
|
||||
BMI2,
|
||||
CLDEMOTE,
|
||||
CLFLUSHOPT,
|
||||
CLWB,
|
||||
CLZERO,
|
||||
CMOV,
|
||||
CMPCCXADD,
|
||||
CMPXCHG16B,
|
||||
CX8,
|
||||
ENQCMD,
|
||||
F16C,
|
||||
FMA,
|
||||
FMA4,
|
||||
FSGSBASE,
|
||||
FXSR,
|
||||
GFNI,
|
||||
HRESET,
|
||||
INVPCID,
|
||||
KL,
|
||||
LWP,
|
||||
LZCNT,
|
||||
MMX,
|
||||
MOVBE,
|
||||
MOVDIR64B,
|
||||
MOVDIRI,
|
||||
MOVRS,
|
||||
MWAITX,
|
||||
PCLMUL,
|
||||
PCONFIG,
|
||||
PKU,
|
||||
POPCNT,
|
||||
PREFETCHI,
|
||||
PREFETCHWT1,
|
||||
PRFCHW,
|
||||
PTWRITE,
|
||||
RAOINT,
|
||||
RDPID,
|
||||
RDPRU,
|
||||
RDRND,
|
||||
RDSEED,
|
||||
RTM,
|
||||
SAHF,
|
||||
SERIALIZE,
|
||||
SGX,
|
||||
SHA,
|
||||
SHA512,
|
||||
SHSTK,
|
||||
SM3,
|
||||
SM4,
|
||||
SSE,
|
||||
SSE2,
|
||||
SSE3,
|
||||
SSE4_1,
|
||||
SSE4_2,
|
||||
SSE4_A,
|
||||
SSSE3,
|
||||
TBM,
|
||||
TSXLDTRK,
|
||||
UINTR,
|
||||
USERMSR,
|
||||
VAES,
|
||||
VPCLMULQDQ,
|
||||
WAITPKG,
|
||||
WBNOINVD,
|
||||
WIDEKL,
|
||||
X87,
|
||||
XOP,
|
||||
XSAVE,
|
||||
XSAVEC,
|
||||
XSAVEOPT,
|
||||
XSAVES,
|
||||
}
|
||||
|
||||
uint128 x86_features;
|
||||
|
||||
fn void add_feature_if_bit(X86Feature feature, uint register, int bit)
|
||||
{
|
||||
if (register & 1U << bit) x86_features |= 1u128 << feature.ordinal;
|
||||
}
|
||||
|
||||
fn void x86_initialize_cpu_features()
|
||||
{
|
||||
uint max_level = x86_cpuid(0).eax;
|
||||
CpuId feat = x86_cpuid(1);
|
||||
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : {};
|
||||
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : {};
|
||||
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : {};
|
||||
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : {};
|
||||
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : {};
|
||||
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : {};
|
||||
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : {};
|
||||
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : {};
|
||||
add_feature_if_bit(ADX, leaf7.ebx, 19);
|
||||
add_feature_if_bit(AES, feat.ecx, 25);
|
||||
add_feature_if_bit(AMX_BF16, leaf7.edx, 22);
|
||||
add_feature_if_bit(AMX_COMPLEX, leaf7s1.edx, 8);
|
||||
add_feature_if_bit(AMX_FP16, leaf7s1.eax, 21);
|
||||
add_feature_if_bit(AMX_INT8, leaf7.edx, 25);
|
||||
add_feature_if_bit(AMX_TILE, leaf7.edx, 24);
|
||||
add_feature_if_bit(APXF, leaf7s1.edx, 21);
|
||||
add_feature_if_bit(AVX, feat.ecx, 28);
|
||||
add_feature_if_bit(AVX10_1_256, leaf7s1.edx, 19);
|
||||
add_feature_if_bit(AVX10_1_512, leaf_24.ebx, 18);
|
||||
add_feature_if_bit(AVX2, leaf7.ebx, 5);
|
||||
add_feature_if_bit(AVX5124FMAPS, leaf7.edx, 3);
|
||||
add_feature_if_bit(AVX5124VNNIW, leaf7.edx, 2);
|
||||
add_feature_if_bit(AVX512BF16, leaf7s1.eax, 5);
|
||||
add_feature_if_bit(AVX512BITALG, leaf7.ecx, 12);
|
||||
add_feature_if_bit(AVX512BW, leaf7.ebx, 30);
|
||||
add_feature_if_bit(AVX512CD, leaf7.ebx, 28);
|
||||
add_feature_if_bit(AVX512DQ, leaf7.ebx, 17);
|
||||
add_feature_if_bit(AVX512ER, leaf7.ebx, 27);
|
||||
add_feature_if_bit(AVX512F, leaf7.ebx, 16);
|
||||
add_feature_if_bit(AVX512FP16, leaf7.edx, 23);
|
||||
add_feature_if_bit(AVX512IFMA, leaf7.ebx, 21);
|
||||
add_feature_if_bit(AVX512PF, leaf7.ebx, 26);
|
||||
add_feature_if_bit(AVX512VBMI, leaf7.ecx, 1);
|
||||
add_feature_if_bit(AVX512VBMI2, leaf7.ecx, 6);
|
||||
add_feature_if_bit(AVX512VL, leaf7.ebx, 31);
|
||||
add_feature_if_bit(AVX512VNNI, leaf7.ecx, 11);
|
||||
add_feature_if_bit(AVX512VP2INTERSECT, leaf7.edx, 8);
|
||||
add_feature_if_bit(AVX512VPOPCNTDQ, leaf7.ecx, 14);
|
||||
add_feature_if_bit(AVXIFMA, leaf7s1.eax, 23);
|
||||
add_feature_if_bit(AVXNECONVERT, leaf7s1.edx, 5);
|
||||
add_feature_if_bit(AVXVNNI, leaf7s1.eax, 4);
|
||||
add_feature_if_bit(AVXVNNIINT16, leaf7s1.edx, 10);
|
||||
add_feature_if_bit(AVXVNNIINT8, leaf7s1.edx, 4);
|
||||
add_feature_if_bit(BMI, leaf7.ebx, 3);
|
||||
add_feature_if_bit(BMI2, leaf7.ebx, 8);
|
||||
add_feature_if_bit(CLDEMOTE, leaf7.ecx, 25);
|
||||
add_feature_if_bit(CLFLUSHOPT, leaf7.ebx, 23);
|
||||
add_feature_if_bit(CLWB, leaf7.ebx, 24);
|
||||
add_feature_if_bit(CLZERO, ext8.ecx, 0);
|
||||
add_feature_if_bit(CMOV, feat.edx, 15);
|
||||
add_feature_if_bit(CMPCCXADD, leaf7s1.eax, 7);
|
||||
add_feature_if_bit(CMPXCHG16B, feat.ecx, 12);
|
||||
add_feature_if_bit(CX8, feat.edx, 8);
|
||||
add_feature_if_bit(ENQCMD, leaf7.ecx, 29);
|
||||
add_feature_if_bit(F16C, feat.ecx, 29);
|
||||
add_feature_if_bit(FMA, feat.ecx, 12);
|
||||
add_feature_if_bit(FMA4, ext1.ecx, 16);
|
||||
add_feature_if_bit(FSGSBASE, leaf7.ebx, 0);
|
||||
add_feature_if_bit(FXSR, feat.edx, 24);
|
||||
add_feature_if_bit(GFNI, leaf7.ecx, 8);
|
||||
add_feature_if_bit(HRESET, leaf7s1.eax, 22);
|
||||
add_feature_if_bit(INVPCID, leaf7.ebx, 10);
|
||||
add_feature_if_bit(KL, leaf7.ecx, 23);
|
||||
add_feature_if_bit(LWP, ext1.ecx, 15);
|
||||
add_feature_if_bit(LZCNT, ext1.ecx, 5);
|
||||
add_feature_if_bit(MMX, feat.edx, 23);
|
||||
add_feature_if_bit(MOVBE, feat.ecx, 22);
|
||||
add_feature_if_bit(MOVDIR64B, leaf7.ecx, 28);
|
||||
add_feature_if_bit(MOVDIRI, leaf7.ecx, 27);
|
||||
add_feature_if_bit(MWAITX, ext1.ecx, 29);
|
||||
add_feature_if_bit(PCLMUL, feat.ecx, 1);
|
||||
add_feature_if_bit(PCONFIG, leaf7.edx, 18);
|
||||
add_feature_if_bit(PKU, leaf7.ecx, 4);
|
||||
add_feature_if_bit(POPCNT, feat.ecx, 23);
|
||||
add_feature_if_bit(PREFETCHI, leaf7s1.edx, 14);
|
||||
add_feature_if_bit(PREFETCHWT1, leaf7.ecx, 0);
|
||||
add_feature_if_bit(PRFCHW, ext1.ecx, 8);
|
||||
add_feature_if_bit(PTWRITE, leaf_14.ebx, 4);
|
||||
add_feature_if_bit(RAOINT, leaf7s1.eax, 3);
|
||||
add_feature_if_bit(RDPID, leaf7.ecx, 22);
|
||||
add_feature_if_bit(RDPRU, ext8.ecx, 4);
|
||||
add_feature_if_bit(RDRND, feat.ecx, 30);
|
||||
add_feature_if_bit(RDSEED, leaf7.ebx, 18);
|
||||
add_feature_if_bit(RTM, leaf7.ebx, 11);
|
||||
add_feature_if_bit(SAHF, ext1.ecx, 0);
|
||||
add_feature_if_bit(SERIALIZE, leaf7.edx, 14);
|
||||
add_feature_if_bit(SGX, leaf7.ebx, 2);
|
||||
add_feature_if_bit(SHA, leaf7.ebx, 29);
|
||||
add_feature_if_bit(SHA512, leaf7s1.eax, 0);
|
||||
add_feature_if_bit(SHSTK, leaf7.ecx, 7);
|
||||
add_feature_if_bit(SM3, leaf7s1.eax, 1);
|
||||
add_feature_if_bit(SM4, leaf7s1.eax, 2);
|
||||
add_feature_if_bit(SSE, feat.edx, 25);
|
||||
add_feature_if_bit(SSE2, feat.edx, 26);
|
||||
add_feature_if_bit(SSE3, feat.ecx, 0);
|
||||
add_feature_if_bit(SSE4_1, feat.ecx, 19);
|
||||
add_feature_if_bit(SSE4_2, feat.ecx, 20);
|
||||
add_feature_if_bit(SSE4_A, ext1.ecx, 6);
|
||||
add_feature_if_bit(SSSE3, feat.ecx, 9);
|
||||
add_feature_if_bit(TBM, ext1.ecx, 21);
|
||||
add_feature_if_bit(TSXLDTRK, leaf7.edx, 16);
|
||||
add_feature_if_bit(UINTR, leaf7.edx, 5);
|
||||
add_feature_if_bit(USERMSR, leaf7s1.edx, 15);
|
||||
add_feature_if_bit(VAES, leaf7.ecx, 9);
|
||||
add_feature_if_bit(VPCLMULQDQ, leaf7.ecx, 10);
|
||||
add_feature_if_bit(WAITPKG, leaf7.ecx, 5);
|
||||
add_feature_if_bit(WBNOINVD, ext8.ecx, 9);
|
||||
add_feature_if_bit(WIDEKL, leaf_19.ebx, 2);
|
||||
add_feature_if_bit(X87, feat.edx, 0);
|
||||
add_feature_if_bit(XOP, ext1.ecx, 11);
|
||||
add_feature_if_bit(XSAVE, feat.ecx, 26);
|
||||
add_feature_if_bit(XSAVEC, leaf_d.eax, 1);
|
||||
add_feature_if_bit(XSAVEOPT, leaf_d.eax, 0);
|
||||
add_feature_if_bit(XSAVES, leaf_d.eax, 3);
|
||||
|
||||
}
|
||||
254
lib7/std/core/private/macho_runtime.c3
Normal file
254
lib7/std/core/private/macho_runtime.c3
Normal file
@@ -0,0 +1,254 @@
|
||||
module std::core::machoruntime @if(env::DARWIN) @private;
|
||||
|
||||
struct SegmentCommand64
|
||||
{
|
||||
uint cmd;
|
||||
uint cmdsize;
|
||||
char[16] segname;
|
||||
ulong vmaddr;
|
||||
ulong vmsize;
|
||||
ulong fileoff;
|
||||
ulong filesize;
|
||||
uint maxprot;
|
||||
uint initprot;
|
||||
uint nsects;
|
||||
uint flags;
|
||||
}
|
||||
|
||||
struct LoadCommand
|
||||
{
|
||||
uint cmd;
|
||||
uint cmdsize;
|
||||
}
|
||||
|
||||
struct Section64
|
||||
{
|
||||
char[16] sectname;
|
||||
char[16] segname;
|
||||
ulong addr;
|
||||
ulong size;
|
||||
uint offset;
|
||||
uint align;
|
||||
uint reloff;
|
||||
uint nreloc;
|
||||
uint flags;
|
||||
uint reserved1;
|
||||
uint reserved2;
|
||||
uint reserved3;
|
||||
}
|
||||
|
||||
struct MachHeader
|
||||
{
|
||||
uint magic;
|
||||
uint cputype;
|
||||
uint cpusubtype;
|
||||
uint filetype;
|
||||
uint ncmds;
|
||||
uint sizeofcmds;
|
||||
uint flags;
|
||||
}
|
||||
|
||||
struct MachHeader64
|
||||
{
|
||||
inline MachHeader header;
|
||||
uint reserved;
|
||||
}
|
||||
|
||||
const LC_SEGMENT_64 = 0x19;
|
||||
|
||||
fault MachoSearch
|
||||
{
|
||||
NOT_FOUND
|
||||
}
|
||||
fn bool name_cmp(char* a, char[16]* b)
|
||||
{
|
||||
for (usz i = 0; i < 16; i++)
|
||||
{
|
||||
if (a[i] != (*b)[i]) return false;
|
||||
if (a[i] == '\0') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn SegmentCommand64*! find_segment(MachHeader* header, char* segname)
|
||||
{
|
||||
LoadCommand* command = (void*)header + MachHeader64.sizeof;
|
||||
for (uint i = 0; i < header.ncmds; i++)
|
||||
{
|
||||
if (command.cmd == LC_SEGMENT_64)
|
||||
{
|
||||
SegmentCommand64* segment = (SegmentCommand64*)command;
|
||||
if (name_cmp(segname, &segment.segname)) return segment;
|
||||
}
|
||||
command = (void*)command + command.cmdsize;
|
||||
}
|
||||
return MachoSearch.NOT_FOUND?;
|
||||
}
|
||||
fn Section64*! find_section(SegmentCommand64* command, char* sectname)
|
||||
{
|
||||
Section64* section = (void*)command + SegmentCommand64.sizeof;
|
||||
for (uint i = 0; i < command.nsects; i++)
|
||||
{
|
||||
if (name_cmp(sectname, §ion.sectname)) return section;
|
||||
section++;
|
||||
}
|
||||
return MachoSearch.NOT_FOUND?;
|
||||
}
|
||||
|
||||
macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type)
|
||||
{
|
||||
|
||||
Section64*! section = find_section(find_segment(header, segname), sectname);
|
||||
if (catch section)
|
||||
{
|
||||
return ($Type[]){};
|
||||
}
|
||||
$Type* ptr = (void*)header + section.offset;
|
||||
return ptr[:section.size / $Type.sizeof];
|
||||
}
|
||||
|
||||
def DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide);
|
||||
|
||||
extern fn void _dyld_register_func_for_add_image(DyldCallback);
|
||||
|
||||
|
||||
struct DlInfo
|
||||
{
|
||||
char* dli_fname;
|
||||
void* dli_fbase;
|
||||
char* dli_sname;
|
||||
void* dli_saddr;
|
||||
}
|
||||
|
||||
extern fn void printf(char*, ...);
|
||||
extern fn int dladdr(MachHeader* mh, DlInfo* dlinfo);
|
||||
extern fn void* realloc(void* ptr, usz size);
|
||||
extern fn void* malloc(usz size);
|
||||
extern fn void free(void* ptr);
|
||||
|
||||
def CallbackFn = fn void();
|
||||
struct Callback
|
||||
{
|
||||
uint priority;
|
||||
CallbackFn xtor;
|
||||
Callback* next;
|
||||
}
|
||||
struct DynamicMethod
|
||||
{
|
||||
void* fn_ptr;
|
||||
char* sel;
|
||||
union
|
||||
{
|
||||
DynamicMethod* next;
|
||||
TypeId* type;
|
||||
}
|
||||
}
|
||||
|
||||
enum StartupState
|
||||
{
|
||||
NOT_STARTED,
|
||||
INIT,
|
||||
RUN_CTORS,
|
||||
READ_DYLIB,
|
||||
RUN_DYLIB_CTORS,
|
||||
RUN_DTORS,
|
||||
SHUTDOWN
|
||||
}
|
||||
|
||||
StartupState runtime_state = NOT_STARTED;
|
||||
|
||||
Callback* ctor_first;
|
||||
Callback* dtor_first;
|
||||
|
||||
fn void runtime_startup() @public @export("__c3_runtime_startup")
|
||||
{
|
||||
if (runtime_state != NOT_STARTED) return;
|
||||
runtime_state = INIT;
|
||||
_dyld_register_func_for_add_image(&dl_reg_callback);
|
||||
assert(runtime_state == INIT);
|
||||
runtime_state = RUN_CTORS;
|
||||
Callback* ctor = ctor_first;
|
||||
while (ctor)
|
||||
{
|
||||
ctor.xtor();
|
||||
ctor = ctor.next;
|
||||
}
|
||||
assert(runtime_state == RUN_CTORS);
|
||||
runtime_state = READ_DYLIB;
|
||||
ctor_first = null;
|
||||
}
|
||||
|
||||
fn void runtime_finalize() @public @export("__c3_runtime_finalize")
|
||||
{
|
||||
if (runtime_state != READ_DYLIB) return;
|
||||
runtime_state = RUN_DTORS;
|
||||
Callback* dtor = dtor_first;
|
||||
while (dtor)
|
||||
{
|
||||
dtor.xtor();
|
||||
dtor = dtor.next;
|
||||
}
|
||||
assert(runtime_state == RUN_DTORS);
|
||||
runtime_state = SHUTDOWN;
|
||||
}
|
||||
|
||||
fn void append_xxlizer(Callback** ref, Callback* cb)
|
||||
{
|
||||
while (Callback* current = *ref, current)
|
||||
{
|
||||
if (current.priority > cb.priority)
|
||||
{
|
||||
cb.next = current;
|
||||
break;
|
||||
}
|
||||
ref = ¤t.next;
|
||||
}
|
||||
*ref = cb;
|
||||
}
|
||||
|
||||
struct TypeId
|
||||
{
|
||||
char type;
|
||||
TypeId* parentof;
|
||||
DynamicMethod* dtable;
|
||||
usz sizeof;
|
||||
TypeId* inner;
|
||||
usz len;
|
||||
typeid[?] additional;
|
||||
}
|
||||
|
||||
fn void dl_reg_callback(MachHeader* mh, isz vmaddr_slide)
|
||||
{
|
||||
usz size = 0;
|
||||
assert(runtime_state == INIT || runtime_state == READ_DYLIB, "State was %s", runtime_state);
|
||||
foreach (&dm : find_segment_section_body(mh, "__DATA", "__c3_dynamic", DynamicMethod))
|
||||
{
|
||||
TypeId* type = dm.type;
|
||||
dm.next = type.dtable;
|
||||
type.dtable = dm;
|
||||
DynamicMethod* m = dm;
|
||||
while (m)
|
||||
{
|
||||
m = m.next;
|
||||
}
|
||||
}
|
||||
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3dtor", Callback))
|
||||
{
|
||||
append_xxlizer(&dtor_first, cb);
|
||||
}
|
||||
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3ctor", Callback))
|
||||
{
|
||||
append_xxlizer(&ctor_first, cb);
|
||||
}
|
||||
if (runtime_state != READ_DYLIB) return;
|
||||
runtime_state = RUN_DYLIB_CTORS;
|
||||
Callback* ctor = ctor_first;
|
||||
ctor_first = null;
|
||||
while (ctor)
|
||||
{
|
||||
ctor.xtor();
|
||||
ctor = ctor.next;
|
||||
}
|
||||
assert(runtime_state == RUN_DYLIB_CTORS);
|
||||
runtime_state = READ_DYLIB;
|
||||
}
|
||||
180
lib7/std/core/private/main_stub.c3
Normal file
180
lib7/std/core/private/main_stub.c3
Normal file
@@ -0,0 +1,180 @@
|
||||
module std::core::main_stub;
|
||||
|
||||
macro usz _strlen(ptr) @private
|
||||
{
|
||||
usz len = 0;
|
||||
while (ptr[len]) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
macro int @main_to_err_main(#m, int, char**)
|
||||
{
|
||||
if (catch #m()) return 1;
|
||||
return 0;
|
||||
}
|
||||
macro int @main_to_int_main(#m, int, char**) => #m();
|
||||
macro int @main_to_void_main(#m, int, char**)
|
||||
{
|
||||
#m();
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro String[] args_to_strings(int argc, char** argv) @private
|
||||
{
|
||||
String[] list = mem::alloc_array(String, argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
char* arg = argv[i];
|
||||
usz len = 0;
|
||||
list[i] = (String)arg[:_strlen(arg)];
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
macro int @main_to_err_main_args(#m, int argc, char** argv)
|
||||
{
|
||||
String[] list = args_to_strings(argc, argv);
|
||||
defer free(list.ptr);
|
||||
if (catch #m(list)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @main_to_int_main_args(#m, int argc, char** argv)
|
||||
{
|
||||
String[] list = args_to_strings(argc, argv);
|
||||
defer free(list.ptr);
|
||||
return #m(list);
|
||||
}
|
||||
|
||||
macro int @_main_runner(#m, int argc, char** argv)
|
||||
{
|
||||
String[] list = args_to_strings(argc, argv);
|
||||
defer free(list.ptr);
|
||||
return #m(list) ? 0 : 1;
|
||||
}
|
||||
|
||||
macro int @main_to_void_main_args(#m, int argc, char** argv)
|
||||
{
|
||||
String[] list = args_to_strings(argc, argv);
|
||||
defer free(list.ptr);
|
||||
#m(list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
module std::core::main_stub @if(env::WIN32);
|
||||
|
||||
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @extern("CommandLineToArgvW");
|
||||
|
||||
macro String[] win_command_line_to_strings(ushort* cmd_line) @private
|
||||
{
|
||||
int argc;
|
||||
Char16** argv = _win_command_line_to_argv_w(cmd_line, &argc);
|
||||
return wargs_strings(argc, argv);
|
||||
}
|
||||
|
||||
macro String[] wargs_strings(int argc, Char16** argv) @private
|
||||
{
|
||||
String[] list = mem::alloc_array(String, argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
Char16* arg = argv[i];
|
||||
Char16[] argstring = arg[:_strlen(arg)];
|
||||
list[i] = string::new_from_utf16(mem, argstring) ?? "?".copy(mem);
|
||||
}
|
||||
return list[:argc];
|
||||
}
|
||||
|
||||
macro void release_wargs(String[] list) @private
|
||||
{
|
||||
foreach (s : list) free(s.ptr);
|
||||
free(list.ptr);
|
||||
}
|
||||
|
||||
macro int @win_to_err_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
if (catch #m()) return 1;
|
||||
return 0;
|
||||
}
|
||||
macro int @win_to_int_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd) => #m();
|
||||
macro int @win_to_void_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
#m();
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(args)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @win_to_int_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
return #m(args);
|
||||
}
|
||||
|
||||
macro int @win_to_void_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
#m(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @win_to_err_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(handle, prev_handle, args, show_cmd)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @win_to_int_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
return #m(handle, prev_handle, args, show_cmd);
|
||||
}
|
||||
|
||||
macro int @win_to_void_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
#m(handle, prev_handle, args, show_cmd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @wmain_to_err_main_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(args)) return 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
return #m(args);
|
||||
}
|
||||
|
||||
macro int @_wmain_runner(#m, int argc, Char16** argv)
|
||||
{
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
return #m(args) ? 0 : 1;
|
||||
}
|
||||
|
||||
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
#m(args);
|
||||
return 0;
|
||||
}
|
||||
33
lib7/std/core/runtime.c3
Normal file
33
lib7/std/core/runtime.c3
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::runtime;
|
||||
import libc, std::time, std::io, std::sort;
|
||||
|
||||
struct ReflectedParam (Printable) @if(!$defined(ReflectedParam))
|
||||
{
|
||||
String name;
|
||||
typeid type;
|
||||
}
|
||||
|
||||
struct AnyRaw
|
||||
{
|
||||
void* ptr;
|
||||
typeid type;
|
||||
}
|
||||
|
||||
struct SliceRaw
|
||||
{
|
||||
void* ptr;
|
||||
usz len;
|
||||
}
|
||||
|
||||
|
||||
module std::core::runtime @if(WASM_NOLIBC);
|
||||
|
||||
extern fn void __wasm_call_ctors();
|
||||
fn void wasm_initialize() @extern("_initialize") @wasm
|
||||
{
|
||||
// The linker synthesizes this to call constructors.
|
||||
__wasm_call_ctors();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user