Compare commits

..

1 Commits

Author SHA1 Message Date
Christoffer Lerno
3348f4dc7d Test of overloading. 2022-09-15 22:31:31 +02:00
2144 changed files with 93257 additions and 170311 deletions

View File

@@ -1,25 +0,0 @@
# EditorConfig is awesome: https://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
[CMakeLists.txt]
indent_style = space
indent_size = 4
[*.{c,cc,h}]
indent_style = tab
[*.{c3}]
indent_style = tab
[*.{json,toml,yml,gyp}]
indent_style = space
indent_size = 2
[*.{py,pyi}]
indent_style = tab

2
.gitattributes vendored
View File

@@ -1,2 +1,4 @@
$ cat .gitattributes
* text=auto
*.c3 linguist-language=C
*.c3t linguist-language=C

14
.github/FUNDING.yml vendored
View File

@@ -1,14 +0,0 @@
# These are supported funding model platforms
github: [c3lang]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: c3lang
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -2,16 +2,13 @@ name: CI
on:
push:
branches: [ master, dev, ci_testing, experiments ]
branches: [ master, dev, ci_testing ]
pull_request:
branches: [ master, dev ]
branches: [ master ]
env:
LLVM_RELEASE_VERSION_WINDOWS: 18
LLVM_RELEASE_VERSION_MAC: 17
LLVM_RELEASE_VERSION_LINUX: 17
LLVM_RELEASE_VERSION_UBUNTU22: 17
LLVM_DEV_VERSION: 21
LLVM_RELEASE_VERSION: 14
jobs:
build-msvc:
@@ -26,7 +23,7 @@ jobs:
run:
shell: cmd
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: CMake
run: |
@@ -36,81 +33,83 @@ jobs:
- name: Compile and run some examples
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe compile-run -L C:\ --print-linking examples\hello_world_many.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\time.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\fannkuch-redux.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\hello_world_many.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\fannkuch-redux.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\contextfree\boolerr.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\ls.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\load_world.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\process.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\args.c3 -- foo -bar "baz baz"
..\build\${{ matrix.build_type }}\c3c.exe compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run msvc_stack.c3
- name: Build testproject
run: |
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\windows-x64
..\..\build\${{ matrix.build_type }}\c3c.exe clean
dir build\llvm\windows-x64
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log run hello_world_win32
- name: Build testproject lib
run: |
cd resources/testproject
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv build hello_world_win32_lib --trust=full
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv dynamic-lib add.c3
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv compile-run test.c3 -l ./add.lib
- name: Compile and run staticlib-test
run: |
cd resources/examples/staticlib-test
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv static-lib add.c3
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv compile-run test.c3 -l ./add.lib
- name: Vendor-fetch
run: |
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55
- name: Try raylib5
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55 --print-linking examples\raylib\raylib_arkanoid.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55 --print-linking examples\raylib\raylib_snake.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55 --print-linking examples\raylib\raylib_tetris.c3
- name: Compile run unit tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log build hello_world_win32_lib
- name: run compiler tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ..\build\${{ matrix.build_type }}\c3c.exe test_suite/ --no-terminal
- name: Test python script
run: |
py msvc_build_libraries.py --accept-license
dir msvc_sdk
python3.exe src/tester.py ..\build\${{ matrix.build_type }}\c3c.exe test_suite/
- name: upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: c3-windows-${{ matrix.build_type }}
path: |
build\${{ matrix.build_type }}\c3c.exe
build\${{ matrix.build_type }}\c3c_rt
path: build\${{ matrix.build_type }}\c3c.exe
build-msys2-mingw:
runs-on: windows-latest
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [Release, Debug]
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v3
- uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
install: git binutils mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
- shell: msys2 {0}
run: |
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-13.0.1-2-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-13.0.1-2-any.pkg.tar.zst
- name: CMake
run: |
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run --debug-log
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib --debug-log
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c.exe test_suite/
build-msys2-clang:
runs-on: windows-latest
if: ${{ false }}
strategy:
@@ -123,68 +122,7 @@ jobs:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
install: git binutils mingw-w64-x86_64-clang mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
- shell: msys2 {0}
run: |
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-20.1.0-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-20.1.0-1-any.pkg.tar.zst
- name: CMake
run: |
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_LINKER=lld
cmake --build build
- name: Compile and run some examples
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
../build/c3c compile-run --print-linking examples/load_world.c3
../build/c3c compile-run --print-linking examples/args.c3 -- foo -bar "baz baz"
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib55
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib --cc cc -vvv --trust=full
- name: run compiler tests
run: |
cd test
../build/c3c.exe compile --target windows-x64 -O1 src/test_suite_runner.c3
./test_suite_runner.exe ../build/c3c.exe test_suite/ --no-terminal
build-msys2-clang:
runs-on: windows-latest
#if: ${{ false }}
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [Release, Debug]
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: msys2/setup-msys2@v2
with:
@@ -201,234 +139,55 @@ jobs:
run: |
cd resources
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run --debug-log
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib -vvv --trust=full
../../build/c3c build hello_world_lib --debug-log
- name: run compiler tests
run: |
cd test
../build/c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c.exe test_suite/ --no-terminal
python3 src/tester.py ../build/c3c.exe test_suite/
build-linux:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20]
llvm_version: [12, 13, 14, 15, 16]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Install common deps
run: |
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build
- name: Install Clang ${{matrix.llvm_version}}
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
if [[ "${{matrix.llvm_version}}" < 18 ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
sudo apt-get update
sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
else
if [[ "${{matrix.llvm_version}}" < "${{env.LLVM_DEV_VERSION}}" ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
sudo apt-get update
sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
sudo apt-get install -y -t llvm-toolchain-focal libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
fi
fi
- name: CMake
if: matrix.llvm_version < 18 || matrix.llvm_version == env.LLVM_DEV_VERSION
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: CMake18
if: matrix.llvm_version >= 18 && matrix.llvm_version != env.LLVM_DEV_VERSION
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile examples/base64.c3
../build/c3c compile examples/binarydigits.c3
../build/c3c compile examples/brainfk.c3
../build/c3c compile examples/factorial_macro.c3
../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile examples/load_world.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
../../../build/c3c -vv dynamic-lib add.c3
mv add.so libadd.so
cc test.c -L. -ladd -Wl,-rpath=.
./a.out
../../../build/c3c compile-run test.c3 -L . -l add -z -Wl,-rpath=.
- name: Compile and run staticlib-test
run: |
cd resources/examples/staticlib-test
../../../build/c3c -vv static-lib add.c3
mv add.a libadd.a
cc test.c -L. -ladd
./a.out
../../../build/c3c compile-run test.c3 -L . -l add
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
- name: Test WASM
run: |
cd resources/testfragments
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
- name: Install QEMU and Risc-V toolchain
run: |
sudo apt-get install opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf
- name: Compile and run Baremetal Risc-V Example
run: |
cd resources/examples/embedded/riscv-qemu
make C3C_PATH=../../../../build/ run
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
- name: Init a library & a project
run: |
./build/c3c init-lib mylib
ls mylib.c3l
./build/c3c init myproject
ls myproject
- name: run compiler tests
run: |
cd test
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
run: |
mkdir c3
cp -r lib c3
cp msvc_build_libraries.py c3
cp build/c3c c3
cp README.md c3
cp releasenotes.md c3
tar czf c3-linux-${{matrix.build_type}}.tar.gz c3
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
uses: actions/upload-artifact@v4
with:
name: c3-linux-${{matrix.build_type}}
path: c3-linux-${{matrix.build_type}}.tar.gz
build-linux-ubuntu22:
runs-on: ubuntu-22.04
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20]
steps:
- uses: actions/checkout@v4
- name: Install common deps
run: |
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
- name: Install Clang ${{matrix.llvm_version}}
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
if [[ "${{matrix.llvm_version}}" < "${{env.LLVM_DEV_VERSION}}" ]]; then
if [[ "${{matrix.llvm_version}}" < 16 ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
fi
sudo apt-get update
sudo apt-get install -y clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
sudo apt-get install -y libpolly-${{matrix.llvm_version}}-dev
- name: CMake Old
if: matrix.llvm_version < 18 || matrix.llvm_version == env.LLVM_DEV_VERSION
if [[ "${{matrix.llvm_version}}" > 12 ]]; then
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
fi
- name: CMake
run: |
cmake -B build \
-G Ninja \
@@ -441,176 +200,49 @@ jobs:
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: CMake
if: matrix.llvm_version >= 18 && matrix.llvm_version != env.LLVM_DEV_VERSION
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile examples/gameoflife.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile-run examples/hash.c3
../build/c3c compile-run examples/nbodies.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/contextfree/dynscope.c3
../build/c3c compile-run examples/contextfree/multi.c3
../build/c3c compile-run examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/base64.c3
../build/c3c compile-run examples/binarydigits.c3
../build/c3c compile-run examples/brainfk.c3
../build/c3c compile-run examples/factorial_macro.c3
../build/c3c compile-run examples/fasta.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit --sanitize=address
../build/c3c compile-run examples/contextfree/boolerr.c3
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run --debug-log
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
../../build/c3c run --debug-log --forcelinker
- name: run compiler tests
run: |
cd test
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
if [[ "${{matrix.llvm_version}}" < 15 ]]; then
python3 src/tester.py ../build/c3c test_suite/
else
python3 src/tester.py ../build/c3c test_suite2/
fi
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU22
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
run: |
mkdir c3
cp -r lib c3
cp README.md c3
cp releasenotes.md c3
cp msvc_build_libraries.py c3
cp build/c3c c3
tar czf c3-ubuntu-22-${{matrix.build_type}}.tar.gz c3
mkdir linux
cp -r lib linux
cp msvc_build_libraries.py linux
cp build/c3c linux
tar czf c3-linux-${{matrix.build_type}}.tar.gz linux
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU22
uses: actions/upload-artifact@v4
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
uses: actions/upload-artifact@v3
with:
name: c3-ubuntu-22-${{matrix.build_type}}
path: c3-ubuntu-22-${{matrix.build_type}}.tar.gz
name: c3-linux-${{matrix.build_type}}
path: c3-linux-${{matrix.build_type}}.tar.gz
build-with-docker:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
ubuntu_version: [20.04, 22.04]
build_type: [Release, Debug]
llvm_version: [17, 18, 19]
steps:
- uses: actions/checkout@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Make script executable
run: chmod +x ./build-with-docker.sh
- name: Run build
run: |
LLVM_VERSION=${{ matrix.llvm_version }} UBUNTU_VERSION=${{ matrix.ubuntu_version }} CMAKE_BUILD_TYPE=${{ matrix.build_type }} ./build-with-docker.sh
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile examples/base64.c3
../build/c3c compile examples/binarydigits.c3
../build/c3c compile examples/brainfk.c3
../build/c3c compile examples/factorial_macro.c3
../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile examples/load_world.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
- name: Test WASM
run: |
cd resources/testfragments
../../build/c3c compile --reloc=none --target wasm32 -g0 --no-entry -Os wasm4.c3
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
- name: Init a library & a project
run: |
./build/c3c init-lib mylib
ls mylib.c3l
./build/c3c init myproject
ls myproject
- name: run compiler tests
run: |
cd test
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
build-mac:
runs-on: macos-latest
@@ -619,178 +251,170 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18]
llvm_version: [12, 13, 14]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Download LLVM
run: |
brew install llvm@${{ matrix.llvm_version }} ninja curl
echo "/opt/homebrew/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
brew install llvm@${{ matrix.llvm_version }} botan ninja
echo "/usr/local/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
TMP_PATH=$(xcrun --show-sdk-path)/user/include
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
- name: CMake
if: matrix.llvm_version < 18
run: |
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
- name: CMake18
if: matrix.llvm_version >= 18
run: |
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}}.1 -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib55
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run -O5 examples/load_world.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
../../../build/c3c -vv dynamic-lib add.c3
../../../build/c3c compile-run test.c3 -l ./add.dylib
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit -O1
- name: Test WASM
run: |
cd resources/testfragments
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run --debug-log
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
../../build/c3c run --debug-log --forcelinker
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib -vvv --trust=full
../../build/c3c build hello_world_lib --debug-log
- name: run compiler tests
run: |
cd test
../build/c3c compile -O1 src/test_suite_runner.c3
./test_suite_runner ../build/c3c test_suite/
- name: run build test suite runner
run: |
cd test
../build/c3c compile -O1 src/test_suite_runner.c3
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
run: |
mkdir macos
cp -r lib macos
cp msvc_build_libraries.py macos
cp README.md macos
cp releasenotes.md macos
cp build/c3c macos
zip -r c3-macos-${{matrix.build_type}}.zip macos
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
uses: actions/upload-artifact@v4
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
uses: actions/upload-artifact@v3
with:
name: c3-macos-${{matrix.build_type}}
path: c3-macos-${{matrix.build_type}}.zip
build-nix:
runs-on: ubuntu-22.04
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [ Release, Debug ]
nixpkgs: [ Lock, Latest ]
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: Update flake (if necessary)
run: |
if [[ matrix.nixpkgs == "Latest" ]]; then
nix flake update
fi
nix flake info
- name: Build and check
run: |
if [[ ${{ matrix.build_type }} = "Debug" ]]; then
nix build -L ".#c3c-debug-checks"
else
nix build -L ".#c3c-checks"
fi
release:
runs-on: ubuntu-22.04
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu22]
runs-on: ubuntu-latest
needs: [build-msvc, build-linux, build-mac]
if: github.ref == 'refs/heads/master'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: actions/checkout@v3
- name: delete tag
continue-on-error: true
uses: actions/github-script@v6
with:
script: |
github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'tags/latest',
sha: context.sha
})
- name: create tag
uses: actions/github-script@v6
with:
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/latest',
sha: context.sha
})
- uses: actions/download-artifact@v3
- run: cp -r lib c3-windows-Release
- run: cp -r lib c3-windows-Debug
- run: cp msvc_build_libraries.py c3-windows-Release
- run: cp msvc_build_libraries.py c3-windows-Debug
- run: cp README.md c3-windows-Release
- run: cp README.md c3-windows-Debug
- run: cp releasenotes.md c3-windows-Release
- run: cp releasenotes.md c3-windows-Debug
- run: zip -r c3-windows.zip c3-windows-Release
- run: zip -r c3-windows-debug.zip c3-windows-Debug
- run: mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux-Release/c3-linux.tar.gz
- run: mv c3-linux-Debug/c3-linux-Debug.tar.gz c3-linux-Debug/c3-linux-debug.tar.gz
- run: mv c3-ubuntu-22-Release/c3-ubuntu-22-Release.tar.gz c3-ubuntu-22-Release/c3-ubuntu-22.tar.gz
- run: mv c3-ubuntu-22-Debug/c3-ubuntu-22-Debug.tar.gz c3-ubuntu-22-Debug/c3-ubuntu-22-debug.tar.gz
- run: mv c3-macos-Release/c3-macos-Release.zip c3-macos-Release/c3-macos.zip
- run: mv c3-macos-Debug/c3-macos-Debug.zip c3-macos-Debug/c3-macos-debug.zip
- run: gh release delete latest-prerelease --cleanup-tag -y || true
- run: echo "RELEASE_NAME=latest-prerelease-$(date +'%Y%m%d-%H%M')" >> $GITHUB_ENV
- run: cp install_win_reqs.bat c3-windows-Release
- run: cp install_win_reqs.bat c3-windows-Debug
- run: zip -r c3-windows-Release.zip c3-windows-Release
- run: zip -r c3-windows-Debug.zip c3-windows-Debug
- id: create_release
uses: softprops/action-gh-release@v2
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: latest-prerelease
name: ${{ env.RELEASE_NAME }}
tag_name: latest
release_name: latest
draft: false
prerelease: true
files: |
c3-windows.zip
c3-windows-debug.zip
c3-linux-Release/c3-linux.tar.gz
c3-linux-Debug/c3-linux-debug.tar.gz
c3-ubuntu-22-Release/c3-ubuntu-22.tar.gz
c3-ubuntu-22-Debug/c3-ubuntu-22-debug.tar.gz
c3-macos-Release/c3-macos.zip
c3-macos-Debug/c3-macos-debug.zip
- name: upload windows
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-windows-Release.zip
asset_name: c3-windows.zip
asset_content_type: application/zip
- name: upload windows debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-windows-Debug.zip
asset_name: c3-windows-debug.zip
asset_content_type: application/zip
- name: upload linux
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-linux-Release/c3-linux-Release.tar.gz
asset_name: c3-linux.tar.gz
asset_content_type: application/gzip
- name: upload linux debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-linux-Debug/c3-linux-Debug.tar.gz
asset_name: c3-linux-debug.tar.gz
asset_content_type: application/gzip
- name: upload macos
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-macos-Release/c3-macos-Release.zip
asset_name: c3-macos.zip
asset_content_type: application/zip
- name: upload macos debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-macos-Debug/c3-macos-Debug.zip
asset_name: c3-macos-debug.zip
asset_content_type: application/zip

19
.gitignore vendored
View File

