mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 20:11:17 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bd66dcf18 | ||
|
|
3e31d20603 |
@@ -1,15 +0,0 @@
|
||||
IndentWidth: 4
|
||||
UseCRLF: false
|
||||
IndentCaseLabels: true
|
||||
UseTab: ForIndentation
|
||||
TabWidth: 4
|
||||
BreakBeforeBraces: Allman
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeParens: ControlStatementsExceptControlMacros
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
44
.clang-tidy
44
.clang-tidy
@@ -1,7 +1,21 @@
|
||||
---
|
||||
# Configure clang-tidy for this project.
|
||||
|
||||
|
||||
IndentWidth: 4
|
||||
UseCRLF: false
|
||||
IndentCaseLabels: true
|
||||
UseTab: UT_ForIndentation
|
||||
TabWidth: 4
|
||||
BreakBeforeBraces: Allman
|
||||
AllowShortBlocksOnASingleLine: SBS_Empty
|
||||
AllowShortIfStatementsOnASingleLine: SIS_WithoutElse
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeParens: SBPO_ControlStatementsExceptControlMacros
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
|
||||
# Disabled:
|
||||
# -google-readability-namespace-comments the *_CLIENT_NS is a macro, and
|
||||
@@ -26,19 +40,17 @@ Checks: >
|
||||
|
||||
# Turn all the warnings from the checks above into errors.
|
||||
WarningsAsErrors: "*"
|
||||
CheckOptions:
|
||||
- { key: readability-function-cognitive-complexity.Threshold, value: 100 }
|
||||
- { key: readability-identifier-naming.StructCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.FunctionCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.ConstexprVariablePrefix, value: k }
|
||||
- { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.GlobalConstantPrefix, value: k }
|
||||
- { key: readability-identifier-naming.StaticConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.StaticConstantPrefix, value: k }
|
||||
- { key: readability-identifier-naming.MinimumParameterNameLength, value: 0 }
|
||||
|
||||
MinimumParameterNameLength: 0
|
||||
CheckOptions:
|
||||
- { key: readability-function-cognitive-complexity.Threshold, value: 100 }
|
||||
- { key: readability-identifier-naming.StructCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.FunctionCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.ConstexprVariablePrefix, value: k }
|
||||
- { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.GlobalConstantPrefix, value: k }
|
||||
- { key: readability-identifier-naming.StaticConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.StaticConstantPrefix, value: k }
|
||||
@@ -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
2
.gitattributes
vendored
@@ -1,2 +1,4 @@
|
||||
$ cat .gitattributes
|
||||
* text=auto
|
||||
*.c3 linguist-language=C
|
||||
*.c3t linguist-language=C
|
||||
14
.github/FUNDING.yml
vendored
14
.github/FUNDING.yml
vendored
@@ -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']
|
||||
834
.github/workflows/main.yml
vendored
834
.github/workflows/main.yml
vendored
@@ -2,161 +2,229 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev, ci_testing, experiments ]
|
||||
branches: [ master, dev, ci_testing ]
|
||||
pull_request:
|
||||
branches: [ master, dev ]
|
||||
workflow_dispatch:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
LLVM_RELEASE_VERSION_WINDOWS: 21.1.8
|
||||
LLVM_RELEASE_VERSION_MAC: 21
|
||||
LLVM_RELEASE_VERSION_LINUX: 21
|
||||
LLVM_RELEASE_VERSION_LINUX_MUSL: 20
|
||||
LLVM_RELEASE_VERSION_OPENBSD: 20
|
||||
LLVM_RELEASE_VERSION_NETBSD: 19
|
||||
LLVM_DEV_VERSION: 22
|
||||
LLVM_RELEASE_VERSION: 16
|
||||
|
||||
jobs:
|
||||
|
||||
build-msvc:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
# Don't abort runners if a single one fails
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [ Release, Debug ]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: cmd
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: build/_deps
|
||||
key: ${{ runner.os }}-llvm-${{ env.LLVM_RELEASE_VERSION_WINDOWS }}-${{ matrix.build_type }}-${{ hashFiles('CMakeLists.txt', '.github/workflows/main.yml') }}
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# set up the environment for Ninja
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: x64
|
||||
|
||||
- name: CMake Build
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake --build build --config ${{ matrix.build_type }}
|
||||
|
||||
# We remove the GNU link tool so C3C picks up the MSVC link.exe
|
||||
- name: Remove GNU Link
|
||||
shell: bash
|
||||
run: rm -f /usr/bin/link
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
cd resources
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\hello_world_many.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\time.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 --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run msvc_stack.c3
|
||||
|
||||
- name: Run Unified Tests
|
||||
shell: bash
|
||||
run: ./scripts/tools/ci_tests.sh "./build/c3c.exe"
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log --emit-llvm run hello_world_win32
|
||||
dir build\llvm_ir
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe clean
|
||||
dir build\llvm_ir
|
||||
|
||||
|
||||
- name: Cache MSVC SDK
|
||||
id: cache-msvc-sdk
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: msvc_sdk
|
||||
key: msvc-sdk-${{ runner.os }}-${{ hashFiles('msvc_build_libraries.py') }}
|
||||
- name: Build testproject lib
|
||||
run: |
|
||||
cd resources/testproject
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log build hello_world_win32_lib
|
||||
|
||||
- if: steps.cache-msvc-sdk.outputs.cache-hit != 'true'
|
||||
run: py msvc_build_libraries.py --accept-license
|
||||
- name: Vendor-fetch
|
||||
run: |
|
||||
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib
|
||||
|
||||
- name: Bundle (Windows)
|
||||
shell: bash
|
||||
run: ./scripts/tools/package_build.sh "./build/c3c.exe" "c3-windows-${{ matrix.build_type }}" "zip"
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3.exe src/tester.py ..\build\${{ matrix.build_type }}\c3c.exe test_suite/
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
|
||||
|
||||
- name: upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: c3-windows-${{ matrix.build_type }}
|
||||
path: c3-windows-${{ matrix.build_type }}.zip
|
||||
path: build\${{ matrix.build_type }}\c3c.exe
|
||||
|
||||
build-msys2-mingw:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 30
|
||||
# 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@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
update: false
|
||||
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 mingw-w64-x86_64-llvm mingw-w64-x86_64-llvm-libs
|
||||
|
||||
- name: Install LLD
|
||||
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: |
|
||||
echo "Server = https://mirror.msys2.org/mingw/mingw64" > /etc/pacman.d/mirrorlist.mingw64
|
||||
pacman -Sy --noconfirm --needed \
|
||||
mingw-w64-x86_64-llvm \
|
||||
mingw-w64-x86_64-llvm-libs \
|
||||
mingw-w64-x86_64-lld \
|
||||
mingw-w64-x86_64-clang
|
||||
|
||||
- name: CMake Build
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-17.0.4-1-any.pkg.tar.zst
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-17.0.4-1-any.pkg.tar.zst
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_LINKER=lld -DC3_LINK_DYNAMIC=OFF -DLLVM_DIR=/mingw64/lib/cmake/llvm -DC3_LLD_DIR=/mingw64/lib/
|
||||
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake --build build
|
||||
- name: Run Unified Tests
|
||||
run: ./scripts/tools/ci_tests.sh "./build/c3c"
|
||||
|
||||
- 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/load_world.c3
|
||||
../build/c3c compile --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log
|
||||
|
||||
- name: Vendor-fetch
|
||||
run: |
|
||||
./build/c3c vendor-fetch raylib
|
||||
|
||||
- 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
|
||||
timeout-minutes: 30
|
||||
#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@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: CLANG64
|
||||
update: false
|
||||
install: git binutils mingw-w64-clang-x86_64-cmake mingw-w64-clang-x86_64-toolchain mingw-w64-clang-x86_64-python
|
||||
- name: CMake Build
|
||||
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake --build build
|
||||
- name: Run Unified Tests
|
||||
run: ./scripts/tools/ci_tests.sh "./build/c3c"
|
||||
|
||||
- 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/load_world.c3
|
||||
../build/c3c compile --test -g -O0 --threads 1 --target macos-x64 examples/constants.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-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, 21, 22]
|
||||
llvm_version: [15, 16, 17, 18]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: sudo apt-get update && sudo apt-get install -y zlib1g zlib1g-dev python3 ninja-build curl libcurl4-openssl-dev
|
||||
- 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 -
|
||||
REPO_URL="http://apt.llvm.org/focal/ llvm-toolchain-focal"
|
||||
if [[ "${{matrix.llvm_version}}" != "${{env.LLVM_DEV_VERSION}}" ]]; then REPO_URL="$REPO_URL-${{matrix.llvm_version}}"; fi
|
||||
sudo add-apt-repository "deb $REPO_URL main"
|
||||
sudo apt-get update
|
||||
PKGS="clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libpolly-${{matrix.llvm_version}}-dev"
|
||||
if [[ "${{matrix.llvm_version}}" < 18 ]]; then PKGS="$PKGS libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools"; fi
|
||||
sudo apt-get install -y $PKGS
|
||||
- name: CMake Build
|
||||
if [[ "${{matrix.llvm_version}}" < 16 ]]; then
|
||||
sudo apt remove libllvm15
|
||||
fi
|
||||
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
|
||||
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 libmlir-${{matrix.llvm_version}} \
|
||||
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
|
||||
fi
|
||||
- name: CMake
|
||||
run: |
|
||||
C3_LLVM_VER=${{matrix.llvm_version}}
|
||||
if [[ "${{matrix.llvm_version}}" -ge 18 && "${{matrix.llvm_version}}" != "${{env.LLVM_DEV_VERSION}}" ]]; then C3_LLVM_VER="${{matrix.llvm_version}}.1"; fi
|
||||
|
||||
cmake -B build -G Ninja \
|
||||
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}} \
|
||||
@@ -164,305 +232,389 @@ jobs:
|
||||
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
|
||||
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
|
||||
-DLLVM_ENABLE_LIBXML2=OFF \
|
||||
-DC3_LLVM_VERSION=$C3_LLVM_VER
|
||||
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
|
||||
cmake --build build
|
||||
- name: Run Unified Tests
|
||||
run: ./scripts/tools/ci_tests.sh "./build/c3c"
|
||||
|
||||
- name: Embedded/QEMU Tests
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
sudo apt-get install -y opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf
|
||||
cd resources/examples/embedded/riscv-qemu
|
||||
make C3C_PATH=../../../../build/ 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 examples/levenshtein.c3
|
||||
../build/c3c compile examples/load_world.c3
|
||||
../build/c3c compile 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 --system-linker=no linux_stack.c3
|
||||
../build/c3c compile-run linux_stack.c3
|
||||
|
||||
- name: Bundle & Upload (Linux)
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
bash ./scripts/tools/package_build.sh "./build/c3c" "c3-linux-${{matrix.build_type}}" "tar"
|
||||
- uses: actions/upload-artifact@v6
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
|
||||
cd test
|
||||
../build/c3c compile-test unit
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log
|
||||
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log --system-linker=no
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == 16
|
||||
run: |
|
||||
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 == 16
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: c3-linux-${{matrix.build_type}}
|
||||
path: c3-linux-${{matrix.build_type}}.tar.gz
|
||||
|
||||
build-linux-alpine:
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: alpine:3.22
|
||||
build-linux-ubuntu20:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
# Don't abort runners if a single one fails
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
llvm_version: [18, 19, 20]
|
||||
llvm_version: [16]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: apk update && apk add zlib-dev zlib-static python3 samurai cmake curl-dev curl-static openssl-dev bash
|
||||
- name: Install Clang
|
||||
run: apk add "llvm${{matrix.llvm_version}}-dev" "llvm${{matrix.llvm_version}}-static" "llvm${{matrix.llvm_version}}-gtest" "llvm${{matrix.llvm_version}}-linker-tools" "lld${{matrix.llvm_version}}-dev" "clang${{matrix.llvm_version}}-dev" "clang${{matrix.llvm_version}}-static"
|
||||
- name: CMake Build
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install common deps
|
||||
run: |
|
||||
C3_LLVM_VER=${{matrix.llvm_version}}
|
||||
if [[ "${{matrix.llvm_version}}" -ge 18 && "${{matrix.llvm_version}}" != "${{env.LLVM_DEV_VERSION}}" ]]; then C3_LLVM_VER="${{matrix.llvm_version}}.1"; fi
|
||||
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}} -DLLVM_ENABLE_LIBXML2=OFF -DC3_LINK_DYNAMIC=ON -DC3_LLVM_VERSION=$C3_LLVM_VER
|
||||
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}}" < 17 ]]; 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
|
||||
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: Run Unified Tests
|
||||
run: ./scripts/tools/ci_tests.sh "./build/c3c"
|
||||
|
||||
- name: Bundle & Upload (Alpine)
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX_MUSL
|
||||
run: bash ./scripts/tools/package_build.sh "./build/c3c" "c3-linux-musl-${{matrix.build_type}}" "tar"
|
||||
- uses: actions/upload-artifact@v6
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX_MUSL
|
||||
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
cd resources
|
||||
../build/c3c compile examples/gameoflife.c3
|
||||
../build/c3c compile examples/levenshtein.c3
|
||||
../build/c3c compile 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 --system-linker=no linux_stack.c3
|
||||
../build/c3c compile-run linux_stack.c3
|
||||
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log
|
||||
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log --system-linker=no
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == 16
|
||||
run: |
|
||||
mkdir linux
|
||||
cp -r lib linux
|
||||
cp msvc_build_libraries.py linux
|
||||
cp build/c3c linux
|
||||
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz linux
|
||||
|
||||
- name: upload artifacts
|
||||
if: matrix.llvm_version == 16
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: c3-linux-musl-${{matrix.build_type}}
|
||||
path: c3-linux-musl-${{matrix.build_type}}.tar.gz
|
||||
name: c3-ubuntu-20-${{matrix.build_type}}
|
||||
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
|
||||
|
||||
build-mac:
|
||||
runs-on: macos-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, 21]
|
||||
llvm_version: [15, 16]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download LLVM
|
||||
run: |
|
||||
brew_install() {
|
||||
for pkg in "$@"; do
|
||||
brew list "$pkg" &>/dev/null || brew install "$pkg"
|
||||
done
|
||||
}
|
||||
if [[ "${{ matrix.llvm_version }}" == "21" ]]; then
|
||||
brew_install llvm lld ninja curl
|
||||
echo "/opt/homebrew/opt/llvm/bin" >> $GITHUB_PATH
|
||||
echo "/opt/homebrew/opt/lld/bin" >> $GITHUB_PATH
|
||||
else
|
||||
brew_install llvm@${{ matrix.llvm_version }} ninja curl
|
||||
echo "/opt/homebrew/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
|
||||
if [[ "${{ matrix.llvm_version }}" -ge 19 ]]; then
|
||||
brew_install lld@${{ matrix.llvm_version }}
|
||||
echo "/opt/homebrew/opt/lld@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
|
||||
fi
|
||||
fi
|
||||
echo "CPATH=$(xcrun --show-sdk-path)/user/include" >> $GITHUB_ENV
|
||||
- name: CMake Build
|
||||
brew install llvm@${{ matrix.llvm_version }} ninja curl
|
||||
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
|
||||
run: |
|
||||
C3_LLVM_VER=${{matrix.llvm_version}}
|
||||
if [[ "${{matrix.llvm_version}}" -ge 18 ]]; then C3_LLVM_VER="${{matrix.llvm_version}}.1"; fi
|
||||
if [[ "${{ matrix.llvm_version }}" == "21" ]]; then
|
||||
C3_LLD_DIR="/opt/homebrew/opt/lld/lib"
|
||||
C3_LLD_INCLUDE_DIR="/opt/homebrew/opt/lld/include"
|
||||
elif [[ "${{ matrix.llvm_version }}" -ge 19 ]]; then
|
||||
C3_LLD_DIR="/opt/homebrew/opt/lld@${{ matrix.llvm_version }}/lib"
|
||||
C3_LLD_INCLUDE_DIR="/opt/homebrew/opt/lld@${{ matrix.llvm_version }}/include"
|
||||
else
|
||||
C3_LLD_DIR=""
|
||||
C3_LLD_INCLUDE_DIR=""
|
||||
fi
|
||||
cmake -B build -G Ninja -DC3_LLVM_VERSION=$C3_LLVM_VER -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DC3_LLD_DIR=$C3_LLD_DIR -DC3_LLD_INCLUDE_DIR=$C3_LLD_INCLUDE_DIR
|
||||
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake --build build
|
||||
- name: Run Unified Tests
|
||||
run: ./scripts/tools/ci_tests.sh "./build/c3c"
|
||||
|
||||
- name: Build Lib (Mac)
|
||||
|
||||
- name: Vendor-fetch
|
||||
run: |
|
||||
./build/c3c vendor-fetch raylib
|
||||
|
||||
- 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
|
||||
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c build hello_world_lib -vv --trust=full
|
||||
../../build/c3c run --debug-log
|
||||
|
||||
- name: Bundle & Upload (Mac)
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
|
||||
run: bash ./scripts/tools/package_build.sh "./build/c3c" "c3-macos-${{matrix.build_type}}" "zip"
|
||||
- uses: actions/upload-artifact@v6
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log --system-linker=no
|
||||
|
||||
- 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 test_suite/
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
|
||||
run: |
|
||||
mkdir macos
|
||||
cp -r lib macos
|
||||
cp msvc_build_libraries.py 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
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: c3-macos-${{matrix.build_type}}
|
||||
path: c3-macos-${{matrix.build_type}}.zip
|
||||
|
||||
build-openbsd:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
version: ['7.8']
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Build, Test and Package in OpenBSD
|
||||
uses: vmactions/openbsd-vm@v1
|
||||
with:
|
||||
sync: rsync
|
||||
usesh: true
|
||||
release: ${{ matrix.version }}
|
||||
prepare: pkg_add ninja cmake llvm%${{ env.LLVM_RELEASE_VERSION_OPENBSD }} bash
|
||||
# Combine all logic here so rsync copyback triggers at the end
|
||||
run: |
|
||||
export MALLOC_OPTIONS=j # Disable junk filling (it's faster)
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
|
||||
# Build
|
||||
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{matrix.build_type}}
|
||||
cmake --build build
|
||||
|
||||
# Run Unified Tests
|
||||
chmod +x scripts/tools/ci_tests.sh
|
||||
ulimit -d unlimited
|
||||
./scripts/tools/ci_tests.sh "./build/c3c"
|
||||
|
||||
# Package
|
||||
chmod +x scripts/tools/package_build.sh
|
||||
./scripts/tools/package_build.sh "./build/c3c" "c3-openbsd-${{matrix.build_type}}" "tar"
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: c3-openbsd-${{matrix.build_type}}
|
||||
path: c3-openbsd-${{matrix.build_type}}.tar.gz
|
||||
|
||||
build-netbsd:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
version: ['10.1']
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Build, Test and Package in NetBSD
|
||||
uses: vmactions/netbsd-vm@v1
|
||||
with:
|
||||
sync: rsync
|
||||
usesh: true
|
||||
release: ${{ matrix.version }}
|
||||
prepare: |
|
||||
export PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH"
|
||||
export PKG_PATH="https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/$(uname -m)/$(uname -r)/All/"
|
||||
/usr/sbin/pkg_add pkgin
|
||||
pkgin -y update
|
||||
pkgin -y install cmake gcc14 ninja-build bash \
|
||||
'llvm>=${{ env.LLVM_RELEASE_VERSION_NETBSD }}' \
|
||||
'clang>=${{ env.LLVM_RELEASE_VERSION_NETBSD }}' \
|
||||
'lld>=${{ env.LLVM_RELEASE_VERSION_NETBSD }}'
|
||||
run: |
|
||||
export PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH"
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
|
||||
# Build
|
||||
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{matrix.build_type}}
|
||||
cmake --build build
|
||||
|
||||
# Run Unified Tests
|
||||
chmod +x scripts/tools/ci_tests.sh
|
||||
./scripts/tools/ci_tests.sh "./build/c3c"
|
||||
|
||||
# Package
|
||||
chmod +x scripts/tools/package_build.sh
|
||||
./scripts/tools/package_build.sh "./build/c3c" "c3-netbsd-${{matrix.build_type}}" "tar"
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: c3-netbsd-${{matrix.build_type}}
|
||||
path: c3-netbsd-${{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@v6
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Build
|
||||
run: |
|
||||
chmod +x ./build-with-docker.sh
|
||||
LLVM_VERSION=${{ matrix.llvm_version }} UBUNTU_VERSION=${{ matrix.ubuntu_version }} CMAKE_BUILD_TYPE=${{ matrix.build_type }} ./build-with-docker.sh
|
||||
- name: Run Unified Tests in Docker
|
||||
run: |
|
||||
chmod +x ./scripts/tools/ci_tests.sh
|
||||
docker run --rm \
|
||||
-u 0 \
|
||||
-v "$(pwd):/home/c3c/source" \
|
||||
-w /home/c3c/source \
|
||||
c3c-builder \
|
||||
bash -c "./scripts/tools/ci_tests.sh ./bin/c3c"
|
||||
|
||||
build-nix:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [ Release, Debug ]
|
||||
nixpkgs: [ Lock, Latest ]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Update flake
|
||||
if: matrix.nixpkgs == 'Latest'
|
||||
run: nix flake update
|
||||
- name: CMake build and run Unified Tests
|
||||
run: |
|
||||
CHECK_NAME=".#c3c-checks"
|
||||
if [[ ${{ matrix.build_type }} = "Debug" ]]; then CHECK_NAME=".#c3c-debug-checks"; fi
|
||||
nix build -L "$CHECK_NAME"
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [build-msvc, build-linux, build-mac, build-linux-alpine, build-openbsd, build-netbsd]
|
||||
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@v6
|
||||
- uses: actions/download-artifact@v4
|
||||
|
||||
- name: Prepare Assets
|
||||
run: |
|
||||
# --- Release Assets ---
|
||||
mv c3-windows-Release/c3-windows-Release.zip c3-windows.zip
|
||||
mv c3-macos-Release/c3-macos-Release.zip c3-macos.zip
|
||||
|
||||
mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux.tar.gz
|
||||
mv c3-linux-musl-Release/c3-linux-musl-Release.tar.gz c3-linux-musl.tar.gz
|
||||
mv c3-openbsd-Release/c3-openbsd-Release.tar.gz c3-openbsd.tar.gz || true
|
||||
mv c3-netbsd-Release/c3-netbsd-Release.tar.gz c3-netbsd.tar.gz || true
|
||||
|
||||
# --- Debug Assets ---
|
||||
mv c3-windows-Debug/c3-windows-Debug.zip c3-windows-debug.zip
|
||||
mv c3-macos-Debug/c3-macos-Debug.zip c3-macos-debug.zip
|
||||
|
||||
mv c3-linux-Debug/c3-linux-Debug.tar.gz c3-linux-debug.tar.gz
|
||||
mv c3-linux-musl-Debug/c3-linux-musl-Debug.tar.gz c3-linux-musl-debug.tar.gz
|
||||
mv c3-openbsd-Debug/c3-openbsd-Debug.tar.gz c3-openbsd-debug.tar.gz || true
|
||||
mv c3-netbsd-Debug/c3-netbsd-Debug.tar.gz c3-netbsd-debug.tar.gz || true
|
||||
|
||||
- run: gh release delete latest-prerelease-tag --cleanup-tag -y || true
|
||||
- run: echo "RELEASE_NAME=latest-prerelease-$(date +'%Y%m%d-%H%M')" >> $GITHUB_ENV
|
||||
|
||||
- uses: softprops/action-gh-release@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: delete tag
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
tag_name: latest-prerelease-tag
|
||||
name: ${{ env.RELEASE_NAME }}
|
||||
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 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: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: latest
|
||||
release_name: latest
|
||||
draft: false
|
||||
prerelease: true
|
||||
files: |
|
||||
c3-windows.zip
|
||||
c3-macos.zip
|
||||
c3-linux.tar.gz
|
||||
c3-linux-musl.tar.gz
|
||||
c3-openbsd.tar.gz
|
||||
c3-netbsd.tar.gz
|
||||
# --- Debug Artifacts ---
|
||||
c3-windows-debug.zip
|
||||
c3-macos-debug.zip
|
||||
c3-linux-debug.tar.gz
|
||||
c3-linux-musl-debug.tar.gz
|
||||
c3-openbsd-debug.tar.gz
|
||||
c3-netbsd-debug.tar.gz
|
||||
|
||||
- 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 ubuntu 20
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
|
||||
asset_path: c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz
|
||||
asset_name: c3-ubuntu-20.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
|
||||
- name: upload ubuntu 20 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-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz
|
||||
asset_name: c3-ubuntu-20-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
|
||||
|
||||
34
.gitignore
vendored
34
.gitignore
vendored
@@ -1,7 +1,5 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
testrun
|
||||
benchmarkrun
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
@@ -9,8 +7,6 @@ benchmarkrun
|
||||
*.obj
|
||||
*.elf
|
||||
*.ll
|
||||
*.wasm
|
||||
*.s
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
@@ -23,7 +19,6 @@ benchmarkrun
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.tlb
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
@@ -72,32 +67,3 @@ out/
|
||||
|
||||
/cmake-build-debug/
|
||||
/cmake-build-release/
|
||||
|
||||
# etags(Emacs), ctags, gtags
|
||||
TAGS
|
||||
GPATH
|
||||
GRTAGS
|
||||
GTAGS
|
||||
tags
|
||||
|
||||
# Clangd LSP files
|
||||
/.cache/
|
||||
/compile_commands.json
|
||||
|
||||
# Nix
|
||||
result
|
||||
/.envrc
|
||||
/.direnv/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# tests
|
||||
/test/tmp/*
|
||||
/test/testrun
|
||||
/test/test_suite_runner
|
||||
|
||||
# patches, originals and rejects
|
||||
*.patch
|
||||
*.rej
|
||||
*.orig
|
||||
|
||||
682
CMakeLists.txt
682
CMakeLists.txt
@@ -1,12 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
set(C3_LLVM_MIN_VERSION 17)
|
||||
set(C3_LLVM_MAX_VERSION 22)
|
||||
set(C3_LLVM_DEFAULT_VERSION 21)
|
||||
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
|
||||
message(FATAL_ERROR "In-tree build detected, please build in a separate directory")
|
||||
endif()
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
# Grab the version
|
||||
file(READ "src/version.h" ver)
|
||||
@@ -15,28 +7,11 @@ if (NOT ${ver} MATCHES "COMPILER_VERSION \"([0-9]+.[0-9]+.[0-9]+)\"")
|
||||
endif()
|
||||
|
||||
# Set the project and version
|
||||
project(c3c VERSION ${CMAKE_MATCH_1} LANGUAGES C CXX)
|
||||
message("Configuring C3C ${CMAKE_PROJECT_VERSION} for ${CMAKE_SYSTEM_NAME}")
|
||||
|
||||
# Helper functions
|
||||
function(c3_print_variables)
|
||||
set(msg "")
|
||||
foreach(var ${ARGN})
|
||||
if(msg)
|
||||
string(APPEND msg " ; ")
|
||||
endif()
|
||||
string(APPEND msg "${c3_print_prefix}${var}=\"${${var}}\"")
|
||||
endforeach()
|
||||
message(STATUS "${msg}")
|
||||
endfunction()
|
||||
|
||||
# Avoid warning for FetchContent
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
||||
cmake_policy(SET CMP0135 NEW)
|
||||
endif()
|
||||
project(c3c VERSION ${CMAKE_MATCH_1})
|
||||
message("C3C version: ${CMAKE_PROJECT_VERSION}")
|
||||
|
||||
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
|
||||
if (WIN32)
|
||||
if (MSVC)
|
||||
set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib")
|
||||
set(CMAKE_INSTALL_BINDIR "c:\\c3c")
|
||||
else ()
|
||||
@@ -57,60 +32,31 @@ set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
if(MSVC)
|
||||
message(STATUS "MSVC version ${MSVC_VERSION}")
|
||||
add_compile_options(/utf-8)
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
FetchContent_GetProperties(LLVM_Windows)
|
||||
if(NOT LLVM_Windows_URL MATCHES "msvcrt")
|
||||
set(MSVC_CRT_SUFFIX "")
|
||||
message(STATUS "Detected STATIC LLVM (libcmt)")
|
||||
else()
|
||||
set(MSVC_CRT_SUFFIX "DLL")
|
||||
message(STATUS "Detected DYNAMIC LLVM (msvcrt)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Force the Runtime to Release (/MT or /MD) even in Debug
|
||||
# This is required to match our RelWithDebInfo LLVM builds
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
set_property(GLOBAL PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
|
||||
add_compile_definitions(_ITERATOR_DEBUG_LEVEL=0)
|
||||
|
||||
# Suppresses the LNK4098 mismatch warning in Debug builds
|
||||
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:libcmtd /NODEFAULTLIB:msvcrtd")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa")
|
||||
else()
|
||||
add_compile_options(-gdwarf-3 -fno-exceptions)
|
||||
|
||||
#add_compile_options(-fsanitize=address,undefined)
|
||||
#add_link_options(-fsanitize=address,undefined)
|
||||
if (true)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
|
||||
set(CMAKE_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()
|
||||
endif()
|
||||
|
||||
# Options
|
||||
set(C3_LINK_DYNAMIC OFF CACHE BOOL "Link dynamically with LLVM/LLD libs")
|
||||
set(C3_WITH_LLVM ON CACHE BOOL "Build with LLVM")
|
||||
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
|
||||
set(C3_USE_MIMALLOC OFF CACHE BOOL "Use built-in mimalloc")
|
||||
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
|
||||
set(C3_USE_TB OFF CACHE BOOL "Use TB")
|
||||
set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory")
|
||||
set(C3_LLD_INCLUDE_DIR "" CACHE STRING "Use custom LLD include directory")
|
||||
set(C3_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation")
|
||||
set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
|
||||
set(TCC_LIB_PATH "/usr/lib/tcc/libtcc1.a" CACHE STRING "Use custom libtcc1.a path")
|
||||
option(C3_LINK_DYNAMIC "link dynamically with LLVM/LLD libs")
|
||||
|
||||
set(C3_OPTIONS
|
||||
C3_LINK_DYNAMIC
|
||||
C3_WITH_LLVM
|
||||
C3_LLVM_VERSION
|
||||
C3_USE_MIMALLOC
|
||||
C3_MIMALLOC_TAG
|
||||
C3_USE_TB
|
||||
C3_LLD_DIR
|
||||
C3_ENABLE_CLANGD_LSP
|
||||
LLVM_CRT_LIBRARY_DIR
|
||||
)
|
||||
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")
|
||||
|
||||
set(C3_USE_MIMALLOC OFF)
|
||||
if(C3_USE_MIMALLOC)
|
||||
@@ -119,15 +65,20 @@ 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)
|
||||
endif()
|
||||
if (NOT C3_LLVM_VERSION STREQUAL "auto")
|
||||
if (${C3_LLVM_VERSION} VERSION_LESS 15 OR ${C3_LLVM_VERSION} VERSION_GREATER 18)
|
||||
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(Git QUIET)
|
||||
if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
@@ -144,219 +95,150 @@ if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Clangd LSP support
|
||||
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
|
||||
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
|
||||
if (C3_LLVM_VERSION STREQUAL "auto")
|
||||
set(C3_LLVM_VERSION "16")
|
||||
endif()
|
||||
FetchContent_Declare(
|
||||
LLVM_Windows
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_16_0_2/llvm-16.0.2-windows-amd64-msvc17-libcmt.7z
|
||||
)
|
||||
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 ${C3_LLVM_DEFAULT_VERSION})
|
||||
endif()
|
||||
FetchContent_Declare(
|
||||
LLVM_Windows
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_21_1_8/llvm-21.1.8-windows-amd64-msvc17-libcmt.7z
|
||||
)
|
||||
FetchContent_Declare(
|
||||
LLVM_Windows_debug
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_21_1_8/llvm-21.1.8-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()
|
||||
message("Loaded Windows LLVM libraries into ${llvm_dir}")
|
||||
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH})
|
||||
|
||||
FetchContent_Declare(
|
||||
LLVM_Windows_debug
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_16_0_2/llvm-16.0.2-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")
|
||||
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.
|
||||
c3_print_variables(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()
|
||||
|
||||
if (EXISTS /opt/homebrew/lib)
|
||||
list(APPEND LLVM_LIBRARY_DIRS /opt/homebrew/lib)
|
||||
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()
|
||||
|
||||
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
|
||||
|
||||
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
|
||||
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
|
||||
message(STATUS "LLVM libraries located in: ${LLVM_LIBRARY_DIRS}")
|
||||
|
||||
if (${LLVM_PACKAGE_VERSION} VERSION_LESS C3_LLVM_MIN_VERSION OR
|
||||
${LLVM_PACKAGE_VERSION} VERSION_GREATER C3_LLVM_MAX_VERSION)
|
||||
message(FATAL_ERROR "LLVM ${LLVM_PACKAGE_VERSION} is not supported! LLVM version between ${C3_LLVM_MIN_VERSION} and ${C3_LLVM_MAX_VERSION} is required.")
|
||||
endif()
|
||||
|
||||
if(LLVM_ENABLE_RTTI)
|
||||
message(STATUS "LLVM was built with RTTI")
|
||||
else()
|
||||
message(STATUS "LLVM was not built with RTTI")
|
||||
endif()
|
||||
|
||||
string(REPLACE "." ";" VERSION_LIST ${LLVM_PACKAGE_VERSION})
|
||||
list(GET VERSION_LIST 0 LLVM_MAJOR_VERSION)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
|
||||
|
||||
if(NOT ${C3_LLD_DIR} EQUAL "" AND EXISTS ${C3_LLD_DIR})
|
||||
list(APPEND LLVM_LIBRARY_DIRS ${C3_LLD_DIR})
|
||||
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
|
||||
endif()
|
||||
|
||||
message(STATUS "Looking for static lld libraries in ${LLVM_LIBRARY_DIRS}")
|
||||
|
||||
# These don't seem to be reliable on windows.
|
||||
find_library(LLD_COFF NAMES liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.dll.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.dll.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.dll.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
||||
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
else()
|
||||
set(LLD_MACHO "")
|
||||
endif()
|
||||
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.dll.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.dll.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
else()
|
||||
message(STATUS "Looking for shared lld libraries in ${LLVM_LIBRARY_DIRS}")
|
||||
|
||||
#find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
if(UNIX AND NOT WIN32)
|
||||
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} REQUIRED)
|
||||
else()
|
||||
find_library(LLVM NAMES libLLVM.a LLVM.lib PATHS ${LLVM_LIBRARY_DIRS} REQUIRED)
|
||||
endif()
|
||||
set(llvm_libs ${LLVM})
|
||||
|
||||
# These don't seem to be reliable on windows.
|
||||
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
||||
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
else()
|
||||
set(LLD_MACHO "")
|
||||
endif()
|
||||
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
endif()
|
||||
|
||||
# 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 ${llvm_libs}")
|
||||
message(STATUS "Linking to lld libs ${lld_libs}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
|
||||
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
|
||||
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
|
||||
|
||||
if(LLVM_ENABLE_RTTI)
|
||||
message(STATUS "LLVM was built with RTTI")
|
||||
else()
|
||||
message(STATUS "LLVM was not built with RTTI")
|
||||
endif()
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
|
||||
|
||||
# These don't seem to be reliable on windows.
|
||||
message(STATUS "using find_library")
|
||||
find_library(LLD_COFF NAMES lldCOFF.lib lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_COMMON NAMES lldCommon.lib lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_ELF NAMES lldELF.lib lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_MACHO NAMES lldMachO.lib lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_MINGW NAMES lldMinGW.lib lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_WASM NAMES lldWasm.lib lldWasm.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()
|
||||
|
||||
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
|
||||
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
|
||||
|
||||
if (${LLVM_PACKAGE_VERSION} VERSION_GREATER_EQUAL 16)
|
||||
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_COMMON}
|
||||
${LLD_WASM}
|
||||
${LLD_MINGW}
|
||||
${LLD_ELF}
|
||||
${LLD_MACHO}
|
||||
)
|
||||
else()
|
||||
set(lld_libs
|
||||
${LLD_COFF}
|
||||
${LLD_COMMON}
|
||||
${LLD_WASM}
|
||||
${LLD_MINGW}
|
||||
${LLD_ELF}
|
||||
${LLD_MACHO}
|
||||
)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
set(lld_libs ${lld_libs} xar)
|
||||
endif ()
|
||||
|
||||
message(STATUS "linking to llvm libs ${lld_libs}")
|
||||
message(STATUS "Found lld libs ${lld_libs}")
|
||||
|
||||
|
||||
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
|
||||
add_library(miniz STATIC dependencies/miniz/miniz.c)
|
||||
|
||||
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/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
|
||||
@@ -364,7 +246,9 @@ add_executable(c3c
|
||||
src/compiler/headers.c
|
||||
src/compiler/json_output.c
|
||||
src/compiler/lexer.c
|
||||
src/compiler/libraries.c
|
||||
src/compiler/linker.c
|
||||
src/compiler/llvm_codegen.c
|
||||
src/compiler/abi/c_abi_aarch64.c
|
||||
src/compiler/abi/c_abi.c
|
||||
src/compiler/abi/c_abi_riscv.c
|
||||
@@ -372,6 +256,14 @@ add_executable(c3c
|
||||
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_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
|
||||
@@ -413,75 +305,18 @@ add_executable(c3c
|
||||
src/utils/whereami.c
|
||||
src/utils/cpus.c
|
||||
src/utils/unzipper.c
|
||||
src/compiler/c_codegen.c
|
||||
src/compiler/decltable.c
|
||||
src/compiler/methodtable.c
|
||||
src/compiler/mac_support.c
|
||||
src/compiler/llvm_codegen_storeload.c
|
||||
src/compiler/windows_support.c
|
||||
src/compiler/codegen_asm.c
|
||||
src/compiler/asm_target.c
|
||||
src/compiler/llvm_codegen_builtins.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/sema_liveness.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)
|
||||
if (MSVC)
|
||||
# Use the same detected CRT for the wrapper
|
||||
set_target_properties(c3c_wrappers PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
set_target_properties(miniz PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
|
||||
target_compile_options(c3c PRIVATE
|
||||
"$<$<CONFIG:Debug>:/EHa>"
|
||||
"$<$<CONFIG:Release>:/EHsc>")
|
||||
endif()
|
||||
|
||||
if(C3_LLD_INCLUDE_DIR)
|
||||
target_include_directories(c3c_wrappers PRIVATE ${C3_LLD_INCLUDE_DIR})
|
||||
endif()
|
||||
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}")
|
||||
|
||||
target_include_directories(miniz PUBLIC
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/miniz/")
|
||||
|
||||
if (C3_USE_TB)
|
||||
file(GLOB tilde-sources
|
||||
@@ -495,7 +330,7 @@ if (C3_USE_TB)
|
||||
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
|
||||
@@ -515,29 +350,23 @@ if (C3_USE_TB)
|
||||
|
||||
target_include_directories(c3c PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/tilde-backend/include/")
|
||||
|
||||
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 PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/src/")
|
||||
|
||||
target_include_directories(c3c_wrappers PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/wrapper/include/")
|
||||
|
||||
target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
|
||||
target_include_directories(c3c_wrappers PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/wrapper/src/")
|
||||
|
||||
else()
|
||||
target_include_directories(miniz PUBLIC
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/miniz/")
|
||||
|
||||
target_link_libraries(c3c ${llvm_libs} miniz ${lld_libs})
|
||||
|
||||
endif()
|
||||
target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
|
||||
target_link_libraries(c3c ${llvm_libs} miniz c3c_wrappers ${lld_libs})
|
||||
|
||||
if(C3_USE_MIMALLOC)
|
||||
target_link_libraries(c3c mimalloc-static)
|
||||
@@ -547,149 +376,44 @@ 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_INCLUDE_DIRS})
|
||||
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)
|
||||
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-)
|
||||
message("Adding MSVC options")
|
||||
target_compile_options(c3c PRIVATE /wd4068 /wd4090 /WX /Wv:18)
|
||||
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)
|
||||
target_compile_options(c3c_wrappers PUBLIC /MTd)
|
||||
target_compile_options(miniz PUBLIC /MTd)
|
||||
if (C3_USE_TB)
|
||||
target_compile_options(tilde-backend PUBLIC /MTd)
|
||||
endif()
|
||||
target_link_options(c3c_wrappers PUBLIC /ignore:4099)
|
||||
endif()
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
# The the sanitizer libs are in the folder "lib/clang/21/lib/windows/" but use the find anyway
|
||||
file(GLOB_RECURSE FOUND_ASAN_LIB "${llvm_dir}/*clang_rt.asan_dynamic-x86_64.lib")
|
||||
if(FOUND_ASAN_LIB)
|
||||
list(GET FOUND_ASAN_LIB 0 _asan_path)
|
||||
get_filename_component(_asan_dir "${_asan_path}" DIRECTORY)
|
||||
set(sanitizer_runtime_libraries
|
||||
${_asan_dir}/clang_rt.asan_dynamic-x86_64.lib
|
||||
${_asan_dir}/clang_rt.asan_dynamic-x86_64.dll
|
||||
${_asan_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
|
||||
message(STATUS "Found Sanitizer binaries at: ${_asan_dir}")
|
||||
else()
|
||||
message(WARNING "Could not find sanitizer runtime libraries in ${llvm_dir}")
|
||||
else()
|
||||
target_compile_options(c3c PUBLIC /MT)
|
||||
target_compile_options(c3c_wrappers PUBLIC /MT)
|
||||
target_compile_options(miniz PUBLIC /MT)
|
||||
if (C3_USE_TB)
|
||||
target_compile_options(tilde-backend PUBLIC /MT)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
else()
|
||||
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
|
||||
-Wno-unused-function
|
||||
-Wno-unused-variable
|
||||
-Wno-unused-parameter
|
||||
-Wno-char-subscripts
|
||||
)
|
||||
message(STATUS "using gcc/clang warning switches")
|
||||
target_link_options(c3c PRIVATE -pthread)
|
||||
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
|
||||
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)
|
||||
endif()
|
||||
|
||||
if(CMAKE_C_COMPILER_ID STREQUAL "TinyCC")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-eh-frame-hdr -z noexecstack")
|
||||
|
||||
# Link the static tcc runtime archive if it exists
|
||||
if(EXISTS "${TCC_LIB_PATH}")
|
||||
target_link_libraries(c3c "${TCC_LIB_PATH}")
|
||||
else()
|
||||
message(FATAL_ERROR "TCC runtime not found at ${TCC_LIB_PATH}; Ensure the path is correct.")
|
||||
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()
|
||||
|
||||
# Copy stdlib
|
||||
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 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)
|
||||
|
||||
message(STATUS "Building ${CMAKE_PROJECT_NAME} with the following configuration:")
|
||||
|
||||
set(c3_print_prefix " ")
|
||||
|
||||
foreach(option IN LISTS C3_OPTIONS)
|
||||
if (DEFINED ${option})
|
||||
c3_print_variables(${option})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(flag_var
|
||||
CMAKE_BUILD_TYPE
|
||||
CMAKE_C_COMPILER
|
||||
CMAKE_CXX_COMPILER
|
||||
CMAKE_LINKER
|
||||
CMAKE_OBJCOPY
|
||||
CMAKE_STRIP
|
||||
CMAKE_DLLTOOL)
|
||||
c3_print_variables(${flag_var})
|
||||
endforeach()
|
||||
|
||||
message(STATUS "Build flags:")
|
||||
foreach(flag_var
|
||||
CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
|
||||
CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO
|
||||
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
|
||||
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
|
||||
c3_print_variables(${flag_var})
|
||||
endforeach()
|
||||
|
||||
message(STATUS "Output to: \"${CMAKE_BINARY_DIR}\"")
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "windows-base",
|
||||
"hidden": true,
|
||||
"architecture": {
|
||||
"value": "x64"
|
||||
},
|
||||
"toolset": {
|
||||
"value": "host=x64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-vs-2022-release",
|
||||
"generator": "Visual Studio 17 2022",
|
||||
"displayName": "Windows x64 Visual Studio 17 2022",
|
||||
"inherits": "windows-base",
|
||||
"binaryDir": "build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CONFIGURATION_TYPES": "Release;RelWithDebInfo",
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-vs-2022-debug",
|
||||
"generator": "Visual Studio 17 2022",
|
||||
"displayName": "Windows x64 Visual Studio 17 2022 (Debug)",
|
||||
"inherits": "windows-base",
|
||||
"binaryDir": "build-debug",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CONFIGURATION_TYPES": "Debug",
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "windows-vs-2022-debug",
|
||||
"displayName": "Debug",
|
||||
"configurePreset": "windows-vs-2022-debug",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "windows-vs-2022-release",
|
||||
"displayName": "Release",
|
||||
"configurePreset": "windows-vs-2022-release",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "windows-vs-2022-release-with-debug-info",
|
||||
"displayName": "RelWithDebInfo",
|
||||
"configurePreset": "windows-vs-2022-release",
|
||||
"configuration": "RelWithDebInfo"
|
||||
}
|
||||
]
|
||||
}
|
||||
114
CODESTYLE.md
114
CODESTYLE.md
@@ -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
|
||||
|
||||
@@ -147,114 +147,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 library please try your best to adhere to the
|
||||
following style requirements to ensure a consistent style in the stdlib and to
|
||||
facilitate 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.
|
||||
@@ -59,7 +59,7 @@ further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at info@c3-lang.org. All
|
||||
reported by contacting the project team at . All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
@@ -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 it’s 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.
|
||||
179
LICENSE
179
LICENSE
@@ -1,20 +1,165 @@
|
||||
Copyright (c) 2022-2025 Christoffer Lernö and contributors
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
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:
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
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.
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
|
||||
165
LICENSE_SRC
165
LICENSE_SRC
@@ -1,165 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
20
LICENSE_STDLIB
Normal file
20
LICENSE_STDLIB
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2022 Christoffer Lernö and contributors
|
||||
|
||||
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.
|
||||
340
README.md
340
README.md
@@ -8,19 +8,15 @@ 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-prerelease-tag/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
|
||||
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
|
||||
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
|
||||
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
|
||||
- OpenBSD x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz), [install instructions](#installing-on-openbsd-with-precompiled-binaries).
|
||||
- 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).
|
||||
- MacOS x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-mac-with-precompiled-binaries).
|
||||
|
||||
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
|
||||
|
||||

|
||||
|
||||
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.
|
||||
@@ -36,9 +32,9 @@ whole new language.
|
||||
|
||||
### Example code
|
||||
|
||||
The following code shows [generics](https://c3-lang.org/generic-programming/generics/) (more examples can be found at https://c3-lang.org/language-overview/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/).
|
||||
|
||||
```c3
|
||||
```c++
|
||||
module stack <Type>;
|
||||
// Above: the parameterized type is applied to the entire module.
|
||||
|
||||
@@ -58,7 +54,7 @@ fn void Stack.push(Stack* this, Type element)
|
||||
if (this.capacity == this.size)
|
||||
{
|
||||
this.capacity *= 2;
|
||||
if (this.capacity < 16) this.capacity = 16;
|
||||
if (this.capacity < 16) this.capacity = 16;
|
||||
this.elems = realloc(this.elems, Type.sizeof * this.capacity);
|
||||
}
|
||||
this.elems[this.size++] = element;
|
||||
@@ -78,18 +74,18 @@ fn bool Stack.empty(Stack* this)
|
||||
|
||||
Testing it out:
|
||||
|
||||
```c3
|
||||
```cpp
|
||||
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};
|
||||
def IntStack = Stack(<int>);
|
||||
// The second creates another copy with "Type" set to "double"
|
||||
alias DoubleStack = Stack {double};
|
||||
def 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
|
||||
@@ -126,8 +122,7 @@ fn void main()
|
||||
- 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
|
||||
@@ -142,16 +137,15 @@ fn void main()
|
||||
|
||||
### Current status
|
||||
|
||||
The current stable version of the compiler is **version 0.7.9**.
|
||||
The current stable version of the compiler is **version 0.5**.
|
||||
|
||||
The upcoming 0.7.x releases will focus on expanding the standard library,
|
||||
fixing bugs and improving compile time analysis.
|
||||
The upcoming 0.6 release will focus on expanding the standard library.
|
||||
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)
|
||||
or discuss C3 on its dedicated Discord: [https://discord.gg/qN76R87](https://discord.gg/qN76R87).
|
||||
|
||||
The compiler is currently verified to compile on Linux, OpenBSD, Windows and MacOS.
|
||||
The compiler is currently verified to compile on Linux, Windows and MacOS.
|
||||
|
||||
**Support matrix**
|
||||
|
||||
@@ -172,21 +166,17 @@ The compiler is currently verified to compile on Linux, OpenBSD, Windows and Mac
|
||||
| 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 |
|
||||
| ELF freestanding Xtensa* | 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 | Yes* | Yes | Yes* | 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*<br>
|
||||
*\* OpenBSD 7.7 is the only tested version*<br>
|
||||
*\* OpenBSD has limited stacktrace, needs to be tested further*<br>
|
||||
*\* Xtensa support is enabled by compiling with `-DXTENSA_ENABLE`. The [espressif llvm fork](https://github.com/espressif/llvm-project) is recommended for best compatibility*
|
||||
*\* Inline asm is still a work in progress*
|
||||
|
||||
More platforms will be supported in the future.
|
||||
|
||||
@@ -202,86 +192,30 @@ More platforms will be supported in the future.
|
||||
|
||||
### Installing
|
||||
|
||||
This installs the latest prerelease build, as opposed to the latest released version.
|
||||
|
||||
#### Installing on Windows with precompiled binaries
|
||||
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows-debug.zip))
|
||||
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)
|
||||
(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.
|
||||
4. Run `c3c.exe`.
|
||||
|
||||
#### Installing on Windows with the install script
|
||||
|
||||
Open a PowerShell terminal (you may need to run it as an administrator) and run the following command:
|
||||
```bash
|
||||
iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex
|
||||
```
|
||||
The script will inform you once the installation is successful and add the `~/.c3` directory to your PATH, which will allow you to run the c3c command from any location.
|
||||
|
||||
You can choose another version with option `C3_VERSION`.
|
||||
For example, you can force the installation of the 0.7.4 version:
|
||||
```bash
|
||||
$env:C3_VERSION='0.7.4'; powershell -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex"
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
||||
#### Installing on Debian with precompiled binaries
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux-debug.tar.gz))
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-linux-debug.tar.gz))
|
||||
2. Unpack executable and standard lib.
|
||||
3. Run `./c3c`.
|
||||
|
||||
#### Installing on Debian with the install script
|
||||
|
||||
Open a terminal and run the following command:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | bash
|
||||
```
|
||||
The C3 compiler will be installed, and the script will also update your ~/.bashrc to include `~/.c3` in your PATH, allowing you to invoke the c3c command from anywhere. You might need to restart your terminal or source your shell for the changes to take effect.
|
||||
|
||||
You can choose another version with option `C3_VERSION`.
|
||||
For example, you can force the installation of the 0.7.4 version:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | C3_VERSION=0.7.4 bash
|
||||
```
|
||||
|
||||
#### Installing on Ubuntu with precompiled binaries
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/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-prerelease-tag/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos.zip)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos-debug.zip))
|
||||
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 OpenBSD with precompiled binaries
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd-debug.tar.gz))
|
||||
2. Unpack executable and standard lib.
|
||||
3. Run `./c3c`.
|
||||
|
||||
(*Note that this is specifically for OpenBSD 7.7, running it on any other version is prone to ABI breaks)
|
||||
|
||||
#### Installing on Arch Linux
|
||||
Arch includes c3c in the official 'extra' repo. It can be easily installed the usual way:
|
||||
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
|
||||
|
||||
```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).
|
||||
Due to some issues with the LLVM packaged for Arch Linux, the AUR package will download and use LLVM 16 for Ubuntu-23.04 to compile the c3c compiler.
|
||||
|
||||
You can use your AUR package manager:
|
||||
```sh
|
||||
@@ -297,94 +231,38 @@ cd c3c-git
|
||||
makepkg -si
|
||||
```
|
||||
|
||||
#### Installing via Nix
|
||||
|
||||
You can access `c3c` via [flake.nix](./flake.nix), which will contain the latest commit of the compiler. To add `c3c` to your `flake.nix`, do the following:
|
||||
```nix
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
c3c.url = "github:c3lang/c3c";
|
||||
# Those are desired if you don't want to copy extra nixpkgs
|
||||
c3c.inputs = {
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import inputs.nixpkgs { inherit system; };
|
||||
c3c = inputs.c3c.packages.${system}.c3c;
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.c3c
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Installing on Gentoo
|
||||
|
||||
`c3c` is available in the [Gentoo GURU overlay](https://wiki.gentoo.org/wiki/Project:GURU).
|
||||
|
||||
Enable and sync the GURU repository (if not already done):
|
||||
|
||||
```sh
|
||||
sudo eselect repository enable guru
|
||||
sudo emaint sync -r guru
|
||||
```
|
||||
|
||||
Install `c3c` with:
|
||||
|
||||
```sh
|
||||
sudo emerge -av dev-lang/c3c
|
||||
```
|
||||
|
||||
* The compiler binary is installed to `/usr/bin/c3c`.
|
||||
* The standard library is installed to `/usr/lib/c3`.
|
||||
|
||||
For Gentoo-specific issues, please use the [Gentoo Bugzilla](https://bugs.gentoo.org/) (Product: *GURU*).
|
||||
|
||||
#### 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
|
||||
|
||||
1. Install [Homebrew](https://brew.sh/)
|
||||
2. Install LLVM 17+: `brew install llvm`
|
||||
3. Install lld: `brew install lld`
|
||||
4. Install CMake: `brew install cmake`
|
||||
5. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
6. Enter the C3C directory `cd c3c`.
|
||||
7. Set up CMake build for debug: `cmake -B build -S .`
|
||||
8. Build: `cmake --build build`
|
||||
9. Change directory to the build directory `cd build`
|
||||
|
||||
#### Installing on Windows using Scoop
|
||||
|
||||
c3c is included in 'Main' bucket.
|
||||
|
||||
```sh
|
||||
scoop install c3
|
||||
```
|
||||
2. Install CMake: `brew install cmake`
|
||||
3. Install LLVM 15: `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`
|
||||
7. Change directory to the build directory `cd build`
|
||||
8. Set up CMake build for debug: `cmake ..`
|
||||
9. Build: `cmake --build .`
|
||||
|
||||
#### Getting started with a "hello world"
|
||||
|
||||
Create a `main.c3` file with:
|
||||
```c3
|
||||
```c++
|
||||
module hello_world;
|
||||
import std::io;
|
||||
|
||||
@@ -409,121 +287,58 @@ 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`.
|
||||
5. Set up the CMake build: `cmake --preset windows-vs-2022-release`
|
||||
6. Build: `cmake --build --preset windows-vs-2022-release`
|
||||
4. Enter the C3C directory `cd c3c`.
|
||||
5. Set up the CMake build `cmake -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release`
|
||||
6. Build: `cmake --build build --config Release`
|
||||
7. You should now have the c3c.exe
|
||||
|
||||
You should now have a `c3c` executable in `build\Release`.
|
||||
You should now have a `c3c` executable.
|
||||
|
||||
You can try it out by running some sample code: `c3c.exe compile ../../resources/examples/hash.c3`
|
||||
|
||||
Building `c3c` using Visual Studio Code is also supported when using the `CMake Tools` extension. Simply select the `Windows x64 Visual Studio 17 2022` configure preset and build.
|
||||
You can try it out by running some sample code: `c3c.exe compile ../resources/examples/hash.c3`
|
||||
|
||||
*Note that if you run into linking issues when building, make sure that you are using the latest version of VS17.*
|
||||
|
||||
#### Compiling on Windows (Debug)
|
||||
|
||||
Debug build requires a different set of LLVM libraries to be loaded for which a separate CMake configuration is used to avoid conflicts.
|
||||
1. Configure: `cmake --preset windows-vs-2022-debug`
|
||||
2. Build: `cmake --build --preset windows-vs-2022-debug`
|
||||
|
||||
You should now have a `c3c` executable in `build-debug\Debug`.
|
||||
|
||||
#### 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`. If you're using Ubuntu 25.04, also install `libpolly-20-dev`.
|
||||
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
4. Enter the C3C directory `cd c3c`.
|
||||
5. Set up CMake build: `cmake -B build -S .`
|
||||
6. Build: `cmake --build build`
|
||||
2. Install CMake: `sudo apt install cmake`
|
||||
3. Install LLVM 15 (or greater: C3C supports LLVM 15-17): `sudo apt-get install clang-15 zlib1g zlib1g-dev libllvm15 llvm-15 llvm-15-dev llvm-15-runtime liblld-15-dev liblld-15`
|
||||
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 the CMake build cache: `cmake -B build -S .`
|
||||
5. Build: `cmake --build build`
|
||||
6. Enter the build directory: `cd build`
|
||||
|
||||
Your c3c executable should have compiled properly. You may want to test it: `./c3c compile ../resources/examples/hash.c3`
|
||||
For a system-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 the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake -B build -S . -DC3_LINK_DYNAMIC=1`
|
||||
6. Build the project: `cmake --build build`
|
||||
7. Enter the build directory: `cd 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 Arch Linux
|
||||
|
||||
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm libxml2`
|
||||
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: `git clone https://github.com/c3lang/c3c.git --depth=1`
|
||||
3. Enter the C3C directory: `cd c3c`
|
||||
4. Create the CMake build cache:
|
||||
```bash
|
||||
cmake -B build \
|
||||
-D C3_LINK_DYNAMIC=ON \
|
||||
-D CMAKE_BUILD_TYPE=Release
|
||||
```
|
||||
5. Build the project: `cmake --build build`.
|
||||
|
||||
After compilation, the `c3c` binary will be located in the `build` directory. You can test it by compiling an example: `./build/c3c compile resources/examples/ls.c3`.
|
||||
|
||||
6. To install the compiler globally: `sudo cmake --install build`
|
||||
|
||||
#### Compiling on NixOS
|
||||
|
||||
1. Enter nix shell, by typing `nix develop` in root directory
|
||||
2. Configure cmake via `cmake . -Bbuild $=C3_CMAKE_FLAGS`. Note: passing `C3_CMAKE_FLAGS` is needed in due to generate `compile_commands.json` and find missing libs.
|
||||
4. Build it `cmake --build build`
|
||||
5. Test it out: `./build/c3c -V`
|
||||
6. If you use `clangd` lsp server for your editor, it is recommended to make a symbolic link to `compile_command.json` in the root: `ln -s ./build/compile_commands.json compile_commands.json`
|
||||
|
||||
#### 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 15+ or higher)
|
||||
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
4. Enter the C3C directory `cd c3c`.
|
||||
5. Set up CMake build for debug: `cmake -B build -S .`. At this point you may need to manually
|
||||
provide the link path to the LLVM CMake directories, e.g. `cmake -B build -S . -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/`
|
||||
6. Build: `cmake --build build`
|
||||
7. Change directory to the build directory `cd build`
|
||||
5. Create a build directory `mkdir build`
|
||||
6. Change directory to the build directory `cd build`
|
||||
7. Set up CMake build for debug: `cmake ..`. At this point you may need to manually
|
||||
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 use the compiler without it.*
|
||||
this functionality is non-essential and it is perfectly fine to user the compiler without it.*
|
||||
|
||||
#### Licensing
|
||||
|
||||
Unless specified otherwise, the code in this repository is MIT licensed.
|
||||
The exception is the compiler source code (the source code under `src`),
|
||||
which is licensed under LGPL 3.0.
|
||||
|
||||
This means you are free to use all parts of standard library,
|
||||
tests, benchmarks, grammar, examples and so on under the MIT license, including
|
||||
using those libraries and tests if you build your own C3 compiler.
|
||||
The C3 compiler is licensed under LGPL 3.0, the standard library itself is
|
||||
MIT licensed.
|
||||
|
||||
#### Editor plugins
|
||||
|
||||
@@ -537,18 +352,3 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
|
||||
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 [Zack Puhl](https://github.com/NotsoanoNimus) and [konimarti](https://github.com/konimarti) for going the extra mile.
|
||||
|
||||
And honorable mention goes to past sponsors:
|
||||
[Ygor Pontelo](https://github.com/ygorpontelo), [Simone Raimondi](https://github.com/SRaimondi),
|
||||
[Jan Válek](https://github.com/jan-valek), [Pierre Curto](https://github.com/pierrec),
|
||||
[Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad)
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#c3lang/c3c&Date)
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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.
|
||||
//
|
||||
// Some benchmark test ideas are sourced from this article on C++ hashmap benchmarking:
|
||||
// https://martin.ankerl.com/2022/08/27/hashmap-bench-01/
|
||||
//
|
||||
module hashmap_benchmarks;
|
||||
|
||||
import std::collections::map;
|
||||
import std::math::random;
|
||||
|
||||
|
||||
const DEFAULT_ITERATIONS = 16384;
|
||||
|
||||
Lcg64Random rand;
|
||||
|
||||
HashMap { int, int } modifying_numbers_random;
|
||||
|
||||
fn void bench_setup() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(DEFAULT_ITERATIONS);
|
||||
|
||||
// TODO: Cannot take the address of a @benchmark function. If we could, we could pass &insert_erase as a fn ptr and use the $qnameof CT eval internally.
|
||||
set_benchmark_func_iterations($qnameof(insert_erase), 32);
|
||||
set_benchmark_func_iterations($qnameof(random_access), 1024);
|
||||
|
||||
random::seed(&rand, 0x4528_21e6_38d0_1377);
|
||||
|
||||
for (usz i = 0; i < 1_000; ++i) modifying_numbers_random.set(rand.next_int(), rand.next_int());
|
||||
}
|
||||
|
||||
|
||||
// ==============================================================================================
|
||||
module hashmap_benchmarks @benchmark;
|
||||
|
||||
import std::collections::map;
|
||||
|
||||
import std::math::random;
|
||||
import std::encoding::base64;
|
||||
|
||||
|
||||
fn void generic_hash_speeds()
|
||||
{
|
||||
(char){}.hash();
|
||||
(char[<100>]){}.hash();
|
||||
(char[100]){}.hash();
|
||||
(ichar){}.hash();
|
||||
(ichar[<100>]){}.hash();
|
||||
(ichar[100]){}.hash();
|
||||
(short){}.hash();
|
||||
(short[<100>]){}.hash();
|
||||
(short[100]){}.hash();
|
||||
(ushort){}.hash();
|
||||
(ushort[<100>]){}.hash();
|
||||
(ushort[100]){}.hash();
|
||||
(int){}.hash();
|
||||
(int[<100>]){}.hash();
|
||||
(int[100]){}.hash();
|
||||
(uint){}.hash();
|
||||
(uint[<100>]){}.hash();
|
||||
(uint[100]){}.hash();
|
||||
(long){}.hash();
|
||||
(long[<20>]){}.hash();
|
||||
(long[100]){}.hash();
|
||||
(ulong){}.hash();
|
||||
(ulong[<20>]){}.hash();
|
||||
(ulong[100]){}.hash();
|
||||
(int128){}.hash();
|
||||
(int128[<20>]){}.hash();
|
||||
(int128[100]){}.hash();
|
||||
(uint128){}.hash();
|
||||
(uint128[<20>]){}.hash();
|
||||
(uint128[100]){}.hash();
|
||||
(bool){}.hash();
|
||||
(bool[<100>]){}.hash();
|
||||
(bool[100]){}.hash();
|
||||
String x = "abc";
|
||||
char[] y = "abc";
|
||||
assert(x.hash() == y.hash());
|
||||
String z1 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
|
||||
char[] z2 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
|
||||
assert(z1.hash() == z2.hash());
|
||||
assert(int.typeid.hash());
|
||||
}
|
||||
|
||||
|
||||
fn void hash_speeds_of_many_random_values() => @pool()
|
||||
{
|
||||
var $arrsz = 10_000;
|
||||
uint fake_checksum;
|
||||
|
||||
char[] chars = allocator::new_array(tmem, char, $arrsz)[:$arrsz];
|
||||
foreach (&v : chars) *v = (char)random::next(&rand, uint.max);
|
||||
|
||||
ushort[] shorts = allocator::new_array(tmem, ushort, $arrsz)[:$arrsz];
|
||||
foreach (&v : shorts) *v = (ushort)random::next(&rand, uint.max);
|
||||
|
||||
uint[] ints = allocator::new_array(tmem, uint, $arrsz)[:$arrsz];
|
||||
foreach (&v : ints) *v = random::next(&rand, uint.max);
|
||||
|
||||
ulong[] longs = allocator::new_array(tmem, ulong, $arrsz)[:$arrsz];
|
||||
foreach (&v : longs) *v = (ulong)random::next(&rand, uint.max);
|
||||
|
||||
uint128[] vwideints = allocator::new_array(tmem, uint128, $arrsz)[:$arrsz];
|
||||
foreach (&v : vwideints) *v = (uint128)random::next(&rand, uint.max);
|
||||
|
||||
char[48][] zstrs = allocator::new_array(tmem, char[48], $arrsz)[:$arrsz];
|
||||
|
||||
String[] strs = mem::temp_array(String, $arrsz);
|
||||
foreach (x, &v : zstrs)
|
||||
{
|
||||
foreach (&c : (*v)[:random::next(&rand, 48)]) *c = (char)random::next(&rand, char.max);
|
||||
strs[x] = ((ZString)&v[0]).str_view();
|
||||
}
|
||||
|
||||
runtime::@start_benchmark();
|
||||
foreach (v : chars) fake_checksum += v.hash();
|
||||
foreach (v : shorts) fake_checksum += v.hash();
|
||||
foreach (v : ints) fake_checksum += v.hash();
|
||||
foreach (v : longs) fake_checksum += v.hash();
|
||||
foreach (v : vwideints) fake_checksum += v.hash();
|
||||
foreach (v : strs) fake_checksum += v.hash();
|
||||
runtime::@end_benchmark();
|
||||
}
|
||||
|
||||
|
||||
fn void modifying_numbers_init_from_map() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit_from_map(&modifying_numbers_random);
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void insert_erase() => @pool()
|
||||
{
|
||||
uint iters = 1_000_000;
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (int i = 0; i < iters; ++i) v[i] = i;
|
||||
for (int i = 0; i < iters; ++i) v.remove(i);
|
||||
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
uint bound = 10_000;
|
||||
usz pseudo_checksum = 0;
|
||||
|
||||
for (uint i = 0; i < bound; ++i) v[i] = i;
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (uint i = 0; i < 1_000_000; ++i) pseudo_checksum += (v[i.hash() % bound] ?? 0);
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access_erase() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
uint bound = 10_000;
|
||||
|
||||
for (uint i = 0; i < bound; ++i) v[i] = i;
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (uint i = 0; i < bound; ++i)
|
||||
{
|
||||
v[i.hash() % bound] = i; // supplant an entry
|
||||
|
||||
v.remove(random::next(&rand, bound)); // remove a random entry
|
||||
}
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access_string_keys() => @pool()
|
||||
{
|
||||
HashMap { String, ulong } v;
|
||||
v.tinit();
|
||||
|
||||
usz pseudo_checksum = 0;
|
||||
String[] saved = mem::temp_array(String, 5_000);
|
||||
|
||||
for (usz i = 0; i < saved.len; ++i)
|
||||
{
|
||||
ulong hash = i.hash();
|
||||
String b64key = base64::tencode(@as_char_view(hash));
|
||||
|
||||
v[b64key] = hash;
|
||||
|
||||
if (i < saved.len) saved[i] = b64key;
|
||||
}
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (usz i = 0; i < saved.len; ++i)
|
||||
{
|
||||
pseudo_checksum += v[ saved[random::next(&rand, saved.len)] ]!! % 512;
|
||||
}
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
module linkedlist_benchmarks;
|
||||
|
||||
import std::collections::linkedlist;
|
||||
|
||||
|
||||
LinkedList{int} long_list;
|
||||
const HAY = 2;
|
||||
const NEEDLE = 1000;
|
||||
|
||||
fn void bench_setup() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(4096);
|
||||
|
||||
int[*] haystack = { [0..999] = HAY };
|
||||
long_list = linkedlist::@new{int}(mem, haystack[..]);
|
||||
long_list.push(NEEDLE);
|
||||
long_list.push_all(haystack[..]);
|
||||
}
|
||||
|
||||
|
||||
// ==============================================================================================
|
||||
module linkedlist_benchmarks @benchmark;
|
||||
|
||||
String die_str = "Failed to find the value `1`. Is something broken?";
|
||||
|
||||
|
||||
fn void foreach_iterator()
|
||||
{
|
||||
foreach (v : long_list.array_view()) if (v == NEEDLE) return;
|
||||
runtime::@kill_benchmark(die_str);
|
||||
}
|
||||
|
||||
fn void foreach_r_iterator()
|
||||
{
|
||||
foreach_r (v : long_list.array_view()) if (v == NEEDLE) return;
|
||||
runtime::@kill_benchmark(die_str);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
module string_trim_wars;
|
||||
|
||||
const String WHITESPACE_TARGET = " \n\t\r\f\va \tbcde\v\f\r\t\n ";
|
||||
const String WHITESPACE_NUMERIC_TARGET = " 25290 0969 99a \tbcde12332 34 43 0000";
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(64);
|
||||
set_benchmark_max_iterations(1 << 24);
|
||||
}
|
||||
|
||||
macro void trim_bench($trim_str, String $target = WHITESPACE_TARGET) => @pool()
|
||||
{
|
||||
String s1;
|
||||
String s2 = $target.tcopy();
|
||||
|
||||
runtime::@start_benchmark();
|
||||
|
||||
$switch:
|
||||
$case $typeof($trim_str) == String:
|
||||
s1 = s2.trim($trim_str);
|
||||
$case $typeof($trim_str) == AsciiCharset:
|
||||
s1 = s2.trim_charset($trim_str);
|
||||
$default: $error "Unable to determine the right String `trim` operation to use.";
|
||||
$endswitch
|
||||
|
||||
@volatile_load(s1);
|
||||
|
||||
runtime::@end_benchmark();
|
||||
}
|
||||
|
||||
|
||||
module string_trim_wars @benchmark;
|
||||
|
||||
fn void trim_control() => trim_bench(" "); // only spaces
|
||||
|
||||
fn void trim_whitespace_default() => trim_bench("\t\n\r "); // default set
|
||||
fn void trim_whitespace_default_ordered() => trim_bench(" \n\t\r"); // default \w set, but ordered by expected freq
|
||||
|
||||
fn void trim_whitespace_bad() => trim_bench("\f\v\n\t\r "); // bad-perf ordering, all \w
|
||||
|
||||
fn void trim_whitespace_ordered_extended() => trim_bench(" \n\t\r\f\v"); // proposed ordering, all \w
|
||||
fn void trim_charset_whitespace() => trim_bench(ascii::WHITESPACE_SET); // use charset, all \w
|
||||
|
||||
fn void trim_many() => trim_bench(" \n\t\r\f\v0123456789", WHITESPACE_NUMERIC_TARGET); // ordered, all \w + num
|
||||
fn void trim_charset_many() => trim_bench(ascii::WHITESPACE_SET | ascii::NUMBER_SET, WHITESPACE_NUMERIC_TARGET); // set, all \w + num
|
||||
@@ -1,31 +0,0 @@
|
||||
module std::crypto::aes_bench;
|
||||
|
||||
import std::crypto::aes;
|
||||
|
||||
fn void init() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(5);
|
||||
set_benchmark_max_iterations(10_000);
|
||||
}
|
||||
|
||||
|
||||
AesType aes = AES256;
|
||||
char[] key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4";
|
||||
char[] text = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710";
|
||||
char[] cipher = x"601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6";
|
||||
char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
|
||||
|
||||
fn void bench_ctr_xcrypt() @benchmark
|
||||
{
|
||||
char[64] out;
|
||||
Aes ctx;
|
||||
|
||||
// encrypt
|
||||
ctx.init(aes, key, iv);
|
||||
ctx.encrypt_buffer(text, &out);
|
||||
|
||||
// decrypt
|
||||
ctx.init(aes, key, iv);
|
||||
ctx.decrypt_buffer(cipher, &out);
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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 chacha20_benchmarks;
|
||||
|
||||
import std::crypto::chacha20;
|
||||
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(1024);
|
||||
}
|
||||
|
||||
const char[] KEY = x'98bef1469be7269837a45bfbc92a5a6ac762507cf96443bf33b96b1bd4c6f8f6';
|
||||
const char[] NONCE = x'44e792d63335abb1582e9253';
|
||||
const uint COUNTER = 42;
|
||||
|
||||
char[] one_mb @align(ulong.sizeof) = { [0..1024*1024] = 0xA5 };
|
||||
|
||||
// This doesn't test both encryption + decryption, because it's a symmetric operation that shares
|
||||
// a single common data transformation. Testing one limb is enough.
|
||||
fn void gogo_chacha20() @benchmark
|
||||
{
|
||||
chacha20::encrypt_mut(one_mb[..], KEY, NONCE, COUNTER);
|
||||
}
|
||||
|
||||
// Check what the speed of an unligned buffer looks like.
|
||||
fn void gogo_chacha20_unaligned() @benchmark => @pool()
|
||||
{
|
||||
char[] copy = mem::talloc_array(char, one_mb.len + 3);
|
||||
char[] im_off_slightly = copy[3..];
|
||||
copy[3..] = one_mb[..];
|
||||
assert((usz)im_off_slightly.ptr % usz.sizeof > 0);
|
||||
|
||||
runtime::@start_benchmark();
|
||||
chacha20::encrypt_mut(im_off_slightly, KEY, NONCE, COUNTER);
|
||||
runtime::@end_benchmark();
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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 crypto_hash_benchmarks;
|
||||
|
||||
import std::collections::pair;
|
||||
|
||||
|
||||
const usz COMMON_ITERATIONS = 1 << 17;
|
||||
|
||||
char* common_1mib_ptr;
|
||||
char[] common_16;
|
||||
char[] common_256;
|
||||
char[] common_4kib;
|
||||
char[] common_1mib;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(COMMON_ITERATIONS + 3);
|
||||
|
||||
common_1mib_ptr = mem::alloc_array(char, 1024*1024);
|
||||
common_1mib = common_1mib_ptr[:1024*1024];
|
||||
common_1mib[..] = 0xA5;
|
||||
|
||||
common_16 = common_1mib[:16];
|
||||
common_256 = common_1mib[:256];
|
||||
common_4kib = common_1mib[:4096];
|
||||
|
||||
static String[] function_prefixes = {
|
||||
$qnameof(md5_16)[..^4],
|
||||
$qnameof(sha1_16)[..^4],
|
||||
$qnameof(sha2_256_16)[..^4],
|
||||
$qnameof(sha2_512_16)[..^4],
|
||||
$qnameof(blake2s_256_16)[..^4],
|
||||
$qnameof(blake2b_256_16)[..^4],
|
||||
$qnameof(blake3_16)[..^4],
|
||||
$qnameof(ripemd_160_16)[..^4],
|
||||
$qnameof(whirlpool_16)[..^4],
|
||||
$qnameof(streebog_256_16)[..^4],
|
||||
$qnameof(streebog_512_16)[..^4],
|
||||
};
|
||||
|
||||
static Pair{ String, uint }[] to_iters = {
|
||||
{ "_4kib", 1 << 15 },
|
||||
{ "_1mib", 1024 },
|
||||
};
|
||||
|
||||
foreach (p : to_iters)
|
||||
{
|
||||
foreach (name : function_prefixes) set_benchmark_func_iterations(name.tconcat(p.first), p.second);
|
||||
}
|
||||
}
|
||||
|
||||
fn void teardown_bench() @finalizer
|
||||
{
|
||||
mem::free(common_1mib_ptr);
|
||||
}
|
||||
|
||||
|
||||
// =======================================================================================
|
||||
module crypto_hash_benchmarks @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
|
||||
fn void md5_16() => md5::hash(common_16);
|
||||
fn void sha1_16() => sha1::hash(common_16);
|
||||
fn void sha2_256_16() => sha256::hash(common_16);
|
||||
fn void sha2_512_16() => sha512::hash(common_16);
|
||||
fn void blake2s_256_16() => blake2::s(256, common_16);
|
||||
fn void blake2b_256_16() => blake2::b(256, common_16);
|
||||
fn void blake3_16() => blake3::hash(common_16);
|
||||
fn void ripemd_160_16() => ripemd::hash{160}(common_16);
|
||||
fn void whirlpool_16() => whirlpool::hash(common_16);
|
||||
fn void streebog_256_16() => streebog::hash_256(common_16);
|
||||
fn void streebog_512_16() => streebog::hash_512(common_16);
|
||||
|
||||
fn void md5_256() => md5::hash(common_256);
|
||||
fn void sha1_256() => sha1::hash(common_256);
|
||||
fn void sha2_256_256() => sha256::hash(common_256);
|
||||
fn void sha2_512_256() => sha512::hash(common_256);
|
||||
fn void blake2s_256_256() => blake2::s(256, common_256);
|
||||
fn void blake2b_256_256() => blake2::b(256, common_256);
|
||||
fn void blake3_256() => blake3::hash(common_256);
|
||||
fn void ripemd_160_256() => ripemd::hash{160}(common_256);
|
||||
fn void whirlpool_256() => whirlpool::hash(common_256);
|
||||
fn void streebog_256_256() => streebog::hash_256(common_256);
|
||||
fn void streebog_512_256() => streebog::hash_512(common_256);
|
||||
|
||||
fn void md5_4kib() => md5::hash(common_4kib);
|
||||
fn void sha1_4kib() => sha1::hash(common_4kib);
|
||||
fn void sha2_256_4kib() => sha256::hash(common_4kib);
|
||||
fn void sha2_512_4kib() => sha512::hash(common_4kib);
|
||||
fn void blake2s_256_4kib() => blake2::s(256, common_4kib);
|
||||
fn void blake2b_256_4kib() => blake2::b(256, common_4kib);
|
||||
fn void blake3_4kib() => blake3::hash(common_4kib);
|
||||
fn void ripemd_160_4kib() => ripemd::hash{160}(common_4kib);
|
||||
fn void whirlpool_4kib() => whirlpool::hash(common_4kib);
|
||||
fn void streebog_256_4kib() => streebog::hash_256(common_4kib);
|
||||
fn void streebog_512_4kib() => streebog::hash_512(common_4kib);
|
||||
|
||||
fn void md5_1mib() => md5::hash(common_1mib);
|
||||
fn void sha1_1mib() => sha1::hash(common_1mib);
|
||||
fn void sha2_256_1mib() => sha256::hash(common_1mib);
|
||||
fn void sha2_512_1mib() => sha512::hash(common_1mib);
|
||||
fn void blake2s_256_1mib() => blake2::s(256, common_1mib);
|
||||
fn void blake2b_256_1mib() => blake2::b(256, common_1mib);
|
||||
fn void blake3_1mib() => blake3::hash(common_1mib);
|
||||
fn void ripemd_160_1mib() => ripemd::hash{160}(common_1mib);
|
||||
fn void whirlpool_1mib() => whirlpool::hash(common_1mib);
|
||||
fn void streebog_256_1mib() => streebog::hash_256(common_1mib);
|
||||
fn void streebog_512_1mib() => streebog::hash_512(common_1mib);
|
||||
@@ -1,94 +0,0 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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 non_crypto_benchmarks;
|
||||
|
||||
|
||||
const usz COMMON_ITERATIONS = 1 << 18;
|
||||
|
||||
const char[] COMMON_1 = { 0xA5 };
|
||||
const char[] COMMON_4 = { 0xA5, 0xA5, 0xA5, 0xA5, };
|
||||
const char[] COMMON_8 = { [0..7] = 0xA5 };
|
||||
const char[] COMMON_16 = { [0..15] = 0xA5 };
|
||||
const char[] COMMON_32 = { [0..31] = 0xA5 };
|
||||
const char[] COMMON_64 = { [0..63] = 0xA5 };
|
||||
const char[] COMMON_128 = { [0..127] = 0xA5 };
|
||||
const char[] COMMON_1024 = { [0..1023] = 0xA5 };
|
||||
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(COMMON_ITERATIONS + 3);
|
||||
}
|
||||
|
||||
|
||||
// =======================================================================================
|
||||
module non_crypto_benchmarks @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
|
||||
fn void fnv64a_1() => fnv64a::hash(COMMON_1);
|
||||
fn void fnv32a_1() => fnv32a::hash(COMMON_1);
|
||||
fn void wyhash2_1() => wyhash2::hash(COMMON_1);
|
||||
fn void metro64_1() => metro64::hash(COMMON_1);
|
||||
fn void metro128_1() => metro128::hash(COMMON_1);
|
||||
fn void a5hash_1() => a5hash::hash(COMMON_1);
|
||||
fn void komi_1() => komi::hash(COMMON_1);
|
||||
|
||||
fn void fnv64a_4() => fnv64a::hash(COMMON_4);
|
||||
fn void fnv32a_4() => fnv32a::hash(COMMON_4);
|
||||
fn void wyhash2_4() => wyhash2::hash(COMMON_4);
|
||||
fn void metro64_4() => metro64::hash(COMMON_4);
|
||||
fn void metro128_4() => metro128::hash(COMMON_4);
|
||||
fn void a5hash_4() => a5hash::hash(COMMON_4);
|
||||
fn void komi_4() => komi::hash(COMMON_4);
|
||||
|
||||
fn void fnv64a_8() => fnv64a::hash(COMMON_8);
|
||||
fn void fnv32a_8() => fnv32a::hash(COMMON_8);
|
||||
fn void wyhash2_8() => wyhash2::hash(COMMON_8);
|
||||
fn void metro64_8() => metro64::hash(COMMON_8);
|
||||
fn void metro128_8() => metro128::hash(COMMON_8);
|
||||
fn void a5hash_8() => a5hash::hash(COMMON_8);
|
||||
fn void komi_8() => komi::hash(COMMON_8);
|
||||
|
||||
fn void fnv64a_16() => fnv64a::hash(COMMON_16);
|
||||
fn void fnv32a_16() => fnv32a::hash(COMMON_16);
|
||||
fn void wyhash2_16() => wyhash2::hash(COMMON_16);
|
||||
fn void metro64_16() => metro64::hash(COMMON_16);
|
||||
fn void metro128_16() => metro128::hash(COMMON_16);
|
||||
fn void a5hash_16() => a5hash::hash(COMMON_16);
|
||||
fn void komi_16() => komi::hash(COMMON_16);
|
||||
|
||||
fn void fnv64a_32() => fnv64a::hash(COMMON_32);
|
||||
fn void fnv32a_32() => fnv32a::hash(COMMON_32);
|
||||
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
|
||||
fn void metro64_32() => metro64::hash(COMMON_32);
|
||||
fn void metro128_32() => metro128::hash(COMMON_32);
|
||||
fn void a5hash_32() => a5hash::hash(COMMON_32);
|
||||
fn void komi_32() => komi::hash(COMMON_32);
|
||||
|
||||
fn void fnv64a_64() => fnv64a::hash(COMMON_64);
|
||||
fn void fnv32a_64() => fnv32a::hash(COMMON_64);
|
||||
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
|
||||
fn void metro64_64() => metro64::hash(COMMON_64);
|
||||
fn void metro128_64() => metro128::hash(COMMON_64);
|
||||
fn void a5hash_64() => a5hash::hash(COMMON_64);
|
||||
fn void komi_64() => komi::hash(COMMON_64);
|
||||
|
||||
fn void fnv64a_128() => fnv64a::hash(COMMON_128);
|
||||
fn void fnv32a_128() => fnv32a::hash(COMMON_128);
|
||||
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
|
||||
fn void metro64_128() => metro64::hash(COMMON_128);
|
||||
fn void metro128_128() => metro128::hash(COMMON_128);
|
||||
fn void a5hash_128() => a5hash::hash(COMMON_128);
|
||||
fn void komi_128() => komi::hash(COMMON_128);
|
||||
|
||||
fn void fnv64a_1024() => fnv64a::hash(COMMON_1024);
|
||||
fn void fnv32a_1024() => fnv32a::hash(COMMON_1024);
|
||||
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
|
||||
fn void metro64_1024() => metro64::hash(COMMON_1024);
|
||||
fn void metro128_1024() => metro128::hash(COMMON_1024);
|
||||
fn void a5hash_1024() => a5hash::hash(COMMON_1024);
|
||||
fn void komi_1024() => komi::hash(COMMON_1024);
|
||||
@@ -1,76 +0,0 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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 ripemd_bench;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(256);
|
||||
|
||||
input = mem::alloc_array(char, BUFSZ);
|
||||
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
|
||||
input_slice = input[:BUFSZ];
|
||||
}
|
||||
|
||||
fn void teardown_bench() @finalizer
|
||||
{
|
||||
mem::free(input);
|
||||
input = null;
|
||||
}
|
||||
|
||||
char* input;
|
||||
char[] input_slice;
|
||||
const usz BUFSZ = 1024 * 1024;
|
||||
|
||||
module ripemd_bench @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
fn void ripemd_128()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{128}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void ripemd_160()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{160}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void ripemd_256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{256}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void ripemd_320()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{320}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_sha256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = sha256::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_whirlpool()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = whirlpool::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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 streebog_bench;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(256);
|
||||
}
|
||||
|
||||
const char[] INPUT = { [0..1024*1024] = 0xA5 };
|
||||
const char[*] EXPECTED_256 = x'694676905b7cf099755db1cc186f741f0fd1877aaaa4badcbfb305537f986971';
|
||||
const char[*] EXPECTED_512 = x'fe21d08857ea97e79035d1e5c9ba5130786e8d1875bc74d628349560d94d6bdff0b0dcd2f6347eb8b3f0239b6cca76b5028c0ff45f631fcdf77b1d551dd079f3';
|
||||
|
||||
|
||||
module streebog_bench @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
fn void get_in_the_bog_256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = streebog::hash_256(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void get_in_the_bog_512()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = streebog::hash_512(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_sha256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = sha256::hash(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_whirlpool()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = whirlpool::hash(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
## build-with-docker.sh
|
||||
## @author gdm85
|
||||
## @modified by Kenta
|
||||
##
|
||||
## Script to build c3c for Ubuntu 22
|
||||
##
|
||||
#
|
||||
|
||||
: ${DOCKER:=docker}
|
||||
: ${IMAGE:="c3c-builder"}
|
||||
: ${CMAKE_BUILD_TYPE:=Release}
|
||||
: ${LLVM_VERSION:=18}
|
||||
: ${UBUNTU_VERSION:="22.04"}
|
||||
: ${CMAKE_VERSION:="3.20.0"}
|
||||
read -p "Select Build Type: Debug/Release: " config
|
||||
|
||||
cd docker || exit 1 # Exit if the 'docker' directory doesn't exist
|
||||
set -e
|
||||
|
||||
$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
|
||||
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 [ $config == "Debug" ]; then
|
||||
CMAKE_BUILD_TYPE=Debug
|
||||
else
|
||||
CMAKE_BUILD_TYPE="$config"
|
||||
fi
|
||||
|
||||
UBUNTU_VERSION="22.10"
|
||||
LLVM_VERSION="15"
|
||||
|
||||
IMAGE="$IMAGE:22"
|
||||
|
||||
cd docker && $DOCKER build -t $IMAGE\
|
||||
--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
552
c3c.1
@@ -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
|
||||
@@ -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++ libcurl4-openssl-dev && \
|
||||
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 -o --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
61
flake.lock
generated
@@ -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
|
||||
}
|
||||
42
flake.nix
42
flake.nix
@@ -1,42 +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; };
|
||||
c3cBuild = set: pkgs.callPackage ./nix/default.nix (set // {
|
||||
rev = self.rev or "unknown";
|
||||
});
|
||||
in {
|
||||
packages = {
|
||||
default = self.packages.${system}.c3c;
|
||||
|
||||
c3c = c3cBuild {};
|
||||
|
||||
c3c-checks = c3cBuild {
|
||||
checks = true;
|
||||
};
|
||||
|
||||
c3c-debug = c3cBuild {
|
||||
debug = true;
|
||||
};
|
||||
|
||||
c3c-debug-checks = c3cBuild {
|
||||
debug = true;
|
||||
checks = true;
|
||||
};
|
||||
};
|
||||
|
||||
devShells = {
|
||||
default = pkgs.callPackage ./nix/shell.nix {
|
||||
c3c = self.packages.${system}.c3c-debug;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -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")
|
||||
@@ -1,189 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
C3 install script.
|
||||
.DESCRIPTION
|
||||
This script installs C3 on Windows from the command line.
|
||||
.PARAMETER C3Version
|
||||
Specifies the version of C3 to install.
|
||||
Default is 'latest'. Can also be set via environment variable 'C3_VERSION'.
|
||||
.PARAMETER C3Home
|
||||
Specifies C3's installation directory.
|
||||
Default is '$Env:USERPROFILE\.c3'. Can also be set via environment variable 'C3_HOME'.
|
||||
.PARAMETER NoPathUpdate
|
||||
If specified, the script will not modify the PATH environment variable.
|
||||
.PARAMETER C3Repourl
|
||||
Specifies the repository URL of C3.
|
||||
Default is 'https://github.com/c3lang/c3c'. Can also be set via environment variable 'C3_REPOURL'.
|
||||
.LINK
|
||||
https://c3-lang.org/
|
||||
.LINK
|
||||
https://github.com/c3lang/c3c
|
||||
#>
|
||||
|
||||
# Script parameters with defaults
|
||||
param (
|
||||
[string] $C3Version = 'latest',
|
||||
[string] $C3Home = "$Env:USERPROFILE\.c3",
|
||||
[switch] $NoPathUpdate,
|
||||
[string] $C3Repourl = 'https://github.com/c3lang/c3c'
|
||||
)
|
||||
|
||||
# Enable strict mode for better error handling
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
# Function to broadcast environment variable changes to Windows system
|
||||
function Publish-Env {
|
||||
# Add P/Invoke type if it does not exist
|
||||
if (-not ("Win32.NativeMethods" -as [Type])) {
|
||||
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr SendMessageTimeout(
|
||||
IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
|
||||
uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
|
||||
"@
|
||||
}
|
||||
|
||||
# Constants for broadcasting environment changes
|
||||
$HWND_BROADCAST = [IntPtr] 0xffff
|
||||
$WM_SETTINGCHANGE = 0x1a
|
||||
$result = [UIntPtr]::Zero
|
||||
|
||||
# Broadcast the message to all windows
|
||||
[Win32.Nativemethods]::SendMessageTimeout($HWND_BROADCAST,
|
||||
$WM_SETTINGCHANGE,
|
||||
[UIntPtr]::Zero,
|
||||
"Environment",
|
||||
2,
|
||||
5000,
|
||||
[ref] $result
|
||||
) | Out-Null
|
||||
}
|
||||
|
||||
# Function to write or update an environment variable in the registry
|
||||
function Write-Env {
|
||||
param(
|
||||
[String] $name,
|
||||
[String] $val,
|
||||
[Switch] $global
|
||||
)
|
||||
|
||||
# Determine the registry key based on scope (user or system)
|
||||
$RegisterKey = if ($global) {
|
||||
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
|
||||
} else {
|
||||
Get-Item -Path 'HKCU:'
|
||||
}
|
||||
|
||||
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment', $true)
|
||||
|
||||
# If value is null, delete the variable
|
||||
if ($null -eq $val) {
|
||||
$EnvRegisterKey.DeleteValue($name)
|
||||
} else {
|
||||
# Determine the correct registry value type
|
||||
$RegistryValueKind = if ($val.Contains('%')) {
|
||||
[Microsoft.Win32.RegistryValueKind]::ExpandString
|
||||
} elseif ($EnvRegisterKey.GetValue($name)) {
|
||||
$EnvRegisterKey.GetValueKind($name)
|
||||
} else {
|
||||
[Microsoft.Win32.RegistryValueKind]::String
|
||||
}
|
||||
$EnvRegisterKey.SetValue($name, $val, $RegistryValueKind)
|
||||
}
|
||||
|
||||
# Broadcast the change to the system
|
||||
Publish-Env
|
||||
}
|
||||
|
||||
# Function to get an environment variable from the registry
|
||||
function Get-Env {
|
||||
param(
|
||||
[String] $name,
|
||||
[Switch] $global
|
||||
)
|
||||
|
||||
# Determine registry key based on scope
|
||||
$RegisterKey = if ($global) {
|
||||
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
|
||||
} else {
|
||||
Get-Item -Path 'HKCU:'
|
||||
}
|
||||
|
||||
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment')
|
||||
$RegistryValueOption = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
|
||||
|
||||
# Retrieve the value without expanding environment variables
|
||||
$EnvRegisterKey.GetValue($name, $null, $RegistryValueOption)
|
||||
}
|
||||
|
||||
# Override defaults if environment variables exist
|
||||
if ($Env:C3_VERSION) { $C3Version = $Env:C3_VERSION }
|
||||
if ($Env:C3_HOME) { $C3Home = $Env:C3_HOME }
|
||||
if ($Env:C3_NO_PATH_UPDATE) { $NoPathUpdate = $true }
|
||||
if ($Env:C3_REPOURL) { $C3Repourl = $Env:C3_REPOURL -replace '/$', '' }
|
||||
|
||||
# Set binary name
|
||||
$BINARY = "c3-windows"
|
||||
|
||||
# Determine the download URL based on version
|
||||
if ($C3Version -eq 'latest') {
|
||||
$DOWNLOAD_URL = "$C3Repourl/releases/latest/download/$BINARY.zip"
|
||||
} else {
|
||||
# Ensure version starts with 'v'
|
||||
$C3Version = "v" + ($C3Version -replace '^v', '')
|
||||
$DOWNLOAD_URL = "$C3Repourl/releases/download/$C3Version/$BINARY.zip"
|
||||
}
|
||||
|
||||
$BinDir = $C3Home
|
||||
|
||||
Write-Host "This script will automatically download and install C3 ($C3Version) for you."
|
||||
Write-Host "Getting it from this url: $DOWNLOAD_URL"
|
||||
Write-Host "The binary will be installed into '$BinDir'"
|
||||
|
||||
# Create temporary file for download
|
||||
$TEMP_FILE = [System.IO.Path]::GetTempFileName()
|
||||
|
||||
try {
|
||||
# Download the binary
|
||||
Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $TEMP_FILE
|
||||
|
||||
# Remove previous installation if it exists
|
||||
if (Test-Path -Path $BinDir) {
|
||||
Remove-Item -Path $BinDir -Recurse -Force | Out-Null
|
||||
}
|
||||
|
||||
# Rename temp file to .zip
|
||||
$ZIP_FILE = $TEMP_FILE + ".zip"
|
||||
Rename-Item -Path $TEMP_FILE -NewName $ZIP_FILE
|
||||
|
||||
# Extract downloaded zip
|
||||
Expand-Archive -Path $ZIP_FILE -DestinationPath $Env:USERPROFILE -Force
|
||||
|
||||
# Rename extracted folder to target installation directory
|
||||
Rename-Item -Path "$Env:USERPROFILE/c3-windows-Release" -NewName $BinDir
|
||||
} catch {
|
||||
Write-Host "Error: '$DOWNLOAD_URL' is not available or failed to download"
|
||||
exit 1
|
||||
} finally {
|
||||
# Cleanup temporary zip file
|
||||
Remove-Item -Path $ZIP_FILE
|
||||
}
|
||||
|
||||
# Update PATH environment variable if requested
|
||||
if (!$NoPathUpdate) {
|
||||
$PATH = Get-Env 'PATH'
|
||||
if ($PATH -notlike "*$BinDir*") {
|
||||
Write-Output "Adding $BinDir to PATH"
|
||||
|
||||
# Persist PATH for future sessions
|
||||
Write-Env -name 'PATH' -val "$BinDir;$PATH"
|
||||
|
||||
# Update PATH for current session
|
||||
$Env:PATH = "$BinDir;$PATH"
|
||||
Write-Output "You may need to restart your shell"
|
||||
} else {
|
||||
Write-Output "$BinDir is already in PATH"
|
||||
}
|
||||
} else {
|
||||
Write-Output "You may need to update your PATH manually to use c3"
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail # Exit on error, unset variables, and fail pipelines on any error
|
||||
|
||||
__wrap__() {
|
||||
# Version of C3 to install (default: latest)
|
||||
VERSION="${C3_VERSION:-latest}"
|
||||
# Installation directory (default: ~/.c3)
|
||||
C3_HOME="${C3_HOME:-$HOME/.c3}"
|
||||
# Expand '~' if present
|
||||
C3_HOME="${C3_HOME/#\~/$HOME}"
|
||||
BIN_DIR="$C3_HOME"
|
||||
# C3 compiler repository URL
|
||||
REPO="c3lang/c3c"
|
||||
REPOURL="${C3_REPOURL:-https://github.com/$REPO}"
|
||||
|
||||
detect_platform() {
|
||||
# Detects the operating system
|
||||
local os_type
|
||||
os_type="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
case "$os_type" in
|
||||
darwin) # macOS
|
||||
echo "macos"
|
||||
;;
|
||||
msys*|mingw*|cygwin*) # Windows (Git Bash / MSYS / Cygwin)
|
||||
IS_MSYS=true
|
||||
echo "windows"
|
||||
;;
|
||||
*)
|
||||
echo $os_type
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Determine platform string
|
||||
PLATFORM="$(detect_platform)"
|
||||
|
||||
# File extension for the archive (ZIP for Windows, TAR.GZ for others)
|
||||
EXT=".tar.gz"
|
||||
BINARY="c3-${PLATFORM}"
|
||||
if [[ "${IS_MSYS:-false}" == true ]]; then
|
||||
EXT=".zip"
|
||||
fi
|
||||
|
||||
# Determine the download URL (latest release or specific version)
|
||||
if [[ "$VERSION" == "latest" ]]; then
|
||||
URL="${REPOURL%/}/releases/latest/download/${BINARY}${EXT}"
|
||||
else
|
||||
URL="${REPOURL%/}/releases/download/v${VERSION#v}/${BINARY}${EXT}"
|
||||
fi
|
||||
|
||||
# Temporary file for the downloaded archive
|
||||
TEMP_FILE="$(mktemp "${TMPDIR:-/tmp}/.C3_install.XXXXXXXX")"
|
||||
trap 'rm -f "$TEMP_FILE"' EXIT # Ensure temp file is deleted on exit
|
||||
|
||||
download_file() {
|
||||
# Download the archive using curl or wget
|
||||
# Check that the curl version is not 8.8.0, which is broken for --write-out
|
||||
# https://github.com/curl/curl/issues/13845
|
||||
if command -v curl >/dev/null && [[ "$(curl --version | awk 'NR==1{print $2}')" != "8.8.0" ]]; then
|
||||
curl -SL "$URL" -o "$TEMP_FILE"
|
||||
elif command -v wget >/dev/null; then
|
||||
wget -O "$TEMP_FILE" "$URL"
|
||||
else
|
||||
echo "Error: curl or wget is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Downloading C3 ($VERSION) from $URL..."
|
||||
download_file
|
||||
|
||||
# Remove existing installation and extract the new one
|
||||
rm -rf "$BIN_DIR"
|
||||
if [[ "$EXT" == ".zip" ]]; then
|
||||
unzip "$TEMP_FILE" -d "$HOME"
|
||||
else
|
||||
tar -xzf "$TEMP_FILE" -C "$HOME"
|
||||
fi
|
||||
|
||||
# Move extracted folder to installation directory
|
||||
mv "$HOME/c3" "$BIN_DIR"
|
||||
chmod +x "$BIN_DIR/c3c" # Ensure compiler binary is executable
|
||||
echo "✅ Installation completed in $BIN_DIR"
|
||||
|
||||
# Update PATH unless suppressed by environment variable
|
||||
if [ -n "${C3_NO_PATH_UPDATE:-}" ]; then
|
||||
echo "No path update because C3_NO_PATH_UPDATE is set"
|
||||
else
|
||||
update_shell() {
|
||||
FILE="$1"
|
||||
LINE="$2"
|
||||
|
||||
# Create shell config file if missing
|
||||
if [ ! -f "$FILE" ]; then
|
||||
touch "$FILE"
|
||||
fi
|
||||
|
||||
# Add the PATH line if not already present
|
||||
if ! grep -Fxq "$LINE" "$FILE"; then
|
||||
echo "Updating '${FILE}'"
|
||||
echo "$LINE" >>"$FILE"
|
||||
echo "Please restart or source your shell."
|
||||
fi
|
||||
}
|
||||
|
||||
# Detect the current shell and add C3 to its PATH
|
||||
case "$(basename "${SHELL-}")" in
|
||||
bash)
|
||||
# Default to bashrc as that is used in non login shells instead of the profile.
|
||||
LINE="export PATH=\"${BIN_DIR}:\$PATH\""
|
||||
update_shell ~/.bashrc "$LINE"
|
||||
;;
|
||||
fish)
|
||||
LINE="fish_add_path ${BIN_DIR}"
|
||||
update_shell ~/.config/fish/config.fish "$LINE"
|
||||
;;
|
||||
zsh)
|
||||
LINE="export PATH=\"${BIN_DIR}:\$PATH\""
|
||||
update_shell ~/.zshrc "$LINE"
|
||||
;;
|
||||
tcsh)
|
||||
LINE="set path = ( ${BIN_DIR} \$path )"
|
||||
update_shell ~/.tcshrc "$LINE"
|
||||
;;
|
||||
'')
|
||||
echo "warn: Could not detect shell type." >&2
|
||||
echo " Please permanently add '${BIN_DIR}' to your \$PATH to enable the 'c3c' command." >&2
|
||||
;;
|
||||
*)
|
||||
echo "warn: Could not update shell $(basename "$SHELL")" >&2
|
||||
echo " Please permanently add '${BIN_DIR}' to your \$PATH to enable the 'c3c' command." >&2
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
__wrap__
|
||||
17
install_win_reqs.bat
Normal file
17
install_win_reqs.bat
Normal 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
|
||||
@@ -1,42 +1,77 @@
|
||||
<* This module is scheduled for removal, use std::core::ascii *>
|
||||
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_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 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) @deprecated => is_lower_m(c);
|
||||
fn bool uint.is_upper(uint c) @deprecated => is_upper_m(c);
|
||||
fn bool uint.is_digit(uint c) @deprecated => is_digit_m(c);
|
||||
fn bool uint.is_bdigit(uint c) @deprecated => is_bdigit_m(c);
|
||||
fn bool uint.is_odigit(uint c) @deprecated => is_odigit_m(c);
|
||||
fn bool uint.is_xdigit(uint c) @deprecated => is_xdigit_m(c);
|
||||
fn bool uint.is_alpha(uint c) @deprecated => is_alpha_m(c);
|
||||
fn bool uint.is_print(uint c) @deprecated => is_print_m(c);
|
||||
fn bool uint.is_graph(uint c) @deprecated => is_graph_m(c);
|
||||
fn bool uint.is_space(uint c) @deprecated => is_space_m(c);
|
||||
fn bool uint.is_alnum(uint c) @deprecated => is_alnum_m(c);
|
||||
fn bool uint.is_punct(uint c) @deprecated => is_punct_m(c);
|
||||
fn bool uint.is_blank(uint c) @deprecated => is_blank_m(c);
|
||||
fn bool uint.is_cntrl(uint c) @deprecated => is_cntrl_m(c);
|
||||
fn uint uint.to_lower(uint c) @deprecated => (uint)to_lower_m(c);
|
||||
fn uint uint.to_upper(uint c) @deprecated => (uint)to_upper_m(c);
|
||||
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);
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// Copyright (c) 2023-2025 Eduardo José Gómez Hernández. All rights reserved.
|
||||
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::atomic::types <Type>;
|
||||
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"
|
||||
*>
|
||||
/**
|
||||
* 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;
|
||||
@@ -28,12 +28,12 @@ macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
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"
|
||||
*>
|
||||
/**
|
||||
* 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;
|
||||
@@ -76,7 +76,7 @@ macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTEN
|
||||
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_max, data, value, ordering);
|
||||
return @atomic_exec(atomic::fetch_div, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
@@ -85,49 +85,36 @@ macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTEN
|
||||
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)
|
||||
macro Type Atomic.or(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_or, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.xor(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
fn Type Atomic.xor(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.and(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
macro Type Atomic.and(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_and, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.shr(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
macro Type Atomic.shift_right(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.shl(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
macro Type Atomic.shift_left(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
|
||||
}
|
||||
|
||||
macro 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)
|
||||
@@ -137,65 +124,23 @@ macro @atomic_exec(#func, data, value, ordering) @local
|
||||
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.");
|
||||
default: assert(false, "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 FUNC:
|
||||
$case FLOAT:
|
||||
$case BOOL:
|
||||
return true;
|
||||
$case DISTINCT:
|
||||
$case CONST_ENUM:
|
||||
return is_native_atomic_type($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param [in] y "the value to be added to ptr."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
@@ -204,18 +149,16 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
return $$atomic_fetch_add(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param [in] y "the value to be added to ptr."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
@@ -224,25 +167,23 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
return $$atomic_fetch_sub(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param [in] y "the value to be added to ptr."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
@@ -252,37 +193,34 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
|
||||
do
|
||||
{
|
||||
|
||||
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);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param [in] y "the value to be added to ptr."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
@@ -292,89 +230,163 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
|
||||
do
|
||||
{
|
||||
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);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param [in] y "the value to be added to ptr."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
* @require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value | storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param [in] y "the value to be added to ptr."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
* @require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value ^ storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param [in] y "the value to be added to ptr."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
* @require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value & storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param [in] y "the value to be added to ptr."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
* @require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
@@ -385,38 +397,34 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do
|
||||
{
|
||||
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);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param [in] y "the value to be added to ptr."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
* @require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
@@ -427,84 +435,65 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do
|
||||
{
|
||||
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);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value = true;
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = 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);
|
||||
|
||||
do {
|
||||
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
|
||||
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value = false;
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = 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);
|
||||
|
||||
do {
|
||||
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
|
||||
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [in] y : "the value to be 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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param [in] y "the value to be added to ptr."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
@@ -513,17 +502,15 @@ macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
/**
|
||||
* @param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
* @param [in] y "the value to be added to ptr."
|
||||
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
* @return "returns the old value of ptr"
|
||||
*
|
||||
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
**/
|
||||
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
|
||||
@@ -3,33 +3,32 @@
|
||||
// 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)
|
||||
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");
|
||||
default: assert(false, "Unrecognized failure ordering");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, success, failure, $alignment)
|
||||
{
|
||||
switch (success)
|
||||
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");
|
||||
default: assert(false, "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")
|
||||
fn CInt __atomic_compare_exchange(CInt size, any* ptr, any* expected, any* desired, CInt success, CInt failure) @extern("__atomic_compare_exchange") @export
|
||||
{
|
||||
switch (size)
|
||||
{
|
||||
@@ -58,7 +57,7 @@ fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired,
|
||||
nextcase;
|
||||
$endif
|
||||
default:
|
||||
unreachable("Unsupported size (%d) for atomic_compare_exchange", size);
|
||||
assert(false, "Unsuported size (%d) for atomic_compare_exchange", size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
module std::bits;
|
||||
|
||||
<*
|
||||
@require types::is_intlike($typeof(i)) : `The input must be an integer or integer vector`
|
||||
*>
|
||||
/**
|
||||
* @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`
|
||||
*>
|
||||
/**
|
||||
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
|
||||
**/
|
||||
macro bswap(i) @builtin => $$bswap(i);
|
||||
|
||||
macro uint[<*>].popcount(self) => $$popcount(self);
|
||||
|
||||
@@ -1,122 +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::collections::interfacelist;
|
||||
|
||||
alias AnyPredicate = InterfacePredicate {any};
|
||||
alias AnyTest = InterfaceTest {any};
|
||||
|
||||
<*
|
||||
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.
|
||||
*>
|
||||
typedef AnyList = inline InterfaceList {any};
|
||||
|
||||
<*
|
||||
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 => InterfaceList {any}.first(self);
|
||||
|
||||
<*
|
||||
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 => InterfaceList {any}.last(self);
|
||||
|
||||
<*
|
||||
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);
|
||||
}
|
||||
|
||||
<*
|
||||
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);
|
||||
}
|
||||
|
||||
<*
|
||||
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([]) => InterfaceList {any}.get(self, index);
|
||||
|
||||
<*
|
||||
Return the length of the list.
|
||||
|
||||
@return "The number of elements in the list"
|
||||
*>
|
||||
fn usz AnyList.len(&self) @operator(len) @inline => InterfaceList {any}.len(self);
|
||||
@@ -1,22 +1,18 @@
|
||||
// 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>;
|
||||
/**
|
||||
* @require SIZE > 0
|
||||
**/
|
||||
module std::collections::bitset(<SIZE>);
|
||||
|
||||
const BITS = uint.sizeof * 8;
|
||||
def Type = uint;
|
||||
|
||||
const BITS = Type.sizeof * 8;
|
||||
const SZ = (SIZE + BITS - 1) / BITS;
|
||||
|
||||
struct BitSet
|
||||
{
|
||||
uint[SZ] data;
|
||||
Type[SZ] data;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "The number of bits set"
|
||||
*>
|
||||
fn usz BitSet.cardinality(&self)
|
||||
{
|
||||
usz n;
|
||||
@@ -27,13 +23,9 @@ fn usz BitSet.cardinality(&self)
|
||||
return n;
|
||||
}
|
||||
|
||||
<*
|
||||
Set a bit in the bitset.
|
||||
|
||||
@param i : "The index to set"
|
||||
|
||||
@require i < SIZE : "Index was out of range"
|
||||
*>
|
||||
/**
|
||||
* @require i < SIZE
|
||||
**/
|
||||
fn void BitSet.set(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
@@ -41,88 +33,9 @@ fn void BitSet.set(&self, usz i)
|
||||
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"
|
||||
*>
|
||||
/**
|
||||
* @require i < SIZE
|
||||
**/
|
||||
fn void BitSet.unset(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
@@ -130,14 +43,9 @@ fn void BitSet.unset(&self, usz i)
|
||||
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"
|
||||
@pure
|
||||
*>
|
||||
/**
|
||||
* @require i < SIZE
|
||||
**/
|
||||
fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
{
|
||||
usz q = i / BITS;
|
||||
@@ -145,58 +53,48 @@ fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
return self.data[q] & (1 << r) != 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the number of bits.
|
||||
|
||||
@pure
|
||||
*>
|
||||
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"
|
||||
*>
|
||||
/**
|
||||
* @require i < SIZE
|
||||
**/
|
||||
fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
|
||||
{
|
||||
if (value) return self.set(i);
|
||||
self.unset(i);
|
||||
}
|
||||
|
||||
<*
|
||||
@require Type.kindof == UNSIGNED_INT
|
||||
*>
|
||||
module std::collections::growablebitset <Type>;
|
||||
/**
|
||||
* @require Type.kindof == UNSIGNED_INT
|
||||
**/
|
||||
module std::collections::growablebitset(<Type>);
|
||||
import std::collections::list;
|
||||
|
||||
const BITS = Type.sizeof * 8;
|
||||
|
||||
alias GrowableBitSetList = List{Type};
|
||||
def GrowableBitSetList = List(<Type>);
|
||||
|
||||
struct GrowableBitSet
|
||||
{
|
||||
GrowableBitSetList data;
|
||||
}
|
||||
|
||||
<*
|
||||
@param initial_capacity
|
||||
@param [&inout] allocator : "The allocator to use, defaults to the heap allocator"
|
||||
*>
|
||||
fn GrowableBitSet* GrowableBitSet.init(&self, Allocator allocator, usz initial_capacity = 1)
|
||||
/**
|
||||
* @param initial_capacity
|
||||
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
**/
|
||||
fn GrowableBitSet* GrowableBitSet.init_new(&self, usz initial_capacity = 1, Allocator* allocator = mem::heap())
|
||||
{
|
||||
self.data.init(allocator, initial_capacity);
|
||||
self.data.init_new(initial_capacity, allocator);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn GrowableBitSet* GrowableBitSet.tinit(&self, usz initial_capacity = 1)
|
||||
fn GrowableBitSet* GrowableBitSet.init_temp(&self, usz initial_capacity = 1)
|
||||
{
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
return self.init_new(initial_capacity, mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.free(&self)
|
||||
@@ -219,10 +117,15 @@ 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)
|
||||
if (q >= current_len)
|
||||
{
|
||||
self.data.push(0);
|
||||
current_len++;
|
||||
usz n = q + 1;
|
||||
self.data.reserve(n);
|
||||
if (n - 1 >= current_len)
|
||||
{
|
||||
self.data.entries[current_len .. (n - 1)] = 0;
|
||||
}
|
||||
self.data.size = n;
|
||||
}
|
||||
self.data.set(q, self.data[q] | (1 << r));
|
||||
}
|
||||
|
||||
@@ -1,461 +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 bool 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) @deprecated("Use push_all_to_limit")
|
||||
{
|
||||
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 as many values from this array as possible, returning the
|
||||
number of elements that didn't fit.
|
||||
|
||||
@param [in] array
|
||||
*>
|
||||
fn usz ElasticArray.push_all_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) @deprecated("Use push_all")
|
||||
{
|
||||
if (!array.len) return;
|
||||
foreach (&value : array)
|
||||
{
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
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.push_all(&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);
|
||||
}
|
||||
@@ -1,13 +1,8 @@
|
||||
<*
|
||||
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap"
|
||||
@require Enum.values.len > 0 : "Only non-empty enums may be used with enummap"
|
||||
*>
|
||||
module std::collections::enummap <Enum, ValueType>;
|
||||
import std::io;
|
||||
module std::collections::enummap(<Enum, ValueType>);
|
||||
|
||||
struct EnumMap (Printable)
|
||||
{
|
||||
ValueType[Enum.values.len] values;
|
||||
ValueType[Enum.len] values;
|
||||
}
|
||||
|
||||
fn void EnumMap.init(&self, ValueType init_value)
|
||||
@@ -18,30 +13,40 @@ fn void EnumMap.init(&self, ValueType init_value)
|
||||
}
|
||||
}
|
||||
|
||||
fn usz? EnumMap.to_format(&self, Formatter* formatter) @dynamic
|
||||
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.printf("%s: %s", (Enum)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 String EnumMap.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
return string::new_format("%s", *self, .allocator = allocator);
|
||||
}
|
||||
|
||||
fn String EnumMap.to_tstring(&self) @dynamic
|
||||
{
|
||||
return string::tformat("%s", *self);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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."
|
||||
*>
|
||||
/**
|
||||
* @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];
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021 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;
|
||||
/**
|
||||
* @require Enum.kindof == TypeKind.ENUM : "Only enums maybe be used with an enumset"
|
||||
**/
|
||||
module std::collections::enumset(<Enum>);
|
||||
|
||||
const ENUM_COUNT @private = Enum.values.len;
|
||||
alias EnumSetType @private = $typefrom(type_for_enum_elements(ENUM_COUNT));
|
||||
def EnumSetType = $typefrom(private::type_for_enum_elements(Enum.elements)) @private ;
|
||||
|
||||
const IS_CHAR_ARRAY = ENUM_COUNT > 128;
|
||||
typedef EnumSet (Printable) = EnumSetType;
|
||||
const IS_CHAR_ARRAY = Enum.elements > 128;
|
||||
distinct EnumSet (Printable) = EnumSetType;
|
||||
|
||||
fn void EnumSet.add(&self, Enum v)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
(*self)[(usz)v.ordinal / 8] |= (char)(1u << ((usz)v.ordinal % 8));
|
||||
(*self)[(usz)v / 8] |= (char)(1u << ((usz)v % 8));
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v.ordinal);
|
||||
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v);
|
||||
$endif
|
||||
}
|
||||
|
||||
@@ -36,11 +34,11 @@ 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));
|
||||
(*self)[(usz)v / 8] &= (char)~(1u << ((usz)v % 8));
|
||||
return true;
|
||||
$else
|
||||
EnumSetType old = (EnumSetType)*self;
|
||||
EnumSetType new = old & ~(1u << (EnumSetType)v.ordinal);
|
||||
EnumSetType new = old & ~(1u << (EnumSetType)v);
|
||||
*self = (EnumSet)new;
|
||||
return old != new;
|
||||
$endif
|
||||
@@ -49,9 +47,9 @@ fn bool EnumSet.remove(&self, Enum v)
|
||||
fn bool EnumSet.has(&self, Enum v)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
return (bool)(((*self)[(usz)v.ordinal / 8] << ((usz)v.ordinal % 8)) & 0x01);
|
||||
return (bool)(((*self)[(usz)v / 8] << ((usz)v % 8)) & 0x01);
|
||||
$else
|
||||
return ((EnumSetType)*self & (1u << (EnumSetType)v.ordinal)) != 0;
|
||||
return ((EnumSetType)*self & (1u << (EnumSetType)v)) != 0;
|
||||
$endif
|
||||
}
|
||||
|
||||
@@ -127,7 +125,7 @@ fn EnumSet EnumSet.xor_of(&self, EnumSet s)
|
||||
$endif
|
||||
}
|
||||
|
||||
fn usz? EnumSet.to_format(&set, Formatter* formatter) @dynamic
|
||||
fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
|
||||
{
|
||||
usz n = formatter.print("[")!;
|
||||
bool found;
|
||||
@@ -142,9 +140,21 @@ fn usz? EnumSet.to_format(&set, Formatter* formatter) @dynamic
|
||||
return n;
|
||||
}
|
||||
|
||||
macro typeid type_for_enum_elements(usz $elements) @local
|
||||
fn String EnumSet.to_new_string(&set, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
$switch:
|
||||
return string::new_format("%s", *set, .allocator = allocator);
|
||||
}
|
||||
|
||||
fn String EnumSet.to_tstring(&set) @dynamic
|
||||
{
|
||||
return string::tformat("%s", *set);
|
||||
}
|
||||
|
||||
module std::collections::enumset::private;
|
||||
|
||||
macro typeid type_for_enum_elements(usz $elements)
|
||||
{
|
||||
$switch
|
||||
$case ($elements > 128):
|
||||
return char[($elements + 7) / 8].typeid;
|
||||
$case ($elements > 64):
|
||||
|
||||
@@ -1,610 +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;
|
||||
<* Last inserted LinkedEntry *>
|
||||
uint count;
|
||||
<* Resize limit *>
|
||||
uint threshold;
|
||||
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.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: 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 Value* HashMap.get_or_create_ref(&map, Key key) @operator(&[])
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
if (map.count)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
map.set(key, {});
|
||||
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;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
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 set it to the value
|
||||
|
||||
@require $defined(Value val = #expr)
|
||||
*>
|
||||
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(&self) => self.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;
|
||||
@@ -1,654 +0,0 @@
|
||||
<*
|
||||
@require $defined((Value){}.hash()) : `No .hash function found on the value`
|
||||
*>
|
||||
module std::collections::set <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 Allocator SET_HEAP_ALLOCATOR = (Allocator)&dummy;
|
||||
|
||||
<* Copy the ONHEAP allocator to initialize to a set that is heap allocated *>
|
||||
const HashSet ONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
|
||||
|
||||
struct Entry
|
||||
{
|
||||
uint hash;
|
||||
Value value;
|
||||
Entry* next;
|
||||
}
|
||||
|
||||
struct HashSet (Printable)
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator allocator;
|
||||
<* Number of elements *>
|
||||
usz count;
|
||||
<* Resize limit *>
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
fn usz HashSet.len(&self) @operator(len) => self.count;
|
||||
|
||||
<*
|
||||
@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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
self.allocator = allocator;
|
||||
self.threshold = (usz) (capacity * load_factor);
|
||||
self.load_factor = 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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.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 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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashSet* HashSet.init_with_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++:
|
||||
self.add($vaarg[$i]);
|
||||
$endfor
|
||||
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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashSet* HashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the HashSet"
|
||||
@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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
foreach (v : values) self.add(v);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the HashSet entries"
|
||||
@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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_from_values(tmem, values, capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
Has this hash set been initialized yet?
|
||||
|
||||
@param [&in] set : "The hash set we are testing"
|
||||
@return "Returns true if it has been initialized, false otherwise"
|
||||
*>
|
||||
fn bool HashSet.is_initialized(&set)
|
||||
{
|
||||
return set.allocator && set.allocator.ptr != &dummy;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn HashSet* HashSet.init_from_set(&self, Allocator allocator, HashSet* other_set)
|
||||
{
|
||||
self.init(allocator, other_set.table.len, other_set.load_factor);
|
||||
self.put_all_for_create(other_set);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !set.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn HashSet* HashSet.tinit_from_set(&set, HashSet* other_set)
|
||||
{
|
||||
return set.init_from_set(tmem, other_set) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set is empty
|
||||
|
||||
@return "true if it is empty"
|
||||
@pure
|
||||
*>
|
||||
fn bool HashSet.is_empty(&set) @inline
|
||||
{
|
||||
return !set.count;
|
||||
}
|
||||
|
||||
<*
|
||||
Add all elements in the slice to the set.
|
||||
|
||||
@param [in] list
|
||||
@return "The number of new elements added"
|
||||
@ensure total <= list.len
|
||||
*>
|
||||
fn usz HashSet.add_all(&set, Value[] list)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : list)
|
||||
{
|
||||
if (set.add(v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other
|
||||
@return "The number of new elements added"
|
||||
@ensure return <= other.count
|
||||
*>
|
||||
fn usz HashSet.add_all_from(&set, HashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (set.add(value)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to add"
|
||||
@return "true if the value didn't exist in the set"
|
||||
*>
|
||||
fn bool HashSet.add(&set, Value value)
|
||||
{
|
||||
// If the set isn't initialized, use the defaults to initialize it.
|
||||
switch (set.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
set.init(mem);
|
||||
case null:
|
||||
set.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint hash = rehash(value.hash());
|
||||
uint index = index_for(hash, set.table.len);
|
||||
for (Entry *e = set.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return false;
|
||||
}
|
||||
set.add_entry(hash, value, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Iterate over all the values in the set
|
||||
*>
|
||||
macro HashSet.@each(set; @body(value))
|
||||
{
|
||||
if (!set.count) return;
|
||||
foreach (Entry* entry : set.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@body(entry.value);
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set contains the given value.
|
||||
|
||||
@param value : "The value to check"
|
||||
@return "true if it exists in the set"
|
||||
*>
|
||||
fn bool HashSet.contains(&set, Value value)
|
||||
{
|
||||
if (!set.count) return false;
|
||||
uint hash = rehash(value.hash());
|
||||
for (Entry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
Remove a single value from the set.
|
||||
|
||||
@param value : "The value to remove"
|
||||
@return? NOT_FOUND : "If the entry is not found"
|
||||
*>
|
||||
fn void? HashSet.remove(&set, Value value) @maydiscard
|
||||
{
|
||||
if (!set.remove_entry_for_value(value)) return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz HashSet.remove_all(&set, Value[] values)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : values)
|
||||
{
|
||||
if (set.remove_entry_for_value(v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other : "Other set"
|
||||
*>
|
||||
fn usz HashSet.remove_all_from(&set, HashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value val)
|
||||
{
|
||||
if (set.remove_entry_for_value(val)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
Free all memory allocated by the hash set.
|
||||
*>
|
||||
fn void HashSet.free(&set)
|
||||
{
|
||||
if (!set.is_initialized()) return;
|
||||
set.clear();
|
||||
set.free_internal(set.table.ptr);
|
||||
*set = {};
|
||||
}
|
||||
|
||||
<*
|
||||
Clear all elements from the set while keeping the underlying storage
|
||||
|
||||
@ensure set.count == 0
|
||||
*>
|
||||
fn void HashSet.clear(&set)
|
||||
{
|
||||
if (!set.count) return;
|
||||
|
||||
foreach (Entry** &entry_ref : set.table)
|
||||
{
|
||||
Entry* entry = *entry_ref;
|
||||
if (!entry) continue;
|
||||
|
||||
Entry *next = entry.next;
|
||||
while (next)
|
||||
{
|
||||
Entry *to_delete = next;
|
||||
next = next.next;
|
||||
set.free_entry(to_delete);
|
||||
}
|
||||
|
||||
set.free_entry(entry);
|
||||
*entry_ref = null;
|
||||
}
|
||||
set.count = 0;
|
||||
}
|
||||
|
||||
fn void HashSet.reserve(&set, usz capacity)
|
||||
{
|
||||
if (capacity > set.threshold)
|
||||
{
|
||||
set.resize(math::next_power_of_2(capacity));
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] HashSet.tvalues(&self) => self.values(tmem) @inline;
|
||||
|
||||
fn Value[] HashSet.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;
|
||||
}
|
||||
|
||||
// --- Set Operations ---
|
||||
|
||||
<*
|
||||
Returns the union of two sets (A | B)
|
||||
|
||||
@param [&in] other : "The other set to union with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the union of both sets"
|
||||
*>
|
||||
fn HashSet HashSet.set_union(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
usz new_capacity = math::next_power_of_2(self.count + other.count);
|
||||
HashSet result;
|
||||
result.init(allocator, new_capacity, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
result.add_all_from(other);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tset_union(&self, HashSet* other) => self.set_union(tmem, other);
|
||||
|
||||
<*
|
||||
Returns the intersection of the two sets (A & B)
|
||||
|
||||
@param [&in] other : "The other set to intersect with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the intersection of both sets"
|
||||
*>
|
||||
fn HashSet HashSet.intersection(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
HashSet result;
|
||||
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
|
||||
|
||||
// Iterate through the smaller set for efficiency
|
||||
HashSet* smaller = self.count <= other.count ? self : other;
|
||||
HashSet* larger = self.count > other.count ? self : other;
|
||||
|
||||
smaller.@each(;Value value)
|
||||
{
|
||||
if (larger.contains(value)) result.add(value);
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tintersection(&self, HashSet* other) => self.intersection(tmem, other);
|
||||
|
||||
<*
|
||||
Return this set - other, so (A & ~B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set but not in the other"
|
||||
*>
|
||||
fn HashSet HashSet.difference(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
HashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
self.@each(;Value value)
|
||||
{
|
||||
if (!other.contains(value))
|
||||
{
|
||||
result.add(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tdifference(&self, HashSet* other) => self.difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Return (A ^ B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set or the other, but not both"
|
||||
*>
|
||||
fn HashSet HashSet.symmetric_difference(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
HashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (!result.add(value))
|
||||
{
|
||||
result.remove(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tsymmetric_difference(&self, HashSet* other) => self.symmetric_difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Check if this hash set is a subset of another set.
|
||||
|
||||
@param [&in] other : "The other set to check against"
|
||||
@return "True if all elements of this set are in the other set"
|
||||
*>
|
||||
fn bool HashSet.is_subset(&self, HashSet* other)
|
||||
{
|
||||
if (self.count == 0) return true;
|
||||
if (self.count > other.count) return false;
|
||||
|
||||
self.@each(;Value value)
|
||||
{
|
||||
if (!other.contains(value)) return false;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void HashSet.add_entry(&set, uint hash, Value value, uint bucket_index) @private
|
||||
{
|
||||
Entry* entry = allocator::new(set.allocator, Entry, { .hash = hash, .value = value, .next = set.table[bucket_index] });
|
||||
set.table[bucket_index] = entry;
|
||||
if (set.count++ >= set.threshold)
|
||||
{
|
||||
set.resize(set.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashSet.resize(&self, usz new_capacity) @private
|
||||
{
|
||||
Entry*[] old_table = self.table;
|
||||
usz old_capacity = old_table.len;
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
self.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
Entry*[] new_table = allocator::new_array(self.allocator, Entry*, new_capacity);
|
||||
self.transfer(new_table);
|
||||
self.table = new_table;
|
||||
self.free_internal(old_table.ptr);
|
||||
self.threshold = (uint)(new_capacity * self.load_factor);
|
||||
}
|
||||
|
||||
fn usz? HashSet.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len;
|
||||
len += f.print("{ ")!;
|
||||
self.@each(; Value value)
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s", value)!;
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
fn void HashSet.transfer(&self, Entry*[] new_table) @private
|
||||
{
|
||||
Entry*[] src = self.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 HashSet.put_all_for_create(&set, HashSet* other_set) @private
|
||||
{
|
||||
if (!other_set.count) return;
|
||||
foreach (Entry *e : other_set.table)
|
||||
{
|
||||
while (e)
|
||||
{
|
||||
set.put_for_create(e.value);
|
||||
e = e.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashSet.put_for_create(&set, Value value) @private
|
||||
{
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
for (Entry *e = set.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
// Value already exists, no need to do anything
|
||||
return;
|
||||
}
|
||||
}
|
||||
set.create_entry(hash, value, i);
|
||||
}
|
||||
|
||||
fn void HashSet.free_internal(&self, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(self.allocator, ptr);
|
||||
}
|
||||
|
||||
fn void HashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
|
||||
{
|
||||
Entry* entry = allocator::new(set.allocator, Entry, {
|
||||
.hash = hash,
|
||||
.value = value,
|
||||
.next = set.table[bucket_index]
|
||||
});
|
||||
set.table[bucket_index] = entry;
|
||||
set.count++;
|
||||
}
|
||||
|
||||
<*
|
||||
Removes the entry for the specified value if present
|
||||
@return "true if found and removed, false otherwise"
|
||||
*>
|
||||
fn bool HashSet.remove_entry_for_value(&set, Value value) @private
|
||||
{
|
||||
if (!set.count) return false;
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
Entry* prev = set.table[i];
|
||||
Entry* e = prev;
|
||||
while (e)
|
||||
{
|
||||
Entry *next = e.next;
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
set.count--;
|
||||
if (prev == e)
|
||||
{
|
||||
set.table[i] = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
set.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void HashSet.free_entry(&set, Entry *entry) @private
|
||||
{
|
||||
allocator::free(set.allocator, entry);
|
||||
}
|
||||
|
||||
struct HashSetIterator
|
||||
{
|
||||
HashSet* set;
|
||||
usz bucket_index;
|
||||
Entry* current;
|
||||
}
|
||||
|
||||
fn HashSetIterator HashSet.iter(&set) => { .set = set, .bucket_index = 0, .current = null };
|
||||
|
||||
fn Value? HashSetIterator.next(&self)
|
||||
{
|
||||
if (self.current)
|
||||
{
|
||||
Value value = self.current.value;
|
||||
self.current = self.current.next;
|
||||
return value;
|
||||
}
|
||||
|
||||
while (self.bucket_index < self.set.table.len)
|
||||
{
|
||||
self.current = self.set.table[self.bucket_index++];
|
||||
if (self.current)
|
||||
{
|
||||
Value value = self.current.value;
|
||||
self.current = self.current.next;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz HashSetIterator.len(&self) @operator(len)
|
||||
{
|
||||
return self.set.count;
|
||||
}
|
||||
|
||||
<* @pure *>
|
||||
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 => hash & (capacity - 1);
|
||||
|
||||
int dummy @local;
|
||||
@@ -1,539 +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.
|
||||
<*
|
||||
@require Type.kindof == INTERFACE || Type.kindof == ANY : "The kind of an interfacelist must be an interface or `any`"
|
||||
*>
|
||||
module std::collections::interfacelist <Type>;
|
||||
import std::io,std::math;
|
||||
|
||||
alias InterfacePredicate = fn bool(Type value);
|
||||
alias InterfaceTest = fn bool(Type type, Type context);
|
||||
|
||||
<*
|
||||
The InterfaceList contains a heterogenous set of types implementing an interface. anything placed in the
|
||||
list will shallowly copied in order to be stored as the interface. This means
|
||||
that the list will copy and free its elements.
|
||||
|
||||
However, because we're getting interface 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 value.
|
||||
*>
|
||||
struct InterfaceList (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
Type* 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 InterfaceList* InterfaceList.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, Type, 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 InterfaceList* InterfaceList.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn bool InterfaceList.is_initialized(&self) @inline => self.allocator != null;
|
||||
|
||||
<*
|
||||
Push an element on the list by cloning it.
|
||||
@require $defined(Type t = &element) : "Element must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.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 InterfaceList.free_element(&self, Type element) @inline
|
||||
{
|
||||
allocator::free(self.allocator, element.ptr);
|
||||
}
|
||||
|
||||
<*
|
||||
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 Type? InterfaceList.copy_pop(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return (Type)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 Type? InterfaceList.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 Type? InterfaceList.pop_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Remove all elements in the list.
|
||||
*>
|
||||
fn void InterfaceList.clear(&self)
|
||||
{
|
||||
for (usz i = 0; i < self.size; i++)
|
||||
{
|
||||
self.free_element(self.entries[i]);
|
||||
}
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
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 Type? InterfaceList.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 Type? InterfaceList.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 (Type)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 Type? InterfaceList.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 InterfaceList.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 InterfaceList.
|
||||
|
||||
@param [&in] other_list : "The list to add"
|
||||
*>
|
||||
fn void InterfaceList.add_all(&self, InterfaceList* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
self.reserve(other_list.size);
|
||||
foreach (value : other_list)
|
||||
{
|
||||
self.entries[self.size++] = (Type)allocator::clone_any(self.allocator, value);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the order of the elements in the list.
|
||||
*>
|
||||
fn void InterfaceList.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 Type[] InterfaceList.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"
|
||||
@require $defined(Type t = &value) : "Value must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.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"
|
||||
@require $defined(Type t = &type) : "Type must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.insert_at(&self, usz index, type)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(type);
|
||||
return;
|
||||
}
|
||||
Type value = allocator::clone(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 InterfaceList.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 InterfaceList.remove_first(&self)
|
||||
{
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
<*
|
||||
Return the first element
|
||||
|
||||
@return "The first element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.first(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[0] : NO_MORE_ELEMENT~;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the last element
|
||||
|
||||
@return "The last element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.last(&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 InterfaceList.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the length of the list.
|
||||
|
||||
@return "The number of elements in the list"
|
||||
*>
|
||||
fn usz InterfaceList.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
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 Type InterfaceList.get(&self, usz index) @inline @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
<*
|
||||
Completely free and clear a list.
|
||||
*>
|
||||
fn void InterfaceList.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 InterfaceList.swap(&self, usz i, usz j)
|
||||
{
|
||||
Type temp = self.entries[i];
|
||||
self.entries[i] = self.entries[j];
|
||||
self.entries[j] = temp;
|
||||
}
|
||||
|
||||
<*
|
||||
Print the list to a formatter.
|
||||
*>
|
||||
fn usz? InterfaceList.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 Type 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 InterfaceList.remove_if(&self, InterfacePredicate 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 InterfaceList.retain_if(&self, InterfacePredicate selection)
|
||||
{
|
||||
return self._remove_if(selection, true);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove Type 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 InterfaceList.remove_using_test(&self, InterfaceTest filter, Type context)
|
||||
{
|
||||
return self._remove_using_test(filter, false, context);
|
||||
}
|
||||
|
||||
<*
|
||||
Retain Type 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 InterfaceList.retain_using_test(&self, InterfaceTest selection, Type 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 InterfaceList.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, Type.sizeof * min_capacity);
|
||||
self.capacity = min_capacity;
|
||||
}
|
||||
|
||||
<*
|
||||
Set the element at Type index.
|
||||
|
||||
@param index : "The index where to set the value."
|
||||
@param value : "The value to set"
|
||||
@require index <= self.size : "Index out of range"
|
||||
@require $defined(Type t = &value) : "Value must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.set(&self, usz index, value)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(value);
|
||||
return;
|
||||
}
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index] = allocator::clone(self.allocator, value);
|
||||
}
|
||||
|
||||
// -- private
|
||||
|
||||
fn void InterfaceList.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 InterfaceList._append(&self, Type element) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void InterfaceList._insert_at(&self, usz index, Type 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 InterfaceList._remove_using_test(&self, InterfaceTest 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 InterfaceList._remove_if(&self, InterfacePredicate 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;
|
||||
}
|
||||
@@ -1,333 +0,0 @@
|
||||
module std::collections::blockingqueue <Value>;
|
||||
import std::thread, std::time;
|
||||
|
||||
|
||||
const INITIAL_CAPACITY = 16;
|
||||
|
||||
struct QueueEntry
|
||||
{
|
||||
Value value;
|
||||
<* Next in queue order *>
|
||||
QueueEntry* next;
|
||||
<* Previous in queue order *>
|
||||
QueueEntry* prev;
|
||||
}
|
||||
|
||||
struct LinkedBlockingQueue
|
||||
{
|
||||
<* First element in queue *>
|
||||
QueueEntry* head;
|
||||
<* Last element in queue *>
|
||||
QueueEntry* tail;
|
||||
<* Current number of elements *>
|
||||
usz count;
|
||||
<* Maximum capacity (0 for unbounded) *>
|
||||
usz capacity;
|
||||
Mutex lock;
|
||||
ConditionVariable not_empty;
|
||||
ConditionVariable not_full;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param capacity : "Maximum capacity (0 for unbounded)"
|
||||
@require !self.is_initialized() : "Queue was already initialized"
|
||||
*>
|
||||
fn LinkedBlockingQueue* LinkedBlockingQueue.init(&self, Allocator allocator, usz capacity = 0)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.capacity = capacity;
|
||||
self.count = 0;
|
||||
self.head = null;
|
||||
self.tail = null;
|
||||
|
||||
self.lock.init()!!;
|
||||
self.not_empty.init()!!;
|
||||
self.not_full.init()!!;
|
||||
return self;
|
||||
}
|
||||
|
||||
fn LinkedBlockingQueue* LinkedBlockingQueue.tinit(&self, usz capacity = 0)
|
||||
{
|
||||
return self.init(tmem, capacity) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
*>
|
||||
fn void LinkedBlockingQueue.free(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
// Free all remaining entries
|
||||
QueueEntry* entry = self.head;
|
||||
while (entry != null)
|
||||
{
|
||||
QueueEntry* next = entry.next;
|
||||
allocator::free(self.allocator, entry);
|
||||
entry = next;
|
||||
}
|
||||
};
|
||||
|
||||
self.lock.destroy();
|
||||
self.not_empty.destroy();
|
||||
self.not_full.destroy();
|
||||
}
|
||||
|
||||
fn void LinkedBlockingQueue.link_entry(&self, QueueEntry* entry) @private
|
||||
{
|
||||
entry.next = null;
|
||||
entry.prev = self.tail;
|
||||
|
||||
if (self.tail == null)
|
||||
{
|
||||
// First element in queue
|
||||
self.head = entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append to tail
|
||||
self.tail.next = entry;
|
||||
}
|
||||
self.tail = entry;
|
||||
self.count++;
|
||||
}
|
||||
|
||||
|
||||
fn QueueEntry* LinkedBlockingQueue.unlink_head(&self) @private
|
||||
{
|
||||
if (self.head == null) return null;
|
||||
|
||||
QueueEntry* entry = self.head;
|
||||
self.head = entry.next;
|
||||
|
||||
if (self.head != null)
|
||||
{
|
||||
self.head.prev = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queue is now empty
|
||||
self.tail = null;
|
||||
}
|
||||
|
||||
self.count--;
|
||||
return entry;
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "Value to add to the queue"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
*>
|
||||
fn void LinkedBlockingQueue.push(&self, Value value)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
while (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
self.not_full.wait(&self.lock);
|
||||
}
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
self.link_entry(entry);
|
||||
|
||||
// Signal that queue is no longer empty
|
||||
self.not_empty.signal();
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Get a value from the queue, blocking if there is no element in the queue.
|
||||
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value"
|
||||
*>
|
||||
fn Value LinkedBlockingQueue.poll(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
while (self.count == 0)
|
||||
{
|
||||
self.not_empty.wait(&self.lock);
|
||||
}
|
||||
|
||||
QueueEntry* entry = self.unlink_head();
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Pop an element from the queue, fail is it is empty.
|
||||
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value"
|
||||
@return? NO_MORE_ELEMENT : "If the queue is empty"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.pop(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.count == 0) return NO_MORE_ELEMENT~;
|
||||
|
||||
QueueEntry* entry = self.unlink_head();
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Poll with a timeout.
|
||||
|
||||
@param timeout : "Timeout in microseconds"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value or null if timeout occurred"
|
||||
@return? NO_MORE_ELEMENT : "If we reached the timeout"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.poll_timeout(&self, Duration timeout)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
// Use while loop to handle spurious wakeups
|
||||
if (!self.count)
|
||||
{
|
||||
Time start = time::now();
|
||||
Time end = start + timeout;
|
||||
while (!self.count)
|
||||
{
|
||||
if (end <= time::now()) break;
|
||||
if (catch self.not_empty.wait_until(&self.lock, end)) break;
|
||||
}
|
||||
if (!self.count) return NO_MORE_ELEMENT~;
|
||||
}
|
||||
|
||||
QueueEntry* entry = self.unlink_head();
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
|
||||
// Must signal not_full after removing an item
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "Current size of the queue"
|
||||
*>
|
||||
fn usz LinkedBlockingQueue.size(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return self.count;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "True if queue is empty"
|
||||
*>
|
||||
fn bool LinkedBlockingQueue.is_empty(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return self.count == 0;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Try to push, return CAPACITY_EXCEEDED if the queue is full.
|
||||
|
||||
@param value : "Value to add to the queue"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return? CAPACITY_EXCEEDED : "If the queue is full"
|
||||
*>
|
||||
fn void? LinkedBlockingQueue.try_push(&self, Value value)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED~;
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
self.link_entry(entry);
|
||||
self.not_empty.signal();
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Try to push, return CAPACITY_EXCEEDED if the queue is still full after timeout is reached.
|
||||
|
||||
@param value : "Value to add to the queue"
|
||||
@param timeout : "Timeout in microseconds"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return? CAPACITY_EXCEEDED : "If the queue is full"
|
||||
*>
|
||||
fn void? LinkedBlockingQueue.push_timeout(&self, Value value, Duration timeout)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
Time start = time::now();
|
||||
Time end = start + timeout;
|
||||
while (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
if (end <= time::now()) break;
|
||||
if (catch self.not_empty.wait_until(&self.lock, end)) break;
|
||||
}
|
||||
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED~;
|
||||
}
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
self.link_entry(entry);
|
||||
self.not_empty.signal();
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The head value or NO_MORE_ELEMENT~ if queue is empty"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.peek(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return (self.head != null) ? self.head.value : NO_MORE_ELEMENT~;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@return "True if queue is initialized"
|
||||
*>
|
||||
fn bool LinkedBlockingQueue.is_initialized(&self)
|
||||
{
|
||||
return self.allocator && self.lock.initialized;
|
||||
}
|
||||
@@ -1,651 +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 LinkedHashMap LINKEDONHEAP = { .allocator = MAP_HEAP_ALLOCATOR };
|
||||
|
||||
struct LinkedEntry
|
||||
{
|
||||
uint hash;
|
||||
Key key;
|
||||
Value value;
|
||||
<* For bucket chain *>
|
||||
LinkedEntry* next;
|
||||
<* Previous in insertion order *>
|
||||
LinkedEntry* before;
|
||||
<* Next in insertion order *>
|
||||
LinkedEntry* after;
|
||||
}
|
||||
|
||||
struct LinkedHashMap (Printable)
|
||||
{
|
||||
LinkedEntry*[] table;
|
||||
Allocator allocator;
|
||||
usz count;
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
<* First inserted LinkedEntry *>
|
||||
LinkedEntry* head;
|
||||
<* Last inserted LinkedEntry *>
|
||||
LinkedEntry* tail;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@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 LinkedHashMap* LinkedHashMap.init(&self, Allocator allocator, usz 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 = (usz)(capacity * load_factor);
|
||||
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
|
||||
self.head = null;
|
||||
self.tail = null;
|
||||
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 LinkedHashMap* LinkedHashMap.tinit(&self, usz 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 LinkedHashMap* LinkedHashMap.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 LinkedHashMap* LinkedHashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys : "The keys for the LinkedHashMap entries"
|
||||
@param [in] values : "The values for the LinkedHashMap 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 LinkedHashMap* LinkedHashMap.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 LinkedHashMap entries"
|
||||
@param [in] values : "The values for the LinkedHashMap 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 LinkedHashMap* LinkedHashMap.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 LinkedHashMap.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 LinkedHashMap* LinkedHashMap.init_from_map(&self, Allocator allocator, LinkedHashMap* 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 LinkedHashMap* LinkedHashMap.tinit_from_map(&map, LinkedHashMap* other_map)
|
||||
{
|
||||
return map.init_from_map(tmem, other_map) @inline;
|
||||
}
|
||||
|
||||
fn bool LinkedHashMap.is_empty(&map) @inline
|
||||
{
|
||||
return !map.count;
|
||||
}
|
||||
|
||||
fn usz LinkedHashMap.len(&map) @inline => map.count;
|
||||
|
||||
fn Value*? LinkedHashMap.get_ref(&map, Key key)
|
||||
{
|
||||
if (!map.count) return NOT_FOUND~;
|
||||
uint hash = rehash(key.hash());
|
||||
for (LinkedEntry *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 LinkedEntry*? LinkedHashMap.get_entry(&map, Key key)
|
||||
{
|
||||
if (!map.count) return NOT_FOUND~;
|
||||
uint hash = rehash(key.hash());
|
||||
for (LinkedEntry *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 set it to the value
|
||||
|
||||
@require $defined(Value val = #expr)
|
||||
*>
|
||||
macro Value LinkedHashMap.@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 (LinkedEntry *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? LinkedHashMap.get(&map, Key key) @operator([]) => *map.get_ref(key) @inline;
|
||||
|
||||
fn bool LinkedHashMap.has_key(&map, Key key) => @ok(map.get_ref(key));
|
||||
|
||||
fn bool LinkedHashMap.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 (LinkedEntry *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? LinkedHashMap.remove(&map, Key key) @maydiscard
|
||||
{
|
||||
if (!map.remove_entry_for_key(key)) return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.clear(&map)
|
||||
{
|
||||
if (!map.count) return;
|
||||
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
LinkedEntry* next = entry.after;
|
||||
map.free_entry(entry);
|
||||
entry = next;
|
||||
}
|
||||
|
||||
foreach (LinkedEntry** &bucket : map.table)
|
||||
{
|
||||
*bucket = null;
|
||||
}
|
||||
|
||||
map.count = 0;
|
||||
map.head = null;
|
||||
map.tail = null;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.free(&map)
|
||||
{
|
||||
if (!map.is_initialized()) return;
|
||||
map.clear();
|
||||
map.free_internal(map.table.ptr);
|
||||
map.table = {};
|
||||
}
|
||||
|
||||
fn Key[] LinkedHashMap.tkeys(&self)
|
||||
{
|
||||
return self.keys(tmem) @inline;
|
||||
}
|
||||
|
||||
fn Key[] LinkedHashMap.keys(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
|
||||
Key[] list = allocator::alloc_array(allocator, Key, self.count);
|
||||
usz index = 0;
|
||||
|
||||
LinkedEntry* entry = self.head;
|
||||
while (entry)
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
list[index++] = entry.key.copy(allocator);
|
||||
$else
|
||||
list[index++] = entry.key;
|
||||
$endif
|
||||
entry = entry.after;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
macro LinkedHashMap.@each(map; @body(key, value))
|
||||
{
|
||||
map.@each_entry(; LinkedEntry* entry)
|
||||
{
|
||||
@body(entry.key, entry.value);
|
||||
};
|
||||
}
|
||||
|
||||
macro LinkedHashMap.@each_entry(map; @body(entry))
|
||||
{
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
@body(entry);
|
||||
entry = entry.after;
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] LinkedHashMap.tvalues(&map) => map.values(tmem) @inline;
|
||||
|
||||
fn Value[] LinkedHashMap.values(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, self.count);
|
||||
usz index = 0;
|
||||
LinkedEntry* entry = self.head;
|
||||
while (entry)
|
||||
{
|
||||
list[index++] = entry.value;
|
||||
entry = entry.after;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
fn bool LinkedHashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
|
||||
{
|
||||
if (!map.count) return false;
|
||||
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
if (equals(v, entry.value)) return true;
|
||||
entry = entry.after;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn LinkedHashMapIterator LinkedHashMap.iter(&self) => { .map = self, .current = self.head, .started = false };
|
||||
|
||||
fn LinkedHashMapValueIterator LinkedHashMap.value_iter(&self) => { .map = self, .current = self.head, .started = false };
|
||||
|
||||
fn LinkedHashMapKeyIterator LinkedHashMap.key_iter(&self) => { .map = self, .current = self.head, .started = false };
|
||||
|
||||
fn bool LinkedHashMapIterator.next(&self)
|
||||
{
|
||||
if (!self.started)
|
||||
{
|
||||
self.current = self.map.head;
|
||||
self.started = true;
|
||||
}
|
||||
else if (self.current)
|
||||
{
|
||||
self.current = self.current.after;
|
||||
}
|
||||
return self.current != null;
|
||||
}
|
||||
|
||||
fn LinkedEntry*? LinkedHashMapIterator.get(&self)
|
||||
{
|
||||
return self.current ? self.current : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn Value*? LinkedHashMapValueIterator.get(&self)
|
||||
{
|
||||
return self.current ? &self.current.value : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn Key*? LinkedHashMapKeyIterator.get(&self)
|
||||
{
|
||||
return self.current ? &self.current.key : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool LinkedHashMapIterator.has_next(&self)
|
||||
{
|
||||
if (!self.started) return self.map.head != null;
|
||||
return self.current && self.current.after != null;
|
||||
}
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
|
||||
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.key = key,
|
||||
.value = value,
|
||||
.next = map.table[bucket_index],
|
||||
.before = map.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
// Update bucket chain
|
||||
map.table[bucket_index] = entry;
|
||||
|
||||
// Update linked list
|
||||
if (map.tail)
|
||||
{
|
||||
map.tail.after = entry;
|
||||
entry.before = map.tail;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.head = entry;
|
||||
}
|
||||
map.tail = entry;
|
||||
|
||||
if (map.count++ >= map.threshold)
|
||||
{
|
||||
map.resize(map.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.resize(&map, uint new_capacity) @private
|
||||
{
|
||||
LinkedEntry*[] old_table = map.table;
|
||||
uint old_capacity = old_table.len;
|
||||
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
map.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedEntry*[] new_table = allocator::new_array(map.allocator, LinkedEntry*, new_capacity);
|
||||
map.table = new_table;
|
||||
map.threshold = (uint)(new_capacity * map.load_factor);
|
||||
|
||||
// Rehash all entries - linked list order remains unchanged
|
||||
foreach (uint i, LinkedEntry *e : old_table)
|
||||
{
|
||||
if (!e) continue;
|
||||
|
||||
// Split the bucket chain into two chains based on new bit
|
||||
LinkedEntry* lo_head = null;
|
||||
LinkedEntry* lo_tail = null;
|
||||
LinkedEntry* hi_head = null;
|
||||
LinkedEntry* hi_tail = null;
|
||||
|
||||
do
|
||||
{
|
||||
LinkedEntry* next = e.next;
|
||||
if ((e.hash & old_capacity) == 0)
|
||||
{
|
||||
if (!lo_tail)
|
||||
{
|
||||
lo_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
lo_tail.next = e;
|
||||
}
|
||||
lo_tail = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!hi_tail)
|
||||
{
|
||||
hi_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
hi_tail.next = e;
|
||||
}
|
||||
hi_tail = e;
|
||||
}
|
||||
e.next = null;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
|
||||
if (lo_tail)
|
||||
{
|
||||
lo_tail.next = null;
|
||||
new_table[i] = lo_head;
|
||||
}
|
||||
if (hi_tail)
|
||||
{
|
||||
hi_tail.next = null;
|
||||
new_table[i + old_capacity] = hi_head;
|
||||
}
|
||||
}
|
||||
|
||||
map.free_internal(old_table.ptr);
|
||||
}
|
||||
|
||||
fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len;
|
||||
len += f.print("{ ")!;
|
||||
self.@each_entry(; LinkedEntry* entry)
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s: %s", entry.key, entry.value)!;
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.transfer(&map, LinkedEntry*[] new_table) @private
|
||||
{
|
||||
LinkedEntry*[] src = map.table;
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, LinkedEntry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
LinkedEntry* 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 LinkedHashMap.put_all_for_create(&map, LinkedHashMap* other_map) @private
|
||||
{
|
||||
if (!other_map.count) return;
|
||||
other_map.@each(; Key key, Value value) {
|
||||
map.set(key, value);
|
||||
};
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.put_for_create(&map, Key key, Value value) @private
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
for (LinkedEntry *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 LinkedHashMap.free_internal(&map, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(map.allocator, ptr);
|
||||
}
|
||||
|
||||
fn bool LinkedHashMap.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);
|
||||
LinkedEntry* prev = null;
|
||||
LinkedEntry* e = map.table[i];
|
||||
|
||||
while (e)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
if (prev)
|
||||
{
|
||||
prev.next = e.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.table[i] = e.next;
|
||||
}
|
||||
|
||||
if (e.before)
|
||||
{
|
||||
e.before.after = e.after;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.head = e.after;
|
||||
}
|
||||
|
||||
if (e.after)
|
||||
{
|
||||
e.after.before = e.before;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.tail = e.before;
|
||||
}
|
||||
|
||||
map.count--;
|
||||
map.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = e.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
|
||||
{
|
||||
LinkedEntry *e = map.table[bucket_index];
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
|
||||
map.table[bucket_index] = entry;
|
||||
map.count++;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.free_entry(&self, LinkedEntry *entry) @local
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
allocator::free(self.allocator, entry.key);
|
||||
$endif
|
||||
self.free_internal(entry);
|
||||
}
|
||||
|
||||
|
||||
struct LinkedHashMapIterator
|
||||
{
|
||||
LinkedHashMap* map;
|
||||
LinkedEntry* current;
|
||||
bool started;
|
||||
}
|
||||
|
||||
typedef LinkedHashMapValueIterator = inline LinkedHashMapIterator;
|
||||
typedef LinkedHashMapKeyIterator = inline LinkedHashMapIterator;
|
||||
|
||||
fn usz LinkedHashMapValueIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz LinkedHashMapKeyIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz LinkedHashMapIterator.len(self) @operator(len) => self.map.count;
|
||||
|
||||
int dummy @local;
|
||||
@@ -1,730 +0,0 @@
|
||||
<*
|
||||
@require $defined((Value){}.hash()) : `No .hash function found on the value`
|
||||
*>
|
||||
module std::collections::set <Value>;
|
||||
import std::math;
|
||||
import std::io @norecurse;
|
||||
|
||||
const LinkedHashSet LINKEDONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
|
||||
|
||||
struct LinkedEntry
|
||||
{
|
||||
uint hash;
|
||||
Value value;
|
||||
<* For bucket chain *>
|
||||
LinkedEntry* next;
|
||||
<* Previous in insertion order *>
|
||||
LinkedEntry* before;
|
||||
<* Next in insertion order *>
|
||||
LinkedEntry* after;
|
||||
}
|
||||
|
||||
struct LinkedHashSet (Printable)
|
||||
{
|
||||
LinkedEntry*[] table;
|
||||
Allocator allocator;
|
||||
<* Number of elements *>
|
||||
usz count;
|
||||
<* Resize limit *>
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
<* Resize limit *>
|
||||
LinkedEntry* head;
|
||||
<* First inserted LinkedEntry *>
|
||||
LinkedEntry* tail;
|
||||
}
|
||||
|
||||
fn usz LinkedHashSet.len(&self) @operator(len) => self.count;
|
||||
|
||||
<*
|
||||
@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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
self.allocator = allocator;
|
||||
self.threshold = (usz)(capacity * load_factor);
|
||||
self.load_factor = load_factor;
|
||||
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
|
||||
|
||||
self.head = null;
|
||||
self.tail = null;
|
||||
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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.tinit(&self, usz 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 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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro LinkedHashSet* LinkedHashSet.init_with_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++:
|
||||
self.add($vaarg[$i]);
|
||||
$endfor
|
||||
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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro LinkedHashSet* LinkedHashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the LinkedHashSet"
|
||||
@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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
foreach (v : values) self.add(v);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the LinkedHashSet entries"
|
||||
@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() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_from_values(tmem, values, capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
Has this linked hash set been initialized yet?
|
||||
|
||||
@param [&in] set : "The linked hash set we are testing"
|
||||
@return "Returns true if it has been initialized, false otherwise"
|
||||
*>
|
||||
fn bool LinkedHashSet.is_initialized(&set)
|
||||
{
|
||||
return set.allocator && set.allocator.ptr != &dummy;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.init_from_set(&self, Allocator allocator, LinkedHashSet* other_set)
|
||||
{
|
||||
self.init(allocator, other_set.table.len, other_set.load_factor);
|
||||
LinkedEntry* entry = other_set.head;
|
||||
while (entry) // Save insertion order
|
||||
{
|
||||
self.put_for_create(entry.value);
|
||||
entry = entry.after;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !set.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.tinit_from_set(&set, LinkedHashSet* other_set)
|
||||
{
|
||||
return set.init_from_set(tmem, other_set) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set is empty
|
||||
|
||||
@return "true if it is empty"
|
||||
@pure
|
||||
*>
|
||||
fn bool LinkedHashSet.is_empty(&set) @inline
|
||||
{
|
||||
return !set.count;
|
||||
}
|
||||
|
||||
<*
|
||||
Add all elements in the slice to the set.
|
||||
|
||||
@param [in] list
|
||||
@return "The number of new elements added"
|
||||
@ensure total <= list.len
|
||||
*>
|
||||
fn usz LinkedHashSet.add_all(&set, Value[] list)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : list)
|
||||
{
|
||||
if (set.add(v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other
|
||||
@return "The number of new elements added"
|
||||
@ensure return <= other.count
|
||||
*>
|
||||
fn usz LinkedHashSet.add_all_from(&set, LinkedHashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (set.add(value)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to add"
|
||||
@return "true if the value didn't exist in the set"
|
||||
*>
|
||||
fn bool LinkedHashSet.add(&set, Value value)
|
||||
{
|
||||
// If the set isn't initialized, use the defaults to initialize it.
|
||||
switch (set.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
set.init(mem);
|
||||
case null:
|
||||
set.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint hash = rehash(value.hash());
|
||||
uint index = index_for(hash, set.table.len);
|
||||
for (LinkedEntry *e = set.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return false;
|
||||
}
|
||||
set.add_entry(hash, value, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Iterate over all the values in the set
|
||||
*>
|
||||
macro LinkedHashSet.@each(set; @body(value))
|
||||
{
|
||||
if (!set.count) return;
|
||||
LinkedEntry* entry = set.head;
|
||||
while (entry)
|
||||
{
|
||||
@body(entry.value);
|
||||
entry = entry.after;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set contains the given value.
|
||||
|
||||
@param value : "The value to check"
|
||||
@return "true if it exists in the set"
|
||||
*>
|
||||
fn bool LinkedHashSet.contains(&set, Value value)
|
||||
{
|
||||
if (!set.count) return false;
|
||||
uint hash = rehash(value.hash());
|
||||
for (LinkedEntry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
Remove a single value from the set.
|
||||
|
||||
@param value : "The value to remove"
|
||||
@return? NOT_FOUND : "If the entry is not found"
|
||||
*>
|
||||
fn void? LinkedHashSet.remove(&set, Value value) @maydiscard
|
||||
{
|
||||
if (!set.remove_entry_for_value(value)) return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz LinkedHashSet.remove_all(&set, Value[] values)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : values)
|
||||
{
|
||||
if (set.remove_entry_for_value(v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other : "Other set"
|
||||
*>
|
||||
fn usz LinkedHashSet.remove_all_from(&set, LinkedHashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value val)
|
||||
{
|
||||
if (set.remove_entry_for_value(val)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
Free all memory allocated by the hash set.
|
||||
*>
|
||||
fn void LinkedHashSet.free(&set)
|
||||
{
|
||||
if (!set.is_initialized()) return;
|
||||
set.clear();
|
||||
set.free_internal(set.table.ptr);
|
||||
set.table = {};
|
||||
}
|
||||
|
||||
<*
|
||||
Clear all elements from the set while keeping the underlying storage
|
||||
|
||||
@ensure set.count == 0
|
||||
*>
|
||||
fn void LinkedHashSet.clear(&set)
|
||||
{
|
||||
if (!set.count) return;
|
||||
|
||||
LinkedEntry* entry = set.head;
|
||||
while (entry)
|
||||
{
|
||||
LinkedEntry* next = entry.after;
|
||||
set.free_entry(entry);
|
||||
entry = next;
|
||||
}
|
||||
|
||||
foreach (LinkedEntry** &bucket : set.table)
|
||||
{
|
||||
*bucket = null;
|
||||
}
|
||||
|
||||
set.count = 0;
|
||||
set.head = null;
|
||||
set.tail = null;
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.reserve(&set, usz capacity)
|
||||
{
|
||||
if (capacity > set.threshold)
|
||||
{
|
||||
set.resize(math::next_power_of_2(capacity));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Set Operations ---
|
||||
|
||||
<*
|
||||
Returns the union of two sets (A | B)
|
||||
|
||||
@param [&in] other : "The other set to union with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the union of both sets"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.set_union(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
usz new_capacity = math::next_power_of_2(self.count + other.count);
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, new_capacity, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
result.add_all_from(other);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tset_union(&self, LinkedHashSet* other) => self.set_union(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Returns the intersection of the two sets (A & B)
|
||||
|
||||
@param [&in] other : "The other set to intersect with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the intersection of both sets"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.intersection(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
|
||||
|
||||
// Iterate through the smaller set for efficiency
|
||||
LinkedHashSet* smaller = self.count <= other.count ? self : other;
|
||||
LinkedHashSet* larger = self.count > other.count ? self : other;
|
||||
|
||||
smaller.@each(;Value value)
|
||||
{
|
||||
if (larger.contains(value)) result.add(value);
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tintersection(&self, LinkedHashSet* other) => self.intersection(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Return this set - other, so (A & ~B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set but not in the other"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.difference(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
self.@each(;Value value)
|
||||
{
|
||||
if (!other.contains(value))
|
||||
{
|
||||
result.add(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tdifference(&self, LinkedHashSet* other) => self.difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Return (A ^ B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set or the other, but not both"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.symmetric_difference(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (!result.add(value))
|
||||
{
|
||||
result.remove(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tsymmetric_difference(&self, LinkedHashSet* other) => self.symmetric_difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Check if this hash set is a subset of another set.
|
||||
|
||||
@param [&in] other : "The other set to check against"
|
||||
@return "True if all elements of this set are in the other set"
|
||||
*>
|
||||
fn bool LinkedHashSet.is_subset(&self, LinkedHashSet* other)
|
||||
{
|
||||
if (self.count == 0) return true;
|
||||
if (self.count > other.count) return false;
|
||||
|
||||
self.@each(; Value value) {
|
||||
if (!other.contains(value)) return false;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void LinkedHashSet.add_entry(&set, uint hash, Value value, uint bucket_index) @private
|
||||
{
|
||||
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.value = value,
|
||||
.next = set.table[bucket_index],
|
||||
.before = set.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
// Update bucket chain
|
||||
set.table[bucket_index] = entry;
|
||||
|
||||
// Update linked list
|
||||
if (set.tail)
|
||||
{
|
||||
set.tail.after = entry;
|
||||
entry.before = set.tail;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.head = entry;
|
||||
}
|
||||
set.tail = entry;
|
||||
|
||||
if (set.count++ >= set.threshold)
|
||||
{
|
||||
set.resize(set.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.resize(&set, usz new_capacity) @private
|
||||
{
|
||||
LinkedEntry*[] old_table = set.table;
|
||||
usz old_capacity = old_table.len;
|
||||
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
set.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedEntry*[] new_table = allocator::new_array(set.allocator, LinkedEntry*, new_capacity);
|
||||
set.table = new_table;
|
||||
set.threshold = (uint)(new_capacity * set.load_factor);
|
||||
|
||||
// Rehash all entries - linked list order remains unchanged
|
||||
foreach (uint i, LinkedEntry *e : old_table)
|
||||
{
|
||||
if (!e) continue;
|
||||
|
||||
// Split the bucket chain into two chains based on new bit
|
||||
LinkedEntry* lo_head = null;
|
||||
LinkedEntry* lo_tail = null;
|
||||
LinkedEntry* hi_head = null;
|
||||
LinkedEntry* hi_tail = null;
|
||||
|
||||
do
|
||||
{
|
||||
LinkedEntry* next = e.next;
|
||||
if ((e.hash & old_capacity) == 0)
|
||||
{
|
||||
if (!lo_tail)
|
||||
{
|
||||
lo_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
lo_tail.next = e;
|
||||
}
|
||||
lo_tail = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!hi_tail)
|
||||
{
|
||||
hi_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
hi_tail.next = e;
|
||||
}
|
||||
hi_tail = e;
|
||||
}
|
||||
e.next = null;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
|
||||
if (lo_tail)
|
||||
{
|
||||
lo_tail.next = null;
|
||||
new_table[i] = lo_head;
|
||||
}
|
||||
if (hi_tail)
|
||||
{
|
||||
hi_tail.next = null;
|
||||
new_table[i + old_capacity] = hi_head;
|
||||
}
|
||||
}
|
||||
|
||||
set.free_internal(old_table.ptr);
|
||||
}
|
||||
|
||||
fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len;
|
||||
len += f.print("{ ")!;
|
||||
self.@each(; Value value)
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s", value)!;
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.transfer(&set, LinkedEntry*[] new_table) @private
|
||||
{
|
||||
LinkedEntry*[] src = set.table;
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, LinkedEntry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
LinkedEntry* 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 LinkedHashSet.put_for_create(&set, Value value) @private
|
||||
{
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
for (LinkedEntry *e = set.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
// Value already exists, no need to do anything
|
||||
return;
|
||||
}
|
||||
}
|
||||
set.create_entry(hash, value, i);
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.free_internal(&set, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(set.allocator, ptr);
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
|
||||
{
|
||||
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.value = value,
|
||||
.next = set.table[bucket_index],
|
||||
.before = set.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
set.table[bucket_index] = entry;
|
||||
|
||||
// Update linked list
|
||||
if (set.tail)
|
||||
{
|
||||
set.tail.after = entry;
|
||||
entry.before = set.tail;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.head = entry;
|
||||
}
|
||||
set.tail = entry;
|
||||
|
||||
set.count++;
|
||||
}
|
||||
|
||||
fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
|
||||
{
|
||||
if (!set.count) return false;
|
||||
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
LinkedEntry* prev = null;
|
||||
LinkedEntry* e = set.table[i];
|
||||
|
||||
while (e)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
if (prev)
|
||||
{
|
||||
prev.next = e.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.table[i] = e.next;
|
||||
}
|
||||
|
||||
if (e.before)
|
||||
{
|
||||
e.before.after = e.after;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.head = e.after;
|
||||
}
|
||||
|
||||
if (e.after)
|
||||
{
|
||||
e.after.before = e.before;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.tail = e.before;
|
||||
}
|
||||
|
||||
set.count--;
|
||||
set.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = e.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.free_entry(&set, LinkedEntry *entry) @private
|
||||
{
|
||||
allocator::free(set.allocator, entry);
|
||||
}
|
||||
|
||||
struct LinkedHashSetIterator
|
||||
{
|
||||
LinkedHashSet* set;
|
||||
LinkedEntry* current;
|
||||
bool started;
|
||||
}
|
||||
|
||||
fn LinkedHashSetIterator LinkedHashSet.iter(&set) => { .set = set, .current = set.head, .started = false };
|
||||
|
||||
fn bool LinkedHashSetIterator.next(&self)
|
||||
{
|
||||
if (!self.started)
|
||||
{
|
||||
self.current = self.set.head;
|
||||
self.started = true;
|
||||
}
|
||||
else if (self.current)
|
||||
{
|
||||
self.current = self.current.after;
|
||||
}
|
||||
return self.current != null;
|
||||
}
|
||||
|
||||
fn Value*? LinkedHashSetIterator.get(&self)
|
||||
{
|
||||
return self.current ? &self.current.value : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool LinkedHashSetIterator.has_next(&self)
|
||||
{
|
||||
if (!self.started) return self.set.head != null;
|
||||
return self.current && self.current.after != null;
|
||||
}
|
||||
|
||||
fn usz LinkedHashSetIterator.len(&self) @operator(len)
|
||||
{
|
||||
return self.set.count;
|
||||
}
|
||||
|
||||
int dummy @local;
|
||||
@@ -1,81 +1,62 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021 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>;
|
||||
import std::io;
|
||||
module std::collections::linkedlist(<Type>);
|
||||
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
|
||||
struct Node
|
||||
struct Node @private
|
||||
{
|
||||
Node* next;
|
||||
Node* prev;
|
||||
Node *next;
|
||||
Node *prev;
|
||||
Type value;
|
||||
}
|
||||
|
||||
struct LinkedList
|
||||
{
|
||||
Allocator allocator;
|
||||
Allocator *allocator;
|
||||
usz size;
|
||||
Node* _first;
|
||||
Node* _last;
|
||||
Node *_first;
|
||||
Node *_last;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.to_format(&self, Formatter* f) @dynamic
|
||||
fn void LinkedList.push(&self, Type value)
|
||||
{
|
||||
usz len = f.print("{ ")!;
|
||||
for (Node* node = self._first; node != null; node = node.next)
|
||||
{
|
||||
len += f.printf(node.next ? "%s, " : "%s", node.value)!;
|
||||
}
|
||||
return len + f.print(" }");
|
||||
self.link_first(value);
|
||||
}
|
||||
|
||||
macro LinkedList @new(Allocator allocator, Type[] #default_values = {})
|
||||
fn void LinkedList.push_last(&self, Type value)
|
||||
{
|
||||
LinkedList new_list;
|
||||
new_list.init(allocator);
|
||||
new_list.push_all(#default_values);
|
||||
return new_list;
|
||||
self.link_last(value);
|
||||
}
|
||||
|
||||
macro LinkedList @tnew(Type[] #default_values = {})
|
||||
{
|
||||
return @new(tmem, #default_values);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use, defaults to the heap allocator"
|
||||
@return "the initialized list"
|
||||
*>
|
||||
fn LinkedList* LinkedList.init(&self, Allocator allocator)
|
||||
/**
|
||||
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
* @return "the initialized list"
|
||||
**/
|
||||
fn LinkedList* LinkedList.init_new(&self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
*self = { .allocator = allocator };
|
||||
return self;
|
||||
}
|
||||
|
||||
fn LinkedList* LinkedList.tinit(&self)
|
||||
fn LinkedList* LinkedList.init_temp(&self)
|
||||
{
|
||||
return self.init(tmem) @inline;
|
||||
return self.init_new(mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn bool LinkedList.is_initialized(&self) @inline => self.allocator != null;
|
||||
|
||||
<*
|
||||
@require self.is_initialized()
|
||||
*>
|
||||
/**
|
||||
* @require self.allocator
|
||||
**/
|
||||
macro void LinkedList.free_node(&self, Node* node) @private
|
||||
{
|
||||
allocator::free(self.allocator, node);
|
||||
self.allocator.free(node);
|
||||
}
|
||||
|
||||
macro Node* LinkedList.alloc_node(&self) @private
|
||||
{
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
return allocator::alloc(self.allocator, Node);
|
||||
if (!self.allocator) self.allocator = mem::heap();
|
||||
return self.allocator.new(Node);
|
||||
}
|
||||
|
||||
fn void LinkedList.push_front(&self, Type value)
|
||||
fn void LinkedList.link_first(&self, Type value) @private
|
||||
{
|
||||
Node *first = self._first;
|
||||
Node *new_node = self.alloc_node();
|
||||
@@ -92,12 +73,7 @@ fn void LinkedList.push_front(&self, Type value)
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn void LinkedList.push_front_all(&self, Type[] value)
|
||||
{
|
||||
foreach_r (v : value) self.push_front(v);
|
||||
}
|
||||
|
||||
fn void LinkedList.push(&self, Type value)
|
||||
fn void LinkedList.link_last(&self, Type value) @private
|
||||
{
|
||||
Node *last = self._last;
|
||||
Node *new_node = self.alloc_node();
|
||||
@@ -114,23 +90,18 @@ fn void LinkedList.push(&self, Type value)
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn void LinkedList.push_all(&self, Type[] value)
|
||||
{
|
||||
foreach (v : value) self.push(v);
|
||||
}
|
||||
fn Type! LinkedList.peek(&self) => self.first() @inline;
|
||||
fn Type! LinkedList.peek_last(&self) => self.last() @inline;
|
||||
|
||||
fn Type? LinkedList.peek(&self) => self.first() @inline;
|
||||
fn Type? LinkedList.peek_last(&self) => self.last() @inline;
|
||||
|
||||
fn Type? LinkedList.first(&self)
|
||||
fn Type! LinkedList.first(&self)
|
||||
{
|
||||
if (!self._first) return NO_MORE_ELEMENT~;
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn Type? LinkedList.last(&self)
|
||||
fn Type! LinkedList.last(&self)
|
||||
{
|
||||
if (!self._last) return NO_MORE_ELEMENT~;
|
||||
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self._last.value;
|
||||
}
|
||||
|
||||
@@ -151,9 +122,9 @@ fn void LinkedList.clear(&self)
|
||||
|
||||
fn usz LinkedList.len(&self) @inline => self.size;
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
/**
|
||||
* @require index < self.size
|
||||
**/
|
||||
macro Node* LinkedList.node_at_index(&self, usz index)
|
||||
{
|
||||
if (index * 2 >= self.size)
|
||||
@@ -167,77 +138,48 @@ macro Node* LinkedList.node_at_index(&self, usz index)
|
||||
while (index--) node = node.next;
|
||||
return node;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
/**
|
||||
* @require index < self.size
|
||||
**/
|
||||
fn Type LinkedList.get(&self, usz index)
|
||||
{
|
||||
return self.node_at_index(index).value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn Type* LinkedList.get_ref(&self, usz index)
|
||||
{
|
||||
return &self.node_at_index(index).value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
/**
|
||||
* @require index < self.size
|
||||
**/
|
||||
fn void LinkedList.set(&self, usz index, Type element)
|
||||
{
|
||||
self.node_at_index(index).value = element;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.index_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = self._first, usz i = 0; node != null; node = node.next, ++i)
|
||||
{
|
||||
if (node.value == t) return i;
|
||||
}
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.rindex_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = self._last, usz i = self.size - 1; node != null; node = node.prev, --i)
|
||||
{
|
||||
if (node.value == t) return i;
|
||||
if (i == 0) break;
|
||||
}
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void LinkedList.remove_at(&self, usz index)
|
||||
/**
|
||||
* @require index < self.size
|
||||
**/
|
||||
fn void LinkedList.remove(&self, usz index)
|
||||
{
|
||||
self.unlink(self.node_at_index(index));
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size
|
||||
*>
|
||||
fn void LinkedList.insert_at(&self, usz index, Type element)
|
||||
/**
|
||||
* @require index <= self.size
|
||||
**/
|
||||
fn void LinkedList.insert(&self, usz index, Type element)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
self.push_front(element);
|
||||
case self.size:
|
||||
self.push(element);
|
||||
case self.size:
|
||||
self.push_last(element);
|
||||
default:
|
||||
self.link_before(self.node_at_index(index), element);
|
||||
}
|
||||
}
|
||||
<*
|
||||
@require succ != null
|
||||
*>
|
||||
/**
|
||||
* @require succ != null
|
||||
**/
|
||||
fn void LinkedList.link_before(&self, Node *succ, Type value) @private
|
||||
{
|
||||
Node* pred = succ.prev;
|
||||
@@ -255,9 +197,9 @@ fn void LinkedList.link_before(&self, Node *succ, Type value) @private
|
||||
self.size++;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self._first != null
|
||||
*>
|
||||
/**
|
||||
* @require self._first
|
||||
**/
|
||||
fn void LinkedList.unlink_first(&self) @private
|
||||
{
|
||||
Node* f = self._first;
|
||||
@@ -275,58 +217,7 @@ fn void LinkedList.unlink_first(&self) @private
|
||||
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)
|
||||
fn bool LinkedList.remove_value(&self, Type t)
|
||||
{
|
||||
for (Node* node = self._first; node != null; node = node.next)
|
||||
{
|
||||
@@ -339,7 +230,7 @@ fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
return false;
|
||||
}
|
||||
|
||||
fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
fn bool LinkedList.remove_last_value(&self, Type t)
|
||||
{
|
||||
for (Node* node = self._last; node != null; node = node.prev)
|
||||
{
|
||||
@@ -351,9 +242,29 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
<*
|
||||
@require self._last != null
|
||||
*>
|
||||
|
||||
fn Type! LinkedList.pop(&self)
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.unlink_first();
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn void! LinkedList.remove_last(&self)
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.unlink_last();
|
||||
}
|
||||
|
||||
fn void! LinkedList.remove_first(&self)
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.unlink_first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @require self._last
|
||||
**/
|
||||
fn void LinkedList.unlink_last(&self) @inline @private
|
||||
{
|
||||
Node* l = self._last;
|
||||
@@ -371,9 +282,9 @@ fn void LinkedList.unlink_last(&self) @inline @private
|
||||
self.size--;
|
||||
}
|
||||
|
||||
<*
|
||||
@require x != null
|
||||
*>
|
||||
/**
|
||||
* @require x != null
|
||||
**/
|
||||
fn void LinkedList.unlink(&self, Node* x) @private
|
||||
{
|
||||
Node* next = x.next;
|
||||
@@ -397,69 +308,3 @@ fn void LinkedList.unlink(&self, Node* x) @private
|
||||
self.free_node(x);
|
||||
self.size--;
|
||||
}
|
||||
|
||||
|
||||
macro bool LinkedList.eq(&self, other) @operator(==) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
Node* node1 = self._first;
|
||||
Node* node2 = other._first;
|
||||
while (true)
|
||||
{
|
||||
if (!node1) return node2 == null;
|
||||
if (!node2) return false;
|
||||
if (node1.value != node2.value) return false;
|
||||
node1 = node1.next;
|
||||
node2 = node2.next;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn LinkedListArrayView LinkedList.array_view(&self)
|
||||
{
|
||||
return { .list = self, .current_node = self._first };
|
||||
}
|
||||
|
||||
|
||||
struct LinkedListArrayView
|
||||
{
|
||||
LinkedList* list;
|
||||
Node* current_node;
|
||||
usz current_index;
|
||||
}
|
||||
|
||||
fn usz LinkedListArrayView.len(&self) @operator(len) => self.list.size;
|
||||
|
||||
<*
|
||||
@require index < self.list.size
|
||||
*>
|
||||
fn Type LinkedListArrayView.get(&self, usz index) @operator([])
|
||||
{
|
||||
return *self.get_ref(index);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.list.size
|
||||
*>
|
||||
fn Type* LinkedListArrayView.get_ref(&self, usz index) @operator(&[])
|
||||
{
|
||||
if (index == self.list.size - 1)
|
||||
{
|
||||
self.current_node = self.list._last;
|
||||
self.current_index = index;
|
||||
}
|
||||
|
||||
while (self.current_index != index)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case index < self.current_index: // reverse iteration
|
||||
self.current_node = self.current_node.prev;
|
||||
self.current_index--;
|
||||
case index > self.current_index:
|
||||
self.current_node = self.current_node.next;
|
||||
self.current_index++;
|
||||
}
|
||||
}
|
||||
|
||||
return &self.current_node.value;
|
||||
}
|
||||
|
||||
@@ -1,93 +1,66 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021 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;
|
||||
module std::collections::list(<Type>);
|
||||
import std::io;
|
||||
import std::math;
|
||||
|
||||
alias ElementPredicate = fn bool(Type *type);
|
||||
alias ElementTest = fn bool(Type *type, any context);
|
||||
def ElementPredicate = fn bool(Type *type);
|
||||
def ElementTest = fn bool(Type *type, any* context);
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
|
||||
|
||||
const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
|
||||
|
||||
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR };
|
||||
|
||||
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct List (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
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)
|
||||
/**
|
||||
* @param initial_capacity "The initial capacity to reserve"
|
||||
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
**/
|
||||
fn List* List.init_new(&self, usz initial_capacity = 16, Allocator* allocator = mem::heap())
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
self.capacity = 0;
|
||||
self.entries = null;
|
||||
self.reserve(initial_capacity);
|
||||
if (initial_capacity > 0)
|
||||
{
|
||||
initial_capacity = math::next_power_of_2(initial_capacity);
|
||||
self.entries = allocator.alloc_aligned(Type.sizeof * initial_capacity, .alignment = Type[1].alignof)!!;
|
||||
}
|
||||
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 List* List.tinit(&self, usz initial_capacity = 16)
|
||||
/**
|
||||
* Initialize the list using the temp allocator.
|
||||
*
|
||||
* @param initial_capacity "The initial capacity to reserve"
|
||||
**/
|
||||
fn List* List.init_temp(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
return self.init_new(initial_capacity, mem::temp()) @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.push_all(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.push_all(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)
|
||||
/**
|
||||
* @require self.size == 0 "The List must be empty"
|
||||
**/
|
||||
fn void List.init_wrapping_array(&self, Type[] types, Allocator* allocator = mem::heap())
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = types.len;
|
||||
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
|
||||
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
@@ -107,85 +80,98 @@ fn usz? List.to_format(&self, Formatter* formatter) @dynamic
|
||||
}
|
||||
}
|
||||
|
||||
fn void List.push(&self, Type element) @inline
|
||||
fn String List.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
self.reserve(1);
|
||||
self.entries[self.set_size(self.size + 1)] = element;
|
||||
return string::new_format("%s", *self, .allocator = allocator);
|
||||
}
|
||||
|
||||
fn Type? List.pop(&self)
|
||||
fn String List.to_tstring(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.set_size(self.size - 1);
|
||||
return self.entries[self.size - 1];
|
||||
return string::tformat("%s", *self);
|
||||
}
|
||||
|
||||
fn void List.push(&self, Type element) @inline
|
||||
{
|
||||
self.append(element);
|
||||
}
|
||||
|
||||
fn void List.append(&self, Type element)
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require self.size > 0
|
||||
**/
|
||||
fn Type List.pop(&self)
|
||||
{
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
fn void List.clear(&self)
|
||||
{
|
||||
self.set_size(0);
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
fn Type? List.pop_first(&self)
|
||||
/**
|
||||
* @require self.size > 0
|
||||
**/
|
||||
fn Type List.pop_first(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
Type value = self.entries[0];
|
||||
self.remove_at(0);
|
||||
return value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size : `Removed element out of bounds`
|
||||
*>
|
||||
/**
|
||||
* @require index < self.size
|
||||
**/
|
||||
fn void List.remove_at(&self, usz index)
|
||||
{
|
||||
usz new_size = self.size - 1;
|
||||
defer self.set_size(new_size);
|
||||
if (!new_size || index == new_size) return;
|
||||
self.entries[index .. new_size - 1] = self.entries[index + 1 .. new_size];
|
||||
for (usz i = index + 1; i < self.size; i++)
|
||||
{
|
||||
self.entries[i - 1] = self.entries[i];
|
||||
}
|
||||
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;
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
fn Type[] List.to_aligned_array(&self, Allocator allocator)
|
||||
fn Type[] List.to_new_array(&self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
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);
|
||||
if (!self.size) return Type[] {};
|
||||
Type[] result = allocator.new_array(Type, self.size);
|
||||
result[..] = self.entries[:self.size];
|
||||
return result;
|
||||
}
|
||||
|
||||
fn Type[] List.to_tarray(&self)
|
||||
{
|
||||
$if type_is_overaligned():
|
||||
return self.to_aligned_array(tmem);
|
||||
$else
|
||||
return self.to_array(tmem);
|
||||
$endif;
|
||||
return self.to_new_array(mem::temp());
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the elements in a list.
|
||||
*>
|
||||
/**
|
||||
* Reverse the elements in a list.
|
||||
**/
|
||||
fn void List.reverse(&self)
|
||||
{
|
||||
list_common::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]);
|
||||
}
|
||||
}
|
||||
|
||||
fn Type[] List.array_view(&self)
|
||||
@@ -193,32 +179,14 @@ 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) @deprecated("Use push_all")
|
||||
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[..];
|
||||
}
|
||||
|
||||
<*
|
||||
Add the values of an array to this list.
|
||||
|
||||
@param [in] array
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void List.push_all(&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[..];
|
||||
foreach (&value : array)
|
||||
{
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
fn void List.push_front(&self, Type type) @inline
|
||||
@@ -226,50 +194,52 @@ fn void List.push_front(&self, Type type) @inline
|
||||
self.insert_at(0, type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size : `Insert was out of bounds`
|
||||
*>
|
||||
/**
|
||||
* @require index < self.size
|
||||
**/
|
||||
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.ensure_capacity();
|
||||
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
|
||||
*>
|
||||
/**
|
||||
* @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
|
||||
/**
|
||||
* @require self.size > 0
|
||||
**/
|
||||
fn void List.remove_last(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
self.set_size(self.size - 1);
|
||||
self.size--;
|
||||
}
|
||||
|
||||
fn void? List.remove_first(&self) @maydiscard
|
||||
/**
|
||||
* @require self.size > 0
|
||||
**/
|
||||
fn void List.remove_first(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type? List.first(&self)
|
||||
fn Type* List.first(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[0];
|
||||
return self.size ? &self.entries[0] : null;
|
||||
}
|
||||
|
||||
fn Type? List.last(&self)
|
||||
fn Type* List.last(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[self.size - 1];
|
||||
return self.size ? &self.entries[self.size - 1] : null;
|
||||
}
|
||||
|
||||
fn bool List.is_empty(&self) @inline
|
||||
@@ -277,19 +247,11 @@ 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];
|
||||
@@ -297,121 +259,125 @@ fn Type List.get(&self, usz index) @inline
|
||||
|
||||
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;
|
||||
if (!self.allocator) return;
|
||||
self.allocator.free_aligned(self.entries);
|
||||
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"
|
||||
*>
|
||||
/**
|
||||
* @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);
|
||||
return self._remove_if(filter, false);
|
||||
}
|
||||
|
||||
<*
|
||||
@param selection : "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
/**
|
||||
* @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);
|
||||
return self._remove_if(selection, true);
|
||||
}
|
||||
|
||||
fn usz List.remove_using_test(&self, ElementTest filter, any context)
|
||||
macro usz List._remove_if(&self, ElementPredicate filter, bool $invert) @local
|
||||
{
|
||||
usz old_size = self.size;
|
||||
defer
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
// 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 list_common::list_remove_using_test(self, filter, false, context);
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn usz List.retain_using_test(&self, ElementTest filter, any context)
|
||||
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, true, context);
|
||||
return self._remove_using_test(filter, false, context);
|
||||
}
|
||||
|
||||
fn void List.ensure_capacity(&self, usz min_capacity) @local
|
||||
fn usz List.retain_using_test(&self, ElementTest filter, any* context)
|
||||
{
|
||||
return self._remove_using_test(filter, true, context);
|
||||
}
|
||||
|
||||
macro usz List._remove_using_test(&self, ElementTest 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;
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserve at least min_capacity
|
||||
**/
|
||||
fn void List.reserve(&self, usz min_capacity)
|
||||
{
|
||||
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
|
||||
|
||||
if (!self.allocator) self.allocator = mem::heap();
|
||||
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.entries = self.allocator.realloc_aligned(self.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof) ?? null;
|
||||
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)
|
||||
fn void List.ensure_capacity(&self, usz added = 1) @inline @private
|
||||
{
|
||||
usz new_size = self.size + added;
|
||||
if (self.capacity >= new_size) return;
|
||||
@@ -419,64 +385,28 @@ fn void List.reserve(&self, usz added)
|
||||
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);
|
||||
self.reserve(new_capacity);
|
||||
}
|
||||
|
||||
// Functions for equatable types
|
||||
|
||||
|
||||
fn usz? List.index_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE)
|
||||
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~;
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn usz? List.rindex_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE)
|
||||
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~;
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -489,13 +419,13 @@ fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
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"
|
||||
*>
|
||||
/**
|
||||
* 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)
|
||||
@@ -505,56 +435,37 @@ fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
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)
|
||||
/**
|
||||
* @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(&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
|
||||
usz size = self.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
if (!equals(self.entries[i - 1], value)) continue;
|
||||
for (usz j = i; j < size; j++)
|
||||
{
|
||||
self.entries[j - 1] = self.entries[j];
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
return list_common::list_remove_item(self, value);
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
fn void List.remove_all(&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);
|
||||
foreach (v : other_list) self.remove(v);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] self
|
||||
@return "The number non-null values in the list"
|
||||
*>
|
||||
/**
|
||||
* @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;
|
||||
@@ -564,11 +475,15 @@ fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
|
||||
|
||||
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);
|
||||
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 list_common::list_compact(self);
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
int dummy @local;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
424
lib/std/collections/map.c3
Normal file
424
lib/std/collections/map.c3
Normal file
@@ -0,0 +1,424 @@
|
||||
// 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::map(<Key, Value>);
|
||||
import std::math;
|
||||
|
||||
const uint DEFAULT_INITIAL_CAPACITY = 16;
|
||||
const uint MAXIMUM_CAPACITY = 1u << 31;
|
||||
const float DEFAULT_LOAD_FACTOR = 0.75;
|
||||
const VALUE_IS_EQUATABLE = Value.is_eq;
|
||||
const bool COPY_KEYS = types::implements_copy(Key);
|
||||
|
||||
struct HashMap
|
||||
{
|
||||
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 !map.allocator "Map was already initialized"
|
||||
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
**/
|
||||
fn HashMap* HashMap.init_new(&map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator* allocator = mem::heap())
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
map.allocator = allocator;
|
||||
map.load_factor = load_factor;
|
||||
map.threshold = (uint)(capacity * load_factor);
|
||||
map.table = allocator.new_zero_array(Entry*, capacity);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require capacity > 0 "The capacity must be 1 or higher"
|
||||
* @require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
* @require !map.allocator "Map was already initialized"
|
||||
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
**/
|
||||
fn HashMap* HashMap.init_temp(&map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return map.init_new(capacity, load_factor, mem::temp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Has this hash map been initialized yet?
|
||||
*
|
||||
* @param [&in] map "The hash map we are testing"
|
||||
* @return "Returns true if it has been initialized, false otherwise"
|
||||
**/
|
||||
fn bool HashMap.is_initialized(&map)
|
||||
{
|
||||
return (bool)map.allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] allocator "The allocator to use"
|
||||
* @param [&in] other_map "The map to copy from."
|
||||
**/
|
||||
fn HashMap* HashMap.init_new_from_map(&self, HashMap* other_map, Allocator* allocator = mem::heap())
|
||||
{
|
||||
self.init_new(other_map.table.len, other_map.load_factor, allocator);
|
||||
self.put_all_for_create(other_map);
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] other_map "The map to copy from."
|
||||
**/
|
||||
fn HashMap* HashMap.init_temp_from_map(&map, HashMap* other_map)
|
||||
{
|
||||
return map.init_new_from_map(other_map, mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn bool HashMap.is_empty(&map) @inline
|
||||
{
|
||||
return !map.count;
|
||||
}
|
||||
|
||||
fn usz HashMap.len(&map) @inline
|
||||
{
|
||||
return map.count;
|
||||
}
|
||||
|
||||
fn Value*! HashMap.get_ref(&map, Key key)
|
||||
{
|
||||
if (!map.count) return SearchResult.MISSING?;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return &e.value;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn Entry*! HashMap.get_entry(&map, Key key)
|
||||
{
|
||||
if (!map.count) return SearchResult.MISSING?;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value or update and
|
||||
**/
|
||||
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
{
|
||||
if (!map.count)
|
||||
{
|
||||
Value val = #expr;
|
||||
map.set(key, val);
|
||||
return val;
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e.value;
|
||||
}
|
||||
Value val = #expr;
|
||||
map.add_entry(hash, key, val, index);
|
||||
return val;
|
||||
}
|
||||
|
||||
fn Value! HashMap.get(&map, Key key) @operator([])
|
||||
{
|
||||
return *map.get_ref(key) @inline;
|
||||
}
|
||||
|
||||
fn bool HashMap.has_key(&map, Key key)
|
||||
{
|
||||
return @ok(map.get_ref(key));
|
||||
}
|
||||
|
||||
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
|
||||
{
|
||||
// If the map isn't initialized, use the defaults to initialize it.
|
||||
if (!map.allocator)
|
||||
{
|
||||
map.init_new();
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
e.value = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
map.add_entry(hash, key, value, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void! HashMap.remove(&map, Key key) @maydiscard
|
||||
{
|
||||
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn void HashMap.clear(&map)
|
||||
{
|
||||
if (!map.count) return;
|
||||
foreach (Entry** &entry_ref : map.table)
|
||||
{
|
||||
Entry* entry = *entry_ref;
|
||||
if (!entry) continue;
|
||||
Entry *next = entry.next;
|
||||
while (next)
|
||||
{
|
||||
Entry *to_delete = next;
|
||||
next = next.next;
|
||||
map.free_entry(to_delete);
|
||||
}
|
||||
map.free_entry(entry);
|
||||
*entry_ref = null;
|
||||
}
|
||||
map.count = 0;
|
||||
}
|
||||
|
||||
fn void HashMap.free(&map)
|
||||
{
|
||||
if (!map.allocator) return;
|
||||
map.clear();
|
||||
map.free_internal(map.table.ptr);
|
||||
map.table = {};
|
||||
}
|
||||
|
||||
fn Key[] HashMap.key_tlist(&map)
|
||||
{
|
||||
return map.key_new_list(mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn Key[] HashMap.key_new_list(&map, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (!map.count) return {};
|
||||
|
||||
Key[] list = allocator.new_array(Key, map.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
list[index++] = entry.key;
|
||||
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)
|
||||
{
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@body(entry);
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] HashMap.value_tlist(&map)
|
||||
{
|
||||
return map.value_new_list(mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn Value[] HashMap.value_new_list(&map, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (!map.count) return {};
|
||||
Value[] list = allocator.new_array(Value, map.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.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;
|
||||
}
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
|
||||
{
|
||||
Entry* entry = map.allocator.new(Entry);
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
*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 = map.allocator.new_zero_array(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 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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!e) continue;
|
||||
map.put_for_create(e.key, e.value);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
map.allocator.free(ptr);
|
||||
}
|
||||
|
||||
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
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];
|
||||
Entry* entry = map.allocator.new(Entry);
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
*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:
|
||||
self.allocator.free(entry.key);
|
||||
$endif
|
||||
self.free_internal(entry);
|
||||
}
|
||||
|
||||
struct Entry
|
||||
{
|
||||
uint hash;
|
||||
Key key;
|
||||
Value value;
|
||||
Entry* next;
|
||||
}
|
||||
@@ -1,28 +1,11 @@
|
||||
module std::collections::maybe <Type>;
|
||||
import std::io;
|
||||
module std::collections::maybe(<Type>);
|
||||
|
||||
struct Maybe (Printable)
|
||||
struct Maybe
|
||||
{
|
||||
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 };
|
||||
@@ -30,16 +13,7 @@ fn Maybe value(Type val)
|
||||
|
||||
const Maybe EMPTY = { };
|
||||
|
||||
macro Type? Maybe.get(self)
|
||||
macro Type! Maybe.get(self)
|
||||
{
|
||||
return self.has_value ? self.value : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool Maybe.equals(self, Maybe other) @operator(==) @if(types::is_equatable_type(Type))
|
||||
{
|
||||
if (self.has_value)
|
||||
{
|
||||
return other.has_value && equals(self.value, other.value);
|
||||
}
|
||||
return !other.has_value;
|
||||
return self.has_value ? self.value : SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// 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;
|
||||
import std::collections::map;
|
||||
import std::collections::list;
|
||||
import std::io;
|
||||
|
||||
const Object TRUE_OBJECT = { .b = true, .type = bool.typeid };
|
||||
const Object FALSE_OBJECT = { .b = false, .type = bool.typeid };
|
||||
@@ -11,7 +13,7 @@ const Object NULL_OBJECT = { .type = void*.typeid };
|
||||
struct Object (Printable)
|
||||
{
|
||||
typeid type;
|
||||
Allocator allocator;
|
||||
Allocator* allocator;
|
||||
union
|
||||
{
|
||||
uint128 i;
|
||||
@@ -25,7 +27,7 @@ struct Object (Printable)
|
||||
}
|
||||
|
||||
|
||||
fn usz? Object.to_format(&self, Formatter* formatter) @dynamic
|
||||
fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.type)
|
||||
{
|
||||
@@ -48,9 +50,9 @@ fn usz? Object.to_format(&self, Formatter* formatter) @dynamic
|
||||
return n;
|
||||
case ObjectInternalMap:
|
||||
usz n = formatter.printf("{")!;
|
||||
@stack_mem(1024; Allocator mem)
|
||||
@pool()
|
||||
{
|
||||
foreach (i, key : self.map.keys(mem))
|
||||
foreach (i, key : self.map.key_tlist())
|
||||
{
|
||||
if (i > 0) n += formatter.printf(",")!;
|
||||
n += formatter.printf(`"%s":`, key)!;
|
||||
@@ -63,11 +65,11 @@ fn usz? Object.to_format(&self, Formatter* formatter) @dynamic
|
||||
switch (self.type.kindof)
|
||||
{
|
||||
case SIGNED_INT:
|
||||
return formatter.printf("%d", (int128)self.i)!;
|
||||
return formatter.printf("%d", self.i)!;
|
||||
case UNSIGNED_INT:
|
||||
return formatter.printf("%d", (uint128)self.i)!;
|
||||
case FLOAT:
|
||||
return formatter.printf("%g", self.f)!;
|
||||
return formatter.printf("%d", self.f)!;
|
||||
case ENUM:
|
||||
return formatter.printf("%d", self.i)!;
|
||||
default:
|
||||
@@ -76,9 +78,11 @@ fn usz? Object.to_format(&self, Formatter* formatter) @dynamic
|
||||
}
|
||||
}
|
||||
|
||||
fn Object* new_obj(Allocator allocator)
|
||||
fn Object* new_obj(Allocator* allocator)
|
||||
{
|
||||
return allocator::new(allocator, Object, { .allocator = allocator, .type = void.typeid });
|
||||
Object* o = allocator.new(Object);
|
||||
*o = { .allocator = allocator, .type = void.typeid };
|
||||
return o;
|
||||
}
|
||||
|
||||
fn Object* new_null()
|
||||
@@ -86,24 +90,32 @@ fn Object* new_null()
|
||||
return &NULL_OBJECT;
|
||||
}
|
||||
|
||||
fn Object* new_int(int128 i, Allocator allocator)
|
||||
fn Object* new_int(int128 i, Allocator* allocator)
|
||||
{
|
||||
return allocator::new(allocator, Object, { .i = i, .allocator = allocator, .type = int128.typeid });
|
||||
Object* o = allocator.new(Object);
|
||||
*o = { .i = i, .allocator = allocator, .type = int128.typeid };
|
||||
return o;
|
||||
}
|
||||
|
||||
macro Object* new_enum(e, Allocator allocator)
|
||||
macro Object* new_enum(e, Allocator* allocator)
|
||||
{
|
||||
return allocator::new(allocator, Object, { .i = (int128)e, .allocator = allocator, .type = @typeid(e) });
|
||||
Object* o = allocator.new(Object);
|
||||
*o = { .i = (int128)e, .allocator = allocator, .type = @typeid(e) };
|
||||
return o;
|
||||
}
|
||||
|
||||
fn Object* new_float(double f, Allocator allocator)
|
||||
fn Object* new_float(double f, Allocator* allocator)
|
||||
{
|
||||
return allocator::new(allocator, Object, { .f = f, .allocator = allocator, .type = double.typeid });
|
||||
Object* o = allocator.new(Object);
|
||||
*o = { .f = f, .allocator = allocator, .type = double.typeid };
|
||||
return o;
|
||||
}
|
||||
|
||||
fn Object* new_string(String s, Allocator allocator)
|
||||
fn Object* new_string(String s, Allocator* allocator)
|
||||
{
|
||||
return allocator::new(allocator, Object, { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid });
|
||||
Object* o = allocator.new(Object);
|
||||
*o = { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid };
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +131,7 @@ fn void Object.free(&self)
|
||||
case void:
|
||||
break;
|
||||
case String:
|
||||
allocator::free(self.allocator, self.s);
|
||||
self.allocator.free(self.s);
|
||||
case ObjectInternalList:
|
||||
foreach (ol : self.array)
|
||||
{
|
||||
@@ -128,13 +140,13 @@ fn void Object.free(&self)
|
||||
self.array.free();
|
||||
case ObjectInternalMap:
|
||||
self.map.@each_entry(; ObjectInternalMapEntry* entry) {
|
||||
self.allocator.free(entry.key);
|
||||
entry.value.free();
|
||||
};
|
||||
self.map.free();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (self.allocator) allocator::free(self.allocator, self);
|
||||
if (self.allocator) self.allocator.free(self);
|
||||
}
|
||||
|
||||
fn bool Object.is_null(&self) @inline => self == &NULL_OBJECT;
|
||||
@@ -148,49 +160,50 @@ 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()
|
||||
*>
|
||||
/**
|
||||
* @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);
|
||||
self.map.init_new(.allocator = self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
/**
|
||||
* @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);
|
||||
self.array.init_new(.allocator = self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
/**
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
fn void Object.set_object(&self, String key, Object* new_object) @private
|
||||
{
|
||||
self.init_map_if_needed();
|
||||
Object*? val = self.map.get_entry(key).value;
|
||||
defer (void)val.free();
|
||||
self.map.set(key, new_object);
|
||||
ObjectInternalMapEntry*! entry = self.map.get_entry(key);
|
||||
defer
|
||||
{
|
||||
(void)self.allocator.free(entry.key);
|
||||
(void)entry.value.free();
|
||||
}
|
||||
self.map.set(key.copy(self.map.allocator), new_object);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.allocator != null : "This object is not properly initialized, was it really created using 'new'"
|
||||
@require $typeof(value) != void* ||| value == null : "void pointers cannot be stored in an object"
|
||||
*>
|
||||
macro Object* Object.object_from_value(&self, value) @private
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
$switch:
|
||||
|
||||
$switch
|
||||
$case types::is_int($Type):
|
||||
return new_int(value, self.allocator);
|
||||
$case types::is_float($Type):
|
||||
@@ -202,8 +215,9 @@ macro Object* Object.object_from_value(&self, value) @private
|
||||
$case $Type.typeid == Object*.typeid:
|
||||
return value;
|
||||
$case $Type.typeid == void*.typeid:
|
||||
if (value != null) return CastResult.TYPE_MISMATCH?;
|
||||
return &NULL_OBJECT;
|
||||
$case $defined(String x = value):
|
||||
$case $assignable(value, String):
|
||||
return new_string(value, self.allocator);
|
||||
$default:
|
||||
$error "Unsupported object type.";
|
||||
@@ -218,9 +232,9 @@ macro Object* Object.set(&self, String key, value)
|
||||
return val;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
/**
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
macro Object* Object.set_at(&self, usz index, String key, value)
|
||||
{
|
||||
Object* val = self.object_from_value(value);
|
||||
@@ -228,71 +242,72 @@ macro Object* Object.set_at(&self, usz index, String key, value)
|
||||
return val;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
@ensure return != null
|
||||
*>
|
||||
macro Object* Object.push(&self, value)
|
||||
/**
|
||||
* @require self.is_indexable()
|
||||
* @ensure return != null
|
||||
**/
|
||||
macro Object* Object.append(&self, value)
|
||||
{
|
||||
Object* val = self.object_from_value(value);
|
||||
self.push_object(val);
|
||||
self.append_object(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn Object*? Object.get(&self, String key) => self.is_empty() ? NOT_FOUND~ : self.map.get(key);
|
||||
/**
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
fn Object*! Object.get(&self, String key) => self.is_empty() ? SearchResult.MISSING? : self.map.get(key);
|
||||
|
||||
|
||||
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
/**
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
fn Object* Object.get_at(&self, usz index)
|
||||
{
|
||||
return self.array.get(index);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
/**
|
||||
* @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)
|
||||
/**
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
fn void Object.append_object(&self, Object* to_append)
|
||||
{
|
||||
self.init_array_if_needed();
|
||||
self.array.push(to_append);
|
||||
self.array.append(to_append);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
/**
|
||||
* @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);
|
||||
self.array.append(&NULL_OBJECT);
|
||||
}
|
||||
if (self.array.len() == index)
|
||||
{
|
||||
self.array.push(to_set);
|
||||
self.array.append(to_set);
|
||||
return;
|
||||
}
|
||||
self.array.get(index).free();
|
||||
self.array.set_at(index, to_set);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof.is_int() : "Expected an integer type."
|
||||
*>
|
||||
/**
|
||||
* @require $Type.kindof.is_int() "Expected an integer type."
|
||||
**/
|
||||
macro get_integer_value(Object* value, $Type)
|
||||
{
|
||||
if (value.is_float())
|
||||
@@ -307,118 +322,118 @@ macro get_integer_value(Object* value, $Type)
|
||||
return ($Type)value.s.to_uint128();
|
||||
$endif
|
||||
}
|
||||
if (!value.is_int()) return string::MALFORMED_INTEGER~;
|
||||
if (!value.is_int()) return NumberConversion.MALFORMED_INTEGER?;
|
||||
return ($Type)value.i;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
@require $Type.kindof.is_int() : "Expected an integer type"
|
||||
*>
|
||||
/**
|
||||
* @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"
|
||||
*>
|
||||
/**
|
||||
* @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(&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 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(&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);
|
||||
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)
|
||||
/**
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
fn String! Object.get_string(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_string()) return TYPE_MISMATCH~;
|
||||
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn String? Object.get_string_at(&self, usz index)
|
||||
/**
|
||||
* @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~;
|
||||
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
macro String? Object.get_enum(&self, $EnumType, String key)
|
||||
/**
|
||||
* @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~;
|
||||
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
macro String? Object.get_enum_at(&self, $EnumType, usz index)
|
||||
/**
|
||||
* @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~;
|
||||
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn bool? Object.get_bool(&self, String key)
|
||||
/**
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
fn bool! Object.get_bool(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_bool()) return TYPE_MISMATCH~;
|
||||
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn bool? Object.get_bool_at(&self, usz index)
|
||||
/**
|
||||
* @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~;
|
||||
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn double? Object.get_float(&self, String key)
|
||||
/**
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
fn double! Object.get_float(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
switch (value.type.kindof)
|
||||
@@ -430,14 +445,14 @@ fn double? Object.get_float(&self, String key)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
return TYPE_MISMATCH~;
|
||||
return CastResult.TYPE_MISMATCH?;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn double? Object.get_float_at(&self, usz index)
|
||||
/**
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
fn double! Object.get_float_at(&self, usz index)
|
||||
{
|
||||
Object* value = self.get_at(index);
|
||||
switch (value.type.kindof)
|
||||
@@ -449,7 +464,7 @@ fn double? Object.get_float_at(&self, usz index)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
return TYPE_MISMATCH~;
|
||||
return CastResult.TYPE_MISMATCH?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,7 +476,7 @@ fn Object* Object.get_or_create_obj(&self, String key)
|
||||
return container;
|
||||
}
|
||||
|
||||
alias ObjectInternalMap @private = HashMap {String, Object*};
|
||||
alias ObjectInternalList @private = List {Object*};
|
||||
alias ObjectInternalMapEntry @private = Entry {String, Object*};
|
||||
def ObjectInternalMap = HashMap(<String, Object*>) @private;
|
||||
def ObjectInternalList = List(<Object*>) @private;
|
||||
def ObjectInternalMapEntry = Entry(<String, Object*>) @private;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// priorityqueue.c3
|
||||
// A priority queue using a classic binary heap for C3.
|
||||
//
|
||||
// Copyright (c) 2022-2025 David Kopec
|
||||
// Copyright (c) 2022 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
|
||||
@@ -20,30 +20,32 @@
|
||||
// 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;
|
||||
import std::collections::list, std::io;
|
||||
module std::collections::priorityqueue(<Type>);
|
||||
import std::collections::priorityqueue::private;
|
||||
|
||||
typedef PriorityQueue <Type> = inline PrivatePriorityQueue{Type, false};
|
||||
typedef PriorityQueueMax <Type> = inline PrivatePriorityQueue{Type, true};
|
||||
distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
|
||||
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);
|
||||
|
||||
struct PrivatePriorityQueue (Printable) <Type, MAX>
|
||||
module std::collections::priorityqueue::private(<Type, MAX>);
|
||||
import std::collections::list;
|
||||
|
||||
def Heap = List(<Type>);
|
||||
|
||||
struct PrivatePriorityQueue (Printable)
|
||||
{
|
||||
List{Type} heap;
|
||||
Heap heap;
|
||||
}
|
||||
|
||||
fn PrivatePriorityQueue* PrivatePriorityQueue.init(&self, Allocator allocator, usz initial_capacity = 16, ) @inline
|
||||
fn void PrivatePriorityQueue.init_new(&self, usz initial_capacity = 16, Allocator* allocator = mem::heap()) @inline
|
||||
{
|
||||
self.heap.init(allocator, initial_capacity);
|
||||
return self;
|
||||
self.heap.init_new(initial_capacity, allocator);
|
||||
}
|
||||
|
||||
fn PrivatePriorityQueue* PrivatePriorityQueue.tinit(&self, usz initial_capacity = 16) @inline
|
||||
fn void PrivatePriorityQueue.init_temp(&self, usz initial_capacity = 16) @inline
|
||||
{
|
||||
self.init(tmem, initial_capacity);
|
||||
return self;
|
||||
self.heap.init_new(initial_capacity, mem::temp()) @inline;
|
||||
}
|
||||
|
||||
|
||||
fn void PrivatePriorityQueue.push(&self, Type element)
|
||||
{
|
||||
self.heap.push(element);
|
||||
@@ -64,52 +66,37 @@ fn void PrivatePriorityQueue.push(&self, Type element)
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@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)
|
||||
/**
|
||||
* @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)
|
||||
if (!len) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
usz newCount = len - 1;
|
||||
self.heap.swap(0, newCount);
|
||||
while ((2 * i + 1) < newCount)
|
||||
{
|
||||
usz j = 2 * i + 1;
|
||||
Type left = self.heap[j];
|
||||
Type item = self.heap[i];
|
||||
switch
|
||||
Type itemj = self.heap[j];
|
||||
if ((j + 1) < newCount)
|
||||
{
|
||||
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
|
||||
Type nextj = self.heap[j + 1];
|
||||
$if MAX:
|
||||
bool ok = greater(nextj, itemj);
|
||||
$else
|
||||
bool ok = less(nextj, itemj);
|
||||
$endif
|
||||
if (ok) j++;
|
||||
}
|
||||
Type item = self.heap[i];
|
||||
$if MAX:
|
||||
bool ok = less(item, itemj);
|
||||
$else
|
||||
bool ok = greater(item, itemj);
|
||||
$endif
|
||||
if (!ok) break;
|
||||
self.heap.swap(i, j);
|
||||
i = j;
|
||||
}
|
||||
@@ -117,9 +104,10 @@ fn Type? PrivatePriorityQueue.pop(&self)
|
||||
return self.heap.pop();
|
||||
}
|
||||
|
||||
fn Type? PrivatePriorityQueue.first(&self)
|
||||
fn Type! PrivatePriorityQueue.peek(&self)
|
||||
{
|
||||
return self.heap.first();
|
||||
if (!self.len()) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.heap.get(0);
|
||||
}
|
||||
|
||||
fn void PrivatePriorityQueue.free(&self)
|
||||
@@ -129,24 +117,29 @@ fn void PrivatePriorityQueue.free(&self)
|
||||
|
||||
fn usz PrivatePriorityQueue.len(&self) @operator(len)
|
||||
{
|
||||
return self.heap.len() @inline;
|
||||
return self.heap.len();
|
||||
}
|
||||
|
||||
fn bool PrivatePriorityQueue.is_empty(&self)
|
||||
{
|
||||
return self.heap.is_empty() @inline;
|
||||
return self.heap.is_empty();
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len()
|
||||
*>
|
||||
fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
|
||||
/**
|
||||
* @require index < self.len()
|
||||
*/
|
||||
fn Type PrivatePriorityQueue.peek_at(&self, usz index) @operator([])
|
||||
{
|
||||
return self.heap[index];
|
||||
}
|
||||
|
||||
fn usz? PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
|
||||
fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return self.heap.to_format(formatter);
|
||||
}
|
||||
|
||||
fn String PrivatePriorityQueue.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
return self.heap.to_new_string(allocator);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<*
|
||||
@require Type.is_ordered : "The type must be ordered"
|
||||
*>
|
||||
module std::collections::range <Type>;
|
||||
import std::io;
|
||||
/**
|
||||
* @require Type.is_ordered : "The type must be ordered"
|
||||
**/
|
||||
module std::collections::range(<Type>);
|
||||
|
||||
struct Range (Printable)
|
||||
{
|
||||
@@ -21,15 +20,25 @@ 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"
|
||||
*>
|
||||
/**
|
||||
* @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
|
||||
fn String Range.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
return string::new_format("[%s..%s]", self.start, self.end, .allocator = allocator);
|
||||
}
|
||||
|
||||
fn String Range.to_tstring(&self)
|
||||
{
|
||||
return self.to_new_string(mem::temp());
|
||||
}
|
||||
|
||||
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return formatter.printf("[%s..%s]", self.start, self.end)!;
|
||||
}
|
||||
@@ -51,14 +60,24 @@ fn bool ExclusiveRange.contains(&self, Type value) @inline
|
||||
return value >= self.start && value < self.end;
|
||||
}
|
||||
|
||||
fn usz? ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
|
||||
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 String ExclusiveRange.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
return string::new_format("[%s..<%s]", self.start, self.end, .allocator = allocator);
|
||||
}
|
||||
|
||||
fn String ExclusiveRange.to_tstring(&self)
|
||||
{
|
||||
return self.to_new_string(mem::temp());
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < self.len() : "Can't index into an empty range"
|
||||
**/
|
||||
fn Type ExclusiveRange.get(&self, usz index) @operator([])
|
||||
{
|
||||
return (Type)(self.start + index);
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
<*
|
||||
@require Type.kindof == ARRAY : "Required an array type"
|
||||
*>
|
||||
module std::collections::ringbuffer <Type>;
|
||||
import std::io;
|
||||
module std::collections::ringbuffer(<Type, SIZE>);
|
||||
|
||||
alias Element = $typeof((Type){}[0]);
|
||||
|
||||
struct RingBuffer (Printable)
|
||||
struct RingBuffer
|
||||
{
|
||||
Type buf;
|
||||
Type[SIZE] buf;
|
||||
usz written;
|
||||
usz head;
|
||||
}
|
||||
@@ -18,9 +12,9 @@ fn void RingBuffer.init(&self) @inline
|
||||
*self = {};
|
||||
}
|
||||
|
||||
fn void RingBuffer.push(&self, Element c)
|
||||
fn void RingBuffer.putc(&self, Type c)
|
||||
{
|
||||
if (self.written < self.buf.len)
|
||||
if (self.written < SIZE)
|
||||
{
|
||||
self.buf[self.written] = c;
|
||||
self.written++;
|
||||
@@ -28,14 +22,14 @@ fn void RingBuffer.push(&self, Element c)
|
||||
else
|
||||
{
|
||||
self.buf[self.head] = c;
|
||||
self.head = (self.head + 1) % self.buf.len;
|
||||
self.head = (self.head + 1) % SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
fn Element RingBuffer.get(&self, usz index) @operator([])
|
||||
fn Type RingBuffer.getc(&self, usz index)
|
||||
{
|
||||
index %= self.buf.len;
|
||||
usz avail = self.buf.len - self.head;
|
||||
index %= SIZE;
|
||||
usz avail = SIZE - self.head;
|
||||
if (index < avail)
|
||||
{
|
||||
return self.buf[self.head + index];
|
||||
@@ -43,31 +37,25 @@ fn Element RingBuffer.get(&self, usz index) @operator([])
|
||||
return self.buf[index - avail];
|
||||
}
|
||||
|
||||
fn Element? RingBuffer.pop(&self)
|
||||
fn Type! RingBuffer.popc(&self)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case self.written == 0:
|
||||
return NO_MORE_ELEMENT~;
|
||||
case self.written < self.buf.len:
|
||||
return SearchResult.MISSING?;
|
||||
case self.written < SIZE:
|
||||
self.written--;
|
||||
return self.buf[self.written];
|
||||
default:
|
||||
self.head = (self.head - 1) % self.buf.len;
|
||||
self.head = (self.head - 1) % SIZE;
|
||||
return self.buf[self.head];
|
||||
}
|
||||
}
|
||||
|
||||
fn usz? RingBuffer.to_format(&self, Formatter* format) @dynamic
|
||||
fn usz RingBuffer.get(&self, usz index, Type[] buffer)
|
||||
{
|
||||
// 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)
|
||||
index %= SIZE;
|
||||
if (self.written < SIZE)
|
||||
{
|
||||
if (index >= self.written) return 0;
|
||||
usz end = self.written - index;
|
||||
@@ -75,7 +63,7 @@ fn usz RingBuffer.read(&self, usz index, Element[] buffer)
|
||||
buffer[:n] = self.buf[index:n];
|
||||
return n;
|
||||
}
|
||||
usz end = self.buf.len - self.head;
|
||||
usz end = SIZE - self.head;
|
||||
if (index >= end)
|
||||
{
|
||||
index -= end;
|
||||
@@ -84,13 +72,13 @@ fn usz RingBuffer.read(&self, usz index, Element[] buffer)
|
||||
buffer[:n] = self.buf[index:n];
|
||||
return n;
|
||||
}
|
||||
if (buffer.len <= self.buf.len - index)
|
||||
if (buffer.len <= SIZE - index)
|
||||
{
|
||||
usz n = buffer.len;
|
||||
buffer[:n] = self.buf[self.head + index:n];
|
||||
return n;
|
||||
}
|
||||
usz n1 = self.buf.len - index;
|
||||
usz n1 = SIZE - index;
|
||||
buffer[:n1] = self.buf[self.head + index:n1];
|
||||
buffer = buffer[n1..];
|
||||
index -= n1;
|
||||
@@ -99,10 +87,10 @@ fn usz RingBuffer.read(&self, usz index, Element[] buffer)
|
||||
return n1 + n2;
|
||||
}
|
||||
|
||||
fn void RingBuffer.write(&self, Element[] buffer)
|
||||
fn void RingBuffer.push(&self, Type[] buffer)
|
||||
{
|
||||
usz i;
|
||||
while (self.written < self.buf.len && i < buffer.len)
|
||||
while (self.written < SIZE && i < buffer.len)
|
||||
{
|
||||
self.buf[self.written] = buffer[i++];
|
||||
self.written++;
|
||||
@@ -110,6 +98,6 @@ fn void RingBuffer.write(&self, Element[] buffer)
|
||||
foreach (c : buffer[i..])
|
||||
{
|
||||
self.buf[self.head] = c;
|
||||
self.head = (self.head + 1) % self.buf.len;
|
||||
self.head = (self.head + 1) % SIZE;
|
||||
}
|
||||
}
|
||||
@@ -1,72 +1,16 @@
|
||||
module std::collections::pair <Type1, Type2>;
|
||||
import std::io;
|
||||
module std::collections::tuple(<Type1, Type2>);
|
||||
|
||||
struct Pair (Printable)
|
||||
struct Tuple
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
}
|
||||
|
||||
fn usz? Pair.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
return f.printf("{ %s, %s }", self.first, self.second);
|
||||
}
|
||||
module std::collections::triple(<Type1, Type2, Type3>);
|
||||
|
||||
<*
|
||||
@param [&out] a
|
||||
@param [&out] b
|
||||
@require $defined(*a = self.first) : "You cannot assign the first value to a"
|
||||
@require $defined(*b = self.second) : "You cannot assign the second value to b"
|
||||
*>
|
||||
macro void Pair.unpack(&self, a, b)
|
||||
{
|
||||
*a = self.first;
|
||||
*b = self.second;
|
||||
}
|
||||
|
||||
fn bool Pair.equal(self, Pair other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2))
|
||||
{
|
||||
return self.first == other.first && self.second == other.second;
|
||||
}
|
||||
|
||||
module std::collections::triple <Type1, Type2, Type3>;
|
||||
import std::io;
|
||||
|
||||
struct Triple (Printable)
|
||||
struct Triple
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
Type3 third;
|
||||
}
|
||||
|
||||
fn usz? Triple.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
return f.printf("{ %s, %s, %s }", self.first, self.second, self.third);
|
||||
}
|
||||
<*
|
||||
@param [&out] a
|
||||
@param [&out] b
|
||||
@param [&out] c
|
||||
@require $defined(*a = self.first) : "You cannot assign the first value to a"
|
||||
@require $defined(*b = self.second) : "You cannot assign the second value to b"
|
||||
@require $defined(*c = self.third) : "You cannot assign the second value to c"
|
||||
*>
|
||||
macro void Triple.unpack(&self, a, b, c)
|
||||
{
|
||||
*a = self.first;
|
||||
*b = self.second;
|
||||
*c = self.third;
|
||||
}
|
||||
|
||||
fn bool Triple.equal(self, Triple other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2) &&& types::has_equals(Type3))
|
||||
{
|
||||
return self.first == other.first && self.second == other.second && self.third == other.third;
|
||||
}
|
||||
|
||||
module std::collections::tuple <Type1, Type2>;
|
||||
|
||||
struct Tuple @deprecated("Use 'Pair' instead")
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
}
|
||||
@@ -1,466 +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 : const char
|
||||
{
|
||||
<* sRGB with linear alpha *>
|
||||
SRGB = 0,
|
||||
<* all channels linear *>
|
||||
LINEAR = 1
|
||||
}
|
||||
|
||||
<*
|
||||
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 : const inline char
|
||||
{
|
||||
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;
|
||||
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,
|
||||
.colorspace = desc.colorspace
|
||||
};
|
||||
|
||||
uint pos = Header.sizeof; // Current position in output
|
||||
uint loc; // Current position in image (top-left corner)
|
||||
uint loc_end = image_size - desc.channels; // 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)
|
||||
{
|
||||
// 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
|
||||
uint width = desc.width = bswap(header.be_width);
|
||||
uint height = desc.height = bswap(header.be_height);
|
||||
QOIChannels desc_channels = desc.channels = header.channels;
|
||||
desc.colorspace = header.colorspace;
|
||||
if (desc_channels == AUTO) return INVALID_DATA~; // Channels must be specified in the header
|
||||
|
||||
// check width and height
|
||||
if (width == 0 || height == 0) return INVALID_DATA~;
|
||||
|
||||
// check pixel count
|
||||
ulong pixels = (ulong)width * (ulong)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;
|
||||
char[] image = allocator::alloc_array(allocator, char, image_size);
|
||||
defer catch allocator::free(allocator, image);
|
||||
|
||||
for (loc = 0; loc < image_size; loc += channels)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
<* magic bytes "qoif" *>
|
||||
uint be_magic;
|
||||
<* image width in pixels (BE) *>
|
||||
uint be_width;
|
||||
<* image height in pixels (BE) *>
|
||||
uint be_height;
|
||||
|
||||
// informative fields
|
||||
<* 3 = RGB, 4 = RGB *>
|
||||
QOIChannels channels;
|
||||
<* 0 = sRGB with linear alpha, 1 = all channels linear *>
|
||||
QOIColorspace colorspace;
|
||||
}
|
||||
|
||||
const char[*] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1};
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
// Copyright (c) 2023-2025 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
// 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)
|
||||
{
|
||||
@@ -14,60 +9,29 @@ struct ArenaAllocator (Allocator)
|
||||
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)
|
||||
/**
|
||||
* Initialize a memory arena for use using the provided bytes.
|
||||
**/
|
||||
fn void 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, may be empty.`
|
||||
|
||||
@return `An arena allocator using the bytes`
|
||||
*>
|
||||
macro ArenaAllocator* wrap(char[] bytes)
|
||||
struct ArenaAllocatorHeader @local
|
||||
{
|
||||
return (ArenaAllocator){}.init(bytes);
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
<*
|
||||
"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
|
||||
{
|
||||
if (!ptr) return;
|
||||
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.
|
||||
@@ -76,52 +40,61 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
self.used -= header.size + ArenaAllocatorHeader.sizeof;
|
||||
}
|
||||
}
|
||||
fn usz ArenaAllocator.mark(&self) @dynamic => self.used;
|
||||
fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark;
|
||||
|
||||
|
||||
<*
|
||||
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
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
|
||||
**/
|
||||
fn void*! ArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (!size) return null;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return mem::INVALID_ALLOC_SIZE~;
|
||||
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
|
||||
void* start_mem = self.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof;
|
||||
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(mem - self.data.ptr) + size;
|
||||
if (end > total_len) return mem::OUT_OF_MEMORY~;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof + offset;
|
||||
void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(aligned_pointer_to_offset - self.data.ptr) + size - offset;
|
||||
if (end > total_len) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.used = end;
|
||||
void* mem = aligned_pointer_to_offset - offset;
|
||||
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
|
||||
header.size = size;
|
||||
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
if (clear) 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
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
|
||||
**/
|
||||
fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
self.release(old_pointer, alignment > 0);
|
||||
return null;
|
||||
}
|
||||
if (!old_pointer)
|
||||
{
|
||||
return self.acquire(size, true, alignment, offset);
|
||||
}
|
||||
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~;
|
||||
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
|
||||
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
// Do last allocation and alignment match?
|
||||
if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer + offset, alignment))
|
||||
{
|
||||
if (old_size >= size)
|
||||
{
|
||||
@@ -130,22 +103,14 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
|
||||
else
|
||||
{
|
||||
usz new_used = self.used + size - old_size;
|
||||
if (new_used > total_len) return mem::OUT_OF_MEMORY~;
|
||||
if (new_used > total_len) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.used = new_used;
|
||||
}
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
}
|
||||
// Otherwise just allocate new memory.
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(mem, old_pointer, math::min(size, old_size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
void* mem = self.acquire(size, false, alignment, offset)!;
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
// Internal data
|
||||
|
||||
struct ArenaAllocatorHeader @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::io, std::math;
|
||||
|
||||
<*
|
||||
The backed arena allocator provides an allocator that will allocate from a pre-allocated chunk of memory
|
||||
provided by it's backing allocator. The allocator supports mark / reset operations, so it can be used
|
||||
as a stack (push-pop) allocator. If the initial memory is used up, it will fall back to regular allocations,
|
||||
that will be safely freed on `reset`.
|
||||
|
||||
While this allocator is similar to the dynamic arena, it supports multiple "save points", which the dynamic arena
|
||||
doesn't.
|
||||
*>
|
||||
struct BackedArenaAllocator (Allocator)
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
ExtraPage* last_page;
|
||||
usz used;
|
||||
usz capacity;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
struct AllocChunk @local
|
||||
{
|
||||
usz size;
|
||||
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, math::min(size, 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];
|
||||
}
|
||||
@@ -1,33 +1,21 @@
|
||||
// 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::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
<*
|
||||
The dynamic arena allocator is an arena allocator that can grow by adding additional arena "pages".
|
||||
It only supports reset, at which point all pages except the first one is released to the backing
|
||||
allocator.
|
||||
|
||||
If you want multiple save points, use the BackedArenaAllocator instead.
|
||||
|
||||
The advantage over the BackedArenaAllocator, is that when allocating beyond the first "page", it will
|
||||
retain the characteristics of an arena allocator (allocating a large piece of memory then handing off
|
||||
memory from that memory), whereas the BackedArenaAllocator will have heap allocator characteristics.
|
||||
*>
|
||||
struct DynamicArenaAllocator (Allocator)
|
||||
{
|
||||
Allocator backing_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)
|
||||
/**
|
||||
* @param [&inout] allocator
|
||||
* @require page_size >= 128
|
||||
**/
|
||||
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator* allocator)
|
||||
{
|
||||
self.page = null;
|
||||
self.unused_page = null;
|
||||
@@ -41,16 +29,14 @@ fn void DynamicArenaAllocator.free(&self)
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
allocator::free(self.backing_allocator, page.memory);
|
||||
allocator::free(self.backing_allocator, page);
|
||||
self.backing_allocator.free(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);
|
||||
self.backing_allocator.free(page);
|
||||
page = next_page;
|
||||
}
|
||||
self.page = null;
|
||||
@@ -71,12 +57,12 @@ struct DynamicArenaChunk @local
|
||||
usz size;
|
||||
}
|
||||
|
||||
<*
|
||||
@require ptr != null
|
||||
@require self.page != null : `tried to free pointer on invalid allocator`
|
||||
*>
|
||||
/**
|
||||
* @require self.page `tried to free pointer on invalid allocator`
|
||||
*/
|
||||
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
{
|
||||
if (!ptr) return;
|
||||
DynamicArenaPage* current_page = self.page;
|
||||
if (ptr == current_page.current_stack_ptr)
|
||||
{
|
||||
@@ -85,14 +71,20 @@ fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
current_page.current_stack_ptr = null;
|
||||
}
|
||||
|
||||
<*
|
||||
@require size > 0 : `Resize doesn't support zeroing`
|
||||
@require old_pointer != null : `Resize doesn't handle null pointers`
|
||||
@require self.page != null : `tried to realloc pointer on invalid allocator`
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
/**
|
||||
* @require self.page `tried to realloc pointer on invalid allocator`
|
||||
*/
|
||||
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
self.release(old_pointer, alignment > 0);
|
||||
return null;
|
||||
}
|
||||
if (!old_pointer)
|
||||
{
|
||||
return self.acquire(size, true, alignment, offset);
|
||||
}
|
||||
DynamicArenaPage* current_page = self.page;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
|
||||
@@ -116,13 +108,14 @@ fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a
|
||||
current_page.used += add_size;
|
||||
return old_pointer;
|
||||
}
|
||||
void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(new_mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT);
|
||||
void* new_mem = self.acquire(size, false, alignment, offset)!;
|
||||
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return new_mem;
|
||||
}
|
||||
|
||||
fn void DynamicArenaAllocator.reset(&self)
|
||||
fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
|
||||
{
|
||||
assert(mark == 0, "Unexpectedly reset dynamic arena allocator with mark %d", mark);
|
||||
DynamicArenaPage* page = self.page;
|
||||
DynamicArenaPage** unused_page_ptr = &self.unused_page;
|
||||
while (page)
|
||||
@@ -137,26 +130,25 @@ fn void DynamicArenaAllocator.reset(&self)
|
||||
self.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
|
||||
*/
|
||||
fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment, usz offset) @local
|
||||
{
|
||||
// First, make sure that we can align it, extending the page size if needed.
|
||||
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + alignment, alignment));
|
||||
assert(page_size > size + DynamicArenaChunk.sizeof);
|
||||
usz page_size = max(self.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 = self.backing_allocator.alloc_checked(page_size)!;
|
||||
DynamicArenaPage*! page = self.backing_allocator.new(DynamicArenaPage);
|
||||
if (catch err = page)
|
||||
{
|
||||
allocator::free(self.backing_allocator, mem);
|
||||
return err~;
|
||||
self.backing_allocator.free(mem);
|
||||
return err?;
|
||||
}
|
||||
page.memory = mem;
|
||||
void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment);
|
||||
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;
|
||||
@@ -168,37 +160,29 @@ fn void*? DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @loca
|
||||
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)
|
||||
*/
|
||||
fn void*! DynamicArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (!size) return null;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
DynamicArenaPage* page = self.page;
|
||||
|
||||
void* ptr @noinit;
|
||||
do SET_DONE:
|
||||
{
|
||||
void* ptr = {|
|
||||
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);
|
||||
if (!page) return self._alloc_new(size, alignment, offset);
|
||||
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
|
||||
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);
|
||||
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
|
||||
new_used = start + size - page.memory;
|
||||
if (page.total >= new_used)
|
||||
{
|
||||
@@ -208,15 +192,15 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
|
||||
break ALLOCATE_NEW;
|
||||
}
|
||||
}
|
||||
ptr = self._alloc_new(size, alignment)!;
|
||||
break SET_DONE;
|
||||
return self._alloc_new(size, alignment, offset);
|
||||
}
|
||||
page.used = new_used;
|
||||
assert(start + size == page.memory + page.used);
|
||||
ptr = start;
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)ptr - 1;
|
||||
void* mem = start;
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
|
||||
chunk.size = size;
|
||||
};
|
||||
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
|}!;
|
||||
if (clear) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -1,46 +1,49 @@
|
||||
// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021-2023 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
<*
|
||||
The SimpleHeapAllocator implements a simple heap allocator on top of an allocator function.
|
||||
|
||||
It uses the given allocator function to allocate memory from some source, but never frees it.
|
||||
This allocator is intended to be used in environments where there isn't any native libc malloc,
|
||||
and it has to be emulated from a memory region, or wrapping linear memory as is the case for plain WASM.
|
||||
*>
|
||||
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"
|
||||
*>
|
||||
/**
|
||||
* @require allocator "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
|
||||
fn void*! SimpleHeapAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
if (!size) return null;
|
||||
if (clear)
|
||||
{
|
||||
return alignment > 0 ? @aligned_alloc(self._calloc, size, alignment) : self._calloc(size);
|
||||
return alignment > 0 ? @aligned_calloc(self._calloc, size, alignment, offset) : self._calloc(size);
|
||||
}
|
||||
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size);
|
||||
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment, offset) : self._alloc(size);
|
||||
}
|
||||
|
||||
fn void*? SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
self.release(old_pointer, alignment > 0);
|
||||
return null;
|
||||
}
|
||||
if (!old_pointer)
|
||||
{
|
||||
return self.acquire(size, true, alignment, offset);
|
||||
}
|
||||
return alignment > 0
|
||||
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment)
|
||||
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment, offset)
|
||||
: self._realloc(old_pointer, size);
|
||||
}
|
||||
|
||||
@@ -56,10 +59,10 @@ fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dyn
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require old_pointer && bytes > 0
|
||||
*>
|
||||
fn void*? SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
|
||||
/**
|
||||
* @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;
|
||||
@@ -71,14 +74,14 @@ fn void*? SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @loc
|
||||
return new;
|
||||
}
|
||||
|
||||
fn void*? SimpleHeapAllocator._calloc(&self, usz bytes) @local
|
||||
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
|
||||
fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
|
||||
{
|
||||
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
if (!self.free_list)
|
||||
@@ -105,7 +108,7 @@ fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
|
||||
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.size = current.size - aligned_bytes;
|
||||
unallocated.next = current.next;
|
||||
if (current == self.free_list)
|
||||
{
|
||||
@@ -127,7 +130,7 @@ fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
|
||||
return self._alloc(aligned_bytes);
|
||||
}
|
||||
|
||||
fn void? SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
|
||||
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)!;
|
||||
|
||||
@@ -1,149 +1,52 @@
|
||||
// Copyright (c) 2021-2025 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::mem::allocator @if(env::LIBC);
|
||||
import std::io;
|
||||
|
||||
module std::core::mem::allocator;
|
||||
import libc;
|
||||
|
||||
<*
|
||||
The LibcAllocator is a wrapper around malloc to conform to the Allocator interface.
|
||||
*>
|
||||
typedef LibcAllocator (Allocator) = uptr;
|
||||
const LibcAllocator LIBC_ALLOCATOR = {};
|
||||
|
||||
module std::core::mem::allocator @if(env::POSIX);
|
||||
import std::os;
|
||||
import libc;
|
||||
|
||||
distinct LibcAllocator (Allocator) = uptr;
|
||||
|
||||
fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
fn void*! LibcAllocator.acquire(&self, usz bytes, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
assert(alignment != 0 || offset == 0);
|
||||
if (clear)
|
||||
{
|
||||
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~;
|
||||
void* data = alignment ? @aligned_calloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment, offset)!! : libc::calloc(bytes, 1);
|
||||
return data ?: AllocationFailure.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;
|
||||
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment, offset)!! : libc::malloc(bytes);
|
||||
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment, usz offset) @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)
|
||||
assert(alignment != 0 || offset == 0);
|
||||
if (!new_bytes)
|
||||
{
|
||||
if (alignment > 0)
|
||||
{
|
||||
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY~;
|
||||
self.release(old_ptr, alignment > 0);
|
||||
return null;
|
||||
}
|
||||
if (!old_ptr)
|
||||
{
|
||||
return self.acquire(new_bytes, true, alignment, offset);
|
||||
}
|
||||
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~;
|
||||
void* data = @aligned_realloc(fn void*(usz bytes) => libc::calloc(bytes, 1), libc::free, old_ptr, new_bytes, alignment, offset)!!;
|
||||
return data ?: AllocationFailure.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_MEMORY~;
|
||||
}
|
||||
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~;
|
||||
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
<*
|
||||
The OnStackAllocator is similar to the ArenaAllocator: it allocates from a chunk of memory
|
||||
given to it.
|
||||
|
||||
The difference is that when it runs out of memory it will go directly to its backing allocator
|
||||
rather than failing.
|
||||
|
||||
It is utilized by the @stack_mem macro as an alternative to the temp allocator.
|
||||
*>
|
||||
struct OnStackAllocator (Allocator)
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
Allocator* backing_allocator;
|
||||
char[] data;
|
||||
usz used;
|
||||
OnStackAllocatorExtraChunk* chunk;
|
||||
}
|
||||
|
||||
|
||||
struct OnStackAllocatorExtraChunk @local
|
||||
{
|
||||
bool is_aligned;
|
||||
@@ -24,12 +16,11 @@ struct OnStackAllocatorExtraChunk @local
|
||||
void* data;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a memory arena for use using the provided bytes.
|
||||
|
||||
@param [&inout] allocator
|
||||
*>
|
||||
fn void OnStackAllocator.init(&self, char[] data, Allocator allocator)
|
||||
/**
|
||||
* @param [&inout] allocator
|
||||
* Initialize a memory arena for use using the provided bytes.
|
||||
**/
|
||||
fn void OnStackAllocator.init(&self, char[] data, Allocator* allocator)
|
||||
{
|
||||
self.data = data;
|
||||
self.backing_allocator = allocator;
|
||||
@@ -43,15 +34,15 @@ fn void OnStackAllocator.free(&self)
|
||||
{
|
||||
if (chunk.is_aligned)
|
||||
{
|
||||
allocator::free_aligned(self.backing_allocator, chunk.data);
|
||||
self.backing_allocator.free_aligned(chunk.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator::free(self.backing_allocator, chunk.data);
|
||||
self.backing_allocator.free(chunk.data);
|
||||
}
|
||||
void* old = chunk;
|
||||
chunk = chunk.prev;
|
||||
allocator::free(self.backing_allocator, old);
|
||||
self.backing_allocator.free(old);
|
||||
}
|
||||
self.chunk = null;
|
||||
self.used = 0;
|
||||
@@ -63,14 +54,12 @@ struct OnStackAllocatorHeader
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
<*
|
||||
@require old_pointer != null
|
||||
*>
|
||||
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
|
||||
{
|
||||
if (!old_pointer) return;
|
||||
if (allocation_in_stack_mem(self, old_pointer)) return;
|
||||
on_stack_allocator_remove_chunk(self, old_pointer);
|
||||
self.backing_allocator.release(old_pointer, aligned);
|
||||
self.release(old_pointer, aligned);
|
||||
}
|
||||
|
||||
fn bool allocation_in_stack_mem(OnStackAllocator* a, void* ptr) @local
|
||||
@@ -87,7 +76,7 @@ fn void on_stack_allocator_remove_chunk(OnStackAllocator* a, void* ptr) @local
|
||||
if (chunk.data == ptr)
|
||||
{
|
||||
*addr = chunk.prev;
|
||||
allocator::free(a.backing_allocator, chunk);
|
||||
a.backing_allocator.free(chunk);
|
||||
return;
|
||||
}
|
||||
addr = &chunk.prev;
|
||||
@@ -107,51 +96,58 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a
|
||||
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
|
||||
/**
|
||||
* @require size > 0
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require mem::aligned_offset(offset, OnStackAllocatorExtraChunk.alignof) == offset
|
||||
**/
|
||||
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @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)!;
|
||||
return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment, offset)!;
|
||||
}
|
||||
|
||||
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
void* mem = self.acquire(size, true, alignment, offset)!;
|
||||
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
|
||||
/**
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require offset == 0 || alignment > 0
|
||||
* @require mem::aligned_offset(offset, OnStackAllocatorHeader.alignof) == offset
|
||||
**/
|
||||
fn void*! OnStackAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (size == 0) return null;
|
||||
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;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof + offset;
|
||||
void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(aligned_pointer_to_offset - self.data.ptr) + size - offset;
|
||||
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);
|
||||
OnStackAllocatorExtraChunk* chunk = backing_allocator.alloc_checked(OnStackAllocatorExtraChunk.sizeof)!;
|
||||
defer catch backing_allocator.free(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)!;
|
||||
return chunk.data = backing_allocator.acquire(size, clear, aligned ? alignment : 0, offset)!;
|
||||
}
|
||||
self.used = end;
|
||||
void *mem = aligned_pointer_to_offset - offset;
|
||||
OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader.sizeof;
|
||||
header.size = size;
|
||||
return mem;
|
||||
|
||||
@@ -1,47 +1,5 @@
|
||||
module std::core::mem::allocator @if(!(env::POSIX || env::WIN32) || !$feature(VMEM_TEMP));
|
||||
import std::io, std::math;
|
||||
|
||||
// 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)
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
TempAllocatorPage* last_page;
|
||||
TempAllocator* derived;
|
||||
bool allocated;
|
||||
usz reserve_size;
|
||||
usz realloc_size;
|
||||
usz min_size;
|
||||
usz used;
|
||||
usz capacity;
|
||||
usz original_capacity;
|
||||
char[*] data;
|
||||
}
|
||||
module std::core::mem::allocator;
|
||||
import std::io;
|
||||
|
||||
struct TempAllocatorChunk @local
|
||||
{
|
||||
@@ -49,12 +7,23 @@ struct TempAllocatorChunk @local
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
|
||||
struct TempAllocator (Allocator)
|
||||
{
|
||||
Allocator* backing_allocator;
|
||||
TempAllocatorPage* last_page;
|
||||
usz used;
|
||||
usz capacity;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
|
||||
|
||||
|
||||
struct TempAllocatorPage
|
||||
{
|
||||
TempAllocatorPage* prev_page;
|
||||
void* start;
|
||||
usz mark;
|
||||
usz size;
|
||||
usz ident;
|
||||
char[*] data;
|
||||
@@ -63,110 +32,20 @@ struct TempAllocatorPage
|
||||
macro usz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
|
||||
macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
|
||||
|
||||
<*
|
||||
@require size >= 64
|
||||
@require realloc_size >= 64
|
||||
@require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator."
|
||||
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
|
||||
*>
|
||||
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve = temp_allocator_reserve_size, usz min_size = temp_allocator_min_size, usz realloc_size = temp_allocator_realloc_size)
|
||||
/**
|
||||
* @require size >= 16
|
||||
**/
|
||||
fn TempAllocator*! new_temp(usz size, Allocator* allocator)
|
||||
{
|
||||
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
|
||||
TempAllocator* temp = allocator.alloc_checked(TempAllocator.sizeof + size)!;
|
||||
temp.last_page = null;
|
||||
temp.backing_allocator = allocator;
|
||||
temp.used = 0;
|
||||
temp.min_size = min_size;
|
||||
temp.realloc_size = realloc_size;
|
||||
temp.reserve_size = reserve;
|
||||
temp.allocated = true;
|
||||
temp.derived = null;
|
||||
temp.original_capacity = temp.capacity = size;
|
||||
temp.capacity = size;
|
||||
return temp;
|
||||
}
|
||||
<*
|
||||
@require !self.derived
|
||||
*>
|
||||
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0)
|
||||
{
|
||||
if (!reserve) reserve = self.reserve_size;
|
||||
usz remaining = self.capacity - self.used;
|
||||
void* mem @noinit;
|
||||
usz size @noinit;
|
||||
if (self.min_size + reserve > remaining)
|
||||
{
|
||||
return self.derived = new_temp_allocator(self.backing_allocator, self.realloc_size, self.reserve_size, self.min_size, self.realloc_size)!;
|
||||
}
|
||||
usz start = mem::aligned_offset(self.used + reserve, 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.min_size = self.min_size;
|
||||
temp.reserve_size = self.reserve_size;
|
||||
temp.realloc_size = self.realloc_size;
|
||||
temp.allocated = false;
|
||||
temp.derived = null;
|
||||
temp.original_capacity = temp.capacity = self.capacity - start - TempAllocator.sizeof;
|
||||
self.capacity = start;
|
||||
self.derived = temp;
|
||||
return temp;
|
||||
}
|
||||
|
||||
<*
|
||||
Reset the entire temp allocator, which will merge all the children into it.
|
||||
*>
|
||||
fn void TempAllocator.reset(&self)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
fn void TempAllocator.destroy(&self) @local
|
||||
{
|
||||
TempAllocatorPage *last_page = self.last_page;
|
||||
while (last_page)
|
||||
{
|
||||
TempAllocatorPage *to_free = last_page;
|
||||
last_page = last_page.prev_page;
|
||||
self._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
|
||||
}
|
||||
fn usz TempAllocator.mark(&self) @dynamic => self.used;
|
||||
|
||||
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
|
||||
{
|
||||
@@ -174,18 +53,29 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
|
||||
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
|
||||
fn void TempAllocator.reset(&self, usz mark) @dynamic
|
||||
{
|
||||
void* mem = page.start;
|
||||
return self.backing_allocator.release(mem, page.is_aligned());
|
||||
TempAllocatorPage *last_page = self.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)!!;
|
||||
}
|
||||
self.last_page = last_page;
|
||||
self.used = mark;
|
||||
}
|
||||
|
||||
fn void*? TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
|
||||
fn void! TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
|
||||
{
|
||||
void* mem = page.start;
|
||||
if (page.is_aligned()) return self.backing_allocator.free_aligned(mem);
|
||||
return self.backing_allocator.free(mem);
|
||||
}
|
||||
|
||||
fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment, usz offset) @inline @local
|
||||
{
|
||||
// Then the actual start pointer:
|
||||
void* real_pointer = page.start;
|
||||
@@ -200,65 +90,53 @@ fn void*? TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size,
|
||||
*pointer_to_prev = page.prev_page;
|
||||
usz page_size = page.pagesize();
|
||||
// Clear on size > original size.
|
||||
void* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
if (page_size > size) page_size = size;
|
||||
void* data = self.acquire(size, size > page_size, alignment, offset)!;
|
||||
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());
|
||||
if (page.is_aligned())
|
||||
{
|
||||
self.backing_allocator.free_aligned(real_pointer);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.backing_allocator.free(real_pointer);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
|
||||
fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
self.release(pointer, alignment > 0);
|
||||
return null;
|
||||
}
|
||||
if (!pointer)
|
||||
{
|
||||
return self.acquire(size, true, alignment, offset);
|
||||
}
|
||||
TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof;
|
||||
if (chunk.size == (usz)-1)
|
||||
{
|
||||
assert(self.last_page, "Realloc of non temp pointer");
|
||||
// First grab the page
|
||||
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
|
||||
return self._realloc_page(page, size, alignment);
|
||||
}
|
||||
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 self._realloc_page(page, size, alignment, offset);
|
||||
}
|
||||
|
||||
// TODO optimize last allocation
|
||||
TempAllocatorChunk* data = self.acquire(size, size > chunk.size, alignment, offset)!;
|
||||
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
<*
|
||||
@require size > 0
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
*>
|
||||
fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
**/
|
||||
fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (!size) return null;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
void* start_mem = &self.data;
|
||||
void* starting_ptr = start_mem + self.used;
|
||||
@@ -266,18 +144,17 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
|
||||
void* mem = aligned_header_start + TempAllocatorChunk.sizeof;
|
||||
if (alignment > TempAllocatorChunk.alignof)
|
||||
{
|
||||
mem = mem::aligned_pointer(mem, alignment);
|
||||
mem = mem::aligned_pointer(mem + offset, alignment) - offset;
|
||||
}
|
||||
usz new_usage = (usz)(mem - start_mem) + size;
|
||||
|
||||
// Arena allocation, simple!
|
||||
// Arena alignment, simple!
|
||||
if (new_usage <= self.capacity)
|
||||
{
|
||||
asan::unpoison_memory_region(starting_ptr, new_usage - self.used);
|
||||
TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof;
|
||||
chunk_start.size = size;
|
||||
self.used = new_usage;
|
||||
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
@@ -285,22 +162,19 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
|
||||
TempAllocatorPage* page;
|
||||
|
||||
// We have something we need to align.
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
if (alignment > mem::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)
|
||||
usz total_alloc_size = TempAllocatorPage.sizeof + size;
|
||||
if (clear)
|
||||
{
|
||||
mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
|
||||
page = self.backing_allocator.calloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
|
||||
page = self.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
|
||||
@@ -308,7 +182,7 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
|
||||
// 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)!;
|
||||
void* alloc = self.backing_allocator.acquire(total_alloc_size, clear, 0, 0)!;
|
||||
|
||||
// Find the page.
|
||||
page = alloc + padded_header_size - TempAllocatorPage.sizeof;
|
||||
@@ -320,87 +194,29 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
module std::core::mem::allocator @if((env::POSIX || env::WIN32) && $feature(VMEM_TEMP));
|
||||
import std::math;
|
||||
|
||||
tlocal VmemOptions temp_allocator_default_options = {
|
||||
.shrink_on_reset = env::MEMORY_ENV != NORMAL,
|
||||
.protect_unused_pages = env::COMPILER_OPT_LEVEL <= O1 || env::COMPILER_SAFE_MODE,
|
||||
.scratch_released_data = env::COMPILER_SAFE_MODE
|
||||
};
|
||||
|
||||
|
||||
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve = temp_allocator_reserve_size, usz min_size = temp_allocator_min_size, usz realloc_size = temp_allocator_realloc_size)
|
||||
fn void! TempAllocator.print_pages(&self, File* f)
|
||||
{
|
||||
Vmem mem;
|
||||
TempAllocator* t = allocator::new(allocator, TempAllocator);
|
||||
defer catch allocator::free(allocator, t);
|
||||
t.vmem.init(preferred_size: isz.sizeof > 4 ? 4 * mem::GB : 512 * mem::MB,
|
||||
reserve_page_size: isz.sizeof > 4 ? 256 * mem::KB : 0,
|
||||
options: temp_allocator_default_options)!;
|
||||
t.allocator = allocator;
|
||||
return t;
|
||||
TempAllocatorPage *last_page = self.last_page;
|
||||
if (!last_page)
|
||||
{
|
||||
io::fprintf(f, "No pages.\n")!;
|
||||
return;
|
||||
}
|
||||
io::fprintf(f, "---Pages----\n")!;
|
||||
uint index = 0;
|
||||
while (last_page)
|
||||
{
|
||||
bool is_not_aligned = !(last_page.size & (1u64 << 63));
|
||||
io::fprintf(f, "%d. Alloc: %d %d at %p%s\n", ++index,
|
||||
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]")!;
|
||||
last_page = last_page.prev_page;
|
||||
}
|
||||
}
|
||||
|
||||
struct TempAllocator (Allocator)
|
||||
{
|
||||
Vmem vmem;
|
||||
TempAllocator* derived;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
<*
|
||||
@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
|
||||
{
|
||||
return self.vmem.acquire(size, init_type, alignment) @inline;
|
||||
}
|
||||
|
||||
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0)
|
||||
{
|
||||
if (self.derived) return self.derived;
|
||||
return self.derived = new_temp_allocator(self.allocator, 0)!;
|
||||
}
|
||||
|
||||
<*
|
||||
Reset the entire temp allocator, destroying all children
|
||||
*>
|
||||
fn void TempAllocator.reset(&self)
|
||||
{
|
||||
TempAllocator* child = self.derived;
|
||||
if (!child) return;
|
||||
child.reset();
|
||||
child.vmem.reset(0);
|
||||
}
|
||||
fn void TempAllocator.free(&self)
|
||||
{
|
||||
self.destroy();
|
||||
}
|
||||
|
||||
fn void TempAllocator.destroy(&self) @local
|
||||
{
|
||||
TempAllocator* child = self.derived;
|
||||
if (!child) return;
|
||||
child.destroy();
|
||||
self.vmem.free() @inline;
|
||||
allocator::free(self.allocator, self) @inline;
|
||||
}
|
||||
|
||||
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
return self.vmem.resize(pointer, size, alignment) @inline;
|
||||
}
|
||||
|
||||
fn void TempAllocator.release(&self, void* old_pointer, bool b) @dynamic
|
||||
{
|
||||
self.vmem.release(old_pointer, b) @inline;
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
// Copyright (c) 2021-2025 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::mem::allocator;
|
||||
import std::collections, std::io, std::os::backtrace;
|
||||
import std::collections::map;
|
||||
import std::collections::list;
|
||||
|
||||
const MAX_BACKTRACE = 16;
|
||||
struct Allocation
|
||||
@@ -13,116 +14,114 @@ struct Allocation
|
||||
void*[MAX_BACKTRACE] backtrace;
|
||||
}
|
||||
|
||||
alias AllocMap = HashMap { uptr, Allocation };
|
||||
def AllocMap = HashMap(<uptr, Allocation>);
|
||||
|
||||
// A simple tracking allocator.
|
||||
// It tracks allocations using a hash map but
|
||||
// is not compatible with allocators that uses mark()
|
||||
//
|
||||
// It is also embarrassingly single-threaded, so
|
||||
// do not use it to track allocations that cross threads.
|
||||
|
||||
struct TrackingAllocator (Allocator)
|
||||
{
|
||||
Allocator inner_allocator;
|
||||
Allocator* inner_allocator;
|
||||
AllocMap map;
|
||||
usz mem_total;
|
||||
usz allocs_total;
|
||||
usz usage;
|
||||
usz max_usage;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a tracking allocator to wrap (and track) another allocator.
|
||||
|
||||
@param [&inout] allocator : "The allocator to track"
|
||||
*>
|
||||
fn void TrackingAllocator.init(&self, Allocator allocator)
|
||||
/**
|
||||
* 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);
|
||||
self.map.init_new(.allocator = allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
Free this tracking 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()
|
||||
/**
|
||||
* @return "the total allocated memory not yet freed."
|
||||
**/
|
||||
fn usz TrackingAllocator.allocated(&self)
|
||||
{
|
||||
usz allocated = 0;
|
||||
foreach (&allocation : self.map.tvalues()) allocated += allocation.size;
|
||||
@pool()
|
||||
{
|
||||
foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
|
||||
};
|
||||
return allocated;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "the total memory allocated (freed or not)."
|
||||
*>
|
||||
/**
|
||||
* @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)."
|
||||
*>
|
||||
/**
|
||||
* @return "the total number of allocations (freed or not)."
|
||||
**/
|
||||
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
|
||||
|
||||
<*
|
||||
@return "the maximum amount of memory allocated"
|
||||
*>
|
||||
fn usz TrackingAllocator.max_allocated(&self) => self.max_usage;
|
||||
|
||||
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
|
||||
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator* allocator)
|
||||
{
|
||||
return self.map.tvalues();
|
||||
return self.map.value_tlist();
|
||||
}
|
||||
|
||||
<*
|
||||
@return "the number of non-freed allocations."
|
||||
*>
|
||||
/**
|
||||
* @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
|
||||
fn void*! TrackingAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
void* data = self.inner_allocator.acquire(size, init_type, alignment)!;
|
||||
void* data = self.inner_allocator.acquire(size, clear, alignment, offset)!;
|
||||
self.allocs_total++;
|
||||
void*[MAX_BACKTRACE] bt;
|
||||
backtrace::capture_current(&bt);
|
||||
self.map.set((uptr)data, { data, size, bt });
|
||||
self.mem_total += size;
|
||||
self.usage += size;
|
||||
if (self.usage > self.max_usage) self.max_usage = self.usage;
|
||||
if (data)
|
||||
{
|
||||
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.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
void* data = self.inner_allocator.resize(old_pointer, size, alignment)!;
|
||||
self.usage -= self.map[(uptr)old_pointer]!!.size;
|
||||
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.usage += size;
|
||||
if (self.usage > self.max_usage) self.max_usage = self.usage;
|
||||
self.allocs_total++;
|
||||
void* data = self.inner_allocator.resize(old_pointer, size, alignment, offset)!;
|
||||
if (old_pointer)
|
||||
{
|
||||
self.map.remove((uptr)old_pointer);
|
||||
}
|
||||
if (data)
|
||||
{
|
||||
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
|
||||
{
|
||||
usz? old_size = self.map[(uptr)old_pointer].size;
|
||||
if (catch self.map.remove((uptr)old_pointer))
|
||||
if (old_pointer)
|
||||
{
|
||||
unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
|
||||
if (catch self.map.remove((uptr)old_pointer))
|
||||
{
|
||||
assert(false, "Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
|
||||
}
|
||||
}
|
||||
self.usage -= old_size!!;
|
||||
self.inner_allocator.release(old_pointer, is_aligned);
|
||||
}
|
||||
|
||||
@@ -131,104 +130,101 @@ 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()
|
||||
fn void! TrackingAllocator.fprint_report(&self, OutStream* out)
|
||||
{
|
||||
|
||||
usz total = 0;
|
||||
usz entries = 0;
|
||||
bool leaks = false;
|
||||
|
||||
Allocation[] allocs = self.map.tvalues();
|
||||
if (allocs.len)
|
||||
@pool()
|
||||
{
|
||||
if (!allocs[0].backtrace[0])
|
||||
Allocation[] allocs = self.map.value_tlist();
|
||||
if (allocs.len)
|
||||
{
|
||||
io::fprintn(out, "======== Memory Report ========")!;
|
||||
io::fprintn(out, "Size in bytes Address")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
if (!allocs[0].backtrace[0])
|
||||
{
|
||||
entries++;
|
||||
total += allocation.size;
|
||||
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
|
||||
}
|
||||
io::fprintn(out, "===============================")!;
|
||||
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(allocation.backtrace[3:1], mem::temp()).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, "================================== 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, "===================================================================================")!;
|
||||
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
|
||||
}
|
||||
}
|
||||
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)!;
|
||||
io::fprintfn(out, "- Maximum memory usage: %d", self.max_usage)!;
|
||||
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;
|
||||
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 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;
|
||||
BacktraceList backtraces = {};
|
||||
usz end = MAX_BACKTRACE;
|
||||
foreach (j, val : allocation.backtrace)
|
||||
{
|
||||
if (!val)
|
||||
{
|
||||
end = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trace.is_unknown())
|
||||
{
|
||||
io::fprintfn(out, " ??? (in unknown)");
|
||||
continue;
|
||||
BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], mem::temp())!;
|
||||
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);
|
||||
}
|
||||
io::fprintfn(out, " %s (source unavailable)", trace.function);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
module std::core::mem::allocator @if(env::POSIX || env::WIN32);
|
||||
import std::math, std::os::posix, libc, std::bits;
|
||||
import std::core::mem;
|
||||
import std::core::env;
|
||||
|
||||
|
||||
|
||||
// Virtual Memory allocator
|
||||
|
||||
faultdef VMEM_RESERVE_FAILED;
|
||||
|
||||
struct Vmem (Allocator)
|
||||
{
|
||||
VirtualMemory memory;
|
||||
usz allocated;
|
||||
usz pagesize;
|
||||
usz page_pot;
|
||||
usz last_page;
|
||||
usz high_water;
|
||||
VmemOptions options;
|
||||
}
|
||||
|
||||
bitstruct VmemOptions : int
|
||||
{
|
||||
<* Release memory on reset *>
|
||||
bool shrink_on_reset;
|
||||
<* Protect unused pages on reset *>
|
||||
bool protect_unused_pages;
|
||||
<* Overwrite released data with 0xAA *>
|
||||
bool scratch_released_data;
|
||||
}
|
||||
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require !reserve_page_size || math::is_power_of_2(reserve_page_size)
|
||||
@require reserve_page_size <= preferred_size : "The min reserve_page_size size must be less or equal to the preferred size"
|
||||
@require preferred_size >= 1 * mem::KB : "The preferred size must exceed 1 KB"
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY, VMEM_RESERVE_FAILED
|
||||
*>
|
||||
fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOptions options = { true, true, env::COMPILER_SAFE_MODE }, usz min_size = 0)
|
||||
{
|
||||
static usz page_size = 0;
|
||||
if (!page_size) page_size = mem::os_pagesize();
|
||||
if (page_size < reserve_page_size) page_size = reserve_page_size;
|
||||
preferred_size = mem::aligned_offset(preferred_size, page_size);
|
||||
if (!min_size) min_size = max(preferred_size / 1024, 1);
|
||||
VirtualMemory? memory = mem::OUT_OF_MEMORY~;
|
||||
while (preferred_size >= min_size)
|
||||
{
|
||||
memory = vm::virtual_alloc(preferred_size, PROTECTED);
|
||||
// It worked?
|
||||
if (try memory) break;
|
||||
switch (@catch(memory))
|
||||
{
|
||||
case mem::OUT_OF_MEMORY:
|
||||
case vm::RANGE_OVERFLOW:
|
||||
// Try a smaller size.
|
||||
preferred_size /= 2;
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (catch memory) return VMEM_RESERVE_FAILED~;
|
||||
if (page_size > preferred_size) page_size = preferred_size;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(memory.ptr, memory.size);
|
||||
$endif
|
||||
*self = { .memory = memory,
|
||||
.high_water = 0,
|
||||
.pagesize = page_size,
|
||||
.page_pot = page_size.ctz(),
|
||||
.options = options,
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
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*? Vmem.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = self.memory.size;
|
||||
if (size > total_len) return mem::INVALID_ALLOC_SIZE~;
|
||||
void* start_mem = self.memory.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.allocated + VmemHeader.sizeof;
|
||||
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz after = (usz)(mem - start_mem) + size;
|
||||
if (after > total_len) return mem::OUT_OF_MEMORY~;
|
||||
if (init_type == ZERO && self.high_water <= self.allocated)
|
||||
{
|
||||
init_type = NO_ZERO;
|
||||
}
|
||||
protect(self, after)!;
|
||||
VmemHeader* header = mem - VmemHeader.sizeof;
|
||||
header.size = size;
|
||||
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
fn bool Vmem.owns_pointer(&self, void* ptr) @inline
|
||||
{
|
||||
return (uptr)ptr >= (uptr)self.memory.ptr && (uptr)ptr < (uptr)self.memory.ptr + self.memory.size;
|
||||
}
|
||||
<*
|
||||
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*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
if (size > self.memory.size) return mem::INVALID_ALLOC_SIZE~;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
assert(self.owns_pointer(old_pointer), "Pointer originates from a different allocator: %p, not in %p - %p", old_pointer, self.memory.ptr, self.memory.ptr + self.allocated);
|
||||
VmemHeader* header = old_pointer - VmemHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
if (old_size == size) return old_pointer;
|
||||
// Do last allocation and alignment match?
|
||||
if (self.memory.ptr + self.allocated == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
if (old_size > size)
|
||||
{
|
||||
unprotect(self, self.allocated + size - old_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
usz allocated = self.allocated + size - old_size;
|
||||
if (allocated > self.memory.size) return mem::OUT_OF_MEMORY~;
|
||||
protect(self, allocated)!;
|
||||
}
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
}
|
||||
if (old_size > size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(old_pointer + size, old_size - size);
|
||||
$endif
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
}
|
||||
// Otherwise just allocate new memory.
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
assert(size > old_size);
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require ptr != null
|
||||
*>
|
||||
fn void Vmem.release(&self, void* ptr, bool) @dynamic
|
||||
{
|
||||
assert(self.owns_pointer(ptr), "Pointer originates from a different allocator %p.", ptr);
|
||||
VmemHeader* header = ptr - VmemHeader.sizeof;
|
||||
// Reclaim memory if it's the last element.
|
||||
if (ptr + header.size == self.memory.ptr + self.allocated)
|
||||
{
|
||||
unprotect(self, self.allocated - header.size - VmemHeader.sizeof);
|
||||
}
|
||||
}
|
||||
|
||||
fn usz Vmem.mark(&self)
|
||||
{
|
||||
return self.allocated;
|
||||
}
|
||||
|
||||
<*
|
||||
@require mark <= self.allocated : "Invalid mark"
|
||||
*>
|
||||
fn void Vmem.reset(&self, usz mark)
|
||||
{
|
||||
if (mark == self.allocated) return;
|
||||
unprotect(self, mark);
|
||||
}
|
||||
|
||||
fn void Vmem.free(&self)
|
||||
{
|
||||
if (!self.memory.ptr) return;
|
||||
$switch:
|
||||
$case env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(self.memory.ptr, self.memory.size);
|
||||
$case env::COMPILER_SAFE_MODE:
|
||||
((char*)self.memory.ptr)[0:self.allocated] = 0xAA;
|
||||
$endswitch
|
||||
(void)self.memory.destroy();
|
||||
*self = {};
|
||||
}
|
||||
|
||||
// Internal data
|
||||
|
||||
struct VmemHeader @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
macro void? protect(Vmem* mem, usz after) @local
|
||||
{
|
||||
usz shift = mem.page_pot;
|
||||
usz page_after = (after + mem.pagesize - 1) >> shift;
|
||||
usz last_page = mem.last_page;
|
||||
bool over_high_water = mem.high_water < after;
|
||||
if (page_after > last_page)
|
||||
{
|
||||
usz page_start = last_page << shift;
|
||||
usz page_len = (page_after - last_page) << shift;
|
||||
mem.memory.commit(page_start, page_len)!;
|
||||
if (mem.options.protect_unused_pages || over_high_water)
|
||||
{
|
||||
mem.memory.protect(page_start, page_len, READWRITE)!;
|
||||
}
|
||||
mem.last_page = page_after;
|
||||
}
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(mem.memory.ptr + mem.allocated, after - mem.allocated);
|
||||
$endif
|
||||
mem.allocated = after;
|
||||
if (over_high_water) mem.high_water = after;
|
||||
}
|
||||
|
||||
macro void unprotect(Vmem* mem, usz after) @local
|
||||
{
|
||||
usz shift = mem.page_pot;
|
||||
usz last_page = mem.last_page;
|
||||
usz page_after = mem.last_page = (after + mem.pagesize - 1) >> shift;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(mem.memory.ptr + after, mem.allocated - after);
|
||||
$else
|
||||
if (mem.options.scratch_released_data)
|
||||
{
|
||||
mem::set(mem.memory.ptr + after, 0xAA, mem.allocated - after);
|
||||
}
|
||||
$endif
|
||||
if ((mem.options.shrink_on_reset || mem.options.protect_unused_pages) && page_after < last_page)
|
||||
{
|
||||
usz start = page_after << shift;
|
||||
usz len = (last_page - page_after) << shift;
|
||||
if (mem.options.shrink_on_reset) (void)mem.memory.decommit(start, len, false);
|
||||
if (mem.options.protect_unused_pages) (void)mem.memory.protect(start, len, PROTECTED);
|
||||
}
|
||||
mem.allocated = after;
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
module std::core::string::ansi;
|
||||
import std::io;
|
||||
|
||||
enum Ansi : const inline String
|
||||
{
|
||||
RESET = "\e[0m",
|
||||
BOLD = "\e[1m",
|
||||
DIM = "\e[2m",
|
||||
ITALIC = "\e[3m",
|
||||
UNDERLINE = "\e[4m",
|
||||
BLINK = "\e[5m",
|
||||
BLINK_FAST = "\e[6m",
|
||||
INVERT = "\e[7m",
|
||||
HIDDEN = "\e[8m",
|
||||
STRIKETHROUGH = "\e[9m",
|
||||
DOUBLE_UNDER = "\e[21m",
|
||||
NO_DIM = "\e[22m",
|
||||
NO_ITALIC = "\e[23m",
|
||||
NO_UNDERLINE = "\e[24m",
|
||||
NO_BLINK = "\e[25m",
|
||||
NO_INVERT = "\e[27m",
|
||||
NO_HIDDEN = "\e[28m",
|
||||
NO_STRIKETHROUGH = "\e[29m",
|
||||
BLACK = "\e[30m",
|
||||
RED = "\e[31m",
|
||||
GREEN = "\e[32m",
|
||||
YELLOW = "\e[33m",
|
||||
BLUE = "\e[34m",
|
||||
MAGENTA = "\e[35m",
|
||||
CYAN = "\e[36m",
|
||||
WHITE = "\e[37m",
|
||||
DEFAULT = "\e[39m",
|
||||
BRIGHT_BLACK = "\e[90m",
|
||||
BRIGHT_RED = "\e[91m",
|
||||
BRIGHT_GREEN = "\e[92m",
|
||||
BRIGHT_YELLOW = "\e[93m",
|
||||
BRIGHT_BLUE = "\e[94m",
|
||||
BRIGHT_MAGENTA = "\e[95m",
|
||||
BRIGHT_CYAN = "\e[96m",
|
||||
BRIGHT_WHITE = "\e[97m",
|
||||
BG_BLACK = "\e[40m",
|
||||
BG_RED = "\e[41m",
|
||||
BG_GREEN = "\e[42m",
|
||||
BG_YELLOW = "\e[43m",
|
||||
BG_BLUE = "\e[44m",
|
||||
BG_MAGENTA = "\e[45m",
|
||||
BG_CYAN = "\e[46m",
|
||||
BG_WHITE = "\e[47m",
|
||||
BG_DEFAULT = "\e[49m",
|
||||
BG_BRIGHT_BLACK = "\e[100m",
|
||||
BG_BRIGHT_RED = "\e[101m",
|
||||
BG_BRIGHT_GREEN = "\e[102m",
|
||||
BG_BRIGHT_YELLOW = "\e[103m",
|
||||
BG_BRIGHT_BLUE = "\e[104m",
|
||||
BG_BRIGHT_MAGENTA = "\e[105m",
|
||||
BG_BRIGHT_CYAN = "\e[106m",
|
||||
BG_BRIGHT_WHITE = "\e[107m",
|
||||
}
|
||||
|
||||
struct AnsiColor (Printable)
|
||||
{
|
||||
char r, g, b;
|
||||
bool bg;
|
||||
}
|
||||
|
||||
fn usz? AnsiColor.to_format(&self, Formatter* fmt) @dynamic
|
||||
{
|
||||
return fmt.printf("\e[%s8;2;%s;%s;%sm", self.bg ? 4 : 3, self.r, self.g, self.b);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code
|
||||
|
||||
@return `A struct that, when formatted with '%s', sets the foreground or background colour to the specified rgb value`
|
||||
*>
|
||||
fn AnsiColor get_color_rgb(char r, char g, char b, bool bg = false)
|
||||
{
|
||||
return {r, g, b, bg};
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code
|
||||
|
||||
@return `A struct that, when formatted with '%s', sets the foreground or background colour of printed text to the specified rgb value`
|
||||
*>
|
||||
fn AnsiColor get_color(uint rgb, bool bg = false)
|
||||
{
|
||||
return {(char)(rgb >> 16), (char)((rgb & 0x00FF00) >> 8), (char)rgb, bg};
|
||||
}
|
||||
|
||||
<*
|
||||
8-bit color code
|
||||
|
||||
@return `the formatting char for the given background color`
|
||||
*>
|
||||
macro String color_8bit(char $index, bool $bg = false) @const
|
||||
{
|
||||
int $mode = $bg ? 4 : 3;
|
||||
return @sprintf("\e[%s8;5;%sm", $mode, $index);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code
|
||||
|
||||
@return `the string for the given foreground color`
|
||||
*>
|
||||
macro String color_rgb(char $r, char $g, char $b, bool $bg = false) @const
|
||||
{
|
||||
int $mode = $bg ? 4 : 3;
|
||||
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $r, $g, $b);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@require $rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
macro String color(uint $rgb, bool $bg = false) @const
|
||||
{
|
||||
int $mode = $bg ? 4 : 3;
|
||||
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $rgb >> 16, ($rgb & 0xFF00) >> 8, $rgb & 0xFF);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_color(Allocator mem, uint rgb, bool bg = false) @deprecated("use get_color instead")
|
||||
{
|
||||
return make_color_rgb(mem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_tcolor(uint rgb, bool bg = false) @deprecated("use get_color instead")
|
||||
{
|
||||
return make_color_rgb(tmem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_color_rgb(Allocator mem, char r, char g, char b, bool bg = false) @deprecated("use get_color_rgb instead")
|
||||
{
|
||||
return string::format(mem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_tcolor_rgb(char r, char g, char b, bool bg = false) @deprecated("use get_color_rgb instead")
|
||||
{
|
||||
return string::format(tmem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
|
||||
}
|
||||
@@ -1,99 +1,50 @@
|
||||
module std::core::array;
|
||||
import std::collections::pair, std::io;
|
||||
|
||||
<*
|
||||
Returns true if the array contains at least one element, else false
|
||||
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@require $kindof(array) == SLICE || $kindof(array) == ARRAY
|
||||
@require @typematch(array[0], 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
|
||||
@require $kindof(array) == SLICE || $kindof(array) == ARRAY
|
||||
@require @typematch(array[0], element) : "array and element must have the same type"
|
||||
@return "the first index of the element"
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro usz? index_of(array, element)
|
||||
/**
|
||||
* @param [in] array
|
||||
* @param [in] element
|
||||
* @return "the first index of the element"
|
||||
* @return! SearchResult.MISSING
|
||||
**/
|
||||
macro index_of(array, element)
|
||||
{
|
||||
foreach (i, &e : array)
|
||||
{
|
||||
if (*e == element) return i;
|
||||
}
|
||||
return NOT_FOUND~;
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
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 $kindof(array_ptr) == POINTER
|
||||
@require $kindof(*array_ptr) == VECTOR || $kindof(*array_ptr) == ARRAY
|
||||
@require $kindof((*array_ptr)[0]) == VECTOR || $kindof((*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 usz? rindex_of(array, element)
|
||||
/**
|
||||
* @param [in] array
|
||||
* @param [in] element
|
||||
* @return "the last index of the element"
|
||||
* @return! SearchResult.MISSING
|
||||
**/
|
||||
macro rindex_of(array, element)
|
||||
{
|
||||
foreach_r (i, &e : array)
|
||||
{
|
||||
if (*e == element) return i;
|
||||
}
|
||||
return NOT_FOUND~;
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
|
||||
|
||||
@param [in] arr1
|
||||
@param [in] arr2
|
||||
@param [&inout] allocator : "The allocator to use, default is the heap allocator"
|
||||
@require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY
|
||||
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY
|
||||
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type"
|
||||
@ensure result.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro concat(Allocator allocator, arr1, arr2) @nodiscard
|
||||
/**
|
||||
* Concatenate two arrays or subarrays, returning a subarray 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) == SUBARRAY || @typekind(arr1) == ARRAY
|
||||
* @require @typekind(arr2) == SUBARRAY || @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_new(arr1, arr2, Allocator* allocator = mem::heap())
|
||||
{
|
||||
var $Type = $typeof(arr1[0]);
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len);
|
||||
$Type[] result = allocator.new_array($Type, arr1.len + arr2.len);
|
||||
if (arr1.len > 0)
|
||||
{
|
||||
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $Type.alignof, $Type.alignof);
|
||||
@@ -105,449 +56,15 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
|
||||
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 $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY
|
||||
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY
|
||||
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type"
|
||||
@ensure return.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
|
||||
|
||||
|
||||
<*
|
||||
Apply a reduction/folding operation to an iterable type. This walks along the input array
|
||||
and applies an `#operation` to each value, returning it to the `identity` (or "accumulator")
|
||||
base value.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
int[] my_slice = { 1, 8, 12 };
|
||||
int folded = array::@reduce(my_slice, 2, fn (i, e) => i * e);
|
||||
assert(folded == (2 * 1 * 8 * 12));
|
||||
```
|
||||
|
||||
Notice how the given `identity` value started the multiplication chain at 2. When enumerating
|
||||
`my_slice`, each element is accumulated onto the `identity` value with each sequential iteration.
|
||||
```
|
||||
i = 2; // identity value
|
||||
i *= 1; // my_slice[0]
|
||||
i *= 8; // my_slice[1]
|
||||
i *= 12; // my_slice[2]
|
||||
```
|
||||
|
||||
@param [in] array
|
||||
@param identity
|
||||
@param #operation : "The reduction/folding lambda function or function pointer to apply."
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@reduce_fn(array, identity)) $func = #operation) : "Invalid lambda or function pointer type"
|
||||
*>
|
||||
macro @reduce(array, identity, #operation)
|
||||
{
|
||||
$typefrom(@reduce_fn(array, identity)) $func = #operation;
|
||||
foreach (index, element : array) identity = $func(identity, element, index);
|
||||
return identity;
|
||||
}
|
||||
|
||||
<*
|
||||
Apply a summation operator (+) to an identity value across a span of array elements
|
||||
and return the final accumulated result.
|
||||
|
||||
@pure
|
||||
|
||||
@param [in] array
|
||||
@param identity_value : "The base accumulator value to use for the sum"
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined(array[0] + array[0]) : "Array element type must implement the '+' operator"
|
||||
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
|
||||
*>
|
||||
macro @sum(array, identity_value = 0)
|
||||
{
|
||||
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc + e);
|
||||
}
|
||||
|
||||
<*
|
||||
Apply a product operator (*) to an identity value across a span of array elements
|
||||
and return the final accumulated result.
|
||||
|
||||
@pure
|
||||
|
||||
@param [in] array
|
||||
@param identity_value : "The base accumulator value to use for the product"
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined(array[0] * array[0]) : "Array element type must implement the '*' operator"
|
||||
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
|
||||
*>
|
||||
macro @product(array, identity_value = 1)
|
||||
{
|
||||
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc * e);
|
||||
}
|
||||
|
||||
<*
|
||||
Applies a given predicate function to each element of an array and returns a new
|
||||
array of `usz` values, each element representing an index within the original array
|
||||
where the predicate returned `true`.
|
||||
|
||||
The `.len` value of the returned array can also be used to quickly identify how many
|
||||
input array elements matched the predicate.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
int[] arr = { 0, 20, 4, 30 };
|
||||
int[] matched_indices = array::@indices_of(mem, arr, fn (u, a) => a > 10);
|
||||
```
|
||||
|
||||
The `matched_indices` variable should contain a dynamically-allocated array of `[1, 3]`,
|
||||
and thus its count indicates that 2 of the 4 elements matched the predicate condition.
|
||||
|
||||
@param [&inout] allocator
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro usz[] @indices_of(Allocator allocator, array, #predicate)
|
||||
{
|
||||
usz[] results = allocator::new_array(allocator, usz, lengthof(array));
|
||||
usz matches;
|
||||
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array)
|
||||
{
|
||||
if ($predicate(element, index)) results[matches++] = index;
|
||||
}
|
||||
|
||||
return results[:matches];
|
||||
}
|
||||
|
||||
<*
|
||||
Array `@indices_of` using the temp allocator.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro usz[] @tindices_of(array, #predicate)
|
||||
{
|
||||
return @indices_of(tmem, array, #predicate);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Applies a predicate function to each element of an input array and returns a new array
|
||||
containing shallow copies of _only_ the elements for which the predicate function returned
|
||||
a `true` value.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
int[] my_arr = { 1, 2, 4, 10, 11, 45 };
|
||||
int[] evens = array::@filter(mem, my_arr, fn (e, u) => !(e % 2));
|
||||
assert(evens == (int[]){2, 4, 10 });
|
||||
```
|
||||
|
||||
@param [&inout] allocator
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro @filter(Allocator allocator, array, #predicate) @nodiscard
|
||||
{
|
||||
var $InnerType = $typeof(array[0]);
|
||||
|
||||
usz[] matched_indices = @indices_of(allocator, array, #predicate);
|
||||
defer allocator::free(allocator, matched_indices.ptr); // can free this upon leaving this call
|
||||
|
||||
if (!matched_indices.len) return ($InnerType[]){};
|
||||
|
||||
$InnerType[] result = allocator::new_array(allocator, $InnerType, matched_indices.len);
|
||||
|
||||
foreach (i, index : matched_indices) result[i] = array[index];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Array `@filter` using the temp allocator.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro @tfilter(array, #predicate) @nodiscard
|
||||
{
|
||||
return @filter(tmem, array, #predicate);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns `true` if _any_ element of the input array returns `true` when
|
||||
the `#predicate` function is applied.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro bool @any(array, #predicate)
|
||||
{
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array) if ($predicate(element, index)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns `true` if _all_ elements of the input array return `true` when
|
||||
the `#predicate` function is applied.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro bool @all(array, #predicate)
|
||||
{
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array) if (!$predicate(element, index)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Zip together two separate arrays/slices into a single array of Pairs or return values. Values will
|
||||
be collected up to the length of the shorter array if `fill_with` is left undefined; otherwise, they
|
||||
will be collected up to the length of the LONGER array, with missing values in the shorter array being
|
||||
assigned to the value of `fill_with`. Return array elements do not have to be of the same type.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
uint[] chosen_session_ids = server::get_random_sessions(instance)[:128];
|
||||
String[200] refreshed_session_keys = prng::new_keys_batch();
|
||||
|
||||
Pair { uint, String }[] sessions_meta = array::zip(mem, chosen_session_ids, refreshed_session_keys);
|
||||
// The resulting Pair{}[] slice is then length of the shortest of the two arrays, so 128.
|
||||
|
||||
foreach (i, &sess : sessions:meta) {
|
||||
// distribute new session keys to associated instance IDs
|
||||
}
|
||||
```
|
||||
|
||||
Or:
|
||||
```c3
|
||||
String[] client_names = server::online_usernames(instance);
|
||||
uint128[] session_ids = server::user_keys();
|
||||
|
||||
// in this example, we 'know' ahead of time that 'session_ids' can only ever be SHORTER
|
||||
// than 'client_names', but never longer, because it's possible new users have logged
|
||||
// in without getting whatever this 'session ID' is delegated to them.
|
||||
Pair { String, uint128 }[] zipped = array::tzip(client_names, session_ids, fill_with: uint128.max);
|
||||
|
||||
server::refresh_session_keys_by_pair(zipped)!;
|
||||
```
|
||||
|
||||
### When an `operation` is supplied...
|
||||
Apply an operation to each element of two slices or arrays and return the results of
|
||||
each operation into a newly allocated array.
|
||||
|
||||
This essentially combines Iterable1 with Iterable2 using the `operation` functor.
|
||||
|
||||
See the functional `zipWith` construct, which has a more appropriate name than, e.g., `map`;
|
||||
a la: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#v:zipWith
|
||||
|
||||
Similar to "normal" `zip`, this macro pads the shorter input array with a given `fill_with`, or
|
||||
an empty value if one isn't supplied. This `fill_with` is supplied to the `operation` functor
|
||||
_BEFORE_ calculating its result while zipping.
|
||||
|
||||
For example: a functor of `fn char (char a, char b) => a + b` with a `fill_with` of 7,
|
||||
where the `left` array is the shorter iterable, will put 7 into that lambda in each place
|
||||
where `left` is being filled in during the zip operation.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use; default is the heap allocator."
|
||||
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
|
||||
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
|
||||
|
||||
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
|
||||
@require @is_valid_operation(left, right, ...#operation) : "The operator must take two parameters matching the elements of the left and right side"
|
||||
@require @is_valid_fill(left, right, ...fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
|
||||
*>
|
||||
macro @zip(Allocator allocator, left, right, #operation = ..., fill_with = ...) @nodiscard
|
||||
{
|
||||
var $LeftType = $typeof(left[0]);
|
||||
var $RightType = $typeof(right[0]);
|
||||
|
||||
var $Type = Pair { $LeftType, $RightType };
|
||||
bool $is_op = $defined(#operation);
|
||||
$if $is_op:
|
||||
$Type = $typeof(#operation).returns;
|
||||
$endif
|
||||
|
||||
usz left_len = lengthof(left);
|
||||
usz right_len = lengthof(right);
|
||||
|
||||
$LeftType left_fill;
|
||||
$RightType right_fill;
|
||||
usz result_len = min(left_len, right_len);
|
||||
$if $defined(fill_with):
|
||||
switch
|
||||
{
|
||||
case left_len > right_len:
|
||||
$if !$defined(($RightType)fill_with):
|
||||
unreachable();
|
||||
$else
|
||||
right_fill = ($RightType)fill_with;
|
||||
result_len = left_len;
|
||||
$endif
|
||||
case left_len < right_len:
|
||||
$if !$defined(($LeftType)fill_with):
|
||||
unreachable();
|
||||
$else
|
||||
left_fill = ($LeftType)fill_with;
|
||||
result_len = right_len;
|
||||
$endif
|
||||
}
|
||||
$endif
|
||||
|
||||
if (result_len == 0) return ($Type[]){};
|
||||
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, result_len);
|
||||
|
||||
foreach (idx, &item : result)
|
||||
{
|
||||
$if $is_op:
|
||||
var $LambdaType = $typeof(fn $Type ($LeftType a, $RightType b) => ($Type){});
|
||||
$LambdaType $operation = ($LambdaType)#operation;
|
||||
$LeftType lval = idx >= left_len ? left_fill : left[idx];
|
||||
$RightType rval = idx >= right_len ? right_fill : right[idx];
|
||||
*item = $operation(lval, rval);
|
||||
$else
|
||||
*item = {
|
||||
idx >= left_len ? left_fill : left[idx],
|
||||
idx >= right_len ? right_fill : right[idx]
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Array 'zip' using the temp allocator.
|
||||
|
||||
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
|
||||
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
|
||||
|
||||
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
|
||||
@require @is_valid_operation(left, right, ...#operation) : "The operator must take two parameters matching the elements of the left and right side"
|
||||
@require @is_valid_fill(left, right, ...fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
|
||||
*>
|
||||
macro @tzip(left, right, #operation = ..., fill_with = ...) @nodiscard
|
||||
{
|
||||
return @zip(tmem, left, right, #operation: ...#operation, fill_with: ...fill_with);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Apply an operation to each element of two slices or arrays and store the results of
|
||||
each operation into the 'left' value.
|
||||
|
||||
This is useful because no memory allocations are required in order to perform the operation.
|
||||
|
||||
A good example of using this might be using algorithmic transformations on data in-place:
|
||||
```
|
||||
char[] partial_cipher = get_next_plaintext_block();
|
||||
|
||||
array::@zip_into(
|
||||
partial_cipher[ENCRYPT_OFFSET:BASE_KEY.len],
|
||||
BASE_KEY,
|
||||
fn char (char a, char b) => a ^ (b * 5) % 37
|
||||
);
|
||||
```
|
||||
|
||||
This parameterizes the lambda function with left (`partial_cipher`) and right (`BASE_KEY`) slice
|
||||
elements and stores the end result in-place within the left slice. This is in contrast to a
|
||||
regular `zip_with` which will create a cloned final result and return it.
|
||||
|
||||
@param [inout] left : `Slice to store results of applied functor/operation.`
|
||||
@param [in] right : `Slice to apply in the functor/operation.`
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
|
||||
@require @is_valid_list(left) : "Expected a valid list"
|
||||
@require @is_valid_list(right) : "Expected a valid list"
|
||||
@require lengthof(right) >= lengthof(left) : `Right side length must be >= the destination (left) side length; consider using a sub-array of data for the assignment.`
|
||||
@require $defined($typefrom(@zip_into_fn(left, right)) x = #operation) : "The functor must use the same types as the `left` and `right` inputs, and return a value of the `left` type."
|
||||
*>
|
||||
macro @zip_into(left, right, #operation)
|
||||
{
|
||||
$typefrom(@zip_into_fn(left, right)) $operation = #operation;
|
||||
|
||||
foreach (i, &v : left) *v = $operation(left[i], right[i]);
|
||||
}
|
||||
|
||||
|
||||
// --- helper functions
|
||||
module std::core::array @private;
|
||||
|
||||
|
||||
macro typeid @predicate_fn(#array) @const
|
||||
{
|
||||
return $typeof(fn bool ($typeof(#array[0]) a, usz index = 0) => true).typeid;
|
||||
}
|
||||
|
||||
macro typeid @reduce_fn(#array, #identity) @const
|
||||
{
|
||||
return @typeid(fn $typeof(#identity) ($typeof(#identity) i, $typeof(#array[0]) a, usz index = 0) => i);
|
||||
}
|
||||
|
||||
macro typeid @zip_into_fn(#left, #right) @const
|
||||
{
|
||||
return @typeid(fn $typeof(#left[0]) ($typeof(#left[0]) l, $typeof(#right[0]) r) => l);
|
||||
}
|
||||
|
||||
macro bool @is_valid_operation(#left, #right, #operation = ...) @const
|
||||
{
|
||||
$switch:
|
||||
$case !$defined(#operation):
|
||||
return true;
|
||||
$case $kindof(#operation) != FUNC:
|
||||
return false;
|
||||
$default:
|
||||
return $defined(#operation(#left[0], #right[0]));
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool @is_valid_list(#expr) @const
|
||||
{
|
||||
return $defined(#expr[0], lengthof(#expr));
|
||||
}
|
||||
|
||||
macro bool @is_valid_fill(left, right, fill_with = ...)
|
||||
{
|
||||
$if !$defined(fill_with):
|
||||
return true;
|
||||
$else
|
||||
usz left_len = lengthof(left);
|
||||
usz right_len = lengthof(right);
|
||||
if (left_len == right_len) return true;
|
||||
return left_len > right_len ? $defined(($typeof(right[0]))fill_with) : $defined(($typeof(left[0]))fill_with);
|
||||
$endif
|
||||
}
|
||||
/**
|
||||
* Concatenate two arrays or subarrays, returning a subarray containing the concatenation of them,
|
||||
* allocated using the temp allocator.
|
||||
*
|
||||
* @param [in] arr1
|
||||
* @param [in] arr2
|
||||
* @require @typekind(arr1) == SUBARRAY || @typekind(arr1) == ARRAY
|
||||
* @require @typekind(arr2) == SUBARRAY || @typekind(arr2) == ARRAY
|
||||
* @require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
|
||||
* @ensure result.len == arr1.len + arr2.len
|
||||
**/
|
||||
macro tconcat(arr1, arr2) => concat(arr1, arr2, mem::temp());
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
<*
|
||||
This module contains utils for handling ASCII characters. They only operate on
|
||||
characters corresponding to 0-127.
|
||||
*>
|
||||
module std::core::ascii;
|
||||
|
||||
macro bool @is_lower(c) => ASCII_LOOKUP[c].lower; // Is a-z
|
||||
macro bool @is_upper(c) => ASCII_LOOKUP[c].upper; // Is A-Z
|
||||
macro bool @is_digit(c) => ASCII_LOOKUP[c].digit; // Is 0-9
|
||||
macro bool @is_bdigit(c) => ASCII_LOOKUP[c].bin_digit; // Is 0-1
|
||||
macro bool @is_odigit(c) => ASCII_LOOKUP[c].oct_digit; // Is 0-7
|
||||
macro bool @is_xdigit(c) => ASCII_LOOKUP[c].hex_digit; // Is 0-9 or a-f or A-F
|
||||
macro bool @is_alpha(c) => ASCII_LOOKUP[c].alpha; // Is a-z or A-Z
|
||||
macro bool @is_print(c) => ASCII_LOOKUP[c].printable; // Is a printable character (space or higher and < 127
|
||||
macro bool @is_graph(c) => ASCII_LOOKUP[c].graph; // Does it show any graphics (printable but not space)
|
||||
macro bool @is_space(c) => ASCII_LOOKUP[c].space; // Is it a space character: space, tab, linefeed etc
|
||||
macro bool @is_alnum(c) => ASCII_LOOKUP[c].alphanum; // Is it alpha or digit
|
||||
macro bool @is_punct(c) => ASCII_LOOKUP[c].punct; // Is it "graph" but not digit or letter
|
||||
macro bool @is_blank(c) => ASCII_LOOKUP[c].blank; // Is it a blank space: space or tab
|
||||
macro bool @is_cntrl(c) => ASCII_LOOKUP[c].control; // Is it a control character: before space or 127
|
||||
macro char @to_lower(c) => c + TO_LOWER[c]; // Convert A-Z to a-z if found
|
||||
macro char @to_upper(c) => c - TO_UPPER[c]; // Convert a-z to A-Z if found
|
||||
|
||||
fn bool is_lower(char c) => @is_lower(c); // Is a-z
|
||||
fn bool is_upper(char c) => @is_upper(c); // Is A-Z
|
||||
fn bool is_digit(char c) => @is_digit(c); // Is 0-9
|
||||
fn bool is_bdigit(char c) => @is_bdigit(c); // Is 0-1
|
||||
fn bool is_odigit(char c) => @is_odigit(c); // Is 0-7
|
||||
fn bool is_xdigit(char c) => @is_xdigit(c); // Is 0-9 or a-f or A-F
|
||||
fn bool is_alpha(char c) => @is_alpha(c); // Is a-z or A-Z
|
||||
fn bool is_print(char c) => @is_print(c); // Is a printable character (space or higher and < 127
|
||||
fn bool is_graph(char c) => @is_graph(c); // Does it show any graphics (printable but not space)
|
||||
fn bool is_space(char c) => @is_space(c); // Is it a space character: space, tab, linefeed etc
|
||||
fn bool is_alnum(char c) => @is_alnum(c); // Is it alpha or digit
|
||||
fn bool is_punct(char c) => @is_punct(c); // Is it "graph" but not digit or letter
|
||||
fn bool is_blank(char c) => @is_blank(c); // Is it a blank space: space or tab
|
||||
fn bool is_cntrl(char c) => @is_cntrl(c); // Is it a control character: before space or 127
|
||||
fn char to_lower(char c) => @to_lower(c); // Convert A-Z to a-z if found
|
||||
fn char to_upper(char c) => @to_upper(c); // Convert a-z to A-Z if found
|
||||
|
||||
// The following methods are macro methods for the same functions
|
||||
macro bool char.is_lower(char c) => @is_lower(c);
|
||||
macro bool char.is_upper(char c) => @is_upper(c);
|
||||
macro bool char.is_digit(char c) => @is_digit(c);
|
||||
macro bool char.is_bdigit(char c) => @is_bdigit(c);
|
||||
macro bool char.is_odigit(char c) => @is_odigit(c);
|
||||
macro bool char.is_xdigit(char c) => @is_xdigit(c);
|
||||
macro bool char.is_alpha(char c) => @is_alpha(c);
|
||||
macro bool char.is_print(char c) => @is_print(c);
|
||||
macro bool char.is_graph(char c) => @is_graph(c);
|
||||
macro bool char.is_space(char c) => @is_space(c);
|
||||
macro bool char.is_alnum(char c) => @is_alnum(c);
|
||||
macro bool char.is_punct(char c) => @is_punct(c);
|
||||
macro bool char.is_blank(char c) => @is_blank(c);
|
||||
macro bool char.is_cntrl(char c) => @is_cntrl(c);
|
||||
macro char char.to_lower(char c) => @to_lower(c);
|
||||
macro char char.to_upper(char c) => @to_upper(c);
|
||||
|
||||
<*
|
||||
Convert a-f/A-F/0-9 to the appropriate hex value.
|
||||
|
||||
@require c.is_xdigit()
|
||||
@ensure return >= 0 && return <= 15
|
||||
*>
|
||||
macro char char.from_hex(char c) => HEX_VALUE[c];
|
||||
|
||||
<*
|
||||
Bitstruct containing the different properties of a character
|
||||
*>
|
||||
bitstruct CharType : ushort @private
|
||||
{
|
||||
bool lower;
|
||||
bool upper;
|
||||
bool digit;
|
||||
bool bin_digit;
|
||||
bool hex_digit;
|
||||
bool oct_digit;
|
||||
bool alpha;
|
||||
bool alphanum;
|
||||
bool space;
|
||||
bool printable;
|
||||
bool blank;
|
||||
bool punct;
|
||||
bool control;
|
||||
bool graph;
|
||||
}
|
||||
|
||||
const CharType[256] ASCII_LOOKUP @private = {
|
||||
[0..31] = { .control },
|
||||
[9..13] = { .control, .space },
|
||||
['\t'] = { .control, .space, .blank },
|
||||
[' '] = { .space, .printable, .blank },
|
||||
[33..126] = { .printable, .graph, .punct },
|
||||
['0'..'9'] = { .printable, .graph, .alphanum, .hex_digit, .digit },
|
||||
['2'..'7'] = { .printable, .graph, .alphanum, .hex_digit, .digit, .oct_digit },
|
||||
['0'..'1'] = { .printable, .graph, .alphanum, .hex_digit, .digit, .oct_digit, .bin_digit },
|
||||
['A'..'Z'] = { .printable, .graph, .alphanum, .alpha, .upper },
|
||||
['A'..'F'] = { .printable, .graph, .alphanum, .alpha, .upper, .hex_digit },
|
||||
['a'..'z'] = { .printable, .graph, .alphanum, .alpha, .lower },
|
||||
['a'..'f'] = { .printable, .graph, .alphanum, .alpha, .lower, .hex_digit },
|
||||
[127] = { .control },
|
||||
};
|
||||
|
||||
const char[256] HEX_VALUE = {
|
||||
['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4,
|
||||
['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9,
|
||||
['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14,
|
||||
['F'] = 15, ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13,
|
||||
['e'] = 14, ['f'] = 15
|
||||
};
|
||||
|
||||
const char[256] TO_UPPER @private = { ['a'..'z'] = 'a' - 'A' };
|
||||
const char[256] TO_LOWER @private = { ['A'..'Z'] = 'a' - 'A' };
|
||||
|
||||
typedef AsciiCharset = uint128;
|
||||
|
||||
macro AsciiCharset @create_set(String $string) @const
|
||||
{
|
||||
AsciiCharset $set;
|
||||
$foreach $c : $string:
|
||||
$set |= 1ULL << $c;
|
||||
$endforeach
|
||||
return $set;
|
||||
}
|
||||
|
||||
fn AsciiCharset create_set(String string)
|
||||
{
|
||||
AsciiCharset set;
|
||||
foreach (c : string) set |= (AsciiCharset)1ULL << c;
|
||||
return set;
|
||||
}
|
||||
|
||||
macro bool AsciiCharset.@contains($set, char $c) @const => !!($c < 128) & !!($set & (AsciiCharset)(1ULL << $c));
|
||||
|
||||
macro AsciiCharset @combine_sets(AsciiCharset $first, AsciiCharset... $sets) @const
|
||||
{
|
||||
var $res = $first;
|
||||
$foreach $c : $sets:
|
||||
$res |= $c;
|
||||
$endforeach
|
||||
return $res;
|
||||
}
|
||||
fn AsciiCharset combine_sets(AsciiCharset first, AsciiCharset... sets)
|
||||
{
|
||||
foreach (c : sets) first |= c;
|
||||
return first;
|
||||
}
|
||||
|
||||
macro bool AsciiCharset.contains(set, char c) => !!(c < 128) & !!(set & (AsciiCharset)(1ULL << c));
|
||||
|
||||
const AsciiCharset WHITESPACE_SET = @create_set("\t\n\v\f\r ");
|
||||
const AsciiCharset NUMBER_SET = @create_set("0123456789");
|
||||
const AsciiCharset ALPHA_UPPER_SET = @create_set("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
const AsciiCharset ALPHA_LOWER_SET = @create_set("abcdefghijklmnopqrstuvwxyz");
|
||||
const AsciiCharset ALPHA_SET = @combine_sets(ALPHA_UPPER_SET, ALPHA_LOWER_SET);
|
||||
const AsciiCharset ALPHANUMERIC_SET = @combine_sets(ALPHA_SET, NUMBER_SET);
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2023-2025 Christoffer Lerno and contributors. All rights reserved.
|
||||
// Copyright (c) 2023 Christoffer Lerno and contributors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::bitorder;
|
||||
@@ -87,100 +87,94 @@ 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"
|
||||
@require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is too short to contain value"
|
||||
*>
|
||||
/**
|
||||
* @require is_array_or_sub_of_char(bytes) "argument must be an array, a pointer to an array or a subarray of char"
|
||||
* @require is_bitorder($Type) "type must be a bitorder integer"
|
||||
**/
|
||||
macro read(bytes, $Type)
|
||||
{
|
||||
char *ptr;
|
||||
$switch $kindof(bytes):
|
||||
$case POINTER:
|
||||
ptr = bytes;
|
||||
$default:
|
||||
ptr = bytes[..].ptr;
|
||||
char[] s;
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$endswitch
|
||||
return bitcast(mem::load((char[$Type.sizeof]*)ptr, $align: 1), $Type).val;
|
||||
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"
|
||||
@require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is not sufficent to hold value"
|
||||
*>
|
||||
/**
|
||||
* @require is_arrayptr_or_sub_of_char(bytes) "argument must be a pointer to an array or a subarray of char"
|
||||
* @require is_bitorder($Type) "type must be a bitorder integer"
|
||||
**/
|
||||
macro write(x, bytes, $Type)
|
||||
{
|
||||
char *ptr;
|
||||
$switch $kindof(bytes):
|
||||
$case POINTER:
|
||||
ptr = bytes;
|
||||
$default:
|
||||
ptr = bytes[..].ptr;
|
||||
char[] s;
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$endswitch
|
||||
mem::store(($typeof(x)*)ptr, bitcast(x, $Type).val, 1);
|
||||
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
|
||||
}
|
||||
|
||||
macro bool is_bitorder($Type)
|
||||
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;
|
||||
$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) @deprecated("Use @is_array_or_slice_of_char")
|
||||
macro bool is_array_or_sub_of_char(bytes)
|
||||
{
|
||||
return @is_array_or_slice_of_char(bytes);
|
||||
}
|
||||
|
||||
macro bool @is_array_or_slice_of_char(#bytes) @const
|
||||
{
|
||||
var $Type = $typeof(#bytes);
|
||||
$switch $Type.kindof:
|
||||
$case POINTER:
|
||||
typeid $inner = $Type.inner;
|
||||
return $inner.kindof == ARRAY &&& $inner.inner == char.typeid;
|
||||
$case ARRAY:
|
||||
$case SLICE:
|
||||
return $Type.inner == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$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 SUBARRAY:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
return $Inner.typeid == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_arrayptr_or_slice_of_char(bytes) @deprecated("Use @is_arrayptr_or_slice_of_char")
|
||||
macro bool is_arrayptr_or_sub_of_char(bytes)
|
||||
{
|
||||
return @is_arrayptr_or_slice_of_char(bytes);
|
||||
}
|
||||
|
||||
macro bool @is_arrayptr_or_slice_of_char(#bytes) @const
|
||||
{
|
||||
var $Type = $typeof(#bytes);
|
||||
$switch $Type.kindof:
|
||||
$case POINTER:
|
||||
typeid $inner = $Type.inner;
|
||||
return $inner.kindof == ARRAY &&& $inner.inner == char.typeid;
|
||||
$case SLICE:
|
||||
return $Type.inner == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$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 SUBARRAY:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
return $Inner.typeid == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
}
|
||||
@@ -1,312 +1,162 @@
|
||||
// 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;
|
||||
import std::hash;
|
||||
|
||||
|
||||
<*
|
||||
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 convertible.
|
||||
|
||||
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)
|
||||
/**
|
||||
* Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
|
||||
**/
|
||||
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 @deprecated("Use `#arg = ...` instead.") = null;
|
||||
|
||||
typedef EmptySlot = void*;
|
||||
macro bool @is_empty_macro_slot(#arg) @const @builtin
|
||||
@deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.")
|
||||
=> $typeof(#arg) == EmptySlot;
|
||||
macro bool @is_valid_macro_slot(#arg) @const @builtin
|
||||
@deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.")
|
||||
=> $typeof(#arg) != EmptySlot;
|
||||
|
||||
<*
|
||||
Returns a random value at compile time.
|
||||
|
||||
@ensure return >= 0.0 && return < 1.0
|
||||
@return "A compile time random"
|
||||
*>
|
||||
macro @rnd() @const @builtin => $$rnd();
|
||||
|
||||
/*
|
||||
Use `NO_MORE_ELEMENT` when reading the end of an iterator, or accessing a result out of bounds.
|
||||
*/
|
||||
faultdef NO_MORE_ELEMENT @builtin;
|
||||
|
||||
/*
|
||||
Use `NOT_FOUND` when trying to return a value from some collection but the element is missing.
|
||||
*/
|
||||
faultdef NOT_FOUND @builtin;
|
||||
|
||||
/*
|
||||
Use `TYPE_MISMATCH` when an attempt at conversion fails.
|
||||
*/
|
||||
faultdef TYPE_MISMATCH @builtin;
|
||||
|
||||
/*
|
||||
Use `CAPACITY_EXCEEDED` when trying to add to a bounded list or similar.
|
||||
*/
|
||||
faultdef CAPACITY_EXCEEDED @builtin;
|
||||
/*
|
||||
Use `NOT_IMPLEMENTED` when something is conditionally available.
|
||||
*/
|
||||
faultdef NOT_IMPLEMENTED @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 $defined(#variable = #variable) : `Expected an actual variable`
|
||||
*>
|
||||
macro void @scope(#variable; @body) @builtin
|
||||
/**
|
||||
* Use `SearchResult` when trying to return a value from some collection but the element is missing.
|
||||
**/
|
||||
fault SearchResult
|
||||
{
|
||||
var temp = #variable;
|
||||
defer #variable = temp;
|
||||
MISSING
|
||||
}
|
||||
|
||||
/**
|
||||
* Use `CastResult` when an attempt at conversion fails.
|
||||
**/
|
||||
fault CastResult
|
||||
{
|
||||
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
|
||||
{
|
||||
var temp = *variable;
|
||||
defer *variable = temp;
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
Swap two variables
|
||||
@require $defined(#a = #b, #b = #a) : `The values must be mutually assignable`
|
||||
*>
|
||||
macro void @swap(#a, #b) @builtin
|
||||
/**
|
||||
* Swap two variables
|
||||
* @require $assignable(*b, $typeof(*a)) && $assignable(*a, $typeof(*b))
|
||||
**/
|
||||
macro void @swap(&a, &b) @builtin
|
||||
{
|
||||
var temp = #a;
|
||||
#a = #b;
|
||||
#b = temp;
|
||||
var temp = *a;
|
||||
*a = *b;
|
||||
*b = temp;
|
||||
}
|
||||
|
||||
macro usz bitsizeof($Type) @builtin @const => $Type.sizeof * 8u;
|
||||
|
||||
macro usz @bitsizeof(#expr) @builtin @const => $sizeof(#expr) * 8u;
|
||||
|
||||
<*
|
||||
Compile-time check for whether a set of constants contains a certain expression.
|
||||
|
||||
@param #needle : "The expression whose value should be located."
|
||||
*>
|
||||
macro bool @in(#needle, ...) @builtin @const
|
||||
/**
|
||||
* Convert an `any` type to a type, returning an failure if there is a type mismatch.
|
||||
*
|
||||
* @param v `the any to convert to the given type.`
|
||||
* @param $Type `the type to convert to`
|
||||
* @return `The any.ptr converted to its type.`
|
||||
* @ensure @typeis(return, $Type*)
|
||||
* @return! CastResult.TYPE_MISMATCH
|
||||
**/
|
||||
macro anycast(any* v, $Type) @builtin
|
||||
{
|
||||
$for var $x = 0; $x < $vacount; $x++:
|
||||
$assert $defined(#needle == $vaconst[$x])
|
||||
: "Index %s: types '%s' (needle) and '%s' are not equatable", $x, $typeof(#needle), $typeof($vaconst[$x]);
|
||||
$if #needle == $vaconst[$x]: return true; $endif
|
||||
$endfor
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
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 $typeof(return) == $Type*
|
||||
@return? TYPE_MISMATCH
|
||||
*>
|
||||
macro anycast(any v, $Type) @builtin
|
||||
{
|
||||
if (v.type != $Type.typeid) return TYPE_MISMATCH~;
|
||||
if (v.type != $Type.typeid) return CastResult.TYPE_MISMATCH?;
|
||||
return ($Type*)v.ptr;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "The value in the pointer"
|
||||
@return? TYPE_MISMATCH
|
||||
*>
|
||||
macro any.to(self, $Type)
|
||||
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE)
|
||||
{
|
||||
if (self.type != $Type.typeid) return TYPE_MISMATCH~;
|
||||
return *($Type*)self.ptr;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.type == $Type : "The 'any' contained an unexpected type."
|
||||
@return "The value in the pointer"
|
||||
*>
|
||||
macro any.as(self, $Type)
|
||||
{
|
||||
return *($Type*)self.ptr;
|
||||
}
|
||||
|
||||
macro bool @assignable_to(#foo, $Type) @const @builtin @deprecated("use '$defined($Type x = #foo)'") => $defined(*&&($Type){} = #foo);
|
||||
|
||||
macro @addr(#val) @builtin
|
||||
{
|
||||
$if $defined(&#val):
|
||||
return &#val;
|
||||
$else
|
||||
return &&#val;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro typeid @typeid(#value) @const @builtin
|
||||
{
|
||||
return $typeof(#value).typeid;
|
||||
}
|
||||
|
||||
macro TypeKind @typekind(#value) @const @builtin @deprecated("Use `$kindof(#value)`.")
|
||||
{
|
||||
return $kindof(#value);
|
||||
}
|
||||
|
||||
macro bool @typeis(#value, $Type) @const @builtin @deprecated("Use `$typeof(#value) == $Type` instead.")
|
||||
{
|
||||
return $typeof(#value).typeid == $Type.typeid;
|
||||
}
|
||||
|
||||
|
||||
fn bool print_backtrace(String message, int backtraces_to_ignore, void *added_backtrace = null) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
|
||||
{
|
||||
void*[256] buffer;
|
||||
void*[] backtraces = backtrace::capture_current(&buffer);
|
||||
if (added_backtrace)
|
||||
@pool()
|
||||
{
|
||||
backtraces[++backtraces_to_ignore] = added_backtrace;
|
||||
}
|
||||
@stack_mem(4096; Allocator mem)
|
||||
{
|
||||
BacktraceList? backtrace = backtrace::symbolize_backtrace(mem, backtraces);
|
||||
void*[256] buffer;
|
||||
void*[] backtraces = backtrace::capture_current(&buffer);
|
||||
backtraces_to_ignore++;
|
||||
BacktraceList! backtrace = backtrace::symbolize_backtrace(backtraces, mem::temp());
|
||||
if (catch backtrace) return false;
|
||||
if (backtrace.len() <= backtraces_to_ignore) return false;
|
||||
io::eprint("\nERROR: '");
|
||||
io::eprint(message);
|
||||
io::eprintn("'");
|
||||
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);
|
||||
{
|
||||
if (i < backtraces_to_ignore) continue;
|
||||
if (trace.is_unknown())
|
||||
{
|
||||
io::eprintn(" in ???");
|
||||
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);
|
||||
{
|
||||
io::eprintfn(" in %s (%s:%d) [%s]", trace.function, trace.file, trace.line, trace.object_file);
|
||||
continue;
|
||||
}
|
||||
io::eprintfn(" in %s (source unavailable) [%s]", trace.function, trace.object_file);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
|
||||
{
|
||||
in_panic = true;
|
||||
$if $defined(io::stderr) && env::PANIC_MSG:
|
||||
if (!print_backtrace(message, 2))
|
||||
{
|
||||
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
|
||||
}
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace(message, 2))
|
||||
{
|
||||
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
|
||||
return;
|
||||
}
|
||||
$endif
|
||||
$$trap();
|
||||
|
||||
}
|
||||
|
||||
macro void abort(String string = "Unrecoverable error reached", ...) @format(0) @builtin @noreturn
|
||||
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
|
||||
{
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
|
||||
$$trap();
|
||||
}
|
||||
|
||||
bool in_panic @private = false;
|
||||
|
||||
fn void default_panic(String message, String file, String function, uint line) @if (!env::NATIVE_STACKTRACE)
|
||||
{
|
||||
$if $defined(io::stderr) && env::PANIC_MSG:
|
||||
if (in_panic)
|
||||
{
|
||||
io::eprintn("Panic inside of panic.");
|
||||
return;
|
||||
}
|
||||
in_panic = true;
|
||||
$if $defined(io::stderr):
|
||||
io::eprint("\nERROR: '");
|
||||
io::eprint(message);
|
||||
io::eprintfn("', in %s (%s:%d)", function, file, line);
|
||||
$endif
|
||||
in_panic = false;
|
||||
$if $defined(io::stderr):
|
||||
io::eprint("\nERROR: '");
|
||||
io::eprint(message);
|
||||
io::eprintfn("', in %s (%s:%d)", function, file, line);
|
||||
$endif
|
||||
$$trap();
|
||||
}
|
||||
|
||||
alias PanicFn = fn void(String message, String file, String function, uint line);
|
||||
def PanicFn = fn void(String message, String file, String function, uint line);
|
||||
|
||||
PanicFn panic = &default_panic;
|
||||
|
||||
fn void panicf(String fmt, String file, String function, uint line, args...)
|
||||
{
|
||||
$if $defined(io::stderr) && env::PANIC_MSG:
|
||||
if (in_panic)
|
||||
{
|
||||
io::eprint("Panic inside of panic: ");
|
||||
io::eprintn(fmt);
|
||||
return;
|
||||
}
|
||||
in_panic = true;
|
||||
@stack_mem(512; Allocator allocator)
|
||||
{
|
||||
DString s;
|
||||
s.init(allocator);
|
||||
s.appendf(fmt, ...args);
|
||||
in_panic = false;
|
||||
panic(s.str_view(), file, function, line);
|
||||
};
|
||||
$endif
|
||||
@stack_mem(512; Allocator* allocator)
|
||||
{
|
||||
DString s;
|
||||
s.init_new(.allocator = allocator);
|
||||
s.appendf(fmt, ...args);
|
||||
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"
|
||||
*>
|
||||
/**
|
||||
* 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);
|
||||
$else
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat());
|
||||
$$unreachable();
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Marks the path as unsupported, this is similar to unreachable.
|
||||
@param [in] string : "The error message"
|
||||
*>
|
||||
/**
|
||||
* 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);
|
||||
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);
|
||||
@@ -322,13 +172,13 @@ 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 $typeof(return) == $Type*
|
||||
*>
|
||||
/**
|
||||
* @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):
|
||||
@@ -340,99 +190,82 @@ macro bitcast(expr, $Type) @builtin
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param $Type : `The type of the enum`
|
||||
@param [in] enum_name : `The name of the enum to search for`
|
||||
@require $Type.kindof == ENUM : `Only enums may be used`
|
||||
@ensure $typeof(return) == $Type*
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
/**
|
||||
* @param $Type `The type of the enum`
|
||||
* @param [in] enum_name `The name of the enum to search for`
|
||||
* @require $Type.kindof == ENUM `Only enums may be used`
|
||||
* @ensure @typeis(return, $Type)
|
||||
* @return! SearchResult.MISSING
|
||||
**/
|
||||
macro enum_by_name($Type, String enum_name) @builtin
|
||||
{
|
||||
typeid x = $Type.typeid;
|
||||
foreach (i, name : x.names)
|
||||
{
|
||||
if (name == enum_name) return $Type.from_ordinal(i);
|
||||
if (name == enum_name) return ($Type)i;
|
||||
}
|
||||
return NOT_FOUND~;
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
@param $Type : `The type of the enum`
|
||||
@require $Type.kindof == ENUM : `Only enums may be used`
|
||||
@require $defined($Type.#value) : `Expected '#value' to match an enum associated value`
|
||||
@require $defined($typeof(($Type){}.#value) v = value) : `Expected the value to match the type of the associated value`
|
||||
@ensure $typeof(return) == $Type*
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.lookup_field and Enum.lookup")
|
||||
{
|
||||
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
|
||||
*>
|
||||
/**
|
||||
* 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:
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value;
|
||||
$case $probability == 1.0:
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, true);
|
||||
$default:
|
||||
$default:
|
||||
return $$expect_with_probability(#value, true, $probability);
|
||||
$endswitch
|
||||
$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
|
||||
*>
|
||||
/**
|
||||
* 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:
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value;
|
||||
$case $probability == 1.0:
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, false);
|
||||
$default:
|
||||
$default:
|
||||
return $$expect_with_probability(#value, false, $probability);
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require values::@is_int(#value) || values::@is_bool(#value)
|
||||
@require $defined($typeof(#value) v = expected)
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
/**
|
||||
* @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:
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value == expected;
|
||||
$case $probability == 1.0:
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, ($typeof(#value))expected);
|
||||
$default:
|
||||
$default:
|
||||
return $$expect_with_probability(#value, expected, $probability);
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Locality for prefetch, levels 0 - 3, corresponding
|
||||
to "extremely local" to "no locality"
|
||||
*>
|
||||
/**
|
||||
* Locality for prefetch, levels 0 - 3, corresponding
|
||||
* to "extremely local" to "no locality"
|
||||
**/
|
||||
enum PrefetchLocality
|
||||
{
|
||||
NO_LOCALITY,
|
||||
@@ -441,13 +274,13 @@ enum PrefetchLocality
|
||||
VERY_NEAR,
|
||||
}
|
||||
|
||||
<*
|
||||
Prefetch a pointer.
|
||||
/**
|
||||
* 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.`
|
||||
*>
|
||||
* @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:
|
||||
@@ -455,268 +288,53 @@ macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write =
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Shuffle a vector by its index
|
||||
|
||||
int[<4>] a = { 1, 2, 3, 4 };
|
||||
assert(swizzle(a, 0, 1, 1, 3) == (int[<4>]) { 1, 2, 2, 4 });
|
||||
*>
|
||||
macro swizzle(v, ...) @builtin
|
||||
{
|
||||
return $$swizzle(v, $vasplat);
|
||||
return $$swizzle(v, $vasplat());
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Shuffle two vectors by a common index from arranging the vectors sequentially in memory
|
||||
|
||||
int[<4>] a = { 1, 2, 3, 4 };
|
||||
int[<4>] b = { 100, 1000, 10000, 100000 };
|
||||
assert(swizzle2(a, b, 0, 1, 4, 6, 2) == (int[<5>]) { 1, 2, 100, 10000, 3 });
|
||||
*>
|
||||
macro swizzle2(v, v2, ...) @builtin
|
||||
{
|
||||
return $$swizzle2(v, v2, $vasplat);
|
||||
return $$swizzle2(v, v2, $vasplat());
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns the count of leading zero bits from an integer at compile-time.
|
||||
|
||||
@require types::is_int($typeof($value)) : "Input value must be an integer"
|
||||
@require $sizeof($value) * 8 <= 128 : "Input value must be 128 bits wide or lower"
|
||||
*>
|
||||
macro uint @clz($value) @builtin @const
|
||||
{
|
||||
$if $value == 0:
|
||||
return $sizeof($value) * 8; // it's all leading zeroes
|
||||
$endif
|
||||
|
||||
usz $n = 0;
|
||||
uint128 $x = (uint128)$value;
|
||||
|
||||
$if $x <= 0x0000_0000_0000_0000_FFFF_FFFF_FFFF_FFFF: $n += 64; $x <<= 64; $endif
|
||||
$if $x <= 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 32; $x <<= 32; $endif
|
||||
$if $x <= 0x0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 16; $x <<= 16; $endif
|
||||
$if $x <= 0x00FF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 8; $x <<= 8; $endif
|
||||
$if $x <= 0x0FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 4; $x <<= 4; $endif
|
||||
$if $x <= 0x3FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 2; $x <<= 2; $endif
|
||||
$if $x <= 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 1; $endif
|
||||
|
||||
return $n % ($sizeof($value) * 8); // mod by the bitsize of the input value to go back from uint128 -> it's-type
|
||||
}
|
||||
|
||||
<*
|
||||
Return the excuse in the Optional if it is Empty, otherwise
|
||||
return a null fault.
|
||||
|
||||
@require $kindof(#expr) == OPTIONAL : `@catch expects an Optional value`
|
||||
*>
|
||||
macro fault @catch(#expr) @builtin
|
||||
macro anyfault @catch(#expr) @builtin
|
||||
{
|
||||
if (catch f = #expr) return f;
|
||||
return {};
|
||||
return anyfault {};
|
||||
}
|
||||
|
||||
<*
|
||||
Check if an Optional expression holds a value or is empty, returning true
|
||||
if it has a value.
|
||||
|
||||
@require $kindof(#expr) == OPTIONAL : `@ok expects an Optional value`
|
||||
*>
|
||||
macro bool @ok(#expr) @builtin
|
||||
{
|
||||
if (catch #expr) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Check if an Optional expression evaluates to a fault. If so, return it;
|
||||
else, assign the result to an expression.
|
||||
|
||||
@require $defined(#v = #v) : "#v must be a variable"
|
||||
@require $defined(#expr!) : "Expected an optional expression"
|
||||
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
|
||||
*>
|
||||
macro void? @try(#v, #expr) @builtin @maydiscard
|
||||
macro char[] @as_char_view(&value) @builtin
|
||||
{
|
||||
var res = #expr;
|
||||
if (catch err = res) return err~;
|
||||
#v = res;
|
||||
return ((char*)value)[:$sizeof(*value)];
|
||||
}
|
||||
|
||||
<*
|
||||
Check if an Optional expression evaluates to a fault. If so, return true if it is the
|
||||
expected fault, the optional if it is unexpected, or false if there was no fault and
|
||||
the assign happened.
|
||||
|
||||
This can be used in like this:
|
||||
|
||||
while (true)
|
||||
{
|
||||
char[] data;
|
||||
// Read until end of file
|
||||
if (@try_catch(data, load_line(), io::EOF)!) break;
|
||||
.. use data ..
|
||||
}
|
||||
|
||||
In this example we read until we reach an EOF, which is expected. However, if we encounter some other
|
||||
fault, we rethrow is. Without this macro, the code is instead written like:
|
||||
|
||||
while (true)
|
||||
{
|
||||
char[]? data;
|
||||
data = load_line();
|
||||
if (catch err = data)
|
||||
{
|
||||
if (err = io::EOF) break;
|
||||
return err?
|
||||
}
|
||||
.. use data ..
|
||||
}
|
||||
|
||||
@require $defined(#v = #v) : "#v must be a variable"
|
||||
@require $defined(#expr!) : "Expected an optional expression"
|
||||
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
|
||||
@return "True if it was the expected fault, false if the variable was assigned, otherwise returns an optional."
|
||||
*>
|
||||
macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin
|
||||
{
|
||||
var res = #expr;
|
||||
if (catch err = res)
|
||||
{
|
||||
return err == expected_fault ? true : err~;
|
||||
}
|
||||
#v = res;
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
@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 String @str_pascalcase(String $str) @builtin => $$str_pascalcase($str);
|
||||
macro String @str_snakecase(String $str) @builtin => $$str_snakecase($str);
|
||||
macro String @str_camelcase(String $str) @builtin => @str_capitalize($$str_pascalcase($str));
|
||||
macro String @str_constantcase(String $str) @builtin => @str_upper($$str_snakecase($str));
|
||||
macro String @str_replace(String $str, String $pattern, String $replace, uint $limit = 0) @builtin => $$str_replace($str, $pattern, $replace, $limit);
|
||||
macro String @str_capitalize(String $str) @builtin
|
||||
{
|
||||
$switch $str.len:
|
||||
$case 0: return $str;
|
||||
$case 1: return $$str_upper($str);
|
||||
$default: return $$str_upper($str[0:1]) +++ $str[1..];
|
||||
$endswitch
|
||||
}
|
||||
macro String @str_uncapitalize(String $str) @builtin
|
||||
{
|
||||
$switch $str.len:
|
||||
$case 0: return $str;
|
||||
$case 1: return $$str_lower($str);
|
||||
$default: return $$str_lower($str[0:1]) +++ $str[1..];
|
||||
$endswitch
|
||||
}
|
||||
|
||||
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 uint @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)a5hash::hash(c);
|
||||
macro uint char[].hash(char[] c) => (uint)a5hash::hash(c);
|
||||
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
|
||||
|
||||
<*
|
||||
@require $kindof(array_ptr) == POINTER &&& $kindof(*array_ptr) == ARRAY
|
||||
*>
|
||||
macro uint hash_array(array_ptr) @local
|
||||
{
|
||||
var $len = $sizeof(*array_ptr);
|
||||
|
||||
$if $len > 16:
|
||||
return (uint)komi::hash(((char*)array_ptr)[:$len]);
|
||||
$else
|
||||
return (uint)wyhash2::hash(((char*)array_ptr)[:$len]);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $kindof(vec) == VECTOR
|
||||
*>
|
||||
macro uint hash_vec(vec) @local
|
||||
{
|
||||
var $len = $sizeof(vec);
|
||||
|
||||
$if $len > 16:
|
||||
return (uint)komi::hash(((char*)&&vec)[:$len]);
|
||||
$else
|
||||
return (uint)wyhash2::hash(((char*)&&vec)[:$len]);
|
||||
$endif
|
||||
}
|
||||
macro uint int.hash(int i) => i;
|
||||
macro uint uint.hash(uint i) => i;
|
||||
macro uint short.hash(short s) => s;
|
||||
macro uint ushort.hash(ushort s) => s;
|
||||
macro uint char.hash(char c) => c;
|
||||
macro uint ichar.hash(ichar c) => c;
|
||||
macro uint long.hash(long i) => (uint)((i >> 32) ^ i);
|
||||
macro uint ulong.hash(ulong i) => (uint)((i >> 32) ^ i);
|
||||
macro uint int128.hash(int128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^ i);
|
||||
macro uint uint128.hash(uint128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^ i);
|
||||
macro uint bool.hash(bool b) => (uint)b;
|
||||
macro uint typeid.hash(typeid t) => ((ulong)(uptr)t).hash();
|
||||
macro uint String.hash(String c) => (uint)fnv32a::encode(c);
|
||||
macro uint char[].hash(char[] c) => (uint)fnv32a::encode(c);
|
||||
macro uint void*.hash(void* ptr) => ((ulong)(uptr)ptr).hash();
|
||||
|
||||
const MAX_FRAMEADDRESS = 128;
|
||||
<*
|
||||
@require n >= 0
|
||||
*>
|
||||
/**
|
||||
* @require n >= 0
|
||||
**/
|
||||
macro void* get_frameaddress(int n)
|
||||
{
|
||||
if (n > MAX_FRAMEADDRESS) return null;
|
||||
@@ -855,9 +473,9 @@ macro void* get_frameaddress(int n)
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require n >= 0
|
||||
*>
|
||||
/**
|
||||
* @require n >= 0
|
||||
**/
|
||||
macro void* get_returnaddress(int n)
|
||||
{
|
||||
if (n > MAX_FRAMEADDRESS) return null;
|
||||
@@ -996,74 +614,58 @@ macro void* get_returnaddress(int n)
|
||||
}
|
||||
}
|
||||
|
||||
module std::core::builtin @if((env::LINUX || env::ANDROID || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
|
||||
import libc, std::io, std::os::posix;
|
||||
module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
|
||||
import libc;
|
||||
|
||||
fn void sig_panic(String message)
|
||||
{
|
||||
default_panic(message, "???", "???", 0);
|
||||
}
|
||||
|
||||
fn void sig_bus_error(CInt i, void* info, void* context)
|
||||
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.", 2, posix::stack_instruction(context)))
|
||||
if (!print_backtrace("Illegal memory access.", 1))
|
||||
{
|
||||
io::eprintn("\nERROR: 'Illegal memory access'.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
os::fastexit(128 + i);
|
||||
$$trap();
|
||||
}
|
||||
|
||||
fn void sig_segmentation_fault(CInt i, void* p1, void* context)
|
||||
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.", 2, posix::stack_instruction(context)))
|
||||
if (!print_backtrace("Out of bounds memory access.", 1))
|
||||
{
|
||||
io::eprintn("\nERROR: Memory error without backtrace, possible stack overflow.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
os::fastexit(128 + i);
|
||||
$$trap();
|
||||
}
|
||||
|
||||
fn void sig_illegal_instruction(CInt i, void* p1, void* context)
|
||||
fn void install_signal_handler(CInt signal, SignalFunction func) @local
|
||||
{
|
||||
if (in_panic) os::fastexit(128 + i);
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Illegal instruction.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Illegal instruction.", 2, posix::stack_instruction(context)))
|
||||
{
|
||||
io::eprintn("\nERROR: Illegal instruction.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
os::fastexit(128 + i);
|
||||
SignalFunction old = libc::signal(signal, func);
|
||||
// Restore
|
||||
if ((iptr)old > 1024) libc::signal(signal, old);
|
||||
}
|
||||
|
||||
char[64 * 1024] sig_stack @local @if(env::BACKTRACE && env::LINUX);
|
||||
|
||||
// Clean this up
|
||||
fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE)
|
||||
fn void install_signal_handlers() @init(101) @local
|
||||
{
|
||||
$if env::LINUX:
|
||||
Stack_t ss = {
|
||||
.ss_sp = &sig_stack,
|
||||
.ss_size = sig_stack.len
|
||||
};
|
||||
libc::sigaltstack(&ss, null);
|
||||
$endif
|
||||
|
||||
posix::install_signal_handler(libc::SIGBUS, &sig_bus_error);
|
||||
posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
|
||||
posix::install_signal_handler(libc::SIGILL, &sig_illegal_instruction);
|
||||
install_signal_handler(libc::SIGBUS, &sig_bus_error);
|
||||
install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// 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)
|
||||
*>
|
||||
macro bool less(a, b) @builtin
|
||||
/**
|
||||
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
|
||||
**/
|
||||
macro less(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return a.less(b);
|
||||
$case $defined(a.compare_to):
|
||||
@@ -18,12 +18,12 @@ macro bool less(a, b) @builtin
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro bool less_eq(a, b) @builtin
|
||||
/**
|
||||
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
|
||||
**/
|
||||
macro less_eq(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return !b.less(a);
|
||||
$case $defined(a.compare_to):
|
||||
@@ -33,12 +33,12 @@ macro bool less_eq(a, b) @builtin
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro bool greater(a, b) @builtin
|
||||
/**
|
||||
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
|
||||
**/
|
||||
macro greater(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return b.less(a);
|
||||
$case $defined(a.compare_to):
|
||||
@@ -48,12 +48,12 @@ macro bool greater(a, b) @builtin
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
/**
|
||||
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
|
||||
**/
|
||||
macro int compare_to(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$switch
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b);
|
||||
$case $defined(a.less):
|
||||
@@ -62,12 +62,12 @@ macro int compare_to(a, b) @builtin
|
||||
return (int)(a > b) - (int)(a < b);
|
||||
$endswitch
|
||||
}
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro bool greater_eq(a, b) @builtin
|
||||
/**
|
||||
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
|
||||
**/
|
||||
macro greater_eq(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return !a.less(b);
|
||||
$case $defined(a.compare_to):
|
||||
@@ -77,12 +77,12 @@ macro bool greater_eq(a, b) @builtin
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@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:
|
||||
$switch
|
||||
$case $defined(a.equals, a.equals(b)):
|
||||
return a.equals(b);
|
||||
$case $defined(a.compare_to, a.compare_to(b)):
|
||||
@@ -97,13 +97,13 @@ macro bool equals(a, b) @builtin
|
||||
macro min(x, ...) @builtin
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return less(x, $vaarg[0]) ? x : $vaarg[0];
|
||||
return less(x, $vaarg(0)) ? x : $vaarg(0);
|
||||
$else
|
||||
var result = x;
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
if (less($vaarg[$i], result))
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (less($vaarg($i), result))
|
||||
{
|
||||
result = $vaarg[$i];
|
||||
result = $vaarg($i);
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
@@ -113,49 +113,16 @@ macro min(x, ...) @builtin
|
||||
macro max(x, ...) @builtin
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return greater(x, $vaarg[0]) ? x : $vaarg[0];
|
||||
return greater(x, $vaarg(0)) ? x : $vaarg(0);
|
||||
$else
|
||||
var result = x;
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
if (greater($vaarg[$i], result))
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (greater($vaarg($i), result))
|
||||
{
|
||||
result = $vaarg[$i];
|
||||
result = $vaarg($i);
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require types::is_numerical($typeof($a))
|
||||
*>
|
||||
macro @max($a, ...) @builtin @const
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return $a > $vaconst[0] ? $a : $vaconst[0];
|
||||
$else
|
||||
var $result = $a;
|
||||
$for var $x = 0; $x < $vacount; ++$x:
|
||||
$if $vaconst[$x] > $result: $result = $vaconst[$x]; $endif
|
||||
$endfor
|
||||
return $result;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::is_numerical($typeof($a))
|
||||
*>
|
||||
macro @min($a, ...) @builtin @const
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return $a < $vaconst[0] ? $a : $vaconst[0];
|
||||
$else
|
||||
var $result = $a;
|
||||
$for var $x = 0; $x < $vacount; ++$x:
|
||||
$if $vaconst[$x] < $result: $result = $vaconst[$x]; $endif
|
||||
$endfor
|
||||
return $result;
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// 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;
|
||||
import std::core::env;
|
||||
|
||||
|
||||
const C_INT_SIZE = $$C_INT_SIZE;
|
||||
const C_LONG_SIZE = $$C_LONG_SIZE;
|
||||
@@ -18,29 +16,23 @@ $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;
|
||||
def CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
|
||||
def CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
|
||||
def CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
|
||||
def CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
|
||||
def CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
|
||||
def CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
|
||||
def CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
def CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
def CSChar = ichar;
|
||||
def CUChar = char;
|
||||
|
||||
alias CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
|
||||
|
||||
enum CBool : char
|
||||
{
|
||||
FALSE,
|
||||
TRUE
|
||||
}
|
||||
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
|
||||
|
||||
// Helper macros
|
||||
macro typeid signed_int_from_bitsize(usz $bitsize) @private
|
||||
{
|
||||
$switch $bitsize:
|
||||
$switch ($bitsize)
|
||||
$case 128: return int128.typeid;
|
||||
$case 64: return long.typeid;
|
||||
$case 32: return int.typeid;
|
||||
@@ -52,7 +44,7 @@ macro typeid signed_int_from_bitsize(usz $bitsize) @private
|
||||
|
||||
macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
|
||||
{
|
||||
$switch $bitsize:
|
||||
$switch ($bitsize)
|
||||
$case 128: return uint128.typeid;
|
||||
$case 64: return ulong.typeid;
|
||||
$case 32: return uint.typeid;
|
||||
@@ -61,57 +53,3 @@ macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
|
||||
$default: $error("Invalid bitsize");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
const USE_STACK_VALIST = env::ARCH_32_BIT || env::WIN32 || (env::DARWIN && env::AARCH64);
|
||||
module std::core::cinterop @if(USE_STACK_VALIST);
|
||||
|
||||
typedef CVaList = void*;
|
||||
macro CVaList.next(&self, $Type)
|
||||
{
|
||||
void *ptr = mem::aligned_pointer((void*)*self, max($Type.alignof, 8));
|
||||
defer *self = (CVaList)(ptr + 1);
|
||||
return *($Type*)ptr;
|
||||
}
|
||||
|
||||
module std::core::cinterop @if(env::X86_64 && !env::WIN32);
|
||||
|
||||
struct CVaListData
|
||||
{
|
||||
uint gp_offset;
|
||||
uint fp_offset;
|
||||
void *overflow_arg_area;
|
||||
void *reg_save_area;
|
||||
}
|
||||
|
||||
typedef CVaList = CVaListData*;
|
||||
|
||||
macro CVaList.next(self, $Type)
|
||||
{
|
||||
CVaListData* data = (CVaListData*)self;
|
||||
$switch:
|
||||
$case $Type.kindof == FLOAT ||| ($Type.kindof == VECTOR && $Type.sizeof <= 16):
|
||||
var $LoadType = $Type.sizeof < 8 ? double : $Type;
|
||||
if (data.fp_offset < 6 * 8 + 8 * 16 )
|
||||
{
|
||||
defer data.fp_offset += (uint)mem::aligned_offset($Type.sizeof, 16);
|
||||
return ($Type)*($LoadType*)(data.reg_save_area + data.fp_offset);
|
||||
}
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return ($Type)*($LoadType*)ptr;
|
||||
$case $Type.kindof == SIGNED_INT || $Type.kindof == UNSIGNED_INT:
|
||||
var $LoadType = $Type.sizeof < 4 ? int : $Type;
|
||||
if (data.gp_offset < 6 * 8 && $Type.sizeof <= 8)
|
||||
{
|
||||
defer data.gp_offset += (uint)mem::aligned_offset($Type.sizeof, 8);
|
||||
return ($Type)*($LoadType*)(data.reg_save_area + data.gp_offset);
|
||||
}
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return ($Type)*($LoadType*)ptr;
|
||||
$default:
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return *($Type*)ptr;
|
||||
$endswitch
|
||||
}
|
||||
@@ -9,32 +9,31 @@ const uint UTF16_SURROGATE_BITS @private = 10;
|
||||
const uint UTF16_SURROGATE_LOW_VALUE @private = 0xDC00;
|
||||
const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
|
||||
|
||||
<*
|
||||
@param c : `The utf32 codepoint to convert`
|
||||
@param [out] output : `the resulting buffer`
|
||||
@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`
|
||||
**/
|
||||
fn usz! char32_to_utf8(Char32 c, char[] output)
|
||||
{
|
||||
if (!output.len) return string::CONVERSION_FAILED~;
|
||||
if (!output.len) return UnicodeResult.CONVERSION_FAILED?;
|
||||
switch (true)
|
||||
{
|
||||
case c <= 0x7f:
|
||||
output[0] = (char)c;
|
||||
return 1;
|
||||
case c <= 0x7ff:
|
||||
if (output.len < 2) return string::CONVERSION_FAILED~;
|
||||
if (output.len < 2) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xC0 | c >> 6);
|
||||
output[1] = (char)(0x80 | (c & 0x3F));
|
||||
return 2;
|
||||
case c <= 0xffff:
|
||||
if (output.len < 3) return string::CONVERSION_FAILED~;
|
||||
if (output.len < 3) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xE0 | c >> 12);
|
||||
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c & 0x3F));
|
||||
return 3;
|
||||
case c <= 0x10ffff:
|
||||
if (output.len < 4) return string::CONVERSION_FAILED~;
|
||||
if (output.len < 4) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xF0 | c >> 18);
|
||||
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
@@ -42,16 +41,16 @@ fn usz? char32_to_utf8(Char32 c, char[] output)
|
||||
return 4;
|
||||
default:
|
||||
// 0x10FFFF and above is not defined.
|
||||
return string::CONVERSION_FAILED~;
|
||||
return UnicodeResult.CONVERSION_FAILED?;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Convert a code pointer into 1-2 UTF16 characters.
|
||||
|
||||
@param c : `The character to convert.`
|
||||
@param [inout] output : `the resulting UTF16 buffer to write to.`
|
||||
*>
|
||||
/**
|
||||
* 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 +66,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, usz *available, char** output)
|
||||
{
|
||||
Char16 high = *ptr;
|
||||
if (high & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
|
||||
@@ -84,15 +83,15 @@ fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
return;
|
||||
}
|
||||
// Low surrogate first is an error
|
||||
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return string::INVALID_UTF16~;
|
||||
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
|
||||
@@ -101,22 +100,22 @@ fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
char32_to_utf8_unsafe(uc, output);
|
||||
*available = 2;
|
||||
}
|
||||
<*
|
||||
@param c : `The utf32 codepoint to convert`
|
||||
@param [inout] output : `the resulting buffer`
|
||||
*>
|
||||
/**
|
||||
* @param c `The utf32 codepoint to convert`
|
||||
* @param [inout] output `the resulting buffer`
|
||||
**/
|
||||
fn usz char32_to_utf8_unsafe(Char32 c, char** output)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case c <= 0x7f:
|
||||
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:
|
||||
case c < 0xffff:
|
||||
(*output)++[0] = (char)(0xE0 | c >> 12);
|
||||
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
@@ -130,15 +129,15 @@ fn usz char32_to_utf8_unsafe(Char32 c, char** output)
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@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, usz* size)
|
||||
{
|
||||
usz max_size = *size;
|
||||
if (max_size < 1) return string::INVALID_UTF8~;
|
||||
if (max_size < 1) return UnicodeResult.INVALID_UTF8?;
|
||||
char c = (ptr++)[0];
|
||||
|
||||
if ((c & 0x80) == 0)
|
||||
@@ -148,47 +147,47 @@ fn Char32? utf8_to_char32(char* ptr, usz* size)
|
||||
}
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
if (max_size < 2) return string::INVALID_UTF8~;
|
||||
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 (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
if (max_size < 3) return string::INVALID_UTF8~;
|
||||
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 (!uc || 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~;
|
||||
if (max_size < 4) return UnicodeResult.INVALID_UTF8?;
|
||||
if ((c & 0xF8) != 0xF0) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 4;
|
||||
Char32 uc = (c & 0x07) << 18;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return 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 (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
|
||||
<*
|
||||
@param utf8 : `An UTF-8 encoded slice of bytes`
|
||||
@return `the number of encoded code points`
|
||||
*>
|
||||
/**
|
||||
* @param utf8 `An UTF-8 encoded slice of bytes`
|
||||
* @return `the number of encoded code points`
|
||||
**/
|
||||
fn usz utf8_codepoints(String utf8)
|
||||
{
|
||||
usz len = 0;
|
||||
@@ -199,11 +198,11 @@ 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`
|
||||
*>
|
||||
/**
|
||||
* Calculate the UTF8 length required to encode an UTF32 array.
|
||||
* @param [in] utf32 `the utf32 data to calculate from`
|
||||
* @return `the length of the resulting UTF8 array`
|
||||
**/
|
||||
fn usz utf8len_for_utf32(Char32[] utf32)
|
||||
{
|
||||
usz len = 0;
|
||||
@@ -211,11 +210,11 @@ fn usz utf8len_for_utf32(Char32[] 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,11 +223,11 @@ 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`
|
||||
*>
|
||||
/**
|
||||
* Calculate the UTF8 length required to encode an UTF16 array.
|
||||
* @param [in] utf16 `the utf16 data to calculate from`
|
||||
* @return `the length of the resulting UTF8 array`
|
||||
**/
|
||||
fn usz utf8len_for_utf16(Char16[] utf16)
|
||||
{
|
||||
usz len = 0;
|
||||
@@ -238,12 +237,12 @@ fn usz utf8len_for_utf16(Char16[] utf16)
|
||||
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,11 +255,11 @@ 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`
|
||||
*>
|
||||
/**
|
||||
* Calculate the UTF16 length required to encode a UTF8 array.
|
||||
* @param utf8 `the utf8 data to calculate from`
|
||||
* @return `the length of the resulting UTF16 array`
|
||||
**/
|
||||
fn usz utf16len_for_utf8(String utf8)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
@@ -280,10 +279,10 @@ fn usz utf16len_for_utf8(String utf8)
|
||||
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.`
|
||||
*>
|
||||
/**
|
||||
* @param [in] utf32 `the UTF32 array to check the length for`
|
||||
* @return `the required length of an UTF16 array to hold the UTF32 data.`
|
||||
**/
|
||||
fn usz utf16len_for_utf32(Char32[] utf32)
|
||||
{
|
||||
usz len = utf32.len;
|
||||
@@ -294,14 +293,14 @@ 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 usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
|
||||
{
|
||||
char[] buffer = utf8_buffer;
|
||||
foreach (uc : utf32)
|
||||
@@ -314,14 +313,14 @@ fn usz? utf32to8(Char32[] utf32, char[] utf8_buffer)
|
||||
return utf8_buffer.len - buffer.len;
|
||||
}
|
||||
|
||||
<*
|
||||
Convert an UTF8 array to an UTF32 array.
|
||||
|
||||
@param [in] utf8
|
||||
@param [out] utf32_buffer
|
||||
@return `the number of Char32s written.`
|
||||
*>
|
||||
fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
/**
|
||||
* Convert an UTF8 array to an UTF32 array.
|
||||
*
|
||||
* @param [in] utf8
|
||||
* @param [out] utf32_buffer
|
||||
* @return `the number of Char32s written.`
|
||||
**/
|
||||
fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
Char32* ptr = utf32_buffer.ptr;
|
||||
@@ -329,7 +328,7 @@ fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
usz buf_len = utf32_buffer.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
if (len32 == buf_len) return string::CONVERSION_FAILED~;
|
||||
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED?;
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
@@ -340,15 +339,15 @@ fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
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;)
|
||||
@@ -359,15 +358,15 @@ fn void? utf16to8_unsafe(Char16[] utf16, char* utf8_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(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(String utf8, Char32* utf32_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
for (usz i = 0; i < len;)
|
||||
@@ -379,15 +378,15 @@ fn void? utf8to32_unsafe(String utf8, Char32* utf32_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(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(String utf8, Char16* utf16_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
for (usz i = 0; i < len;)
|
||||
@@ -399,14 +398,14 @@ fn void? utf8to16_unsafe(String utf8, Char16* utf16_buffer)
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
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;
|
||||
|
||||
@@ -1,56 +1,43 @@
|
||||
module std::core::dstring;
|
||||
import std::io;
|
||||
|
||||
<*
|
||||
The DString offers a dynamic string builder.
|
||||
*>
|
||||
typedef DString (OutStream) = DStringOpaque*;
|
||||
typedef DStringOpaque = void;
|
||||
distinct DString (OutStream) = 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)
|
||||
/**
|
||||
* @require !self.data() "String already initialized"
|
||||
**/
|
||||
fn DString DString.init_new(&self, usz capacity = MIN_CAPACITY, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
|
||||
StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
|
||||
StringData* data = allocator.new(StringData, .end_padding = 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)
|
||||
/**
|
||||
* @require !self.data() "String already initialized"
|
||||
**/
|
||||
fn DString DString.init_temp(&self, usz capacity = MIN_CAPACITY)
|
||||
{
|
||||
return self.init(tmem, capacity) @inline;
|
||||
self.init_new(capacity, mem::temp()) @inline;
|
||||
return *self;
|
||||
}
|
||||
|
||||
fn DString new_with_capacity(Allocator allocator, usz capacity)
|
||||
fn DString new_with_capacity(usz capacity, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return (DString){}.init(allocator, capacity);
|
||||
return DString{}.init_new(capacity, allocator);
|
||||
}
|
||||
|
||||
fn DString temp_with_capacity(usz capacity) => new_with_capacity(tmem, capacity) @inline;
|
||||
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, mem::temp()) @inline;
|
||||
|
||||
fn DString new(Allocator allocator, String c = "")
|
||||
fn DString new(String c = "", Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz len = c.len;
|
||||
StringData* data = (StringData*)new_with_capacity(allocator, len);
|
||||
StringData* data = (StringData*)new_with_capacity(len, allocator);
|
||||
if (len)
|
||||
{
|
||||
data.len = len;
|
||||
@@ -59,69 +46,18 @@ fn DString new(Allocator allocator, String c = "")
|
||||
return (DString)data;
|
||||
}
|
||||
|
||||
fn DString temp(String s = "") => new(tmem, s) @inline;
|
||||
fn DString temp_new(String s = "") => new(s, mem::temp()) @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_string(replacement);
|
||||
match = 0;
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (match > 0)
|
||||
{
|
||||
self.append_string(str[i - match:match]);
|
||||
match = 0;
|
||||
}
|
||||
self.append_char(c);
|
||||
}
|
||||
if (match > 0) self.append_string(str[^match:match]);
|
||||
};
|
||||
}
|
||||
|
||||
fn DString DString.concat(self, Allocator allocator, DString b) @nodiscard
|
||||
fn DString DString.new_concat(self, DString b, Allocator* allocator = mem::heap())
|
||||
{
|
||||
DString string;
|
||||
string.init(allocator, self.len() + b.len());
|
||||
string.init_new(self.len() + b.len(), allocator);
|
||||
string.append(self);
|
||||
string.append(b);
|
||||
return string;
|
||||
}
|
||||
|
||||
fn DString DString.tconcat(self, DString b) => self.concat(tmem, b);
|
||||
fn DString DString.new_tconcat(self, DString b) => self.new_concat(b, mem::temp());
|
||||
|
||||
fn ZString DString.zstr_view(&self)
|
||||
{
|
||||
@@ -146,15 +82,15 @@ fn usz DString.capacity(self)
|
||||
return self.data().capacity;
|
||||
}
|
||||
|
||||
fn usz DString.len(&self) @dynamic @operator(len)
|
||||
fn usz DString.len(&self) @dynamic
|
||||
{
|
||||
if (!*self) return 0;
|
||||
return self.data().len;
|
||||
}
|
||||
|
||||
<*
|
||||
@require new_size <= self.len()
|
||||
*>
|
||||
/**
|
||||
* @require new_size <= self.len()
|
||||
*/
|
||||
fn void DString.chop(self, usz new_size)
|
||||
{
|
||||
if (!self) return;
|
||||
@@ -168,39 +104,19 @@ fn String DString.str_view(self)
|
||||
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)
|
||||
fn void 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([]=)
|
||||
/**
|
||||
* @require index < self.len()
|
||||
**/
|
||||
fn void DString.set(self, usz index, char c)
|
||||
{
|
||||
self.data().chars[index] = c;
|
||||
}
|
||||
@@ -216,10 +132,10 @@ fn void DString.append_repeat(&self, char c, usz times)
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require c <= 0x10ffff
|
||||
*>
|
||||
fn usz DString.append_char32(&self, Char32 c)
|
||||
/**
|
||||
* @require c <= 0x10ffff
|
||||
*/
|
||||
fn void DString.append_char32(&self, Char32 c)
|
||||
{
|
||||
char[4] buffer @noinit;
|
||||
char* p = &buffer;
|
||||
@@ -228,40 +144,44 @@ fn usz DString.append_char32(&self, Char32 c)
|
||||
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.tcopy(&self) => self.copy(mem::temp());
|
||||
|
||||
fn DString DString.copy(self, Allocator allocator) @nodiscard
|
||||
fn DString DString.copy(self, Allocator* allocator = null)
|
||||
{
|
||||
if (!self) return new(allocator);
|
||||
if (!self)
|
||||
{
|
||||
if (allocator) return new_with_capacity(0, allocator);
|
||||
return (DString)null;
|
||||
}
|
||||
StringData* data = self.data();
|
||||
DString new_string = new_with_capacity(allocator, data.capacity);
|
||||
if (!allocator) allocator = mem::heap();
|
||||
DString new_string = new_with_capacity(data.capacity, allocator);
|
||||
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
|
||||
return new_string;
|
||||
}
|
||||
|
||||
fn ZString DString.copy_zstr(self, Allocator allocator) @nodiscard
|
||||
fn ZString DString.copy_zstr(self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz str_len = self.len();
|
||||
if (!str_len)
|
||||
{
|
||||
return (ZString)allocator::calloc(allocator, 1);
|
||||
return (ZString)allocator.calloc(1);
|
||||
}
|
||||
char* zstr = allocator::malloc(allocator, str_len + 1);
|
||||
char* zstr = allocator.alloc(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) @nodiscard
|
||||
fn String DString.copy_str(self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return (String)self.copy_zstr(allocator)[:self.len()];
|
||||
}
|
||||
|
||||
fn String DString.tcopy_str(self) @nodiscard => self.copy_str(tmem) @inline;
|
||||
fn String DString.tcopy_str(self) => self.copy_str(mem::temp()) @inline;
|
||||
|
||||
fn bool DString.equals(self, DString other_string)
|
||||
{
|
||||
@@ -284,7 +204,7 @@ fn void DString.free(&self)
|
||||
if (!*self) return;
|
||||
StringData* data = self.data();
|
||||
if (!data) return;
|
||||
allocator::free(data.allocator, data);
|
||||
data.allocator.free(data);
|
||||
*self = (DString)null;
|
||||
}
|
||||
|
||||
@@ -305,49 +225,27 @@ fn bool DString.less(self, DString other_string)
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void DString.append_chars(&self, String str) @deprecated("Use append_string")
|
||||
fn void DString.append_chars(&self, String str)
|
||||
{
|
||||
self.append_bytes(str);
|
||||
}
|
||||
|
||||
fn void DString.append_bytes(&self, char[] bytes)
|
||||
{
|
||||
usz other_len = bytes.len;
|
||||
usz other_len = str.len;
|
||||
if (!other_len) return;
|
||||
if (!*self)
|
||||
{
|
||||
*self = temp((String)bytes);
|
||||
*self = new(str);
|
||||
return;
|
||||
}
|
||||
self.reserve(other_len);
|
||||
StringData* data = self.data();
|
||||
mem::copy(&data.chars[data.len], bytes.ptr, other_len);
|
||||
mem::copy(&data.chars[data.len], str.ptr, other_len);
|
||||
data.len += other_len;
|
||||
}
|
||||
|
||||
fn Char32[] DString.copy_utf32(&self, Allocator allocator)
|
||||
fn Char32[] DString.copy_utf32(&self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return self.str_view().to_utf32(allocator) @inline!!;
|
||||
return self.str_view().to_new_utf32(allocator) @inline!!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $typeof(str) == String || $typeof(str) == DString : "Expected string or DString"
|
||||
*>
|
||||
macro void DString.append_string(&self, str)
|
||||
{
|
||||
$if $typeof(str) == String:
|
||||
self.append_bytes(str);
|
||||
$else
|
||||
self.append_string_deprecated(str);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void DString.append_string_deprecated(&self, DString str) @deprecated("Use .append_dstring()")
|
||||
{
|
||||
self.append_dstring(str);
|
||||
}
|
||||
|
||||
fn void DString.append_dstring(&self, DString str)
|
||||
fn void DString.append_string(&self, DString str)
|
||||
{
|
||||
StringData* other = str.data();
|
||||
if (!other) return;
|
||||
@@ -360,13 +258,13 @@ fn void DString.clear(self)
|
||||
self.data().len = 0;
|
||||
}
|
||||
|
||||
fn usz? DString.write(&self, char[] buffer) @dynamic
|
||||
fn usz! DString.write(&self, char[] buffer) @dynamic
|
||||
{
|
||||
self.append_bytes(buffer);
|
||||
self.append_chars((String)buffer);
|
||||
return buffer.len;
|
||||
}
|
||||
|
||||
fn void? DString.write_byte(&self, char c) @dynamic
|
||||
fn void! DString.write_byte(&self, char c) @dynamic
|
||||
{
|
||||
self.append_char(c);
|
||||
}
|
||||
@@ -375,74 +273,40 @@ fn void DString.append_char(&self, char c)
|
||||
{
|
||||
if (!*self)
|
||||
{
|
||||
*self = temp_with_capacity(MIN_CAPACITY);
|
||||
*self = new_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:
|
||||
$switch ($Type)
|
||||
$case char:
|
||||
$case ichar:
|
||||
self.append_char(value);
|
||||
$case DString:
|
||||
self.append_dstring(value);
|
||||
$case String:
|
||||
self.append_string(value);
|
||||
$case String:
|
||||
self.append_chars(value);
|
||||
$case Char32:
|
||||
self.append_char32(value);
|
||||
$default:
|
||||
$switch:
|
||||
$switch
|
||||
$case $defined((Char32)value):
|
||||
self.append_char32((Char32)value);
|
||||
$case $defined((String)value):
|
||||
self.append_string((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)
|
||||
fn void DString.insert_at(&self, usz index, String s)
|
||||
{
|
||||
if (s.len == 0) return;
|
||||
self.reserve(s.len);
|
||||
@@ -474,162 +338,46 @@ fn void DString.insert_chars_at(&self, usz index, String s)
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.len()
|
||||
*>
|
||||
fn void DString.insert_string_at(&self, usz index, DString str)
|
||||
fn usz! DString.appendf(&self, String format, args...) @maydiscard
|
||||
{
|
||||
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
|
||||
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;
|
||||
};
|
||||
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) @nodiscard
|
||||
fn DString new_join(String[] s, String joiner, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (!s.len) return new(allocator);
|
||||
if (!s.len) return (DString)null;
|
||||
usz total_size = joiner.len * s.len;
|
||||
foreach (String* &str : s)
|
||||
{
|
||||
total_size += str.len;
|
||||
}
|
||||
DString res = new_with_capacity(allocator, total_size);
|
||||
DString res = new_with_capacity(total_size, allocator);
|
||||
res.append(s[0]);
|
||||
foreach (String str : s[1..])
|
||||
foreach (String* &str : s[1..])
|
||||
{
|
||||
res.append(joiner);
|
||||
res.append(str);
|
||||
res.append(*str);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
fn void? out_string_append_fn(void* data, char c) @private
|
||||
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
|
||||
{
|
||||
@@ -641,7 +389,7 @@ fn void DString.reserve(&self, usz addition)
|
||||
StringData* data = self.data();
|
||||
if (!data)
|
||||
{
|
||||
*self = dstring::temp_with_capacity(addition);
|
||||
*self = dstring::new_with_capacity(addition);
|
||||
return;
|
||||
}
|
||||
usz len = data.len + addition;
|
||||
@@ -650,10 +398,10 @@ fn void DString.reserve(&self, usz addition)
|
||||
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);
|
||||
*self = (DString)data.allocator.realloc(data, StringData.sizeof + new_capacity);
|
||||
}
|
||||
|
||||
fn usz? DString.read_from_stream(&self, InStream reader)
|
||||
fn usz! DString.read_from_stream(&self, InStream* reader)
|
||||
{
|
||||
if (&reader.available)
|
||||
{
|
||||
@@ -685,7 +433,7 @@ fn usz? DString.read_from_stream(&self, InStream reader)
|
||||
|
||||
struct StringData @private
|
||||
{
|
||||
Allocator allocator;
|
||||
Allocator* allocator;
|
||||
usz len;
|
||||
usz capacity;
|
||||
char[*] chars;
|
||||
|
||||
@@ -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::env;
|
||||
@@ -57,7 +57,6 @@ enum OsType
|
||||
HURD,
|
||||
WASI,
|
||||
EMSCRIPTEN,
|
||||
ANDROID,
|
||||
}
|
||||
|
||||
enum ArchType
|
||||
@@ -113,38 +112,27 @@ enum ArchType
|
||||
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 usz MAX_VECTOR_SIZE = $$MAX_VECTOR_SIZE;
|
||||
const bool ARCH_32_BIT = $$REGISTER_SIZE == 32;
|
||||
const bool ARCH_64_BIT = $$REGISTER_SIZE == 64;
|
||||
const OsType OS_TYPE = (OsType)$$OS_TYPE;
|
||||
const ArchType ARCH_TYPE = (ArchType)$$ARCH_TYPE;
|
||||
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
|
||||
const bool NO_LIBC = !LIBC && !CUSTOM_LIBC;
|
||||
const bool CUSTOM_LIBC = $$CUSTOM_LIBC;
|
||||
const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel.from_ordinal($$COMPILER_OPT_LEVEL);
|
||||
const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
|
||||
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 F128_SUPPORT = $$PLATFORM_F128_SUPPORTED;
|
||||
const REGISTER_SIZE = $$REGISTER_SIZE;
|
||||
const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE;
|
||||
const bool DEBUG_SYMBOLS = $$DEBUG_SYMBOLS;
|
||||
const bool BACKTRACE = $$BACKTRACE;
|
||||
const usz LLVM_VERSION = $$LLVM_VERSION;
|
||||
const bool BENCHMARKING = $$BENCHMARKING;
|
||||
const bool TESTING = $$TESTING;
|
||||
const bool PANIC_MSG = $$PANIC_MSG;
|
||||
const MemoryEnvironment MEMORY_ENV = MemoryEnvironment.from_ordinal($$MEMORY_ENVIRONMENT);
|
||||
const MemoryEnvironment MEMORY_ENV = (MemoryEnvironment)$$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 || OPENBSD || WIN32;
|
||||
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;
|
||||
@@ -152,26 +140,12 @@ const bool POSIX = LIBC && os_is_posix();
|
||||
const bool OPENBSD = LIBC && OS_TYPE == OPENBSD;
|
||||
const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
|
||||
const bool NETBSD = LIBC && OS_TYPE == NETBSD;
|
||||
const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD;
|
||||
const bool WASI = LIBC && OS_TYPE == WASI;
|
||||
const bool WASM = ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool ANDROID = LIBC && OS_TYPE == ANDROID;
|
||||
const bool WASM_NOLIBC @builtin @deprecated("Use 'FREESTANDING_WASM' instead") = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool FREESTANDING_PE32 = NO_LIBC && OS_TYPE == WIN32;
|
||||
const bool FREESTANDING_MACHO = NO_LIBC && OS_TYPE == MACOS;
|
||||
const bool FREESTANDING_ELF = NO_LIBC && !env::FREESTANDING_PE32 && !env::FREESTANDING_MACHO && !env::FREESTANDING_WASM;
|
||||
const bool FREESTANDING_WASM = NO_LIBC && WASM;
|
||||
const bool FREESTANDING = env::FREESTANDING_PE32 || env::FREESTANDING_MACHO || env::FREESTANDING_ELF || env::FREESTANDING_WASM;
|
||||
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;
|
||||
const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::OPENBSD || env::DARWIN || env::WIN32 || env::NETBSD;
|
||||
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
|
||||
macro bool os_is_darwin() @const
|
||||
macro bool os_is_darwin()
|
||||
{
|
||||
$switch OS_TYPE:
|
||||
$switch (OS_TYPE)
|
||||
$case IOS:
|
||||
$case MACOS:
|
||||
$case TVOS:
|
||||
@@ -182,9 +156,9 @@ macro bool os_is_darwin() @const
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool os_is_posix() @const
|
||||
macro bool os_is_posix()
|
||||
{
|
||||
$switch OS_TYPE:
|
||||
$switch (OS_TYPE)
|
||||
$case IOS:
|
||||
$case MACOS:
|
||||
$case NETBSD:
|
||||
@@ -195,7 +169,6 @@ macro bool os_is_posix() @const
|
||||
$case SOLARIS:
|
||||
$case TVOS:
|
||||
$case WATCHOS:
|
||||
$case ANDROID:
|
||||
return true;
|
||||
$case WIN32:
|
||||
$case WASI:
|
||||
@@ -206,8 +179,6 @@ macro bool os_is_posix() @const
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
const String[] AUTHORS = $$AUTHORS;
|
||||
const String[] AUTHOR_EMAILS = $$AUTHOR_EMAILS;
|
||||
const String PROJECT_VERSION = $$PROJECT_VERSION;
|
||||
|
||||
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
|
||||
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
module std::core::log;
|
||||
import std::io, std::thread, std::time, std::math::random;
|
||||
|
||||
const FULL_LOG = env::COMPILER_SAFE_MODE || $feature(FULL_LOG);
|
||||
|
||||
typedef LogCategory = inline char;
|
||||
typedef LogTag = char[12];
|
||||
|
||||
const LogCategory CATEGORY_APPLICATION = 0;
|
||||
const LogCategory CATEGORY_SYSTEM = 1;
|
||||
const LogCategory CATEGORY_KERNEL = 2;
|
||||
const LogCategory CATEGORY_AUDIO = 3;
|
||||
const LogCategory CATEGORY_VIDEO = 4;
|
||||
const LogCategory CATEGORY_RENDER = 5;
|
||||
const LogCategory CATEGORY_INPUT = 6;
|
||||
const LogCategory CATEGORY_NETWORK = 7;
|
||||
const LogCategory CATEGORY_SOCKET = 8;
|
||||
const LogCategory CATEGORY_SECURITY = 9;
|
||||
const LogCategory CATEGORY_TEST = 10;
|
||||
const LogCategory CATEGORY_ERROR = 11;
|
||||
const LogCategory CATEGORY_ASSERT = 12;
|
||||
const LogCategory CATEGORY_CRASH = 13;
|
||||
const LogCategory CATEGORY_STATS = 14;
|
||||
const LogCategory CATEGORY_CUSTOM_START = 100;
|
||||
|
||||
tlocal LogCategory default_category = CATEGORY_APPLICATION;
|
||||
tlocal LogTag current_tag;
|
||||
|
||||
|
||||
enum LogPriority : int
|
||||
{
|
||||
VERBOSE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR,
|
||||
CRITICAL,
|
||||
}
|
||||
|
||||
interface Logger
|
||||
{
|
||||
fn void log(LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
|
||||
}
|
||||
|
||||
macro void verbose(String fmt, ..., LogCategory category = default_category) => call_log(VERBOSE, category, fmt, $vasplat);
|
||||
macro void debug(String fmt, ..., LogCategory category = default_category) => call_log(DEBUG, category, fmt, $vasplat);
|
||||
macro void info(String fmt, ..., LogCategory category = default_category) => call_log(INFO, category, fmt, $vasplat);
|
||||
macro void warn(String fmt, ..., LogCategory category = default_category) => call_log(WARN, category, fmt, $vasplat);
|
||||
macro void error(String fmt, ..., LogCategory category = default_category) => call_log(ERROR, category, fmt, $vasplat);
|
||||
macro void critical(String fmt, ..., LogCategory category = default_category) => call_log(CRITICAL, category, fmt, $vasplat);
|
||||
|
||||
macro void @category_scope(LogCategory new_category; @body)
|
||||
{
|
||||
LogCategory old = default_category;
|
||||
default_category = new_category;
|
||||
defer default_category = old;
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
macro void @tag_scope(String tag_prefix = ""; @body)
|
||||
{
|
||||
LogTag old = current_tag;
|
||||
push_tag(tag_prefix);
|
||||
defer current_tag = old;
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
macro void push_tag(String tag_prefix = "")
|
||||
{
|
||||
current_tag = create_tag(tag_prefix);
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
fn LogTag create_tag(String tag_prefix)
|
||||
{
|
||||
LogTag tag @noinit;
|
||||
int start = 0;
|
||||
foreach (int i, c : tag_prefix)
|
||||
{
|
||||
if (c == 0) break;
|
||||
tag[start++] = c;
|
||||
}
|
||||
if (start > 0) tag[start++] = '_';
|
||||
for (int i = start; i < tag.len; i++)
|
||||
{
|
||||
tag[i] = (char)rand_in_range('a', 'z');
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
fn void set_priority_for_category(LogCategory category, LogPriority new_priority)
|
||||
{
|
||||
@atomic_store(config_priorities[category], new_priority, UNORDERED);
|
||||
}
|
||||
|
||||
fn LogPriority get_priority_for_category(LogCategory category)
|
||||
{
|
||||
return @atomic_load(config_priorities[category], UNORDERED);
|
||||
}
|
||||
|
||||
fn void set_priority_all(LogPriority new_priority)
|
||||
{
|
||||
for (int i = 0; i < config_priorities.len; i++)
|
||||
{
|
||||
@atomic_store(config_priorities[i], new_priority, UNORDERED);
|
||||
}
|
||||
}
|
||||
fn void set_logger(Logger logger)
|
||||
{
|
||||
init();
|
||||
if (!logger_mutex.is_initialized())
|
||||
{
|
||||
current_logger = logger;
|
||||
current_logfn = &logger.log;
|
||||
return;
|
||||
}
|
||||
logger_mutex.@in_lock()
|
||||
{
|
||||
current_logger = logger;
|
||||
current_logfn = &logger.log;
|
||||
};
|
||||
}
|
||||
|
||||
macro void init()
|
||||
{
|
||||
log_init.call(fn () => (void)logger_mutex.init());
|
||||
}
|
||||
|
||||
macro void call_log(LogPriority prio, LogCategory category, String fmt, args...)
|
||||
{
|
||||
$if FULL_LOG:
|
||||
call_log_internal(prio, category, $$FILE, $$FUNC, $$LINE, fmt, args);
|
||||
$else
|
||||
call_log_internal(prio, category, "", "", 0, fmt, args);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void call_log_internal(LogPriority prio, LogCategory category, String file, String func, int line, String fmt, any[] args)
|
||||
{
|
||||
LogPriority priority = mem::@atomic_load(config_priorities[category], UNORDERED);
|
||||
if (priority > prio) return;
|
||||
init();
|
||||
bool locked = logger_mutex.is_initialized();
|
||||
if (locked) logger_mutex.lock();
|
||||
Logger logger = current_logger;
|
||||
LogFn logfn = current_logfn;
|
||||
defer if (locked) logger_mutex.unlock();
|
||||
logfn(logger.ptr, prio, category, current_tag, file, func, line, fmt, args);
|
||||
}
|
||||
|
||||
fn String? get_category_name(LogCategory category)
|
||||
{
|
||||
String val = category_names[category];
|
||||
return val ?: NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn void set_category_name(LogCategory category, String name)
|
||||
{
|
||||
category_names[category] = name;
|
||||
}
|
||||
|
||||
struct NullLogger (Logger)
|
||||
{
|
||||
void* dummy;
|
||||
}
|
||||
|
||||
fn void NullLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
|
||||
{}
|
||||
|
||||
struct MultiLogger (Logger)
|
||||
{
|
||||
Logger[] loggers;
|
||||
}
|
||||
|
||||
fn void MultiLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
|
||||
{
|
||||
foreach (logger : self.loggers)
|
||||
{
|
||||
logger.log(priority, category, tag, file, function, line, fmt, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module std::core::log @private;
|
||||
import std::io, std::thread, std::time;
|
||||
|
||||
struct StderrLogger (Logger) @if(env::LIBC)
|
||||
{
|
||||
void* dummy;
|
||||
}
|
||||
|
||||
fn void StderrLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic @if(env::LIBC)
|
||||
{
|
||||
@stack_mem(256 + 64; Allocator mem)
|
||||
{
|
||||
DString str;
|
||||
str.init(mem, 256);
|
||||
str.appendf(fmt, ...args);
|
||||
TzDateTime time = datetime::now().to_local();
|
||||
$if FULL_LOG:
|
||||
io::eprintfn("[%02d:%02d:%02d:%04d] %s:%d [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), file, line, priority, str);
|
||||
$else
|
||||
io::eprintfn("[%02d:%02d:%02d:%04d] [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), priority, str);
|
||||
$endif
|
||||
};
|
||||
}
|
||||
|
||||
alias LogFn = fn void(void*, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
|
||||
LogFn current_logfn = env::LIBC ??? (LogFn)&StderrLogger.log : (LogFn)&NullLogger.log;
|
||||
OnceFlag log_init;
|
||||
Mutex logger_mutex;
|
||||
Logger current_logger = env::LIBC ??? &stderr_logger : &null_logger;
|
||||
StderrLogger stderr_logger @if (env::LIBC);
|
||||
NullLogger null_logger;
|
||||
LogPriority[256] config_priorities = { [0..255] = ERROR, [CATEGORY_APPLICATION] = INFO, [CATEGORY_TEST] = VERBOSE, [CATEGORY_ASSERT] = WARN};
|
||||
String[256] category_names = {
|
||||
[CATEGORY_APPLICATION] = "APP",
|
||||
[CATEGORY_SYSTEM] = "SYSTEM",
|
||||
[CATEGORY_KERNEL] = "KERNEL",
|
||||
[CATEGORY_AUDIO] = "AUDIO",
|
||||
[CATEGORY_VIDEO] = "VIDEO",
|
||||
[CATEGORY_RENDER] = "RENDER",
|
||||
[CATEGORY_INPUT] = "INPUT",
|
||||
[CATEGORY_NETWORK] = "NETWORD",
|
||||
[CATEGORY_SOCKET] = "SOCKET",
|
||||
[CATEGORY_SECURITY] = "SECURITY",
|
||||
[CATEGORY_TEST] = "TEST",
|
||||
[CATEGORY_ERROR] = "ERROR",
|
||||
[CATEGORY_ASSERT] = "ASSERT",
|
||||
[CATEGORY_CRASH] = "CRASH",
|
||||
[CATEGORY_STATS] = "STATS"
|
||||
};
|
||||
1182
lib/std/core/mem.c3
1182
lib/std/core/mem.c3
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,4 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
// C3 has several different allocators available:
|
||||
//
|
||||
// Name Arena Uses buffer OOM Fallback? Mark? Reset?
|
||||
// ArenaAllocator Yes Yes No Yes Yes
|
||||
// BackedArenaAllocator Yes No Yes Yes Yes
|
||||
// DynamicArenaAllocator Yes No Yes No Yes
|
||||
// HeapAllocator No No No No No *Note: Not for normal use
|
||||
// LibcAllocator No No No No No *Note: Wraps malloc
|
||||
// OnStackAllocator Yes Yes Yes No No *Note: Used by @stack_mem
|
||||
// TempAllocator Yes No Yes No* No* *Note: Mark/reset using @pool
|
||||
// TrackingAllocator No No N/A No No *Note: Wraps other heap allocator
|
||||
// Vmem Yes No No Yes Yes *Note: Can be set to huge sizes
|
||||
|
||||
const DEFAULT_SIZE_PREFIX = usz.sizeof;
|
||||
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
|
||||
@@ -24,413 +10,71 @@ struct TrackingEnv
|
||||
uint line;
|
||||
}
|
||||
|
||||
enum AllocInitType
|
||||
{
|
||||
NO_ZERO,
|
||||
ZERO
|
||||
}
|
||||
|
||||
interface Allocator
|
||||
{
|
||||
<*
|
||||
Acquire memory from the allocator, with the given alignment and initialization type.
|
||||
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require size > 0 : "The size must be 1 or more"
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*? acquire(usz size, AllocInitType init_type, usz alignment = 0);
|
||||
|
||||
<*
|
||||
Resize acquired memory from the allocator, with the given new size and alignment.
|
||||
|
||||
@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);
|
||||
|
||||
<*
|
||||
Release memory acquired using `acquire` or `resize`.
|
||||
|
||||
@require ptr != null : "Empty pointers should never be released"
|
||||
*>
|
||||
fn void reset(usz mark) @optional;
|
||||
fn usz mark() @optional;
|
||||
fn void*! acquire(usz size, bool clear, usz alignment, usz offset);
|
||||
fn void*! resize(void* ptr, usz new_size, usz alignment, usz offset);
|
||||
fn void release(void* ptr, bool aligned);
|
||||
}
|
||||
|
||||
alias MemoryAllocFn = fn char[]?(usz);
|
||||
|
||||
|
||||
|
||||
fn usz alignment_for_allocation(usz alignment) @inline
|
||||
{
|
||||
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
|
||||
}
|
||||
|
||||
macro void* malloc(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
return malloc_try(allocator, size)!!;
|
||||
}
|
||||
|
||||
macro void*? malloc_try(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
$if env::TESTING:
|
||||
char* data = allocator.acquire(size, NO_ZERO)!;
|
||||
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
$else
|
||||
return allocator.acquire(size, NO_ZERO);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void* calloc(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
return calloc_try(allocator, size)!!;
|
||||
}
|
||||
|
||||
macro void*? calloc_try(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator.acquire(size, ZERO);
|
||||
}
|
||||
|
||||
macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard
|
||||
{
|
||||
return realloc_try(allocator, ptr, new_size)!!;
|
||||
}
|
||||
|
||||
macro void*? realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
|
||||
{
|
||||
if (!new_size)
|
||||
{
|
||||
free(allocator, ptr);
|
||||
return null;
|
||||
}
|
||||
if (!ptr) return allocator.acquire(new_size, NO_ZERO);
|
||||
return allocator.resize(ptr, new_size);
|
||||
}
|
||||
|
||||
macro void free(Allocator allocator, void* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
$if env::TESTING:
|
||||
((char*)ptr)[0] = 0xBA;
|
||||
$endif
|
||||
allocator.release(ptr, false);
|
||||
}
|
||||
|
||||
macro void*? malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
$if env::TESTING:
|
||||
char* data = allocator.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
$else
|
||||
return allocator.acquire(size, NO_ZERO, alignment);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void*? calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator.acquire(size, ZERO, alignment);
|
||||
}
|
||||
|
||||
macro void*? realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!new_size)
|
||||
{
|
||||
free_aligned(allocator, ptr);
|
||||
return null;
|
||||
}
|
||||
if (!ptr)
|
||||
{
|
||||
return malloc_aligned(allocator, new_size, alignment);
|
||||
}
|
||||
return allocator.resize(ptr, new_size, alignment);
|
||||
}
|
||||
|
||||
macro void free_aligned(Allocator allocator, void* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
$if env::TESTING:
|
||||
((char*)ptr)[0] = 0xBA;
|
||||
$endif
|
||||
allocator.release(ptr, aligned: true);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc(allocator, $Type.sizeof);
|
||||
$else
|
||||
$Type* val = malloc(allocator, $Type.sizeof);
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_try(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc_try(allocator, $Type.sizeof);
|
||||
$else
|
||||
$Type* val = malloc_try(allocator, $Type.sizeof)!;
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "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];
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
|
||||
*>
|
||||
macro realloc_array(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return realloc_array_try(allocator, ptr, $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 realloc_array_aligned(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)realloc_aligned(allocator, ptr, $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 realloc_array_try(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)realloc_try(allocator, ptr, $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);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator used to clone"
|
||||
@param slice : "The slice to clone"
|
||||
@return "A pointer to the cloned slice"
|
||||
|
||||
@require $kindof(slice) == SLICE || $kindof(slice) == ARRAY
|
||||
*>
|
||||
macro clone_slice(Allocator allocator, slice) @nodiscard
|
||||
{
|
||||
if (!lengthof(slice)) return {};
|
||||
|
||||
var $Type = $typeof(slice[0]);
|
||||
|
||||
$Type[] new_arr = new_array(allocator, $Type, slice.len);
|
||||
mem::copy(new_arr.ptr, &slice[0], slice.len * $Type.sizeof);
|
||||
|
||||
return new_arr;
|
||||
}
|
||||
|
||||
<*
|
||||
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 $kindof(#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)
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if $kindof(#free_fn(desc.start)) == OPTIONAL:
|
||||
#free_fn(desc.start)!;
|
||||
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
|
||||
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
|
||||
void* data = #alloc_fn(header + bytes)!;
|
||||
$else
|
||||
#free_fn(desc.start);
|
||||
void* data = #alloc_fn(header + bytes);
|
||||
$endif
|
||||
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
|
||||
assert(mem > data);
|
||||
AlignedBlock* desc = (AlignedBlock*)mem - 1;
|
||||
*desc = { bytes, data };
|
||||
return mem;
|
||||
}
|
||||
|
||||
<*
|
||||
@require bytes > 0
|
||||
@require alignment > 0
|
||||
*>
|
||||
macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_calloc(#calloc_fn, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
|
||||
$if @typekind(#calloc_fn(bytes)) == OPTIONAL:
|
||||
void* data = #calloc_fn(header + bytes)!;
|
||||
$else
|
||||
void* data = #calloc_fn(header + bytes);
|
||||
$endif
|
||||
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
|
||||
AlignedBlock* desc = (AlignedBlock*)mem - 1;
|
||||
assert(mem > data);
|
||||
*desc = { bytes, data };
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
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 $kindof(#free_fn(data_start)) == OPTIONAL:
|
||||
void* new_data = @aligned_calloc(#calloc_fn, bytes, alignment, offset)!;
|
||||
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
$if @typekind(#free_fn(data_start)) == OPTIONAL:
|
||||
#free_fn(data_start)!;
|
||||
$else
|
||||
#free_fn(data_start);
|
||||
@@ -438,160 +82,143 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
|
||||
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_reserve_size = temp_allocator_default_reserve_size();
|
||||
usz temp_allocator_realloc_size = temp_allocator_default_min_size() * 4;
|
||||
|
||||
fn PoolState push_pool(usz reserve = 0)
|
||||
macro void! @aligned_free(#free_fn, void* old_pointer)
|
||||
{
|
||||
Allocator old = top_temp ? current_temp : create_temp_allocator_on_demand();
|
||||
current_temp = ((TempAllocator*)old).derive_allocator(reserve)!!;
|
||||
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;
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
|
||||
#free_fn(desc.start)!;
|
||||
$else
|
||||
return &allocator::NULL_ALLOCATOR;
|
||||
#free_fn(desc.start);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro usz temp_allocator_size() @local
|
||||
def MemoryAllocFn = fn char[]!(usz);
|
||||
|
||||
fault AllocationFailure
|
||||
{
|
||||
$switch env::MEMORY_ENV:
|
||||
$case NORMAL: return 256 * 1024;
|
||||
$case SMALL: return 1024 * 32;
|
||||
$case TINY: return 1024 * 4;
|
||||
$case NONE: return 0;
|
||||
$endswitch
|
||||
OUT_OF_MEMORY,
|
||||
CHUNK_TOO_LARGE,
|
||||
}
|
||||
|
||||
macro usz temp_allocator_default_min_size() @local
|
||||
fn usz alignment_for_allocation(usz alignment) @inline @private
|
||||
{
|
||||
$switch env::MEMORY_ENV:
|
||||
$case NORMAL: return 16 * 1024;
|
||||
$case SMALL: return 1024 * 2;
|
||||
$case TINY: return 256;
|
||||
$case NONE: return 256;
|
||||
$endswitch
|
||||
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? alignment = mem::DEFAULT_MEM_ALIGNMENT : alignment;
|
||||
}
|
||||
|
||||
macro usz temp_allocator_default_reserve_size() @local
|
||||
// Allocator "functions"
|
||||
|
||||
macro void*! Allocator.alloc_checked(&self, usz size)
|
||||
{
|
||||
$switch env::MEMORY_ENV:
|
||||
$case NORMAL: return 1024;
|
||||
$case SMALL: return 128;
|
||||
$case TINY: return 64;
|
||||
$case NONE: return 64;
|
||||
$endswitch
|
||||
$if env::TESTING:
|
||||
char* data = self.acquire(size, false, 0, 0)!;
|
||||
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
$else
|
||||
return self.acquire(size, false, 0, 0);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro Allocator heap() @deprecated("Use 'mem' instead.") => thread_allocator;
|
||||
|
||||
<*
|
||||
@require !top_temp : "This should never be called when temp already exists"
|
||||
*>
|
||||
fn Allocator create_temp_allocator_on_demand() @private
|
||||
macro void*! Allocator.calloc_checked(&self, usz size)
|
||||
{
|
||||
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(), temp_allocator_reserve_size, temp_allocator_min_size, temp_allocator_realloc_size);
|
||||
return self.acquire(size, true, 0, 0);
|
||||
}
|
||||
macro void*! Allocator.realloc_checked(&self, void* ptr, usz new_size)
|
||||
{
|
||||
return self.resize(ptr, new_size, 0, 0);
|
||||
}
|
||||
|
||||
<*
|
||||
@require !top_temp : "This should never be called when temp already exists"
|
||||
*>
|
||||
fn Allocator create_temp_allocator(Allocator allocator, usz size, usz reserve, usz min_size, usz realloc_size) @private
|
||||
macro Allocator.new_array(&self, $Type, usz size, usz end_padding = 0)
|
||||
{
|
||||
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size, reserve, min_size, realloc_size)!!;
|
||||
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size]!!;
|
||||
}
|
||||
|
||||
macro Allocator temp() @deprecated("Use 'tmem' instead")
|
||||
macro Allocator.new_array_checked(&self, $Type, usz size, usz end_padding = 0)
|
||||
{
|
||||
return current_temp;
|
||||
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size];
|
||||
}
|
||||
|
||||
alias tmem @builtin = current_temp;
|
||||
|
||||
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::FREESTANDING_WASM)
|
||||
macro Allocator.new_zero_array(&self, $Type, usz size, usz end_padding = 0)
|
||||
{
|
||||
auto_create_temp = true;
|
||||
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size]!!;
|
||||
}
|
||||
|
||||
fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::LIBC)
|
||||
macro Allocator.new_zero_array_checked(&self, $Type, usz size, usz end_padding = 0)
|
||||
{
|
||||
destroy_temp_allocators();
|
||||
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size];
|
||||
}
|
||||
|
||||
<*
|
||||
Call this to destroy any memory used by the temp allocators. This will invalidate all temp memory.
|
||||
*>
|
||||
fn void destroy_temp_allocators()
|
||||
macro Allocator.new(&self, $Type, usz end_padding = 0) @nodiscard
|
||||
{
|
||||
if (!top_temp) return;
|
||||
top_temp.free();
|
||||
top_temp = null;
|
||||
current_temp = &LAZY_TEMP;
|
||||
return ($Type*)self.alloc_checked($Type.sizeof + end_padding)!!;
|
||||
}
|
||||
|
||||
import libc;
|
||||
typedef LazyTempAllocator (Allocator) @private = uptr;
|
||||
|
||||
fn void*? LazyTempAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
macro Allocator.new_checked(&self, $Type, usz end_padding = 0) @nodiscard
|
||||
{
|
||||
if (!top_temp) create_temp_allocator_on_demand();
|
||||
return top_temp.acquire(bytes, init_type, alignment);
|
||||
return ($Type*)self.alloc_checked($Type.sizeof + end_padding);
|
||||
}
|
||||
|
||||
fn void*? LazyTempAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
macro Allocator.new_clear(&self, $Type, usz end_padding = 0) @nodiscard
|
||||
{
|
||||
if (!top_temp) create_temp_allocator_on_demand();
|
||||
return top_temp.resize(old_ptr, new_bytes, alignment);
|
||||
return ($Type*)self.calloc_checked($Type.sizeof + end_padding)!!;
|
||||
}
|
||||
|
||||
fn void LazyTempAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
macro Allocator.new_clear_checked(&self, $Type, usz end_padding = 0) @nodiscard
|
||||
{
|
||||
return ($Type*)self.calloc_checked($Type.sizeof + end_padding);
|
||||
}
|
||||
|
||||
const NullAllocator NULL_ALLOCATOR = {};
|
||||
typedef NullAllocator (Allocator) = uptr;
|
||||
|
||||
fn void*? NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
macro Allocator.clone(&self, value)
|
||||
{
|
||||
return mem::OUT_OF_MEMORY~;
|
||||
var x = self.alloc($typeof(value));
|
||||
*x = value;
|
||||
return x;
|
||||
}
|
||||
|
||||
fn void*? NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
macro void* Allocator.alloc(&self, usz size) @nodiscard
|
||||
{
|
||||
return mem::OUT_OF_MEMORY~;
|
||||
return self.alloc_checked(size)!!;
|
||||
}
|
||||
macro void* Allocator.calloc(&self, usz size) @nodiscard
|
||||
{
|
||||
return self.acquire(size, true, 0, 0)!!;
|
||||
}
|
||||
macro void* Allocator.realloc(&self, void* ptr, usz new_size) @nodiscard
|
||||
{
|
||||
return self.resize(ptr, new_size, 0, 0)!!;
|
||||
}
|
||||
|
||||
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
macro void*! Allocator.alloc_aligned(&self, usz size, usz alignment, usz offset = 0)
|
||||
{
|
||||
$if env::TESTING:
|
||||
char* data = self.acquire(size, false, alignment, offset)!;
|
||||
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
$else
|
||||
return self.acquire(size, false, alignment, offset);
|
||||
$endif
|
||||
}
|
||||
macro void*! Allocator.calloc_aligned(&self, usz size, usz alignment, usz offset = 0)
|
||||
{
|
||||
return self.acquire(size, true, alignment, offset);
|
||||
}
|
||||
macro void*! Allocator.realloc_aligned(&self, void* ptr, usz new_size, usz alignment = 0, usz offset = 0)
|
||||
{
|
||||
return self.resize(ptr, new_size, alignment, offset);
|
||||
}
|
||||
|
||||
macro void Allocator.free(&self, void* ptr)
|
||||
{
|
||||
$if env::TESTING:
|
||||
if (ptr) ((char*)ptr)[0] = 0xBA;
|
||||
$endif
|
||||
self.release(ptr, false);
|
||||
}
|
||||
macro void Allocator.free_aligned(&self, void* ptr)
|
||||
{
|
||||
$if env::TESTING:
|
||||
if (ptr) ((char*)ptr)[0] = 0xBA;
|
||||
$endif
|
||||
self.release(ptr, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
module std::core::mem::mempool;
|
||||
import std::core::mem, std::core::mem::allocator, std::math;
|
||||
import std::core::sanitizer::asan;
|
||||
|
||||
const INITIAL_CAPACITY = 0;
|
||||
|
||||
struct FixedBlockPoolNode
|
||||
{
|
||||
void* buffer;
|
||||
FixedBlockPoolNode *next;
|
||||
usz capacity;
|
||||
}
|
||||
|
||||
struct FixedBlockPoolEntry
|
||||
{
|
||||
void *previous;
|
||||
}
|
||||
|
||||
<*
|
||||
Fixed blocks pool pre-allocating blocks backed by an Allocator which are then reserved for the user,
|
||||
blocks deallocated by the user are later re-used by future blocks allocations
|
||||
|
||||
`grow_capacity` can be changed in order to affect how many blocks will be allocated by next pool allocation,
|
||||
it has to be greater than 0
|
||||
`allocated` number of allocated blocks
|
||||
`used` number of used blocks by the user
|
||||
*>
|
||||
struct FixedBlockPool
|
||||
{
|
||||
Allocator allocator;
|
||||
FixedBlockPoolNode head;
|
||||
FixedBlockPoolNode *tail;
|
||||
void *next_free;
|
||||
void *freelist;
|
||||
usz block_size;
|
||||
usz grow_capacity;
|
||||
usz allocated;
|
||||
usz page_size;
|
||||
usz alignment;
|
||||
usz used;
|
||||
bool initialized;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool
|
||||
|
||||
@param [in] allocator : "The allocator to use"
|
||||
@param block_size : "The block size to use"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@param alignment : "The alignment of the buffer"
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
@require block_size > 0 : "Block size must be non zero"
|
||||
@require calculate_actual_capacity(capacity, block_size) * block_size >= block_size
|
||||
: "Total memory would overflow"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_size, usz capacity = INITIAL_CAPACITY, usz alignment = 0)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.tail = &self.head;
|
||||
self.head.next = null;
|
||||
self.block_size = math::max(block_size, FixedBlockPoolEntry.sizeof);
|
||||
capacity = calculate_actual_capacity(capacity, self.block_size);
|
||||
self.alignment = allocator::alignment_for_allocation(alignment);
|
||||
self.page_size = capacity * self.block_size;
|
||||
assert(self.page_size >= self.block_size, "Total memory would overflow %d %d", block_size, capacity);
|
||||
self.head.buffer = self.allocate_page();
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(self.head.buffer, self.page_size);
|
||||
$endif
|
||||
self.head.capacity = capacity;
|
||||
self.next_free = self.head.buffer;
|
||||
self.freelist = null;
|
||||
self.grow_capacity = capacity;
|
||||
self.initialized = true;
|
||||
self.allocated = capacity;
|
||||
self.used = 0;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool
|
||||
|
||||
@param [in] allocator : "The allocator to use"
|
||||
@param $Type : "The type used for setting the block size"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.init_for_type(&self, Allocator allocator, $Type, usz capacity = INITIAL_CAPACITY)
|
||||
{
|
||||
return self.init(allocator, $Type.sizeof, capacity, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool using Temporary allocator
|
||||
|
||||
@param $Type : "The type used for setting the block size"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.tinit_for_type(&self, $Type, usz capacity = INITIAL_CAPACITY) => self.init_for_type(tmem, $Type, capacity);
|
||||
|
||||
<*
|
||||
Initialize an block pool using Temporary allocator
|
||||
|
||||
@param block_size : "The block size to use"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.tinit(&self, usz block_size, usz capacity = INITIAL_CAPACITY) => self.init(tmem, block_size, capacity);
|
||||
|
||||
<*
|
||||
Free up the entire block pool
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn void FixedBlockPool.free(&self)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(self.head.buffer, self.page_size);
|
||||
$endif
|
||||
self.free_page(self.head.buffer);
|
||||
FixedBlockPoolNode* iter = self.head.next;
|
||||
|
||||
while (iter)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(iter.buffer, self.page_size);
|
||||
$endif
|
||||
self.free_page(iter.buffer);
|
||||
FixedBlockPoolNode* current = iter;
|
||||
iter = iter.next;
|
||||
allocator::free(self.allocator, current);
|
||||
}
|
||||
self.initialized = false;
|
||||
self.allocated = 0;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate an block on the block pool, re-uses previously deallocated blocks
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn void* FixedBlockPool.alloc(&self)
|
||||
{
|
||||
defer self.used++;
|
||||
|
||||
if (self.freelist)
|
||||
{
|
||||
FixedBlockPoolEntry* entry = self.freelist;
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(entry, self.block_size);
|
||||
$endif
|
||||
self.freelist = entry.previous;
|
||||
mem::clear(entry, self.block_size);
|
||||
return entry;
|
||||
}
|
||||
|
||||
void* end = self.tail.buffer + (self.tail.capacity * self.block_size);
|
||||
if (self.next_free >= end) self.new_node();
|
||||
void* ptr = self.next_free;
|
||||
self.next_free += self.block_size;
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(ptr, self.block_size);
|
||||
$endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
<*
|
||||
Deallocate a block from the block pool
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
@require self.check_ptr(ptr) : "The pointer should be part of the pool"
|
||||
*>
|
||||
fn void FixedBlockPool.dealloc(&self, void* ptr)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
|
||||
mem::set(ptr, 0xAA, self.block_size);
|
||||
$endif
|
||||
|
||||
FixedBlockPoolEntry* entry = ptr;
|
||||
entry.previous = self.freelist;
|
||||
self.freelist = entry;
|
||||
self.used--;
|
||||
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(ptr, self.block_size);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn bool FixedBlockPool.check_ptr(&self, void *ptr) @local
|
||||
{
|
||||
FixedBlockPoolNode* iter = &self.head;
|
||||
|
||||
while (iter)
|
||||
{
|
||||
void* end = iter.buffer + (iter.capacity * self.block_size);
|
||||
if (ptr >= iter.buffer && ptr < end) return true;
|
||||
iter = iter.next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.grow_capacity > 0 : "How many blocks will it store"
|
||||
*>
|
||||
fn void FixedBlockPool.new_node(&self) @local
|
||||
{
|
||||
FixedBlockPoolNode* node = allocator::new(self.allocator, FixedBlockPoolNode);
|
||||
node.buffer = self.allocate_page();
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(node.buffer, self.page_size);
|
||||
$endif
|
||||
node.capacity = self.grow_capacity;
|
||||
self.tail.next = node;
|
||||
self.tail = node;
|
||||
self.next_free = node.buffer;
|
||||
self.allocated += node.capacity;
|
||||
}
|
||||
|
||||
macro void* FixedBlockPool.allocate_page(&self) @private
|
||||
{
|
||||
return self.alignment > mem::DEFAULT_MEM_ALIGNMENT
|
||||
? allocator::calloc_aligned(self.allocator, self.page_size, self.alignment)!!
|
||||
: allocator::calloc(self.allocator, self.page_size);
|
||||
}
|
||||
|
||||
macro void FixedBlockPool.free_page(&self, void* page) @private
|
||||
{
|
||||
if (self.alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
allocator::free_aligned(self.allocator, page);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator::free(self.allocator, page);
|
||||
}
|
||||
}
|
||||
|
||||
macro usz calculate_actual_capacity(usz capacity, usz block_size) @private
|
||||
{
|
||||
// Assume some overhead
|
||||
if (capacity) return capacity;
|
||||
capacity = (mem::os_pagesize() - 128) / block_size;
|
||||
return capacity ?: 1;
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
<*
|
||||
The VM module holds code for working with virtual memory on supported platforms (currently Win32 and Posix)
|
||||
*>
|
||||
module std::core::mem::vm;
|
||||
import std::io, std::os::win32, std::os::posix, libc;
|
||||
|
||||
<*
|
||||
VirtualMemory is an abstraction for working with an allocated virtual memory area. It will invoke vm:: functions
|
||||
but will perform more checks and track its size (required to unmap the memory on Posix)
|
||||
*>
|
||||
struct VirtualMemory
|
||||
{
|
||||
void* ptr;
|
||||
usz size;
|
||||
VirtualMemoryAccess default_access;
|
||||
}
|
||||
|
||||
faultdef RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, UNMAPPED_ACCESS, UNALIGNED_ADDRESS, RELEASE_FAILED, UPDATE_FAILED, INVALID_ARGS;
|
||||
|
||||
enum VirtualMemoryAccess
|
||||
{
|
||||
PROTECTED,
|
||||
READ,
|
||||
WRITE,
|
||||
READWRITE,
|
||||
EXEC,
|
||||
EXECREAD,
|
||||
EXECWRITE,
|
||||
ANY
|
||||
}
|
||||
|
||||
fn usz aligned_alloc_size(usz size)
|
||||
{
|
||||
$if env::WIN32:
|
||||
return size > 0 ? mem::aligned_offset(size, win32::allocation_granularity()) : win32::allocation_granularity();
|
||||
$else
|
||||
return size > 0 ? mem::aligned_offset(size, mem::os_pagesize()) : mem::os_pagesize();
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate virtual memory, size is rounded up to platform granularity (Win32) / page size (Posix).
|
||||
|
||||
@param size : "The size of the memory to allocate, will be rounded up"
|
||||
@param access : "The initial access permissions."
|
||||
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
|
||||
@return "Pointer to the allocated memory, page aligned"
|
||||
*>
|
||||
fn void*? alloc(usz size, VirtualMemoryAccess access)
|
||||
{
|
||||
$switch:
|
||||
$case env::POSIX:
|
||||
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), posix::MAP_PRIVATE | posix::MAP_ANONYMOUS, -1, 0);
|
||||
if (ptr != posix::MAP_FAILED) return ptr;
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ENOMEM: return mem::OUT_OF_MEMORY~;
|
||||
case errno::EOVERFLOW: return RANGE_OVERFLOW~;
|
||||
case errno::EPERM: return ACCESS_DENIED~;
|
||||
case errno::EINVAL: return INVALID_ARGS~;
|
||||
default: return UNKNOWN_ERROR~;
|
||||
}
|
||||
$case env::WIN32:
|
||||
void* ptr = win32::virtualAlloc(null, aligned_alloc_size(size), MEM_RESERVE, access.to_win32());
|
||||
if (ptr) return ptr;
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_NOT_ENOUGH_MEMORY:
|
||||
case win32::ERROR_COMMITMENT_LIMIT: return mem::OUT_OF_MEMORY~;
|
||||
default: return UNKNOWN_ERROR~;
|
||||
}
|
||||
$default:
|
||||
unsupported("Virtual alloc only available on Win32 and Posix");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Release memory allocated with "alloc".
|
||||
|
||||
@param [&inout] ptr : "Pointer to page to release, should be allocated using vm::alloc"
|
||||
@param size : "The size of the allocated pointer"
|
||||
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
|
||||
*>
|
||||
fn void? release(void* ptr, usz size)
|
||||
{
|
||||
$switch:
|
||||
$case env::POSIX:
|
||||
if (posix::munmap(ptr, aligned_alloc_size(size)))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EINVAL: return INVALID_ARGS~; // Not a valid mapping or size
|
||||
case errno::ENOMEM: return UNMAPPED_ACCESS~; // Address not mapped
|
||||
default: return RELEASE_FAILED~;
|
||||
}
|
||||
}
|
||||
$case env::WIN32:
|
||||
if (win32::virtualFree(ptr, 0, MEM_RELEASE)) return;
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_INVALID_ADDRESS: return INVALID_ARGS~;
|
||||
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY~;
|
||||
default: return RELEASE_FAILED~;
|
||||
}
|
||||
$default:
|
||||
unsupported("Virtual free only available on Win32 and Posix");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Change the access protection of a region in memory. The region must be page aligned.
|
||||
|
||||
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
|
||||
@param len : "To what len to update, must be page aligned"
|
||||
@param access : "The new access"
|
||||
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
|
||||
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
|
||||
*>
|
||||
fn void? protect(void* ptr, usz len, VirtualMemoryAccess access)
|
||||
{
|
||||
$switch:
|
||||
$case env::POSIX:
|
||||
if (!posix::mprotect(ptr, len, access.to_posix())) return;
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES: return ACCESS_DENIED~;
|
||||
case errno::EINVAL: return UNALIGNED_ADDRESS~;
|
||||
case errno::EOVERFLOW: return RANGE_OVERFLOW~;
|
||||
case errno::ENOMEM: return UNMAPPED_ACCESS~;
|
||||
default: return UPDATE_FAILED~;
|
||||
}
|
||||
$case env::WIN32:
|
||||
Win32_Protect old;
|
||||
if (win32::virtualProtect(ptr, len, access.to_win32(), &old)) return;
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~;
|
||||
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~;
|
||||
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~;
|
||||
default: return UPDATE_FAILED~;
|
||||
}
|
||||
$default:
|
||||
unsupported("'virtual_protect' is only available on Win32 and Posix.");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Makes a region of memory available that was previously retrieved using 'alloc'. This is necessary on Win32,
|
||||
but optional on Posix.
|
||||
|
||||
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
|
||||
@param len : "To what len to commit, must be page aligned"
|
||||
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
|
||||
@return? UNKNOWN_ERROR, mem::OUT_OF_MEMORY, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
|
||||
*>
|
||||
fn void? commit(void* ptr, usz len, VirtualMemoryAccess access = READWRITE)
|
||||
{
|
||||
$switch:
|
||||
$case env::POSIX:
|
||||
return protect(ptr, len, READWRITE) @inline;
|
||||
$case env::WIN32:
|
||||
void* result = win32::virtualAlloc(ptr, len, MEM_COMMIT, access.to_win32());
|
||||
if (result) return;
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~;
|
||||
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~;
|
||||
case win32::ERROR_COMMITMENT_LIMIT:
|
||||
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY~;
|
||||
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~;
|
||||
default: return UNKNOWN_ERROR~;
|
||||
}
|
||||
$default:
|
||||
unsupported("'virtual_commit' is only available on Win32 and Posix.");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Notifies that the memory in the region can be released back to the OS. On Win32 this decommits the region,
|
||||
whereas on Posix it tells the system that it may be reused using madvise. The "block" parameter is only
|
||||
respected on Posix, and protects the region from read/write/exec. On Win32 this always happens.
|
||||
|
||||
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
|
||||
@param len : "To what len to commit, must be page aligned"
|
||||
@param block : "Set the released memory to protected"
|
||||
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
|
||||
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
|
||||
*>
|
||||
fn void? decommit(void* ptr, usz len, bool block = true)
|
||||
{
|
||||
$switch:
|
||||
$case env::POSIX:
|
||||
if (posix::madvise(ptr, len, posix::MADV_DONTNEED))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EINVAL: return UNALIGNED_ADDRESS~;
|
||||
case errno::ENOMEM: return UNMAPPED_ACCESS~;
|
||||
default: return UPDATE_FAILED~;
|
||||
}
|
||||
}
|
||||
if (block) (void)protect(ptr, len, PROTECTED) @inline;
|
||||
$case env::WIN32:
|
||||
if (!win32::virtualFree(ptr, len, MEM_DECOMMIT))
|
||||
{
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~;
|
||||
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~;
|
||||
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~;
|
||||
default: return UPDATE_FAILED~;
|
||||
}
|
||||
}
|
||||
$default:
|
||||
unsupported("'virtual_decommit' is only available on Win32 and Posix.");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Map a portion of an already-opened file into memory.
|
||||
|
||||
@param fd : "File descriptor"
|
||||
@param size : "Number of bytes to map, will be rounded up to page size"
|
||||
@param offset : "Byte offset in file, must be page size aligned"
|
||||
@param access : "The initial access permissions"
|
||||
@param shared : "if True then MAP_SHARED else MAP_PRIVATE"
|
||||
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
|
||||
@return "Pointer to the mapped region"
|
||||
*>
|
||||
fn void*? mmap_file(Fd fd, usz size, usz offset = 0, VirtualMemoryAccess access = READ, bool shared = false) @if (env::POSIX)
|
||||
{
|
||||
CInt flags = shared ? posix::MAP_SHARED : posix::MAP_PRIVATE;
|
||||
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), flags, fd, offset);
|
||||
if (ptr != posix::MAP_FAILED) return ptr;
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ENOMEM: return mem::OUT_OF_MEMORY~;
|
||||
case errno::EOVERFLOW: return RANGE_OVERFLOW~;
|
||||
case errno::EPERM: return ACCESS_DENIED~;
|
||||
case errno::EINVAL: return INVALID_ARGS~;
|
||||
case errno::EACCES: return io::NO_PERMISSION~;
|
||||
case errno::EBADF: return io::FILE_NOT_VALID~;
|
||||
case errno::EAGAIN: return io::WOULD_BLOCK~;
|
||||
case errno::ENXIO: return io::FILE_NOT_FOUND~;
|
||||
default: return UNKNOWN_ERROR~;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Create a VirtualMemory using
|
||||
|
||||
@param size : "The size of the memory to allocate."
|
||||
@require size > 0 : "The size must be non-zero"
|
||||
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
|
||||
*>
|
||||
fn VirtualMemory? virtual_alloc(usz size, VirtualMemoryAccess access = PROTECTED)
|
||||
{
|
||||
size = aligned_alloc_size(size);
|
||||
void* ptr = alloc(size, access)!;
|
||||
return { ptr, size, access };
|
||||
}
|
||||
|
||||
<*
|
||||
Commits memory, using vm::commit
|
||||
|
||||
@param offset : "Starting from what offset to commit"
|
||||
@param len : "To what len to commit"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
|
||||
@require offset < self.size : "Offset out of range"
|
||||
@require offset + len <= self.size : "Length out of range"
|
||||
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
|
||||
*>
|
||||
macro void? VirtualMemory.commit(self, usz offset, usz len)
|
||||
{
|
||||
return commit(self.ptr + offset, len, self.default_access);
|
||||
}
|
||||
|
||||
<*
|
||||
Changes protection of a part of memory using vm::protect
|
||||
|
||||
@param offset : "Starting from what offset to update"
|
||||
@param len : "To what len to update"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
|
||||
@require offset < self.size : "Offset out of range"
|
||||
@require offset + len < self.size : "Length out of range"
|
||||
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
|
||||
*>
|
||||
macro void? VirtualMemory.protect(self, usz offset, usz len, VirtualMemoryAccess access)
|
||||
{
|
||||
return protect(self.ptr + offset, len, access);
|
||||
}
|
||||
<*
|
||||
Decommits a part of memory using vm::decommit
|
||||
|
||||
@param offset : "Starting from what offset to decommit"
|
||||
@param len : "To what len to decommit"
|
||||
@param block : "Should the memory be blocked from access after decommit"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
|
||||
@require offset < self.size : "Offset out of range"
|
||||
@require offset + len < self.size : "Length out of range"
|
||||
@return? UPDATE_FAILED
|
||||
*>
|
||||
fn void? VirtualMemory.decommit(self, usz offset, usz len, bool block = true)
|
||||
{
|
||||
return decommit(self.ptr + offset, len, block);
|
||||
}
|
||||
|
||||
<*
|
||||
Releases the memory region
|
||||
|
||||
@require self.ptr != null : "Virtual memory must be initialized to call destroy"
|
||||
*>
|
||||
fn void? VirtualMemory.destroy(&self)
|
||||
{
|
||||
return release(self.ptr, self.size);
|
||||
}
|
||||
|
||||
fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX) @private
|
||||
{
|
||||
switch (self)
|
||||
{
|
||||
case PROTECTED: return posix::PROT_NONE;
|
||||
case READ: return posix::PROT_READ;
|
||||
case WRITE: return posix::PROT_WRITE;
|
||||
case EXEC: return posix::PROT_EXEC;
|
||||
case READWRITE: return posix::PROT_READ | posix::PROT_WRITE;
|
||||
case EXECREAD: return posix::PROT_READ | posix::PROT_EXEC;
|
||||
case EXECWRITE: return posix::PROT_WRITE | posix::PROT_EXEC;
|
||||
case ANY: return posix::PROT_WRITE | posix::PROT_READ | posix::PROT_EXEC;
|
||||
}
|
||||
}
|
||||
|
||||
fn Win32_Protect VirtualMemoryAccess.to_win32(self) @if(env::WIN32) @private
|
||||
{
|
||||
switch (self)
|
||||
{
|
||||
case PROTECTED: return PAGE_NOACCESS;
|
||||
case READ: return PAGE_READONLY;
|
||||
case WRITE: return PAGE_READWRITE;
|
||||
case EXEC: return PAGE_EXECUTE;
|
||||
case READWRITE: return PAGE_READWRITE;
|
||||
case EXECWRITE: return PAGE_EXECUTE_READWRITE;
|
||||
case EXECREAD: return PAGE_EXECUTE_READ;
|
||||
case ANY: return PAGE_EXECUTE_READWRITE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ struct WasmMemory
|
||||
uptr use;
|
||||
}
|
||||
|
||||
fn char[]? WasmMemory.allocate_block(&self, usz bytes)
|
||||
fn char[]! WasmMemory.allocate_block(&self, usz bytes)
|
||||
{
|
||||
if (!self.allocation)
|
||||
{
|
||||
@@ -25,7 +25,7 @@ fn char[]? WasmMemory.allocate_block(&self, usz 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~;
|
||||
if ($$wasm_memory_grow(0, blocks_required) == -1) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
defer self.use += bytes;
|
||||
return ((char*)self.use)[:bytes];
|
||||
@@ -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 |= 1ULL << 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);
|
||||
|
||||
}
|
||||
@@ -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, §ion.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 = ¤t.next;
|
||||
}
|
||||
*ref = cb;
|
||||
}
|
||||
|
||||
struct TypeId
|
||||
{
|
||||
char type;
|
||||
TypeId* parentof;
|
||||
DynamicMethod* dtable;
|
||||
usz sizeof;
|
||||
TypeId* inner;
|
||||
usz len;
|
||||
typeid[*] additional;
|
||||
}
|
||||
|
||||
fn void dl_reg_callback(MachHeader* mh, isz vmaddr_slide)
|
||||
{
|
||||
usz size = 0;
|
||||
assert(runtime_state == INIT || runtime_state == READ_DYLIB, "State was %s", runtime_state);
|
||||
foreach (&dm : find_segment_section_body(mh, "__DATA", "__c3_dynamic", DynamicMethod))
|
||||
{
|
||||
TypeId* type = dm.type;
|
||||
dm.next = type.dtable;
|
||||
type.dtable = dm;
|
||||
DynamicMethod* m = dm;
|
||||
while (m)
|
||||
{
|
||||
m = m.next;
|
||||
}
|
||||
}
|
||||
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3dtor", Callback))
|
||||
{
|
||||
append_xxlizer(&dtor_first, cb);
|
||||
}
|
||||
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3ctor", Callback))
|
||||
{
|
||||
append_xxlizer(&ctor_first, cb);
|
||||
}
|
||||
if (runtime_state != READ_DYLIB) return;
|
||||
runtime_state = RUN_DYLIB_CTORS;
|
||||
Callback* ctor = ctor_first;
|
||||
ctor_first = null;
|
||||
while (ctor)
|
||||
{
|
||||
ctor.xtor();
|
||||
ctor = ctor.next;
|
||||
}
|
||||
assert(runtime_state == RUN_DYLIB_CTORS);
|
||||
runtime_state = READ_DYLIB;
|
||||
}
|
||||
@@ -12,10 +12,7 @@ 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**)
|
||||
{
|
||||
return #m();
|
||||
}
|
||||
macro int @main_to_int_main(#m, int, char**) => #m();
|
||||
macro int @main_to_void_main(#m, int, char**)
|
||||
{
|
||||
#m();
|
||||
@@ -24,7 +21,7 @@ macro int @main_to_void_main(#m, int, char**)
|
||||
|
||||
macro String[] args_to_strings(int argc, char** argv) @private
|
||||
{
|
||||
String[] list = mem::alloc_array(String, argc);
|
||||
String[] list = mem::new_array(String, argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
char* arg = argv[i];
|
||||
@@ -50,13 +47,6 @@ macro int @main_to_int_main_args(#m, int argc, char** argv)
|
||||
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);
|
||||
@@ -66,19 +56,8 @@ macro int @main_to_void_main_args(#m, int argc, char** argv)
|
||||
}
|
||||
|
||||
module std::core::main_stub @if(env::WIN32);
|
||||
import std::os::win32;
|
||||
|
||||
macro win32_set_utf8_codepage() @local
|
||||
{
|
||||
// By default windows uses an OEM codepage that differs based on locale
|
||||
// and does not support printing utf-8 characters. This allows both
|
||||
// printing utf-8 characters from strings and reading them from stdin.
|
||||
win32::setConsoleCP(UTF8);
|
||||
win32::setConsoleOutputCP(UTF8);
|
||||
}
|
||||
|
||||
|
||||
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @cname("CommandLineToArgvW");
|
||||
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
|
||||
{
|
||||
@@ -89,12 +68,12 @@ macro String[] win_command_line_to_strings(ushort* cmd_line) @private
|
||||
|
||||
macro String[] wargs_strings(int argc, Char16** argv) @private
|
||||
{
|
||||
String[] list = mem::alloc_array(String, argc);
|
||||
String[] list = mem::new_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);
|
||||
list[i] = string::new_from_utf16(argstring) ?? "?".copy();
|
||||
}
|
||||
return list[:argc];
|
||||
}
|
||||
@@ -105,80 +84,66 @@ macro void release_wargs(String[] list) @private
|
||||
free(list.ptr);
|
||||
}
|
||||
|
||||
macro int @win_to_err_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_to_err_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
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)
|
||||
macro int @win_to_int_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) => #m();
|
||||
macro int @win_to_void_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
return #m();
|
||||
}
|
||||
|
||||
macro int @win_to_void_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
#m();
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_to_err_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
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)
|
||||
macro int @win_to_int_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
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)
|
||||
macro int @win_to_void_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
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)
|
||||
macro int @win_to_err_main(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(handle, prev_handle, args, show_cmd)) return 1;
|
||||
if (catch #m(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)
|
||||
macro int @win_to_int_main(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
return #m(handle, prev_handle, args, show_cmd);
|
||||
return #m(handle, args, show_cmd);
|
||||
}
|
||||
|
||||
macro int @win_to_void_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_to_void_main(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
#m(handle, prev_handle, args, show_cmd);
|
||||
#m(handle, args, show_cmd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @wmain_to_err_main_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(args)) return 1;
|
||||
@@ -187,23 +152,13 @@ macro int @wmain_to_err_main_args(#m, int argc, Char16** argv)
|
||||
|
||||
macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
return #m(args);
|
||||
}
|
||||
|
||||
macro int @_wmain_runner(#m, int argc, Char16** argv)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
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)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
#m(args);
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
<*
|
||||
Ref provides a general *external* ref counted wrapper for a pointer. For convenience, a ref count of 0
|
||||
means the reference is still valid.
|
||||
|
||||
When the rc drops to -1, it will first run the dealloc function on the underlying pointer (if it exists),
|
||||
then free the pointer and the atomic variable assuming that they are allocated using the Allocator in the Ref.
|
||||
|
||||
@require !$defined(Type.dealloc) ||| $defined(Type.dealloc(&&(Type){})) : "'dealloc' must only take a pointer to the underlying type"
|
||||
@require !$defined(Type.dealloc) ||| $typeof((Type){}.dealloc()) == void : "'dealloc' must return 'void'"
|
||||
*>
|
||||
module std::core::mem::ref <Type>;
|
||||
import std::thread, std::atomic;
|
||||
|
||||
const OVERALIGNED @private = Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
alias DeallocFn = fn void(void*);
|
||||
|
||||
fn Ref wrap(Type* ptr, Allocator allocator = mem)
|
||||
{
|
||||
return { .refcount = allocator::new(allocator, Atomic{int}), .ptr = ptr, .allocator = allocator };
|
||||
}
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $defined(Type a = $vaexpr[0]) : "The first argument must be an initializer for the type"
|
||||
*>
|
||||
macro Ref new(..., Allocator allocator = mem)
|
||||
{
|
||||
|
||||
$switch:
|
||||
$case OVERALIGNED && !$vacount:
|
||||
Type* ptr = allocator::calloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
|
||||
$case OVERALIGNED:
|
||||
Type* ptr = allocator::malloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
|
||||
*ptr = $vaexpr[0];
|
||||
$case !$vacount:
|
||||
Type* ptr = allocator::calloc(allocator, Type.sizeof);
|
||||
$default:
|
||||
Type* ptr = allocator::malloc(allocator, Type.sizeof);
|
||||
*ptr = $vaexpr[0];
|
||||
$endswitch
|
||||
return { .refcount = allocator::new(allocator, Atomic{int}),
|
||||
.ptr = ptr,
|
||||
.allocator = allocator };
|
||||
}
|
||||
|
||||
struct Ref
|
||||
{
|
||||
Atomic{int}* refcount;
|
||||
Type* ptr;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
fn Ref* Ref.retain(&self)
|
||||
{
|
||||
assert(self.refcount != null, "Reference already released");
|
||||
assert(self.refcount.load(RELAXED) >= 0, "Retaining zombie");
|
||||
self.refcount.add(1, RELAXED);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn void Ref.release(&self)
|
||||
{
|
||||
assert(self.refcount != null, "Reference already released");
|
||||
assert(self.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
|
||||
if (self.refcount.sub(1, RELAXED) == 0)
|
||||
{
|
||||
thread::fence(ACQUIRE);
|
||||
$if $defined(Type.dealloc):
|
||||
self.ptr.dealloc();
|
||||
$endif
|
||||
$if OVERALIGNED:
|
||||
allocator::free_aligned(self.allocator, self.ptr);
|
||||
$else
|
||||
allocator::free(self.allocator, self.ptr);
|
||||
$endif
|
||||
allocator::free(self.allocator, self.refcount);
|
||||
*self = {};
|
||||
}
|
||||
}
|
||||
|
||||
module std::core::mem::rc;
|
||||
import std::thread, std::atomic;
|
||||
|
||||
<*
|
||||
A RefCounted struct should be an inline base of a struct.
|
||||
If a `dealloc` is defined, then it will be called rather than `free`
|
||||
|
||||
For convenience, a ref count of 0 is still valid, and the struct is
|
||||
only freed when when ref count drops to -1.
|
||||
|
||||
The macros rc::retain and rc::release must be used on the full pointer,
|
||||
not on the RefCounted substruct.
|
||||
|
||||
So `Foo* f = ...; RefCounted* rc = f; rc::release(rc);` will not do the right thing.
|
||||
*>
|
||||
struct RefCounted
|
||||
{
|
||||
Atomic{int} refcount;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
|
||||
*>
|
||||
macro retain(refcounted)
|
||||
{
|
||||
if (refcounted)
|
||||
{
|
||||
assert(refcounted.refcount.load(RELAXED) >= 0, "Retaining zombie");
|
||||
refcounted.refcount.add(1, RELAXED);
|
||||
}
|
||||
return refcounted;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
|
||||
@require !$defined(refcounted.dealloc()) ||| $typeof(refcounted.dealloc()) == void
|
||||
: "Expected refcounted type to have a valid dealloc"
|
||||
*>
|
||||
macro void release(refcounted)
|
||||
{
|
||||
if (!refcounted) return;
|
||||
assert(refcounted.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
|
||||
if (refcounted.refcount.sub(1, RELAXED) == 0)
|
||||
{
|
||||
thread::fence(ACQUIRE);
|
||||
$if $defined(refcounted.dealloc):
|
||||
refcounted.dealloc();
|
||||
$else
|
||||
free(refcounted);
|
||||
$endif
|
||||
}
|
||||
}
|
||||
@@ -2,47 +2,248 @@
|
||||
// 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;
|
||||
import libc;
|
||||
|
||||
struct ReflectedParam (Printable) @if(!$defined(ReflectedParam))
|
||||
{
|
||||
String name;
|
||||
typeid type;
|
||||
}
|
||||
|
||||
struct AnyRaw
|
||||
struct AnyStruct
|
||||
{
|
||||
void* ptr;
|
||||
typeid type;
|
||||
}
|
||||
|
||||
struct SliceRaw
|
||||
struct SubArrayStruct
|
||||
{
|
||||
void* ptr;
|
||||
usz len;
|
||||
}
|
||||
|
||||
macro @enum_lookup($Type, #value, value)
|
||||
def BenchmarkFn = fn void!();
|
||||
|
||||
struct BenchmarkUnit
|
||||
{
|
||||
$foreach $val : $Type.values:
|
||||
if ($val.#value == value) return $val;
|
||||
$endforeach
|
||||
return NOT_FOUND~;
|
||||
String name;
|
||||
BenchmarkFn func;
|
||||
}
|
||||
|
||||
macro @enum_lookup_new($Type, $name, value)
|
||||
fn BenchmarkUnit[] benchmark_collection_create(Allocator* allocator = mem::heap())
|
||||
{
|
||||
$foreach $val : $Type.values:
|
||||
if ($val.$eval($name) == value) return $val;
|
||||
$endforeach
|
||||
return NOT_FOUND~;
|
||||
BenchmarkFn[] fns = $$BENCHMARK_FNS;
|
||||
String[] names = $$BENCHMARK_NAMES;
|
||||
BenchmarkUnit[] benchmarks = allocator.new_array(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;
|
||||
|
||||
module std::core::runtime @if(env::FREESTANDING_WASM);
|
||||
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)
|
||||
{
|
||||
int benchmarks_passed = 0;
|
||||
int benchmark_count = benchmarks.len;
|
||||
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;
|
||||
anyfault err;
|
||||
|
||||
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++)
|
||||
{
|
||||
err = @catch(unit.func()) @inline;
|
||||
@volatile_load(err);
|
||||
}
|
||||
|
||||
clock = std::time::clock::now();
|
||||
sys_clock_started = $$sysclock();
|
||||
|
||||
for (uint i = 0; i < benchmark_max_iterations; i++)
|
||||
{
|
||||
err = @catch(unit.func()) @inline;
|
||||
@volatile_load(err);
|
||||
}
|
||||
|
||||
sys_clock_finished = $$sysclock();
|
||||
NanoDuration nano_seconds = clock.mark();
|
||||
sys_clocks = sys_clock_finished - sys_clock_started;
|
||||
|
||||
if (err)
|
||||
{
|
||||
io::printfn("[failed] Failed due to: %s", err);
|
||||
continue;
|
||||
}
|
||||
|
||||
io::printfn("[ok] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
|
||||
benchmarks_passed++;
|
||||
}
|
||||
|
||||
io::printfn("\n%d benchmark%s run.\n", benchmark_count, benchmark_count > 1 ? "s" : "");
|
||||
io::printfn("Benchmarks Result: %s. %d passed, %d failed.",
|
||||
benchmarks_passed < benchmark_count ? "FAILED" : "ok",
|
||||
benchmarks_passed,
|
||||
benchmark_count - benchmarks_passed);
|
||||
|
||||
return benchmark_count == benchmarks_passed;
|
||||
}
|
||||
|
||||
fn bool default_benchmark_runner()
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
return run_benchmarks(benchmark_collection_create(mem::temp()));
|
||||
};
|
||||
}
|
||||
|
||||
def TestFn = fn void!();
|
||||
|
||||
struct TestUnit
|
||||
{
|
||||
String name;
|
||||
TestFn func;
|
||||
}
|
||||
|
||||
fn TestUnit[] test_collection_create(Allocator* allocator = mem::heap())
|
||||
{
|
||||
TestFn[] fns = $$TEST_FNS;
|
||||
String[] names = $$TEST_NAMES;
|
||||
TestUnit[] tests = allocator.new_array(TestUnit, names.len);
|
||||
foreach (i, test : fns)
|
||||
{
|
||||
tests[i] = { names[i], fns[i] };
|
||||
}
|
||||
return tests;
|
||||
}
|
||||
|
||||
struct TestContext
|
||||
{
|
||||
JmpBuf buf;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
TestContext* test_context @private;
|
||||
|
||||
fn void test_panic(String message, String file, String function, uint line)
|
||||
{
|
||||
io::printn("[error]");
|
||||
io::print("\n Error: ");
|
||||
io::print(message);
|
||||
io::printn();
|
||||
io::printfn(" - in %s %s:%s.\n", function, file, line);
|
||||
libc::longjmp(&test_context.buf, 1);
|
||||
}
|
||||
|
||||
fn bool run_tests(TestUnit[] tests)
|
||||
{
|
||||
usz max_name;
|
||||
foreach (&unit : tests)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
}
|
||||
quicksort(tests, &cmp_test_unit);
|
||||
|
||||
TestContext context;
|
||||
test_context = &context;
|
||||
|
||||
PanicFn old_panic = builtin::panic;
|
||||
defer builtin::panic = old_panic;
|
||||
builtin::panic = &test_panic;
|
||||
int tests_passed = 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);
|
||||
io::printn(name);
|
||||
name.clear();
|
||||
foreach(unit : tests)
|
||||
{
|
||||
defer name.clear();
|
||||
name.appendf("Testing %s ", unit.name);
|
||||
name.append_repeat('.', max_name - unit.name.len + 2);
|
||||
io::printf("%s ", name.str_view());
|
||||
if (libc::setjmp(&context.buf) == 0)
|
||||
{
|
||||
if (catch err = unit.func())
|
||||
{
|
||||
io::printfn("[failed] Failed due to: %s", err);
|
||||
continue;
|
||||
}
|
||||
io::printn("[ok]");
|
||||
tests_passed++;
|
||||
}
|
||||
}
|
||||
io::printfn("\n%d test%s run.\n", test_count, test_count > 1 ? "s" : "");
|
||||
io::printfn("Test Result: %s. %d passed, %d failed.",
|
||||
tests_passed < test_count ? "FAILED" : "ok", tests_passed, test_count - tests_passed);
|
||||
return test_count == tests_passed;
|
||||
}
|
||||
|
||||
fn bool default_test_runner()
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
return run_tests(test_collection_create(mem::temp()));
|
||||
};
|
||||
}
|
||||
|
||||
module std::core::runtime @if(WASM_NOLIBC);
|
||||
|
||||
extern fn void __wasm_call_ctors();
|
||||
fn void wasm_initialize() @cname("_initialize") @wasm
|
||||
fn void wasm_initialize() @extern("_initialize") @wasm
|
||||
{
|
||||
// The linker synthesizes this to call constructors.
|
||||
__wasm_call_ctors();
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
module std::core::runtime;
|
||||
import libc, std::time, std::io, std::sort, std::math, std::collections::map;
|
||||
|
||||
alias BenchmarkFn = fn void ();
|
||||
|
||||
HashMap { String, uint } bench_fn_iters @local;
|
||||
|
||||
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] };
|
||||
if (!bench_fn_iters.has_key(names[i])) bench_fn_iters[names[i]] = benchmark_max_iterations;
|
||||
}
|
||||
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;
|
||||
foreach (k : bench_fn_iters.key_iter()) bench_fn_iters[k] = value;
|
||||
}
|
||||
|
||||
fn void set_benchmark_func_iterations(String func, uint value) @builtin
|
||||
{
|
||||
assert(value > 0);
|
||||
bench_fn_iters[func] = value;
|
||||
}
|
||||
|
||||
|
||||
Clock benchmark_clock @local;
|
||||
NanoDuration benchmark_nano_seconds @local;
|
||||
long cycle_start @local;
|
||||
long cycle_stop @local;
|
||||
DString benchmark_log @local;
|
||||
bool benchmark_warming @local;
|
||||
uint this_iteration @local;
|
||||
bool benchmark_stop @local;
|
||||
|
||||
macro void @start_benchmark()
|
||||
{
|
||||
benchmark_clock = clock::now();
|
||||
cycle_start = $$sysclock();
|
||||
}
|
||||
|
||||
macro void @end_benchmark()
|
||||
{
|
||||
benchmark_nano_seconds = benchmark_clock.mark();
|
||||
cycle_stop = $$sysclock();
|
||||
}
|
||||
|
||||
macro void @kill_benchmark(String format, ...)
|
||||
{
|
||||
@log_benchmark(format, $vasplat);
|
||||
benchmark_stop = true;
|
||||
}
|
||||
|
||||
macro void @log_benchmark(msg, args...) => @pool()
|
||||
{
|
||||
if (benchmark_warming) return;
|
||||
|
||||
benchmark_log.appendf("%s [%d]: ", $$FUNC, this_iteration);
|
||||
benchmark_log.appendfn(msg, ...args);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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());
|
||||
|
||||
benchmark_warming = true;
|
||||
for (uint i = 0; i < benchmark_warmup_iterations; i++)
|
||||
{
|
||||
unit.func() @inline;
|
||||
}
|
||||
benchmark_warming = false;
|
||||
|
||||
NanoDuration running_timer;
|
||||
long total_clocks;
|
||||
|
||||
uint current_benchmark_iterations = bench_fn_iters[unit.name] ?? benchmark_max_iterations;
|
||||
char[] perc_str = { [0..19] = ' ', [20] = 0 };
|
||||
int perc = 0;
|
||||
uint print_step = current_benchmark_iterations / 100;
|
||||
|
||||
for (this_iteration = 0; this_iteration < current_benchmark_iterations; ++this_iteration, benchmark_nano_seconds = {})
|
||||
{
|
||||
if (0 == this_iteration % print_step) // only print right about when the % will update
|
||||
{
|
||||
perc_str[0..(uint)math::floor((this_iteration / (float)current_benchmark_iterations) * 20)] = '#';
|
||||
perc = (uint)math::ceil(100 * (this_iteration / (float)current_benchmark_iterations));
|
||||
|
||||
io::printf("\r%s [%s] %d / %d (%d%%)", name.str_view(), (ZString)perc_str, this_iteration, current_benchmark_iterations, perc);
|
||||
io::stdout().flush()!!;
|
||||
}
|
||||
|
||||
@start_benchmark(); // can be overridden by calls inside the unit's func
|
||||
|
||||
unit.func() @inline;
|
||||
if (benchmark_stop) return false;
|
||||
|
||||
if (benchmark_nano_seconds == (NanoDuration){}) @end_benchmark(); // only mark when it wasn't already by the unit.func
|
||||
|
||||
total_clocks += cycle_stop - cycle_start;
|
||||
running_timer += benchmark_nano_seconds;
|
||||
}
|
||||
|
||||
float clock_cycles = (float)total_clocks / current_benchmark_iterations;
|
||||
float measurement = (float)running_timer / current_benchmark_iterations;
|
||||
String[] units = { "nanoseconds", "microseconds", "milliseconds", "seconds" };
|
||||
|
||||
float adjusted_measurement = measurement;
|
||||
while (adjusted_measurement > 1_000) adjusted_measurement /= 1_000;
|
||||
float adjusted_runtime_total = (float)running_timer;
|
||||
while (adjusted_runtime_total > 1_000) adjusted_runtime_total /= 1_000;
|
||||
|
||||
io::printf("\r%s ", name.str_view());
|
||||
io::printfn(
|
||||
"[COMPLETE] %.2f %s, %.2f CPU clocks, %d iterations (runtime %.2f %s)",
|
||||
adjusted_measurement,
|
||||
units[math::min(3, (int)math::floor(math::log(measurement, 1_000)))],
|
||||
clock_cycles,
|
||||
current_benchmark_iterations,
|
||||
adjusted_runtime_total,
|
||||
units[math::min(3, (int)math::floor(math::log((float)running_timer, 1_000)))],
|
||||
);
|
||||
}
|
||||
|
||||
io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
fn bool default_benchmark_runner(String[] args) => @pool()
|
||||
{
|
||||
benchmark_log.init(mem);
|
||||
defer
|
||||
{
|
||||
if (benchmark_log.len()) io::printfn("\n---------- BENCHMARK LOG ----------\n%s\n", benchmark_log.str_view());
|
||||
benchmark_log.free();
|
||||
}
|
||||
|
||||
return run_benchmarks(benchmark_collection_create(tmem));
|
||||
}
|
||||
@@ -1,383 +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, std::os;
|
||||
|
||||
|
||||
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;
|
||||
<* Controls level of printed logs *>
|
||||
LogPriority log_level;
|
||||
|
||||
// internal state
|
||||
bool assert_print_backtrace;
|
||||
bool has_ansi_codes;
|
||||
bool is_in_panic;
|
||||
bool is_quiet_mode;
|
||||
bool is_no_capture;
|
||||
bool sort;
|
||||
bool check_leaks;
|
||||
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 sig_bus_error(CInt i, void*, void* context) @local @if(env::POSIX)
|
||||
{
|
||||
panic_test("Bus error", "Unknown", "Unknown", 1, posix::stack_instruction(context));
|
||||
}
|
||||
|
||||
fn void sig_segmentation_fault(CInt i, void*, void* context) @local @if(env::POSIX)
|
||||
{
|
||||
panic_test("Segmentation fault", "Unknown", "Unknown", 1, posix::stack_instruction(context));
|
||||
}
|
||||
|
||||
fn void test_panic(String message, String file, String function, uint line) @local
|
||||
{
|
||||
panic_test(message, file, function, line);
|
||||
}
|
||||
fn void panic_test(String message, String file, String function, uint line, void* extra_trace = null) @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, extra_trace ? 3 : 0, extra_trace);
|
||||
$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::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;
|
||||
if (!tests.len)
|
||||
{
|
||||
io::printn("There are no test units to run.");
|
||||
return true; // no tests == technically a pass
|
||||
}
|
||||
$if !env::NO_LIBC && env::POSIX:
|
||||
posix::install_signal_handler(libc::SIGBUS, &sig_bus_error);
|
||||
posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
|
||||
$endif
|
||||
foreach (&unit : tests)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
}
|
||||
TestContext context =
|
||||
{
|
||||
.assert_print_backtrace = true,
|
||||
.breakpoint_on_assert = false,
|
||||
.sort = true,
|
||||
.check_leaks = true,
|
||||
.log_level = LogPriority.ERROR,
|
||||
.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":
|
||||
context.sort = false;
|
||||
case "--test-noleak":
|
||||
context.check_leaks = false;
|
||||
case "--test-nocapture":
|
||||
case "--test-show-output":
|
||||
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++;
|
||||
case "--test-log-level":
|
||||
if (i == args.len - 1)
|
||||
{
|
||||
io::printn("Missing log level for argument `--test-log-level`.");
|
||||
return false;
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
String upper = args[i + 1].to_upper_copy(tmem);
|
||||
if (catch @try(context.log_level, enum_by_name(LogPriority, upper)))
|
||||
{
|
||||
io::printn("Log level given to `--test-log-level` is not one of verbose, debug, info, warn, error or critical.");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
i++;
|
||||
default:
|
||||
io::printfn("Unknown argument: %s", args[i]);
|
||||
}
|
||||
}
|
||||
test_context = &context;
|
||||
log::set_priority_all(test_context.log_level);
|
||||
|
||||
if (context.sort)
|
||||
{
|
||||
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 (context.check_leaks) allocator::thread_allocator = &mem;
|
||||
@pool()
|
||||
{
|
||||
unit.func();
|
||||
};
|
||||
// track cleanup that may take place in teardown_fn
|
||||
if (context.teardown_fn)
|
||||
{
|
||||
context.teardown_fn();
|
||||
}
|
||||
if (context.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));
|
||||
}
|
||||
|
||||
@@ -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 void 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 void 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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -1,169 +0,0 @@
|
||||
module std::core::array;
|
||||
|
||||
<*
|
||||
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>
|
||||
{
|
||||
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 };
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,261 +0,0 @@
|
||||
// Copyright (c) 2024-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.
|
||||
|
||||
<*
|
||||
This module provides functionality for escaping and unescaping strings
|
||||
with standard C-style escape sequences, similar to what's used in JSON
|
||||
and other string literals.
|
||||
*>
|
||||
module std::core::string;
|
||||
import std::io;
|
||||
|
||||
faultdef INVALID_ESCAPE_SEQUENCE, UNTERMINATED_STRING, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE;
|
||||
|
||||
<*
|
||||
Escape a string by adding quotes and converting special characters to escape sequences.
|
||||
|
||||
@param allocator : "The allocator to use for the result"
|
||||
@param s : "The string to escape"
|
||||
@param strip_quotes : "Do not include beginning and end quotes, defaults to false"
|
||||
@return "The escaped string with surrounding quotes, can safely be cast to ZString"
|
||||
*>
|
||||
fn String String.escape(String s, Allocator allocator, bool strip_quotes = true)
|
||||
{
|
||||
// Conservative allocation: most strings need minimal escaping
|
||||
usz initial_capacity = s.len + s.len / 5 + 2; // ~1.2x + quotes
|
||||
|
||||
if (allocator == tmem)
|
||||
{
|
||||
DString result = dstring::new_with_capacity(tmem, initial_capacity);
|
||||
escape_dstring(s, result, strip_quotes);
|
||||
return result.str_view();
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
DString result = dstring::temp_with_capacity(initial_capacity);
|
||||
escape_dstring(s, result, strip_quotes);
|
||||
return result.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
fn void escape_dstring(String s, DString result, bool strip_quotes) @private
|
||||
{
|
||||
if (!strip_quotes) result.append_char('"');
|
||||
|
||||
foreach (char c : s)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"': result.append(`\"`);
|
||||
case '\\': result.append(`\\`);
|
||||
case '\b': result.append(`\b`);
|
||||
case '\f': result.append(`\f`);
|
||||
case '\n': result.append(`\n`);
|
||||
case '\r': result.append(`\r`);
|
||||
case '\t': result.append(`\t`);
|
||||
case '\v': result.append(`\v`);
|
||||
case '\0': result.append(`\0`);
|
||||
default:
|
||||
if (c >= 32 && c <= 126)
|
||||
{
|
||||
// Printable ASCII
|
||||
result.append_char(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-printable, use hex escape
|
||||
result.appendf("\\x%02x", (uint)c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!strip_quotes) result.append_char('"');
|
||||
|
||||
}
|
||||
<*
|
||||
Escape a string using the temp allocator.
|
||||
|
||||
@param s : "The string to escape"
|
||||
@param strip_quotes : "Do not include beginning and end quotes, defaults to false"
|
||||
@return "The escaped string with surrounding quotes"
|
||||
*>
|
||||
fn String String.tescape(String s, bool strip_quotes = false) => s.escape(tmem, strip_quotes);
|
||||
|
||||
<*
|
||||
Calculate the length needed for an escaped string (including quotes).
|
||||
|
||||
@param s : "The string to check"
|
||||
@return "The length needed for the escaped version"
|
||||
*>
|
||||
fn usz escape_len(String s)
|
||||
{
|
||||
usz len = 2; // For quotes
|
||||
foreach (char c : s)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\v':
|
||||
case '\0':
|
||||
len += 2; // \X
|
||||
default:
|
||||
if (c >= 32 && c <= 126)
|
||||
{
|
||||
len += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
len += 4; // \xHH
|
||||
}
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
<*
|
||||
Unescape a quoted string by parsing escape sequences.
|
||||
|
||||
@param allocator : "The allocator to use for the result"
|
||||
@param s : "The quoted string to unescape"
|
||||
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
|
||||
@param lenient : "Be lenient with escapes, resolving unknown sequences to the escape character, defaults to false"
|
||||
@return "The unescaped string without quotes, safe to convert to ZString"
|
||||
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
|
||||
*>
|
||||
fn String? String.unescape(String s, Allocator allocator, bool allow_unquoted = false, bool lenient = false)
|
||||
{
|
||||
if (s.len >= 2 && s[0] == '"' && s[^1] == '"')
|
||||
{
|
||||
// Remove quotes.
|
||||
s = s[1:^2];
|
||||
}
|
||||
else if (!allow_unquoted) return UNTERMINATED_STRING~;
|
||||
|
||||
// Handle empty string case
|
||||
if (!s.len)
|
||||
{
|
||||
return "".copy(allocator);
|
||||
}
|
||||
if (allocator == tmem)
|
||||
{
|
||||
DString result = dstring::new_with_capacity(tmem, s.len);
|
||||
unescape_dstring(s, result, allow_unquoted, lenient)!;
|
||||
return result.str_view();
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
DString result = dstring::temp_with_capacity(s.len);
|
||||
unescape_dstring(s, result, allow_unquoted, lenient)!;
|
||||
return result.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
fn void? unescape_dstring(String s, DString result, bool allow_unquoted = false, bool lenient = false) @private
|
||||
{
|
||||
usz len = s.len;
|
||||
for (usz i = 0; i < len; i++)
|
||||
{
|
||||
char c = s[i];
|
||||
if (c != '\\')
|
||||
{
|
||||
result.append_char(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle escape sequence
|
||||
if (i + 1 >= len) return INVALID_ESCAPE_SEQUENCE~;
|
||||
|
||||
char escape_char = s[++i];
|
||||
switch (escape_char)
|
||||
{
|
||||
case '"': result.append_char('"');
|
||||
case '\\': result.append_char('\\');
|
||||
case '/': result.append_char('/');
|
||||
case 'b': result.append_char('\b');
|
||||
case 'f': result.append_char('\f');
|
||||
case 'n': result.append_char('\n');
|
||||
case 'r': result.append_char('\r');
|
||||
case 't': result.append_char('\t');
|
||||
case 'v': result.append_char('\v');
|
||||
case '0': result.append_char('\0');
|
||||
case 'x':
|
||||
// Hex escape \xHH
|
||||
if (i + 2 >= len) return INVALID_HEX_ESCAPE~;
|
||||
char h1 = s[++i];
|
||||
char h2 = s[++i];
|
||||
if (!h1.is_xdigit() || !h2.is_xdigit()) return INVALID_HEX_ESCAPE~;
|
||||
uint val = h1 > '9' ? (h1 | 32) - 'a' + 10 : h1 - '0';
|
||||
val = val << 4;
|
||||
val += h2 > '9' ? (h2 | 32) - 'a' + 10 : h2 - '0';
|
||||
result.append_char((char)val);
|
||||
case 'u':
|
||||
// Unicode escape \uHHHH
|
||||
if (i + 4 >= len) return INVALID_UNICODE_ESCAPE~;
|
||||
uint val;
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
char hex_char = s[++i];
|
||||
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~;
|
||||
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
|
||||
}
|
||||
result.append_char32(val);
|
||||
case 'U':
|
||||
// Unicode escape \UHHHHHHHH
|
||||
if (i + 8 >= len) return INVALID_UNICODE_ESCAPE~;
|
||||
uint val;
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
char hex_char = s[++i];
|
||||
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~;
|
||||
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
|
||||
}
|
||||
result.append_char32(val);
|
||||
default:
|
||||
if (!lenient) return INVALID_ESCAPE_SEQUENCE~;
|
||||
result.append_char(escape_char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Unescape a quoted string using the temp allocator.
|
||||
|
||||
@param s : "The quoted string to unescape"
|
||||
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
|
||||
@param lenient : "Be lenient with escapes, resolving unknown sequences to the escape character, defaults to false"
|
||||
@return "The unescaped string without quotes"
|
||||
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
|
||||
*>
|
||||
fn String? String.tunescape(String s, bool allow_unquoted = false, bool lenient = false) => s.unescape(tmem, allow_unquoted, lenient);
|
||||
|
||||
<*
|
||||
Check if a character needs to be escaped in a string literal.
|
||||
|
||||
@param c : "The character to check"
|
||||
@return "True if the character needs escaping"
|
||||
*>
|
||||
fn bool needs_escape(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\v':
|
||||
case '\0':
|
||||
return true;
|
||||
default:
|
||||
return c < 32 || c > 126;
|
||||
}
|
||||
}
|
||||
@@ -11,39 +11,13 @@ fn void StringIterator.reset(&self)
|
||||
self.current = 0;
|
||||
}
|
||||
|
||||
fn Char32? StringIterator.next(&self)
|
||||
fn Char32! StringIterator.next(&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)!;
|
||||
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;
|
||||
}
|
||||
usz len = self.utf8.len;
|
||||
usz current = self.current;
|
||||
if (current >= len) return IteratorResult.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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user