@@ -19,7 +19,6 @@
# Libraries
*.lib
*.tlb
*.a
*.la
*.lo
@@ -68,21 +67,3 @@ out/
/cmake-build-debug/
/cmake-build-release/
# Emacs files
TAGS
# Clangd LSP files
/.cache/
/compile_commands.json
# 'nix build' resulting symlink
result
# macOS
.DS_Store
# tests
/test/tmp/*
/test/testrun
/test/test_suite_runner

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "tilde-backend"]
path = tilde-backend
url = https://github.com/c3lang/tilde-backend

View File

@@ -1,71 +1,33 @@
cmake_minimum_required(VERSION 3.20)
# Grab the version
file(READ "src/version.h" ver)
if (NOT ${ver} MATCHES "COMPILER_VERSION \"([0-9]+.[0-9]+.[0-9]+)\"")
message(FATAL_ERROR "Compiler version could not be parsed from version.h")
endif()
# Set the project and version
project(c3c VERSION ${CMAKE_MATCH_1})
message("C3C version: ${CMAKE_PROJECT_VERSION}")
# Avoid warning for FetchContent
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
cmake_policy(SET CMP0135 NEW)
endif()
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
if (MSVC)
set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib")
set(CMAKE_INSTALL_BINDIR "c:\\c3c")
else ()
set(CMAKE_INSTALL_LIBDIR "/usr/local/lib/c3")
set(CMAKE_INSTALL_BINDIR "/usr/local/bin/c3c")
endif()
endif ()
# Enable fetching (for Windows)
cmake_minimum_required(VERSION 3.15)
project(c3c)
include(FetchContent)
include(FeatureSummary)
set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
# We use C11 and C++17
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
if(MSVC)
message(STATUS "MSVC version ${MSVC_VERSION}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi")
else()
if (true)
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()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined,address -fno-exceptions")
endif()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3")
endif()
#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O1 -fsanitize=undefined")
#set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O1 -fsanitize=undefined")
#set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined")
#set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fsanitize=undefined")
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined")
option(C3_LINK_DYNAMIC "link dynamically with LLVM/LLD libs")
option(C3_USE_TB "Enable TB" OFF)
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF)
option(C3_USE_TB "Use TB" OFF)
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
option(C3_WITH_LLVM "Build with LLVM" ON)
option(C3_LLD_DIR "Use custom LLD directory" "")
option(LLVM_CRT_LIBRARY_DIR "Use custom llvm's compiler-rt directory" "")
set(C3_USE_MIMALLOC OFF)
if(C3_USE_MIMALLOC)
@@ -74,255 +36,174 @@ if(C3_USE_MIMALLOC)
option(MI_PADDING OFF)
option(MI_DEBUG_FULL OFF)
FetchContent_Declare(
mimalloc
GIT_REPOSITORY https://github.com/microsoft/mimalloc.git
GIT_TAG ${C3_MIMALLOC_TAG}
mimalloc
GIT_REPOSITORY https://github.com/microsoft/mimalloc.git
GIT_TAG ${C3_MIMALLOC_TAG}
)
FetchContent_MakeAvailable(mimalloc)
endif()
if (NOT WIN32)
find_package(CURL)
if (NOT C3_LLVM_VERSION STREQUAL "auto")
if (${C3_LLVM_VERSION} VERSION_LESS 12 OR ${C3_LLVM_VERSION} VERSION_GREATER 16)
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
endif()
endif()
if(C3_WITH_LLVM)
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
if (C3_LLVM_VERSION STREQUAL "auto")
set(C3_LLVM_VERSION "14")
endif()
FetchContent_Declare(
LLVM_Windows
URL https://github.com/c3lang/win-llvm/releases/download/llvm1406/llvm-14.0.6-windows-amd64-msvc17-libcmt.7z
)
FetchContent_Declare(
LLVM_Windows_debug
URL https://github.com/c3lang/win-llvm/releases/download/llvm1406/llvm-14.0.6-windows-amd64-msvc17-libcmt-dbg.7z
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message("Loading Windows LLVM debug libraries, this may take a while...")
FetchContent_MakeAvailable(LLVM_Windows_debug)
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_windows_debug_SOURCE_DIR} ${CMAKE_SYSTEM_PREFIX_PATH})
else()
message("Loading Windows LLVM libraries, this may take a while...")
FetchContent_MakeAvailable(LLVM_Windows)
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_windows_SOURCE_DIR} ${CMAKE_SYSTEM_PREFIX_PATH})
endif()
find_package(LLVM REQUIRED CONFIG)
find_package(LLD REQUIRED CONFIG)
else()
if (NOT C3_LLVM_VERSION STREQUAL "auto")
if (${C3_LLVM_VERSION} VERSION_LESS 17 OR ${C3_LLVM_VERSION} VERSION_GREATER 21)
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
endif()
endif()
endif()
find_package(Git QUIET)
if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
# Update submodules as needed
option(GIT_SUBMODULE "Check submodules during build" ON)
if(GIT_SUBMODULE)
message(STATUS "Submodule update")
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
endif()
endif()
endif()
# Clangd LSP support
option(C3_ENABLE_CLANGD_LSP "Enable/Disable output of compile commands during generation." OFF)
if(C3_ENABLE_CLANGD_LSP)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink
${CMAKE_BINARY_DIR}/compile_commands.json
${CMAKE_SOURCE_DIR}/compile_commands.json
)
endif(C3_ENABLE_CLANGD_LSP)
if(C3_WITH_LLVM)
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
if (C3_LLVM_VERSION STREQUAL "auto")
set(C3_LLVM_VERSION "19")
endif()
FetchContent_Declare(
LLVM_Windows
URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt.7z
)
FetchContent_Declare(
LLVM_Windows_debug
URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt-dbg.7z
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message("Loading Windows LLVM debug libraries, this may take a while...")
FetchContent_MakeAvailable(LLVM_Windows_debug)
set(llvm_dir ${llvm_windows_debug_SOURCE_DIR})
else()
message("Loading Windows LLVM libraries, this may take a while...")
FetchContent_MakeAvailable(LLVM_Windows)
set(llvm_dir ${llvm_windows_SOURCE_DIR})
endif()
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH})
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
else()
find_package(LLVM REQUIRED CONFIG)
find_package(LLD REQUIRED CONFIG)
else()
# Add paths for LLVM CMake files of version 19 and higher as they follow a new installation
# layout and are now in /usr/lib/llvm/*/lib/cmake/llvm/ rather than /usr/lib/cmake/llvm/
#
# Because of CMAKE_FIND_PACKAGE_SORT_ORDER CMAKE_FIND_PACKAGE_SORT_DIRECTION,
# the newest version will always be found first.
message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}")
if (DEFINED LLVM_DIR)
message(STATUS "Looking for LLVM CMake files in user-specified directory ${LLVM_DIR}")
else()
file (GLOB LLVM_CMAKE_PATHS "/usr/lib/llvm/*/lib/cmake/llvm/")
list (APPEND CMAKE_PREFIX_PATH ${LLVM_CMAKE_PATHS} "/usr/lib/")
message(STATUS "No LLVM_DIR specified, searching default directories ${CMAKE_PREFIX_PATH}")
endif()
if (NOT C3_LLVM_VERSION STREQUAL "auto")
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
else()
find_package(LLVM REQUIRED CONFIG)
endif()
endif()
endif()
if (EXISTS /usr/lib)
# Some systems (such as Alpine Linux) seem to put some of the relevant
# LLVM files in /usr/lib, but this doesn't seem to be included in the
# value of LLVM_LIBRARY_DIRS.
list(APPEND LLVM_LIBRARY_DIRS /usr/lib)
endif()
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
set(LLVM_LINK_COMPONENTS
AllTargetsAsmParsers
AllTargetsCodeGens
AllTargetsDescs
AllTargetsDisassemblers
AllTargetsInfos
Analysis
AsmPrinter
BitReader
Core
DebugInfoPDB
InstCombine
IrReader
LibDriver
Linker
LTO
MC
MCDisassembler
native
nativecodegen
Object
Option
ScalarOpts
Support
Target
TransformUtils
WindowsManifest
)
if (NOT LLVM_PACKAGE_VERSION VERSION_GREATER_EQUAL 15.0)
message(FATAL_ERROR "LLVM version 15.0 or later is required.")
endif()
if (${LLVM_PACKAGE_VERSION} VERSION_GREATER 14.1)
set(LLVM_LINK_COMPONENTS ${LLVM_LINK_COMPONENTS} WindowsDriver)
endif()
if(LLVM_ENABLE_RTTI)
message(STATUS "LLVM was built with RTTI")
else()
message(STATUS "LLVM was not built with RTTI")
endif()
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
string(REPLACE "." ";" VERSION_LIST ${LLVM_PACKAGE_VERSION})
list(GET VERSION_LIST 0 LLVM_MAJOR_VERSION)
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})
add_definitions(${LLVM_DEFINITIONS})
if(NOT C3_LINK_DYNAMIC)
set(LLVM_LINK_COMPONENTS
AllTargetsAsmParsers
AllTargetsCodeGens
AllTargetsDescs
AllTargetsDisassemblers
AllTargetsInfos
Analysis
AsmPrinter
BitReader
Core
DebugInfoPDB
InstCombine
IrReader
LibDriver
Linker
LTO
MC
MCDisassembler
native
nativecodegen
Object
Option
ScalarOpts
Support
Target
TransformUtils
WindowsManifest
WindowsDriver
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
if(C3_USE_TB)
find_library(TB_LIB NAMES tildebackend.a tildebackend.lib PATHS ${CMAKE_SOURCE_DIR}/tb/)
endif()
find_library(LLD_COFF NAMES lldCOFF.lib lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_COMMON NAMES lldCommon.lib lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_ELF NAMES lldELF.lib lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS})
if (${LLVM_PACKAGE_VERSION} VERSION_LESS 14)
find_library(LLD_MACHO NAMES lldMachO2.lib lldMachO2.a liblldMachO2.a PATHS ${LLVM_LIBRARY_DIRS})
else ()
find_library(LLD_MACHO NAMES lldMachO.lib lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS})
endif ()
find_library(LLD_MINGW NAMES lldMinGW.lib lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_WASM NAMES lldWasm.lib lldWasm.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS})
if (${LLVM_PACKAGE_VERSION} VERSION_LESS 14)
find_library(LLD_CORE NAMES lldCore.lib lldCore.a liblldCore.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_DRIVER NAMES lldDriver.lib lldDriver.a liblldDriver.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_READER_WRITER NAMES lldReaderWriter.lib lldReaderWriter.a liblldReaderWriter.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_YAML NAMES lldYAML.lib lldYAML.a liblldYAML.a PATHS ${LLVM_LIBRARY_DIRS})
endif ()
set(lld_libs
${LLD_COFF}
${LLD_COMMON}
${LLD_WASM}
${LLD_MINGW}
${LLD_ELF}
${LLD_DRIVER}
${LLD_READER_WRITER}
${LLD_MACHO}
${LLD_YAML}
${LLD_CORE}
)
if (APPLE)
set(lld_libs ${lld_libs} xar)
endif ()
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
message(STATUS "linking to llvm libs ${lld_libs}")
message(STATUS "Found lld libs ${lld_libs}")
if(NOT ${C3_LLD_DIR} EQUAL "" AND EXISTS ${C3_LLD_DIR})
message("C3_LLD_DIR: " ${C3_LLD_DIR})
set(LLVM_LIBRARY_DIRS
"${LLVM_LIBRARY_DIRS}"
"${C3_LLD_DIR}"
)
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
endif()
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.dll.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.dll.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.dll.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.dll.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.dll.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
else()
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(llvm_libs ${LLVM})
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
endif()
endif()
if (NOT(${CMAKE_BINARY_DIR} EQUAL ${CMAKE_SOURCE_DIR}))
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
endif()
if(C3_WITH_LLVM)
find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(lld_libs
${LLD_COFF}
${LLD_WASM}
${LLD_MINGW}
${LLD_ELF}
${LLD_MACHO}
${LLD_COMMON}
)
if (APPLE)
set(lld_libs ${lld_libs} xar)
find_file(RT_ASAN_DYNAMIC NAMES libclang_rt.asan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
find_file(RT_TSAN_DYNAMIC NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
find_file(RT_UBSAN_DYNAMIC NAMES libclang_rt.ubsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
find_file(RT_LSAN_DYNAMIC NAMES libclang_rt.lsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
set(sanitizer_runtime_libraries
${RT_ASAN_DYNAMIC}
${RT_TSAN_DYNAMIC}
# Unused
# ${RT_UBSAN_DYNAMIC}
# ${RT_LSAN_DYNAMIC}
)
endif()
message(STATUS "linking to llvm libs ${lld_libs}")
message(STATUS "Found lld libs ${lld_libs}")
endif()
add_library(miniz STATIC dependencies/miniz/miniz.c)
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
add_executable(c3c
src/build/builder.c
src/build/build_options.c
src/build/project_creation.c
src/build/project_manipulation.c
src/build/libraries.c
src/compiler/ast.c
src/compiler/bigint.c
src/compiler/c_abi_internal.h
src/compiler/codegen_general.c
src/compiler/compiler.c
src/compiler/compiler.h
src/compiler/subprocess.c
src/compiler/subprocess.h
src/compiler/context.c
src/compiler/copying.c
src/compiler/diagnostics.c
src/compiler/dwarf.h
src/compiler/enums.h
src/compiler/float.c
src/compiler/headers.c
src/compiler/json_output.c
src/compiler/lexer.c
src/compiler/libraries.c
src/compiler/linker.c
src/compiler/abi/c_abi_aarch64.c
src/compiler/abi/c_abi.c
src/compiler/abi/c_abi_riscv.c
src/compiler/abi/c_abi_wasm.c
src/compiler/abi/c_abi_win64.c
src/compiler/abi/c_abi_x64.c
src/compiler/abi/c_abi_x86.c
src/compiler/llvm_codegen.c
src/compiler/llvm_codegen_c_abi_aarch64.c
src/compiler/llvm_codegen_c_abi.c
src/compiler/llvm_codegen_c_abi_riscv.c
src/compiler/llvm_codegen_c_abi_wasm.c
src/compiler/llvm_codegen_c_abi_win64.c
src/compiler/llvm_codegen_c_abi_x64.c
src/compiler/llvm_codegen_c_abi_x86.c
src/compiler/llvm_codegen_debug_info.c
src/compiler/llvm_codegen_expr.c
src/compiler/llvm_codegen_function.c
src/compiler/llvm_codegen_instr.c
src/compiler/llvm_codegen_module.c
src/compiler/llvm_codegen_stmt.c
src/compiler/llvm_codegen_type.c
src/compiler/llvm_codegen_value.c
src/compiler/module.c
src/compiler/number.c
src/compiler/parse_expr.c
@@ -335,9 +216,6 @@ add_executable(c3c
src/compiler/sema_expr.c
src/compiler/sema_internal.h
src/compiler/sema_name_resolution.c
src/compiler/sema_errors.c
src/compiler/sema_builtins.c
src/compiler/sema_initializers.c
src/compiler/semantic_analyser.c
src/compiler/sema_passes.c
src/compiler/sema_stmts.c
@@ -346,6 +224,11 @@ add_executable(c3c
src/compiler/symtab.c
src/compiler/target.c
src/compiler/sema_asm.c
src/compiler/tb_codegen.c
src/compiler/tilde_codegen.c
src/compiler/tilde_codegen_instr.c
src/compiler/tilde_codegen_value.c
src/compiler/tilde_codegen_storeload.c
src/compiler_tests/benchmark.c
src/compiler_tests/tests.c
src/compiler/tokens.c
@@ -362,213 +245,65 @@ add_executable(c3c
src/utils/vmem.c
src/utils/vmem.h
src/utils/whereami.c
src/utils/cpus.c
src/utils/unzipper.c
src/compiler/c_codegen.c
src/compiler/decltable.c
src/compiler/mac_support.c
src/compiler/tilde_codegen_storeload.c
src/compiler/llvm_codegen_storeload.c
src/compiler/tilde_codegen_expr.c
src/compiler/tilde_codegen_stmt.c
src/compiler/tilde_codegen_type.c
src/compiler/windows_support.c
src/compiler/codegen_asm.c
src/compiler/asm_target.c
src/compiler/expr.c
src/utils/time.c
src/utils/http.c
src/compiler/sema_liveness.c
src/build/common_build.c
src/compiler/sema_const.c
${CMAKE_BINARY_DIR}/git_hash.h
)
src/compiler/asm_target.c)
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
# We are inside of a git repository so rebuilding the hash every time something changes.
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/git_hash.h
COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_LIST_DIR}/git_hash.cmake"
DEPENDS "${CMAKE_CURRENT_LIST_DIR}/.git")
else()
# We are NOT inside of a git repository. Building the has only once.
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/git_hash.h
COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_LIST_DIR}/git_hash.cmake")
endif()
if(C3_WITH_LLVM)
target_sources(c3c PRIVATE
src/compiler/llvm_codegen.c
src/compiler/llvm_codegen_debug_info.c
src/compiler/llvm_codegen_expr.c
src/compiler/llvm_codegen_function.c
src/compiler/llvm_codegen_instr.c
src/compiler/llvm_codegen_module.c
src/compiler/llvm_codegen_stmt.c
src/compiler/llvm_codegen_type.c
src/compiler/llvm_codegen_value.c
src/compiler/llvm_codegen_storeload.c
src/compiler/llvm_codegen_builtins.c)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=1)
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
else()
target_sources(c3c PRIVATE src/utils/hostinfo.c)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0)
endif()
target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/src/"
"${CMAKE_BINARY_DIR}")
"${CMAKE_SOURCE_DIR}/src/")
target_include_directories(miniz PUBLIC
"${CMAKE_SOURCE_DIR}/dependencies/miniz/")
target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/tb/")
if (C3_USE_TB)
file(GLOB tilde-sources
tilde-backend/src/tb/*.c
tilde-backend/src/tb/codegen/*.c
tilde-backend/src/tb/bigint/*.c
tilde-backend/src/tb/objects/*.c
tilde-backend/src/tb/system/*.c
tilde-backend/src/tb/debug/cv/*.c
tilde-backend/src/tb/opt/*.c
tilde-backend/src/tb/x64/*.c
tilde-backend/src/tb/wasm/*.c
tilde-backend/src/tb/aarch64/*.c
)
target_sources(c3c PRIVATE
src/compiler/tilde_codegen.c
src/compiler/tilde_codegen_instr.c
src/compiler/tilde_codegen_value.c
src/compiler/tilde_codegen_storeload.c
src/compiler/tilde_codegen_expr.c
src/compiler/tilde_codegen_stmt.c
src/compiler/tilde_codegen_type.c
src/compiler/tilde_codegen_abi.c
src/compiler/tilde_codegen_storeload.c)
target_include_directories(c3c_wrappers PRIVATE
"${CMAKE_SOURCE_DIR}/wrapper/src/")
target_compile_definitions(c3c PUBLIC TB_AVAILABLE=1)
target_link_libraries(c3c tilde-backend)
add_library(tilde-backend STATIC ${tilde-sources})
target_include_directories(tilde-backend PRIVATE
"${CMAKE_SOURCE_DIR}/tilde-backend/src/" "${CMAKE_SOURCE_DIR}/tilde-backend/include")
target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/tilde-backend/include/")
target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
target_link_libraries(c3c ${llvm_libs} c3c_wrappers ${lld_libs})
if(C3_USE_TB)
target_link_libraries(c3c c3c_wrappers ${TB_LIB})
target_compile_definitions(c3c PUBLIC TB_BACKEND=1)
else()
target_compile_definitions(c3c PUBLIC TB_AVAILABLE=0)
endif()
if(C3_WITH_LLVM)
target_link_libraries(c3c ${llvm_libs} miniz c3c_wrappers ${lld_libs})
target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/wrapper/include/")
target_include_directories(c3c_wrappers PRIVATE
"${CMAKE_SOURCE_DIR}/wrapper/include/")
target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
else()
target_link_libraries(c3c ${llvm_libs} miniz ${lld_libs})
target_compile_definitions(c3c PUBLIC TB_BACKEND=0)
endif()
if(C3_USE_MIMALLOC)
target_link_libraries(c3c mimalloc-static)
endif()
if (WIN32)
target_link_libraries(c3c Winhttp.lib)
endif()
if(MINGW)
message("Increase stack for msys")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,8388608")
endif ()
if (CURL_FOUND)
target_link_libraries(c3c ${CURL_LIBRARIES})
target_include_directories(c3c PRIVATE ${CURL_INCLUDES})
target_compile_definitions(c3c PUBLIC CURL_FOUND=1)
else()
target_compile_definitions(c3c PUBLIC CURL_FOUND=0)
endif()
if(MSVC)
message("Adding MSVC options")
target_compile_options(c3c PRIVATE /wd4068 /wd4090 /WX /Wv:18)
if(C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /wd4624 /wd4267 /wd4244 /WX /Wv:18)
if(NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PUBLIC /GR-)
endif()
target_link_options(c3c_wrappers PUBLIC /ignore:4099)
endif()
target_compile_options(c3c_wrappers PUBLIC /wd4624 /wd4267 /wd4244 /WX /Wv:18)
target_link_options(c3c_wrappers PUBLIC /ignore:4099)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(c3c PUBLIC /MTd)
if (C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /MTd)
endif()
target_compile_options(miniz PUBLIC /MTd)
if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MTd)
endif()
target_compile_options(c3c_wrappers PUBLIC /MTd)
else()
target_compile_options(c3c PUBLIC /MT)
if (C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /MT)
endif()
target_compile_options(miniz PUBLIC /MT)
if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MT)
endif()
endif()
if(C3_WITH_LLVM)
set(clang_lib_dir ${llvm_dir}/lib/clang/${C3_LLVM_VERSION}/lib/windows)
set(sanitizer_runtime_libraries
${clang_lib_dir}/clang_rt.asan-x86_64.lib
${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.lib
${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.dll
${clang_lib_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
target_compile_options(c3c_wrappers PUBLIC /MT)
endif()
else()
message(STATUS "using gcc/clang warning switches")
target_link_options(c3c PRIVATE -pthread)
if (C3_WITH_LLVM AND NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PRIVATE -fno-rtti)
endif()
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
target_compile_options(c3c PRIVATE -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)
if (WIN32)
target_link_options(c3c PRIVATE -pthread)
target_compile_definitions(c3c PRIVATE USE_PTHREAD=1)
endif()
endif()
install(TARGETS c3c DESTINATION bin)
install(DIRECTORY lib/ DESTINATION lib/c3)
# Man page install (OSX/Linux only)
if (NOT WIN32)
install(FILES c3c.1 DESTINATION "share/man/man1")
endif()
if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
add_custom_command(TARGET c3c POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E rm -rf -- $<TARGET_FILE_DIR:c3c>/c3c_rt
COMMAND "${CMAKE_COMMAND}" -E make_directory $<TARGET_FILE_DIR:c3c>/c3c_rt
COMMAND "${CMAKE_COMMAND}" -E copy ${sanitizer_runtime_libraries} $<TARGET_FILE_DIR:c3c>/c3c_rt
VERBATIM
COMMENT "Copying sanitizer runtime libraries to output directory")
if (APPLE)
# Change LC_ID_DYLIB to be rpath-based instead of having an absolute path
add_custom_command(TARGET c3c POST_BUILD
COMMAND find $<TARGET_FILE_DIR:c3c>/c3c_rt -type f -name "*.dylib" -execdir ${LLVM_TOOLS_BINARY_DIR}/llvm-install-name-tool -id @rpath/{} {} $<SEMICOLON>
VERBATIM)
endif()
install(DIRECTORY $<TARGET_FILE_DIR:c3c>/c3c_rt/ DESTINATION bin/c3c_rt)
endif()
feature_summary(WHAT ALL)

View File

@@ -74,7 +74,7 @@ No space inside parenthesis:
### Tab vs spaces
Use tabs for indentation, no CRLF in the source.
Recommendation: tabs, 4 spaces wide. No CRLF in the source.
### If, braces and new lines
@@ -116,8 +116,7 @@ more tests as you go along.
### Don't bring in dependencies
External libraries has maintainability issues. Try to depend on as few libraries
as possible. Currently, c3c only depends on LLVM and libc with an optional
dependency on libcurl.
as possible. Currently c3c only depends on LLVM and libc.
Do use rewrites of subsets of other libraries to bring in functionality, but avoid
copying in libraries that needs to be updated separately.
@@ -147,113 +146,4 @@ Iterating over the elements are done using `VECEACH`.
### Scratch buffer for strings.
There is a scratch buffer for strings in the `global_context` prefer using that
one with related functions when working on temporary strings.
# C3 Standard library style guide.
When contributing to the standard librairy please to your best to follow the following style requirements
as to ensure a consistent style in the stdlib and also make accepting PRs more quickly.
### Braces are placed on the next line
**NO:**
```c
fn void foo(String bar) {
@pool() {
...
};
}
```
**YES:**
```c
fn void foo(String bar)
{
@pool()
{
...
};
}
```
### Indentation with tabs
Use tab for indentation, not spaces, no CRLF in the sources
### Type names
Use `PascalCase` not `Ada_Case` for type names.
**YES:**
```c
enum MyEnum
{
ABC,
DEF
}
```
**NO:**
```c
enum My_Enum
{
ABC,
DEF
}
```
### Type names when binding to OS libraries
When doing bindings (for instance, adding declarations referring to Win32 APIs),
try to retain the original name when possible. If it isn't possible use (consistently)
one of two options:
1. Prefix: `HANDLE` -> `Win32_HANDLE`
2. Change the first letter to upper case: `mode_t` -> `Mode_t`
### Variables, function, methods and globals
Use `snake_case`, not `camelCase`.
**YES:**
```c
int some_global = 1;
fn void open_file(String special_file)
{
...
}
```
**NO:**
```c
int someGlobal = 1;
fn void openFile(String specialFile)
{
...
}
```
### Variables, function, methods and globals when binding to OS libraries
When doing bindings (for instance, adding declarations referring to Win32 APIs),
try to retain the original name when possible. If it isn't possible use (consistently)
one of two options:
1. Prefix: `win32_GetWindowLongPtrW`. However, this is usually only recommended if it is builtin.
2. Change first character to lower case: `GetWindowLongPtrW` -> `getWindowLongPtrW`
### Use `self` as the first method argument
Unless there is a strong reason not to, use `self` for the first parameter in a method.
### The allocator argument
Prefer always calling the allocator parameter `allocator`, and make it the first regular
argument.
## Add tests to your changes
If you add or fix things, then there should always be tests in `test/unit/stdlib` to verify
the functionality.
one with related functions when working on temporary strings.

View File

@@ -1,70 +0,0 @@
# How to contribute to C3
The C3 project consists of
1. The C3 language itself.
2. The C3 compiler, called c3c.
3. The C3 standard library
4. Various tools, such as the editor plugins
## 1. How to contribute to the C3 language
The C3 language is essentially the language specification. You can contribute to the language by:
1. Filing enhancement requests for changes to the language.
2. Offering feedback on existing features, on Discord or by filing issues.
3. Help working on the language specification.
4. Help working on the grammar.
## 2. How to contribute to the C3 compiler
The C3 compiler consists for the compiler itself + test suites for testing the compiler.
You can contribute by:
1. File bugs (by far the most important thing).
2. Suggest improved diagnostics / error messages.
3. Refactoring existing code (needs deep understanding of the compiler).
4. Add support for more architectures.
5. Add support for more backends.
## 3. How to contribute to the standard library
The standard library is the library itself + test suites for testing the standard library.
You can contribute by:
1. Filing bugs on the standard library.
2. Write additional unit tests.
3. Suggest new functionality by filing an issue.
4. Work on stdlib additions.
5. Fix bugs in the stdlib
6. Maintain a section of the standard library
### How to work on small stdlib additions
If there is just a matter of adding a function or two to an existing module, a pull request
is sufficient. However, please make sure that:
1. It follows the guidelines for the code to ensure a uniform experience (naming standard, indentation, braces etc).
2. Add a line in the release notes about the change.
3. Make sure it has unit tests.
### How to work on non-trivial additions to the stdlib
Regardless whether an addition is approved for inclusion or not, it needs to incubate:
1. First implement it standalone, showing that its working well and has a solid design. This has the advantage of people being able to contribute or even create competing implementations
2. Once it is considered finished it can be proposed for inclusion.
This will greatly help improving the quality of additions.
Note that any new addition needs a full set of unit tests before being included into the standard library.
### Maintain a part of the standard library
A single maintainer is insufficient for a standard library, instead we need one or more maintainer
for each module. The maintainer(s) will review pull requests and actively work on making the module
pristine with the highest possible quality.
## 4. How to contribute to various tools
In general, file a pull request. Depending on who maintains it, rules may differ.

227
README.md
View File

@@ -1,25 +1,14 @@
# C3 Language
C3 is a programming language that builds on the syntax and semantics of the C language,
with the goal of evolving it while still retaining familiarity for C programmers.
It's an evolution, not a revolution: the C-like
for programmers who like C.
Precompiled binaries for the following operating systems are available:
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
C3 is a C-like language striving to be an evolution of C, rather than a
completely new language. As an alternative in the C/C++ niche it
aims to be fast and close to the metal.
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
![vkQuake](https://github.com/c3lang/c3c/blob/master/resources/images/vkQuake.png?raw=true)
Thanks to full ABI compatibility with C, it's possible to mix C and C3 in the same project with no effort. As a demonstration, vkQuake was compiled with a small portion of the code converted to C3 and compiled with the c3c compiler. (The aging fork can be found at https://github.com/c3lang/vkQuake)
A non-curated list of user written projects and other resources can be found [here](https://github.com/c3lang/c3-showcase).
Thanks to full ABI compatibility with C, it's possible to mix C and C3 in the same project with no effort. As a demonstration, vkQuake was compiled with a small portion of the code converted to C3 and compiled with the c3c compiler. (The fork can be found at https://github.com/c3lang/vkQuake)
### Design Principles
- Procedural "get things done"-type of language.
@@ -35,16 +24,16 @@ whole new language.
### Example code
The following code shows [generic modules](https://c3-lang.org/references/docs/generics/) (more examples can be found at https://c3-lang.org/references/docs/examples/).
The following code shows [generic modules](http://www.c3-lang.org/generics/) (more examples can be found at http://www.c3-lang.org/examples/).
```cpp
module stack {Type};
```c++
module stack <Type>;
// Above: the parameterized type is applied to the entire module.
struct Stack
{
usz capacity;
usz size;
usize capacity;
usize size;
Type* elems;
}
@@ -57,8 +46,7 @@ fn void Stack.push(Stack* this, Type element)
if (this.capacity == this.size)
{
this.capacity *= 2;
if (this.capacity < 16) this.capacity = 16;
this.elems = realloc(this.elems, Type.sizeof * this.capacity);
this.elems = mem::realloc(this.elems, Type.sizeof * this.capacity);
}
this.elems[this.size++] = element;
}
@@ -82,20 +70,20 @@ import stack;
// Define our new types, the first will implicitly create
// a complete copy of the entire Stack module with "Type" set to "int"
alias IntStack = Stack {int};
define IntStack = Stack<int>;
// The second creates another copy with "Type" set to "double"
alias DoubleStack = Stack {double};
define DoubleStack = Stack<double>;
// If we had added "alias IntStack2 = Stack {int}"
// If we had added "define IntStack2 = Stack<int>"
// no additional copy would have been made (since we already
// have an parameterization of Stack {int} so it would
// have an parameterization of Stack<int>) so it would
// be same as declaring IntStack2 an alias of IntStack
// Importing an external C function is straightforward
// here is an example of importing libc's printf:
extern fn int printf(char* format, ...);
fn void main()
fn void test()
{
IntStack stack;
// Note that C3 uses zero initialization by default
@@ -115,18 +103,17 @@ fn void main()
dstack.push(2.3);
dstack.push(3.141);
dstack.push(1.1235);
// Prints pop: 1.123500
// Prints pop: 1.1235
printf("pop: %f\n", dstack.pop());
}
```
### In what ways does C3 differ from C?
### In what ways do C3 differ from C?
- No mandatory header files
- New semantic macro system
- Module based name spacing
- Slices
- Operator overloading
- Subarrays (slices)
- Compile time reflection
- Enhanced compile time execution
- Generics based on generic modules
@@ -135,16 +122,20 @@ fn void main()
- Value methods
- Associated enum data
- No preprocessor
- Less undefined behaviour and added runtime checks in "safe" mode
- Less undefined behaviour and runtime checks in "safe" mode
- Limited operator overloading to enable userland dynamic arrays
- Optional pre and post conditions
### Current status
The current stable version of the compiler is **version 0.7.1**.
The current version of the compiler is alpha release 0.2.
The upcoming 0.7.x releases will focus on expanding the standard library,
fixing bugs and improving compile time analysis.
It's possible to try out the current C3 compiler in the browser: https://ide.judge0.com/ this is courtesy of the
developer of Judge0.
Design work on C3 complete aside from fleshing out details, such as
inline asm. As the standard library work progresses, changes and improvements
to the language will happen continuously.
Follow the issues [here](https://github.com/c3lang/c3c/issues).
If you have suggestions on how to improve the language, either [file an issue](https://github.com/c3lang/c3c/issues)
@@ -152,38 +143,7 @@ or discuss C3 on its dedicated Discord: [https://discord.gg/qN76R87](https://dis
The compiler is currently verified to compile on Linux, Windows and MacOS.
**Support matrix**
| Platform | Native C3 compiler available? | Target supported | Stack trace | Threads | Sockets | Inline asm |
|--------------------------|-------------------------------|-------------------------|-------------|----------|----------|------------|
| Win32 x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| Win32 Aarch64 | Untested | Untested | Untested | Untested | Untested | Yes* |
| MacOS x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| MacOS Aarch64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| iOS Aarch64 | No | Untested | Untested | Yes | Yes | Yes* |
| Linux x86 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux x64 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux Aarch64 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux Riscv32 | Yes | Yes | Yes | Yes | Yes | Untested |
| Linux Riscv64 | Yes | Yes | Yes | Yes | Yes | Untested |
| ELF freestanding x86 | No | Untested | No | No | No | Yes* |
| ELF freestanding x64 | No | Untested | No | No | No | Yes* |
| ELF freestanding Aarch64 | No | Untested | No | No | No | Yes* |
| ELF freestanding Riscv64 | No | Untested | No | No | No | Untested |
| ELF freestanding Riscv32 | No | Untested | No | No | No | Untested |
| FreeBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| FreeBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| NetBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| NetBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| MCU x86 | No | Untested | No | No | No | Yes* |
| Wasm32 | No | Yes | No | No | No | No |
| Wasm64 | No | Untested | No | No | No | No |
*\* Inline asm is still a work in progress*
More platforms will be supported in the future.
#### What can you help with?
@@ -198,10 +158,10 @@ More platforms will be supported in the future.
### Installing
#### Installing on Windows with precompiled binaries
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip)
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++" (there is also `c3c/resources/install_win_reqs.bat` to automate this)
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-windows-debug.zip))
2. Unzip exe and standard lib.
3. If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
3. Unzip exe and standard lib.
4. Run `c3c.exe`.
#### Installing on Debian with precompiled binaries
@@ -210,41 +170,16 @@ More platforms will be supported in the future.
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on Ubuntu with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20-debug.tar.gz))
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on MacOS with precompiled binaries
#### Installing on Mac with precompiled binaries
1. Make sure you have XCode with command line tools installed.
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-macos-debug.zip))
3. Unzip executable and standard lib.
4. Run `./c3c`.
(*Note that there is a known issue with debug symbol generation on MacOS 13, see [issue #1086](https://github.com/c3lang/c3c/issues/1086))
#### Installing on Arch Linux
Arch includes c3c in the official 'extra' repo. It can be easily installed the usual way:
```sh
sudo pacman -S c3c
# or paru -S c3c
# or yay -S c3c
# or aura -A c3c
```
There is also an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
You can use your AUR package manager:
```sh
paru -S c3c-git
# or yay -S c3c-git
# or aura -A c3c-git
```
Or clone it manually:
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git)
You can use your AUR package manager or clone it manually:
```sh
git clone https://aur.archlinux.org/c3c-git.git
cd c3c-git
@@ -253,18 +188,25 @@ makepkg -si
#### Building via Docker
You can build `c3c` using an Ubuntu container. By default, the script will build through Ubuntu 22.04. You can specify the version by passing the `UBUNTU_VERSION` environment variable.
You can build `c3c` using either an Ubuntu 18.04 or 20.04 container:
```
UBUNTU_VERSION=20.04 ./build-with-docker.sh
./build-with-docker.sh 18
```
See the `build-with-docker.sh` script for more information on other configurable environment variables.
Replace `18` with `20` to build through Ubuntu 20.04.
For a release build specify:
```
./build-with-docker.sh 20 Release
```
A `c3c` executable will be found under `bin/`.
#### Installing on OS X using Homebrew
2. Install CMake: `brew install cmake`
3. Install LLVM 17+: `brew install llvm`
3. Install LLVM 13: `brew install llvm`
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
5. Enter the C3C directory `cd c3c`.
6. Create a build directory `mkdir build`
@@ -272,14 +214,6 @@ See the `build-with-docker.sh` script for more information on other configurable
8. Set up CMake build for debug: `cmake ..`
9. Build: `cmake --build .`
#### Installing on Windows using Scoop
c3c is included in 'Main' bucket.
```sh
scoop install c3
```
#### Getting started with a "hello world"
Create a `main.c3` file with:
@@ -289,7 +223,7 @@ import std::io;
fn void main()
{
io::printn("Hello, world!");
io::println("Hello, world!");
}
```
@@ -308,7 +242,7 @@ called `hello_world` or `hello_world.exe`depending on platform.
#### Compiling on Windows
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++"
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++" (there is also `c3c/resources/install_win_reqs.bat` to automate this)
2. Install CMake
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
@@ -323,55 +257,27 @@ You can try it out by running some sample code: `c3c.exe compile ../resources/ex
*Note that if you run into linking issues when building, make sure that you are using the latest version of VS17.*
#### Compiling on Ubuntu 24.04 LTS
#### Compiling on Ubuntu 20.10
1. Make sure you have a C compiler that handles C11 and a C++ compiler, such as GCC or Clang. Git also needs to be installed.
2. Install LLVM 18 `sudo apt-get install cmake git clang zlib1g zlib1g-dev libllvm18 llvm llvm-dev llvm-runtime liblld-dev liblld-18 libpolly-18-dev`
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build`
6. Change directory to the build directory `cd build`
7. Set up CMake build: `cmake ..`
8. Build: `cmake --build .`
2. Install CMake: `sudo apt install cmake`
3. Install LLVM 12 (or greater: C3C supports LLVM 12-16): `sudo apt-get install clang-12 zlib1g zlib1g-dev libllvm12 llvm-12 llvm-12-dev llvm-12-runtime liblld-12-dev liblld-12`
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
5. Enter the C3C directory `cd c3c`.
6. Create a build directory `mkdir build`
7. Change directory to the build directory `cd build`
8. Set up CMake build: `cmake ..`
9. Build: `cmake --build .`
You should now have a `c3c` executable.
You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
#### Compiling on Void Linux
1. As root, ensure that all project dependencies are installed: `xbps-install git cmake llvm17 llvm17-devel lld17-devel libcurl-devel ncurses-devel zlib-devel libzstd-devel libxml2-devel`
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone instead: `git clone https://github.com/c3lang/c3c.git --depth=1`
3. Enter the directory: `cd c3c`
4. Create a build directory: `mkdir build`
5. Enter the build directory: `cd build`
6. Create the CMake build cache: `cmake ..`
7. Build: `cmake --build .`
Your c3c executable should have compiled properly. You may want to test it: `./c3c compile ../resources/examples/hash.c3`
For a sytem-wide installation, run the following as root: `cmake --install .`
#### Compiling on Fedora
1. Install required project dependencies: `dnf install cmake clang git llvm llvm-devel lld lld-devel ncurses-devel`
2. Optionally, install additional dependencies: `dnf install libcurl-devel zlib-devel libzstd-devel libxml2-devel libffi-devel`
3. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
4. Enter the C3C directory: `cd c3c`
5. Create a build directory and navigate into it: `mkdir build && cd build`
6. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake .. -DC3_LINK_DYNAMIC=1`
7. Build the project: `cmake --build .`
The c3c binary should be created in the build directory. You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
#### Compiling on other Linux / Unix variants
1. Install CMake.
2. Install or compile LLVM and LLD *libraries* (version 17+ or higher)
2. Install or compile LLVM and LLD *libraries* (version 12+ or higher)
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build`
@@ -380,10 +286,6 @@ The c3c binary should be created in the build directory. You can try it out by r
provide the link path to the LLVM CMake directories, e.g. `cmake -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/ ..`
8. Build: `cmake --build .`
*A note on compiling for Linux/Unix/MacOS: to be able to fetch vendor libraries
libcurl is needed. The CMake script should detect it if it is available. Note that
this functionality is non-essential and it is perfectly fine to user the compiler without it.*
#### Licensing
The C3 compiler is licensed under LGPL 3.0, the standard library itself is
@@ -392,22 +294,3 @@ MIT licensed.
#### Editor plugins
Editor plugins can be found at https://github.com/c3lang/editor-plugins.
#### Contributing unit tests
1. Write the test, either adding to existing test files in `/test/unit/` or add
a new file. (If testing the standard library, put it in the `/test/unit/stdlib/` subdirectory).
2. Make sure that the test functions have the `@test` attribute.
3. Run tests and see that they pass. (Recommended settings: `c3c compile-test -O0 test/unit`.
- in this example `test/unit/` is the relative path to the test directory, so adjust as required)
4. Make a pull request for the new tests.
## Thank yous
A huge **THANK YOU** goes out to all contributors and sponsors.
A special thank you to sponsors [Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad) for going the extra mile.
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=c3lang/c3c&type=Date)](https://www.star-history.com/#c3lang/c3c&Date)

View File

@@ -1,69 +0,0 @@
module sort_bench;
import std::sort;
fn void init() @init
{
set_benchmark_warmup_iterations(5);
set_benchmark_max_iterations(10_000);
}
fn void quicksort_bench() @benchmark
{
// test set: 500 numbers between 0 and 99;
int[] data = {
71, 28, 2, 13, 62, 10, 54, 78, 63, 86,
33, 65, 89, 51, 58, 0, 51, 16, 87, 30,
89, 14, 52, 41, 88, 25, 83, 91, 56, 86,
14, 64, 76, 18, 39, 24, 79, 62, 34, 58,
90, 24, 56, 73, 85, 82, 79, 63, 47, 69,
78, 29, 49, 28, 43, 47, 56, 53, 79, 56,
19, 63, 29, 52, 71, 93, 61, 46, 30, 11,
21, 26, 37, 86, 93, 74, 62, 0, 41, 17,
26, 27, 34, 11, 54, 69, 72, 44, 74, 3,
61, 62, 80, 90, 3, 82, 16, 12, 28, 1,
2, 49, 4, 44, 57, 86, 63, 74, 33, 41,
76, 77, 56, 57, 56, 88, 74, 71, 6, 59,
40, 42, 94, 55, 21, 17, 17, 63, 21, 83,
73, 19, 39, 88, 93, 74, 21, 0, 63, 45,
69, 66, 22, 68, 86, 86, 85, 67, 8, 50,
23, 98, 64, 80, 64, 36, 40, 30, 73, 36,
23, 14, 1, 77, 82, 8, 18, 73, 37, 86,
29, 70, 27, 87, 64, 81, 13, 0, 4, 83,
90, 17, 71, 66, 38, 39, 54, 22, 86, 18,
84, 66, 77, 25, 64, 93, 80, 91, 2, 92,
47, 32, 90, 16, 46, 29, 56, 87, 70, 73,
89, 41, 5, 54, 93, 63, 16, 39, 71, 84,
74, 91, 69, 59, 49, 87, 74, 37, 75, 83,
77, 19, 51, 44, 79, 62, 94, 20, 24, 83,
37, 70, 57, 32, 93, 8, 29, 11, 7, 92,
8, 23, 20, 21, 7, 70, 28, 20, 96, 6,
50, 58, 30, 61, 66, 42, 50, 54, 64, 7,
10, 53, 63, 44, 16, 39, 83, 73, 3, 29,
97, 32, 36, 68, 84, 64, 73, 5, 29, 13,
48, 3, 84, 65, 75, 68, 66, 22, 39, 33,
39, 24, 27, 85, 18, 34, 3, 63, 32, 9,
29, 66, 24, 90, 75, 50, 11, 95, 47, 14,
92, 1, 76, 45, 76, 41, 55, 54, 38, 67,
43, 40, 5, 61, 97, 11, 61, 24, 92, 24,
76, 53, 60, 34, 78, 80, 70, 75, 30, 90,
65, 99, 80, 61, 94, 75, 63, 67, 10, 35,
23, 42, 31, 48, 14, 68, 84, 14, 79, 1,
25, 94, 23, 53, 49, 69, 44, 73, 63, 51,
44, 96, 88, 51, 94, 24, 64, 72, 59, 81,
73, 93, 14, 35, 9, 53, 25, 48, 50, 88,
46, 97, 67, 40, 27, 17, 2, 42, 11, 82,
0, 46, 44, 38, 31, 88, 63, 88, 10, 82,
77, 61, 24, 39, 27, 33, 10, 91, 69, 22,
42, 74, 71, 13, 32, 56, 12, 46, 81, 74,
17, 26, 45, 50, 76, 84, 76, 36, 43, 65,
81, 64, 0, 49, 70, 11, 76, 19, 60, 55,
15, 98, 31, 91, 56, 8, 97, 9, 3, 94,
3, 88, 7, 2, 3, 98, 10, 51, 21, 79,
99, 3, 8, 76, 52, 13, 40, 90, 85, 15,
70, 77, 43, 30, 4, 89, 18, 21, 59, 17,
};
sort::quicksort(data);
}

View File

@@ -1,44 +1,56 @@
#!/bin/bash
## build-with-docker.sh
## @author gdm85
##
## Script to build c3c for either Ubuntu 20, 21 or 22.
##
#
: ${DOCKER:=docker}
: ${IMAGE:="c3c-builder"}
: ${CMAKE_BUILD_TYPE:=Release}
: ${LLVM_VERSION:=18}
: ${UBUNTU_VERSION:="22.04"}
: ${CMAKE_VERSION:="3.20.0"}
cd docker || exit 1 # Exit if the 'docker' directory doesn't exist
$DOCKER build \
--build-arg LLVM_VERSION=$LLVM_VERSION \
--build-arg CMAKE_VERSION=$CMAKE_VERSION \
--build-arg UBUNTU_VERSION=$UBUNTU_VERSION \
-t $IMAGE .
if [ $? -ne 0 ]; then
echo "Docker image build failed. Exiting."
exit 1
if [ $# -ne 1 -a $# -ne 2 ]; then
echo "Usage: build-with-docker.sh (20|21|22) [Debug|Release]" 1>&2
exit 1
fi
set -e
DOCKER=docker
DOCKER_RUN=""
IMAGE="c3c-builder"
if type podman 2>/dev/null >/dev/null; then
DOCKER=podman
DOCKER_RUN="--userns=keep-id"
IMAGE="localhost/$IMAGE"
fi
if [ -z "$2" ]; then
CMAKE_BUILD_TYPE=Debug
else
CMAKE_BUILD_TYPE="$2"
fi
TAG="$1"
if [ "$1" = 20 ]; then
UBUNTU_VERSION="20.04"
LLVM_VERSION="12"
elif [ "$1" = 21 ]; then
UBUNTU_VERSION="21.10"
LLVM_VERSION="13"
elif [ "$1" = 22 ]; then
UBUNTU_VERSION="22.04"
LLVM_VERSION="14"
else
echo "ERROR: expected 20, 21 or 22 as Ubuntu version argument" 1>&2
exit 2
fi
IMAGE="$IMAGE:$TAG"
cd docker && $DOCKER build -t $IMAGE --build-arg UID=$(id -u) --build-arg GID=$(id -g) \
--build-arg DEPS="llvm-$LLVM_VERSION-dev liblld-$LLVM_VERSION-dev clang-$LLVM_VERSION libllvm$LLVM_VERSION llvm-$LLVM_VERSION-runtime" \
--build-arg UBUNTU_VERSION="$UBUNTU_VERSION" .
cd ..
rm -rf build bin
mkdir -p build bin
chmod -R 777 build bin
exec $DOCKER run -i --rm \
-v "$PWD":/home/c3c/source \
-w /home/c3c/source $IMAGE bash -c \
"cmake -S . -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
-DCMAKE_C_COMPILER=clang-$LLVM_VERSION \
-DCMAKE_CXX_COMPILER=clang++-$LLVM_VERSION \
-DCMAKE_LINKER=lld-$LLVM_VERSION \
-DCMAKE_OBJCOPY=llvm-objcopy-$LLVM_VERSION \
-DCMAKE_STRIP=llvm-strip-$LLVM_VERSION \
-DCMAKE_DLLTOOL=llvm-dlltool-$LLVM_VERSION \
-DC3_LLVM_VERSION=auto && \
cmake --build build && \
cp -r build/c3c build/lib bin"
exec $DOCKER run -ti --rm --tmpfs=/tmp $DOCKER_RUN -v "$PWD":/home/c3c/source -w /home/c3c/source $IMAGE bash -c \
"cd build && cmake -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE -DC3_LLVM_VERSION=$LLVM_VERSION .. && cmake --build . && mv c3c lib ../bin/"

552
c3c.1
View File

@@ -1,552 +0,0 @@
.TH "c3c" "1" "2024-10-27" "C3 Compiler" "User Commands"
.SH NAME
c3c \- Compiler for the C3 programming language
.SH SYNOPSIS
.B c3c
[\fIoptions\fR ...] \fIcommand\fR [\fIargs\fR ...]
.SH DESCRIPTION
.B c3c
is the compiler for the C3 language, providing commands for compilation, project
management, testing, and distribution. The available commands allow users to
compile files, initialize projects, build targets, run benchmarks, clean build
files, and more.
.SH COMMANDS
.PP
.B c3c compile
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files without a project into an executable.
.RE
.PP
.B c3c init
\fIproject name\fR
.RS
Initialize a new project structure.
.RE
.PP
.B c3c init-lib
\fIlibrary name\fR
.RS
Initialize a new library structure.
.RE
.PP
.B c3c build
[\fItarget\fR]
.RS
Build the target in the current project.
.RE
.PP
.B c3c benchmark
.RS
Run the benchmarks in the current project.
.RE
.PP
.B c3c test
.RS
Run the unit tests in the current project.
.RE
.PP
.B c3c clean
.RS
Clean all build files.
.RE
.PP
.B c3c run
[\fItarget\fR] [-- [\fIarg1\fR ...]]
.RS
Run (and build if needed) the target in the current project.
.RE
.PP
.B c3c dist
[\fItarget\fR]
.RS
Clean and build a target for distribution.
.RE
.PP
.B c3c directives
[\fItarget\fR]
.RS
Generate documentation for the target.
.RE
.PP
.B c3c bench
[\fItarget\fR]
.RS
Benchmark a target.
.RE
.PP
.B c3c clean-run
[\fItarget\fR] [-- [\fIarg1\fR ...]]
.RS
Clean, then run the target.
.RE
.PP
.B c3c compile-run
\fIfile1\fR [\fIfile2\fR ...] [-- [\fIarg1\fR ...]]
.RS
Compile files then immediately run the result.
.RE
.PP
.B c3c compile-only
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files but do not perform linking.
.RE
.PP
.B c3c compile-benchmark
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files into an executable and run benchmarks.
.RE
.PP
.B c3c compile-test
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files into an executable and run unit tests.
.RE
.PP
.B c3c static-lib
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files without a project into a static library.
.RE
.PP
.B c3c dynamic-lib
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files without a project into a dynamic library.
.RE
.PP
.B c3c headers
\fIfile1\fR [\fIfile2\fR ...]
.RS
Analyze files and generate C headers for public methods.
.RE
.PP
.B c3c vendor-fetch
\fIlibrary\fR ...
.RS
Fetch one or more libraries from the vendor collection.
.RE
.PP
.B c3c project
\fIsubcommand\fR ...
.RS
Manipulate or view project files.
.RE
.SH OPTIONS
.PP
.B --stdlib
\fIdir\fR
.RS
Use this directory as the C3 standard library path.
.RE
.PP
.B --no-entry
.RS
Do not generate (or require) a main function.
.RE
.PP
.B --libdir
\fIdir\fR
.RS
Add this directory to the C3 library search paths.
.RE
.PP
.B --lib
\fIname\fR
.RS
Add this library to the compilation.
.RE
.PP
.B --path
\fIdir\fR
.RS
Use this as the base directory for the current command.
.RE
.PP
.B --template
\fItemplate\fR
.RS
Select template for 'init': "exe", "static-lib", "dynamic-lib" or a path.
.RE
.PP
.B --about
Prints a short description of C3.
.PP
.B --symtab
\fIvalue\fR
.RS
Sets the preferred symtab size.
.RE
.PP
.B --max-mem
\fIvalue\fR
.RS
Sets the preferred max memory size.
.RE
.PP
.B --run-once
.RS
After running the output file, delete it immediately.
.RE
.PP
.B -V, --version
Print version information.
.PP
.B -E
Lex only.
.PP
.B -P
Only parse and output the AST as JSON.
.PP
.B -C
Only lex, parse and check.
.PP
.B -
\fIcode\fR...
.RS
Read code from standard in.
.RE
.PP
.B -o
\fIfile\fR
.RS
Write output to \fIfile\fR.
.RE
.PP
.B -O0
Safe, no optimizations, emit debug info.
.PP
.B -O1
Safe, high optimization, emit debug info.
.PP
.B -O2
Unsafe, high optimization, emit debug info.
.PP
.B -O3
Unsafe, high optimization, single module, emit debug info.
.PP
.B -O4
Unsafe, highest optimization, relaxed maths, single module, emit debug info, no panic messages.
.PP
.B -O5
Unsafe, highest optimization, fast maths, single module, emit debug info, no panic messages, no backtrace.
.PP
.B -Os
Unsafe, high optimization, small code, single module, no debug info, no panic messages.
.PP
.B -Oz
Unsafe, high optimization, tiny code, single module, no debug info, no panic messages, no backtrace.
.PP
.B -D
\fIname\fR
.RS
Add feature flag \fIname\fR.
.RE
.PP
.B -U
\fIname\fR
.RS
Remove feature flag \fIname\fR.
.RE
.PP
.B --trust=
\fIoption\fR
.RS
Trust level: none (default), include ($include allowed), full ($exec / exec allowed).
.RE
.PP
.B --output-dir
\fIdir\fR
.RS
Override general output directory.
.RE
.PP
.B --threads
\fInumber\fR
.RS
Set the number of threads to use for compilation.
.RE
.PP
.B --show-backtrace=
\fIyes|no\fR
.RS
Show detailed backtrace on segfaults.
.RE
.PP
.B -g
Emit debug info.
.PP
.B -g0
Emit no debug info.
.PP
.B -l
\fIlibrary\fR
.RS
Link with the library provided.
.RE
.PP
.B -L
\fIlibrary\fR \fIdir\fR
.RS
Append the directory to the linker search paths.
.RE
.PP
.B -z
\fIargument\fR
.RS
Send the \fIargument\fR as a parameter to the linker.
.RE
.PP
.B --cc
\fIpath\fR
.RS
Set C compiler (for C files in projects and use as system linker).
.RE
.PP
.B --linker=
\fIoption\fR [\fIpath\fR]
.RS
Specify the linker: builtin, cc, custom (default is 'cc'). 'Custom' requires a path.
.RE
.PP
.B --use-stdlib=
\fIyes|no\fR
.RS
Include the standard library (default: yes).
.RE
.PP
.B --link-libc=
\fIyes|no\fR
.RS
Link libc and other default libraries (default: yes).
.RE
.PP
.B --emit-stdlib=
\fIyes|no\fR
.RS
Output files for the standard library (default: yes).
.RE
.PP
.B --panicfn
\fIname\fR
.RS
Override the panic function name.
.RE
.PP
.B --testfn
\fIname\fR
.RS
Override the test runner function name.
.RE
.PP
.B --benchfn
\fIname\fR
.RS
Override the benchmark runner function name.
.RE
.PP
.B --reloc=
\fIoption\fR
.RS
Specify the relocation model: none, pic, PIC, pie, PIE.
.RE
.PP
.B --x86cpu=
\fIoption\fR
.RS
Set general level of x64 CPU: baseline, ssse3, sse4, avx1, avx2-v1, avx2-v2 (Skylake/Zen1+), avx512 (Icelake/Zen4+), native.
.RE
.PP
.B --x86vec=
\fIoption\fR
.RS
Set maximum type of vector use: none, mmx, sse, avx, avx512, default.
.RE
.PP
.B --riscvfloat=
\fIoption\fR
.RS
Set type of RISC-V float support: none, float, double.
.RE
.PP
.B --memory-env=
\fIoption\fR
.RS
Set the memory environment: normal, small, tiny, none.
.RE
.PP
.B --strip-unused=
\fIyes|no\fR
.RS
Strip unused code and globals from the output (default: yes).
.RE
.PP
.B --fp-math=
\fIoption\fR
.RS
Specify floating-point math behavior: strict, relaxed, fast.
.RE
.PP
.B --win64-simd=
\fIoption\fR
.RS
Windows SIMD ABI: array, full.
.RE
.PP
.B --debug-stats
Print debug statistics.
.PP
.B --print-linking
Print linker arguments.
.PP
.B --debug-log
Print debug logging to stdout.
.PP
.B --benchmarking
Run built-in benchmarks.
.PP
.B --testing
Run built-in tests.
.PP
.B --list-attributes
List all attributes.
.PP
.B --list-builtins
List all builtins.
.PP
.B --list-keywords
List all keywords.
.PP
.B --list-operators
List all operators.
.PP
.B --list-precedence
List operator precedence order.
.PP
.B --list-project-properties
List all available keys used in project.json files.
.PP
.B --list-manifest-properties
List all available keys used in manifest.json files.
.PP
.B --list-targets
List all architectures the compiler supports.
.PP
.B --list-type-properties
List all type properties.
.PP
.B --print-output
Print the object files created to stdout.
.PP
.B --print-input
Print inputted C3 files to stdout.
.PP
.B --winsdk
\fIdir\fR
.RS
Set the directory for Windows system library files for cross-compilation.
.RE
.PP
.B --wincrt=
\fIoption\fR
.RS
Windows CRT linking: none, static-debug, static, dynamic-debug (default if debug info enabled), dynamic (default).
.RE
.PP
.B --windef
\fIfile\fR
.RS
Use Windows 'def' file for function exports instead of 'dllexport'.
.RE
.PP
.B --macossdk
\fIdir\fR
.RS
Set the directory for the MacOS SDK for cross-compilation.
.RE
.PP
.B --macos-min-version
\fIver\fR
.RS
Set the minimum MacOS version to compile for.
.RE
.PP
.B --macos-sdk-version
\fIver\fR
.RS
Set the MacOS SDK version to compile for.
.RE
.PP
.B --linux-crt
\fIdir\fR
.RS
Set the directory to use for finding crt1.o and related files.
.RE
.PP
.B --linux-crtbegin
\fIdir\fR
.RS
Set the directory to use for finding crtbegin.o and related files.
.RE
.PP
.B --vector-conv=
\fIoption\fR
.RS
Set vector conversion behavior: default, old.
.RE
.PP
.B --sanitize=
\fIoption\fR
.RS
Enable a sanitizer: address, memory, thread.
.RE
.SH EXAMPLES
.PP
Create a project:
.RS
.B c3c init new_project
.RE
.PP
Create a library project:
.RS
.B c3c init-lib new_library
.RE
.PP
Compile a file:
.RS
.B c3c compile main.c3
.RE
.PP
Build the current project:
.RS
.B c3c build
.RE
.PP
Run tests for the current project:
.RS
.B c3c test
.RE

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +1,16 @@
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION}
ARG LLVM_VERSION=18
ENV LLVM_DEV_VERSION=20
ARG UBUNTU_VERSION
FROM ubuntu:$UBUNTU_VERSION
ARG CMAKE_VERSION=3.20
ARG DEPS
RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ && \
wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-$CMAKE_VERSION-linux-x86_64.sh && \
mkdir -p /opt/cmake && \
sh cmake-${CMAKE_VERSION}-linux-x86_64.sh --prefix=/opt/cmake --skip-license && \
rm cmake-${CMAKE_VERSION}-linux-x86_64.sh && \
ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake
RUN export DEBIAN_FRONTEND=noninteractive && export TERM=xterm && apt-get update && apt-get install -y build-essential cmake zlib1g zlib1g-dev \
$DEPS && \
rm -rf /var/lib/apt/lists/*
RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \
if [ "${LLVM_VERSION}" -lt 18 ]; then \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev libmlir-${LLVM_VERSION} \
libmlir-${LLVM_VERSION}-dev mlir-${LLVM_VERSION}-tools; \
elif [ "${LLVM_VERSION}" -lt "${LLVM_DEV_VERSION}" ]; then \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} clang++-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
else \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
fi && \
rm -rf /var/lib/apt/lists/*
ARG GID=1000
ARG UID=1000
RUN groupadd -g 1337 c3c && \
useradd -m -u 1337 -g c3c c3c
RUN groupadd --gid=$GID c3c && useradd --gid=$GID --uid=$GID --create-home --shell /bin/bash c3c
# Add cmake to PATH for user c3c
USER c3c
ENV PATH="/opt/cmake/bin:${PATH}"
WORKDIR /home/c3c

61
flake.lock generated
View File

@@ -1,61 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1738297584,
"narHash": "sha256-AYvaFBzt8dU0fcSK2jKD0Vg23K2eIRxfsVXIPCW9a0E=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "9189ac18287c599860e878e905da550aa6dec1cd",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -1,44 +0,0 @@
{
description = "C3 compiler flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem
(system:
let pkgs = import inputs.nixpkgs { inherit system; };
call = set: pkgs.callPackage ./nix/default.nix (
set // {
rev = self.rev or "unknown";
}
);
in {
packages = {
default = self.packages.${system}.c3c;
c3c = call {};
c3c-checks = pkgs.callPackage ./nix/default.nix {
checks = true;
};
c3c-debug = pkgs.callPackage ./nix/default.nix {
debug = true;
};
c3c-debug-checks = pkgs.callPackage ./nix/default.nix {
debug = true;
checks = true;
};
};
devShells = {
default = pkgs.callPackage ./nix/shell.nix {
c3c = self.packages.${system}.c3c-debug;
};
};
}
);
}

View File

@@ -1,15 +0,0 @@
find_package(Git QUIET)
set(GIT_HASH "unknown")
if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git")
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY)
endif()
message("Git Hash: ${GIT_HASH}")
file(WRITE ${CMAKE_BINARY_DIR}/git_hash.h "#pragma once\n#define GIT_HASH \"${GIT_HASH}\"\n")

17
install_win_reqs.bat Normal file
View File

@@ -0,0 +1,17 @@
@echo off
set DOWNLOAD_URL=https://aka.ms/vs/17/release
mkdir tmp 2> NUL
if not exist "tmp\vs_buildtools.exe" (
bitsadmin /transfer /download /priority foreground %DOWNLOAD_URL%/vs_buildtools.exe %CD%\tmp\vs_buildtools.exe
)
echo Preparing Build Tools, please wait...
tmp\vs_BuildTools.exe --quiet --wait --layout tmp\ --add Microsoft.VisualStudio.Component.Windows10SDK.19041
echo Installing Build Tools, please wait...
tmp\vs_BuildTools.exe --quiet --wait --noweb --add Microsoft.VisualStudio.Component.Windows10SDK.19041
REM rmdir tmp /s /q

View File

@@ -1,77 +0,0 @@
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);

View File

@@ -1,531 +1,5 @@
// Copyright (c) 2023-2025 Eduardo José Gómez Hernández. All rights reserved.
// 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::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_max, 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, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_or, data, value, ordering);
}
fn Type Atomic.xor(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
}
macro Type Atomic.and(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_and, data, value, ordering);
}
macro Type Atomic.shr(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
}
macro Type Atomic.shl(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
}
macro Type Atomic.set(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
{
Type* data = &self.data;
return @atomic_exec_no_arg(atomic::flag_set, data, ordering);
}
macro Type Atomic.clear(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
{
Type* data = &self.data;
return @atomic_exec_no_arg(atomic::flag_clear, data, 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.");
}
}
macro @atomic_exec_no_arg(#func, data, ordering) @local
{
switch(ordering)
{
case RELAXED: return #func(data, RELAXED);
case ACQUIRE: return #func(data, ACQUIRE);
case RELEASE: return #func(data, RELEASE);
case ACQUIRE_RELEASE: return #func(data, ACQUIRE_RELEASE);
case SEQ_CONSISTENT: return #func(data, SEQ_CONSISTENT);
default: unreachable("Ordering may not be non-atomic or unordered.");
}
}
module std::atomic;
import std::math;
macro bool @is_native_atomic_value(#value) @private
{
return is_native_atomic_type($typeof(#value));
}
macro bool is_native_atomic_type($Type)
{
$if $Type.sizeof > void*.sizeof:
return false;
$else
$switch $Type.kindof:
$case SIGNED_INT:
$case UNSIGNED_INT:
$case POINTER:
$case FLOAT:
$case BOOL:
return true;
$case DISTINCT:
return is_native_atomic_type($typefrom($Type.inner));
$default:
return false;
$endswitch
$endif
}
<*
@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 $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr + y) : "+ must be defined between the values."
@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 subtracted from 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 $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr - y) : "- must be defined between the values."
@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 multiplied with ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr * y) : "* must be defined between the values."
@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 divide ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr * y) : "/ must be defined between the values."
@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 perform a bitwise or with."
@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 $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr | y) : "| must be defined between the values."
@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)
{
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise xor with."
@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 $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "^ must be defined between the values."
@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)
{
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise and with."
@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 $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "& must be defined between the values."
@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)
{
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to shift ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift right must be an integer"
@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 shift ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift left must be an integer"
@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 $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool 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;
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
do
{
old_value = $$atomic_load(ptr, false, $load_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 $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool 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;
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
do
{
old_value = $$atomic_load(ptr, false, $load_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 compared to ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr > y) : "Only values that are comparable with > 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 compared to ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr > y) : "Only values that are comparable with > 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);
}

View File

@@ -1,64 +0,0 @@
// 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) @weak @export("__atomic_compare_exchange")
{
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;
}

View File

@@ -1,171 +1,78 @@
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 popcount(i) @operator(intvec)
{
return $$popcount(i);
}
<*
@require types::is_intlike($typeof(i)) : `The input must be an integer or integer vector`
*>
macro bswap(i) @builtin => $$bswap(i);
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro reverse(i)
{
return $$bitreverse(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);
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro bswap(i) @builtin
{
return $$bswap(i);
}
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);
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro ctz(i) @operator(intvec) @builtin
{
return $$ctz(i);
}
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);
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro clz(i) @operator(intvec) @builtin
{
return $$clz(i);
}
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);
/**
* @require types::is_intlike($typeof(hi)) && types::is_intlike($typeof(lo)) && types::is_intlike($typeof(shift)) `The input must be an integer or integer vector`
* @require types::@has_same(hi, lo, shift) `Hi, low and shift arguments must have the same type`
**/
macro fshl(hi, lo, shift) @builtin
{
return $$fshl(hi, lo, ($typeof(hi))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);
/**
* @require types::is_intlike($typeof(hi)) && types::is_intlike($typeof(lo)) && types::is_intlike($typeof(shift)) `The input must be an integer or integer vector`
* @require types::@has_same(hi, lo, shift) `Hi, low and shift arguments must have the same type`
**/
macro fshr(hi, lo, shift) @builtin
{
return $$fshr(hi, lo, ($typeof(hi))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);
/**
* @require types::is_intlike($typeof(i)) && types::is_intlike($typeof(shift)) `The input must be an integer or integer vector`
* @require types::@has_same(i, shift) `The shift value must have the same type as shifted types`
**/
macro rotl(i, shift) @operator(intvec) @builtin
{
return $$fshl(i, i, 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);
/**
* @require types::is_intlike($typeof(i)) && types::is_intlike($typeof(shift)) `The input must be an integer or integer vector`
* @require types::@has_same(i, shift) `The shift value must have the same type as shifted types`
**/
macro rotr(i, shift) @operator(intvec) @builtin
{
return $$fshr(i, i, 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);

View File

@@ -1,604 +0,0 @@
// 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;
alias AnyPredicate = fn bool(any value);
alias AnyTest = fn bool(any type, any context);
<*
The AnyList contains a heterogenous set of types. Anything placed in the
list will shallowly copied in order to be stored as an `any`. This means
that the list will copy and free its elements.
However, because we're getting `any` values back when we pop, those operations
need to take an allocator, as we can only copy then pop then return the copy.
If we're not doing pop, then things are easier, since we can just hand over
the existing any.
*>
struct AnyList (Printable)
{
usz size;
usz capacity;
Allocator allocator;
any* entries;
}
<*
Initialize the list. If not initialized then it will use the temp allocator
when something is pushed to it.
@param [&inout] allocator : "The allocator to use"
@param initial_capacity : "The initial capacity to reserve, defaults to 16"
*>
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 bool AnyList.is_initialized(&self) @inline => self.allocator != null;
<*
Push an element on the list by cloning it.
*>
macro void AnyList.push(&self, element)
{
if (!self.allocator) self.allocator = tmem;
self._append(allocator::clone(self.allocator, 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.
@param $Type : "The type we assume the value has"
@return "The last value as the type given"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
macro AnyList.pop(&self, $Type)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return *anycast(self.entries[--self.size], $Type);
}
<*
Copy the last value, pop it and return the copy of it.
@param [&inout] allocator : "The allocator to use for copying"
@return "A copy of the last value if it exists"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.copy_pop(&self, Allocator allocator)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return allocator::clone_any(allocator, self.entries[--self.size]);
}
<*
Copy the last value, pop it and return the copy of it.
@return "A temp copy of the last value if it exists"
@return? 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 "The last value if it exists"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.pop_retained(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
<*
Remove all elements in the list.
*>
fn void AnyList.clear(&self)
{
for (usz i = 0; i < self.size; i++)
{
self.free_element(self.entries[i]);
}
self.size = 0;
}
<*
Pop a value who's type is known. If the type is incorrect, this
will still pop the element.
@param $Type : "The type we assume the value has"
@return "The first value as the type given"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
macro AnyList.pop_first(&self, $Type)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return *anycast(self.entries[0], $Type);
}
<*
Pop the first value. It must later be released using `list.free_element()`.
@return "The first value if it exists"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.pop_first_retained(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
<*
Copy the first value, pop it and return the copy of it.
@param [&inout] allocator : "The allocator to use for copying"
@return "A copy of the first value if it exists"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.copy_pop_first(&self, Allocator allocator)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
defer self.remove_at(0);
return allocator::clone_any(allocator, self.entries[0]);
}
<*
Copy the first value, pop it and return the temp copy of it.
@return "A temp copy of the first value if it exists"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.tcopy_pop_first(&self) => self.copy_pop_first(tmem);
<*
Remove the element at the particular index.
@param index : "The index of the element to remove"
@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];
}
<*
Add all the elements in another AnyList.
@param [&in] other_list : "The list to add"
*>
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 order of the elements in the 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);
}
}
<*
Return a view of the data as a slice.
@return "The slice view"
*>
fn any[] AnyList.array_view(&self)
{
return self.entries[:self.size];
}
<*
Push an element to the front of the list.
@param value : "The value to push to the list"
*>
macro void AnyList.push_front(&self, value)
{
self.insert_at(0, value);
}
<*
Insert an element at a particular index.
@param index : "the index where the element should be inserted"
@param type : "the value to insert"
@require index <= self.size : "The index is out of bounds"
*>
macro void AnyList.insert_at(&self, usz index, type)
{
if (index == self.size)
{
self.push(type);
return;
}
any value = allocator::copy(self.allocator, type);
self._insert_at(self, index, value);
}
<*
Remove the last element in the list. The list may not be empty.
@require self.size > 0 : "The list was already empty"
*>
fn void AnyList.remove_last(&self)
{
self.free_element(self.entries[--self.size]);
}
<*
Remove the first element in the list, the list may not be empty.
@require self.size > 0
*>
fn void AnyList.remove_first(&self)
{
self.remove_at(0);
}
<*
Return the first element by value, assuming it is the given type.
@param $Type : "The type of the first element"
@return "The first element"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
macro AnyList.first(&self, $Type)
{
return *anycast(self.first_any(), $Type);
}
<*
Return the first element
@return "The first element"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.first_any(&self) @inline
{
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
}
<*
Return the last element by value, assuming it is the given type.
@param $Type : "The type of the last element"
@return "The last element"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
macro AnyList.last(&self, $Type)
{
return *anycast(self.last_any(), $Type);
}
<*
Return the last element
@return "The last element"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.last_any(&self) @inline
{
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?;
}
<*
Return whether the list is empty.
@return "True if the list is empty"
*>
fn bool AnyList.is_empty(&self) @inline
{
return !self.size;
}
<*
Return the length of the list.
@return "The number of elements in the list"
*>
fn usz AnyList.len(&self) @operator(len) @inline
{
return self.size;
}
<*
Return an element in the list by value, assuming it is the given type.
@param index : "The index of the element to retrieve"
@param $Type : "The type of the element"
@return "The element at the index"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
@require index < self.size : "Index out of range"
*>
macro AnyList.get(&self, usz index, $Type)
{
return *anycast(self.entries[index], $Type);
}
<*
Return an element in the list.
@param index : "The index of the element to retrieve"
@return "The element at the index"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
@require index < self.size : "Index out of range"
*>
fn any AnyList.get_any(&self, usz index) @inline @operator([])
{
return self.entries[index];
}
<*
Completely free and clear a list.
*>
fn void AnyList.free(&self)
{
if (!self.allocator) return;
self.clear();
allocator::free(self.allocator, self.entries);
self.capacity = 0;
self.entries = null;
}
<*
Swap two elements in a list.
@param i : "Index of one of the elements"
@param j : "Index of the other element"
@require i < self.size : "The first index is out of range"
@require j < self.size : "The second index is out of range"
*>
fn void AnyList.swap(&self, usz i, usz j)
{
any temp = self.entries[i];
self.entries[i] = self.entries[j];
self.entries[j] = temp;
}
<*
Print the list to a formatter.
*>
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;
}
}
<*
Remove any elements matching the predicate.
@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);
}
<*
Retain the elements matching the predicate.
@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);
}
<*
Remove any elements matching the predicate.
@param filter : "The function to determine if it should be removed or not"
@param context : "The context to the function"
@return "the number of deleted elements"
*>
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
{
return self._remove_using_test(filter, false, context);
}
<*
Retain any elements matching the predicate.
@param selection : "The function to determine if it should be retained or not"
@param context : "The context to the function"
@return "the number of deleted elements"
*>
fn usz AnyList.retain_using_test(&self, AnyTest selection, any context)
{
return self._remove_using_test(selection, true, context);
}
<*
Reserve memory so that at least the `min_capacity` exists.
@param min_capacity : "The min capacity to hold"
*>
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;
}
<*
Set the element at any index.
@param index : "The index where to set the value."
@param value : "The value to set"
@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);
}
// -- private
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);
}
fn void AnyList._append(&self, any element) @local
{
self.ensure_capacity();
self.entries[self.size++] = element;
}
<*
@require index < self.size
*>
fn void AnyList._insert_at(&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;
}
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;
}
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;
}

View File

@@ -1,251 +0,0 @@
// Copyright (c) 2023-2025 C3 team. 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 SIZE > 0 : "The size of the bitset in bits must be at least 1"
*>
module std::collections::bitset {SIZE};
const BITS = uint.sizeof * 8;
const SZ = (SIZE + BITS - 1) / BITS;
struct BitSet
{
uint[SZ] data;
}
<*
@return "The number of bits set"
*>
fn usz BitSet.cardinality(&self)
{
usz n;
foreach (x : self.data)
{
n += x.popcount();
}
return n;
}
<*
Set a bit in the bitset.
@param i : "The index to set"
@require i < SIZE : "Index was out of range"
*>
fn void BitSet.set(&self, usz i)
{
usz q = i / BITS;
usz r = i % BITS;
self.data[q] |= 1 << r;
}
<*
Perform xor over all bits, mutating itself
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
macro BitSet BitSet.xor_self(&self, BitSet set) @operator(^=)
{
foreach (i, &x : self.data) *x ^= set.data[i];
return *self;
}
<*
Perform xor over all bits, returning a new bit set.
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
fn BitSet BitSet.xor(&self, BitSet set) @operator(^)
{
BitSet new_set @noinit;
foreach (i, x : self.data) new_set.data[i] = x ^ set.data[i];
return new_set;
}
<*
Perform or over all bits, returning a new bit set.
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
fn BitSet BitSet.or(&self, BitSet set) @operator(|)
{
BitSet new_set @noinit;
foreach (i, x : self.data) new_set.data[i] = x | set.data[i];
return new_set;
}
<*
Perform or over all bits, mutating itself
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
macro BitSet BitSet.or_self(&self, BitSet set) @operator(|=)
{
foreach (i, &x : self.data) *x |= set.data[i];
return *self;
}
<*
Perform & over all bits, returning a new bit set.
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
fn BitSet BitSet.and(&self, BitSet set) @operator(&)
{
BitSet new_set @noinit;
foreach (i, x : self.data) new_set.data[i] = x & set.data[i];
return new_set;
}
<*
Perform & over all bits, mutating itself.
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
macro BitSet BitSet.and_self(&self, BitSet set) @operator(&=)
{
foreach (i, &x : self.data) *x &= set.data[i];
return *self;
}
<*
Unset (clear) a bit in the bitset.
@param i : "The index to set"
@require i < SIZE : "Index was out of range"
*>
fn void BitSet.unset(&self, usz i)
{
usz q = i / BITS;
usz r = i % BITS;
self.data[q] &= ~(1 << r);
}
<*
Get a particular bit in the bitset
@param i : "The index of the bit"
@require i < SIZE : "Index was out of range"
*>
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;
}
<*
Change a particular bit in the bitset
@param i : "The index of the bit"
@param value : "The value to set the bit to"
@require i < SIZE : "Index was out of range"
*>
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;
alias 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);
}

View File

@@ -1,429 +0,0 @@
// 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;
alias ElementPredicate = fn bool(Type *type);
alias 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 mem::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 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 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 mem::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 NO_MORE_ELEMENT?;
self.size--;
}
fn void? ElasticArray.remove_first(&self) @maydiscard
{
if (!self.size) return NO_MORE_ELEMENT?;
self.remove_at(0);
}
fn Type? ElasticArray.first(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[0];
}
fn Type? ElasticArray.last(&self)
{
if (!self.size) return 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 NOT_FOUND?;
}
fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach_r (i, v : self)
{
if (equals(v, type)) return i;
}
return NOT_FOUND?;
}
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);
}

View File

@@ -1,57 +0,0 @@
<*
@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;
}

View File

@@ -1,160 +0,0 @@
// 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;
alias EnumSetType @private = $typefrom(type_for_enum_elements(Enum.elements));
const IS_CHAR_ARRAY = Enum.elements > 128;
typedef 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
}

View File

@@ -1,592 +0,0 @@
// 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);
const Allocator MAP_HEAP_ALLOCATOR = (Allocator)&dummy;
const HashMap ONHEAP = { .allocator = MAP_HEAP_ALLOCATOR };
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.is_initialized() : "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.is_initialized() : "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.is_initialized() : "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.is_initialized() : "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.is_initialized() : "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.is_initialized() : "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 map.allocator && map.allocator.ptr != &dummy;
}
<*
@param [&inout] allocator : "The allocator to use"
@param [&in] other_map : "The map to copy from."
@require !self.is_initialized() : "Map was already initialized"
*>
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."
@require !map.is_initialized() : "Map was already initialized"
*>
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 NOT_FOUND?;
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 NOT_FOUND?;
}
fn Entry*? HashMap.get_entry(&map, Key key)
{
if (!map.count) return NOT_FOUND?;
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 NOT_FOUND?;
}
<*
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.
switch (map.allocator.ptr)
{
case &dummy:
map.init(mem);
case null:
map.tinit();
default:
break;
}
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 NOT_FOUND?;
}
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.is_initialized()) 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;
}
typedef HashMapValueIterator = HashMapIterator;
typedef 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);
}
int dummy @local;

View File

@@ -1,336 +0,0 @@
// 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;
}
fn bool LinkedList.is_initialized(&self) @inline => self.allocator != null;
<*
@require self.is_initialized()
*>
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 NO_MORE_ELEMENT?;
return self._first.value;
}
fn Type? LinkedList.last(&self)
{
if (!self._last) return 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 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 NO_MORE_ELEMENT?;
defer self.unlink_first();
return self._first.value;
}
fn void? LinkedList.remove_last(&self) @maydiscard
{
if (!self._first) return NO_MORE_ELEMENT?;
self.unlink_last();
}
fn void? LinkedList.remove_first(&self) @maydiscard
{
if (!self._first) return 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--;
}

View File

@@ -1,558 +0,0 @@
// 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;
alias ElementPredicate = fn bool(Type *type);
alias ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR };
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.is_initialized() : "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 bool List.is_initialized(&self) @inline => self.allocator != null && self.allocator != (Allocator)&dummy;
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 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 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 NO_MORE_ELEMENT?;
self.set_size(self.size - 1);
}
fn void? List.remove_first(&self) @maydiscard
{
if (!self.size) return NO_MORE_ELEMENT?;
self.remove_at(0);
}
fn Type? List.first(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[0];
}
fn Type? List.last(&self)
{
if (!self.size) return 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.allocator.ptr == &dummy || !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;
// Get a proper allocator
switch (self.allocator.ptr)
{
case &dummy:
self.allocator = mem;
case null:
self.allocator = tmem;
default:
break;
}
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;
$if env::ADDRESS_SANITIZER:
if (self.allocator.ptr != &allocator::LIBC_ALLOCATOR) return;
sanitizer::annotate_contiguous_container(self.entries,
&self.entries[self.capacity],
&self.entries[old_size],
&self.entries[new_size]);
$endif
}
<*
@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 NOT_FOUND?;
}
fn usz? List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach_r (i, v : self)
{
if (equals(v, type)) return i;
}
return NOT_FOUND?;
}
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);
}
int dummy @local;

View File

@@ -1,112 +0,0 @@
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;
}

View File

@@ -1,36 +0,0 @@
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 : NOT_FOUND?;
}

View File

@@ -1,468 +0,0 @@
// 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 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() ? NOT_FOUND? : 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 string::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 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 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 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 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 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 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 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 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;
}
alias ObjectInternalMap @private = HashMap {String, Object*};
alias ObjectInternalList @private = List {Object*};
alias ObjectInternalMapEntry @private = Entry {String, Object*};

View File

@@ -1,155 +0,0 @@
// 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;
typedef PriorityQueue = inline PrivatePriorityQueue{Type, false};
typedef 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 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);
}

View File

@@ -1,65 +0,0 @@
<*
@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);
}

View File

@@ -1,115 +0,0 @@
<*
@require Type.kindof == ARRAY : "Required an array type"
*>
module std::collections::ringbuffer{Type};
import std::io;
alias 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 NO_MORE_ELEMENT?;
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;
}
}

View File

@@ -1,16 +0,0 @@
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;
}

View File

@@ -1,473 +0,0 @@
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.
*>
faultdef 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`
@return? FILE_OPEN_FAILED, INVALID_DATA, TOO_MANY_PIXELS
*>
fn char[]? read(Allocator allocator, String filename, QOIDesc* desc, QOIChannels channels = AUTO) => @pool()
{
// read file
char[] data = file::load_temp(filename) ?? 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`
@return? INVALID_PARAMETERS, TOO_MANY_PIXELS, INVALID_DATA
*>
fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
{
// check info in desc
if (desc.width == 0 || desc.height == 0) return INVALID_PARAMETERS?;
if (desc.channels == AUTO) return INVALID_PARAMETERS?;
uint pixels = desc.width * desc.height;
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
// check input data size
uint image_size = pixels * desc.channels.id;
if (image_size != input.len) return 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`
@return? INVALID_DATA, TOO_MANY_PIXELS
*>
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 INVALID_DATA?;
// get header
Header* header = (Header*)data.ptr;
// check magic bytes (FourCC)
if (bswap(header.be_magic) != 'qoif') return 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 INVALID_DATA?; // Channels must be specified in the header
// check width and height
if (desc.width == 0 || desc.height == 0) return INVALID_DATA?;
// check pixel count
ulong pixels = (ulong)desc.width * (ulong)desc.height;
if (pixels > PIXELS_MAX) return 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
<*
@return? INVALID_DATA
*>
macro @enumcast($Type, raw)
{
foreach (value : $Type.values)
{
if (value.id == raw) return value;
}
return INVALID_DATA?;
}
typedef 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;
}

View File

@@ -1,151 +1,119 @@
// Copyright (c) 2023-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;
// The arena allocator allocates up to its maximum data
// and then fails to allocate more, returning out of memory.
// It supports mark and reset to mark.
struct ArenaAllocator (Allocator)
struct ArenaAllocatorHeader
{
char[] data;
usz used;
}
<*
Initialize a memory arena for use using the provided bytes.
@param [inout] data : "The memory to use for the arena."
*>
fn ArenaAllocator* ArenaAllocator.init(&self, char[] data)
{
self.data = data;
self.used = 0;
return self;
}
<*
Reset the usage completely.
*>
fn void ArenaAllocator.clear(&self)
{
self.used = 0;
}
<*
Given some memory, create an arena allocator on the stack for it.
@param [inout] bytes : `The bytes to use`
@return `An arena allocator using the bytes`
*>
macro ArenaAllocator* wrap(char[] bytes)
{
return (ArenaAllocator){}.init(bytes);
}
<*
"Mark" the current state of the arena allocator by returning the use count.
@return `The value to pass to 'reset' in order to reset to the current use.`
*>
fn usz ArenaAllocator.mark(&self) => self.used;
<*
Reset to a previous mark.
@param mark : `The previous mark.`
@require mark <= self.used : "Invalid mark - out of range"
*>
fn void ArenaAllocator.reset(&self, usz mark) => self.used = mark;
<*
Implements the Allocator interface method.
@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;
}
}
<*
Implements the Allocator interface method.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
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 mem::INVALID_ALLOC_SIZE?;
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 mem::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;
}
<*
Implements the Allocator interface method.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require old_pointer != null
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
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 mem::INVALID_ALLOC_SIZE?;
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 mem::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;
}
// Internal data
struct ArenaAllocatorHeader @local
{
usz size;
usize size;
char[*] data;
}
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require data `unexpectedly missing the allocator`
*/
private fn void*! arena_allocator_function(Allocator* data, usize size, usize alignment, usize offset, void* old_pointer, AllocationKind kind)
{
ArenaAllocator* arena = (ArenaAllocator*)data;
bool clear = false;
switch (kind)
{
case CALLOC:
case ALIGNED_CALLOC:
clear = true;
nextcase;
case ALLOC:
case ALIGNED_ALLOC:
assert(!old_pointer, "Unexpected old pointer for alloc.");
if (!size) return null;
alignment = alignment_for_allocation(alignment);
void* mem = arena._alloc(size, alignment, offset)?;
if (clear) mem::clear(mem, size, DEFAULT_MEM_ALIGNMENT);
return mem;
case ALIGNED_REALLOC:
case REALLOC:
if (!size) nextcase FREE;
if (!old_pointer) nextcase ALLOC;
alignment = alignment_for_allocation(alignment);
return arena._realloc(old_pointer, size, alignment, offset)?;
case ALIGNED_FREE:
case FREE:
if (!old_pointer) return null;
assert((uptr)old_pointer >= (uptr)arena.data.ptr, "Pointer originates from a different allocator.");
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
// Reclaim memory if it's the last element.
if (old_pointer + header.size == &arena.data[arena.used])
{
arena.used -= header.size + ArenaAllocatorHeader.sizeof;
}
return null;
case MARK:
return (void*)(uptr)arena.used;
case RESET:
arena.used = size;
return null;
}
unreachable();
}
/**
* @require alignment > 0 `alignment must be non zero`
* @require math::is_power_of_2(alignment)
* @require size > 0
* @require alignment <= MAX_MEMORY_ALIGNMENT `alignment too big`
* @require offset <= MAX_MEMORY_ALIGNMENT `offset too big`
* @require offset <= size && offset >= 0
* @require mem::aligned_offset(offset, $alignof(ArenaAllocatorHeader)) == offset
* @require this != null
**/
private fn void*! ArenaAllocator._alloc(ArenaAllocator* this, usize size, usize alignment, usize offset)
{
usize total_len = this.data.len;
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE!;
void* start_mem = this.data.ptr;
void* unaligned_pointer_to_offset = start_mem + this.used + ArenaAllocatorHeader.sizeof + offset;
void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usize end = (usize)(aligned_pointer_to_offset - this.data.ptr) + size - offset;
if (end > total_len) return AllocationFailure.OUT_OF_MEMORY!;
this.used = end;
void *mem = aligned_pointer_to_offset - offset;
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
header.size = size;
return mem;
}
/**
* @require alignment > 0 `alignment must be non zero`
* @require math::is_power_of_2(alignment)
* @require size > 0
* @require alignment <= MAX_MEMORY_ALIGNMENT `alignment too big`
* @require offset <= MAX_MEMORY_ALIGNMENT `offset too big`
* @require offset <= size && offset >= 0
* @require mem::aligned_offset(offset, $alignof(ArenaAllocatorHeader)) == offset
* @require this != null
**/
private fn void*! ArenaAllocator._realloc(ArenaAllocator* this, void *old_pointer, usize size, usize alignment, usize offset)
{
assert(old_pointer >= this.data.ptr, "Pointer originates from a different allocator.");
usize total_len = this.data.len;
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE!;
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
usize old_size = header.size;
// Do last allocation and alignment match?
if (&this.data[this.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer + offset, alignment))
{
if (old_size >= size)
{
this.used -= old_size - size;
}
else
{
usize new_used = this.used + size - old_size;
if (new_used > total_len) return AllocationFailure.OUT_OF_MEMORY!;
this.used = new_used;
}
header.size = size;
return old_pointer;
}
// Otherwise just allocate new memory.
void* mem = this._alloc(size, alignment, offset)?;
mem::copy(mem, old_pointer, old_size, DEFAULT_MEM_ALIGNMENT, DEFAULT_MEM_ALIGNMENT);
return mem;
}

View File

@@ -1,210 +0,0 @@
module std::core::mem::allocator;
import std::io, std::math;
struct AllocChunk @local
{
usz size;
char[*] data;
}
struct BackedArenaAllocator (Allocator)
{
Allocator backing_allocator;
ExtraPage* last_page;
usz used;
usz capacity;
char[*] data;
}
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
struct ExtraPage @local
{
ExtraPage* prev_page;
void* start;
usz mark;
usz size;
usz ident;
char[*] data;
}
macro usz ExtraPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
macro bool ExtraPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
<*
@require size >= 16
*>
fn BackedArenaAllocator*? new_backed_allocator(usz size, Allocator allocator)
{
BackedArenaAllocator* temp = allocator::alloc_with_padding(allocator, BackedArenaAllocator, size)!;
temp.last_page = null;
temp.backing_allocator = allocator;
temp.used = 0;
temp.capacity = size;
return temp;
}
fn void BackedArenaAllocator.destroy(&self)
{
self.reset(0);
if (self.last_page) (void)self._free_page(self.last_page);
allocator::free(self.backing_allocator, self);
}
fn usz BackedArenaAllocator.mark(&self) => self.used;
fn void BackedArenaAllocator.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 BackedArenaAllocator.reset(&self, usz mark)
{
ExtraPage *last_page = self.last_page;
while (last_page && last_page.mark > mark)
{
self.used = last_page.mark;
ExtraPage *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? BackedArenaAllocator._free_page(&self, ExtraPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
}
fn void*? BackedArenaAllocator._realloc_page(&self, ExtraPage* 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.
ExtraPage **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*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
{
AllocChunk *chunk = pointer - AllocChunk.sizeof;
if (chunk.size == (usz)-1)
{
assert(self.last_page, "Realloc of unrelated pointer");
// First grab the page
ExtraPage *page = pointer - ExtraPage.sizeof;
return self._realloc_page(page, size, alignment);
}
AllocChunk* 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*? BackedArenaAllocator.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, AllocChunk.alignof);
void* mem = aligned_header_start + AllocChunk.sizeof;
if (alignment > AllocChunk.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);
AllocChunk* chunk_start = mem - AllocChunk.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
ExtraPage* 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(ExtraPage.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(ExtraPage.sizeof, alignment);
page = (ExtraPage*)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(ExtraPage.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 - ExtraPage.sizeof;
assert(mem::ptr_is_aligned(page, BackedArenaAllocator.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];
}

View File

@@ -1,211 +1,198 @@
// 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, Allocator allocator, usz page_size)
{
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
private struct DynamicArenaPage
{
void* memory;
void* prev_arena;
usz total;
usz used;
void* current_stack_ptr;
usize total;
usize used;
void* last_ptr;
}
struct DynamicArenaChunk @local
private struct DynamicArenaChunk
{
usz size;
usize size;
}
<*
@require ptr != null
@require self.page != null : `tried to free pointer on invalid allocator`
*>
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
/**
* @require ptr && this
* @require this.page `tried to free pointer on invalid allocator`
*/
private fn void DynamicArenaAllocator.free(DynamicArenaAllocator* this, void* ptr)
{
DynamicArenaPage* current_page = self.page;
if (ptr == current_page.current_stack_ptr)
DynamicArenaPage* current_page = this.page;
if (ptr == current_page.last_ptr)
{
current_page.used = (usz)((ptr - DEFAULT_SIZE_PREFIX) - current_page.memory);
current_page.used = (usize)((ptr - DEFAULT_SIZE_PREFIX) - current_page.memory);
}
current_page.current_stack_ptr = null;
current_page.last_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`
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
/**
* @require old_pointer && size > 0
* @require this.page `tried to realloc pointer on invalid allocator`
*/
private fn void*! DynamicArenaAllocator._realloc(DynamicArenaAllocator* this, void* old_pointer, usize size, usize alignment, usize offset)
{
DynamicArenaPage* current_page = self.page;
DynamicArenaPage* current_page = this.page;
alignment = alignment_for_allocation(alignment);
usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
usz old_size = *old_size_ptr;
usize* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
usize 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)
if (current_page.last_ptr == old_pointer)
{
current_page.used = (usz)((old_pointer - DEFAULT_SIZE_PREFIX) - current_page.memory);
current_page.used = (usize)((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))
if REUSE: (current_page.last_ptr == old_pointer && mem::ptr_is_aligned(old_pointer, alignment))
{
assert(size > old_size);
usz add_size = size - old_size;
usize 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);
void* new_mem = this._alloc(size, alignment, offset)?;
mem::copy(new_mem, old_pointer, old_size, DEFAULT_MEM_ALIGNMENT);
return new_mem;
}
fn void DynamicArenaAllocator.reset(&self)
private fn void DynamicArenaAllocator.reset(DynamicArenaAllocator* this)
{
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;
DynamicArenaPage* page = this.page;
DynamicArenaPage** unused_page_ptr = &this.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;
}
this.page = page;
}
<*
@require math::is_power_of_2(alignment)
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @local
/**
* @require math::is_power_of_2(alignment)
* @require size > 0
*/
private fn void*! DynamicArenaAllocator._alloc_new(DynamicArenaAllocator* this, usize size, usize alignment, usize offset)
{
// 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);
usize page_size = max(this.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + offset, alignment) - offset);
// 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);
void* mem = this.backing_allocator.alloc(page_size)?;
DynamicArenaPage*! page = this.backing_allocator.alloc(DynamicArenaPage.sizeof);
if (catch err = page)
{
allocator::free(self.backing_allocator, mem);
return err?;
this.backing_allocator.free(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.memory = mem;
void* mem_start = mem::aligned_pointer(mem + offset + DynamicArenaChunk.sizeof, alignment) - offset;
assert(mem_start + size < mem + page_size);
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
chunk.size = size;
page.prev_arena = this.page;
page.total = page_size;
page.used = mem_start + size - page.memory;
self.page = page;
page.current_stack_ptr = mem_start;
this.page = page;
page.last_ptr = mem_start;
return mem_start;
}
<*
@require size > 0 : `acquire expects size > 0`
@require !alignment || math::is_power_of_2(alignment)
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require size > 0
* @require this
*/
private fn void*! DynamicArenaAllocator._alloc(DynamicArenaAllocator* this, usize size, usize alignment, usize offset)
{
alignment = alignment_for_allocation(alignment);
DynamicArenaPage* page = self.page;
void* ptr @noinit;
do SET_DONE:
DynamicArenaPage* page = this.page;
if (!page && this.unused_page)
{
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;
this.page = page = this.unused_page;
this.unused_page = page.prev_arena;
page.prev_arena = null;
}
if (!page) return this._alloc_new(size, alignment, offset);
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
usize new_used = start - page.memory + size;
if ALLOCATE_NEW: (new_used > page.total)
{
if ((page = this.unused_page))
{
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
new_used = start + size - page.memory;
if (page.total >= new_used)
{
this.unused_page = page.prev_arena;
page.prev_arena = this.page;
this.page = page;
break ALLOCATE_NEW;
}
}
return this._alloc_new(size, alignment, offset);
}
page.used = new_used;
assert(start + size == page.memory + page.used);
void* mem = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
chunk.size = size;
return mem;
}
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require data `unexpectedly missing the allocator`
*/
private fn void*! dynamic_arena_allocator_function(Allocator* data, usize size, usize alignment, usize offset, void* old_pointer, AllocationKind kind)
{
DynamicArenaAllocator* allocator = (DynamicArenaAllocator*)data;
switch (kind)
{
case CALLOC:
case ALIGNED_CALLOC:
assert(!old_pointer, "Unexpected no old pointer for calloc.");
if (!size) return null;
void* mem = allocator._alloc(size, alignment, offset)?;
mem::clear(mem, size, DEFAULT_MEM_ALIGNMENT);
return mem;
case ALLOC:
case ALIGNED_ALLOC:
assert(!old_pointer, "Unexpected no old pointer for alloc.");
if (!size) return null;
return allocator._alloc(size, alignment, offset);
case REALLOC:
case ALIGNED_REALLOC:
if (!size)
{
if (!old_pointer) return null;
allocator.free(old_pointer);
return null;
}
if (!old_pointer) return allocator._alloc(size, alignment, offset);
void* mem = allocator._realloc(old_pointer, size, alignment, offset)?;
return mem;
case ALIGNED_FREE:
case FREE:
if (!old_pointer) return null;
allocator.free(old_pointer);
return null;
case MARK:
unreachable("Tried to mark a dynamic arena");
case RESET:
allocator.reset();
return null;
}
unreachable();
}

View File

@@ -1,209 +0,0 @@
// 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;
}

View File

@@ -1,161 +0,0 @@
// 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 = {};
typedef 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 mem::OUT_OF_MEMORY?;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
}
else
{
void* data @noinit;
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
}
else
{
if (!(data = libc::malloc(bytes))) return mem::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) ?: mem::OUT_OF_MEMORY?;
void* new_ptr;
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return mem::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) ?: mem::OUT_OF_MEMORY?;
}
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
}
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
if (!data) return mem::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) ?: mem::OUT_OF_MEMORY?;
}
return libc::realloc(old_ptr, new_bytes) ?: mem::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 ?: mem::OUT_OF_MEMORYY?;
}
else
{
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
if (!data) return mem::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 ?: mem::OUT_OF_MEMORY?;
}
return libc::realloc(old_ptr, new_bytes) ?: mem::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);
}
}

View File

@@ -0,0 +1,119 @@
module std::core::mem::allocator;
import libc;
private const Allocator _NULL_ALLOCATOR = { &null_allocator_fn };
private const Allocator _SYSTEM_ALLOCATOR = { &libc_allocator_fn };
private fn void*! null_allocator_fn(Allocator* this, usize bytes, usize alignment, usize offset, void* old_pointer, AllocationKind kind)
{
switch (kind)
{
case ALLOC:
case CALLOC:
case REALLOC:
case ALIGNED_ALLOC:
case ALIGNED_REALLOC:
case ALIGNED_CALLOC:
return AllocationFailure.OUT_OF_MEMORY!;
default:
return null;
}
}
private struct AlignedBlock
{
usize len;
void* start;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
private fn void* _libc_aligned_alloc(usize bytes, usize alignment, usize offset) @inline
{
usize header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
void* data = libc::malloc(header + bytes);
void* mem = mem::aligned_pointer(data + offset, alignment) - offset;
assert(mem > data);
AlignedBlock* desc = (AlignedBlock*)mem - 1;
*desc = { bytes, data };
return mem;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
private fn void* _libc_aligned_calloc(usize bytes, usize alignment, usize offset) @inline
{
usize header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
void* data = libc::calloc(header + bytes, 1);
void* mem = mem::aligned_pointer(data + offset, alignment) - offset;
AlignedBlock* desc = (AlignedBlock*)mem - 1;
assert(mem > data);
*desc = { bytes, data };
return mem;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
private fn void* _libc_aligned_realloc(void* old_pointer, usize bytes, usize alignment, usize offset) @inline
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
void* new_data = _libc_aligned_calloc(bytes, alignment, offset);
mem::copy(new_data, old_pointer, desc.len > bytes ? desc.len : bytes, DEFAULT_MEM_ALIGNMENT, DEFAULT_MEM_ALIGNMENT);
libc::free(data_start);
return new_data;
}
private fn void _libc_aligned_free(void* old_pointer) @inline
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
libc::free(desc.start);
}
fn void*! libc_allocator_fn(Allocator* unused, usize bytes, usize alignment, usize offset, void* old_pointer, AllocationKind kind) @inline
{
if (!alignment) alignment = DEFAULT_MEM_ALIGNMENT;
assert(math::is_power_of_2(alignment), "Alignment was not a power of 2");
void* data;
switch (kind)
{
case ALIGNED_ALLOC:
if (alignment <= DEFAULT_MEM_ALIGNMENT) nextcase ALLOC;
data = _libc_aligned_alloc(bytes, alignment, offset);
case ALLOC:
data = libc::malloc(bytes);
case ALIGNED_CALLOC:
if (alignment <= DEFAULT_MEM_ALIGNMENT) nextcase CALLOC;
data = _libc_aligned_calloc(bytes, alignment, offset);
case CALLOC:
data = libc::calloc(bytes, 1);
case ALIGNED_REALLOC:
if (!bytes) nextcase ALIGNED_FREE;
if (!old_pointer) nextcase ALIGNED_CALLOC;
data = _libc_aligned_realloc(old_pointer, bytes, alignment, offset);
case REALLOC:
if (!bytes) nextcase FREE;
if (!old_pointer) nextcase CALLOC;
data = libc::realloc(old_pointer, bytes);
case RESET:
return AllocationFailure.UNSUPPORTED_OPERATION!;
case ALIGNED_FREE:
_libc_aligned_free(old_pointer);
return null;
case FREE:
libc::free(old_pointer);
return null;
default:
unreachable();
}
if (!data) return AllocationFailure.OUT_OF_MEMORY!;
return data;
}

View File

@@ -1,150 +0,0 @@
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;
}

View File

@@ -1,319 +1,258 @@
module std::core::mem::allocator;
import std::io, std::math;
import std::core::sanitizer::asan;
import std::io;
// This implements the temp allocator.
// The temp allocator is a specialized allocator only intended for use where
// the allocation is strictly stack-like.
//
// It is *not* thread-safe: you cannot safely use the
// temp allocator on a thread and pass it to another thread.
//
// Fundamentally the temp allocator is a thread local arena allocator
// but the stack-like behaviour puts additional constraints to it.
//
// It works great for allocating temporary data in a scope then dropping
// that data, however you should not be storing temporary data in globals
// or locals that have a lifetime outside of the current temp allocator scope.
//
// Furthermore, note that the temp allocator is bounded, with additional
// allocations on top of that causing heap allocations. Such heap allocations
// will be cleaned up but is undesirable from a performance standpoint.
//
// If you want customizable behaviour similar to the temp allocator, consider
// the ArenaAllocator, BackedArenaAllocator or the DynamicArenaAllocator.
//
// Experimenting with the temp allocator to make it work outside of its
// standard usage patterns will invariably end in tears and frustrated
// hair pulling.
//
// Use one of the ArenaAllocators instead.
struct TempAllocator (Allocator)
private struct TempAllocatorChunk
{
Allocator backing_allocator;
usize size;
char[*] data;
}
struct TempAllocator
{
inline Allocator allocator;
Allocator* backing_allocator;
TempAllocatorPage* last_page;
TempAllocator* derived;
bool allocated;
usz used;
usz capacity;
usz original_capacity;
usize used;
usize capacity;
char[*] data;
}
struct TempAllocatorChunk @local
{
usz size;
char[*] data;
}
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
private const usize PAGE_IS_ALIGNED = (usize)isize.max + 1;
struct TempAllocatorPage
{
TempAllocatorPage* prev_page;
void* start;
usz size;
usz ident;
usize mark;
usize size;
usize 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;
macro usize TempAllocatorPage.pagesize(TempAllocatorPage* page) { return page.size & ~PAGE_IS_ALIGNED; }
macro bool TempAllocatorPage.is_aligned(TempAllocatorPage* page) { return page.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED; }
<*
@require size >= 16
@require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator."
*>
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size)
/**
* @require size >= 16
**/
fn TempAllocator*! new_temp(usize size, Allocator* backing_allocator)
{
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
temp.last_page = null;
temp.backing_allocator = allocator;
temp.used = 0;
temp.allocated = true;
temp.derived = null;
temp.original_capacity = temp.capacity = size;
return temp;
TempAllocator* allocator = backing_allocator.alloc(size + TempAllocator.sizeof)?;
allocator.last_page = null;
allocator.function = &temp_allocator_function;
allocator.backing_allocator = backing_allocator;
allocator.used = 0;
allocator.capacity = size;
return allocator;
}
<*
@require !self.derived
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
@require mult > 0 : "The multiple can never be zero"
*>
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz min_size, usz buffer, usz mult)
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require data `unexpectedly missing the allocator`
*/
private fn void*! temp_allocator_function(Allocator* data, usize size, usize alignment, usize offset, void* old_pointer, AllocationKind kind)
{
usz remaining = self.capacity - self.used;
void* mem @noinit;
usz size @noinit;
if (min_size + buffer > remaining)
TempAllocator* arena = (TempAllocator*)data;
switch (kind)
{
return self.derived = new_temp_allocator(self.backing_allocator, min_size * mult)!;
case CALLOC:
case ALIGNED_CALLOC:
assert(!old_pointer, "Unexpected old pointer for alloc.");
if (!size) return null;
return arena._alloc(size, alignment, offset, true);
case ALLOC:
case ALIGNED_ALLOC:
assert(!old_pointer, "Unexpected old pointer for alloc.");
if (!size) return null;
return arena._alloc(size, alignment_for_allocation(alignment), offset, false);
case ALIGNED_REALLOC:
case REALLOC:
if (!size) nextcase FREE;
if (!old_pointer) nextcase ALLOC;
return arena._realloc(old_pointer, size, alignment_for_allocation(alignment), offset);
case FREE:
case ALIGNED_FREE:
if (!old_pointer) return null;
io::println("Freeing stuff\n");
arena._free(old_pointer)?;
return null;
case MARK:
return (void*)(uptr)arena.used;
case RESET:
arena._reset(size)?;
return null;
}
usz start = mem::aligned_offset(self.used + buffer, mem::DEFAULT_MEM_ALIGNMENT);
void* ptr = &self.data[start];
TempAllocator* temp = (TempAllocator*)ptr;
$if env::ADDRESS_SANITIZER:
asan::unpoison_memory_region(ptr, TempAllocator.sizeof);
$endif
temp.last_page = null;
temp.backing_allocator = self.backing_allocator;
temp.used = 0;
temp.allocated = false;
temp.derived = null;
temp.original_capacity = temp.capacity = self.capacity - start - TempAllocator.sizeof;
self.capacity = start;
self.derived = temp;
return temp;
unreachable();
}
<*
Reset the entire temp allocator, which will merge all the children into it.
*>
fn void TempAllocator.reset(&self)
private fn void! TempAllocator._free(TempAllocator* this, void* old_pointer)
{
TempAllocator* child = self.derived;
if (!child) return;
while (child)
{
TempAllocator* old = child;
child = old.derived;
old.destroy();
}
self.capacity = self.original_capacity;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(&self.data[self.used], self.capacity - self.used);
$endif
self.derived = null;
}
<*
@require self.allocated : "Only a top level allocator should be freed."
*>
fn void TempAllocator.free(&self)
{
self.reset();
self.destroy();
// TODO fix free
assert((uptr)old_pointer >= (uptr)&this.data, "Pointer originates from a different allocator.");
usize old_size = *(usize*)(old_pointer - DEFAULT_SIZE_PREFIX);
if (old_pointer + old_size == &this.data[this.used])
{
this.used -= old_size;
}
}
fn void TempAllocator.destroy(&self) @local
private fn void! TempAllocator._reset(TempAllocator* this, usize mark)
{
TempAllocatorPage *last_page = self.last_page;
while (last_page)
TempAllocatorPage *last_page = this.last_page;
while (last_page && last_page.mark > mark)
{
TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
this._free_page(to_free)?;
}
if (self.allocated)
{
allocator::free(self.backing_allocator, self);
return;
}
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
self.data[0 : self.used] = 0xAA;
$else
asan::poison_memory_region(&self.data[0], self.used);
$endif
$endif
this.last_page = last_page;
this.used = mark;
}
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._free_page(&self, TempAllocatorPage* page) @inline @local
private fn void! TempAllocator._free_page(TempAllocator* this, TempAllocatorPage* page) @inline
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
if (page.is_aligned()) return this.backing_allocator.free_aligned(mem);
return this.backing_allocator.free(mem);
}
fn void*? TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
private fn void*! TempAllocator._realloc_page(TempAllocator* this, TempAllocatorPage* page, usize size, usize alignment, usize offset) @inline
{
// 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;
TempAllocatorPage **pointer_to_prev = &this.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();
usize page_size = page.pagesize();
// Clear on size > original size.
void* data = self.acquire(size, NO_ZERO, alignment)!;
if (page_size > size) page_size = size;
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());
void* data = this._alloc(size, alignment, offset, false)?;
mem::copy(data, &page.data[0], page_size, DEFAULT_MEM_ALIGNMENT, DEFAULT_MEM_ALIGNMENT);
if (page.is_aligned())
{
this.backing_allocator.free_aligned(real_pointer)?;
}
else
{
this.backing_allocator.free(real_pointer)?;
}
return data;
}
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
private fn void*! TempAllocator._realloc(TempAllocator* this, void* pointer, usize size, usize alignment, usize offset) @inline
{
TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof;
if (chunk.size == (usz)-1)
if (chunk.size == (usize)-1)
{
assert(self.last_page, "Realloc of non temp pointer");
assert(this.last_page, "Realloc of non temp pointer");
// First grab the page
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
return self._realloc_page(page, size, alignment);
}
bool is_realloc_of_last = chunk.size + pointer == &self.data[self.used];
if (is_realloc_of_last)
{
isz diff = size - chunk.size;
if (diff == 0) return pointer;
if (self.capacity - self.used > diff)
{
chunk.size += diff;
self.used += diff;
$if env::ADDRESS_SANITIZER:
if (diff < 0)
{
asan::poison_memory_region(pointer + chunk.size, -diff);
}
else
{
asan::unpoison_memory_region(pointer, chunk.size);
}
$endif
return pointer;
}
}
void* data = self.acquire(size, NO_ZERO, alignment)!;
usz len_to_copy = chunk.size > size ? size : chunk.size;
mem::copy(data, pointer, len_to_copy, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
if (is_realloc_of_last)
{
self.used = (uptr)chunk - (uptr)&self.data;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(chunk, TempAllocatorChunk.sizeof + chunk.size);
$endif
return this._realloc_page(page, size, alignment, offset);
}
assert(pointer < &this.data + this.capacity && pointer >= &this.data, "This is not a temp allocated pointer.");
assert(pointer < &this.data + this.used, "This is a stale temp pointer.");
// TODO optimize last allocation
TempAllocatorChunk* data = this._alloc(size, alignment, offset, size > chunk.size)?;
mem::copy(data, pointer, chunk.size, DEFAULT_MEM_ALIGNMENT, 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
/**
* @require math::is_power_of_2(alignment)
* @require size > 0
* @require alignment <= MAX_MEMORY_ALIGNMENT `alignment too big`
* @require this != null
**/
private fn void*! TempAllocator._alloc(TempAllocator* this, usize size, usize alignment, usize offset, bool clear)
{
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* start_mem = &this.data;
void* starting_ptr = start_mem + this.used;
void* aligned_header_start = mem::aligned_pointer(starting_ptr, $alignof(TempAllocatorChunk));
void* mem = aligned_header_start + TempAllocatorChunk.sizeof;
if (alignment > TempAllocatorChunk.alignof)
if (alignment > $alignof(TempAllocatorChunk))
{
mem = mem::aligned_pointer(mem, alignment);
mem = mem::aligned_pointer(mem + offset, alignment) - offset;
}
usz new_usage = (usz)(mem - start_mem) + size;
usize new_usage = (usize)(mem - start_mem) + size;
// Arena allocation, simple!
if (new_usage <= self.capacity)
// Arena alignment, simple!
if (new_usage <= this.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;
chunk_start.size = size;
this.used = new_usage;
if (clear) mem::clear(mem, size, DEFAULT_MEM_ALIGNMENT);
return mem;
}
// Fallback to backing allocator
TempAllocatorPage* page;
// We have something we need to align.
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
if (alignment > DEFAULT_MEM_ALIGNMENT || offset)
{
// 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)
usize total_alloc_size = TempAllocatorPage.sizeof + size;
if (clear)
{
mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
page = this.backing_allocator.calloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)?;
}
else
{
mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
page = this.backing_allocator.alloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)?;
}
void* start = mem;
mem += mem::aligned_offset(TempAllocatorPage.sizeof, alignment);
page = (TempAllocatorPage*)mem - 1;
page.start = start;
page.start = page;
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)!;
usize padded_header_size = mem::aligned_offset(TempAllocatorPage.sizeof, DEFAULT_MEM_ALIGNMENT);
usize total_alloc_size = padded_header_size + size;
void* alloc = (clear ? this.backing_allocator.calloc(total_alloc_size) : this.backing_allocator.alloc(total_alloc_size))?;
// 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));
assert(mem::ptr_is_aligned(page, $alignof(TempAllocator)));
assert(mem::ptr_is_aligned(&page.data[0], DEFAULT_MEM_ALIGNMENT));
page.start = alloc;
page.size = size;
}
// Mark it as a page
page.ident = ~(usz)0;
page.ident = ~(usize)0;
// Store when it was created
page.mark = ++this.used;
// Hook up the page.
page.prev_page = self.last_page;
self.last_page = page;
page.prev_page = this.last_page;
this.last_page = page;
return &page.data[0];
}
fn void TempAllocator.print_pages(TempAllocator* this, File f)
{
TempAllocatorPage *last_page = this.last_page;
if (!last_page)
{
f.printf("No pages.\n");
return;
}
f.printf("---Pages----\n");
uint index = 0;
while (last_page)
{
bool is_not_aligned = !(last_page.size & (1u64 << 63));
f.printf("%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;
}
}

View File

@@ -1,219 +0,0 @@
// 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::collections, std::io, std::os::backtrace;
const MAX_BACKTRACE = 16;
struct Allocation
{
void* ptr;
usz size;
void*[MAX_BACKTRACE] backtrace;
}
alias AllocMap = HashMap { uptr, Allocation };
// A simple tracking allocator.
// It tracks allocations using a hash map but
// is not compatible with allocators that uses mark()
//
// It is also embarassingly single-threaded, so
// do not use it to track allocations that cross threads.
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);
}
}
}
}

View File

@@ -1,113 +1,31 @@
module std::core::array;
import std::core::array::slice;
<*
Returns true if the array contains at least one element, else false
@param [in] array
@param [in] element
@require @typekind(array) == SLICE || @typekind(array) == ARRAY
@require @typeis(array[0], $typeof(element)) : "array and element must have the same type"
*>
macro bool contains(array, element)
{
foreach (&item : array)
{
if (*item == element) return true;
}
return false;
}
<*
Return the first index of element found in the array, searching from the start.
@param [in] array
@param [in] element
@return "the first index of the element"
@return? NOT_FOUND
*>
macro index_of(array, element)
{
foreach (i, &e : array)
{
if (*e == element) return i;
}
return NOT_FOUND?;
}
<*
Slice a 2d array and create a Slice2d from it.
@param array_ptr : "the pointer to create a slice from"
@param x : "The starting position of the slice x, optional"
@param y : "The starting position of the slice y, optional"
@param xlen : "The length of the slice in x, defaults to the length of the array"
@param ylen : "The length of the slice in y, defaults to the length of the array"
@return "A Slice2d from the array"
@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 };
}
<*
Return the first index of element found in the array, searching in reverse from the end.
@param [in] array
@param [in] element
@return "the last index of the element"
@return? NOT_FOUND
*>
macro rindex_of(array, element)
{
foreach_r (i, &e : array)
{
if (*e == element) return i;
}
return NOT_FOUND?;
}
<*
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
macro tconcat(arr1, arr2)
{
var $Type = $typeof(arr1[0]);
$Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len);
$Type[] result = array::talloc($Type, arr1.len + arr2.len);
if (arr1.len > 0)
{
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $Type.alignof, $Type.alignof);
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $alignof($Type), $alignof($Type));
}
if (arr2.len > 0)
{
mem::copy(&result[arr1.len], &arr2[0], arr2.len * $Type.sizeof, $Type.alignof, $Type.alignof);
mem::copy(&result[arr1.len], &arr2[0], arr2.len * $Type.sizeof, $alignof($Type), $alignof($Type));
}
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(tmem, arr1, arr2);
macro concat(arr1, arr2)
{
var $Type = $typeof(arr1[0]);
$Type[] result = array::alloc($Type, arr1.len + arr2.len);
if (arr1.len > 0)
{
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $alignof($Type), $alignof($Type));
}
if (arr2.len > 0)
{
mem::copy(&result[arr1.len], &arr2[0], arr2.len * $Type.sizeof, $alignof($Type), $alignof($Type));
}
return result;
}

View File

@@ -1,12 +1,5 @@
// Copyright (c) 2023-2025 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;
@@ -37,16 +30,6 @@ 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;
@@ -76,105 +59,3 @@ 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
}

View File

@@ -1,829 +1,115 @@
// Copyright (c) 2021-2024 Christoffer Lerno and contributors. All rights reserved.
// Copyright (c) 2021-2022 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;
import libc;
<*
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)
fault IteratorResult
{
$if @is_valid_macro_slot(#b):
return invoke_foo2(a, #b);
$else
return invoke_foo1(a);
$endif
NO_MORE_ELEMENT
}
*>
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
typedef 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.
*/
faultdef NO_MORE_ELEMENT @builtin;
/*
Use `SearchResult` when trying to return a value from some collection but the element is missing.
*/
faultdef NOT_FOUND @builtin;
/*
Use `CastResult` when an attempt at conversion fails.
*/
faultdef TYPE_MISMATCH @builtin;
alias 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
fault SearchResult
{
var temp = #variable;
defer #variable = temp;
MISSING
}
fault VarCastResult
{
TYPE_MISMATCH
}
/**
* Stores a variable on the stack, then restores it at the end of the
* macro scope.
*
* @param variable `the variable to store and restore`
**/
macro void @scope(&variable; @body) @builtin
{
$typeof(variable) 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
macro void @swap(&a, &b) @builtin
{
var temp = #a;
#a = #b;
#b = temp;
$typeof(a) 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? TYPE_MISMATCH
*>
macro anycast(any v, $Type) @builtin
/**
* Convert a variant type to a type, returning an failure if there is a type mismatch.
*
* @param v `the variant to convert to the given type.`
* @param $Type `the type to convert to`
* @return `The variant.ptr converted to its type.`
**/
macro varcast(variant v, $Type) @builtin
{
if (v.type != $Type.typeid) return TYPE_MISMATCH?;
if (v.type != $Type.typeid) return VarCastResult.TYPE_MISMATCH!;
return ($Type*)v.ptr;
}
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
struct CallstackElement
{
Allocator t = allocator::current_temp;
TempAllocator* new_t = allocator::new_temp_allocator(smem, 0x1000)!!;
allocator::current_temp = new_t;
defer
{
allocator::current_temp = t;
new_t.free();
}
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;
CallstackElement* prev;
char* function;
char* file;
uint line;
}
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
fn void panic(char* message, char *file, char *function, uint line) @builtin
{
$if $defined(io::stderr):
if (!print_backtrace(message, 2))
CallstackElement* stack = $$stacktrace();
$if ($defined(libc::stderr) && $defined(libc::fprintf)):
if (stack) stack = stack.prev;
if (stack)
{
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
libc::fprintf(libc::stderr(), "\nERROR: '%s'\n", message);
}
$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)
else
{
io::eprintn("Panic inside of panic.");
return;
libc::fprintf(libc::stderr(), "\nERROR: '%s', function %s (%s:%d)\n", message, function, file, line);
}
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();
}
alias 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)
while (stack)
{
io::eprint("Panic inside of panic: ");
io::eprintn(fmt);
return;
libc::fprintf(libc::stderr(), " at function %s (%s:%u)\n", stack.function, stack.file, stack.line);
if (stack == stack.prev) break;
stack = stack.prev;
}
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;
$$trap();
}
macro void unreachable($string = "Unreachable statement reached.") @builtin @noreturn
{
panic($string, $$FILE, $$FUNC, $$LINE);
$$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
var $size = (usize)($sizeof(expr));
$assert($size == $Type.sizeof, "Cannot bitcast between types of different size.");
$Type x = void;
mem::copy(&x, &expr, $size, $alignof($Type), $alignof(expr));
return x;
}
<*
@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? NOT_FOUND
*>
macro enum_by_name($Type, String enum_name) @builtin
/**
* @require $Type.kind == TypeKind.ENUM `Only enums may be used`
**/
macro enum_by_name($Type, char[] enum_name) @builtin
{
typeid x = $Type.typeid;
foreach (i, name : x.names)
{
if (name == enum_name) return $Type.from_ordinal(i);
if (str::compare(name, enum_name)) return ($Type)i;
}
return NOT_FOUND?;
}
<*
@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? NOT_FOUND
*>
macro @enum_from_value($Type, #value, value) @builtin
{
usz elements = $Type.elements;
foreach (e : $Type.values)
{
if (e.#value == value) return e;
}
return NOT_FOUND?;
}
<*
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 fault @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 int128.hash(self) => @generic_hash(self);
macro uint uint128.hash(self) => @generic_hash(self);
macro uint long.hash(self) => @generic_hash(self);
macro uint ulong.hash(self) => @generic_hash(self);
macro uint int.hash(self) => @generic_hash(self);
macro uint uint.hash(self) => @generic_hash(self);
macro uint short.hash(self) => @generic_hash(self);
macro uint ushort.hash(self) => @generic_hash(self);
macro uint ichar.hash(self) => @generic_hash(self);
macro uint char.hash(self) => @generic_hash(self);
macro uint bool.hash(self) => @generic_hash(self);
macro uint int128[*].hash(&self) => hash_array(self);
macro uint uint128[*].hash(&self) => hash_array(self);
macro uint long[*].hash(&self) => hash_array(self);
macro uint ulong[*].hash(&self) => hash_array(self);
macro uint int[*].hash(&self) => hash_array(self);
macro uint uint[*].hash(&self) => hash_array(self);
macro uint short[*].hash(&self) => hash_array(self);
macro uint ushort[*].hash(&self) => hash_array(self);
macro uint char[*].hash(&self) => hash_array(self);
macro uint ichar[*].hash(&self) => hash_array(self);
macro uint bool[*].hash(&self) => hash_array(self);
macro uint int128[<*>].hash(self) => hash_vec(self);
macro uint uint128[<*>].hash(self) => hash_vec(self);
macro uint long[<*>].hash(self) => hash_vec(self);
macro uint ulong[<*>].hash(self) => hash_vec(self);
macro uint int[<*>].hash(self) => hash_vec(self);
macro uint uint[<*>].hash(self) => hash_vec(self);
macro uint short[<*>].hash(self) => hash_vec(self);
macro uint ushort[<*>].hash(self) => hash_vec(self);
macro uint char[<*>].hash(self) => hash_vec(self);
macro uint ichar[<*>].hash(self) => hash_vec(self);
macro uint bool[<*>].hash(self) => hash_vec(self);
macro uint typeid.hash(typeid t) => @generic_hash(((ulong)(uptr)t));
macro uint String.hash(String c) => (uint)fnv32a::hash(c);
macro uint char[].hash(char[] c) => (uint)fnv32a::hash(c);
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
<*
@require @typekind(array_ptr) == POINTER &&& @typekind(*array_ptr) == ARRAY
*>
macro uint hash_array(array_ptr) @local
{
return (uint)fnv32a::hash(((char*)array_ptr)[:$sizeof(*array_ptr)]);
}
<*
@require @typekind(vec) == VECTOR
*>
macro uint hash_vec(vec) @local
{
return (uint)fnv32a::hash(((char*)&&vec)[:$sizeof(vec.len * $typeof(vec).inner.sizeof)]);
}
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::ANDROID || 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);
}
return SearchResult.MISSING!;
}

View File

@@ -1,128 +1,76 @@
// Copyright (c) 2021-2024 Christoffer Lerno and contributors. All rights reserved.
// Copyright (c) 2021-2022 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)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro less(a, b) @builtin
{
$switch:
$case $defined(a.less):
$if ($defined(a.less)):
return a.less(b);
$case $defined(a.compare_to):
$elif ($defined(a.compare_to)):
return a.compare_to(b) < 0;
$default:
$else:
return a < b;
$endswitch
$endif;
}
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro less_eq(a, b) @builtin
{
$switch:
$case $defined(a.less):
$if ($defined(a.less)):
return !b.less(a);
$case $defined(a.compare_to):
$elif ($defined(a.compare_to)):
return a.compare_to(b) <= 0;
$default:
$else:
return a <= b;
$endswitch
$endif;
}
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro greater(a, b) @builtin
{
$switch:
$case $defined(a.less):
$if ($defined(a.less)):
return b.less(a);
$case $defined(a.compare_to):
$elif ($defined(a.compare_to)):
return a.compare_to(b) > 0;
$default:
$else:
return a > b;
$endswitch
$endif;
}
<*
@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)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro greater_eq(a, b) @builtin
{
$switch:
$case $defined(a.less):
$if ($defined(a.less)):
return !a.less(b);
$case $defined(a.compare_to):
$elif ($defined(a.compare_to)):
return a.compare_to(b) >= 0;
$default:
$else:
return a >= b;
$endswitch
$endif;
}
<*
@require types::@equatable_value(a) && types::@equatable_value(b) : `values must be equatable`
*>
/**
* @require types::is_equatable_value(a) && types::is_equatable_value(b) `values must be equatable`
**/
macro bool equals(a, b) @builtin
{
$switch:
$case $defined(a.equals, a.equals(b)):
$if ($defined(a.equals)):
return a.equals(b);
$case $defined(a.compare_to, a.compare_to(b)):
$elif ($defined(a.compare_to)):
return a.compare_to(b) == 0;
$case $defined(a.less):
$elif ($defined(a.less)):
return !a.less(b) && !b.less(a);
$default:
$else:
return a == b;
$endswitch
$endif;
}
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
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// 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::cinterop;
@@ -8,54 +8,80 @@ 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;
$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);
alias CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
alias CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
alias CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
alias CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
alias CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
alias CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
alias CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
alias CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
alias CSChar = ichar;
alias CUChar = char;
$switch ($$C_INT_SIZE):
$case 64:
define CInt = long;
define CUInt = ulong;
$case 32:
define CInt = int;
define CUInt = uint;
$case 16:
define CInt = short;
define CUInt = ushort;
$default:
$assert(false, "Invalid C int size");
$endswitch;
alias CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
$switch ($$C_LONG_SIZE):
$case 64:
define CLong = long;
define CULong = ulong;
$case 32:
define CLong = int;
define CULong = uint;
$case 16:
define CLong = short;
define CULong = ushort;
$default:
$assert(false, "Invalid C long size");
$endswitch;
enum CBool : char
{
FALSE,
TRUE
}
$switch ($$C_SHORT_SIZE):
$case 32:
define CShort = int;
define CUShort = uint;
$case 16:
define CShort = short;
define CUShort = ushort;
$case 8:
define CShort = ichar;
define CUShort = char;
$default:
$assert(false, "Invalid C short size");
$endswitch;
// 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
}
$switch ($$C_LONG_LONG_SIZE):
$case 128:
define CLongLong = int128;
define CULongLong = uint128;
$case 64:
define CLongLong = long;
define CULongLong = ulong;
$case 32:
define CLongLong = int;
define CULongLong = uint;
$case 16:
define CLongLong = short;
define CULongLong = ushort;
$default:
$assert(false, "Invalid C long long size");
$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
}
define CSChar = ichar;
define CUChar = char;
$if ($$C_CHAR_IS_SIGNED):
define CChar = ichar;
$else:
define CChar = char;
$endif;

View File

@@ -1,57 +1,54 @@
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;
private const uint UTF16_SURROGATE_OFFSET = 0x10000;
private const uint UTF16_SURROGATE_GENERIC_MASK = 0xF800;
private const uint UTF16_SURROGATE_GENERIC_VALUE = 0xD800;
private const uint UTF16_SURROGATE_MASK = 0xFC00;
private const uint UTF16_SURROGATE_CODEPOINT_MASK = 0x03FF;
private const uint UTF16_SURROGATE_BITS = 10;
private const uint UTF16_SURROGATE_LOW_VALUE = 0xDC00;
private const uint UTF16_SURROGATE_HIGH_VALUE = 0xD800;
<*
@param c : `The utf32 codepoint to convert`
@param [out] output : `the resulting buffer`
@return? string::CONVERSION_FAILED
*>
fn usz? char32_to_utf8(Char32 c, char[] output)
/**
* @param c `The utf32 codepoint to convert`
* @param [out] output `the resulting buffer`
* @param available `the size available`
**/
fn usize! char32_to_utf8(Char32 c, char* output, usize available)
{
if (!output.len) return string::CONVERSION_FAILED?;
if (!available) return UnicodeResult.CONVERSION_FAILED!;
switch (true)
{
case c <= 0x7f:
case c < 0x7f:
output[0] = (char)c;
return 1;
case c <= 0x7ff:
if (output.len < 2) return string::CONVERSION_FAILED?;
case c < 0x7ff:
if (available < 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 string::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 string::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 string::CONVERSION_FAILED?;
output[1] = (char)(0x80 | (c & 0x3F));
return 2;
case c < 0xffff:
if (available < 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;
default:
if (available < 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;
}
}
<*
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.`
*>
/**
* 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)
@@ -67,14 +64,14 @@ fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
(*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)
/**
* 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, usize *available, char** output)
{
Char16 high = *ptr;
if (high & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
@@ -83,115 +80,107 @@ fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
*available = 1;
return;
}
// Low surrogate first is an error
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return string::INVALID_UTF16?;
// 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 string::INVALID_UTF16?;
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 string::INVALID_UTF16?;
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;
// 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)
/**
* @param c `The utf32 codepoint to convert`
* @param [inout] output `the resulting buffer`
**/
fn void char32_to_utf8_unsafe(Char32 c, char** output)
{
switch
switch (true)
{
case c <= 0x7f:
case c < 0x7f:
(*output)++[0] = (char)c;
return 1;
case c <= 0x7ff:
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;
(*output)++[0] = (char)(0x80 | (c & 0x3F));
case c < 0xffff:
(*output)++[0] = (char)(0xE0 | c >> 12);
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
(*output)++[0] = (char)(0x80 | (c & 0x3F));
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));
}
}
<*
@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)
/**
* @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, usize* size)
{
usz max_size = *size;
if (max_size < 1) return string::INVALID_UTF8?;
usize 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 string::INVALID_UTF8?;
*size = 2;
Char32 uc = (c & 0x1F) << 6;
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 string::INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
return uc + c & 0x3F;
}
if ((c & 0xF0) == 0xE0)
{
if (max_size < 3) return string::INVALID_UTF8?;
*size = 3;
Char32 uc = (c & 0x0F) << 12;
}
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 string::INVALID_UTF8?;
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 string::INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
return uc + c & 0x3F;
}
if (max_size < 4) return string::INVALID_UTF8?;
if ((c & 0xF8) != 0xF0) return string::INVALID_UTF8?;
*size = 4;
Char32 uc = (c & 0x07) << 18;
}
if (max_size < 4) return UnicodeResult.INVALID_UTF8!;
*size = 4;
Char32 uc = (c & 0x07) << 18;
c = ptr++[0];
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
uc += (c & 0x3F) << 12;
c = ptr++[0];
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
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 string::INVALID_UTF8?;
if (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)
/**
* @param utf8 `An UTF-8 encoded slice of bytes`
* @return `the number of encoded code points`
**/
fn usize utf8_codepoints(char[] utf8)
{
usz len = 0;
usize len = 0;
foreach (char c : utf8)
{
if (c & 0xC0 != 0x80) len++;
@@ -199,23 +188,23 @@ fn usz utf8_codepoints(String utf8)
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)
/**
* 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 usize utf8len_for_utf32(Char32[] utf32)
{
usz len = 0;
usize len = 0;
foreach (Char32 uc : utf32)
{
switch (true)
{
case uc <= 0x7f:
case uc < 0x7f:
len++;
case uc <= 0x7ff:
case uc < 0x7ff:
len += 2;
case uc <= 0xffff:
case uc < 0xffff:
len += 3;
default:
len += 4;
@@ -224,26 +213,26 @@ fn usz utf8len_for_utf32(Char32[] utf32)
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)
/**
* 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 usize utf8len_for_utf16(Char16[] utf16)
{
usz len = 0;
usz len16 = utf16.len;
for (usz i = 0; i < len16; i++)
usize len = 0;
usize len16 = utf16.len;
for (usize i = 0; i < len16; i++)
{
Char16 c = utf16[i];
if (c & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
{
if (c <= 0x7f)
if (c < 0x7f)
{
len++;
continue;
}
if (c <= 0x7ff)
if (c < 0x7ff)
{
len += 2;
continue;
@@ -256,16 +245,16 @@ fn usz utf8len_for_utf16(Char16[] utf16)
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)
/**
* 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 usize utf16len_for_utf8(char[] utf8)
{
usz len = utf8.len;
usz len16 = 0;
for (usz i = 0; i < len; i++)
usize len = utf8.len;
usize len16 = 0;
for (usize i = 0; i < len; i++)
{
len16++;
char c = utf8[i];
@@ -276,17 +265,17 @@ fn usz utf16len_for_utf8(String utf8)
if (c & 0xF0 == 0xE0) continue;
i++;
len16++;
}
return 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)
/**
* @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 usize utf16len_for_utf32(Char32[] utf32)
{
usz len = utf32.len;
usize len = utf32.len;
foreach (Char32 uc : utf32)
{
if (uc >= UTF16_SURROGATE_OFFSET) len++;
@@ -294,119 +283,117 @@ fn usz utf16len_for_utf32(Char32[] utf32)
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)
/**
* Convert an UTF32 array to an UTF8 array.
*
* @param [in] utf32
* @param [out] utf8_buffer
* @return `the number of bytes written.`
**/
fn usize! utf32to8(Char32[] utf32, char[] utf8_buffer)
{
char[] buffer = utf8_buffer;
foreach (uc : utf32)
usize len = utf8_buffer.len;
char* ptr = utf8_buffer.ptr;
foreach (Char32 uc : utf32)
{
usz used = char32_to_utf8(uc, buffer) @inline!;
buffer = buffer[used..];
usize used = char32_to_utf8(uc, ptr, len) @inline?;
len -= used;
ptr += used;
}
// Zero terminate if there is space.
if (buffer.len > 0) buffer[0] = 0;
return utf8_buffer.len - buffer.len;
return utf8_buffer.len - 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)
/**
* Convert an UTF8 array to an UTF32 array.
*
* @param [in] utf8
* @param [out] utf32_buffer
* @return `the number of Char32s written.`
**/
fn usize! utf8to32(char[] utf8, Char32[] utf32_buffer)
{
usz len = utf8.len;
usize 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 string::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;
usize len32 = 0;
usize buf_len = utf32_buffer.len;
for (usize i = 0; i < len;)
{
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED!;
usize width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline?;
i += width;
ptr[len32++] = uc;
}
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)
/**
* 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;)
usize len16 = utf16.len;
for (usize i = 0; i < len16;)
{
usz available = len16 - i;
char16_to_utf8_unsafe(&utf16[i], &available, &utf8_buffer) @inline!;
usize 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)
/**
* 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(char[] 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;
}
usize len = utf8.len;
for (usize i = 0; i < len;)
{
usize 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)
/**
* 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(char[] 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;
}
usize len = utf8.len;
for (usize i = 0; i < len;)
{
usize 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.`
*>
/**
* 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;

View File

@@ -1,670 +0,0 @@
module std::core::dstring;
import std::io;
<*
The DString offers a dynamic string builder.
*>
typedef DString (OutStream) = DStringOpaque*;
typedef DStringOpaque = void;
const usz MIN_CAPACITY @private = 16;
<*
Initialize the DString with a particular allocator.
@param [&inout] allocator : "The allocator to use"
@param capacity : "Starting capacity, defaults to MIN_CAPACITY and cannot be smaller"
@return "Return the DString itself"
@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;
}
<*
Initialize the DString with the temp allocator. Note that if the dstring is never
initialized, this is the allocator it will default to.
@param capacity : "Starting capacity, defaults to MIN_CAPACITY and cannot be smaller"
@return "Return the DString itself"
@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()
{
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(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
}
import libc;
fn usz? DString.appendf(&self, String format, args...) @maydiscard
{
if (!self.data()) self.tinit(format.len + 20);
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()
{
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;
}

View File

@@ -1,22 +1,14 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// 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::env;
enum CompilerOptLevel
{
O0,
O1,
O2,
O3
}
enum MemoryEnvironment
{
NORMAL,
SMALL,
TINY,
NONE
O0,
O1,
O2,
O3
}
enum OsType
@@ -32,7 +24,7 @@ enum OsType
KFREEBSD,
LINUX,
PS3,
MACOS,
MACOSX,
NETBSD,
OPENBSD,
SOLARIS,
@@ -57,145 +49,13 @@ enum OsType
HURD,
WASI,
EMSCRIPTEN,
ANDROID,
}
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 OsType OS_TYPE = (OsType)($$OS_TYPE);
const CompilerOptLevel COMPILER_OPT_LEVEL = (CompilerOptLevel)($$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 I128_SUPPORT = $$PLATFORM_I128_SUPPORTED;
const bool F128_SUPPORT = $$PLATFORM_F128_SUPPORTED;
const REGISTER_SIZE = $$REGISTER_SIZE;
const bool F16_SUPPORT = $$PLATFORM_F16_SUPPORTED;
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 ANDROID = LIBC && OS_TYPE == ANDROID;
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;
const int LANGUAGE_DEV_VERSION = $$LANGUAGE_DEV_VERSION;
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:
$case ANDROID:
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);
const usize TEMP_ALLOCATOR_SIZE = 128 * 1024;

File diff suppressed because it is too large Load Diff

View File

@@ -1,531 +1,181 @@
module std::core::mem::allocator;
import std::math;
const DEFAULT_SIZE_PREFIX = usz.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
const DEFAULT_MEM_ALIGNMENT = $alignof(void*) * 2;
const DEFAULT_SIZE_PREFIX = usize.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = $alignof(usize);
struct TrackingEnv
const Allocator* NULL_ALLOCATOR = &_NULL_ALLOCATOR;
const Allocator* LIBC_ALLOCATOR = &_SYSTEM_ALLOCATOR;
define AllocatorFunction = fn void*!(Allocator* allocator, usize new_size, usize alignment, usize offset, void* old_pointer, AllocationKind kind);
struct Allocator
{
String file;
String function;
uint line;
AllocatorFunction function;
}
enum AllocInitType
enum AllocationKind
{
NO_ZERO,
ZERO
ALLOC,
CALLOC,
REALLOC,
FREE,
ALIGNED_ALLOC,
ALIGNED_CALLOC,
ALIGNED_REALLOC,
ALIGNED_FREE,
RESET,
MARK,
}
interface Allocator
fault AllocationFailure
{
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
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
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? resize(void* ptr, usz new_size, usz alignment = 0);
<*
@require ptr != null
*>
fn void release(void* ptr, bool aligned);
OUT_OF_MEMORY,
UNSUPPORTED_OPERATION,
CHUNK_TOO_LARGE,
}
alias MemoryAllocFn = fn char[]?(usz);
fn usz alignment_for_allocation(usz alignment) @inline @private
fn void*! Allocator.alloc(Allocator* allocator, usize size) @inline
{
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
return allocator.function(allocator, size, 0, 0, null, ALLOC);
}
macro void* malloc(Allocator allocator, usz size) @nodiscard
/**
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! Allocator.alloc_aligned(Allocator* allocator, usize size, usize alignment, usize offset = 0) @inline
{
return malloc_try(allocator, size)!!;
return allocator.function(allocator, size, alignment, offset, null, ALIGNED_ALLOC);
}
macro void*? malloc_try(Allocator allocator, usz size) @nodiscard
fn void*! Allocator.realloc(Allocator* allocator, void* old_pointer, usize size) @inline
{
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
return allocator.function(allocator, size, 0, 0, old_pointer, REALLOC);
}
macro void* calloc(Allocator allocator, usz size) @nodiscard
/**
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! Allocator.realloc_aligned(Allocator* allocator, void* old_pointer, usize size, usize alignment, usize offset = 0) @inline
{
return calloc_try(allocator, size)!!;
return allocator.function(allocator, size, alignment, offset, old_pointer, ALIGNED_REALLOC);
}
macro void*? calloc_try(Allocator allocator, usz size) @nodiscard
fn usize! Allocator.mark(Allocator* allocator) @inline
{
if (!size) return null;
return allocator.acquire(size, ZERO);
return (usize)(uptr)allocator.function(allocator, 0, 0, 0, null, MARK);
}
macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard
fn void*! Allocator.calloc(Allocator* allocator, usize size) @inline
{
return realloc_try(allocator, ptr, new_size)!!;
return allocator.function(allocator, size, 0, 0, null, CALLOC);
}
macro void*? realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
/**
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! Allocator.calloc_aligned(Allocator* allocator, usize size, usize alignment, usize offset = 0) @inline
{
if (!new_size)
return allocator.function(allocator, size, alignment, offset, null, ALIGNED_CALLOC);
}
fn void! Allocator.free(Allocator* allocator, void* old_pointer) @inline
{
allocator.function(allocator, 0, 0, 0, old_pointer, FREE)?;
}
fn void! Allocator.free_aligned(Allocator* allocator, void* old_pointer) @inline
{
allocator.function(allocator, 0, 0, 0, old_pointer, ALIGNED_FREE)?;
}
fn void Allocator.reset(Allocator* allocator, usize mark = 0)
{
allocator.function(allocator, mark, 0, 0, null, RESET)!!;
}
private fn usize alignment_for_allocation(usize alignment) @inline
{
if (alignment < DEFAULT_MEM_ALIGNMENT)
{
free(allocator, ptr);
return null;
alignment = DEFAULT_MEM_ALIGNMENT;
}
if (!ptr) return allocator.acquire(new_size, NO_ZERO);
return allocator.resize(ptr, new_size);
return alignment;
}
macro void free(Allocator allocator, void* ptr)
struct DynamicArenaAllocator
{
if (!ptr) return;
$if env::TESTING:
((char*)ptr)[0] = 0xBA;
$endif
allocator.release(ptr, false);
inline Allocator allocator;
Allocator* backing_allocator;
DynamicArenaPage* page;
DynamicArenaPage* unused_page;
usize page_size;
}
macro void*? malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
/**
* @require page_size >= 128
* @require this != null
**/
fn void DynamicArenaAllocator.init(DynamicArenaAllocator* this, usize page_size, Allocator* backing_allocator = mem::current_allocator())
{
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
this.function = &dynamic_arena_allocator_function;
this.page = null;
this.unused_page = null;
this.page_size = page_size;
this.backing_allocator = backing_allocator;
}
macro void*? calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
/**
* @require this != null
**/
fn void DynamicArenaAllocator.destroy(DynamicArenaAllocator* this)
{
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)
DynamicArenaPage* page = this.page;
while (page)
{
free_aligned(allocator, ptr);
return null;
DynamicArenaPage* next_page = page.prev_arena;
this.backing_allocator.free(page)!!;
page = next_page;
}
if (!ptr)
page = this.unused_page;
while (page)
{
return malloc_aligned(allocator, new_size, alignment);
DynamicArenaPage* next_page = page.prev_arena;
this.backing_allocator.free(page)!!;
page = next_page;
}
return allocator.resize(ptr, new_size, alignment);
this.page = null;
this.unused_page = null;
}
macro void free_aligned(Allocator allocator, void* ptr)
struct ArenaAllocator
{
if (!ptr) return;
$if env::TESTING:
((char*)ptr)[0] = 0xBA;
$endif
allocator.release(ptr, aligned: true);
inline Allocator allocator;
char[] data;
usize used;
}
<*
@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
/**
* Initialize a memory arena for use using the provided bytes.
*
* @require this != null
**/
fn void ArenaAllocator.init(ArenaAllocator* this, char[] data)
{
$if $vacount == 0:
return ($Type*)calloc(allocator, $Type.sizeof);
$else
$Type* val = malloc(allocator, $Type.sizeof);
*val = $vaexpr[0];
return val;
$endif
this.function = &arena_allocator_function;
this.data = data;
this.used = 0;
}
<*
@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
/**
* @require this != null
**/
fn void ArenaAllocator.reset(ArenaAllocator* this)
{
$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];
}
<*
Clone a value.
@param [&inout] allocator : "The allocator to use to clone"
@param value : "The value to clone"
@return "A pointer to the cloned value"
@require $alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'clone_aligned' instead"
*>
macro clone(Allocator allocator, value) @nodiscard
{
return new(allocator, $typeof(value), value);
}
<*
Clone overaligned values. Must be released using free_aligned.
@param [&inout] allocator : "The allocator to use to clone"
@param value : "The value to clone"
@return "A pointer to the cloned value"
*>
macro clone_aligned(Allocator allocator, value) @nodiscard
{
return new_aligned(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
alias mem @builtin = thread_allocator ;
tlocal Allocator thread_allocator @private = base_allocator();
Allocator temp_base_allocator @private = base_allocator();
typedef PoolState = TempAllocator*;
const LazyTempAllocator LAZY_TEMP @private = {};
tlocal Allocator current_temp = &LAZY_TEMP;
tlocal TempAllocator* top_temp;
tlocal bool auto_create_temp = false;
usz temp_allocator_min_size = temp_allocator_default_min_size();
usz temp_allocator_buffer_size = temp_allocator_default_buffer_size();
usz temp_allocator_new_mult = 4;
fn PoolState push_pool()
{
Allocator old = top_temp ? current_temp : create_temp_allocator_on_demand();
current_temp = ((TempAllocator*)old).derive_allocator(temp_allocator_min_size, temp_allocator_buffer_size, temp_allocator_new_mult)!!;
return (PoolState)old.ptr;
}
fn void pop_pool(PoolState old)
{
TempAllocator* temp = (TempAllocator*)old;
current_temp = temp;
temp.reset();
}
macro Allocator base_allocator() @private
{
$if env::LIBC:
return &allocator::LIBC_ALLOCATOR;
$else
return &allocator::NULL_ALLOCATOR;
$endif
}
macro usz temp_allocator_size() @local
{
$switch env::MEMORY_ENV:
$case NORMAL: return 256 * 1024;
$case SMALL: return 1024 * 32;
$case TINY: return 1024 * 4;
$case NONE: return 0;
$endswitch
}
macro usz temp_allocator_default_min_size() @local
{
$switch env::MEMORY_ENV:
$case NORMAL: return 16 * 1024;
$case SMALL: return 1024 * 2;
$case TINY: return 256;
$case NONE: return 256;
$endswitch
}
macro usz temp_allocator_default_buffer_size() @local
{
$switch env::MEMORY_ENV:
$case NORMAL: return 1024;
$case SMALL: return 128;
$case TINY: return 64;
$case NONE: return 64;
$endswitch
}
macro Allocator heap() => thread_allocator;
<*
@require !top_temp : "This should never be called when temp already exists"
*>
fn Allocator create_temp_allocator_on_demand() @private
{
if (!auto_create_temp)
{
auto_create_temp = true;
abort("Use '@pool_init()' to enable the temp allocator on a new thread. A temp allocator is only implicitly created on the main thread.");
}
return create_temp_allocator(temp_base_allocator, temp_allocator_size());
}
<*
@require !top_temp : "This should never be called when temp already exists"
*>
fn Allocator create_temp_allocator(Allocator allocator, usz size, usz buffer = temp_allocator_default_buffer_size()) @private
{
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size)!!;
}
macro Allocator temp()
{
return current_temp;
}
alias tmem @builtin = current_temp;
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::WASM_NOLIBC)
{
auto_create_temp = true;
}
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 (!top_temp) return;
top_temp.free();
top_temp = null;
current_temp = &LAZY_TEMP;
}
import libc;
typedef LazyTempAllocator (Allocator) @private = uptr;
fn void*? LazyTempAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
if (!top_temp) create_temp_allocator_on_demand();
return top_temp.acquire(bytes, init_type, alignment);
}
fn void*? LazyTempAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (!top_temp) create_temp_allocator_on_demand();
return top_temp.resize(old_ptr, new_bytes, alignment);
}
fn void LazyTempAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
}
const NullAllocator NULL_ALLOCATOR = {};
typedef NullAllocator (Allocator) = uptr;
fn void*? NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
return mem::OUT_OF_MEMORY?;
}
fn void*? NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
return mem::OUT_OF_MEMORY?;
}
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
}
this.used = 0;
}

40
lib/std/core/mem_array.c3 Normal file
View File

@@ -0,0 +1,40 @@
// 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::mem::array;
/**
* @require usize.max / elements > $Type.sizeof
**/
macro alloc($Type, usize elements)
{
$Type* ptr = malloc($Type.sizeof * elements);
return ptr[:elements];
}
/**
* @require usize.max / elements > $Type.sizeof
**/
macro talloc($Type, usize elements)
{
$Type* ptr = tmalloc($Type.sizeof * elements, $alignof($Type[1]));
return ptr[:elements];
}
/**
* @require (usize.max / elements > $Type.sizeof)
**/
macro make($Type, usize elements)
{
$Type* ptr = calloc($sizeof($Type) * elements);
return ptr[:elements];
}
/**
* @require (usize.max / elements > $Type.sizeof)
**/
macro tmake($Type, usize elements)
{
$Type* ptr = tcalloc($sizeof($Type) * elements, $alignof($Type[1]));
return ptr[:elements];
}

17
lib/std/core/os/linux.c3 Normal file
View File

@@ -0,0 +1,17 @@
module std::core::os::linux;
$if (env::OS_TYPE == OsType.LINUX):
extern fn int* __errno_location();
fn int errno() @inline
{
return *__errno_location();
}
fn void errno_set(int err)
{
*(__errno_location()) = err;
}
$endif;

14
lib/std/core/os/macos.c3 Normal file
View File

@@ -0,0 +1,14 @@
module std::core::os::macos;
$if (env::OS_TYPE == OsType.MACOSX):
extern fn int* __error();
fn int errno() @inline
{
return *__error();
}
fn void errno_set(int err)
{
*(__error()) = err;
}
$endif;

View File

@@ -1,32 +0,0 @@
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 mem::OUT_OF_MEMORY?;
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
defer self.use += bytes;
return ((char*)self.use)[:bytes];
}

View File

@@ -0,0 +1,10 @@
module std::core::os::windows;
$if (env::OS_TYPE == OsType.WIN32):
extern fn int getLastError() @stdcall @extname("GetLastError");
fn int errno() @inline
{
return getLastError();
}
$endif;

View File

@@ -1,268 +0,0 @@
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);
}

View File

@@ -1,251 +0,0 @@
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;
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 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, &section.sectname)) return section;
section++;
}
return 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];
}
alias 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);
alias 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 = &current.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;
}

View File

@@ -1,180 +0,0 @@
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::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;
}

View File

@@ -1,43 +0,0 @@
// 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;
}
macro @enum_lookup($Type, #value, value)
{
var $elements = $Type.elements;
$for var $i = 0; $i < $elements; $i++:
var $val = $Type.from_ordinal($i);
if ($val.#value == value) return $val;
$endfor
return NOT_FOUND?;
}
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();
}

View File

@@ -1,100 +0,0 @@
module std::core::runtime;
import libc, std::time, std::io, std::sort;
alias BenchmarkFn = fn void();
struct BenchmarkUnit
{
String name;
BenchmarkFn func;
}
fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator)
{
BenchmarkFn[] fns = $$BENCHMARK_FNS;
String[] names = $$BENCHMARK_NAMES;
BenchmarkUnit[] benchmarks = allocator::alloc_array(allocator, BenchmarkUnit, names.len);
foreach (i, benchmark : fns)
{
benchmarks[i] = { names[i], fns[i] };
}
return benchmarks;
}
const DEFAULT_BENCHMARK_WARMUP_ITERATIONS = 3;
const DEFAULT_BENCHMARK_MAX_ITERATIONS = 10000;
uint benchmark_warmup_iterations @private = DEFAULT_BENCHMARK_WARMUP_ITERATIONS;
uint benchmark_max_iterations @private = DEFAULT_BENCHMARK_MAX_ITERATIONS;
fn void set_benchmark_warmup_iterations(uint value) @builtin
{
benchmark_warmup_iterations = value;
}
fn void set_benchmark_max_iterations(uint value) @builtin
{
assert(value > 0);
benchmark_max_iterations = value;
}
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
{
usz max_name;
foreach (&unit : benchmarks)
{
if (max_name < unit.name.len) max_name = unit.name.len;
}
usz len = max_name + 9;
DString name = dstring::temp_with_capacity(64);
name.append_repeat('-', len / 2);
name.append(" BENCHMARKS ");
name.append_repeat('-', len - len / 2);
io::printn(name);
name.clear();
long sys_clock_started;
long sys_clock_finished;
long sys_clocks;
Clock clock;
foreach(unit : benchmarks)
{
defer name.clear();
name.appendf("Benchmarking %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
for (uint i = 0; i < benchmark_warmup_iterations; i++)
{
unit.func() @inline;
}
clock = std::time::clock::now();
sys_clock_started = $$sysclock();
for (uint i = 0; i < benchmark_max_iterations; i++)
{
unit.func() @inline;
}
sys_clock_finished = $$sysclock();
NanoDuration nano_seconds = clock.mark();
sys_clocks = sys_clock_finished - sys_clock_started;
io::printfn("[COMPLETE] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
}
io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : "");
return true;
}
fn bool default_benchmark_runner(String[] args) => @pool()
{
return run_benchmarks(benchmark_collection_create(tmem));
}

View File

@@ -1,334 +0,0 @@
// Copyright (c) 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::runtime;
import std::core::test @public;
import std::core::mem::allocator @public;
import libc, std::time, std::io, std::sort;
import std::os::env;
alias TestFn = fn void();
TestContext* test_context @private;
struct TestContext
{
JmpBuf buf;
// Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add'
String test_filter;
// Triggers debugger breakpoint when assert or test:: checks failed
bool breakpoint_on_assert;
// internal state
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;
char* error_buffer;
usz error_buffer_capacity;
File fake_stdout;
struct stored
{
File stdout;
File stderr;
Allocator allocator;
}
}
struct TestUnit
{
String name;
TestFn func;
}
fn TestUnit[] test_collection_create(Allocator allocator)
{
TestFn[] fns = $$TEST_FNS;
String[] names = $$TEST_NAMES;
TestUnit[] tests = allocator::alloc_array(allocator, TestUnit, names.len);
foreach (i, test : fns)
{
tests[i] = { names[i], fns[i] };
}
return tests;
}
// Sort the tests by their name in ascending order.
fn int cmp_test_unit(TestUnit a, TestUnit b)
{
usz an = a.name.len;
usz bn = b.name.len;
if (an > bn) @swap(a, b);
foreach (i, ac : a.name)
{
char bc = b.name[i];
if (ac != bc) return an > bn ? bc - ac : ac - bc;
}
return (int)(an - bn);
}
fn bool terminal_has_ansi_codes() @local => @pool()
{
if (try v = env::tget_var("TERM"))
{
if (v.contains("xterm") || v.contains("vt100") || v.contains("screen")) return true;
}
$if env::WIN32 || env::NO_LIBC:
return false;
$else
return io::stdout().isatty();
$endif
}
fn void test_panic(String message, String file, String function, uint line) @local
{
if (test_context.is_in_panic) return;
test_context.is_in_panic = true;
unmute_output(true);
(void)io::stdout().flush();
if (test_context.assert_print_backtrace)
{
$if env::NATIVE_STACKTRACE:
builtin::print_backtrace(message, 0);
$endif
}
io::printf("\nTest failed ^^^ ( %s:%s ) %s\n", file, line, message);
test_context.assert_print_backtrace = true;
if (test_context.breakpoint_on_assert)
{
breakpoint();
}
if (test_context.teardown_fn)
{
test_context.teardown_fn();
}
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.is_no_capture || !test_context.fake_stdout.file) return;
File* stdout = io::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.is_no_capture || !test_context.fake_stdout.file) return;
File* stdout = io::stdout();
File* stderr = io::stderr();
*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]");
}
if (has_error && log_size > 0)
{
test_context.fake_stdout.write_byte('\n')!!;
test_context.fake_stdout.write_byte('\0')!!;
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
io::printfn("\n========== TEST LOG ============");
io::printfn("%s\n", test_context.current_test_name);
while (try c = test_context.fake_stdout.read_byte())
{
if (@unlikely(c == '\0'))
{
// ignore junk from previous tests
break;
}
libc::putchar(c);
}
io::printf("========== TEST END ============");
}
(void)stdout.flush();
}
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;
}
TestContext context =
{
.assert_print_backtrace = true,
.breakpoint_on_assert = false,
.test_filter = "",
.has_ansi_codes = terminal_has_ansi_codes(),
.stored.allocator = mem,
.stored.stderr = *io::stderr(),
.stored.stdout = *io::stdout(),
};
for (int i = 1; i < args.len; i++)
{
switch (args[i])
{
case "--test-breakpoint":
context.breakpoint_on_assert = true;
case "--test-nosort":
sort_tests = false;
case "--test-noleak":
check_leaks = false;
case "--test-nocapture":
context.is_no_capture = true;
case "--noansi":
context.has_ansi_codes = false;
case "--useansi":
context.has_ansi_codes = true;
case "--test-quiet":
context.is_quiet_mode = true;
case "--test-filter":
if (i == args.len - 1)
{
io::printn("Invalid arguments to test runner.");
return false;
}
context.test_filter = args[i + 1];
i++;
default:
io::printfn("Unknown argument: %s", args[i]);
}
}
test_context = &context;
if (sort_tests)
{
quicksort(tests, &cmp_test_unit);
}
// Buffer for hijacking the output
$if (!env::NO_LIBC):
context.fake_stdout.file = libc::tmpfile();
$endif
if (context.fake_stdout.file == null)
{
io::print("Failed to hijack stdout, tests will print everything");
}
PanicFn old_panic = builtin::panic;
defer builtin::panic = old_panic;
builtin::panic = &test_panic;
int tests_passed = 0;
int tests_skipped = 0;
int test_count = tests.len;
DString name = dstring::temp_with_capacity(64);
usz len = max_name + 9;
name.append_repeat('-', len / 2);
name.append(" TESTS ");
name.append_repeat('-', len - len / 2);
if (!context.is_quiet_mode) io::printn(name);
name.clear();
PoolState temp_state = mem::temp_push();
defer mem::temp_pop(temp_state);
foreach(unit : tests)
{
mem::temp_pop(temp_state);
if (context.test_filter && !unit.name.contains(context.test_filter))
{
tests_skipped++;
continue;
}
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);
if (context.is_quiet_mode)
{
io::print(".");
}
else
{
io::printf("%s ", name.str_view());
}
(void)io::stdout().flush();
TrackingAllocator mem;
mem.init(context.stored.allocator);
if (libc::setjmp(&context.buf) == 0)
{
mute_output();
mem.clear();
if (check_leaks) allocator::thread_allocator = &mem;
unit.func();
// track cleanup that may take place in teardown_fn
if (context.teardown_fn)
{
context.teardown_fn();
}
if (check_leaks) allocator::thread_allocator = context.stored.allocator;
unmute_output(false); // all good, discard output
if (mem.has_leaks())
{
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();
}
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count > 1 ? "s" : "");
int n_failed = test_count - tests_passed - tests_skipped;
io::printf("Test Result: %s%s%s: ",
context.has_ansi_codes ? (n_failed ? "\e[0;31m" : "\e[0;32m") : "",
n_failed ? "FAILED" : "PASSED",
context.has_ansi_codes ? "\e[0m" : "",
);
io::printfn("%d passed, %d failed, %d skipped.",
tests_passed,
n_failed,
tests_skipped);
// cleanup fake_stdout file
if (context.fake_stdout.file) libc::fclose(context.fake_stdout.file);
context.fake_stdout.file = null;
return n_failed == 0;
}
fn bool default_test_runner(String[] args) => @pool()
{
assert(test_context == null, "test suite is already running");
return run_tests(args, test_collection_create(tmem));
}

View File

@@ -1,127 +0,0 @@
// Add this to your code to suppress leak detection or set other default options
// fn ZString __asan_default_options() @export("__asan_default_options") @if(env::ADDRESS_SANITIZER)
// {
// return "detect_leaks=0";
// }
// Add this to break on error
// asan::set_error_report_callback(fn (ZString err)
// {
// breakpoint();
// });
module std::core::sanitizer::asan;
alias ErrorCallback = fn void (ZString);
<*
Marks a memory region ([addr, addr+size)) as unaddressable.
This memory must be previously allocated by your program. Instrumented
code is forbidden from accessing addresses in this region until it is
unpoisoned. This function is not guaranteed to poison the entire region -
it could poison only a subregion of [addr, addr+size) due to ASan
alignment restrictions.
NOTE This function is not thread-safe because no two threads can poison or
unpoison memory in the same memory region simultaneously.
@param addr : "Start of memory region."
@param size : "Size of memory region."
*>
macro poison_memory_region(void* addr, usz size)
{
$if env::ADDRESS_SANITIZER:
__asan_poison_memory_region(addr, size);
$endif
}
<*
Marks a memory region ([addr, addr+size)) as addressable.
This memory must be previously allocated by your program. Accessing
addresses in this region is allowed until this region is poisoned again.
This function could unpoison a super-region of [addr, addr+size) due
to ASan alignment restrictions.
NOTE This function is not thread-safe because no two threads can
poison or unpoison memory in the same memory region simultaneously.
@param addr : "Start of memory region."
@param size : "Size of memory region."
*>
macro unpoison_memory_region(void* addr, usz size)
{
$if env::ADDRESS_SANITIZER:
__asan_unpoison_memory_region(addr, size);
$endif
}
<*
Checks if an address is poisoned.
@return "True if 'addr' is poisoned (that is, 1-byte read/write access to this address would result in an error report from ASan). Otherwise returns false."
@param addr : "Address to check."
*>
macro bool address_is_poisoned(void* addr)
{
$if env::ADDRESS_SANITIZER:
return (bool)__asan_address_is_poisoned(addr);
$else
return false;
$endif
}
<*
Checks if a region is poisoned.
If at least one byte in [beg, beg+size) is poisoned, returns the
address of the first such byte. Otherwise returns 0.
@param beg : "Start of memory region."
@param size : "Start of memory region."
@return "Address of first poisoned byte."
*>
macro void* region_is_poisoned(void* beg, usz size)
{
$if env::ADDRESS_SANITIZER:
return __asan_region_is_poisoned(beg, size);
$else
return null;
$endif
}
<*
Sets the callback function to be called during ASan error reporting.
*>
fn void set_error_report_callback(ErrorCallback callback)
{
$if env::ADDRESS_SANITIZER:
__asan_set_error_report_callback(callback);
$endif
}
module std::core::sanitizer::asan @if(env::ADDRESS_SANITIZER);
extern fn void __asan_poison_memory_region(void* addr, usz size);
extern fn void __asan_unpoison_memory_region(void* addr, usz size);
extern fn CInt __asan_address_is_poisoned(void* addr);
extern fn void* __asan_region_is_poisoned(void* beg, usz size);
extern fn void __asan_describe_address(void* addr);
extern fn CInt __asan_report_present();
extern fn void* __asan_get_report_pc();
extern fn void* __asan_get_report_bp();
extern fn void* __asan_get_report_sp();
extern fn void* __asan_get_report_address();
extern fn CInt __asan_get_report_access_type();
extern fn usz __asan_get_report_access_size();
extern fn ZString __asan_get_report_description();
extern fn ZString __asan_locate_address(void* addr, char* name, usz name_size, void** region_address, usz* region_size);
extern fn usz __asan_get_alloc_stack(void* addr, void** trace, usz size, CInt* thread_id);
extern fn usz __asan_get_free_stack(void* addr, void** trace, usz size, CInt* thread_id);
extern fn void __asan_get_shadow_mapping(usz* shadow_scale, usz* shadow_offset);
extern fn void __asan_set_error_report_callback(ErrorCallback callback);
extern fn void __asan_print_accumulated_stats();
extern fn void* __asan_get_current_fake_stack();
extern fn void* __asan_addr_is_in_fake_stack(void* fake_stack, void* addr, void** beg, void** end);
extern fn void __asan_handle_no_return();
extern fn CInt __asan_update_allocation_context(void* addr);

View File

@@ -1,80 +0,0 @@
module std::core::sanitizer;
macro void annotate_contiguous_container(void* beg, void* end, void* old_mid, void* new_mid)
{
$if env::ADDRESS_SANITIZER:
__sanitizer_annotate_contiguous_container(beg, end, old_mid, new_mid);
$endif
}
macro void annotate_double_ended_contiguous_container(void* storage_beg, void* storage_end, void* old_container_beg, void* old_container_end, void* new_container_beg, void* new_container_end)
{
$if env::ADDRESS_SANITIZER:
__sanitizer_annotate_double_ended_contiguous_container(storage_beg, storage_end, old_container_beg, old_container_end, new_container_beg, new_container_end);
$endif
}
macro void print_stack_trace()
{
$if env::ADDRESS_SANITIZER:
__sanitizer_print_stack_trace();
$endif
}
fn void set_death_callback(VoidFn callback)
{
$if env::ANY_SANITIZER:
__sanitizer_set_death_callback(callback);
$endif
}
module std::core::sanitizer @if (env::ANY_SANITIZER);
struct __Sanitizer_sandbox_arguments
{
CInt coverage_sandboxed;
iptr coverage_fd;
CUInt coverage_max_block_size;
}
extern fn void __sanitizer_set_report_path(ZString path);
extern fn void __sanitizer_set_report_fd(void* fd);
extern fn ZString __sanitizer_get_report_path();
extern fn void __sanitizer_sandbox_on_notify(__Sanitizer_sandbox_arguments* args);
extern fn void __sanitizer_report_error_summary(ZString error_summary);
extern fn ushort __sanitizer_unaligned_load16(void* p);
extern fn uint __sanitizer_unaligned_load32(void* p);
extern fn ulong __sanitizer_unaligned_load64(void* p);
extern fn void __sanitizer_unaligned_store16(void* p, ushort x);
extern fn void __sanitizer_unaligned_store32(void* p, uint x);
extern fn void __sanitizer_unaligned_store64(void* p, ulong x);
extern fn CInt __sanitizer_acquire_crash_state();
extern fn void __sanitizer_annotate_contiguous_container(void* beg, void* end, void* old_mid, void* new_mid);
extern fn void __sanitizer_annotate_double_ended_contiguous_container(void* storage_beg, void* storage_end,
void* old_container_beg, void* old_container_end,
void* new_container_beg, void* new_container_end);
extern fn CInt __sanitizer_verify_contiguous_container(void* beg, void* mid, void* end);
extern fn CInt __sanitizer_verify_double_ended_contiguous_container(
void* storage_beg, void* container_beg,
void* container_end, void* storage_end);
extern fn void* __sanitizer_contiguous_container_find_bad_address(void* beg, void* mid, void* end);
extern fn void* __sanitizer_double_ended_contiguous_container_find_bad_address(
void* storage_beg, void* container_beg,
void* container_end, void* storage_end);
extern fn void __sanitizer_print_stack_trace();
extern fn void __sanitizer_symbolize_pc(void* pc, ZString fmt, char* out_buf, usz out_buf_size);
extern fn void __sanitizer_symbolize_global(void* data_ptr, ZString fmt, char* out_buf, usz out_buf_size);
extern fn void __sanitizer_set_death_callback(VoidFn callback);
extern fn void __sanitizer_weak_hook_memcmp(void* called_pc, void* s1, void* s2, usz n, CInt result);
extern fn void __sanitizer_weak_hook_strncmp(void* called_pc, ZString s1, ZString s2, usz n, CInt result);
extern fn void __sanitizer_weak_hook_strncasecmp(void* called_pc, ZString s1, ZString s2, usz n, CInt result);
extern fn void __sanitizer_weak_hook_strcmp(void* called_pc, ZString s1, ZString s2, CInt result);
extern fn void __sanitizer_weak_hook_strcasecmp(void* called_pc, ZString s1, ZString s2, CInt result);
extern fn void __sanitizer_weak_hook_strstr(void* called_pc, ZString s1, ZString s2, char* result);
extern fn void __sanitizer_weak_hook_strcasestr(void* called_pc, ZString s1, ZString s2, char* result);
extern fn void __sanitizer_weak_hook_memmem(void* called_pc, void* s1, usz len1, void* s2, usz len2, void* result);
extern fn void __sanitizer_print_memory_profile(usz top_percent, usz max_number_of_contexts);
extern fn void __sanitizer_start_switch_fiber(void** fake_stack_save, void* bottom, usz size);
extern fn void __sanitizer_finish_switch_fiber(void* fake_stack_save, void** bottom_old, usz* size_old);
extern fn CInt __sanitizer_get_module_and_offset_for_pc(void* pc, char* module_path, usz module_path_len, void** pc_offset);

View File

@@ -1,39 +0,0 @@
module std::core::sanitizer::tsan;
typedef MutexFlags = inline CUInt;
const MutexFlags MUTEX_LINKER_INIT = 1 << 0;
const MutexFlags MUTEX_WRITE_REENTRANT = 1 << 1;
const MutexFlags MUTEX_READ_REENTRANT = 1 << 2;
const MutexFlags MUTEX_NOT_STATIC = 1 << 8;
const MutexFlags MUTEX_READ_LOCK = 1 << 3;
const MutexFlags MUTEX_TRY_LOCK = 1 << 4;
const MutexFlags MUTEX_TRY_LOCK_FAILED = 1 << 5;
const MutexFlags MUTEX_RECURSIVE_LOCK = 1 << 6;
const MutexFlags MUTEX_RECURSIVE_UNLOCK = 1 << 7;
const MutexFlags MUTEX_TRY_READ_LOCK = MUTEX_READ_LOCK | MUTEX_TRY_LOCK;
const MutexFlags MUTEX_TRY_READ_LOCK_FAILED = MUTEX_TRY_READ_LOCK | MUTEX_TRY_LOCK_FAILED;
macro void mutex_create(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_create(addr, flags); $endif }
macro void mutex_destroy(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_destroy(addr, flags); $endif }
macro void mutex_pre_lock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_lock(addr, flags); $endif }
macro void mutex_post_lock(void* addr, MutexFlags flags, CInt recursion) { $if env::THREAD_SANITIZER: __tsan_mutex_post_lock(addr, flags, recursion); $endif }
macro CInt mutex_pre_unlock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: return __tsan_mutex_pre_unlock(addr, flags); $else return 0; $endif }
macro void mutex_post_unlock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_unlock(addr, flags); $endif }
macro void mutex_pre_signal(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_signal(addr, flags); $endif }
macro void mutex_post_signal(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_signal(addr, flags); $endif }
macro void mutex_pre_divert(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_divert(addr, flags); $endif }
macro void mutex_post_divert(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_divert(addr, flags); $endif }
module std::core::sanitizer::tsan @if(env::THREAD_SANITIZER) @private;
extern fn void __tsan_mutex_create(void* addr, CUInt flags);
extern fn void __tsan_mutex_destroy(void* addr, CUInt flags);
extern fn void __tsan_mutex_pre_lock(void* addr, CUInt flags);
extern fn void __tsan_mutex_post_lock(void* addr, CUInt flags, CInt recursion);
extern fn CInt __tsan_mutex_pre_unlock(void* addr, CUInt flags);
extern fn void __tsan_mutex_post_unlock(void* addr, CUInt flags);
extern fn void __tsan_mutex_pre_signal(void* addr, CUInt flags);
extern fn void __tsan_mutex_post_signal(void* addr, CUInt flags);
extern fn void __tsan_mutex_pre_divert(void* addr, CUInt flags);
extern fn void __tsan_mutex_post_divert(void* addr, CUInt flags);

View File

@@ -1,169 +0,0 @@
module std::core::array::slice {Type};
<*
A slice2d allows slicing an array like int[10][10] into an arbitrary "int[][]"-like counterpart
Typically you'd use array::slice2d(...) to create one.
*>
struct Slice2d
{
Type* ptr;
usz inner_len;
usz ystart;
usz ylen;
usz xstart;
usz xlen;
}
<*
@return `The length of the "outer" slice`
*>
fn usz Slice2d.len(&self) @operator(len)
{
return self.ylen;
}
<*
@return `The total number of elements.`
*>
fn usz Slice2d.count(&self)
{
return self.ylen * self.xlen;
}
<*
Step through each element of the slice.
*>
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
{
foreach (y, line : *self)
{
foreach (x, val : line)
{
@body({ x, y }, val);
}
}
}
<*
Step through each element of the slice *by reference*
*>
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
{
foreach (y, line : *self)
{
foreach (x, &val : line)
{
@body({ x, y }, val);
}
}
}
<*
Return a row as a slice.
@param idy : "The row to return"
@return "The slice for the particular row"
@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];
}
<*
Get the value at a particular x/y position in the slice.
@param coord : "The xy coordinate"
@return "The value at that coordinate"
@require coord.y >= 0 && coord.y < self.ylen : "y value out of range"
@require coord.x >= 0 && coord.x < self.xlen : "x value out of range"
*>
macro Type Slice2d.get_coord(self, usz[<2>] coord)
{
return *self.get_coord_ref(coord);
}
<*
Get a pointer to the value at a particular x/y position in the slice.
@param coord : "The xy coordinate"
@return "A pointer to the value at that coordinate"
@require coord.y >= 0 && coord.y < self.ylen : "y value out of range"
@require coord.x >= 0 && coord.x < self.xlen : "x value out of range"
*>
macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord)
{
return self.get_xy_ref(coord.x, coord.y);
}
<*
Get the value at a particular x/y position in the slice.
@param x : "The x coordinate"
@param y : "The x coordinate"
@return "The value at that coordinate"
@require y >= 0 && y < self.ylen : "y value out of range"
@require x >= 0 && x < self.xlen : "x value out of range"
*>
macro Type Slice2d.get_xy(self, x, y)
{
return *self.get_xy_ref(x, y);
}
<*
Get the value at a particular x/y position in the slice by reference.
@param x : "The x coordinate"
@param y : "The y coordinate"
@return "A pointer to the value at that coordinate"
@require y >= 0 && y < self.ylen : "y value out of range"
@require x >= 0 && x < self.xlen : "x value out of range"
*>
macro Type* Slice2d.get_xy_ref(self, x, y)
{
return self.ptr + self.inner_len * (y + self.ystart) + self.xstart + x;
}
<*
Set the ´value at a particular x/y position in the slice.
@param coord : "The xy coordinate"
@param value : "The new value"
@require coord.y >= 0 && coord.y < self.ylen : "y value out of range"
@require coord.x >= 0 && coord.x < self.xlen : "x value out of range"
*>
macro void Slice2d.set_coord(self, usz[<2>] coord, Type value)
{
*self.get_coord_ref(coord) = value;
}
<*
Set the value at a particular x/y position in the slice.
@param x : "The x coordinate"
@param y : "The y coordinate"
@param value : "The new value"
@require y >= 0 && y < self.ylen : "y value out of range"
@require x >= 0 && x < self.xlen : "x value out of range"
*>
macro void Slice2d.set_xy(self, x, y, Type value)
{
*self.get_xy_ref(x, y) = value;
}
<*
Reslice a slice2d returning a new slice.
@param x : "The starting x"
@param xlen : "The length along x"
@param y : "The starting y"
@param ylen : "The length along y"
@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 };
}

185
lib/std/core/str.c3 Normal file
View File

@@ -0,0 +1,185 @@
module std::core::str;
define ZString = distinct char*;
define Char32 = uint;
define Char16 = ushort;
private const uint SURROGATE_OFFSET = 0x10000;
private const uint SURROGATE_GENERIC_MASK = 0xF800;
private const uint SURROGATE_MASK = 0xFC00;
private const uint SURROGATE_CODEPOINT_MASK = 0x03FF;
private const uint SURROGATE_BITS = 10;
private const uint SURROGATE_LOW_VALUE = 0xDC00;
private const uint SURROGATE_HIGH_VALUE = 0xD800;
fn String join(char[][] s, char[] joiner)
{
if (!s.len) return (String)null;
usize total_size = joiner.len * s.len;
foreach (char[]* &str : s)
{
total_size += str.len;
}
String res = string::new_with_capacity(total_size);
res.append(s[0]);
foreach (char[]* &str : s[1..])
{
res.append(joiner);
res.append(*str);
}
return res;
}
fn usize! str_index_of(char[] s, char[] needle)
{
usize match = 0;
usize needed = needle.len;
if (!needed) return SearchResult.MISSING!;
usize index_start = 0;
char search = needle[0];
foreach (usize i, char c : s)
{
if (c == search)
{
if (!match) index_start = i;
match++;
if (match == needed) return i;
search = needle[match];
continue;
}
if (match)
{
match = 0;
search = needle[0];
}
}
return SearchResult.MISSING!;
}
fn ZString copy_zstring(char[] s)
{
usize len = s.len;
char* str = malloc(len + 1);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (ZString)str;
}
fn ZString tcopy_zstring(char[] s)
{
usize len = s.len;
char* str = tmalloc(len + 1);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (ZString)str;
}
fn bool compare(char[] a, char[] b)
{
if (a.len != b.len) return false;
foreach (i, c : a)
{
if (c != b[i]) return false;
}
return true;
}
fault UnicodeResult
{
INVALID_UTF8,
INVALID_UTF16,
CONVERSION_FAILED,
}
fn usize utf8_codepoints(char[] utf8)
{
usize len = 0;
foreach (char c : utf8)
{
if (c & 0xC0 != 0x80) len++;
}
return len;
}
fn Char32[]! utf8to32(char[] utf8, Allocator* allocator = mem::current_allocator)
{
usize codepoints = conv::utf8_codepoints(utf8);
Char32* data = allocator.alloc(Char32.sizeof * (codepoints + 1))?;
conv::utf8to32_unsafe(utf8, data)?;
data[codepoints] = 0;
return data[0..codepoints - 1];
}
fn char[] utf32to8(Char32[] utf32, Allocator* allocator = mem::current_allocator)
{
usize len = conv::utf8len_for_utf32(utf32);
char* data = allocator.alloc(len + 1)!!;
conv::utf32to8_unsafe(utf32, data);
data[len] = 0;
return data[0..len - 1];
}
fn Char16[]! utf8to16(char[] utf8, Allocator* allocator = mem::current_allocator)
{
usize len16 = conv::utf16len_for_utf8(utf8);
Char16* data = allocator.alloc((len16 + 1) * Char16.sizeof)?;
conv::utf8to16_unsafe(utf8, data)?;
data[len16] = 0;
return data[0..len16 - 1];
}
fn char[]! utf16to8(Char16[] utf16, Allocator* allocator = mem::current_allocator())
{
usize len = conv::utf8len_for_utf16(utf16);
char* data = allocator.alloc(len + 1)?;
conv::utf16to8_unsafe(utf16, data)?;
return data[0 .. len - 1];
}
fn char[] copy(char[] s)
{
usize len = s.len;
ZString str_copy = copy_zstring(s) @inline;
return str_copy[..len];
}
fn char[] tcopy(char[] s)
{
usize len = s.len;
ZString str_copy = tcopy_zstring(s) @inline;
return str_copy[..len];
}
fn char[] tconcat(char[] s1, char[] s2)
{
usize full_len = s1.len + s2.len;
char* str = tmalloc(full_len + 1);
usize s1_len = s1.len;
mem::copy(str, s1.ptr, s1_len);
mem::copy(str + s1_len, s2.ptr, s2.len);
str[full_len] = 0;
return str[..full_len];
}
fn char[] concat(char[] s1, char[] s2)
{
usize full_len = s1.len + s2.len;
char* str = malloc(full_len + 1);
usize s1_len = s1.len;
mem::copy(str, s1.ptr, s1_len);
mem::copy(str + s1_len, s2.ptr, s2.len);
str[full_len] = 0;
return str[..full_len];
}
fn usize ZString.len(ZString *str)
{
usize len = 0;
char* ptr = (char*)*str;
while (char c = ptr++[0])
{
if (c & 0xC0 != 0x80) len++;
}
return len;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +1,25 @@
module std::core::string::iterator;
struct StringIterator
{
String utf8;
usz current;
char[] utf8;
usize current;
}
fn void StringIterator.reset(&self)
fn void StringIterator.reset(StringIterator* this)
{
self.current = 0;
this.current = 0;
}
fn Char32? StringIterator.next(&self)
fn Char32! StringIterator.next(StringIterator* this)
{
usz len = self.utf8.len;
usz current = self.current;
if (current >= len) return NO_MORE_ELEMENT?;
usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
self.current += read;
return res;
}
fn Char32? StringIterator.peek(&self)
{
usz len = self.utf8.len;
usz current = self.current;
if (current >= len) return NO_MORE_ELEMENT?;
usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
return res;
}
fn bool StringIterator.has_next(&self)
{
return self.current < self.utf8.len;
}
fn Char32? StringIterator.get(&self)
{
usz len = self.utf8.len;
usz current = self.current;
usz read = (len - current < 4 ? len - current : 4);
usz index = current > read ? current - read : 0;
if (index >= len) return NO_MORE_ELEMENT?;
Char32 res = conv::utf8_to_char32(&self.utf8[index], &read)!;
return res;
}
usize len = this.utf8.len;
usize current = this.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT!;
usize read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&this.utf8[current], &read)?;
this.current += read;
return res;
}

View File

@@ -1,488 +0,0 @@
module std::core::string;
import std::math;
// Float parsing based on code in Musl floatscan.c by Rich Felker.
// Musl uses the MIT license, copied below:
// ----------------------------------------------------------------------
// Copyright © 2005-2014 Rich Felker, et al.
//
// 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.
// ----------------------------------------------------------------------
const KMAX = 128;
const MASK = KMAX - 1;
const B1B_DIG = 2;
const uint[2] B1B_MAX = { 9007199, 254740991 };
<*
@require chars.len > 0
*>
macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
{
uint[KMAX] x;
const uint[2] TH = B1B_MAX;
int emax = - $emin - $bits + 3;
const int[*] P10S = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
usz index;
bool got_digit = chars[0] == '0';
bool got_rad;
long lrp, dc;
int k, j, lnz;
usz len = chars.len;
usz last_char = len - 1;
assert(len);
char c @noinit;
// Skip past first characters
while ((c = chars[index]) == '0')
{
if (index == last_char) return sign * 0.0;
index++;
}
if (c == '.')
{
got_rad = true;
if (index == last_char)
{
if (!got_digit) return MALFORMED_FLOAT?;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
{
lrp--;
got_digit = true;
while (last_char != index && (c = chars[++index]) == '0')
{
lrp--;
}
}
}
while (c - '0' < 10u || c == '.')
{
switch
{
case c == '.':
if (got_rad) return MALFORMED_FLOAT?;
got_rad = true;
lrp = dc;
case k < KMAX - 3:
dc++;
if (c != '0') lnz = (int)dc;
if (j)
{
x[k] = x[k] * 10 + c - '0';
}
else
{
x[k] = c - '0';
}
if (++j == 9)
{
k++;
j = 0;
}
got_digit = true;
default:
dc++;
if (c != '0') x[KMAX - 4] |= 1;
}
if (index == last_char) break;
assert(index < last_char);
c = chars[++index];
}
if (!got_rad) lrp = dc;
if (!got_digit) return MALFORMED_FLOAT?;
if ((c | 32) == 'e')
{
if (last_char == index) return MALFORMED_FLOAT?;
long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT?!;
lrp += e10;
}
else if (index != last_char)
{
return MALFORMED_FLOAT?;
}
// Handle zero specially to avoid nasty special cases later
if (!x[0]) return sign * 0.0;
// Optimize small integers (w/no exponent) and over/under-flow
if (lrp == dc && dc < 10 && ($bits > 30 || (ulong)x[0] >> $bits == 0)) return sign * (double)x[0];
if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE?;
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
// Align incomplete final B1B digit
if (j)
{
for (; j < 9; j++) x[k] *= 10;
k++;
j = 0;
}
int a;
int z = k;
int e2;
long rp = lrp;
// Optimize small to mid-size integers (even in exp. notation)
if (lnz < 9 && lnz <= rp && rp < 18)
{
if (rp == 9) return sign * (double)x[0];
if (rp < 9) return sign * (double)x[0] / P10S[8 - rp];
int bitlim = $bits - 3 * (int)(rp - 9);
if (bitlim > 30 || x[0] >> bitlim == 0) return sign * (double)x[0] * P10S[rp - 10];
}
// Align radix point to B1B digit boundary
if (rp % 9)
{
long rpm9 = rp >= 0 ? rp % 9 : rp % 9 + 9;
int p10 = P10S[8 - rpm9];
uint carry = 0;
for (k = a; k != z; k++)
{
uint tmp = x[k] % p10;
x[k] = x[k] / p10 + carry;
carry = 1000000000 / p10 * tmp;
if (k == a && !x[k])
{
a = (a + 1) & MASK;
rp -= 9;
}
}
if (carry) x[z++] = carry;
rp += 9 - rpm9;
}
// Upscale until desired number of bits are left of radix point
while (rp < 9 * B1B_DIG || (rp == 9 * B1B_DIG && x[a] < TH[0]))
{
uint carry = 0;
e2 -= 29;
for (k = (z - 1) & MASK; ; k = (k - 1) & MASK)
{
ulong tmp = (ulong)x[k] << 29 + carry;
if (tmp > 1000000000)
{
carry = (uint)(tmp / 1000000000);
x[k] = (uint)(tmp % 1000000000);
}
else
{
carry = 0;
x[k] = (uint)tmp;
}
if (k == (z - 1) & MASK && k != a && !x[k]) z = k;
if (k == a) break;
}
if (carry)
{
rp += 9;
a = (a - 1) & MASK;
if (a == z)
{
z = (z - 1) & MASK;
x[(z - 1) & MASK] |= x[z];
}
x[a] = carry;
}
}
// Downscale until exactly number of bits are left of radix point
while (true)
{
uint carry = 0;
int sh = 1;
int i;
for (i = 0; i < B1B_DIG; i++)
{
k = (a + i) & MASK;
if (k == z || x[k] < TH[i])
{
i = B1B_DIG;
break;
}
if (x[(a + i) & MASK] > TH[i]) break;
}
if (i == B1B_DIG && rp == 9 * B1B_DIG) break;
if (rp > 9 + 9 * B1B_DIG) sh = 9;
e2 += sh;
for (k = a; k != z; k = (k+1) & MASK)
{
uint tmp = x[k] & (1 << sh - 1);
x[k] = x[k] >> sh + carry;
carry = (1000000000 >> sh) * tmp;
if (k == a && !x[k])
{
a = (a + 1) & MASK;
i--;
rp -= 9;
}
}
if (carry)
{
if ((z + 1) & MASK != a)
{
x[z] = carry;
z = (z + 1) & MASK;
}
else
{
x[(z - 1) & MASK] |= 1;
}
}
}
// Assemble desired bits into floating point variable
double y;
int i;
for (i = 0; i < B1B_DIG; i++)
{
if ((a + i) & MASK == z) x[(z = (z + 1) & MASK) - 1] = 0;
y = 1000000000.0 * y + x[(a + i) & MASK];
}
y *= sign;
bool denormal;
// Limit precision for denormal results
uint bits = $bits;
if (bits > math::DOUBLE_MANT_DIG + e2 - $emin)
{
bits = math::DOUBLE_MANT_DIG + e2 - $emin;
if (bits < 0) bits = 0;
denormal = true;
}
// Calculate bias term to force rounding, move out lower bits
double bias;
double frac;
if (bits < math::DOUBLE_MANT_DIG)
{
bias = math::copysign(math::scalbn(1, 2 * math::DOUBLE_MANT_DIG - bits - 1), y);
frac = y % math::scalbn(1, math::DOUBLE_MANT_DIG - bits);
y -= frac;
y += bias;
}
// Process tail of decimal input so it can affect rounding
if ((a + i) & MASK != z)
{
uint t = x[(a + i) & MASK];
switch
{
case t < 500000000 && (t || (a + i + 1) & MASK != z):
frac += 0.25 * sign;
case t > 500000000:
frac += 0.75 * sign;
case t == 500000000:
if ((a + i + 1) & MASK == z)
{
frac += 0.5 * sign;
}
else
{
frac += 0.75 * sign;
}
}
if (math::DOUBLE_MANT_DIG - bits >= 2 && !(frac % 1)) frac++;
}
y += frac;
y -= bias;
if (((e2 + math::DOUBLE_MANT_DIG) & int.max) > emax - 5)
{
if (math::abs(y) >= 0x1p53)
{
if (denormal && bits == math::DOUBLE_MANT_DIG + e2 - $emin) denormal = false;
y *= 0.5;
e2++;
}
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT?;
}
return math::scalbn(y, e2);
}
macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
{
double scale = 1;
uint x;
long rp;
long dc;
char c @noinit;
bool got_rad;
bool got_digit;
bool got_tail;
usz len = chars.len;
usz last_char = len - 1;
usz index;
double y;
// Skip past first characters
while ((c = chars[index]) == '0')
{
if (index == last_char) return 0.0;
index++;
}
if (c == '.')
{
got_rad = true;
if (index == last_char)
{
if (!got_digit) return MALFORMED_FLOAT?;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
{
rp--;
got_digit = true;
while (last_char != index && (c = chars[++index]) == '0')
{
rp--;
}
}
}
while ((c - '0') < 10u || ((c | 32) - 'a') < 6u || c == '.')
{
if (c == '.')
{
if (got_rad) return MALFORMED_FLOAT?;
got_rad = true;
rp = dc;
}
else
{
got_digit = true;
int d = c > '9' ? ((c | 32) + 10 - 'a') : (c - '0');
switch
{
case dc < 8:
x = x * 16 + d;
case dc < math::DOUBLE_MANT_DIG / 4 + 1:
y += d * (scale /= 16);
got_tail = true;
case d && !got_tail:
y += 0.5 * scale;
got_tail = true;
}
dc++;
}
if (index == last_char) break;
c = chars[++index];
}
if (!got_digit) return MALFORMED_FLOAT?;
if (!got_rad) rp = dc;
for (; dc < 8; dc++) x *= 16;
long e2;
if ((c | 32) == 'p')
{
long e2val = String.to_long((String)chars[index + 1..]) ?? (MALFORMED_FLOAT?)!;
e2 = e2val;
}
e2 += 4 * rp - 32;
if (!x) return sign * 0.0;
if (e2 > -$emin) return FLOAT_OUT_OF_RANGE?;
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
while (x < 0x80000000)
{
if (y >= 0.5)
{
x += x + 1;
y += y - 1;
}
else
{
x += x;
y += y;
}
e2--;
}
int bits = $bits;
if ($bits > 32 + e2 - $emin)
{
bits = (int)(32 + e2 - $emin);
if (bits < 0) bits = 0;
}
double bias;
if (bits < math::DOUBLE_MANT_DIG)
{
bias = math::copysign(math::scalbn(1, 32 + math::DOUBLE_MANT_DIG - bits - 1), (double)sign);
}
if (bits < 32 && y && !(x & 1))
{
x++;
y = 0;
}
y = bias + sign * (double)x + sign * y;
y -= bias;
if (!y) return FLOAT_OUT_OF_RANGE?;
return math::scalbn(y, (int)e2);
}
macro String.to_real(chars, $Type) @private
{
int sign = 1;
$switch $Type:
$case float:
const int BITS = math::FLOAT_MANT_DIG;
const int EMIN = math::FLOAT_MIN_EXP - BITS;
$case double:
const int BITS = math::DOUBLE_MANT_DIG;
const int EMIN = math::DOUBLE_MIN_EXP - BITS;
$case float128:
$error "Not yet supported";
$default:
$error "Unexpected type";
$endswitch
while (chars.len && chars[0] == ' ') chars = chars[1..];
if (!chars.len) return MALFORMED_FLOAT?;
if (chars.len != 1)
{
switch (chars[0])
{
case '-':
sign = -1;
nextcase;
case '+':
chars = chars[1..];
}
}
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
if (chars == "NAN" || chars == "nan") return $Type.nan;
if (chars.len > 2 && chars[0] == '0' && (chars[1] | 32) == 'x')
{
return ($Type)hexfloat((char[])chars[2..], BITS, EMIN, sign);
}
return ($Type)decfloat((char[])chars, BITS, EMIN, sign);
}

View File

@@ -1,220 +0,0 @@
<*
Unit test module
This module provides a toolset of macros for running unit test checks
Example:
```c3
module sample::m;
import std::io;
faultdef DIVISION_BY_ZERO;
fn double? divide(int a, int b)
{
if (b == 0) return MathError.DIVISION_BY_ZERO?;
return (double)(a) / (double)(b);
}
fn void? test_div() @test
{
test::eq(2, divide(6, 3)!);
test::ne(1, 2);
test::ge(3, 3);
test::gt(2, divide(3, 3)!);
test::lt(2, 3);
test::le(2, 3);
test::eq_approx(m::divide(1, 3)!, 0.333, places: 3);
test::@check(2 == 2, "divide: %d", divide(6, 3)!);
test::@error(m::divide(3, 0), MathError.DIVISION_BY_ZERO);
}
```
*>
// Copyright (c) 2025 Alex Veden <i@alexveden.com>. 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::test;
import std::core::runtime @public;
import std::math, std::io, libc;
<*
Initializes test case context.
@param setup_fn : `initializer function for test case`
@param teardown_fn : `cleanup function for test context (may be null)`
@require runtime::test_context != null : "Only allowed in @test functions"
@require setup_fn != null : "setup_fn must always be set"
*>
macro @setup(TestFn setup_fn, TestFn teardown_fn = null)
{
runtime::test_context.setup_fn = setup_fn;
runtime::test_context.teardown_fn = teardown_fn;
runtime::test_context.setup_fn();
}
<*
Checks condition and fails assertion if not true
@param #condition : `any boolean condition, will be expanded by text`
@param format : `printf compatible format`
@param args : `vargs for format`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro @check(#condition, String format = "", args...)
{
if (!#condition)
{
@stack_mem(512; Allocator allocator)
{
DString s;
s.init(allocator);
s.appendf("check `%s` failed. ", $stringify(#condition));
s.appendf(format, ...args);
print_panicf(s.str_view());
};
}
}
<*
Check if function returns specific error
@param #funcresult : `result of function execution`
@param error_expected : `expected error of function execution`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro @error(#funcresult, fault error_expected)
{
if (catch err = #funcresult)
{
if (err != error_expected)
{
print_panicf("`%s` expected to return error [%s], got [%s]",
$stringify(#funcresult), error_expected, err);
}
return;
}
print_panicf("`%s` error [%s] was not returned.", $stringify(#funcresult), error_expected);
}
<*
Check if left == right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro eq(left, right)
{
if (!equals(left, right))
{
print_panicf("`%s` != `%s`", left, right);
}
}
<*
Check left floating point value is approximately equals to right value
@param places : `number of decimal places to compare (default: 7)`
@param delta : `minimal allowed difference (overrides places parameter)`
@param equal_nan : `allows comparing nan values, if left and right both nans result is ok`
@require places > 0, places <= 20 : "too many decimal places"
@require delta >= 0, delta <= 1 : "delta must be a small number"
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro void eq_approx(double left, double right, uint places = 7, double delta = 0, bool equal_nan = true)
{
double diff = left - right;
double eps = delta;
if (eps == 0) eps = 1.0 / math::pow(10.0, places);
if (!math::is_approx(left, right, eps))
{
if (equal_nan && math::is_nan(left) && math::is_nan(right)) return;
print_panicf("Not almost equal: `%s` !~~ `%s` delta=%e diff: %e", left, right, eps, diff);
}
}
<*
Check if left != right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro void ne(left, right)
{
if (equals(left, right))
{
print_panicf("`%s` == `%s`", left, right);
}
}
<*
Check if left > right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro gt(left, right)
{
if (!builtin::greater(left, right))
{
print_panicf("`%s` <= `%s`", left, right);
}
}
<*
Check if left >= right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro ge(left, right)
{
if (!builtin::greater_eq(left, right))
{
print_panicf("`%s` < `%s`", left, right);
}
}
<*
Check if left < right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro lt(left, right)
{
if (!builtin::less(left, right))
{
print_panicf("`%s` >= `%s`", left, right);
}
}
<*
Check if left <= right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro le(left, right)
{
if (!builtin::less_eq(left, right))
{
print_panicf("`%s` > `%s`", left, right);
}
}
macro void print_panicf(format, ...) @local
{
runtime::test_context.assert_print_backtrace = false;
builtin::panicf(format, $$FILE, $$FUNC, $$LINE, $vasplat);
}

View File

@@ -1,380 +1,220 @@
module std::core::types;
import libc;
faultdef VALUE_OUT_OF_RANGE, VALUE_OUT_OF_UNSIGNED_RANGE;
<*
@require $Type.kindof.is_int() : "Type was not an integer"
@require v.type.kindof == ENUM : "Value was not an enum"
*>
macro any_to_enum_ordinal(any v, $Type)
fault ConversionResult
{
return any_to_int(v.as_inner(), $Type);
VALUE_OUT_OF_RANGE,
VALUE_OUT_OF_UNSIGNED_RANGE,
}
<*
@require $Type.kindof.is_int() : "Type was not an integer"
@require v.type.kindof.is_int() : "Value was not an integer"
*>
macro any_to_int(any v, $Type)
/**
* @require $Type.kind.is_int() || $Type.kind == TypeKind.ENUM "Argument was not an integer"
**/
macro variant_to_int(variant v, $Type)
{
typeid any_type = v.type;
TypeKind kind = any_type.kindof;
bool is_mixed_signed = $Type.kindof != any_type.kindof;
typeid variant_type = v.type;
TypeKind kind = variant_type.kind;
if (kind == TypeKind.ENUM)
{
variant_type = variant_type.inner;
kind = variant_type.kind;
}
bool is_mixed_signed = $Type.kind != variant_type.kind;
$Type max = $Type.max;
$Type min = $Type.min;
switch (any_type)
switch (variant_type)
{
case ichar:
ichar c = *(char*)v.ptr;
if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (is_mixed_signed && c < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE!;
return ($Type)c;
case short:
short s = *(short*)v.ptr;
if (is_mixed_signed && s < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && s < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE!;
if (s > max || s < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
return ($Type)s;
case int:
int i = *(int*)v.ptr;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && i < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE!;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
return ($Type)i;
case long:
long l = *(long*)v.ptr;
if (is_mixed_signed && l < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
long l = *(long*)v.ptr;;
if (is_mixed_signed && l < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE!;
if (l > max || l < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
return ($Type)l;
case int128:
int128 i = *(int128*)v.ptr;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
return ($Type)i;
$if (env::I128_SUPPORT):
int128 i = *(int128*)v.ptr;
if (is_mixed_signed && i < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE!;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
return ($Type)i;
$else:
unreachable();
$endif;
case char:
char c = *(char*)v.ptr;
if (c > max) return VALUE_OUT_OF_RANGE?;
if (c > max) return ConversionResult.VALUE_OUT_OF_RANGE!;
return ($Type)c;
case ushort:
ushort s = *(ushort*)v.ptr;
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
ushort s = *(ushort*)v.ptr;;
if (s > max || s < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
return ($Type)s;
case uint:
uint i = *(uint*)v.ptr;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
uint i = *(uint*)v.ptr;;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
return ($Type)i;
case ulong:
ulong l = *(ulong*)v.ptr;
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
ulong l = *(ulong*)v.ptr;;
if (l > max || l < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
return ($Type)l;
case uint128:
uint128 i = *(uint128*)v.ptr;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
return ($Type)i;
$if (env::I128_SUPPORT):
uint128 i = *(uint128*)v.ptr;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
return ($Type)i;
$else:
unreachable();
$endif;
default:
unreachable();
}
}
fn bool typeid.is_subtype_of(self, typeid other)
{
while (self != void.typeid)
{
if (self == other) return true;
self = self.parentof;
}
return false;
}
macro bool is_subtype_of($Type, $OtherType)
{
var $typeid = $Type.typeid;
$switch $Type:
$case $OtherType: return true;
$default: return false;
$endswitch
}
macro bool is_numerical($Type)
{
var $kind = $Type.kindof;
$if $kind == TypeKind.DISTINCT:
return is_numerical($typefrom($Type.inner));
$else
var $kind = $Type.kind;
$if ($kind == TypeKind.DISTINCT):
return is_numerical($Type.inner);
$else:
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|| $kind == TypeKind.VECTOR;
$endif
$endif;
}
fn bool TypeKind.is_int(kind) @inline
fn bool TypeKind.is_int(TypeKind kind) @inline
{
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
}
macro bool is_slice_convertable($Type)
macro bool is_comparable($Type)
{
$switch $Type.kindof:
$case SLICE:
var $kind = $Type.kind;
$if ($kind == TypeKind.DISTINCT):
return is_comparable($Type.inner);
$else:
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|| $kind == TypeKind.VECTOR || $kind == TypeKind.BOOL || $kind == TypeKind.POINTER
|| $kind == TypeKind.ENUM;
$endif;
}
macro bool is_subarray_convertable($Type)
{
$switch ($Type.kind):
$case SUBARRAY:
return true;
$case POINTER:
return $Type.inner.kindof == TypeKind.ARRAY;
return $Type.inner.kind == TypeKind.ARRAY;
$default:
return false;
$endswitch
$endswitch;
}
macro bool is_bool($Type) @const => $Type.kindof == TypeKind.BOOL;
macro bool is_int($Type) @const => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT;
<*
@require is_numerical($Type) : "Expected a numerical type"
*>
macro bool is_signed($Type) @const
macro bool is_intlike($Type)
{
$switch inner_kind($Type):
$switch ($Type.kind):
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
$case VECTOR:
return $Type.inner.kind == TypeKind.SIGNED_INT || $Type.inner.kind == TypeKind.UNSIGNED_INT;
$default:
return false;
$endswitch;
}
macro bool is_floatlike($Type)
{
$switch ($Type.kind):
$case FLOAT:
return true;
$case VECTOR:
return is_signed($typefrom($Type.inner));
return $Type.inner.kind == TypeKind.FLOAT;
$default:
return false;
$endswitch
$endswitch;
}
<*
@require is_numerical($Type) : "Expected a numerical type"
*>
macro bool is_unsigned($Type) @const
macro bool is_vector($Type)
{
$switch inner_kind($Type):
$case UNSIGNED_INT:
return true;
$case VECTOR:
return is_unsigned($typefrom($Type.inner));
$default:
return false;
$endswitch
return $Type.kind == TypeKind.VECTOR;
}
macro typeid flat_type($Type) @const
{
$if $Type.kindof == DISTINCT:
return flat_type($typefrom($Type.inner));
$else
return $Type.typeid;
$endif
}
macro TypeKind flat_kind($Type) @const
{
$if $Type.kindof == DISTINCT:
return flat_type($typefrom($Type.inner));
$else
return $Type.kindof;
$endif
}
macro bool is_indexable($Type) @const
{
return $defined(($Type){}[0]);
}
macro bool is_ref_indexable($Type) @const
{
return $defined(&($Type){}[0]);
}
macro bool is_intlike($Type) @const
{
$switch $Type.kindof:
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
$case VECTOR:
return $Type.inner.kindof == TypeKind.SIGNED_INT || $Type.inner.kindof == TypeKind.UNSIGNED_INT;
$default:
return false;
$endswitch
}
macro bool is_underlying_int($Type) @const
{
$switch $Type.kindof:
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
$case DISTINCT:
return is_underlying_int($typefrom($Type.inner));
$default:
return false;
$endswitch
}
macro bool is_float($Type) @const => $Type.kindof == TypeKind.FLOAT;
macro bool is_floatlike($Type) @const
{
$switch $Type.kindof:
$case FLOAT:
return true;
$case VECTOR:
return $Type.inner.kindof == TypeKind.FLOAT;
$default:
return false;
$endswitch
}
macro bool is_vector($Type) @const
{
return $Type.kindof == TypeKind.VECTOR;
}
macro typeid inner_type($Type) @const
{
$if $Type.kindof == TypeKind.DISTINCT:
return inner_type($typefrom($Type.inner));
$else
return $Type.typeid;
$endif
}
macro TypeKind inner_kind($Type) @const
{
return inner_type($Type).kindof;
}
macro bool is_same($TypeA, $TypeB) @const
macro bool is_same($TypeA, $TypeB)
{
return $TypeA.typeid == $TypeB.typeid;
}
macro bool @has_same(#a, #b, ...) @const
macro bool @has_same(#a, #b, ...)
{
var $type_a = @typeid(#a);
$if $type_a != @typeid(#b):
return false;
$endif
$for var $i = 0; $i < $vacount; $i++:
$if @typeid($vaexpr[$i]) != $type_a:
var $type_a = $typeof(#a).typeid;
$if ($type_a != $typeof(#b).typeid):
return false;
$endif;
$for (var $i = 0; $i < $vacount; $i++):
$if ($typeof($vaexpr($i)).typeid != $type_a):
return false;
$endif
$endfor
$endif;
$endfor;
return true;
}
macro bool may_load_atomic($Type) @const
macro bool is_equatable_value(value)
{
$switch $Type.kindof:
$case BOOL:
$case SIGNED_INT:
$case UNSIGNED_INT:
$case POINTER:
$case FLOAT:
$if ($defined(value.less) || $defined(value.compare_to) || $defined(value.equals)):
return true;
$case DISTINCT:
return may_load_atomic($Type.inner);
$default:
return false;
$endswitch
$else:
return is_comparable($typeof(value));
$endif;
}
macro lower_to_atomic_compatible_type($Type) @const
macro bool is_comparable_value(value)
{
$switch $Type.kindof:
$case BOOL:
$case SIGNED_INT:
$case UNSIGNED_INT:
return $Type.typeid;
$case DISTINCT:
return lower_to_atomic_compatible_type($Type.inner);
$case FLOAT:
$switch $Type:
$case float16:
return ushort.typeid;
$case float:
return uint.typeid;
$case double:
return ulong.typeid;
$case float128:
return uint128.typeid;
$default:
return void.typeid;
$endswitch
$default:
return void.typeid;
$endswitch
}
macro bool is_promotable_to_floatlike($Type) @const => types::is_floatlike($Type) || types::is_int($Type);
macro bool is_promotable_to_float($Type) @const => types::is_float($Type) || types::is_int($Type);
macro bool is_same_vector_type($Type1, $Type2) @const
{
$if $Type1.kindof != TypeKind.VECTOR:
return $Type2.kindof != TypeKind.VECTOR;
$else
return $Type1.inner == $Type2.inner && $Type1.len == $Type2.len;
$endif
}
macro bool is_equatable_type($Type) @const
{
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):
$if ($defined(value.less) || $defined(value.compare_to)):
return true;
$else
return $Type.is_eq;
$endif
}
<*
Checks if a type implements the copy protocol.
*>
macro bool implements_copy($Type) @const
{
return $defined($Type.copy) && $defined($Type.free);
}
macro bool @equatable_value(#value) @const
{
return is_equatable_type($typeof(#value));
}
macro bool @comparable_value(#value) @const
{
$if $defined(#value.less) || $defined(#value.compare_to):
return true;
$else
return $typeof(#value).is_ordered;
$endif
$else:
return is_comparable($typeof(value));
$endif;
}
enum TypeKind : char
{
VOID,
BOOL,
SIGNED_INT,
UNSIGNED_INT,
FLOAT,
TYPEID,
FAULT,
ANY,
ENUM,
STRUCT,
UNION,
BITSTRUCT,
FUNC,
OPTIONAL,
ARRAY,
SLICE,
VECTOR,
DISTINCT,
POINTER,
INTERFACE,
VOID,
BOOL,
SIGNED_INT,
UNSIGNED_INT,
FLOAT,
TYPEID,
ANYERR,
ANY,
ENUM,
FAULT,
STRUCT,
UNION,
BITSTRUCT,
FUNC,
FAILABLE,
ARRAY,
SUBARRAY,
VECTOR,
DISTINCT,
POINTER,
VARIANT
}
struct TypeEnum
{
TypeKind type;
usz elements;
usize elements;
}

View File

@@ -1,66 +0,0 @@
module std::core::values;
macro typeid @typeid(#value) @const @builtin => $typeof(#value).typeid;
macro TypeKind @typekind(#value) @const @builtin => $typeof(#value).kindof;
macro bool @typeis(#value, $Type) @const @builtin => $typeof(#value).typeid == $Type.typeid;
<*
Return true if two values have the same type before any conversions.
*>
macro bool @is_same_type(#value1, #value2) @const => $typeof(#value1).typeid == $typeof(#value2).typeid;
macro bool @is_bool(#value) @const => types::is_bool($typeof(#value));
macro bool @is_int(#value) @const => types::is_int($typeof(#value));
macro bool @is_floatlike(#value) @const => types::is_floatlike($typeof(#value));
macro bool @is_float(#value) @const => types::is_float($typeof(#value));
macro bool @is_promotable_to_floatlike(#value) @const => types::is_promotable_to_floatlike($typeof(#value));
macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_float($typeof(#value));
macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2));
macro bool @is_lvalue(#value) => $defined(#value = #value);
macro promote_int(x)
{
$if @is_int(x):
return (double)x;
$else
return x;
$endif
}
<*
Select between two values at compile time,
the values do not have to be of the same type.
This acts like `$bool ? #value_1 : #value_2` but at compile time.
@param $bool : `true for picking the first value, false for the other`
@param #value_1
@param #value_2
@returns `The selected value.`
*>
macro @select(bool $bool, #value_1, #value_2) @builtin
{
$if $bool:
return #value_1;
$else
return #value_2;
$endif
}
macro promote_int_same(x, y)
{
$if @is_int(x):
$switch:
$case @is_vector(y) &&& $typeof(y).inner == float.typeid:
return (float)x;
$case $typeof(y).typeid == float.typeid:
return (float)x;
$default:
return (double)x;
$endswitch
$else
return x;
$endif
}
macro TypeKind @inner_kind(#value) @const => types::inner_kind($typeof(#value));

View File

@@ -1,12 +0,0 @@
module std::crypto;
fn bool safe_compare(void* data1, void* data2, usz len)
{
char match = 0;
for (usz i = 0; i < len; i++)
{
match = match | (mem::@volatile_load(((char*)data1)[i]) ^ mem::@volatile_load(((char*)data2)[i]));
}
return match == 0;
}

View File

@@ -1,12 +0,0 @@
module std::crypto::dh;
import std::math::bigint;
fn BigInt generate_secret(BigInt p, BigInt x, BigInt y)
{
return y.mod_pow(x, p);
}
fn BigInt public_key(BigInt p, BigInt g, BigInt x)
{
return g.mod_pow(x, p);
}

View File

@@ -1,75 +0,0 @@
module std::crypto::rc4;
// 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.
struct Rc4
{
uint i, j;
char[256] state;
}
<*
Initialize the RC4 state.
@param [in] key : "The key to use"
@require key.len > 0 : "The key must be at least 1 byte long"
*>
fn void Rc4.init(&self, char[] key)
{
// Init the state matrix
foreach (char i, &c : self.state) *c = i;
for (int i = 0, int j = 0; i < 256; i++)
{
j = (j + self.state[i] + key[i % key.len]) & 0xFF;
@swap(self.state[i], self.state[j]);
}
self.i = 0;
self.j = 0;
}
<*
Run a single pass of en/decryption using a particular key.
@param [in] key
@param [inout] data
*>
fn void crypt(char[] key, char[] data)
{
Rc4 rc4;
rc4.init(key);
rc4.crypt(data, data);
}
<*
Encrypt or decrypt a sequence of bytes.
@param [in] in : "The input"
@param [out] out : "The output"
@require in.len <= out.len : "Output would overflow"
*>
fn void Rc4.crypt(&self, char[] in, char[] out)
{
uint i = self.i;
uint j = self.j;
char* state = &self.state;
isz len = in.len;
foreach (idx, c : in)
{
i = (i + 1) & 0xFF;
j = (j + state[i]) & 0xFF;
@swap(state[i], state[j]);
out[idx] = in[idx] ^ state[(state[i] + state[j]) & 0xFF];
}
self.i = i;
self.j = j;
}
<*
Clear the rc4 state.
@param [&out] self : "The RC4 State"
*>
fn void Rc4.destroy(&self)
{
*self = {};
}

View File

@@ -1,273 +0,0 @@
module std::encoding::base32;
// This module implements base32 encoding according to RFC 4648
// (https://www.rfc-editor.org/rfc/rfc4648)
struct Base32Alphabet
{
char[32] encoding;
char[256] reverse;
}
const char NO_PAD = 0;
const char DEFAULT_PAD = '=';
<*
Encode the content of src into a newly allocated string
@param [in] src : "The input to be encoded."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@return "The encoded string."
*>
fn String? encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding));
return encode_buffer(src, dst, padding, alphabet);
}
<*
Decode the content of src into a newly allocated char array.
@param [in] src : "The input to be encoded."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@return "The decoded data."
*>
fn char[]? decode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding));
return decode_buffer(src, dst, padding, alphabet);
}
fn String? tencode(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(tmem, code, padding, alphabet);
fn char[]? tdecode(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(tmem, code, padding, alphabet);
<*
Calculate the length in bytes of the decoded data.
@param n : "Length in bytes of input."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@return "Length in bytes of the decoded data."
*>
fn usz decode_len(usz n, char padding)
{
if (padding) return (n / 8) * 5;
// no padding
usz trailing = n % 8;
return n / 8 * 5 + (trailing * 5 ) / 8;
}
<*
Calculate the length in bytes of the encoded data.
@param n : "Length in bytes on input."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@return "Length in bytes of the encoded data."
*>
fn usz encode_len(usz n, char padding)
{
// A character is encoded into 8 x 5-bit blocks.
if (padding) return (n + 4) / 5 * 8;
// no padding
usz trailing = n % 5;
return n / 5 * 8 + (trailing * 8 + 4) / 5;
}
<*
Decode the content of src into dst, which must be properly sized.
@param src : "The input to be decoded."
@param dst : "The decoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@require dst.len >= decode_len(src.len, padding) : "Destination buffer too small"
@return "The resulting dst buffer"
@return? encoding::INVALID_PADDING, encoding::INVALID_CHARACTER
*>
fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return dst[:0];
char* dst_ptr = dst;
usz dn = decode_len(src.len, padding);
usz n;
char[8] buf;
while (src.len > 0 && dst.len > 0)
{
usz i @noinit;
// load 8 bytes into buffer
for (i = 0; i < 8; i++)
{
if (src.len == 0)
{
if (padding > 0) return encoding::INVALID_PADDING?;
break;
}
if (src[0] == padding) break;
buf[i] = alphabet.reverse[src[0]];
if (buf[i] == INVALID) return encoding::INVALID_CHARACTER?;
src = src[1..];
}
// extract 5-bytes from the buffer which contains 8 x 5 bit chunks
switch (i)
{
case 8:
// |66677777| dst[4]
// | 77777| buf[7]
// |666 | buf[6] << 5
dst[4] = buf[7] | buf[6] << 5;
n++;
nextcase 7;
case 7:
// |45555566| dst[3]
// | 66| buf[6] >> 3
// | 55555 | buf[5] << 2
// |4 | buf[4] << 7
dst[3] = buf[6] >> 3 | buf[5] << 2 | buf[4] << 7;
n++;
nextcase 5;
case 5:
// |33334444| dst[2]
// | 4444| buf[4] >> 1
// |3333 | buf[3] << 4
dst[2] = buf[4] >> 1 | buf[3] << 4;
n++;
nextcase 4;
case 4:
// |11222223| dst[1]
// | 3| buf[3] >> 4
// | 22222 | buf[2] << 1
// |11 | buf[1] << 6
dst[1] = buf[3] >> 4 | buf[2] << 1 | buf[1] << 6;
n++;
nextcase 2;
case 2:
// |00000111| dst[0]
// | 111| buf[1] >> 2
// |00000 | buf[0] << 3
dst[0] = buf[1] >> 2 | buf[0] << 3;
n++;
default:
return encoding::INVALID_CHARACTER?;
}
if (dst.len < 5) break;
dst = dst[5..];
}
return dst_ptr[:n];
}
<*
Encode the content of src into dst, which must be properly sized.
@param [in] src : "The input to be encoded."
@param [inout] dst : "The encoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@require dst.len >= encode_len(src.len, padding) : "Destination buffer too small"
@return "The encoded size."
*>
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return (String)dst[:0];
char* dst_ptr = dst;
usz n = (src.len / 5) * 5;
usz dn = encode_len(src.len, padding);
uint msb, lsb;
for (usz i = 0; i < n; i += 5)
{
// to fit 40 bits we need two 32-bit uints
msb = (uint)src[i] << 24 | (uint)src[i+1] << 16
| (uint)src[i+2] << 8 | (uint)src[i+3];
lsb = msb << 8 | (uint)src[i+4];
// now slice them into 5-bit chunks and translate to the
// alphabet.
dst[0] = alphabet.encoding[(msb >> 27) & MASK];
dst[1] = alphabet.encoding[(msb >> 22) & MASK];
dst[2] = alphabet.encoding[(msb >> 17) & MASK];
dst[3] = alphabet.encoding[(msb >> 12) & MASK];
dst[4] = alphabet.encoding[(msb >> 7) & MASK];
dst[5] = alphabet.encoding[(msb >> 2) & MASK];
dst[6] = alphabet.encoding[(lsb >> 5) & MASK];
dst[7] = alphabet.encoding[lsb & MASK];
dst = dst[8..];
}
usz trailing = src.len - n;
if (trailing == 0) return (String)dst_ptr[:dn];
msb = 0;
switch (trailing)
{
case 4:
msb |= (uint)src[n+3];
lsb = msb << 8;
dst[6] = alphabet.encoding[(lsb >> 5) & MASK];
dst[5] = alphabet.encoding[(msb >> 2) & MASK];
nextcase 3;
case 3:
msb |= (uint)src[n+2] << 8;
dst[4] = alphabet.encoding[(msb >> 7) & MASK];
nextcase 2;
case 2:
msb |= (uint)src[n+1] << 16;
dst[3] = alphabet.encoding[(msb >> 12) & MASK];
dst[2] = alphabet.encoding[(msb >> 17) & MASK];
nextcase 1;
case 1:
msb |= (uint)src[n] << 24;
dst[1] = alphabet.encoding[(msb >> 22) & MASK];
dst[0] = alphabet.encoding[(msb >> 27) & MASK];
}
// add the padding
if (padding > 0)
{
for (usz i = (trailing * 8 / 5) + 1; i < 8; i++)
{
dst[i] = padding;
}
}
return (String)dst_ptr[:dn];
}
const uint MASK @private = 0b11111;
const char INVALID @private = 0xff;
const int STD_PADDING = '=';
const int NO_PADDING = -1;
typedef Alphabet = char[32];
// Standard base32 Alphabet
const Alphabet STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
// Extended Hex Alphabet
const Alphabet HEX_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
const Base32Alphabet STANDARD = {
.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
.reverse = x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffff1a1b1c1d1e1fffffffffffffffff
ff000102030405060708090a0b0c0d0e0f10111213141516171819ffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
};
const Base32Alphabet HEX = {
.encoding = "0123456789ABCDEFGHIJKLMNOPQRSTUV",
.reverse = x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff00010203040506070809ffffffffffff
ff0a0b0c0d0e0f101112131415161718191a1b1c1d1e1fffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
};

View File

@@ -1,255 +0,0 @@
module std::encoding::base64;
import std::core::bitorder;
// The implementation is based on https://www.rfc-editor.org/rfc/rfc4648
// Specifically this section:
// https://www.rfc-editor.org/rfc/rfc4648#section-4
const char NO_PAD = 0;
const char DEFAULT_PAD = '=';
struct Base64Alphabet
{
char[64] encoding;
char[256] reverse;
}
const Base64Alphabet STANDARD = {
.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
.reverse =
x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffff3effffff3f3435363738393a3b3c3dffffffffffff
ff000102030405060708090a0b0c0d0e0f10111213141516171819ffffffffff
ff1a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233ffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
};
const Base64Alphabet URL = {
.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
.reverse =
x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffff3effff3435363738393a3b3c3dffffffffffff
ff000102030405060708090a0b0c0d0e0f10111213141516171819ffffffff3f
ff1a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233ffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
};
const STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
fn String encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding));
return encode_buffer(src, dst, padding, alphabet);
}
fn char[]? decode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding))!;
return decode_buffer(src, dst, padding, alphabet);
}
fn String tencode(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(tmem, code, padding, alphabet);
fn char[]? tdecode(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(tmem, code, padding, alphabet);
<*
Calculate the size of the encoded data.
@param n : "Size of the input to be encoded."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@return "The size of the input once encoded."
*>
fn usz encode_len(usz n, char padding)
{
if (padding) return (n + 2) / 3 * 4;
usz trailing = n % 3;
return n / 3 * 4 + (trailing * 4 + 2) / 3;
}
<*
Calculate the size of the decoded data.
@param n : "Size of the input to be decoded."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@return "The size of the input once decoded."
@return? encoding::INVALID_PADDING
*>
fn usz? decode_len(usz n, char padding)
{
usz dn = n / 4 * 3;
usz trailing = n % 4;
if (padding)
{
if (trailing != 0) return encoding::INVALID_PADDING?;
// source size is multiple of 4
return dn;
}
if (trailing == 1) return encoding::INVALID_PADDING?;
return dn + trailing * 3 / 4;
}
<*
Encode the content of src into dst, which must be properly sized.
@param src : "The input to be encoded."
@param dst : "The encoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@return "The encoded size."
*>
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return (String)dst[:0];
usz dn = encode_len(src.len, padding);
char* dst_ptr = dst;
assert(dst.len >= dn);
usz trailing = src.len % 3;
char[] src3 = src[:^trailing];
while (src3.len > 0)
{
uint group = (uint)src3[0] << 16 | (uint)src3[1] << 8 | (uint)src3[2];
dst[0] = alphabet.encoding[group >> 18 & MASK];
dst[1] = alphabet.encoding[group >> 12 & MASK];
dst[2] = alphabet.encoding[group >> 6 & MASK];
dst[3] = alphabet.encoding[group & MASK];
dst = dst[4..];
src3 = src3[3..];
}
// Encode the remaining bytes according to:
// https://www.rfc-editor.org/rfc/rfc4648#section-3.5
switch (trailing)
{
case 1:
uint group = (uint)src[^1] << 16;
dst[0] = alphabet.encoding[group >> 18 & MASK];
dst[1] = alphabet.encoding[group >> 12 & MASK];
if (padding > 0)
{
dst[2] = padding;
dst[3] = padding;
}
case 2:
uint group = (uint)src[^2] << 16 | (uint)src[^1] << 8;
dst[0] = alphabet.encoding[group >> 18 & MASK];
dst[1] = alphabet.encoding[group >> 12 & MASK];
dst[2] = alphabet.encoding[group >> 6 & MASK];
if (padding > 0)
{
dst[3] = padding;
}
case 0:
break;
default:
unreachable();
}
return (String)dst_ptr[:dn];
}
<*
Decode the content of src into dst, which must be properly sized.
@param src : "The input to be decoded."
@param dst : "The decoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require (decode_len(src.len, padding) ?? 0) <= dst.len : "Destination buffer too small"
@require padding < 0xFF : "Invalid padding character"
@return "The decoded data."
@return? encoding::INVALID_CHARACTER, encoding::INVALID_PADDING
*>
fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return dst[:0];
usz dn = decode_len(src.len, padding)!;
assert(dst.len >= dn);
usz trailing = src.len % 4;
char* dst_ptr = dst;
char[] src4 = src;
switch
{
case !padding:
src4 = src[:^trailing];
default:
// If there is padding, keep the last 4 bytes for later.
// NB. src.len >= 4 as decode_len passed
trailing = 4;
if (src[^1] == padding) src4 = src[:^4];
}
while (src4.len > 0)
{
char c0 = alphabet.reverse[src4[0]];
char c1 = alphabet.reverse[src4[1]];
char c2 = alphabet.reverse[src4[2]];
char c3 = alphabet.reverse[src4[3]];
switch (0xFF)
{
case c0:
case c1:
case c2:
case c3:
return encoding::INVALID_CHARACTER?;
}
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6 | (uint)c3;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
dst[2] = (char)group;
dst = dst[3..];
src4 = src4[4..];
}
if (trailing == 0) return dst_ptr[:dn];
src = src[^trailing..];
char c0 = alphabet.reverse[src[0]];
char c1 = alphabet.reverse[src[1]];
if (c0 == 0xFF || c1 == 0xFF) return encoding::INVALID_PADDING?;
if (!padding)
{
switch (src.len)
{
case 2:
uint group = (uint)c0 << 18 | (uint)c1 << 12;
dst[0] = (char)(group >> 16);
case 3:
char c2 = alphabet.reverse[src[2]];
if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
}
}
else
{
// Valid paddings are:
// 2: xx==
// 1: xxx=
switch (padding)
{
case src[2]:
if (src[3] != padding) return encoding::INVALID_PADDING?;
uint group = (uint)c0 << 18 | (uint)c1 << 12;
dst[0] = (char)(group >> 16);
dn -= 2;
case src[3]:
char c2 = alphabet.reverse[src[2]];
if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
dn -= 1;
}
}
return dst_ptr[:dn];
}
const MASK @private = 0b111111;

View File

@@ -1,95 +0,0 @@
module std::encoding::csv;
import std::io;
struct CsvReader
{
InStream stream;
String separator;
}
struct CsvRow (Printable)
{
String[] list;
String row;
Allocator allocator;
}
fn usz? CsvRow.to_format(&self, Formatter* f) @dynamic
{
return f.printf("%s", self.list);
}
fn usz CsvRow.len(&self) @operator(len)
{
return self.list.len;
}
<*
@require col < self.list.len
*>
fn String CsvRow.get_col(&self, usz col) @operator([])
{
return self.list[col];
}
fn void CsvReader.init(&self, InStream stream, String separator = ",")
{
self.stream = stream;
self.separator = separator;
}
<*
@param [&inout] allocator
*>
fn CsvRow? CsvReader.read_row(self, Allocator allocator)
{
String row = io::readline(allocator, self.stream)!;
defer catch allocator::free(allocator, row);
String[] list = row.split(allocator, self.separator);
return { list, row, allocator };
}
fn CsvRow? CsvReader.tread_row(self)
{
return self.read_row(tmem) @inline;
}
<*
@require self.allocator != null : `Row already freed`
*>
fn void CsvRow.free(&self)
{
allocator::free(self.allocator, self.list);
allocator::free(self.allocator, self.row);
self.allocator = null;
}
fn void? CsvReader.skip_row(self) @maydiscard => @pool()
{
(void)io::treadline(self.stream);
}
macro void? @each_row(InStream stream, String separator = ",", int max_rows = int.max; @body(String[] row)) @maydiscard
{
while (max_rows--)
{
@stack_mem(512; mem)
{
String? s = io::readline(mem, stream);
if (catch err = s)
{
if (err == io::EOF) return;
return err?;
}
@body(s.split(mem, separator));
};
}
}
macro void? CsvReader.@each_row(self, int rows = int.max; @body(String[] row)) @maydiscard
{
return @each_row(self.stream, self.separator, rows; row)
{
@body(row);
};
}

View File

@@ -1,3 +0,0 @@
module std::encoding;
faultdef INVALID_CHARACTER, INVALID_PADDING;

View File

@@ -1,108 +0,0 @@
module std::encoding::hex;
import std::encoding @norecurse;
// The implementation is based on https://www.rfc-editor.org/rfc/rfc4648
fn String encode_buffer(char[] code, char[] buffer)
{
return (String)buffer[:encode_bytes(code, buffer)];
}
fn char[]? decode_buffer(char[] code, char[] buffer)
{
return buffer[:decode_bytes(code, buffer)!];
}
fn String encode(Allocator allocator, char[] code)
{
char[] data = allocator::alloc_array(allocator, char, encode_len(code.len));
return (String)data[:encode_bytes(code, data)];
}
fn char[]? decode(Allocator allocator, char[] code)
{
char[] data = allocator::alloc_array(allocator, char, decode_len(code.len));
return data[:decode_bytes(code, data)!];
}
fn String tencode(char[] code) @inline => encode(tmem, code);
fn char[]? tdecode(char[] code) @inline => decode(tmem, code);
<*
Calculate the size of the encoded data.
@param n : "Size of the input to be encoded."
@return "The size of the input once encoded."
*>
fn usz encode_len(usz n) => n * 2;
<*
Encode the content of src into dst, which must be properly sized.
@param src : "The input to be encoded."
@param dst : "The encoded input."
@return "The encoded size."
@require dst.len >= encode_len(src.len) : "Destination array is not large enough"
*>
fn usz encode_bytes(char[] src, char[] dst)
{
usz j = 0;
foreach (v : src)
{
dst[j] = HEXALPHABET[v >> 4];
dst[j + 1] = HEXALPHABET[v & 0x0f];
j = j + 2;
}
return src.len * 2;
}
<*
Calculate the size of the decoded data.
@param n : "Size of the input to be decoded."
@return "The size of the input once decoded."
*>
macro usz decode_len(usz n) => n / 2;
<*
Decodes src into bytes. Returns the actual number of bytes written to dst.
Expects that src only contains hexadecimal characters and that src has even
length.
@param src : "The input to be decoded."
@param dst : "The decoded input."
@require src.len % 2 == 0 : "src is not of even length"
@require dst.len >= decode_len(src.len) : "Destination array is not large enough"
@return? encoding::INVALID_CHARACTER
*>
fn usz? decode_bytes(char[] src, char[] dst)
{
usz i;
for (usz j = 1; j < src.len; j += 2)
{
char a = HEXREVERSE[src[j - 1]];
char b = HEXREVERSE[src[j]];
if (a > 0x0f || b > 0x0f) return encoding::INVALID_CHARACTER?;
dst[i] = (a << 4) | b;
i++;
}
return i;
}
const char[*] HEXALPHABET @private = "0123456789abcdef";
const char[*] HEXREVERSE @private =
x`ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
00010203040506070809ffffffffffff
ff0a0b0c0d0e0fffffffffffffffffff
ffffffffffffffffffffffffffffffff
ff0a0b0c0d0e0fffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff`;

View File

@@ -1,386 +0,0 @@
// 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::encoding::json;
import std::io;
import std::ascii;
import std::collections::object;
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, DUPLICATE_MEMBERS, INVALID_NUMBER;
fn Object*? parse_string(Allocator allocator, String s)
{
return parse(allocator, (ByteReader){}.init(s));
}
fn Object*? tparse_string(String s)
{
return parse(tmem, (ByteReader){}.init(s));
}
fn Object*? parse(Allocator allocator, InStream s)
{
@stack_mem(512; Allocator smem)
{
JsonContext context = { .last_string = dstring::new_with_capacity(smem, 64), .stream = s, .allocator = allocator };
@pool()
{
return parse_any(&context);
};
};
}
fn Object*? tparse(InStream s)
{
return parse(tmem, s);
}
// -- Implementation follows --
enum JsonTokenType @local
{
NO_TOKEN,
LBRACE,
LBRACKET,
COMMA,
COLON,
RBRACE,
RBRACKET,
STRING,
NUMBER,
TRUE,
FALSE,
NULL,
EOF,
}
struct JsonContext @local
{
uint line;
InStream stream;
Allocator allocator;
JsonTokenType token;
DString last_string;
double last_number;
char current;
bitstruct : char {
bool skip_comments;
bool reached_end;
bool pushed_back;
}
}
fn Object*? parse_from_token(JsonContext* context, JsonTokenType token) @local
{
switch (token)
{
case NO_TOKEN: unreachable();
case LBRACE: return parse_map(context);
case LBRACKET: return parse_array(context);
case COMMA:
case RBRACE:
case RBRACKET:
case COLON: return UNEXPECTED_CHARACTER?;
case STRING: return object::new_string(context.last_string.str_view(), context.allocator);
case NUMBER: return object::new_float(context.last_number, context.allocator);
case TRUE: return object::new_bool(true);
case FALSE: return object::new_bool(false);
case NULL: return object::new_null();
case EOF: return io::EOF?;
}
}
fn Object*? parse_any(JsonContext* context) @local
{
return parse_from_token(context, advance(context));
}
fn JsonTokenType? lex_number(JsonContext *context, char c) @local
{
@stack_mem(256; Allocator mem)
{
DString t = dstring::new_with_capacity(mem, 32);
bool negate = c == '-';
if (negate)
{
t.append(c);
c = read_next(context)!;
}
while (c.is_digit())
{
t.append(c);
c = read_next(context)!;
}
if (c == '.')
{
t.append(c);
while (c = read_next(context)!, c.is_digit())
{
t.append(c);
}
}
if ((c | 32) == 'e')
{
t.append(c);
c = read_next(context)!;
switch (c)
{
case '-':
case '+':
t.append(c);
c = read_next(context)!;
}
if (!c.is_digit()) return INVALID_NUMBER?;
while (c.is_digit())
{
t.append(c);
c = read_next(context)!;
}
}
pushback(context, c);
double? d = t.str_view().to_double() ?? INVALID_NUMBER?;
context.last_number = d!;
return NUMBER;
};
}
fn Object*? parse_map(JsonContext* context) @local
{
Object* map = object::new_obj(context.allocator);
defer catch map.free();
JsonTokenType token = advance(context)!;
@stack_mem(256; Allocator mem)
{
DString temp_key = dstring::new_with_capacity(mem, 32);
while (token != JsonTokenType.RBRACE)
{
if (token != JsonTokenType.STRING) return UNEXPECTED_CHARACTER?;
DString string = context.last_string;
if (map.has_key(string.str_view())) return DUPLICATE_MEMBERS?;
// Copy the key to our temp holder, since our
// last_string may be used in parse_any
temp_key.clear();
temp_key.append(string);
parse_expected(context, COLON)!;
Object* element = parse_any(context)!;
map.set(temp_key.str_view(), element);
token = advance(context)!;
if (token == JsonTokenType.COMMA)
{
token = advance(context)!;
continue;
}
if (token != JsonTokenType.RBRACE) return UNEXPECTED_CHARACTER?;
}
return map;
};
}
fn Object*? parse_array(JsonContext* context) @local
{
Object* list = object::new_obj(context.allocator);
defer catch list.free();
JsonTokenType token = advance(context)!;
while (token != JsonTokenType.RBRACKET)
{
Object* element = parse_from_token(context, token)!;
list.push(element);
token = advance(context)!;
if (token == JsonTokenType.COMMA)
{
token = advance(context)!;
continue;
}
if (token != JsonTokenType.RBRACKET) return UNEXPECTED_CHARACTER?;
}
return list;
}
fn void pushback(JsonContext* context, char c) @local
{
if (!context.reached_end)
{
assert(!context.pushed_back);
context.pushed_back = true;
context.current = c;
}
}
fn char? read_next(JsonContext* context) @local
{
if (context.reached_end) return '\0';
if (context.pushed_back)
{
context.pushed_back = false;
return context.current;
}
char? c = context.stream.read_byte();
if (catch err = c)
{
if (err == io::EOF)
{
context.reached_end = true;
return '\0';
}
return err?;
}
if (c == 0)
{
context.reached_end = true;
}
return c;
}
fn JsonTokenType? advance(JsonContext* context) @local
{
char c;
// Skip whitespace
while WS: (c = read_next(context)!)
{
switch (c)
{
case '\n':
context.line++;
nextcase;
case ' ':
case '\t':
case '\r':
case '\v':
continue;
case '/':
if (!context.skip_comments) break;
c = read_next(context)!;
if (c != '*')
{
pushback(context, c);
break WS;
}
while COMMENT: (true)
{
// Skip to */
while (c = read_next(context)!)
{
if (c == '\n') context.line++;
if (c != '*') continue;
// Skip through all the '*'
while (c = read_next(context)!)
{
if (c == '\n') context.line++;
if (c != '*') break;
}
if (c == '/') break COMMENT;
}
}
continue;
default:
break WS;
}
}
switch (c)
{
case '\0':
return io::EOF?;
case '{':
return LBRACE;
case '}':
return RBRACE;
case '[':
return LBRACKET;
case ']':
return RBRACKET;
case ':':
return COLON;
case ',':
return COMMA;
case '"':
return lex_string(context);
case '-':
case '0'..'9':
return lex_number(context, c);
case 't':
match(context, "rue")!;
return TRUE;
case 'f':
match(context, "alse")!;
return FALSE;
case 'n':
match(context, "ull")!;
return NULL;
default:
return UNEXPECTED_CHARACTER?;
}
}
fn void? match(JsonContext* context, String str) @local
{
foreach (c : str)
{
char l = read_next(context)!;
if (l != c) return UNEXPECTED_CHARACTER?;
}
}
fn void? parse_expected(JsonContext* context, JsonTokenType token) @local
{
if (advance(context)! != token) return UNEXPECTED_CHARACTER?;
}
fn JsonTokenType? lex_string(JsonContext* context)
{
context.last_string.clear();
while LOOP: (true)
{
char c = read_next(context)!;
switch (c)
{
case '\0':
return io::EOF?;
case 1..31:
return UNEXPECTED_CHARACTER?;
case '"':
break LOOP;
case '\\':
break;
default:
context.last_string.append(c);
continue;
}
c = read_next(context)!;
switch (c)
{
case '\0':
return io::EOF?;
case 1..31:
return UNEXPECTED_CHARACTER?;
case '"':
case '\\':
case '/':
break;
case 'b':
c = '\b';
case 'f':
c = '\f';
case 'n':
c = '\n';
case 'r':
c = '\r';
case 't':
c = '\t';
case 'u':
uint val;
for (int i = 0; i < 4; i++)
{
c = read_next(context)!;
if (!c.is_xdigit()) return INVALID_ESCAPE_SEQUENCE?;
val = val << 4 + (c > '9' ? (c | 32) - 'a' + 10 : c - '0');
}
context.last_string.append_char32(val);
continue;
default:
return INVALID_ESCAPE_SEQUENCE?;
}
context.last_string.append(c);
}
return STRING;
}

83
lib/std/enumset.c3 Normal file
View File

@@ -0,0 +1,83 @@
// TODO: ensure the type is an enum first.
module std::enumset<Enum>;
$assert(Enum.elements < 64, "Maximum number of elements for an enum used as enum set is 63");
$switch ($$C_INT_SIZE):
$case 64:
private define EnumSetType = ulong;
$case 32:
$if (Enum.elements < 32):
private define EnumSetType = uint;
$else:
private define EnumSetType = ulong;
$endif;
$default:
$if (Enum.elements < 16):
private define EnumSetType = ushort;
$elif (Enum.elements < 31):
private define EnumSetType = uint;
$else:
private define EnumSetType = ulong;
$endif;
$endswitch;
define EnumSet = distinct EnumSetType;
fn void EnumSet.add(EnumSet* this, Enum v)
{
*this = (EnumSet)((EnumSetType)*this | 1u << (EnumSetType)v);
}
fn void EnumSet.clear(EnumSet* this)
{
*this = 0;
}
fn bool EnumSet.remove(EnumSet* this, Enum v)
{
EnumSetType old = (EnumSetType)*this;
EnumSetType new = old & ~(1u << (EnumSetType)v);
*this = (EnumSet)new;
return old != new;
}
fn bool EnumSet.has(EnumSet* this, Enum v)
{
return ((EnumSetType)*this & (1u << (EnumSetType)v)) != 0;
}
fn void EnumSet.add_all(EnumSet* this, EnumSet s)
{
*this = (EnumSet)((EnumSetType)*this | (EnumSetType)s);
}
fn void EnumSet.retain_all(EnumSet* this, EnumSet s)
{
*this = (EnumSet)((EnumSetType)*this & (EnumSetType)s);
}
fn void EnumSet.remove_all(EnumSet* this, EnumSet s)
{
*this = (EnumSet)((EnumSetType)*this & ~(EnumSetType)s);
}
fn EnumSet EnumSet.and_of(EnumSet* this, EnumSet s)
{
return (EnumSet)((EnumSetType)*this & (EnumSetType)s);
}
fn EnumSet EnumSet.or_of(EnumSet* this, EnumSet s)
{
return (EnumSet)((EnumSetType)*this | (EnumSetType)s);
}
fn EnumSet EnumSet.diff_of(EnumSet* this, EnumSet s)
{
return (EnumSet)((EnumSetType)*this & ~(EnumSetType)s);
}
fn EnumSet EnumSet.xor_of(EnumSet* this, EnumSet s)
{
return (EnumSet)((EnumSetType)*this ^ (EnumSetType)s);
}

View File

@@ -1,94 +0,0 @@
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 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 NO_MORE_ELEMENT?;
};
}
}

View File

@@ -4,50 +4,50 @@
module std::hash::adler32;
const uint ADLER_CONST @private = 65521;
private const uint ADLER_CONST = 65521;
struct Adler32
{
uint a;
uint b;
uint a;
uint b;
}
fn void Adler32.init(&self)
fn void Adler32.init(Adler32 *this)
{
*self = { 1, 0 };
*this = { 1, 0 };
}
fn void Adler32.updatec(&self, char c)
fn void Adler32.updatec(Adler32* this, char c)
{
self.a = (self.a + c) % ADLER_CONST;
self.b = (self.b + self.a) % ADLER_CONST;
this.a = (this.a + c) % ADLER_CONST;
this.b = (this.b + this.a) % ADLER_CONST;
}
fn void Adler32.update(&self, char[] data)
fn void Adler32.update(Adler32* this, char[] data)
{
uint a = self.a;
uint b = self.b;
foreach (char x : data)
{
a = (a + x) % ADLER_CONST;
b = (b + a) % ADLER_CONST;
}
*self = { a, b };
uint a = this.a;
uint b = this.b;
foreach (char x : data)
{
a = (a + x) % ADLER_CONST;
b = (b + a) % ADLER_CONST;
}
*this = { a, b };
}
fn uint Adler32.final(&self)
fn uint Adler32.final(Adler32* this)
{
return (self.b << 16) | self.a;
return (this.b << 16) | this.a;
}
fn uint hash(char[] data)
fn uint encode(char[] data)
{
uint a = 1;
uint b = 0;
foreach (char x : data)
{
a = (a + x) % ADLER_CONST;
b = (b + a) % ADLER_CONST;
}
return (b << 16) | a;
uint a = 1;
uint b = 0;
foreach (char x : data)
{
a = (a + x) % ADLER_CONST;
b = (b + a) % ADLER_CONST;
}
return (b << 16) | a;
}

View File

@@ -5,45 +5,45 @@ module std::hash::crc32;
struct Crc32
{
uint result;
uint result;
}
fn void Crc32.init(&self, uint seed = 0)
fn void Crc32.init(Crc32* this, uint seed = 0)
{
self.result = ~seed;
this.result = ~seed;
}
fn void Crc32.updatec(&self, char c)
fn void Crc32.updatec(Crc32* this, char c)
{
self.result = (self.result >> 8) ^ CRC32_TABLE[(self.result ^ c) & 0xFF];
this.result = (this.result >> 8) ^ CRC32_TABLE[(this.result ^ c) & 0xFF];
}
fn void Crc32.update(&self, char[] data)
fn void Crc32.update(Crc32* this, char[] data)
{
uint result = self.result;
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
self.result = result;
uint result = this.result;
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
this.result = result;
}
fn uint Crc32.final(&self)
fn uint Crc32.final(Crc32* this)
{
return ~self.result;
return ~this.result;
}
fn uint hash(char[] data)
fn uint encode(char[] data)
{
uint result = ~(uint)(0);
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
uint result = ~(uint)(0);
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
return ~result;
}
const uint[256] CRC32_TABLE @private = {
private const uint[256] CRC32_TABLE = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,

View File

@@ -5,107 +5,107 @@ module std::hash::crc64;
struct Crc64
{
ulong result;
ulong result;
}
fn void Crc64.init(&self, uint seed = 0)
fn void Crc64.init(Crc64* this, uint seed = 0)
{
self.result = seed;
this.result = seed;
}
fn void Crc64.updatec(&self, char c)
fn void Crc64.updatec(Crc64* this, char c)
{
self.result = (self.result << 8) ^ CRC64_TABLE[(char)((self.result >> 56) ^ c)];
this.result = (this.result << 8) ^ CRC64_TABLE[(char)((this.result >> 56) ^ c)];
}
fn void Crc64.update(&self, char[] data)
fn void Crc64.update(Crc64* this, char[] data)
{
ulong result = self.result;
foreach (char x : data)
{
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
}
self.result = result;
ulong result = this.result;
foreach (char x : data)
{
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
}
this.result = result;
}
fn ulong Crc64.final(&self)
fn ulong Crc64.final(Crc64* this)
{
return self.result;
return this.result;
}
fn ulong hash(char[] data)
fn ulong encode(char[] data)
{
ulong result = (ulong)(0);
foreach (char x : data)
{
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
}
ulong result = (ulong)(0);
foreach (char x : data)
{
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
}
return result;
}
const ulong[256] CRC64_TABLE @private = {
0x0000000000000000, 0x42f0e1eba9ea3693, 0x85e1c3d753d46d26, 0xc711223cfa3e5bb5,
0x493366450e42ecdf, 0x0bc387aea7a8da4c, 0xccd2a5925d9681f9, 0x8e224479f47cb76a,
0x9266cc8a1c85d9be, 0xd0962d61b56fef2d, 0x17870f5d4f51b498, 0x5577eeb6e6bb820b,
0xdb55aacf12c73561, 0x99a54b24bb2d03f2, 0x5eb4691841135847, 0x1c4488f3e8f96ed4,
0x663d78ff90e185ef, 0x24cd9914390bb37c, 0xe3dcbb28c335e8c9, 0xa12c5ac36adfde5a,
0x2f0e1eba9ea36930, 0x6dfeff5137495fa3, 0xaaefdd6dcd770416, 0xe81f3c86649d3285,
0xf45bb4758c645c51, 0xb6ab559e258e6ac2, 0x71ba77a2dfb03177, 0x334a9649765a07e4,
0xbd68d2308226b08e, 0xff9833db2bcc861d, 0x388911e7d1f2dda8, 0x7a79f00c7818eb3b,
0xcc7af1ff21c30bde, 0x8e8a101488293d4d, 0x499b3228721766f8, 0x0b6bd3c3dbfd506b,
0x854997ba2f81e701, 0xc7b97651866bd192, 0x00a8546d7c558a27, 0x4258b586d5bfbcb4,
0x5e1c3d753d46d260, 0x1cecdc9e94ace4f3, 0xdbfdfea26e92bf46, 0x990d1f49c77889d5,
0x172f5b3033043ebf, 0x55dfbadb9aee082c, 0x92ce98e760d05399, 0xd03e790cc93a650a,
0xaa478900b1228e31, 0xe8b768eb18c8b8a2, 0x2fa64ad7e2f6e317, 0x6d56ab3c4b1cd584,
0xe374ef45bf6062ee, 0xa1840eae168a547d, 0x66952c92ecb40fc8, 0x2465cd79455e395b,
0x3821458aada7578f, 0x7ad1a461044d611c, 0xbdc0865dfe733aa9, 0xff3067b657990c3a,
0x711223cfa3e5bb50, 0x33e2c2240a0f8dc3, 0xf4f3e018f031d676, 0xb60301f359dbe0e5,
0xda050215ea6c212f, 0x98f5e3fe438617bc, 0x5fe4c1c2b9b84c09, 0x1d14202910527a9a,
0x93366450e42ecdf0, 0xd1c685bb4dc4fb63, 0x16d7a787b7faa0d6, 0x5427466c1e109645,
0x4863ce9ff6e9f891, 0x0a932f745f03ce02, 0xcd820d48a53d95b7, 0x8f72eca30cd7a324,
0x0150a8daf8ab144e, 0x43a04931514122dd, 0x84b16b0dab7f7968, 0xc6418ae602954ffb,
0xbc387aea7a8da4c0, 0xfec89b01d3679253, 0x39d9b93d2959c9e6, 0x7b2958d680b3ff75,
0xf50b1caf74cf481f, 0xb7fbfd44dd257e8c, 0x70eadf78271b2539, 0x321a3e938ef113aa,
0x2e5eb66066087d7e, 0x6cae578bcfe24bed, 0xabbf75b735dc1058, 0xe94f945c9c3626cb,
0x676dd025684a91a1, 0x259d31cec1a0a732, 0xe28c13f23b9efc87, 0xa07cf2199274ca14,
0x167ff3eacbaf2af1, 0x548f120162451c62, 0x939e303d987b47d7, 0xd16ed1d631917144,
0x5f4c95afc5edc62e, 0x1dbc74446c07f0bd, 0xdaad56789639ab08, 0x985db7933fd39d9b,
0x84193f60d72af34f, 0xc6e9de8b7ec0c5dc, 0x01f8fcb784fe9e69, 0x43081d5c2d14a8fa,
0xcd2a5925d9681f90, 0x8fdab8ce70822903, 0x48cb9af28abc72b6, 0x0a3b7b1923564425,
0x70428b155b4eaf1e, 0x32b26afef2a4998d, 0xf5a348c2089ac238, 0xb753a929a170f4ab,
0x3971ed50550c43c1, 0x7b810cbbfce67552, 0xbc902e8706d82ee7, 0xfe60cf6caf321874,
0xe224479f47cb76a0, 0xa0d4a674ee214033, 0x67c58448141f1b86, 0x253565a3bdf52d15,
0xab1721da49899a7f, 0xe9e7c031e063acec, 0x2ef6e20d1a5df759, 0x6c0603e6b3b7c1ca,
0xf6fae5c07d3274cd, 0xb40a042bd4d8425e, 0x731b26172ee619eb, 0x31ebc7fc870c2f78,
0xbfc9838573709812, 0xfd39626eda9aae81, 0x3a28405220a4f534, 0x78d8a1b9894ec3a7,
0x649c294a61b7ad73, 0x266cc8a1c85d9be0, 0xe17dea9d3263c055, 0xa38d0b769b89f6c6,
0x2daf4f0f6ff541ac, 0x6f5faee4c61f773f, 0xa84e8cd83c212c8a, 0xeabe6d3395cb1a19,
0x90c79d3fedd3f122, 0xd2377cd44439c7b1, 0x15265ee8be079c04, 0x57d6bf0317edaa97,
0xd9f4fb7ae3911dfd, 0x9b041a914a7b2b6e, 0x5c1538adb04570db, 0x1ee5d94619af4648,
0x02a151b5f156289c, 0x4051b05e58bc1e0f, 0x87409262a28245ba, 0xc5b073890b687329,
0x4b9237f0ff14c443, 0x0962d61b56fef2d0, 0xce73f427acc0a965, 0x8c8315cc052a9ff6,
0x3a80143f5cf17f13, 0x7870f5d4f51b4980, 0xbf61d7e80f251235, 0xfd913603a6cf24a6,
0x73b3727a52b393cc, 0x31439391fb59a55f, 0xf652b1ad0167feea, 0xb4a25046a88dc879,
0xa8e6d8b54074a6ad, 0xea16395ee99e903e, 0x2d071b6213a0cb8b, 0x6ff7fa89ba4afd18,
0xe1d5bef04e364a72, 0xa3255f1be7dc7ce1, 0x64347d271de22754, 0x26c49cccb40811c7,
0x5cbd6cc0cc10fafc, 0x1e4d8d2b65facc6f, 0xd95caf179fc497da, 0x9bac4efc362ea149,
0x158e0a85c2521623, 0x577eeb6e6bb820b0, 0x906fc95291867b05, 0xd29f28b9386c4d96,
0xcedba04ad0952342, 0x8c2b41a1797f15d1, 0x4b3a639d83414e64, 0x09ca82762aab78f7,
0x87e8c60fded7cf9d, 0xc51827e4773df90e, 0x020905d88d03a2bb, 0x40f9e43324e99428,
0x2cffe7d5975e55e2, 0x6e0f063e3eb46371, 0xa91e2402c48a38c4, 0xebeec5e96d600e57,
0x65cc8190991cb93d, 0x273c607b30f68fae, 0xe02d4247cac8d41b, 0xa2dda3ac6322e288,
0xbe992b5f8bdb8c5c, 0xfc69cab42231bacf, 0x3b78e888d80fe17a, 0x7988096371e5d7e9,
0xf7aa4d1a85996083, 0xb55aacf12c735610, 0x724b8ecdd64d0da5, 0x30bb6f267fa73b36,
0x4ac29f2a07bfd00d, 0x08327ec1ae55e69e, 0xcf235cfd546bbd2b, 0x8dd3bd16fd818bb8,
0x03f1f96f09fd3cd2, 0x41011884a0170a41, 0x86103ab85a2951f4, 0xc4e0db53f3c36767,
0xd8a453a01b3a09b3, 0x9a54b24bb2d03f20, 0x5d45907748ee6495, 0x1fb5719ce1045206,
0x919735e51578e56c, 0xd367d40ebc92d3ff, 0x1476f63246ac884a, 0x568617d9ef46bed9,
0xe085162ab69d5e3c, 0xa275f7c11f7768af, 0x6564d5fde549331a, 0x279434164ca30589,
0xa9b6706fb8dfb2e3, 0xeb46918411358470, 0x2c57b3b8eb0bdfc5, 0x6ea7525342e1e956,
0x72e3daa0aa188782, 0x30133b4b03f2b111, 0xf7021977f9cceaa4, 0xb5f2f89c5026dc37,
0x3bd0bce5a45a6b5d, 0x79205d0e0db05dce, 0xbe317f32f78e067b, 0xfcc19ed95e6430e8,
0x86b86ed5267cdbd3, 0xc4488f3e8f96ed40, 0x0359ad0275a8b6f5, 0x41a94ce9dc428066,
0xcf8b0890283e370c, 0x8d7be97b81d4019f, 0x4a6acb477bea5a2a, 0x089a2aacd2006cb9,
0x14dea25f3af9026d, 0x562e43b4931334fe, 0x913f6188692d6f4b, 0xd3cf8063c0c759d8,
0x5dedc41a34bbeeb2, 0x1f1d25f19d51d821, 0xd80c07cd676f8394, 0x9afce626ce85b507,
private const ulong[256] CRC64_TABLE = {
0x0000000000000000, 0x42f0e1eba9ea3693, 0x85e1c3d753d46d26, 0xc711223cfa3e5bb5,
0x493366450e42ecdf, 0x0bc387aea7a8da4c, 0xccd2a5925d9681f9, 0x8e224479f47cb76a,
0x9266cc8a1c85d9be, 0xd0962d61b56fef2d, 0x17870f5d4f51b498, 0x5577eeb6e6bb820b,
0xdb55aacf12c73561, 0x99a54b24bb2d03f2, 0x5eb4691841135847, 0x1c4488f3e8f96ed4,
0x663d78ff90e185ef, 0x24cd9914390bb37c, 0xe3dcbb28c335e8c9, 0xa12c5ac36adfde5a,
0x2f0e1eba9ea36930, 0x6dfeff5137495fa3, 0xaaefdd6dcd770416, 0xe81f3c86649d3285,
0xf45bb4758c645c51, 0xb6ab559e258e6ac2, 0x71ba77a2dfb03177, 0x334a9649765a07e4,
0xbd68d2308226b08e, 0xff9833db2bcc861d, 0x388911e7d1f2dda8, 0x7a79f00c7818eb3b,
0xcc7af1ff21c30bde, 0x8e8a101488293d4d, 0x499b3228721766f8, 0x0b6bd3c3dbfd506b,
0x854997ba2f81e701, 0xc7b97651866bd192, 0x00a8546d7c558a27, 0x4258b586d5bfbcb4,
0x5e1c3d753d46d260, 0x1cecdc9e94ace4f3, 0xdbfdfea26e92bf46, 0x990d1f49c77889d5,
0x172f5b3033043ebf, 0x55dfbadb9aee082c, 0x92ce98e760d05399, 0xd03e790cc93a650a,
0xaa478900b1228e31, 0xe8b768eb18c8b8a2, 0x2fa64ad7e2f6e317, 0x6d56ab3c4b1cd584,
0xe374ef45bf6062ee, 0xa1840eae168a547d, 0x66952c92ecb40fc8, 0x2465cd79455e395b,
0x3821458aada7578f, 0x7ad1a461044d611c, 0xbdc0865dfe733aa9, 0xff3067b657990c3a,
0x711223cfa3e5bb50, 0x33e2c2240a0f8dc3, 0xf4f3e018f031d676, 0xb60301f359dbe0e5,
0xda050215ea6c212f, 0x98f5e3fe438617bc, 0x5fe4c1c2b9b84c09, 0x1d14202910527a9a,
0x93366450e42ecdf0, 0xd1c685bb4dc4fb63, 0x16d7a787b7faa0d6, 0x5427466c1e109645,
0x4863ce9ff6e9f891, 0x0a932f745f03ce02, 0xcd820d48a53d95b7, 0x8f72eca30cd7a324,
0x0150a8daf8ab144e, 0x43a04931514122dd, 0x84b16b0dab7f7968, 0xc6418ae602954ffb,
0xbc387aea7a8da4c0, 0xfec89b01d3679253, 0x39d9b93d2959c9e6, 0x7b2958d680b3ff75,
0xf50b1caf74cf481f, 0xb7fbfd44dd257e8c, 0x70eadf78271b2539, 0x321a3e938ef113aa,
0x2e5eb66066087d7e, 0x6cae578bcfe24bed, 0xabbf75b735dc1058, 0xe94f945c9c3626cb,
0x676dd025684a91a1, 0x259d31cec1a0a732, 0xe28c13f23b9efc87, 0xa07cf2199274ca14,
0x167ff3eacbaf2af1, 0x548f120162451c62, 0x939e303d987b47d7, 0xd16ed1d631917144,
0x5f4c95afc5edc62e, 0x1dbc74446c07f0bd, 0xdaad56789639ab08, 0x985db7933fd39d9b,
0x84193f60d72af34f, 0xc6e9de8b7ec0c5dc, 0x01f8fcb784fe9e69, 0x43081d5c2d14a8fa,
0xcd2a5925d9681f90, 0x8fdab8ce70822903, 0x48cb9af28abc72b6, 0x0a3b7b1923564425,
0x70428b155b4eaf1e, 0x32b26afef2a4998d, 0xf5a348c2089ac238, 0xb753a929a170f4ab,
0x3971ed50550c43c1, 0x7b810cbbfce67552, 0xbc902e8706d82ee7, 0xfe60cf6caf321874,
0xe224479f47cb76a0, 0xa0d4a674ee214033, 0x67c58448141f1b86, 0x253565a3bdf52d15,
0xab1721da49899a7f, 0xe9e7c031e063acec, 0x2ef6e20d1a5df759, 0x6c0603e6b3b7c1ca,
0xf6fae5c07d3274cd, 0xb40a042bd4d8425e, 0x731b26172ee619eb, 0x31ebc7fc870c2f78,
0xbfc9838573709812, 0xfd39626eda9aae81, 0x3a28405220a4f534, 0x78d8a1b9894ec3a7,
0x649c294a61b7ad73, 0x266cc8a1c85d9be0, 0xe17dea9d3263c055, 0xa38d0b769b89f6c6,
0x2daf4f0f6ff541ac, 0x6f5faee4c61f773f, 0xa84e8cd83c212c8a, 0xeabe6d3395cb1a19,
0x90c79d3fedd3f122, 0xd2377cd44439c7b1, 0x15265ee8be079c04, 0x57d6bf0317edaa97,
0xd9f4fb7ae3911dfd, 0x9b041a914a7b2b6e, 0x5c1538adb04570db, 0x1ee5d94619af4648,
0x02a151b5f156289c, 0x4051b05e58bc1e0f, 0x87409262a28245ba, 0xc5b073890b687329,
0x4b9237f0ff14c443, 0x0962d61b56fef2d0, 0xce73f427acc0a965, 0x8c8315cc052a9ff6,
0x3a80143f5cf17f13, 0x7870f5d4f51b4980, 0xbf61d7e80f251235, 0xfd913603a6cf24a6,
0x73b3727a52b393cc, 0x31439391fb59a55f, 0xf652b1ad0167feea, 0xb4a25046a88dc879,
0xa8e6d8b54074a6ad, 0xea16395ee99e903e, 0x2d071b6213a0cb8b, 0x6ff7fa89ba4afd18,
0xe1d5bef04e364a72, 0xa3255f1be7dc7ce1, 0x64347d271de22754, 0x26c49cccb40811c7,
0x5cbd6cc0cc10fafc, 0x1e4d8d2b65facc6f, 0xd95caf179fc497da, 0x9bac4efc362ea149,
0x158e0a85c2521623, 0x577eeb6e6bb820b0, 0x906fc95291867b05, 0xd29f28b9386c4d96,
0xcedba04ad0952342, 0x8c2b41a1797f15d1, 0x4b3a639d83414e64, 0x09ca82762aab78f7,
0x87e8c60fded7cf9d, 0xc51827e4773df90e, 0x020905d88d03a2bb, 0x40f9e43324e99428,
0x2cffe7d5975e55e2, 0x6e0f063e3eb46371, 0xa91e2402c48a38c4, 0xebeec5e96d600e57,
0x65cc8190991cb93d, 0x273c607b30f68fae, 0xe02d4247cac8d41b, 0xa2dda3ac6322e288,
0xbe992b5f8bdb8c5c, 0xfc69cab42231bacf, 0x3b78e888d80fe17a, 0x7988096371e5d7e9,
0xf7aa4d1a85996083, 0xb55aacf12c735610, 0x724b8ecdd64d0da5, 0x30bb6f267fa73b36,
0x4ac29f2a07bfd00d, 0x08327ec1ae55e69e, 0xcf235cfd546bbd2b, 0x8dd3bd16fd818bb8,
0x03f1f96f09fd3cd2, 0x41011884a0170a41, 0x86103ab85a2951f4, 0xc4e0db53f3c36767,
0xd8a453a01b3a09b3, 0x9a54b24bb2d03f20, 0x5d45907748ee6495, 0x1fb5719ce1045206,
0x919735e51578e56c, 0xd367d40ebc92d3ff, 0x1476f63246ac884a, 0x568617d9ef46bed9,
0xe085162ab69d5e3c, 0xa275f7c11f7768af, 0x6564d5fde549331a, 0x279434164ca30589,
0xa9b6706fb8dfb2e3, 0xeb46918411358470, 0x2c57b3b8eb0bdfc5, 0x6ea7525342e1e956,
0x72e3daa0aa188782, 0x30133b4b03f2b111, 0xf7021977f9cceaa4, 0xb5f2f89c5026dc37,
0x3bd0bce5a45a6b5d, 0x79205d0e0db05dce, 0xbe317f32f78e067b, 0xfcc19ed95e6430e8,
0x86b86ed5267cdbd3, 0xc4488f3e8f96ed40, 0x0359ad0275a8b6f5, 0x41a94ce9dc428066,
0xcf8b0890283e370c, 0x8d7be97b81d4019f, 0x4a6acb477bea5a2a, 0x089a2aacd2006cb9,
0x14dea25f3af9026d, 0x562e43b4931334fe, 0x913f6188692d6f4b, 0xd3cf8063c0c759d8,
0x5dedc41a34bbeeb2, 0x1f1d25f19d51d821, 0xd80c07cd676f8394, 0x9afce626ce85b507,
};

View File

@@ -1,41 +0,0 @@
// 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::hash::fnv32a;
typedef Fnv32a = uint;
const FNV32A_START @private = 0x811c9dc5;
const FNV32A_MUL @private = 0x01000193;
macro void update(h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV32A_MUL;
fn void Fnv32a.init(&self)
{
*self = FNV32A_START;
}
fn void Fnv32a.update(&self, char[] data)
{
Fnv32a h = *self;
foreach (char x : data)
{
update(&h, x);
}
*self = h;
}
macro void Fnv32a.update_char(&self, char c)
{
update(self, c);
}
fn uint hash(char[] data)
{
uint h = FNV32A_START;
foreach (char x : data)
{
update(&h, x);
}
return h;
}

View File

@@ -1,41 +0,0 @@
// 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::hash::fnv64a;
typedef Fnv64a = ulong;
const FNV64A_START @private = 0xcbf29ce484222325;
const FNV64A_MUL @private = 0x00000100000001b3;
macro void update(h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV64A_MUL;
fn void Fnv64a.init(&self)
{
*self = FNV64A_START;
}
fn void Fnv64a.update(&self, char[] data)
{
Fnv64a h = *self;
foreach (char x : data)
{
update(&h, x);
}
*self = h;
}
macro void Fnv64a.update_char(&self, char c)
{
update(self, c);
}
fn ulong hash(char[] data)
{
ulong h = FNV64A_START;
foreach (char x : data)
{
update(&h, x);
}
return h;
}

View File

@@ -1,107 +0,0 @@
module std::hash::hmac{HashAlg, HASH_BYTES, BLOCK_BYTES};
import std::crypto;
struct Hmac
{
HashAlg a, b;
}
fn char[HASH_BYTES] hash(char[] key, char[] message)
{
Hmac hmac @noinit;
hmac.init(key);
hmac.update(message);
return hmac.final();
}
<*
@require output.len > 0 : "Output must be greater than zero"
@require output.len < int.max / HASH_BYTES : "Output is too large"
*>
fn void pbkdf2(char[] pw, char[] salt, uint iterations, char[] output)
{
usz l = output.len / HASH_BYTES;
usz r = output.len % HASH_BYTES;
Hmac hmac;
hmac.init(pw);
char[] dst_curr = output;
for (usz i = 1; i <= l; i++)
{
@derive(&hmac, salt, iterations, i, dst_curr[:HASH_BYTES]);
dst_curr = dst_curr[HASH_BYTES..];
}
if (r > 0)
{
char[HASH_BYTES] tmp;
@derive(&hmac, salt, iterations, l + 1, &tmp);
dst_curr[..] = tmp[:dst_curr.len];
mem::zero_volatile(&tmp);
}
}
fn void Hmac.init(&self, char[] key)
{
char[BLOCK_BYTES] buffer;
if (key.len > BLOCK_BYTES)
{
self.a.init();
self.a.update(key);
buffer[:HASH_BYTES] = self.a.final()[..];
}
else
{
buffer[:key.len] = key[..];
}
foreach (&b : buffer) *b ^= IPAD;
self.a.init();
self.a.update(&buffer);
foreach (&b : buffer) *b ^= IPAD ^ OPAD;
self.b.init();
self.b.update(&buffer);
mem::zero_volatile(&buffer);
}
fn void Hmac.update(&self, char[] data)
{
self.a.update(data);
}
fn char[HASH_BYTES] Hmac.final(&self)
{
self.b.update(&&self.a.final());
return self.b.final();
}
const IPAD @private = 0x36;
const OPAD @private = 0x5C;
macro @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[] out)
{
assert(out.len == HASH_BYTES);
char[HASH_BYTES] tmp @noinit;
defer mem::zero_volatile(&tmp);
Hmac hmac = *hmac_start;
hmac.update(salt);
UIntBE be = { (uint)index };
hmac.update(&&bitcast(be, char[4]));
tmp = hmac.final();
out[..] = tmp[..];
for (int it = 1; it < iterations; it++)
{
hmac = *hmac_start;
hmac.update(&tmp);
tmp = hmac.final();
foreach (i, v : tmp)
{
out[i] ^= v;
}
}
}

View File

@@ -1,225 +0,0 @@
module std::hash::md5;
import std::hash::hmac;
import std::bits;
const BLOCK_BYTES = 64;
const HASH_BYTES = 16;
struct Md5
{
uint lo, hi;
uint a, b, c, d;
char[64] buffer;
uint[16] block;
}
alias HmacMd5 = Hmac{Md5, HASH_BYTES, BLOCK_BYTES};
alias hmac = hmac::hash{Md5, HASH_BYTES, BLOCK_BYTES};
alias pbkdf2 = hmac::pbkdf2{Md5, HASH_BYTES, BLOCK_BYTES};
fn char[HASH_BYTES] hash(char[] data)
{
Md5 md5;
md5.init();
md5.update(data);
return md5.final();
}
fn void Md5.init(&self)
{
self.a = 0x67452301;
self.b = 0xefcdab89;
self.c = 0x98badcfe;
self.d = 0x10325476;
self.lo = 0;
self.hi = 0;
}
fn void Md5.update(&ctx, char[] data)
{
uint saved_lo = ctx.lo;
if ((ctx.lo = (saved_lo + data.len) & 0x1fffffff) < saved_lo) ctx.hi++;
ctx.hi += data.len >> 29;
usz used = (usz)saved_lo & 0x3f;
if (used)
{
usz available = 64 - used;
if (data.len < available)
{
ctx.buffer[used:data.len] = data[..];
return;
}
ctx.buffer[used:available] = data[:available];
data = data[available..];
body(ctx, &ctx.buffer, 64);
}
if (data.len >= 64)
{
data = body(ctx, data, data.len & ~(usz)0x3f)[:data.len & 0x3f];
}
ctx.buffer[:data.len] = data[..];
}
fn char[HASH_BYTES] Md5.final(&ctx)
{
usz used = (usz)ctx.lo & 0x3f;
ctx.buffer[used++] = 0x80;
usz available = 64 - used;
if (available < 8)
{
ctx.buffer[used:available] = 0;
body(ctx, &ctx.buffer, 64);
used = 0;
available = 64;
}
ctx.buffer[used:available - 8] = 0;
ctx.lo <<= 3;
ctx.buffer[56:4] = bitcast(ctx.lo, char[4])[..];
ctx.buffer[60:4] = bitcast(ctx.hi, char[4])[..];
body(ctx, &ctx.buffer, 64);
char[16] res @noinit;
res[0:4] = bitcast(ctx.a, char[4])[..];
res[4:4] = bitcast(ctx.b, char[4])[..];
res[8:4] = bitcast(ctx.c, char[4])[..];
res[12:4] = bitcast(ctx.d, char[4])[..];
*ctx = {};
return res;
}
module std::hash::md5 @private;
// Implementation
macro @f(x, y, z) => z ^ (x & (y ^ z));
macro @g(x, y, z) => y ^ (z & (x ^ y));
macro @h(x, y, z) => (x ^ y) ^ z;
macro @h2(x, y, z) => x ^ (y ^ z);
macro @i(x, y, z) => y ^ (x | ~z);
macro @step(#f, a, b, c, d, ptr, n, t, s)
{
*a += #f(b, c, d) + @unaligned_load(*(uint *)&ptr[n * 4], 2) + t;
*a = (*a << s) | ((*a & 0xffffffff) >> (32 - s));
*a += b;
}
fn char* body(Md5* ctx, void* data, usz size)
{
char* ptr;
uint a, b, c, d;
uint saved_a, saved_b, saved_c, saved_d;
ptr = data;
a = ctx.a;
b = ctx.b;
c = ctx.c;
d = ctx.d;
do
{
saved_a = a;
saved_b = b;
saved_c = c;
saved_d = d;
/* Round 1 */
@step(@f, &a, b, c, d, ptr, 0, 0xd76aa478, 7) ;
@step(@f, &d, a, b, c, ptr, 1, 0xe8c7b756, 12) ;
@step(@f, &c, d, a, b, ptr, 2, 0x242070db, 17) ;
@step(@f, &b, c, d, a, ptr, 3, 0xc1bdceee, 22) ;
@step(@f, &a, b, c, d, ptr, 4, 0xf57c0faf, 7) ;
@step(@f, &d, a, b, c, ptr, 5, 0x4787c62a, 12) ;
@step(@f, &c, d, a, b, ptr, 6, 0xa8304613, 17) ;
@step(@f, &b, c, d, a, ptr, 7, 0xfd469501, 22) ;
@step(@f, &a, b, c, d, ptr, 8, 0x698098d8, 7) ;
@step(@f, &d, a, b, c, ptr, 9, 0x8b44f7af, 12) ;
@step(@f, &c, d, a, b, ptr, 10, 0xffff5bb1, 17);
@step(@f, &b, c, d, a, ptr, 11, 0x895cd7be, 22);
@step(@f, &a, b, c, d, ptr, 12, 0x6b901122, 7) ;
@step(@f, &d, a, b, c, ptr, 13, 0xfd987193, 12);
@step(@f, &c, d, a, b, ptr, 14, 0xa679438e, 17);
@step(@f, &b, c, d, a, ptr, 15, 0x49b40821, 22);
/* Round 2 */
@step(@g, &a, b, c, d, ptr, 1, 0xf61e2562, 5) ;
@step(@g, &d, a, b, c, ptr, 6, 0xc040b340, 9) ;
@step(@g, &c, d, a, b, ptr, 11, 0x265e5a51, 14);
@step(@g, &b, c, d, a, ptr, 0, 0xe9b6c7aa, 20) ;
@step(@g, &a, b, c, d, ptr, 5, 0xd62f105d, 5) ;
@step(@g, &d, a, b, c, ptr, 10, 0x02441453, 9) ;
@step(@g, &c, d, a, b, ptr, 15, 0xd8a1e681, 14);
@step(@g, &b, c, d, a, ptr, 4, 0xe7d3fbc8, 20) ;
@step(@g, &a, b, c, d, ptr, 9, 0x21e1cde6, 5) ;
@step(@g, &d, a, b, c, ptr, 14, 0xc33707d6, 9) ;
@step(@g, &c, d, a, b, ptr, 3, 0xf4d50d87, 14) ;
@step(@g, &b, c, d, a, ptr, 8, 0x455a14ed, 20) ;
@step(@g, &a, b, c, d, ptr, 13, 0xa9e3e905, 5) ;
@step(@g, &d, a, b, c, ptr, 2, 0xfcefa3f8, 9) ;
@step(@g, &c, d, a, b, ptr, 7, 0x676f02d9, 14) ;
@step(@g, &b, c, d, a, ptr, 12, 0x8d2a4c8a, 20);
/* Round 3 */
@step(@h, &a, b, c, d, ptr, 5, 0xfffa3942, 4);
@step(@h2, &d, a, b, c, ptr, 8, 0x8771f681, 11);
@step(@h, &c, d, a, b, ptr, 11, 0x6d9d6122, 16);
@step(@h2, &b, c, d, a, ptr, 14, 0xfde5380c, 23);
@step(@h, &a, b, c, d, ptr, 1, 0xa4beea44, 4);
@step(@h2, &d, a, b, c, ptr, 4, 0x4bdecfa9, 11);
@step(@h, &c, d, a, b, ptr, 7, 0xf6bb4b60, 16);
@step(@h2, &b, c, d, a, ptr, 10, 0xbebfbc70, 23);
@step(@h, &a, b, c, d, ptr, 13, 0x289b7ec6, 4) ;
@step(@h2, &d, a, b, c, ptr, 0, 0xeaa127fa, 11) ;
@step(@h, &c, d, a, b, ptr, 3, 0xd4ef3085, 16) ;
@step(@h2, &b, c, d, a, ptr, 6, 0x04881d05, 23) ;
@step(@h, &a, b, c, d, ptr, 9, 0xd9d4d039, 4) ;
@step(@h2, &d, a, b, c, ptr, 12, 0xe6db99e5, 11) ;
@step(@h, &c, d, a, b, ptr, 15, 0x1fa27cf8, 16) ;
@step(@h2, &b, c, d, a, ptr, 2, 0xc4ac5665, 23) ;
/* Round 4 */
@step(@i, &a, b, c, d, ptr, 0, 0xf4292244, 6) ;
@step(@i, &d, a, b, c, ptr, 7, 0x432aff97, 10) ;
@step(@i, &c, d, a, b, ptr, 14, 0xab9423a7, 15) ;
@step(@i, &b, c, d, a, ptr, 5, 0xfc93a039, 21) ;
@step(@i, &a, b, c, d, ptr, 12, 0x655b59c3, 6) ;
@step(@i, &d, a, b, c, ptr, 3, 0x8f0ccc92, 10) ;
@step(@i, &c, d, a, b, ptr, 10, 0xffeff47d, 15) ;
@step(@i, &b, c, d, a, ptr, 1, 0x85845dd1, 21) ;
@step(@i, &a, b, c, d, ptr, 8, 0x6fa87e4f, 6) ;
@step(@i, &d, a, b, c, ptr, 15, 0xfe2ce6e0, 10) ;
@step(@i, &c, d, a, b, ptr, 6, 0xa3014314, 15) ;
@step(@i, &b, c, d, a, ptr, 13, 0x4e0811a1, 21) ;
@step(@i, &a, b, c, d, ptr, 4, 0xf7537e82, 6) ;
@step(@i, &d, a, b, c, ptr, 11, 0xbd3af235, 10) ;
@step(@i, &c, d, a, b, ptr, 2, 0x2ad7d2bb, 15) ;
@step(@i, &b, c, d, a, ptr, 9, 0xeb86d391, 21) ;
a += saved_a;
b += saved_b;
c += saved_c;
d += saved_d;
ptr += 64;
} while (size -= 64);
ctx.a = a;
ctx.b = b;
ctx.c = c;
ctx.d = d;
return ptr;
}

View File

@@ -1,257 +0,0 @@
// 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.
//
// Implementation was off Steve Reid's SHA-1 C implementation
module std::hash::sha1;
import std::hash::hmac;
import std::bits;
const BLOCK_BYTES = 64;
const HASH_BYTES = 20;
struct Sha1
{
uint[5] state;
uint[2] count;
char[BLOCK_BYTES] buffer;
}
alias HmacSha1 = Hmac{Sha1, HASH_BYTES, BLOCK_BYTES};
alias hmac = hmac::hash{Sha1, HASH_BYTES, BLOCK_BYTES};
alias pbkdf2 = hmac::pbkdf2{Sha1, HASH_BYTES, BLOCK_BYTES};
fn char[HASH_BYTES] hash(char[] data)
{
Sha1 sha1 @noinit;
sha1.init();
sha1.update(data);
return sha1.final();
}
fn void Sha1.init(&self)
{
// SHA1 initialization constants
*self = {
.state = {
0x67452301,
0xEFCDAB89,
0x98BADCFE,
0x10325476,
0xC3D2E1F0
}
};
}
<*
@param [in] data
@require data.len <= uint.max
*>
fn void Sha1.update(&self, char[] data)
{
uint j = self.count[0];
uint len = data.len;
if ((self.count[0] += len << 3) < j) self.count[1]++;
self.count[1] += len >> 29;
j = (j >> 3) & 63;
uint i;
if (j + len > 63)
{
i = 64 - j;
self.buffer[j..] = data[:i];
sha1_transform(&self.state, &self.buffer);
for (; i + 63 < len; i += 64)
{
sha1_transform(&self.state, &data[i]);
}
j = 0;
}
self.buffer[j:len - i] = data[i..];
}
fn char[HASH_BYTES] Sha1.final(&self)
{
char[8] finalcount;
for (uint i = 0; i < 8; i++)
{
finalcount[i] = (char)((self.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
}
self.update((char[]){ 0o200 });
while ((self.count[0] & 504) != 448)
{
self.update((char[]){ 0 });
}
self.update(&finalcount);
char[HASH_BYTES] digest;
for (uint i = 0; i < HASH_BYTES; i++)
{
digest[i] = (char)((self.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF);
}
// Clear mem
*self = {};
finalcount = {};
return digest;
}
union Long16 @local
{
char[BLOCK_BYTES] c;
uint[16] l;
}
macro blk(Long16* block, i) @local
{
return (block.l[i & 15] = (block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15]
^ block.l[(i + 2) & 15] ^ block.l[i & 15]).rotl(1));
}
macro blk0(Long16* block, i) @local
{
$if env::BIG_ENDIAN:
return block.l[i];
$else
return block.l[i] = (block.l[i].rotl(24) & 0xFF00FF00)
| (block.l[i].rotl(8) & 0x00FF00FF);
$endif
}
macro r0(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + v.rotl(5);
*wref = w.rotl(30);
}
macro r1(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += ((w & (x ^ y)) ^ y) + blk(block, i) + 0x5A827999 + v.rotl(5);
*wref = w.rotl(30);
}
macro r2(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += (w ^ x ^ y) + blk(block, i) + 0x6ED9EBA1 + v.rotl(5);
*wref = w.rotl(30);
}
macro r3(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += (((w | x) & y) | (w & x)) + blk(block, i) + 0x8F1BBCDC + v.rotl(5);
*wref = w.rotl(30);
}
macro r4(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += (w ^ x ^ y) + blk(block, i) + 0xCA62C1D6 + v.rotl(5);
*wref = w.rotl(30);
}
<*
@param [&inout] state
@param [&in] buffer
*>
fn void sha1_transform(uint[5]* state, char* buffer) @local
{
Long16 block;
block.c[..] = buffer[:64];
uint a = (*state)[0];
uint b = (*state)[1];
uint c = (*state)[2];
uint d = (*state)[3];
uint e = (*state)[4];
r0(&block, a, &b, c, d, &e, 0);
r0(&block, e, &a, b, c, &d, 1);
r0(&block, d, &e, a, b, &c, 2);
r0(&block, c, &d, e, a, &b, 3);
r0(&block, b, &c, d, e, &a, 4);
r0(&block, a, &b, c, d, &e, 5);
r0(&block, e, &a, b, c, &d, 6);
r0(&block, d, &e, a, b, &c, 7);
r0(&block, c, &d, e, a, &b, 8);
r0(&block, b, &c, d, e, &a, 9);
r0(&block, a, &b, c, d, &e, 10);
r0(&block, e, &a, b, c, &d, 11);
r0(&block, d, &e, a, b, &c, 12);
r0(&block, c, &d, e, a, &b, 13);
r0(&block, b, &c, d, e, &a, 14);
r0(&block, a, &b, c, d, &e, 15);
r1(&block, e, &a, b, c, &d, 16);
r1(&block, d, &e, a, b, &c, 17);
r1(&block, c, &d, e, a, &b, 18);
r1(&block, b, &c, d, e, &a, 19);
r2(&block, a, &b, c, d, &e, 20);
r2(&block, e, &a, b, c, &d, 21);
r2(&block, d, &e, a, b, &c, 22);
r2(&block, c, &d, e, a, &b, 23);
r2(&block, b, &c, d, e, &a, 24);
r2(&block, a, &b, c, d, &e, 25);
r2(&block, e, &a, b, c, &d, 26);
r2(&block, d, &e, a, b, &c, 27);
r2(&block, c, &d, e, a, &b, 28);
r2(&block, b, &c, d, e, &a, 29);
r2(&block, a, &b, c, d, &e, 30);
r2(&block, e, &a, b, c, &d, 31);
r2(&block, d, &e, a, b, &c, 32);
r2(&block, c, &d, e, a, &b, 33);
r2(&block, b, &c, d, e, &a, 34);
r2(&block, a, &b, c, d, &e, 35);
r2(&block, e, &a, b, c, &d, 36);
r2(&block, d, &e, a, b, &c, 37);
r2(&block, c, &d, e, a, &b, 38);
r2(&block, b, &c, d, e, &a, 39);
r3(&block, a, &b, c, d, &e, 40);
r3(&block, e, &a, b, c, &d, 41);
r3(&block, d, &e, a, b, &c, 42);
r3(&block, c, &d, e, a, &b, 43);
r3(&block, b, &c, d, e, &a, 44);
r3(&block, a, &b, c, d, &e, 45);
r3(&block, e, &a, b, c, &d, 46);
r3(&block, d, &e, a, b, &c, 47);
r3(&block, c, &d, e, a, &b, 48);
r3(&block, b, &c, d, e, &a, 49);
r3(&block, a, &b, c, d, &e, 50);
r3(&block, e, &a, b, c, &d, 51);
r3(&block, d, &e, a, b, &c, 52);
r3(&block, c, &d, e, a, &b, 53);
r3(&block, b, &c, d, e, &a, 54);
r3(&block, a, &b, c, d, &e, 55);
r3(&block, e, &a, b, c, &d, 56);
r3(&block, d, &e, a, b, &c, 57);
r3(&block, c, &d, e, a, &b, 58);
r3(&block, b, &c, d, e, &a, 59);
r4(&block, a, &b, c, d, &e, 60);
r4(&block, e, &a, b, c, &d, 61);
r4(&block, d, &e, a, b, &c, 62);
r4(&block, c, &d, e, a, &b, 63);
r4(&block, b, &c, d, e, &a, 64);
r4(&block, a, &b, c, d, &e, 65);
r4(&block, e, &a, b, c, &d, 66);
r4(&block, d, &e, a, b, &c, 67);
r4(&block, c, &d, e, a, &b, 68);
r4(&block, b, &c, d, e, &a, 69);
r4(&block, a, &b, c, d, &e, 70);
r4(&block, e, &a, b, c, &d, 71);
r4(&block, d, &e, a, b, &c, 72);
r4(&block, c, &d, e, a, &b, 73);
r4(&block, b, &c, d, e, &a, 74);
r4(&block, a, &b, c, d, &e, 75);
r4(&block, e, &a, b, c, &d, 76);
r4(&block, d, &e, a, b, &c, 77);
r4(&block, c, &d, e, a, &b, 78);
r4(&block, b, &c, d, e, &a, 79);
(*state)[0] += a;
(*state)[1] += b;
(*state)[2] += c;
(*state)[3] += d;
(*state)[4] += e;
a = b = c = d = e = 0;
block = {};
}

Some files were not shown because too many files have changed in this diff Show More