Compare commits

...

123 Commits

Author SHA1 Message Date
Christoffer Lerno
1a3cdc01ec Fix deprecation warnings not silenced. 2026-02-26 03:30:35 +01:00
Christoffer Lerno
808ce6d605 Fix deprecation warnings not silenced. 2026-02-26 00:28:36 +01:00
Christoffer Lerno
09606a2998 Update hint for deprecation 2026-02-26 00:15:58 +01:00
Christoffer Lerno
0a21323652 Fixes 2026-02-26 00:11:55 +01:00
Christoffer Lerno
c990b5aaf1 Typekind enums are changed CONST_ENUM -> CONSTDEF, DISTINCT -> TYPEDEF. 2026-02-25 23:52:13 +01:00
Christoffer Lerno
98e0985a10 Fix error message on deprecated enum = 2026-02-25 23:27:22 +01:00
Christoffer Lerno
5974bf6cd0 Make first RC 0.7.10 2026-02-25 18:11:42 +01:00
Manu Linares
992890065a vendor-fetch lists available libraries (#2976)
* vendor-fetch lists available libraries

from `github.com/c3lang/vendor` when no library is specified.

* add release notes
2026-02-24 22:55:18 +01:00
Christoffer Lerno
d4dbe27072 Add mention of LLVM libs 2026-02-24 20:00:50 +01:00
Christoffer Lerno
56a11ae3ca Fix text in error message. 2026-02-24 19:07:38 +01:00
konimarti
f1d03c194f distributions: fix tests (#2975)
Remove stochastic elements in the distributions tests and ensure they
are deterministic.

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-24 16:18:53 +01:00
Christoffer Lerno
0085fe73ad Fix test 2026-02-24 15:18:57 +01:00
Christoffer Lerno
a238b62483 Typo 2026-02-24 14:58:28 +01:00
Book-reader
58022e7cca only enable emulated TLS on android compiling natively in termux (#2974) 2026-02-24 00:10:22 +01:00
Christoffer Lerno
d2f046780d - Compiler crash using ?? with a void? macro #2973 2026-02-23 23:28:32 +01:00
Christoffer Lerno
ec65c5761e Fixed test 2026-02-23 19:21:56 +01:00
Christoffer Lerno
0ed917cdc2 Improve error when trying to use an extern const as a compile time constant. #2969 2026-02-23 19:15:30 +01:00
Christoffer Lerno
8373ab0509 Typo in releasenotes. 2026-02-23 19:15:30 +01:00
srkkov
8f7610345d More x86 instructions (#2964)
* Added most x86 cryptographic instructions

* Fixed popcnt test

* Fixed asm_ops_x64_2.c3t test

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-23 15:27:36 +01:00
Zack Puhl
8bd963ecaf Allow Async w/ Subprocess for POSIX Systems (#2953)
* add native_fflush libc passthru

* add `pipe2` which combines `pipe` and `fcntl` - see docs

* POSIX subprocess: incorporate async reader capability and and fflush for writer

* use the real-name O_NONBLOCK since it's available

* typo

* ok... remove `pipe2` and do this with an existing method......

* Update releasenotes.md

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-22 01:59:44 +01:00
Christoffer Lerno
dc52478c09 - Warn on use of visibility modifiers on methods. #2962 2026-02-21 21:10:08 +01:00
Gantsev Denis
e1ec4b1235 add more x86 instructions 2026-02-20 21:23:35 +01:00
Lucas Alves
7c68396c0d Enable default NDK detection for Android x86_64 (#2910)
* android: detect NDK for x86_64 targets

* Treat Android as POSIX in is_posix

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-20 21:07:44 +01:00
Manu Linares
eae7d0c4a1 stdlib: std::compression::zip and std::compression::deflate (#2930)
* stdlib: implement `std::compression::zip` and `std::compression::deflate`

- C3 implementation of DEFLATE (RFC 1951) and ZIP archive handling.
- Support for reading and writing archives using STORE and DEFLATE
methods.
- Decompression supports both fixed and dynamic Huffman blocks.
- Compression using greedy LZ77 matching.
- Zero dependencies on libc.
- Stream-based entry reading and writing.
- Full unit test coverage.

NOTE: This is an initial implementation. Future improvements could be:

- Optimization of the LZ77 matching (lazy matching).
- Support for dynamic Huffman blocks in compression.
- ZIP64 support for large files/archives.
- Support for encryption and additional compression methods.

* optimizations+refactoring

deflate:
- replace linear search with hash-based match finding.
- implement support for dynamic Huffman blocks using the Package-Merge
algorithm.
- add streaming decompression.
- add buffered StreamBitReader.

zip:
- add ZIP64 support.
- add CP437 and UTF-8 filename encoding detection.
- add DOS date/time conversion and timestamp preservation.
- add ZipEntryReader for streaming entry reads.
- implement ZipArchive.extract and ZipArchive.recover helpers.

other:
- Add `set_modified_time` to std::io;
- Add benchmarks and a few more unit tests.

* zip: add archive comment support

add tests

* forgot to rename the benchmark :(

* detect utf8 names on weird zips

fix method not passed to open_writer

* another edge case where directory doesn't end with /

* testing utilities

- detect encrypted zip
- `ZipArchive.open_writer` default to DEFLATE

* fix zip64 creation, add tests

* fix ZIP header endianness for big-endian compatibility

Update ZipLFH, ZipCDH, ZipEOCD, Zip64EOCD, and Zip64Locator structs to
use little-endian bitstruct types from std::core::bitorder

* fix ZipEntryReader position tracking and seek logic ZIP_METHOD_STORE

added a test to track this

* add package-merge algorithm attribution

Thanks @konimarti

* standalone deflate_benchmark.c3 against `miniz`

* fix integer overflows, leaks and improve safety

* a few safety for 32-bit systems and tests

* deflate compress optimization

* improve match finding, hash updates, and buffer usage

* use ulong for zip offsets

* style changes (#18)

* style changes

* update tests

* style changes in `deflate.c3`

* fix typo

* Allocator first. Some changes to deflate to use `copy_to`

* Fix missing conversion on 32 bits.

* Fix deflate stream. Formatting. Prefer switch over if-elseif

* - Stream functions now use long/ulong rather than isz/usz for seek/available.
- `instream.seek` is replaced by `set_cursor` and `cursor`.
- `instream.available`, `cursor` etc are long/ulong rather than isz/usz to be correct on 32-bit.

* Update to constdef

* Fix test

---------

Co-authored-by: Book-reader <thevoid@outlook.co.nz>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-20 20:41:34 +01:00
mmoustafa8108
5055e86518 test: added comprehensive tests for in-string & complex nested comments (#2956)
* test: added comprehensive tests for in-string & complex nested comments

* Properly handle error

---------

Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
2026-02-20 13:24:08 +01:00
Manu Linares
4b13ad692a fixes for RISC-V ABI Implementation #1567 (#2937)
* fixes for RISC-V ABI Implementation #1567

Fixed RISC-V floating-point ABI by correcting the target triple to
`riscv64-unknown-linux-gnu`, adding the `target-abi` module flag, and
ensuring ABI-required CPU features are enabled.

I tested this with:
```bash
build/c3c compile-only --target linux-riscv64 rv_hello.c3
readelf -h obj/linux-riscv64/rv_hello.o | grep Flags
# Output: Flags: 0x5, RVC, double-float ABI
```

```bash
# and qemu because I don't have a riscv machine :/
qemu-riscv64-static -L /usr/riscv64-linux-gnu ./rv_hello
```

---

@lerno I purposedly left these two failing tests to clearly see the
difference.

`test/test_suite/abi/riscv64-lp64-lp64f-abi-1.c3t`
`test/test_suite/abi/riscv64-lp64-abi.c3t`

* improve Linux cross-compilation, specifically for RISC-V

- Implement automatic sysroot and CRT object discovery for RISC-V.
- Fix dynamic linker paths and emulation flags for LLD.
- Link against libgcc to resolve required arithmetic symbols.

* Update tests.

* fix linker CRT detection by centralizing host arch check

use target_host_arch() in linker.c
move target_host_arch() from hostinfo.c to target.c

* missing debug info

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-20 01:53:29 +01:00
Christoffer Lerno
7c81bb35ca Refactored contracts (#2960)
* - Properly support `@deprecated` as contract.
- Support deprecating enum values.
- Refactor contracts

* Always copy enum constants.

* Bugfix.
2026-02-20 01:51:28 +01:00
Christoffer Lerno
5a82f672b5 Update to constdef 2026-02-20 01:13:20 +01:00
Christoffer Lerno
0387d7666d - Trying to slice an indexable type leads to misleading error message #2958 2026-02-20 00:13:45 +01:00
Christoffer Lerno
392c78860d Add bitorder functions store_le, load_le, store_be, store_le. 2026-02-19 23:59:55 +01:00
Christoffer Lerno
585c66100d - Member access on a struct returned by the assignment expression, cause crash #2947 2026-02-19 20:43:45 +01:00
konimarti
8bb974829d math: implement discrete and continuous distributions (#2955)
* math: implement discrete and continuous distributions

Implement a comprehensive set of continuous and discrete probability
distributions with support for PDF, CDF, inverse CDF, random sampling,
mean, and variance calculations.

The following distributions are implemented:
* Normal
* Uniform
* Exponential
* Chi-Squared
* F-Distribution
* Student t
* Binomial
* Poisson

* update releasenotes.md

* Formatting

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-19 20:09:11 +01:00
Christoffer Lerno
6b3139940c Formatting to be consistent. Make unrolling use macro. 2026-02-19 18:22:21 +01:00
Damien Wilson
9efb9b90d1 Minor attribute combination checking (#2918)
* Minor attribute combination checking

* Added tests to the test suite.
2026-02-19 17:58:39 +01:00
soerlemans
152558f5bc Optimized adler32 hashing algorithm. (#2948)
* Optimized adler32 implementations.

 - Adapted adler32 implementation from Crypto++ public domain library.
 - Added unit tests for adler32 hashing algorithm.

* tabified adler32 implementation to match stdlib.

* Formatting to be consistent. Make unrolling use macro.

---------

Co-authored-by: soerlemans <sebasoerlemans+git@gmail.com>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-19 17:51:33 +01:00
Manuel Barrio Linares
7ae4c5a1ab some refactoring of io formatter internals
extracted logic for hex-buffers (%h), string-padding (%s), hex-floats
(%a), and collection printing into helper functions.
2026-02-19 16:39:27 +01:00
Fernando López Guevara
9fbf6bc213 enforce buff channel size > 0 (#2952)
* fix(stdlib): enforce BufferedChannel size > 0 and add regression test

* chore(repo): ignore cmake/test artifacts

* Formatting

---------

Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
2026-02-18 14:34:41 +01:00
Christoffer Lerno
143fa70f87 Remove init for Blake3 2026-02-17 17:43:54 +01:00
Christoffer Lerno
3ac701be0e Improve error message when providing alias with a typeid expression where a type was expected. #2944 2026-02-17 13:02:34 +01:00
Manu Linares
8cb23cff29 split test_hash_vector_macro into per-type functions (#2940)
* split `test_hash_vector_macro` into per-type functions

it went from a single 18367 instructions function, to eleven separate
functions with (1000 to 1600) instructions each
thats 15649 instructions total, 2718 less instructions overall.

```
$ build/c3c compile-test --suppress-run -O1 --print-large-functions -D
SLOW_TESTS test/unit/ 2>&1 | sort -n | grep "test_hash_vector"
    1041 instructions found in
std::core::builtins:test_hash_vector_int128
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1041 instructions found in
std::core::builtins:test_hash_vector_uint128
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1305 instructions found in std::core::builtins:test_hash_vector_long
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1305 instructions found in
std::core::builtins:test_hash_vector_ulong
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1453 instructions found in std::core::builtins:test_hash_vector_int
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1453 instructions found in std::core::builtins:test_hash_vector_uint
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1565 instructions found in
std::core::builtins:test_hash_vector_short
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1565 instructions found in
std::core::builtins:test_hash_vector_ushort
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1629 instructions found in std::core::builtins:test_hash_vector_char
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1629 instructions found in
std::core::builtins:test_hash_vector_ichar
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1663 instructions found in std::core::builtins:test_hash_vector_bool
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
```

* made the `test_hash_vector_internal` generic
2026-02-17 02:30:30 +01:00
Manuel Barrio Linares
ec6ba8e7ca refactor md5.body function to reduce instruction count
- replaced manual unrolling with loop structures and constant arrays
- instruction count reduced from 12445 to 4016
- maybe about 1 to 2% performance loss on some benchs but take this
number with a grain of salt.
2026-02-16 14:12:54 +01:00
Manuel Barrio Linares
df030ac51c optimize blake3 using a runtime for loop
instruction count went from 60k to 9k
no difference in speed for -O2 or higher
2026-02-16 02:31:47 +01:00
Manu Linares
4b03a84b00 optimize test_ct_intlog2 test and whirlpool hash (#2938)
* optimize `test_ct_intlog2` while still covering all 128 bit positions

* refactor whirlpool to reduce code bloat

replaced the fully unrolled round loop with a runtime loop, reducing
instruction count by 80k in `process_block` and yielding aprox 30%
performance boost due to improved cache locality.

* use compile-time arrays for `test_ct_intlog2`
2026-02-16 01:54:46 +01:00
Christoffer Lerno
5d0c41da6b Refactor dylib name. 2026-02-16 01:21:05 +01:00
Christoffer Lerno
990e9685d2 - Adding the incorrect sized vector to a pointer vector would cause a crash. 2026-02-16 00:39:14 +01:00
Christoffer Lerno
a7309b217e Add --print-large-functions for checking which functions likely dominate the compile time. 2026-02-16 00:13:19 +01:00
Christoffer Lerno
d7cf8fa9ab Reduce macro inlining size. Remove non-const atomic ordering. 2026-02-15 00:43:29 +01:00
Christoffer Lerno
a78a169a17 Slim down the string_to_int tests by 800 MB. 2026-02-15 00:05:35 +01:00
Christoffer Lerno
d80a8629a6 - On assert known false, the message was not show for no-args.
- Fixup in socket_private.c3
2026-02-14 23:49:27 +01:00
Christoffer Lerno
14779bd467 Set CP_UTF8 to underlying number. 2026-02-14 01:49:03 +01:00
Christoffer Lerno
eb80776988 More fixes for typedef @constinit change 2026-02-14 01:37:53 +01:00
Samuel
fac9054f1b Fix array initializer analysis (#2925)
* Fix array initializer analysis: improved semantic checking for arrays with inferred or fixed length,
* Update phrasing

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-13 23:38:07 +01:00
Zack Puhl
768d24d580 [stdlib] Add array even, odd, and unlace macros 2026-02-13 23:19:31 +01:00
Christoffer Lerno
e299a4b630 - Change typedef and const enums to not convert from literals by default. (#2934)
- Add `@constinit` to allow old typedef behaviour.
2026-02-13 20:39:47 +01:00
Christoffer Lerno
bbf89815d6 - Individual warning settings added. 2026-02-13 18:25:19 +01:00
Christoffer Lerno
cc6d552e3d - --safe=no disabled compile-time errors on compile-time known runtime @require checks #2936 2026-02-13 16:34:45 +01:00
Christoffer Lerno
27ceded331 Add a few const char* 2026-02-12 16:51:34 +01:00
Christoffer Lerno
3f279b2f1c Refactoring: make CT_IDENT and unresolved expression. 2026-02-12 16:38:18 +01:00
Christoffer Lerno
acc4a900f5 - New const enum declaration syntax.
- New enum associated value syntax.
2026-02-12 14:43:56 +01:00
Fernando López Guevara
f079fa82b2 fix(std-io): make uint128 decimal formatting safe (#2924)
* fix(std-io): make uint128 decimal formatting safe and add all-base
numeric coverage
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-11 23:50:16 +01:00
Manuel Barrio Linares
7665720264 add cache for Embedded/QEMU Tests
I forgot to add this one
2026-02-11 23:48:18 +01:00
Christoffer Lerno
83705ab340 Fix chacha20. 2026-02-11 23:44:26 +01:00
Christoffer Lerno
adea3dd83f Fix chacha20 2026-02-11 23:39:47 +01:00
Christoffer Lerno
9b52be9ba6 - unsigned % signed and unsigned / signed is no longer allowed without explicit casts, except for const denominators. #2928 2026-02-11 23:18:08 +01:00
Christoffer Lerno
202349d88f - Improved underlining errors/warnings when unicode is used. #2887 2026-02-11 16:49:22 +01:00
Manuel Barrio Linares
18ab18958b CI: add retries and package caching across all platforms
- Add retry loops to all package manager commands (apt, apk, pacman,
pkg).
- Implement daily package caching to minimize external server hits.
- Enable Docker layer caching in CI using buildx and GHA backend.
  - Sync CI Docker build steps with the build-with-docker.sh logic.
  - We don't, or more accurately, can't actually use
`build-with-docker.sh` anymore in CI. Maybe we should move it to
scripts/tools/ to keep the root directory clean.
- add ramdisk for BSD, shave 30/60 seconds.
- change BDSs to cross-platform-actions/action for a cleaner ci and skip
SLOW_TESTS for BSDs: this shaves a few seconds
- set global CACHE_INVALIDATION_SEED
2026-02-11 10:01:14 +01:00
Christoffer Lerno
2237d3b836 Missing check 2026-02-11 02:55:07 +01:00
Christoffer Lerno
e2f17a770b Using [] or .foo on $$ functions would not raise error but instead crash #2919. 2026-02-11 02:53:17 +01:00
konimarti
40e6a2c4a3 codepage: add single-byte code page support (#2891)
* codepage: add single-byte code page support

Add std::encoding::codepage with a shared engine for converting between
single-byte code pages and UTF-8 using table-driven mappings.

Introduce generated tables and wrappers for several code pages[1] each
exposing encode/decode helpers built on a common CodePageTable
structure.

The mapping data is generated by cpgen[2] from the Unicode Consortium’s
published code page mapping files and follows the Unicode standard’s
interpretation of control characters (abstract characters) rather than
historical VGA glyph shapes.

[1] Code page overview/groups:

    DOS/OEM code pages (legacy PC):
    cp437 cp737 cp775 cp850 cp852 cp855 cp857 cp860 cp861 cp862 cp863
    cp864 cp865 cp866 cp869 cp874

    Windows code pages (ANSI/Windows):
    cp1250 cp1251 cp1252 cp1253 cp1254 cp1255 cp1256 cp1257 cp1258

    ISO/IEC 8859 series (Latin/Regional):
    iso_8859_1 iso_8859_2 iso_8859_3 iso_8859_4 iso_8859_5 iso_8859_6
    iso_8859_7 iso_8859_8 iso_8859_9 iso_8859_10 iso_8859_11 iso_8859_13
    iso_8859_14 iso_8859_15 iso_8859_16

[2] github.com/konimarti/cpgen

Signed-off-by: Koni Marti <koni.marti@gmail.com>

* codepage: change encoding format, streamline api

* Use enum to collect the data.

---------

Signed-off-by: Koni Marti <koni.marti@gmail.com>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-11 01:10:12 +01:00
g.denis
a80e40a798 fix fedora dyn lib (#2917)
* also look into /usr/lib64
2026-02-10 23:12:04 +01:00
Manuel Barrio Linares
de8a733c77 Implement lazy-loaded libcurl
- Uses `dlopen` to load libcurl at runtime for better portability.
- Replaces the FETCH_AVAILABLE macro with download_available() check.
- Provides descriptive error messages when libcurl is missing.
2026-02-10 13:05:43 +01:00
Christoffer Lerno
c5d7a03859 Remove unnecessary free. 2026-02-10 11:46:26 +01:00
Christoffer Lerno
0b82537700 Fix 2026-02-10 11:27:55 +01:00
Christoffer Lerno
7b8299c8dd - Reallocating overaligned memory with the LibcAllocator was unsafe. 2026-02-10 11:23:51 +01:00
Christoffer Lerno
dcbd0f8f2d Fix dwarf definitions 2026-02-10 10:23:53 +01:00
Boris Barbulovski
402c00e230 Add Android Aarch64 and x64 support status 2026-02-09 10:26:40 +01:00
Christoffer Lerno
0542c05b88 Update tests. 2026-02-09 10:23:03 +01:00
Christoffer Lerno
67e224c62f Fix windows main 2026-02-09 10:15:49 +01:00
Christoffer Lerno
565b08846f Remove use of tempnam. 2026-02-09 00:04:05 +01:00
Christoffer Lerno
80ad0e02ad Fix tests and error in returning error on function name. 2026-02-08 22:54:24 +01:00
Lucas Alves
b8ee5a4150 fix: add non-API Android NDK lib search path (#2884)
* fix: add sysroot/usr/lib/<triple> to Android linker search paths

* Fix library search order: move libc++_shared logic after API libc paths

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-08 15:02:37 +01:00
Christoffer Lerno
445dd155b7 Another #2814 2026-02-08 15:01:27 +01:00
Rauny
abb3efca00 fix: prevent Homebrew dylib contamination in macOS builds (#2908)
When C3_FETCH_LLVM=ON, the fetched LLVM artifact is self-contained.
Two issues caused the macOS release binary to embed hardcoded
/opt/homebrew paths, making it fail on systems without Homebrew:

1. /opt/homebrew/lib was unconditionally appended to LLVM_LIBRARY_DIRS,
   causing find_library to discover Homebrew's LLD dylibs (whose
   LC_ID_DYLIB points to /opt/homebrew/opt/lld/lib/...). Guard this
   behind NOT C3_FETCH_LLVM so it only applies when using a
   system-installed LLVM.

2. In the static linking block (C3_LINK_DYNAMIC=OFF), find_library NAMES
   listed .dylib before .a, so shared libraries were preferred even when
   static linking was intended. Reorder to prefer .a, keeping .dylib as
   a fallback for systems that only ship shared libraries.

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-08 13:57:07 +01:00
Christoffer Lerno
de0b317461 Fix default main template. 2026-02-08 13:38:43 +01:00
Christoffer Lerno
21758476d4 - For c3c init with library templates, provide example exported functions. #2898 2026-02-07 21:31:12 +01:00
Christoffer Lerno
b69f78be30 Flag --cpu-flags doesn't work if the first item is an exclusion. #2905 2026-02-07 21:21:57 +01:00
Christoffer Lerno
89b9f52f1e Add libc mktemp and tempnam. Update test. 2026-02-07 20:25:28 +01:00
Christoffer Lerno
02a67254cc Fix win forwarding functions. 2026-02-07 19:57:43 +01:00
Manuel Barrio Linares
120f56ac5f disable --fetch-msvc when no curl 2026-02-07 01:22:54 +01:00
Laura Kirsch
5b93212f43 Added Xorshiro128++ random number generator. (#2846)
* Added Xorshiro128++ random number generator.

* Fixes to xorshiro implementation + tests.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-06 20:14:56 +01:00
Zack Puhl
c57415ea78 Optional byte limit for readline
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
2026-02-06 19:15:29 +01:00
Manuel Barrio Linares
36dfbdff45 implement progress bar for fetch-msvc
using deterministic milestones
2026-02-06 17:59:36 +01:00
Christoffer Lerno
d07e454dfe Fix missing ) 2026-02-06 17:56:40 +01:00
Christoffer Lerno
40aa4d4dcd Add Murmur3 hash 2026-02-06 17:53:19 +01:00
Manu Linares
0be291e0d7 compiler: added c3c fetch-msvc command (#2854)
* compiler: added `c3c fetch-msvc` command
ported `msvc_build_libraries.py` to built-in compiler 'fetch-msvc'
subcommand.
* fix alpine build
* fix windows build
* fix WinHTTP download to correctly handle full URLs by splitting hostname and path.
* fix old `cp` and escape args for `file_copy_file`
also cleanup the temp directory when we are finished
it would be better to use a cache :|
* remove msiextract dependency
* Auto-fetch `msvc_sdk` if missing
- auto trigger `msvc_sdk` download when compiling for Windows if no SDK
is detected.
- make native Windows SDK detection non-fatal to allow fallback to the
portable SDK
- add debug logging for diagnostics
* removed the LZX decompression logic
- moved `msvc_sdk` to user cache locations to avoid permission issues.
- refined terminal messaging for better accuracy and clarity.
* use %LOCALAPPDATA% on Windows
* Add support for C3_MSVC_SDK
* Update release notes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-06 14:01:49 +01:00
Manuel Barrio Linares
35d84390bd Simplify CI, do more with less.
- Rename Docker job to **"LLVM ${{ matrix.llvm_version }}
Compatibility"** for better visibility.
- Limit `build_type` to **Debug** for MSYS2, OpenBSD, NetBSD, and Nix
jobs to reduce CI time.
- Refactor **Alpine Linux** job: updated to 3.23, simplified dependency
installation, and removed redundant LLVM matrix.
- Modernize **Docker** builds:
    - Added automatic architecture detection (x86_64/ARM) for CMake
downloads.
    - Generalized LLVM repository logic to support multiple Ubuntu
codenames (Focal/Jammy/Noble).
    - Fixed git permissions issues in containerized builds using
`safe.directory`.
- Standardized Android build packaging and added conditional testing for
Debug builds.
2026-02-06 12:00:00 +01:00
Christoffer Lerno
124efb2684 Improve error message on const int* 2026-02-06 02:00:26 +01:00
Christoffer Lerno
5c158e481b Always have wmain. Cleanup synthetic main generation. Some cleanup for generics. 2026-02-06 01:28:19 +01:00
Christoffer Lerno
42b79d19c1 Update all stub methods. 2026-02-06 00:55:57 +01:00
Zack Puhl
4521a25284 [stdlib] Add PEM Encoding/Decoding Module (#2858)
* [stdlib] Add PEM Encoding/Decoding Module
* release notes
* Removed some unnecessary macro usages. Fixed memory handling with headers.
* Make end of line a parameter. Internal encode method -> function. Use more tmem. Remove t-functions.
* Update API

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
2026-02-06 00:01:07 +01:00
Foxy-Boxes
1933d47ba1 add Intel user interrupt instructions (#2767)
* add Intel user interrupt instructions

* Example code did not actually work, so was removed.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-05 20:56:35 +01:00
Book-reader
bb9e9b54cf Improve android support & add CI (#2664)
* Change context destruction order.

* enable emulated tls on termux

* Fix stdlib on android

* Add a CI workflow for android termux

* update release notes

* use the new unified CI tests on android

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-05 20:04:16 +01:00
Manu Linares
eddbfb8ba3 CMakeLists and CI: Use our custom llvm (#2893)
* c3-llvm: new CMakeLists and CI

uses custom llvm version 21.x from
"https://github.com//ManuLinares/llvm-custom-builds/"

* use c3lang/llvm-for-c3 latest release

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-05 18:55:09 +01:00
Christoffer Lerno
2c55d6e220 Further fix shadowing of generics 2026-02-05 18:43:54 +01:00
Christoffer Lerno
472c49de25 Differentiate errors on generic mismatch. 2026-02-05 16:20:17 +01:00
Christoffer Lerno
016254d38c Improve error message when using functions as values #2856 2026-02-05 16:03:56 +01:00
Christoffer Lerno
054fcd2deb Update advice when an enum is found with "= 2" and no associated values. 2026-02-05 13:57:35 +01:00
Christoffer Lerno
d71aa10f62 - Const inline enums would not always implicitly get converted to the underlying type.
- Update to dstring.append_string to take any type converting to String.
2026-02-05 13:04:57 +01:00
Christoffer Lerno
1d0aef4522 Remove unnecessary macro. 2026-02-05 02:07:42 +01:00
Christoffer Lerno
71b673d241 - Shadowing not detected for generic declarations #2876 2026-02-05 01:33:47 +01:00
Christoffer Lerno
dcf695c726 Fix list[0].i = 5 when list[0] returns a pointer. #2888 2026-02-05 00:34:17 +01:00
Christoffer Lerno
5e656603a5 - Remove dependency on temp allocator in File.open. 2026-02-04 14:27:52 +01:00
Christoffer Lerno
b4426c095b - Remove dependency on temp allocator in String.join. 2026-02-04 13:06:12 +01:00
Christoffer Lerno
5f32c97094 - Compiler crash when using arrays of vectors in lists. #2889 2026-02-04 12:40:16 +01:00
Christoffer Lerno
1c8cb7fa11 Method resolution and $define now works together well unless definitions are out of order for real. 2026-02-04 12:23:37 +01:00
Christoffer Lerno
6bc606a9b1 Removed analyse top pass. Remove sub module tracking. Fix errors where ? should be ~ 2026-02-02 12:20:34 +01:00
Christoffer Lerno
d509b4caa1 Updated info about contributing bindings. 2026-01-31 22:07:14 +01:00
Zack Puhl
12975d07ac [stdlib] Reduce inline code volume from sorting macros (#2831)
* reduce codegen in sorting macros

* remove testing file...

* Fix and some renaming, removing some sub-modules that should not be in use.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-31 20:19:57 +01:00
Laura Kirsch
3d512abaf7 Fix non-LLVM build. 2026-01-31 14:07:48 +01:00
Kiana
a9d93c93d5 Improve linker error message (#2880)
* Improve linker error message

* use file_util/file_is_dir instead

* cleanup unused function

* Some minor updates and merging latest master.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-31 04:55:47 +01:00
Manu Linares
1b7601fdbb stdlib: optimize math::next_power_of_2 to O(1) using hardware CLZ (#2839)
* stdlib: optimize math::next_power_of_2 to O(1) using hardware CLZ

- updated DString.reserve to use this

* bit smearing

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-31 00:20:58 +01:00
Smite Rust
373013046d Fix formatting of path assignment in msvc_build_libraries.py 2026-01-30 22:01:40 +01:00
Christoffer Lerno
10d369d766 Updated grammar, and 0.7.10 first commit 2026-01-30 21:59:20 +01:00
312 changed files with 19831 additions and 4486 deletions

View File

@@ -8,13 +8,13 @@ on:
workflow_dispatch:
env:
LLVM_RELEASE_VERSION_WINDOWS: 21.1.8
LLVM_RELEASE_VERSION_MAC: 21
LLVM_RELEASE_VERSION_LINUX: 21
LLVM_RELEASE_VERSION: 21.x
LLVM_RELEASE_VERSION_LINUX_MUSL: 20
LLVM_RELEASE_VERSION_OPENBSD: 20
LLVM_RELEASE_VERSION_NETBSD: 19
LLVM_DEV_VERSION: 22
# CACHE_INVALIDATION_SEED: '%Y-%m-%d' # Daily
CACHE_INVALIDATION_SEED: '%G-%V-1' # Weekly
jobs:
@@ -33,7 +33,7 @@ jobs:
- 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') }}
key: ${{ runner.os }}-llvm-${{ env.LLVM_RELEASE_VERSION }}-${{ matrix.build_type }}-${{ hashFiles('CMakeLists.txt', '.github/workflows/main.yml') }}
# set up the environment for Ninja
- uses: ilammy/msvc-dev-cmd@v1
@@ -42,7 +42,7 @@ jobs:
- name: CMake Build
run: |
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DC3_FETCH_LLVM=ON
cmake --build build --config ${{ matrix.build_type }}
# We remove the GNU link tool so C3C picks up the MSVC link.exe
@@ -79,7 +79,7 @@ jobs:
strategy:
fail-fast: false
matrix:
build_type: [Release, Debug]
build_type: [Debug]
defaults:
run:
shell: msys2 {0}
@@ -89,16 +89,19 @@ jobs:
with:
msystem: MINGW64
update: false
cache: 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 mingw-w64-x86_64-llvm mingw-w64-x86_64-llvm-libs
- name: Install LLD
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
for i in 1 2; do \
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 && break || sleep 5; \
done
- name: CMake Build
run: |
@@ -113,7 +116,7 @@ jobs:
strategy:
fail-fast: false
matrix:
build_type: [Release, Debug]
build_type: [Debug]
defaults:
run:
shell: msys2 {0}
@@ -123,6 +126,7 @@ jobs:
with:
msystem: CLANG64
update: false
cache: true
install: git binutils mingw-w64-clang-x86_64-cmake mingw-w64-clang-x86_64-toolchain mingw-w64-clang-x86_64-python
- name: CMake Build
run: |
@@ -132,91 +136,111 @@ jobs:
run: ./scripts/tools/ci_tests.sh "./build/c3c"
build-linux:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20, 21, 22]
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
- 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: Get current date context
id: date
run: echo "date=$(date +'${{ env.CACHE_INVALIDATION_SEED }}')" >> $GITHUB_OUTPUT
- name: Cache APT archives
uses: actions/cache@v5
with:
path: apt-cache
key: apt-cache-${{ runner.os }}-${{ steps.date.outputs.date }}
- run: |
mkdir -p apt-cache/partial
for i in 1 2 3; do sudo apt-get update && break || sleep 2; done
for i in 1 2 3; do sudo apt-get install -y -o dir::cache::archives="$(pwd)/apt-cache" --fix-missing zlib1g zlib1g-dev python3 ninja-build curl libcurl4-openssl-dev opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf && break || sleep 2; done
sudo chown -R $USER:$USER apt-cache
- name: CMake Build
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_LLVM_VERSION=$C3_LLVM_VER
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DC3_FETCH_LLVM=ON
cmake --build build
- name: Run Unified Tests
run: ./scripts/tools/ci_tests.sh "./build/c3c"
- name: Embedded/QEMU Tests
run: |
sudo apt-get install -y opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf
# Added `opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf` to the apt-get install command above
cd resources/examples/embedded/riscv-qemu
make C3C_PATH=../../../../build/ run
- name: Bundle & Upload (Linux)
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
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
with:
name: c3-linux-${{matrix.build_type}}
path: c3-linux-${{matrix.build_type}}.tar.gz
build-linux-alpine:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
container:
image: alpine:3.22
image: alpine:3.23
strategy:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [18, 19, 20]
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: Get current date context
id: date
run: echo "date=$(date +'${{ env.CACHE_INVALIDATION_SEED }}')" >> $GITHUB_OUTPUT
#https://wiki.alpinelinux.org/wiki/Local_APK_cache
- name: Prepare real apk cache directory + symlink
run: |
mkdir -p /var/cache/apk
ln -sf /var/cache/apk /etc/apk/cache
- name: Cache APK downloaded packages (daily)
uses: actions/cache@v5
with:
path: /var/cache/apk
key: apk-pkgs-${{ runner.os }}-alpine-3.23-${{ steps.date.outputs.date }}
restore-keys: |
apk-pkgs-${{ runner.os }}-alpine-3.23-
- name: Install dependencies
run: |
V=${{env.LLVM_RELEASE_VERSION_LINUX_MUSL}}
for i in 1 2 3; do apk update && break || sleep 2; done
for i in 1 2 3; do \
apk add \
build-base bash git cmake ninja python3 linux-headers \
tar zstd \
zlib-dev curl-dev libffi-dev \
llvm${V}-dev \
llvm${V}-static \
llvm${V}-gtest \
clang${V}-dev \
clang${V}-static \
lld${V}-dev \
lld${V}-libs && break || sleep 2; \
done
- name: CMake Build
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
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DC3_FETCH_LLVM=OFF \
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LINK_DYNAMIC=ON
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"
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
with:
name: c3-linux-musl-${{matrix.build_type}}
path: c3-linux-musl-${{matrix.build_type}}.tar.gz
name: c3-linux-musl-${{ matrix.build_type }}
path: c3-linux-musl-${{ matrix.build_type }}.tar.gz
build-mac:
runs-on: macos-latest
@@ -224,58 +248,23 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20, 21]
steps:
- uses: actions/checkout@v6
- 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
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 -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DC3_FETCH_LLVM=ON
cmake --build build
- name: Run Unified Tests
run: ./scripts/tools/ci_tests.sh "./build/c3c"
- name: Build Lib (Mac)
run: |
cd resources/testproject
../../build/c3c build hello_world_lib -vv --trust=full
- 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
with:
name: c3-macos-${{matrix.build_type}}
path: c3-macos-${{matrix.build_type}}.zip
@@ -285,36 +274,54 @@ jobs:
strategy:
fail-fast: false
matrix:
build_type: [Release, Debug]
build_type: [Debug]
version: ['7.8']
steps:
- uses: actions/checkout@v6
- name: Build, Test and Package in OpenBSD
uses: vmactions/openbsd-vm@v1
uses: cross-platform-actions/action@master
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
operating_system: openbsd
version: ${{ matrix.version }}
cpu_count: 4
memory: 6G
run: |
export MALLOC_OPTIONS=j # Disable junk filling (it's faster)
cd "$GITHUB_WORKSPACE"
# Install dependencies
for i in 1 2 3; do
sudo pkg_add ninja cmake llvm%${{ env.LLVM_RELEASE_VERSION_OPENBSD }} bash && break || sleep 5
done
export MALLOC_OPTIONS=j
# Setup RAM Disk
sudo mkdir -p /mnt/ram
sudo mount_mfs -s $((2 * 1024 * 1024 * 2)) swap /mnt/ram
sudo chown -R $(id -u):$(id -g) /mnt/ram
export TMPDIR=/mnt/ram
# Copy source to RAM disk and switch to it
cp -rp . /mnt/ram/
cd /mnt/ram
# Build
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{matrix.build_type}}
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
ulimit -d $(ulimit -Hd) || true
./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"
# Move results back to workspace for GitHub rsync/upload
mkdir -p "$GITHUB_WORKSPACE"/build
cp build/c3c "$GITHUB_WORKSPACE"/build/
cp *.tar.gz "$GITHUB_WORKSPACE"/
- uses: actions/upload-artifact@v6
with:
name: c3-openbsd-${{matrix.build_type}}
@@ -325,34 +332,49 @@ jobs:
strategy:
fail-fast: false
matrix:
build_type: [Release, Debug]
build_type: [Debug]
version: ['10.1']
steps:
- uses: actions/checkout@v6
- name: Build, Test and Package in NetBSD
uses: vmactions/netbsd-vm@v1
uses: cross-platform-actions/action@master
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 }}'
operating_system: netbsd
version: ${{ matrix.version }}
cpu_count: 4
memory: 6G
run: |
# Install dependencies
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/"
# Ensure pkgin is available and updated
sudo /usr/sbin/pkg_add -U pkgin || true
for i in 1 2 3; do
sudo pkgin -y update && \
sudo 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 }}' && break || sleep 5
done
export CC=clang
export CXX=clang++
cd "$GITHUB_WORKSPACE"
# Setup RAM Disk
sudo mkdir -p /mnt/ram
sudo mount_tmpfs -s 2G tmpfs /mnt/ram
sudo chown -R $(id -u):$(id -g) /mnt/ram
export TMPDIR=/mnt/ram
# Copy source to RAM disk and switch to it
cp -rp . /mnt/ram/
cd /mnt/ram
# Build
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{matrix.build_type}}
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
# Run Unified Tests
@@ -363,29 +385,72 @@ jobs:
chmod +x scripts/tools/package_build.sh
./scripts/tools/package_build.sh "./build/c3c" "c3-netbsd-${{matrix.build_type}}" "tar"
# Move results back to workspace for GitHub rsync/upload
mkdir -p "$GITHUB_WORKSPACE"/build
cp build/c3c "$GITHUB_WORKSPACE"/build/
cp *.tar.gz "$GITHUB_WORKSPACE"/
- 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
name: LLVM ${{ matrix.llvm_version }} Compatibility
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ubuntu_version: [20.04, 22.04]
build_type: [Release, Debug]
llvm_version: [17, 18, 19]
llvm_version: [17, 18, 19, 20]
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v3
- name: Build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and cache Docker image
uses: docker/build-push-action@v6
with:
context: ./docker
build-args: |
LLVM_VERSION=${{ matrix.llvm_version }}
UBUNTU_VERSION=22.04
CMAKE_VERSION=3.20.0
tags: c3c-builder:latest
outputs: type=docker,dest=/tmp/c3c-builder.tar
cache-from: type=gha,scope=build-with-docker-${{ matrix.llvm_version }}
cache-to: type=gha,mode=max,scope=build-with-docker-${{ matrix.llvm_version }}
env:
DOCKER_BUILD_SUMMARY: false
- name: Load Docker image
run: docker load -i /tmp/c3c-builder.tar
- name: Build C3 in Docker
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
rm -rf build bin
mkdir -p build bin
chmod -R 777 build bin
docker run --rm \
-v "$(pwd):/home/c3c/source" \
-w /home/c3c/source \
c3c-builder:latest \
bash -c "git config --global --add safe.directory /home/c3c/source && \
cmake -S . -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=clang-${{ matrix.llvm_version }} \
-DCMAKE_CXX_COMPILER=clang++-${{ matrix.llvm_version }} \
-DCMAKE_LINKER=lld-${{ 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=auto && \
cmake --build build && \
cp -r build/c3c build/lib bin"
- name: Run Unified Tests
run: |
chmod +x ./scripts/tools/ci_tests.sh
docker run --rm \
-u 0 \
-v "$(pwd):/home/c3c/source" \
@@ -398,12 +463,12 @@ jobs:
strategy:
fail-fast: false
matrix:
build_type: [ Release, Debug ]
build_type: [ Debug ]
nixpkgs: [ Lock, Latest ]
steps:
- uses: actions/checkout@v6
- uses: cachix/install-nix-action@v31
with:
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: Update flake
if: matrix.nixpkgs == 'Latest'
@@ -414,9 +479,90 @@ jobs:
if [[ ${{ matrix.build_type }} = "Debug" ]]; then CHECK_NAME=".#c3c-debug-checks"; fi
nix build -L "$CHECK_NAME"
build-android:
strategy:
fail-fast: false
matrix:
build_type: [ Release, Debug ]
architecture: [ aarch64, x86_64 ]
include:
- architecture: aarch64
runner: ubuntu-24.04-arm
- architecture: x86_64
runner: ubuntu-24.04
runs-on: ${{matrix.runner}}
container:
image: termux/termux-docker:${{matrix.architecture}}
volumes:
- /tmp/node20:/__e/node20
- /tmp/node24:/__e/node24
env:
TERMUX_MAIN_PACKAGE_FORMAT: debian
ANDROID_ROOT: /system
ANDROID_DATA: /data
PREFIX: /data/data/com.termux/files/usr
HOME: /data/data/com.termux/files/home
PATH: /data/data/com.termux/files/usr/bin
TMPDIR: /data/data/com.termux/files/usr/tmp
LANG: en_US.UTF-8
TZ: UTC
steps:
- name: prepare termux
run: |
ln -sf ${PREFIX}/etc/termux/mirrors/default ${PREFIX}/etc/termux/chosen_mirrors
for i in 1 2; do /entrypoint.sh pkg update && break || sleep 5; done
/entrypoint.sh bash -c "yes | pkg upgrade -y"
chmod -R o+x ${PREFIX}/bin
for i in 1 2; do /entrypoint.sh pkg install -y nodejs-lts tar git && break || sleep 5; done
# Symlink Termux node to where GHA expects it
mkdir -p /__e/node20/bin /__e/node24/bin || true
ln -sf ${PREFIX}/bin/node /__e/node20/bin/node || true
ln -sf ${PREFIX}/bin/node /__e/node24/bin/node || true
- name: Get current date context
id: date
run: echo "date=$(date +'${{ env.CACHE_INVALIDATION_SEED }}')" >> $GITHUB_OUTPUT
- name: Cache Termux packages
uses: actions/cache@v5
with:
path: /data/data/com.termux/cache/apt/archives/
key: termux-cache-${{ matrix.architecture }}-${{ steps.date.outputs.date }}
- uses: actions/checkout@v6
- name: fix permissions after checkout
run: chown -R 1000:1000 .
- name: setup
run: |
for i in 1 2; do \
/entrypoint.sh pkg install -y llvm binutils libgc build-essential cmake git libedit zlib clang make mlir llvm-tools libpolly python libllvm-static && break || sleep 10; \
done
- name: build
run: |
cmake -Bbuild -DCMAKE_BUILD_TYPE=${{matrix.build_type}}
cmake --build build
- name: Run Unified Tests
if: matrix.build_type == 'Debug'
run: ./scripts/tools/ci_tests.sh "./build/c3c" "android"
- name: bundle_output
run: |
chmod +x ./scripts/tools/package_build.sh
./scripts/tools/package_build.sh "./build/c3c" "c3-android-${{matrix.architecture}}-${{matrix.build_type}}" "tar"
- name: upload artifacts
uses: actions/upload-artifact@v4
with:
name: c3-android-${{matrix.architecture}}-${{matrix.build_type}}
path: c3-android-${{matrix.architecture}}-${{matrix.build_type}}.tar.gz
release:
runs-on: ubuntu-22.04
needs: [build-msvc, build-linux, build-mac, build-linux-alpine, build-openbsd, build-netbsd]
needs: [build-msvc, build-linux, build-mac, build-linux-alpine, build-openbsd, build-netbsd, build-android]
if: github.ref == 'refs/heads/master'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -429,20 +575,24 @@ jobs:
# --- 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
mv c3-android-aarch64-Release/c3-android-aarch64-Release.tar.gz c3-android-aarch64.tar.gz
mv c3-android-x86_64-Release/c3-android-x86_64-Release.tar.gz c3-android-x86_64.tar.gz
# --- 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
mv c3-android-aarch64-Debug/c3-android-aarch64-Debug.tar.gz c3-android-aarch64-debug.tar.gz
mv c3-android-x86_64-Debug/c3-android-x86_64-Debug.tar.gz c3-android-x86_64-debug.tar.gz
- 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
@@ -459,6 +609,8 @@ jobs:
c3-linux-musl.tar.gz
c3-openbsd.tar.gz
c3-netbsd.tar.gz
c3-android-aarch64.tar.gz
c3-android-x86_64.tar.gz
# --- Debug Artifacts ---
c3-windows-debug.zip
c3-macos-debug.zip
@@ -466,3 +618,5 @@ jobs:
c3-linux-musl-debug.tar.gz
c3-openbsd-debug.tar.gz
c3-netbsd-debug.tar.gz
c3-android-aarch64-debug.tar.gz
c3-android-x86_64-debug.tar.gz

7
.gitignore vendored
View File

@@ -73,6 +73,9 @@ out/
/cmake-build-debug/
/cmake-build-release/
CMakeFiles/cmake.check_cache
CMakeCache.txt
# etags(Emacs), ctags, gtags
TAGS
GPATH
@@ -94,8 +97,8 @@ result
# tests
/test/tmp/*
/test/testrun
/test/test_suite_runner
testrun
test_suite_runner
# patches, originals and rejects
*.patch

View File

@@ -97,12 +97,16 @@ 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(C3_FETCH_LLVM OFF CACHE BOOL "Automatically download LLVM artifacts")
set(C3_LLVM_TAG "llvm_21.x" CACHE STRING "Tag/Branch to download LLVM from")
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")
set(C3_OPTIONS
C3_LINK_DYNAMIC
C3_WITH_LLVM
C3_FETCH_LLVM
C3_LLVM_TAG
C3_LLVM_VERSION
C3_USE_MIMALLOC
C3_MIMALLOC_TAG
@@ -125,9 +129,6 @@ if(C3_USE_MIMALLOC)
)
FetchContent_MakeAvailable(mimalloc)
endif()
if (NOT WIN32)
find_package(CURL)
endif()
find_package(Git QUIET)
if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
@@ -155,55 +156,83 @@ if(C3_ENABLE_CLANGD_LSP)
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})
if(C3_FETCH_LLVM)
# 1. Determine local platform ID
if (WIN32)
set(C3_LLVM_PLATFORM "windows-amd64")
elseif (APPLE)
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64")
set(C3_LLVM_PLATFORM "darwin-aarch64")
else()
set(C3_LLVM_PLATFORM "darwin-amd64")
endif()
else() # Linux
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "aarch64")
set(C3_LLVM_PLATFORM "linux-aarch64")
elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "riscv64")
set(C3_LLVM_PLATFORM "linux-riscv64")
else()
set(C3_LLVM_PLATFORM "linux-amd64")
endif()
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})
find_package(LLVM REQUIRED CONFIG)
find_package(LLD REQUIRED CONFIG)
# 2. Determine if we want Debug or Release
set(C3_LLVM_SUFFIX "")
set(C3_LLVM_TYPE "Release")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(C3_LLVM_SUFFIX "-dbg")
set(C3_LLVM_TYPE "Debug")
endif()
# 3. Construct URL
set(C3_LLVM_ARTIFACT_NAME "llvm-${C3_LLVM_PLATFORM}${C3_LLVM_SUFFIX}")
#set(C3_LLVM_URL "https://github.com/c3lang/c3-llvm/releases/download/${C3_LLVM_TAG}/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
#set(C3_LLVM_URL "https://github.com//ManuLinares/llvm-custom-builds/releases/download/${C3_LLVM_TAG}/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
# We could also just set "latest" here to always fetch the latest release
set(C3_LLVM_URL "https://github.com/c3lang/llvm-for-c3/releases/latest/download/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
message(STATUS "Fetching ${C3_LLVM_TYPE} LLVM artifact for ${C3_LLVM_PLATFORM}...")
message(STATUS "URL: ${C3_LLVM_URL}")
FetchContent_Declare(
LLVM_Artifact
URL ${C3_LLVM_URL}
)
FetchContent_MakeAvailable(LLVM_Artifact)
# 4. Point CMake to the fetched location
set(llvm_dir ${llvm_artifact_SOURCE_DIR})
set(CMAKE_PREFIX_PATH ${llvm_dir} ${CMAKE_PREFIX_PATH})
set(LLVM_DIR "${llvm_dir}/lib/cmake/llvm")
set(LLD_DIR "${llvm_dir}/lib/cmake/lld")
# TEST: For Windows, we might need to add the bin dir to prefix path for find_package to work well
if (WIN32)
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH})
endif()
find_package(LLVM REQUIRED CONFIG NO_DEFAULT_PATH)
find_package(LLD REQUIRED CONFIG NO_DEFAULT_PATH)
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}")
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
# Legacy MSVC path if someone explicitly disabled fetching but is on MSVC
find_package(LLVM REQUIRED CONFIG)
find_package(LLD REQUIRED CONFIG)
else()
# Default system search
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)
if (NOT C3_LLVM_VERSION STREQUAL "auto")
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
else()
find_package(LLVM REQUIRED CONFIG)
endif()
endif()
endif()
if (EXISTS /opt/homebrew/lib)
if (NOT C3_FETCH_LLVM AND EXISTS /opt/homebrew/lib)
list(APPEND LLVM_LIBRARY_DIRS /opt/homebrew/lib)
endif()
@@ -279,16 +308,16 @@ if(C3_WITH_LLVM)
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)
find_library(LLD_COFF NAMES lldCOFF.a liblldCOFF.a liblldCOFF.dylib lldCOFF.lib liblldCOFF.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_COMMON NAMES lldCommon.a liblldCommon.a liblldCommon.dylib lldCommon.lib liblldCommon.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_ELF NAMES lldELF.a liblldELF.a liblldELF.dylib lldELF.lib liblldELF.dll.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)
find_library(LLD_MACHO NAMES lldMachO.a liblldMachO.a liblldMachO.dylib lldMachO.lib liblldMachO.dll.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)
find_library(LLD_MINGW NAMES lldMinGW.a liblldMinGW.a liblldMinGW.dylib lldMinGW.lib liblldMinGW.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_WASM NAMES lldWasm.a liblldWasm.a liblldWasm.dylib lldWasm.lib liblldWasm.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
else()
message(STATUS "Looking for shared lld libraries in ${LLVM_LIBRARY_DIRS}")
@@ -297,7 +326,7 @@ if(C3_WITH_LLVM)
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()
endif()
set(llvm_libs ${LLVM})
# These don't seem to be reliable on windows.
@@ -325,17 +354,36 @@ if(C3_WITH_LLVM)
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}
)
if (llvm_dir)
file(GLOB_RECURSE RT_ASAN_DYNAMIC "${llvm_dir}/*libclang_rt.asan_osx_dynamic.dylib")
file(GLOB_RECURSE RT_TSAN_DYNAMIC "${llvm_dir}/*libclang_rt.tsan_osx_dynamic.dylib")
endif()
if (NOT RT_ASAN_DYNAMIC OR NOT RT_TSAN_DYNAMIC)
# Fallback to searching in LLVM_LIBRARY_DIRS (for non-fetched LLVM)
find_file(RT_ASAN_DYNAMIC_PATH NAMES libclang_rt.asan_osx_dynamic.dylib PATHS ${LLVM_LIBRARY_DIRS} PATH_SUFFIXES "clang/${LLVM_MAJOR_VERSION}/lib/darwin" "clang/${LLVM_PACKAGE_VERSION}/lib/darwin")
find_file(RT_TSAN_DYNAMIC_PATH NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS ${LLVM_LIBRARY_DIRS} PATH_SUFFIXES "clang/${LLVM_MAJOR_VERSION}/lib/darwin" "clang/${LLVM_PACKAGE_VERSION}/lib/darwin")
if (RT_ASAN_DYNAMIC_PATH)
set(RT_ASAN_DYNAMIC ${RT_ASAN_DYNAMIC_PATH})
endif()
if (RT_TSAN_DYNAMIC_PATH)
set(RT_TSAN_DYNAMIC ${RT_TSAN_DYNAMIC_PATH})
endif()
endif()
if (RT_ASAN_DYNAMIC)
list(GET RT_ASAN_DYNAMIC 0 RT_ASAN_DYNAMIC)
endif()
if (RT_TSAN_DYNAMIC)
list(GET RT_TSAN_DYNAMIC 0 RT_TSAN_DYNAMIC)
endif()
if (RT_ASAN_DYNAMIC AND RT_TSAN_DYNAMIC)
set(sanitizer_runtime_libraries
${RT_ASAN_DYNAMIC}
${RT_TSAN_DYNAMIC}
)
endif()
endif()
message(STATUS "Linking to llvm libs ${llvm_libs}")
@@ -413,10 +461,12 @@ add_executable(c3c
src/utils/whereami.c
src/utils/cpus.c
src/utils/unzipper.c
src/utils/msi.c
src/compiler/c_codegen.c
src/compiler/decltable.c
src/compiler/methodtable.c
src/compiler/mac_support.c
src/utils/fetch_msvc.c
src/compiler/windows_support.c
src/compiler/codegen_asm.c
src/compiler/asm_target.c
@@ -474,6 +524,7 @@ if(C3_WITH_LLVM)
else()
target_sources(c3c PRIVATE src/utils/hostinfo.c)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0)
target_link_libraries(c3c m)
endif()
target_include_directories(c3c PRIVATE
@@ -523,7 +574,7 @@ else()
endif()
if(C3_WITH_LLVM)
target_link_libraries(c3c ${llvm_libs} miniz c3c_wrappers ${lld_libs})
target_link_libraries(c3c miniz c3c_wrappers)
target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/wrapper/include/")
@@ -531,11 +582,11 @@ if(C3_WITH_LLVM)
target_include_directories(c3c_wrappers PRIVATE
"${CMAKE_SOURCE_DIR}/wrapper/include/")
target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
target_link_libraries(c3c_wrappers PUBLIC ${lld_libs} ${llvm_libs})
else()
target_link_libraries(c3c ${llvm_libs} miniz ${lld_libs})
target_link_libraries(c3c miniz ${lld_libs} ${llvm_libs})
endif()
@@ -552,12 +603,11 @@ if(MINGW)
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_compile_definitions(c3c PUBLIC CURL_FOUND=1)
else()
target_compile_definitions(c3c PUBLIC CURL_FOUND=0)
if (NOT WIN32)
# For dlopen support
if (CMAKE_DL_LIBS)
target_link_libraries(c3c ${CMAKE_DL_LIBS})
endif()
endif()
@@ -652,8 +702,10 @@ if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
if (APPLE)
# Change LC_ID_DYLIB to be rpath-based instead of having an absolute path
# We set DYLD_LIBRARY_PATH so the tools (which might be dynamic) can find libLLVM.dylib in the artifact
string(REPLACE ";" ":" _dyld_path "${LLVM_LIBRARY_DIRS}")
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>
COMMAND ${CMAKE_COMMAND} -E env "DYLD_LIBRARY_PATH=${_dyld_path}:$ENV{DYLD_LIBRARY_PATH}" 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()

View File

@@ -142,7 +142,7 @@ 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.7.10**.
The upcoming 0.7.x releases will focus on expanding the standard library,
fixing bugs and improving compile time analysis.
@@ -162,6 +162,8 @@ The compiler is currently verified to compile on Linux, OpenBSD, Windows and Mac
| MacOS x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| MacOS Aarch64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| iOS Aarch64 | No | Untested | Untested | Yes | Yes | Yes* |
| Android Aarch64 | No | Untested | Untested | Untested | Untested | Yes* |
| Android x64 | No | Untested | Untested | Untested | Untested | Yes* |
| Linux x86 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux x64 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux Aarch64 | Yes | Yes | Yes | Yes | Yes | Yes* |
@@ -195,7 +197,7 @@ More platforms will be supported in the future.
- If you wish to contribute with ideas, please file issues or discuss on Discord.
- Interested in contributing to the stdlib? Please get in touch on Discord.
- Compilation instructions for other Linux and Unix variants are appreciated.
- Would you like to contribute bindings to some library? It would be nice to have support for SDL, Raylib and more.
- Would you like to contribute bindings to some library? It would be nice to have support for SDL3 and more. If you have created some bindings, please submit them to https://github.com/c3lang/vendor.
- Build something with C3 and show it off and give feedback. The language is still open for significant tweaks.
- Start work on the C -> C3 converter which takes C code and does a "best effort" to translate it to C3. The first version only needs to work on C headers.
- Do you have some specific area you have deep knowledge of and could help make C3 even better at doing? File or comment on issues.

View File

@@ -0,0 +1,51 @@
module deflate_benchmarks;
import std::compression::deflate;
const uint SMALL_ITERATIONS = 50000;
const uint LARGE_ITERATIONS = 100;
// Data to compress
const char[] SMALL_DATA = { [0..1023] = 'A' };
const char[] LARGE_DATA = { [0..1048575] = 'B' };
char[] small_compressed;
char[] large_compressed;
fn void initialize_bench() @init
{
small_compressed = deflate::compress(mem, SMALL_DATA)!!;
large_compressed = deflate::compress(mem, LARGE_DATA)!!;
set_benchmark_warmup_iterations(2);
set_benchmark_max_iterations(10);
set_benchmark_func_iterations($qnameof(deflate_compress_small), SMALL_ITERATIONS);
set_benchmark_func_iterations($qnameof(deflate_decompress_small), SMALL_ITERATIONS);
set_benchmark_func_iterations($qnameof(deflate_compress_large), LARGE_ITERATIONS);
set_benchmark_func_iterations($qnameof(deflate_decompress_large), LARGE_ITERATIONS);
}
// =======================================================================================
module deflate_benchmarks @benchmark;
import std::compression::deflate;
import std::core::mem;
fn void deflate_compress_small() => @pool()
{
char[]? compressed = deflate::compress(tmem, SMALL_DATA);
}
fn void deflate_decompress_small() => @pool()
{
char[]? decompressed = deflate::decompress(tmem, small_compressed);
}
fn void deflate_compress_large() => @pool()
{
char[]? compressed = deflate::compress(tmem, LARGE_DATA);
}
fn void deflate_decompress_large() => @pool()
{
char[]? decompressed = deflate::decompress(tmem, large_compressed);
}

View File

@@ -32,8 +32,8 @@ fn void initialize_bench() @init
$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(blake2s_256_16)[..^4],
//$qnameof(blake2b_256_16)[..^4],
$qnameof(blake3_16)[..^4],
$qnameof(ripemd_160_16)[..^4],
$qnameof(whirlpool_16)[..^4],
@@ -68,8 +68,8 @@ 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 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);
@@ -80,8 +80,8 @@ 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 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);
@@ -92,8 +92,8 @@ 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 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);
@@ -104,8 +104,8 @@ 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 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);

View File

@@ -0,0 +1,57 @@
module blake3_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(128);
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 blake3_bench @benchmark;
import std::hash;
fn void blake3_hash()
{
runtime::@start_benchmark();
char[*] myset = blake3::hash(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_sha512()
{
runtime::@start_benchmark();
char[*] myset = sha512::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[..]);
}

View File

@@ -0,0 +1,41 @@
module md5_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(128);
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 md5_bench @benchmark;
import std::hash;
fn void md5_hash()
{
runtime::@start_benchmark();
char[*] myset = md5::hash(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[..]);
}

View File

@@ -0,0 +1,57 @@
module whirlpool_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(128);
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 whirlpool_bench @benchmark;
import std::hash;
fn void whirlpool_hash()
{
runtime::@start_benchmark();
char[*] myset = whirlpool::hash(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_sha512()
{
runtime::@start_benchmark();
char[*] myset = sha512::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_streebog_512()
{
runtime::@start_benchmark();
char[*] myset = streebog::hash_512(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}

View File

@@ -30,7 +30,8 @@ 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 \
"git config --global --add safe.directory /home/c3c/source && \
cmake -S . -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
-DCMAKE_C_COMPILER=clang-$LLVM_VERSION \

View File

@@ -2,48 +2,44 @@ ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION}
ARG LLVM_VERSION=18
ENV LLVM_DEV_VERSION=20
ARG CMAKE_VERSION=3.20.0
ARG CMAKE_VERSION=3.20
# Prevent interactive prompts during apt install
ENV DEBIAN_FRONTEND=noninteractive
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 && \
RUN for i in 1 2 3; do apt-get update && break || sleep 2; done && \
apt-get install -y --fix-missing \
wget gnupg software-properties-common lsb-release \
zlib1g zlib1g-dev python3 ninja-build curl g++ libcurl4-openssl-dev git && \
CODENAME=$(lsb_release -cs) && \
ARCH=$(uname -m) && \
wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${ARCH}.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
sh cmake-${CMAKE_VERSION}-linux-${ARCH}.sh --prefix=/opt/cmake --skip-license && \
ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake && \
rm cmake-${CMAKE_VERSION}-linux-${ARCH}.sh
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; \
RUN CODENAME=$(lsb_release -cs) && \
for i in 1 2; do wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key && break || sleep 2; done | apt-key add - && \
if [ "${LLVM_VERSION}" -ge 16 ]; then \
add-apt-repository "deb http://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-${LLVM_VERSION} main"; \
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; \
add-apt-repository "deb http://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME} main"; \
fi && \
for i in 1 2 3; do apt-get update && break || sleep 2; done && \
apt-get install -y --fix-missing \
clang-${LLVM_VERSION} \
clang++-${LLVM_VERSION} \
llvm-${LLVM_VERSION} \
llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} \
liblld-${LLVM_VERSION}-dev \
libpolly-${LLVM_VERSION}-dev && \
rm -rf /var/lib/apt/lists/*
RUN groupadd -g 1337 c3c && \
useradd -m -u 1337 -g c3c c3c
# Add cmake to PATH for user c3c
USER c3c
ENV PATH="/opt/cmake/bin:${PATH}"
WORKDIR /home/c3c
WORKDIR /home/c3c

View File

@@ -1,9 +1,9 @@
// Copyright (c) 2023-2025 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;
struct Atomic
struct Atomic <Type>
{
Type data;
}
@@ -11,147 +11,87 @@ struct Atomic
<*
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"
@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)
macro Type Atomic.load(&self, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
switch(ordering)
{
case NOT_ATOMIC: return $$atomic_load(data, false, AtomicOrdering.NOT_ATOMIC.ordinal);
case UNORDERED: return $$atomic_load(data, false, AtomicOrdering.UNORDERED.ordinal);
case RELAXED: return $$atomic_load(data, false, AtomicOrdering.RELAXED.ordinal);
case ACQUIRE: return $$atomic_load(data, false, AtomicOrdering.ACQUIRE.ordinal);
case SEQ_CONSISTENT: return $$atomic_load(data, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
case ACQUIRE_RELEASE:
case RELEASE: unreachable("Invalid ordering.");
}
return $$atomic_load(&self.data, false, $ordering.ordinal);
}
<*
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"
@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)
macro void Atomic.store(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
switch(ordering)
{
case NOT_ATOMIC: $$atomic_store(data, value, false, AtomicOrdering.NOT_ATOMIC.ordinal);
case UNORDERED: $$atomic_store(data, value, false, AtomicOrdering.UNORDERED.ordinal);
case RELAXED: $$atomic_store(data, value, false, AtomicOrdering.RELAXED.ordinal);
case RELEASE: $$atomic_store(data, value, false, AtomicOrdering.RELEASE.ordinal);
case SEQ_CONSISTENT: $$atomic_store(data, value, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
case ACQUIRE_RELEASE:
case ACQUIRE: unreachable("Invalid ordering.");
}
$$atomic_store(&self.data, value, false, $ordering.ordinal);
}
macro Type Atomic.add(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.add(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_add, data, value, ordering);
return atomic::fetch_add(&self.data, value, $ordering);
}
macro Type Atomic.sub(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.sub(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_sub, data, value, ordering);
return atomic::fetch_sub(&self.data, value, $ordering);
}
macro Type Atomic.mul(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.mul(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_mul, data, value, ordering);
return atomic::fetch_mul(&self.data, value, $ordering);
}
macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.div(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_div, data, value, ordering);
return atomic::fetch_div(&self.data, value, $ordering);
}
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
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::fetch_max(&self.data, value, $ordering);
}
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.min(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_min, data, value, ordering);
return atomic::fetch_min(&self.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, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_or, data, value, ordering);
return atomic::fetch_or(&self.data, value, $ordering);
}
macro Type Atomic.xor(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
macro Type Atomic.xor(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
return atomic::fetch_xor(&self.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, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_and, data, value, ordering);
return atomic::fetch_and(&self.data, value, $ordering);
}
macro Type Atomic.shr(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
macro Type Atomic.shr(&self, Type amount, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
return atomic::fetch_shift_right(&self.data, amount, $ordering);
}
macro Type Atomic.shl(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
macro Type Atomic.shl(&self, Type amount, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
return atomic::fetch_shift_left(&self.data, amount, $ordering);
}
macro Type Atomic.set(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
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);
return atomic::flag_set(&self.data, $ordering);
}
macro Type Atomic.clear(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
macro Type Atomic.clear(&self, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
{
Type* data = &self.data;
return @atomic_exec_no_arg(atomic::flag_clear, data, ordering);
}
macro @atomic_exec(#func, data, value, ordering) @local
{
switch(ordering)
{
case RELAXED: return #func(data, value, RELAXED);
case ACQUIRE: return #func(data, value, ACQUIRE);
case RELEASE: return #func(data, value, RELEASE);
case ACQUIRE_RELEASE: return #func(data, value, ACQUIRE_RELEASE);
case SEQ_CONSISTENT: return #func(data, value, SEQ_CONSISTENT);
default: unreachable("Ordering may not be non-atomic or unordered.");
}
}
macro @atomic_exec_no_arg(#func, data, ordering) @local
{
switch(ordering)
{
case RELAXED: return #func(data, RELAXED);
case ACQUIRE: return #func(data, ACQUIRE);
case RELEASE: return #func(data, RELEASE);
case ACQUIRE_RELEASE: return #func(data, ACQUIRE_RELEASE);
case SEQ_CONSISTENT: return #func(data, SEQ_CONSISTENT);
default: unreachable("Ordering may not be non-atomic or unordered.");
}
return atomic::flag_clear(&self.data, $ordering);
}
module std::atomic;
@@ -175,8 +115,8 @@ macro bool is_native_atomic_type($Type)
$case FLOAT:
$case BOOL:
return true;
$case DISTINCT:
$case CONST_ENUM:
$case TYPEDEF:
$case CONSTDEF:
return is_native_atomic_type($Type.inner);
$default:
return false;
@@ -272,7 +212,7 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
@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 $defined(*ptr / y) : "/ must be defined between the values."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)

View File

@@ -150,7 +150,7 @@ fn bool HashMap.is_initialized(&map)
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);
hashmap_put_all_for_create(self, other_map);
return self;
}
@@ -233,7 +233,7 @@ macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
if (e.hash == hash && equals(key, e.key)) return e.value;
}
Value val = #expr;
map.add_entry(hash, key, val, index);
hashmap_add_entry(map, hash, key, val, index);
return val;
}
@@ -269,13 +269,13 @@ fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
return true;
}
}
map.add_entry(hash, key, value, index);
hashmap_add_entry(map, hash, key, value, index);
return false;
}
fn void? HashMap.remove(&map, Key key) @maydiscard
{
if (!map.remove_entry_for_key(key)) return NOT_FOUND~;
if (!hashmap_remove_entry_for_key(map, key)) return NOT_FOUND~;
}
fn void HashMap.clear(&map)
@@ -290,9 +290,9 @@ fn void HashMap.clear(&map)
{
Entry *to_delete = next;
next = next.next;
map.free_entry(to_delete);
hashmap_free_entry(map, to_delete);
}
map.free_entry(entry);
hashmap_free_entry(map, entry);
*entry_ref = null;
}
map.count = 0;
@@ -302,7 +302,7 @@ fn void HashMap.free(&map)
{
if (!map.is_initialized()) return;
map.clear();
map.free_internal(map.table.ptr);
hashmap_free_internal(map, map.table.ptr);
map.table = {};
}
@@ -402,7 +402,7 @@ fn HashMapKeyIterator HashMap.key_iter(&self)
// --- private methods
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
fn void hashmap_add_entry(HashMap* map, uint hash, Key key, Value value, uint bucket_index) @private
{
$if COPY_KEYS:
key = key.copy(map.allocator);
@@ -411,11 +411,11 @@ fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_ind
map.table[bucket_index] = entry;
if (map.count++ >= map.threshold)
{
map.resize(map.table.len * 2);
hashmap_resize(map, map.table.len * 2);
}
}
fn void HashMap.resize(&map, uint new_capacity) @private
fn void hashmap_resize(HashMap* map, uint new_capacity) @private
{
Entry*[] old_table = map.table;
uint old_capacity = old_table.len;
@@ -425,9 +425,9 @@ fn void HashMap.resize(&map, uint new_capacity) @private
return;
}
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
map.transfer(new_table);
hashmap_transfer(map, new_table);
map.table = new_table;
map.free_internal(old_table.ptr);
hashmap_free_internal(map, old_table.ptr);
map.threshold = (uint)(new_capacity * map.load_factor);
}
@@ -443,7 +443,7 @@ fn usz? HashMap.to_format(&self, Formatter* f) @dynamic
return len + f.print(" }");
}
fn void HashMap.transfer(&map, Entry*[] new_table) @private
fn void hashmap_transfer(HashMap* map, Entry*[] new_table) @private
{
Entry*[] src = map.table;
uint new_capacity = new_table.len;
@@ -462,20 +462,20 @@ fn void HashMap.transfer(&map, Entry*[] new_table) @private
}
}
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
fn void hashmap_put_all_for_create(HashMap* 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);
hashmap_put_for_create(map, e.key, e.value);
e = e.next;
}
}
}
fn void HashMap.put_for_create(&map, Key key, Value value) @private
fn void hashmap_put_for_create(HashMap* map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
@@ -487,15 +487,15 @@ fn void HashMap.put_for_create(&map, Key key, Value value) @private
return;
}
}
map.create_entry(hash, key, value, i);
hashmap_create_entry(map, hash, key, value, i);
}
fn void HashMap.free_internal(&map, void* ptr) @inline @private
fn void hashmap_free_internal(HashMap* map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
fn bool hashmap_remove_entry_for_key(HashMap* map, Key key) @private
{
if (!map.count) return false;
uint hash = rehash(key.hash());
@@ -516,7 +516,7 @@ fn bool HashMap.remove_entry_for_key(&map, Key key) @private
{
prev.next = next;
}
map.free_entry(e);
hashmap_free_entry(map, e);
return true;
}
prev = e;
@@ -525,7 +525,7 @@ fn bool HashMap.remove_entry_for_key(&map, Key key) @private
return false;
}
fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
fn void hashmap_create_entry(HashMap* map, uint hash, Key key, Value value, int bucket_index) @private
{
Entry *e = map.table[bucket_index];
$if COPY_KEYS:
@@ -536,12 +536,12 @@ fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_i
map.count++;
}
fn void HashMap.free_entry(&self, Entry *entry) @local
fn void hashmap_free_entry(HashMap* map, Entry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
allocator::free(map.allocator, entry.key);
$endif
self.free_internal(entry);
hashmap_free_internal(map, entry);
}

View File

@@ -135,7 +135,7 @@ fn bool HashSet.is_initialized(&set)
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);
hashset_put_all_for_create(self, other_set);
return self;
}
@@ -213,7 +213,7 @@ fn bool HashSet.add(&set, Value value)
{
if (e.hash == hash && equals(value, e.value)) return false;
}
set.add_entry(hash, value, index);
hashset_add_entry(set, hash, value, index);
return true;
}
@@ -258,7 +258,7 @@ fn bool HashSet.contains(&set, Value value)
*>
fn void? HashSet.remove(&set, Value value) @maydiscard
{
if (!set.remove_entry_for_value(value)) return NOT_FOUND~;
if (!hashset_remove_entry_for_value(set, value)) return NOT_FOUND~;
}
fn usz HashSet.remove_all(&set, Value[] values)
@@ -266,7 +266,7 @@ fn usz HashSet.remove_all(&set, Value[] values)
usz total;
foreach (v : values)
{
if (set.remove_entry_for_value(v)) total++;
if (hashset_remove_entry_for_value(set, v)) total++;
}
return total;
}
@@ -279,7 +279,7 @@ fn usz HashSet.remove_all_from(&set, HashSet* other)
usz total;
other.@each(;Value val)
{
if (set.remove_entry_for_value(val)) total++;
if (hashset_remove_entry_for_value(set, val)) total++;
};
return total;
}
@@ -291,7 +291,7 @@ fn void HashSet.free(&set)
{
if (!set.is_initialized()) return;
set.clear();
set.free_internal(set.table.ptr);
hashset_free_internal(set, set.table.ptr);
*set = {};
}
@@ -314,10 +314,10 @@ fn void HashSet.clear(&set)
{
Entry *to_delete = next;
next = next.next;
set.free_entry(to_delete);
hashset_free_entry(set, to_delete);
}
set.free_entry(entry);
hashset_free_entry(set, entry);
*entry_ref = null;
}
set.count = 0;
@@ -327,7 +327,7 @@ fn void HashSet.reserve(&set, usz capacity)
{
if (capacity > set.threshold)
{
set.resize(math::next_power_of_2(capacity));
hashset_resize(set, math::next_power_of_2(capacity));
}
}
@@ -464,17 +464,17 @@ fn bool HashSet.is_subset(&self, HashSet* other)
// --- private methods
fn void HashSet.add_entry(&set, uint hash, Value value, uint bucket_index) @private
fn void hashset_add_entry(HashSet* 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);
hashset_resize(set, set.table.len * 2);
}
}
fn void HashSet.resize(&self, usz new_capacity) @private
fn void hashset_resize(HashSet* self, usz new_capacity) @private
{
Entry*[] old_table = self.table;
usz old_capacity = old_table.len;
@@ -484,9 +484,9 @@ fn void HashSet.resize(&self, usz new_capacity) @private
return;
}
Entry*[] new_table = allocator::new_array(self.allocator, Entry*, new_capacity);
self.transfer(new_table);
hashset_transfer(self, new_table);
self.table = new_table;
self.free_internal(old_table.ptr);
hashset_free_internal(self, old_table.ptr);
self.threshold = (uint)(new_capacity * self.load_factor);
}
@@ -502,7 +502,7 @@ fn usz? HashSet.to_format(&self, Formatter* f) @dynamic
return len + f.print(" }");
}
fn void HashSet.transfer(&self, Entry*[] new_table) @private
fn void hashset_transfer(HashSet* self, Entry*[] new_table) @private
{
Entry*[] src = self.table;
uint new_capacity = new_table.len;
@@ -521,20 +521,20 @@ fn void HashSet.transfer(&self, Entry*[] new_table) @private
}
}
fn void HashSet.put_all_for_create(&set, HashSet* other_set) @private
fn void hashset_put_all_for_create(HashSet* set, HashSet* other_set) @private
{
if (!other_set.count) return;
foreach (Entry *e : other_set.table)
{
while (e)
{
set.put_for_create(e.value);
hashset_put_for_create(set, e.value);
e = e.next;
}
}
}
fn void HashSet.put_for_create(&set, Value value) @private
fn void hashset_put_for_create(HashSet* set, Value value) @private
{
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
@@ -546,15 +546,15 @@ fn void HashSet.put_for_create(&set, Value value) @private
return;
}
}
set.create_entry(hash, value, i);
hashset_create_entry(set, hash, value, i);
}
fn void HashSet.free_internal(&self, void* ptr) @inline @private
fn void hashset_free_internal(HashSet* self, void* ptr) @inline @private
{
allocator::free(self.allocator, ptr);
}
fn void HashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
fn void hashset_create_entry(HashSet* set, uint hash, Value value, int bucket_index) @private
{
Entry* entry = allocator::new(set.allocator, Entry, {
.hash = hash,
@@ -569,7 +569,7 @@ fn void HashSet.create_entry(&set, uint hash, Value value, int bucket_index) @pr
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
fn bool hashset_remove_entry_for_value(HashSet* set, Value value) @private
{
if (!set.count) return false;
uint hash = rehash(value.hash());
@@ -590,7 +590,7 @@ fn bool HashSet.remove_entry_for_value(&set, Value value) @private
{
prev.next = next;
}
set.free_entry(e);
hashset_free_entry(set, e);
return true;
}
prev = e;
@@ -600,7 +600,7 @@ fn bool HashSet.remove_entry_for_value(&set, Value value) @private
return false;
}
fn void HashSet.free_entry(&set, Entry *entry) @private
fn void hashset_free_entry(HashSet* set, Entry *entry) @private
{
allocator::free(set.allocator, entry);
}

View File

@@ -73,7 +73,7 @@ fn bool InterfaceList.is_initialized(&self) @inline => self.allocator != null;
macro void InterfaceList.push(&self, element)
{
if (!self.allocator) self.allocator = tmem;
self._append(allocator::clone(self.allocator, element));
interfacelist_append(self, allocator::clone(self.allocator, element));
}
<*
@@ -381,7 +381,7 @@ fn usz? InterfaceList.to_format(&self, Formatter* formatter) @dynamic
*>
fn usz InterfaceList.remove_if(&self, InterfacePredicate filter)
{
return self._remove_if(filter, false);
return interfacelist_remove_if(self, filter, false);
}
<*
@@ -392,7 +392,7 @@ fn usz InterfaceList.remove_if(&self, InterfacePredicate filter)
*>
fn usz InterfaceList.retain_if(&self, InterfacePredicate selection)
{
return self._remove_if(selection, true);
return interfacelist_remove_if(self, selection, true);
}
<*
@@ -404,7 +404,7 @@ fn usz InterfaceList.retain_if(&self, InterfacePredicate selection)
*>
fn usz InterfaceList.remove_using_test(&self, InterfaceTest filter, Type context)
{
return self._remove_using_test(filter, false, context);
return interfacelist_remove_using_test(self, filter, false, context);
}
<*
@@ -416,7 +416,7 @@ fn usz InterfaceList.remove_using_test(&self, InterfaceTest filter, Type context
*>
fn usz InterfaceList.retain_using_test(&self, InterfaceTest selection, Type context)
{
return self._remove_using_test(selection, true, context);
return interfacelist_remove_using_test(self, selection, true, context);
}
<*
@@ -455,7 +455,7 @@ macro void InterfaceList.set(&self, usz index, value)
// -- private
fn void InterfaceList.ensure_capacity(&self, usz added = 1) @inline @private
fn void interfacelist_ensure_capacity(InterfaceList* self, usz added = 1) @inline @private
{
usz new_size = self.size + added;
if (self.capacity >= new_size) return;
@@ -466,18 +466,18 @@ fn void InterfaceList.ensure_capacity(&self, usz added = 1) @inline @private
self.reserve(new_capacity);
}
fn void InterfaceList._append(&self, Type element) @local
fn void interfacelist_append(InterfaceList* self, Type element) @local
{
self.ensure_capacity();
interfacelist_ensure_capacity(self);
self.entries[self.size++] = element;
}
<*
@require index < self.size
*>
fn void InterfaceList._insert_at(&self, usz index, Type value) @local
fn void interfacelist_insert_at(InterfaceList* self, usz index, Type value) @local
{
self.ensure_capacity();
interfacelist_ensure_capacity(self);
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
@@ -486,7 +486,7 @@ fn void InterfaceList._insert_at(&self, usz index, Type value) @local
self.entries[index] = value;
}
macro usz InterfaceList._remove_using_test(&self, InterfaceTest filter, bool $invert, ctx) @local
macro usz interfacelist_remove_using_test(InterfaceList* self, InterfaceTest filter, bool $invert, ctx) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
@@ -512,7 +512,7 @@ macro usz InterfaceList._remove_using_test(&self, InterfaceTest filter, bool $in
return size - self.size;
}
macro usz InterfaceList._remove_if(&self, InterfacePredicate filter, bool $invert) @local
macro usz interfacelist_remove_if(InterfaceList* self, InterfacePredicate filter, bool $invert) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)

View File

@@ -75,7 +75,7 @@ fn void LinkedBlockingQueue.free(&self)
self.not_full.destroy();
}
fn void LinkedBlockingQueue.link_entry(&self, QueueEntry* entry) @private
fn void linkedblockingqueue_link_entry(LinkedBlockingQueue* self, QueueEntry* entry) @private
{
entry.next = null;
entry.prev = self.tail;
@@ -95,7 +95,7 @@ fn void LinkedBlockingQueue.link_entry(&self, QueueEntry* entry) @private
}
fn QueueEntry* LinkedBlockingQueue.unlink_head(&self) @private
fn QueueEntry* linkedblockingqueue_unlink_head(LinkedBlockingQueue* self) @private
{
if (self.head == null) return null;
@@ -134,7 +134,7 @@ fn void LinkedBlockingQueue.push(&self, Value value)
.next = null,
.prev = null
});
self.link_entry(entry);
linkedblockingqueue_link_entry(self, entry);
// Signal that queue is no longer empty
self.not_empty.signal();
@@ -156,7 +156,7 @@ fn Value LinkedBlockingQueue.poll(&self)
self.not_empty.wait(&self.lock);
}
QueueEntry* entry = self.unlink_head();
QueueEntry* entry = linkedblockingqueue_unlink_head(self);
Value value = entry.value;
allocator::free(self.allocator, entry);
if (self.capacity > 0)
@@ -180,7 +180,7 @@ fn Value? LinkedBlockingQueue.pop(&self)
{
if (self.count == 0) return NO_MORE_ELEMENT~;
QueueEntry* entry = self.unlink_head();
QueueEntry* entry = linkedblockingqueue_unlink_head(self);
Value value = entry.value;
allocator::free(self.allocator, entry);
@@ -217,7 +217,7 @@ fn Value? LinkedBlockingQueue.poll_timeout(&self, Duration timeout)
if (!self.count) return NO_MORE_ELEMENT~;
}
QueueEntry* entry = self.unlink_head();
QueueEntry* entry = linkedblockingqueue_unlink_head(self);
Value value = entry.value;
allocator::free(self.allocator, entry);
@@ -273,7 +273,7 @@ fn void? LinkedBlockingQueue.try_push(&self, Value value)
.next = null,
.prev = null
});
self.link_entry(entry);
linkedblockingqueue_link_entry(self, entry);
self.not_empty.signal();
};
}
@@ -307,7 +307,7 @@ fn void? LinkedBlockingQueue.push_timeout(&self, Value value, Duration timeout)
.next = null,
.prev = null
});
self.link_entry(entry);
linkedblockingqueue_link_entry(self, entry);
self.not_empty.signal();
};
}

View File

@@ -150,7 +150,7 @@ fn bool LinkedHashMap.is_initialized(&map)
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);
linkedhashmap_put_all_for_create(self, other_map);
return self;
}
@@ -242,13 +242,13 @@ fn bool LinkedHashMap.set(&map, Key key, Value value) @operator([]=)
return true;
}
}
map.add_entry(hash, key, value, index);
linkedhashmap_add_entry(map, hash, key, value, index);
return false;
}
fn void? LinkedHashMap.remove(&map, Key key) @maydiscard
{
if (!map.remove_entry_for_key(key)) return NOT_FOUND~;
if (!linkedhashmap_remove_entry_for_key(map, key)) return NOT_FOUND~;
}
fn void LinkedHashMap.clear(&map)
@@ -259,7 +259,7 @@ fn void LinkedHashMap.clear(&map)
while (entry)
{
LinkedEntry* next = entry.after;
map.free_entry(entry);
linkedhashmap_free_entry(map, entry);
entry = next;
}
@@ -277,7 +277,7 @@ fn void LinkedHashMap.free(&map)
{
if (!map.is_initialized()) return;
map.clear();
map.free_internal(map.table.ptr);
linkedhashmap_free_internal(map, map.table.ptr);
map.table = {};
}
@@ -396,7 +396,7 @@ fn bool LinkedHashMapIterator.has_next(&self)
// --- private methods
fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
fn void linkedhashmap_add_entry(LinkedHashMap* map, uint hash, Key key, Value value, uint bucket_index) @private
{
$if COPY_KEYS:
key = key.copy(map.allocator);
@@ -428,11 +428,11 @@ fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint buck
if (map.count++ >= map.threshold)
{
map.resize(map.table.len * 2);
linkedhashmap_resize(map, map.table.len * 2);
}
}
fn void LinkedHashMap.resize(&map, uint new_capacity) @private
fn void linkedhashmap_resize(LinkedHashMap* map, uint new_capacity) @private
{
LinkedEntry*[] old_table = map.table;
uint old_capacity = old_table.len;
@@ -502,7 +502,7 @@ fn void LinkedHashMap.resize(&map, uint new_capacity) @private
}
}
map.free_internal(old_table.ptr);
linkedhashmap_free_internal(map, old_table.ptr);
}
fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic
@@ -517,7 +517,7 @@ fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic
return len + f.print(" }");
}
fn void LinkedHashMap.transfer(&map, LinkedEntry*[] new_table) @private
fn void linkedhashmap_transfer(LinkedHashMap* map, LinkedEntry*[] new_table) @private
{
LinkedEntry*[] src = map.table;
uint new_capacity = new_table.len;
@@ -536,7 +536,7 @@ fn void LinkedHashMap.transfer(&map, LinkedEntry*[] new_table) @private
}
}
fn void LinkedHashMap.put_all_for_create(&map, LinkedHashMap* other_map) @private
fn void linkedhashmap_put_all_for_create(LinkedHashMap* map, LinkedHashMap* other_map) @private
{
if (!other_map.count) return;
other_map.@each(; Key key, Value value) {
@@ -544,7 +544,7 @@ fn void LinkedHashMap.put_all_for_create(&map, LinkedHashMap* other_map) @privat
};
}
fn void LinkedHashMap.put_for_create(&map, Key key, Value value) @private
fn void linkedhashmap_put_for_create(LinkedHashMap* map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
@@ -556,15 +556,15 @@ fn void LinkedHashMap.put_for_create(&map, Key key, Value value) @private
return;
}
}
map.create_entry(hash, key, value, i);
linkedhashmap_create_entry(map, hash, key, value, i);
}
fn void LinkedHashMap.free_internal(&map, void* ptr) @inline @private
fn void linkedhashmap_free_internal(LinkedHashMap* map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
fn bool linkedhashmap_remove_entry_for_key(LinkedHashMap* map, Key key) @private
{
if (!map.count) return false;
@@ -605,7 +605,7 @@ fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
}
map.count--;
map.free_entry(e);
linkedhashmap_free_entry(map, e);
return true;
}
prev = e;
@@ -614,23 +614,23 @@ fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
return false;
}
fn void LinkedHashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
fn void linkedhashmap_create_entry(LinkedHashMap* 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);
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
fn void linkedhashmap_free_entry(LinkedHashMap* self, LinkedEntry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
allocator::free(self.allocator, entry.key);
$endif
self.free_internal(entry);
linkedhashmap_free_internal(self, entry);
}

View File

@@ -143,7 +143,7 @@ fn LinkedHashSet* LinkedHashSet.init_from_set(&self, Allocator allocator, Linked
LinkedEntry* entry = other_set.head;
while (entry) // Save insertion order
{
self.put_for_create(entry.value);
linkedhashset_put_for_create(self, entry.value);
entry = entry.after;
}
return self;
@@ -223,7 +223,7 @@ fn bool LinkedHashSet.add(&set, Value value)
{
if (e.hash == hash && equals(value, e.value)) return false;
}
set.add_entry(hash, value, index);
linkedhashset_add_entry(set, hash, value, index);
return true;
}
@@ -266,7 +266,7 @@ fn bool LinkedHashSet.contains(&set, Value value)
*>
fn void? LinkedHashSet.remove(&set, Value value) @maydiscard
{
if (!set.remove_entry_for_value(value)) return NOT_FOUND~;
if (!linkedhashset_remove_entry_for_value(set, value)) return NOT_FOUND~;
}
fn usz LinkedHashSet.remove_all(&set, Value[] values)
@@ -274,7 +274,7 @@ fn usz LinkedHashSet.remove_all(&set, Value[] values)
usz total;
foreach (v : values)
{
if (set.remove_entry_for_value(v)) total++;
if (linkedhashset_remove_entry_for_value(set, v)) total++;
}
return total;
}
@@ -287,7 +287,7 @@ fn usz LinkedHashSet.remove_all_from(&set, LinkedHashSet* other)
usz total;
other.@each(;Value val)
{
if (set.remove_entry_for_value(val)) total++;
if (linkedhashset_remove_entry_for_value(set, val)) total++;
};
return total;
}
@@ -299,7 +299,7 @@ fn void LinkedHashSet.free(&set)
{
if (!set.is_initialized()) return;
set.clear();
set.free_internal(set.table.ptr);
linkedhashset_free_internal(set, set.table.ptr);
set.table = {};
}
@@ -316,7 +316,7 @@ fn void LinkedHashSet.clear(&set)
while (entry)
{
LinkedEntry* next = entry.after;
set.free_entry(entry);
linkedhashset_free_entry(set, entry);
entry = next;
}
@@ -334,7 +334,7 @@ fn void LinkedHashSet.reserve(&set, usz capacity)
{
if (capacity > set.threshold)
{
set.resize(math::next_power_of_2(capacity));
linkedhashset_resize(set, math::next_power_of_2(capacity));
}
}
@@ -451,7 +451,7 @@ fn bool LinkedHashSet.is_subset(&self, LinkedHashSet* other)
// --- private methods
fn void LinkedHashSet.add_entry(&set, uint hash, Value value, uint bucket_index) @private
fn void linkedhashset_add_entry(LinkedHashSet* set, uint hash, Value value, uint bucket_index) @private
{
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
.hash = hash,
@@ -478,11 +478,11 @@ fn void LinkedHashSet.add_entry(&set, uint hash, Value value, uint bucket_index)
if (set.count++ >= set.threshold)
{
set.resize(set.table.len * 2);
linkedhashset_resize(set, set.table.len * 2);
}
}
fn void LinkedHashSet.resize(&set, usz new_capacity) @private
fn void linkedhashset_resize(LinkedHashSet* set, usz new_capacity) @private
{
LinkedEntry*[] old_table = set.table;
usz old_capacity = old_table.len;
@@ -552,7 +552,7 @@ fn void LinkedHashSet.resize(&set, usz new_capacity) @private
}
}
set.free_internal(old_table.ptr);
linkedhashset_free_internal(set, old_table.ptr);
}
fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic
@@ -567,7 +567,7 @@ fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic
return len + f.print(" }");
}
fn void LinkedHashSet.transfer(&set, LinkedEntry*[] new_table) @private
fn void linked_hashset_transfer(LinkedHashSet* set, LinkedEntry*[] new_table) @private
{
LinkedEntry*[] src = set.table;
uint new_capacity = new_table.len;
@@ -586,7 +586,7 @@ fn void LinkedHashSet.transfer(&set, LinkedEntry*[] new_table) @private
}
}
fn void LinkedHashSet.put_for_create(&set, Value value) @private
fn void linkedhashset_put_for_create(LinkedHashSet* set, Value value) @private
{
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
@@ -598,15 +598,15 @@ fn void LinkedHashSet.put_for_create(&set, Value value) @private
return;
}
}
set.create_entry(hash, value, i);
linkedhashset_create_entry(set, hash, value, i);
}
fn void LinkedHashSet.free_internal(&set, void* ptr) @inline @private
fn void linkedhashset_free_internal(LinkedHashSet* set, void* ptr) @inline @private
{
allocator::free(set.allocator, ptr);
}
fn void LinkedHashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
fn void linkedhashset_create_entry(LinkedHashSet* set, uint hash, Value value, int bucket_index) @private
{
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
.hash = hash,
@@ -633,7 +633,7 @@ fn void LinkedHashSet.create_entry(&set, uint hash, Value value, int bucket_inde
set.count++;
}
fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
fn bool linkedhashset_remove_entry_for_value(LinkedHashSet* set, Value value) @private
{
if (!set.count) return false;
@@ -674,7 +674,7 @@ fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
}
set.count--;
set.free_entry(e);
linkedhashset_free_entry(set, e);
return true;
}
prev = e;
@@ -683,7 +683,7 @@ fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
return false;
}
fn void LinkedHashSet.free_entry(&set, LinkedEntry *entry) @private
fn void linkedhashset_free_entry(LinkedHashSet* set, LinkedEntry *entry) @private
{
allocator::free(set.allocator, entry);
}

View File

@@ -217,7 +217,7 @@ fn usz? LinkedList.rindex_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
*>
fn void LinkedList.remove_at(&self, usz index)
{
self.unlink(self.node_at_index(index));
linked_list_unlink(self, self.node_at_index(index));
}
<*
@@ -232,47 +232,47 @@ fn void LinkedList.insert_at(&self, usz index, Type element)
case self.size:
self.push(element);
default:
self.link_before(self.node_at_index(index), element);
linked_list_link_before(self, self.node_at_index(index), element);
}
}
<*
@require succ != null
*>
fn void LinkedList.link_before(&self, Node *succ, Type value) @private
fn void linked_list_link_before(LinkedList* l, Node *succ, Type value) @private
{
Node* pred = succ.prev;
Node* new_node = self.alloc_node();
Node* new_node = l.alloc_node();
*new_node = { .prev = pred, .next = succ, .value = value };
succ.prev = new_node;
if (!pred)
{
self._first = new_node;
l._first = new_node;
}
else
{
pred.next = new_node;
}
self.size++;
l.size++;
}
<*
@require self._first != null
@require l._first != null
*>
fn void LinkedList.unlink_first(&self) @private
fn void linked_list_unlink_first(LinkedList* l) @private
{
Node* f = self._first;
Node* f = l._first;
Node* next = f.next;
self.free_node(f);
self._first = next;
l.free_node(f);
l._first = next;
if (!next)
{
self._last = null;
l._last = null;
}
else
{
next.prev = null;
}
self.size--;
l.size--;
}
fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
@@ -285,7 +285,7 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
case equals(node.value, t):
Node* next = node.next;
self.unlink(node);
linked_list_unlink(self, node);
node = next;
default:
node = node.next;
@@ -297,7 +297,7 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
fn Type? LinkedList.pop(&self)
{
if (!self._last) return NO_MORE_ELEMENT~;
defer self.unlink_last();
defer linked_list_unlink_last(self);
return self._last.value;
}
@@ -309,20 +309,20 @@ fn bool LinkedList.is_empty(&self)
fn Type? LinkedList.pop_front(&self)
{
if (!self._first) return NO_MORE_ELEMENT~;
defer self.unlink_first();
defer linked_list_unlink_first(self);
return self._first.value;
}
fn void? LinkedList.remove_last(&self) @maydiscard
{
if (!self._first) return NO_MORE_ELEMENT~;
self.unlink_last();
linked_list_unlink_last(self);
}
fn void? LinkedList.remove_first(&self) @maydiscard
{
if (!self._first) return NO_MORE_ELEMENT~;
self.unlink_first();
linked_list_unlink_first(self);
}
@@ -332,7 +332,7 @@ fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
if (node.value == t)
{
self.unlink(node);
linked_list_unlink(self, node);
return true;
}
}
@@ -345,7 +345,7 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
if (node.value == t)
{
self.unlink(node);
linked_list_unlink(self, node);
return true;
}
}
@@ -354,7 +354,7 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
<*
@require self._last != null
*>
fn void LinkedList.unlink_last(&self) @inline @private
fn void linked_list_unlink_last(LinkedList* self) @inline @private
{
Node* l = self._last;
Node* prev = l.prev;
@@ -374,7 +374,7 @@ fn void LinkedList.unlink_last(&self) @inline @private
<*
@require x != null
*>
fn void LinkedList.unlink(&self, Node* x) @private
fn void linked_list_unlink(LinkedList* self, Node* x) @private
{
Node* next = x.next;
Node* prev = x.prev;

View File

@@ -82,7 +82,7 @@ fn void List.init_wrapping_array(&self, Allocator allocator, Type[] types)
self.allocator = allocator;
self.capacity = types.len;
self.entries = types.ptr;
self.set_size(types.len);
list_set_size(self, types.len);
}
fn bool List.is_initialized(&self) @inline => self.allocator != null && self.allocator != (Allocator)&dummy;
@@ -110,19 +110,19 @@ fn usz? List.to_format(&self, Formatter* formatter) @dynamic
fn void List.push(&self, Type element) @inline
{
self.reserve(1);
self.entries[self.set_size(self.size + 1)] = element;
self.entries[list_set_size(self, self.size + 1)] = element;
}
fn Type? List.pop(&self)
{
if (!self.size) return NO_MORE_ELEMENT~;
defer self.set_size(self.size - 1);
defer list_set_size(self, self.size - 1);
return self.entries[self.size - 1];
}
fn void List.clear(&self)
{
self.set_size(0);
list_set_size(self, 0);
}
fn Type? List.pop_first(&self)
@@ -138,7 +138,7 @@ fn Type? List.pop_first(&self)
fn void List.remove_at(&self, usz index)
{
usz new_size = self.size - 1;
defer self.set_size(new_size);
defer list_set_size(self, new_size);
if (!new_size || index == new_size) return;
self.entries[index .. new_size - 1] = self.entries[index + 1 .. new_size];
}
@@ -147,7 +147,7 @@ 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);
usz index = list_set_size(self, self.size + other_list.size);
foreach (&value : other_list)
{
self.entries[index++] = *value;
@@ -203,7 +203,7 @@ fn void List.add_array(&self, Type[] array) @deprecated("Use push_all")
{
if (!array.len) return;
self.reserve(array.len);
usz index = self.set_size(self.size + array.len);
usz index = list_set_size(self, self.size + array.len);
self.entries[index : array.len] = array[..];
}
@@ -217,7 +217,7 @@ 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);
usz index = list_set_size(self, self.size + array.len);
self.entries[index : array.len] = array[..];
}
@@ -232,7 +232,7 @@ fn void List.push_front(&self, Type type) @inline
fn void List.insert_at(&self, usz index, Type type)
{
self.reserve(1);
self.set_size(self.size + 1);
list_set_size(self, self.size + 1);
for (isz i = self.size - 1; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
@@ -251,7 +251,7 @@ fn void List.set_at(&self, usz index, Type type)
fn void? List.remove_last(&self) @maydiscard
{
if (!self.size) return NO_MORE_ELEMENT~;
self.set_size(self.size - 1);
list_set_size(self, self.size - 1);
}
fn void? List.remove_first(&self) @maydiscard
@@ -299,7 +299,7 @@ fn void List.free(&self)
{
if (!self.allocator || self.allocator.ptr == &dummy || !self.capacity) return;
self.pre_free(); // Remove sanitizer annotation
list_pre_free(self); // Remove sanitizer annotation
$if type_is_overaligned():
allocator::free_aligned(self.allocator, self.entries);
@@ -358,7 +358,7 @@ fn usz List.retain_using_test(&self, ElementTest filter, any context)
return list_common::list_remove_using_test(self, filter, true, context);
}
fn void List.ensure_capacity(&self, usz min_capacity) @local
fn void list_ensure_capacity(List* self, usz min_capacity) @local
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
@@ -374,7 +374,7 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
break;
}
self.pre_free(); // Remove sanitizer annotation
list_pre_free(self); // Remove sanitizer annotation
min_capacity = math::next_power_of_2(min_capacity);
$if type_is_overaligned():
@@ -384,7 +384,7 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
$endif;
self.capacity = min_capacity;
self.post_alloc(); // Add sanitizer annotation
list_post_alloc(self); // Add sanitizer annotation
}
<*
@@ -419,7 +419,7 @@ 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);
list_ensure_capacity(self, new_capacity);
}
fn void List._update_size_change(&self,usz old_size, usz new_size)
@@ -436,7 +436,7 @@ fn void List._update_size_change(&self,usz old_size, usz new_size)
<*
@require new_size == 0 || self.capacity != 0
*>
fn usz List.set_size(&self, usz new_size) @inline @private
fn usz list_set_size(List* self, usz new_size) @inline @private
{
usz old_size = self.size;
self._update_size_change(old_size, new_size);
@@ -444,7 +444,7 @@ fn usz List.set_size(&self, usz new_size) @inline @private
return old_size;
}
macro void List.pre_free(&self) @private
macro void list_pre_free(List* self) @private
{
if (!self.capacity) return;
self._update_size_change(self.size, self.capacity);
@@ -453,7 +453,7 @@ macro void List.pre_free(&self) @private
<*
@require self.capacity > 0
*>
macro void List.post_alloc(&self) @private
macro void list_post_alloc(List* self) @private
{
self._update_size_change(self.capacity, self.size);
}

View File

@@ -151,7 +151,7 @@ fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array();
<*
@require self.is_keyable()
*>
fn void Object.init_map_if_needed(&self) @private
fn void object_init_map_if_needed(Object* self) @private
{
if (self.is_empty())
{
@@ -163,7 +163,7 @@ fn void Object.init_map_if_needed(&self) @private
<*
@require self.is_indexable()
*>
fn void Object.init_array_if_needed(&self) @private
fn void object_init_array_if_needed(Object* self) @private
{
if (self.is_empty())
{
@@ -175,9 +175,9 @@ fn void Object.init_array_if_needed(&self) @private
<*
@require self.is_keyable()
*>
fn void Object.set_object(&self, String key, Object* new_object) @private
fn void object_set_object(Object* self, String key, Object* new_object) @private
{
self.init_map_if_needed();
object_init_map_if_needed(self);
Object*? val = self.map.get_entry(key).value;
defer (void)val.free();
self.map.set(key, new_object);
@@ -214,7 +214,7 @@ macro Object* Object.object_from_value(&self, value) @private
macro Object* Object.set(&self, String key, value)
{
Object* val = self.object_from_value(value);
self.set_object(key, val);
object_set_object(self, key, val);
return val;
}
@@ -267,7 +267,7 @@ fn usz Object.get_len(&self)
*>
fn void Object.push_object(&self, Object* to_append)
{
self.init_array_if_needed();
object_init_array_if_needed(self);
self.array.push(to_append);
}
@@ -276,7 +276,7 @@ fn void Object.push_object(&self, Object* to_append)
*>
fn void Object.set_object_at(&self, usz index, Object* to_set)
{
self.init_array_if_needed();
object_init_array_if_needed(self);
while (self.array.len() < index)
{
self.array.push(&NULL_OBJECT);

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ const uint PIXELS_MAX = 400000000;
Purely informative. It will be saved to the file header,
but does not affect how chunks are en-/decoded.
*>
enum QOIColorspace : const char
constdef QOIColorspace : char
{
<* sRGB with linear alpha *>
SRGB = 0,
@@ -21,7 +21,7 @@ enum QOIColorspace : const char
AUTO can be used when decoding to automatically determine
the channels from the file's header.
*>
enum QOIChannels : const inline char
constdef QOIChannels : inline char
{
AUTO = 0,
RGB = 3,

1215
lib/std/compression/zip.c3 Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -56,7 +56,7 @@ fn BackedArenaAllocator*? new_backed_allocator(usz size, Allocator allocator)
fn void BackedArenaAllocator.destroy(&self)
{
self.reset(0);
if (self.last_page) (void)self._free_page(self.last_page);
if (self.last_page) (void)_free_page(self, self.last_page);
allocator::free(self.backing_allocator, self);
}
@@ -79,7 +79,7 @@ fn void BackedArenaAllocator.reset(&self, usz mark)
self.used = last_page.mark;
ExtraPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
_free_page(self, to_free)!!;
}
self.last_page = last_page;
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
@@ -98,13 +98,13 @@ fn void BackedArenaAllocator.reset(&self, usz mark)
self.used = mark;
}
fn void? BackedArenaAllocator._free_page(&self, ExtraPage* page) @inline @local
fn void? _free_page(BackedArenaAllocator* 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
fn void*? _realloc_page(BackedArenaAllocator* self, ExtraPage* page, usz size, usz alignment) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
@@ -133,7 +133,7 @@ fn void*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignm
assert(self.last_page, "Realloc of unrelated pointer");
// First grab the page
ExtraPage *page = pointer - ExtraPage.sizeof;
return self._realloc_page(page, size, alignment);
return _realloc_page(self, page, size, alignment);
}
AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!;

View File

@@ -137,12 +137,67 @@ fn void DynamicArenaAllocator.reset(&self)
self.page = page;
}
<*
@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
{
alignment = alignment_for_allocation(alignment);
DynamicArenaPage* page = self.page;
void* ptr @noinit;
do SET_DONE:
{
if (!page && self.unused_page)
{
self.page = page = self.unused_page;
self.unused_page = page.prev_arena;
page.prev_arena = null;
}
if (!page)
{
ptr = _alloc_new(self, size, alignment)!;
break SET_DONE;
}
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
usz new_used = start - page.memory + size;
if ALLOCATE_NEW: (new_used > page.total)
{
if ((page = self.unused_page))
{
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
new_used = start + size - page.memory;
if (page.total >= new_used)
{
self.unused_page = page.prev_arena;
page.prev_arena = self.page;
self.page = page;
break ALLOCATE_NEW;
}
}
ptr = _alloc_new(self, size, alignment)!;
break SET_DONE;
}
page.used = new_used;
assert(start + size == page.memory + page.used);
ptr = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)ptr - 1;
chunk.size = size;
};
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
return ptr;
}
<*
@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
fn void*? _alloc_new(DynamicArenaAllocator* self, usz size, usz alignment) @local
{
// First, make sure that we can align it, extending the page size if needed.
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + alignment, alignment));
@@ -166,57 +221,4 @@ fn void*? DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @loca
self.page = page;
page.current_stack_ptr = mem_start;
return mem_start;
}
<*
@require size > 0 : `acquire expects size > 0`
@require !alignment || math::is_power_of_2(alignment)
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
DynamicArenaPage* page = self.page;
void* ptr @noinit;
do SET_DONE:
{
if (!page && self.unused_page)
{
self.page = page = self.unused_page;
self.unused_page = page.prev_arena;
page.prev_arena = null;
}
if (!page)
{
ptr = self._alloc_new(size, alignment)!;
break SET_DONE;
}
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
usz new_used = start - page.memory + size;
if ALLOCATE_NEW: (new_used > page.total)
{
if ((page = self.unused_page))
{
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
new_used = start + size - page.memory;
if (page.total >= new_used)
{
self.unused_page = page.prev_arena;
page.prev_arena = self.page;
self.page = page;
break ALLOCATE_NEW;
}
}
ptr = self._alloc_new(size, alignment)!;
break SET_DONE;
}
page.used = new_used;
assert(start + size == page.memory + page.used);
ptr = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)ptr - 1;
chunk.size = size;
};
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
return ptr;
}
}

View File

@@ -32,58 +32,58 @@ fn void*? SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type,
{
if (init_type == ZERO)
{
return alignment > 0 ? @aligned_alloc(self._calloc, size, alignment) : self._calloc(size);
return alignment > 0 ? @aligned_alloc_fn(self, simple_alloc_calloc, size, alignment) : simple_alloc_calloc(self, size);
}
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size);
return alignment > 0 ? @aligned_alloc_fn(self, simple_alloc_alloc, size, alignment) : simple_alloc_alloc(self, size);
}
fn void*? SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
return alignment > 0
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment)
: self._realloc(old_pointer, size);
? @aligned_realloc_fn(self, simple_alloc_calloc, simple_alloc_free, old_pointer, size, alignment)
: simple_alloc_realloc(self, old_pointer, size);
}
fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{
if (aligned)
{
@aligned_free(self._free, old_pointer)!!;
@aligned_free_fn(self, simple_alloc_free, old_pointer)!!;
}
else
{
self._free(old_pointer);
simple_alloc_free(self, old_pointer);
}
}
<*
@require old_pointer && bytes > 0
*>
fn void*? SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
fn void*? simple_alloc_realloc(SimpleHeapAllocator* self, void* old_pointer, usz bytes) @local
{
// Find the block header.
Header* block = (Header*)old_pointer - 1;
if (block.size >= bytes) return old_pointer;
void* new = self._alloc(bytes)!;
void* new = simple_alloc_alloc(self, bytes)!;
usz max_to_copy = math::min(block.size, bytes);
mem::copy(new, old_pointer, max_to_copy);
self._free(old_pointer);
simple_alloc_free(self, old_pointer);
return new;
}
fn void*? SimpleHeapAllocator._calloc(&self, usz bytes) @local
fn void*? simple_alloc_calloc(SimpleHeapAllocator* self, usz bytes) @local
{
void* data = self._alloc(bytes)!;
void* data = simple_alloc_alloc(self, bytes)!;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
fn void*? simple_alloc_alloc(SimpleHeapAllocator* self, usz bytes) @local
{
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
if (!self.free_list)
{
self.add_block(aligned_bytes)!;
simple_alloc_add_block(self, aligned_bytes)!;
}
Header* current = self.free_list;
@@ -123,22 +123,22 @@ fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
current = current.next;
}
}
self.add_block(aligned_bytes)!;
return self._alloc(aligned_bytes);
simple_alloc_add_block(self, aligned_bytes)!;
return simple_alloc_alloc(self, aligned_bytes);
}
fn void? SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
fn void? simple_alloc_add_block(SimpleHeapAllocator* self, usz aligned_bytes) @local
{
assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes);
char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!;
Header* new_block = (Header*)result.ptr;
new_block.size = result.len - Header.sizeof;
new_block.next = null;
self._free(new_block + 1);
simple_alloc_free(self, new_block + 1);
}
fn void SimpleHeapAllocator._free(&self, void* ptr) @local
fn void simple_alloc_free(SimpleHeapAllocator* self, void* ptr) @local
{
// Empty ptr -> do nothing.
if (!ptr) return;

View File

@@ -15,7 +15,6 @@ module std::core::mem::allocator @if(env::POSIX);
import std::os;
import libc;
fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
if (init_type == ZERO)
@@ -49,25 +48,32 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~;
void* new_ptr;
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return mem::OUT_OF_MEMORY~;
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: 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
// Try realloc, even though it might be unaligned.
void* new_ptr = libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~!;
// If it's aligned then we're done!
uptr ptr_val = (uptr)new_ptr;
if (ptr_val & (alignment - 1) == 0) return new_ptr;
// We failed, so we need to use memalign
// We will free new_ptr before we exit.
defer libc::free(new_ptr);
// Create a pointer which is sure to be aligned.
void* aligned_ptr;
if (posix::posix_memalign(&aligned_ptr, alignment, new_bytes))
{
return mem::OUT_OF_MEMORY~;
}
// Now it is safe to copy the full range of data.
mem::copy(aligned_ptr, new_ptr, new_bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return aligned_ptr;
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);

View File

@@ -127,7 +127,7 @@ fn void TempAllocator.reset(&self)
{
TempAllocator* old = child;
child = old.derived;
old.destroy();
temp_allocator_destroy(old);
}
self.capacity = self.original_capacity;
$if env::ADDRESS_SANITIZER:
@@ -142,17 +142,17 @@ fn void TempAllocator.reset(&self)
fn void TempAllocator.free(&self)
{
self.reset();
self.destroy();
temp_allocator_destroy(self);
}
fn void TempAllocator.destroy(&self) @local
fn void temp_allocator_destroy(TempAllocator* self)
{
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)!!;
_free_page(self, to_free)!!;
}
if (self.allocated)
{
@@ -179,33 +179,6 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
}
fn void? TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
}
fn void*? TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
// Walk backwards to find the pointer to this page.
TempAllocatorPage **pointer_to_prev = &self.last_page;
// Remove the page from the list
while (*pointer_to_prev != page)
{
pointer_to_prev = &((*pointer_to_prev).prev_page);
}
*pointer_to_prev = page.prev_page;
usz page_size = page.pagesize();
// Clear on size > original size.
void* data = self.acquire(size, NO_ZERO, alignment)!;
if (page_size > size) page_size = size;
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
self.backing_allocator.release(real_pointer, page.is_aligned());
return data;
}
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
{
@@ -215,7 +188,7 @@ fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d
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);
return _realloc_page(self, page, size, alignment);
}
bool is_realloc_of_last = chunk.size + pointer == &self.data[self.used];
if (is_realloc_of_last)
@@ -326,9 +299,39 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
return &page.data[0];
}
fn void? _free_page(TempAllocator* self, TempAllocatorPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
}
fn void*? _realloc_page(TempAllocator* self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
// Walk backwards to find the pointer to this page.
TempAllocatorPage **pointer_to_prev = &self.last_page;
// Remove the page from the list
while (*pointer_to_prev != page)
{
pointer_to_prev = &((*pointer_to_prev).prev_page);
}
*pointer_to_prev = page.prev_page;
usz page_size = page.pagesize();
// Clear on size > original size.
void* data = self.acquire(size, NO_ZERO, alignment)!;
if (page_size > size) page_size = size;
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
self.backing_allocator.release(real_pointer, page.is_aligned());
return data;
}
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,
@@ -383,10 +386,10 @@ fn void TempAllocator.reset(&self)
}
fn void TempAllocator.free(&self)
{
self.destroy();
_destroy(self);
}
fn void TempAllocator.destroy(&self) @local
fn void _destroy(TempAllocator* self) @local
{
TempAllocator* child = self.derived;
if (!child) return;
@@ -403,4 +406,4 @@ fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d
fn void TempAllocator.release(&self, void* old_pointer, bool b) @dynamic
{
self.vmem.release(old_pointer, b) @inline;
}
}

View File

@@ -1,7 +1,7 @@
module std::core::string::ansi;
import std::io;
enum Ansi : const inline String
constdef Ansi : inline String
{
RESET = "\e[0m",
BOLD = "\e[1m",

View File

@@ -329,6 +329,72 @@ macro bool @all(array, #predicate)
}
<*
Extract a copy of all even-index elements from an input array.
@param [&inout] allocator : "The allocator used to create the return array."
@param [in] array : "The array from which to extract all even elements."
@require @is_valid_list(array) : "Expected a valid list"
@require $defined(array[:lengthof(array)]) : "Expected a sliceable list"
*>
macro even(Allocator allocator, array)
{
return unlace_impl{$typeof(array[0])}(allocator, array[:lengthof(array)]);
}
<*
Extract a copy of all odd-index elements from an input array.
@param [&inout] allocator : "The allocator used to create the return array."
@param [in] array : "The array from which to extract all odd elements."
@require @is_valid_list(array) : "Expected a valid list"
@require $defined(array[:lengthof(array)]) : "Expected a sliceable list"
*>
macro odd(Allocator allocator, array)
{
return unlace_impl{$typeof(array[0])}(allocator, lengthof(array) > 1 ? array[1..] : ($typeof(array[0])[]){});
}
<*
Private implementation of `even` and `odd` macros, expecting a slice and returning one as well.
This function always extracts the even elements of the input slice.
@param [&inout] allocator : "The allocator used to create the return array."
@param [in] array : "The array from which to extract all odd elements."
*>
fn Type[] unlace_impl(Allocator allocator, Type[] array) <Type> @private
{
usz new_len = array.len / 2 + (array.len % 2 == 0 ? 0 : 1);
if (new_len == 0) return (Type[]){};
Type[] new_array = allocator::new_array(allocator, Type, new_len);
foreach (x, &new : new_array) *new = types::implements_copy(Type) ??? array[x * 2].copy(allocator) : array[x * 2];
return new_array[:new_len];
}
<*
Unlace or partition an input list into its component parts such that `[a, b, c, d, e]` becomes
`[a, c, e]` and `[b, d]`. Returned arrays are allocated by the given allocator and are returned
via two `out` parameters, `left` and `right`.
@param [&inout] allocator : "The allocator used to create the returned arrays."
@param [in] array : "The input array to unlace."
@param [out] left : "Stores a copy of all even-index array elements."
@param [out] right : "Stores a copy of all odd-index array elements."
@require @is_valid_list(array) : "Expected a valid list"
@require $typeof(left) == $typeof(array[0])[]*
@require $typeof(right) == $typeof(array[0])[]*
*>
macro unlace(Allocator allocator, array, left, right)
{
if (left) *left = even(allocator, array);
if (right) *right = odd(allocator, array);
}
<*
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

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::bitorder;
import std::bits;
// This module contains types of different endianness.
// *BE types represent big-endian types
// *LE types represent little-endian types.
@@ -87,6 +87,56 @@ bitstruct UInt128LE : uint128 @littleendian
uint128 val : 0..127;
}
<*
@require $defined(*bytes) : "Pointer must be possible to dereference"
@require types::is_intlike($typeof(*bytes)) : "Type must be an integer or int vector"
*>
macro load_be(bytes)
{
$if env::BIG_ENDIAN:
return mem::load(bytes, $align: 1);
$else
return bswap(mem::load(bytes, $align: 1));
$endif
}
<*
@require $defined(*bytes) : "Pointer must be possible to dereference"
@require types::is_intlike($typeof(*bytes)) : "Type must be an integer or int vector"
*>
macro load_le(bytes)
{
$if env::BIG_ENDIAN:
return bswap(mem::load(bytes, $align: 1));
$else
return mem::load(bytes, $align: 1);
$endif
}
<*
@require types::is_intlike($typeof(value)) : "Type must be an integer or int vector"
*>
macro void store_be(void* dst, value)
{
$if env::BIG_ENDIAN:
mem::store(($typeof(value)*)dst, value, $align: 1);
$else
mem::store(($typeof(value)*)dst, bswap(value), $align: 1);
$endif
}
<*
@require types::is_intlike($typeof(value)) : "Type must be an integer or int vector"
*>
macro void store_le(void* dst, value)
{
$if env::BIG_ENDIAN:
mem::store(($typeof(value)*)dst, bswap(value), $align: 1);
$else
mem::store(($typeof(value)*)dst, value, $align: 1);
$endif
}
<*
@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"

View File

@@ -26,7 +26,7 @@ macro foo(a, #b = EMPTY_MACRO_SLOT)
*>
const EmptySlot EMPTY_MACRO_SLOT @builtin @deprecated("Use `#arg = ...` instead.") = null;
typedef EmptySlot = void*;
typedef EmptySlot @constinit = 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;

View File

@@ -1,5 +1,5 @@
module std::core::dstring;
import std::io;
import std::io, std::math;
<*
The DString offers a dynamic string builder.
@@ -331,14 +331,14 @@ fn Char32[] DString.copy_utf32(&self, Allocator allocator)
}
<*
@require $typeof(str) == String || $typeof(str) == DString : "Expected string or DString"
@require $defined(String s = str) ||| $typeof(str) == DString : "Expected string or DString"
*>
macro void DString.append_string(&self, str)
{
$if $typeof(str) == String:
self.append_bytes(str);
$else
$if $typeof(str) == DString:
self.append_string_deprecated(str);
$else
self.append_bytes((String)str);
$endif
}
@@ -631,7 +631,7 @@ fn void DString.reverse(self)
}
}
fn StringData* DString.data(self) @inline @private
fn StringData* DString.data(self) @inline
{
return (StringData*)self;
}
@@ -648,7 +648,7 @@ fn void DString.reserve(&self, usz addition)
if (data.capacity >= len) return;
usz new_capacity = data.capacity * 2;
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
while (new_capacity < len) new_capacity *= 2;
if (new_capacity < len) new_capacity = math::next_power_of_2(len);
data.capacity = new_capacity;
*self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity);
}
@@ -658,9 +658,10 @@ fn usz? DString.read_from_stream(&self, InStream reader)
if (&reader.available)
{
usz total_read = 0;
while (usz available = reader.available()!)
while (ulong available = reader.available()!)
{
self.reserve(available);
if (available > isz.max) available = (ulong)isz.max;
self.reserve((usz)available);
StringData* data = self.data();
usz len = reader.read(data.chars[data.len..(data.capacity - 1)])!;
total_read += len;

View File

@@ -126,6 +126,7 @@ const bool ARCH_64_BIT = $$REGISTER_SIZE == 64;
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
const bool NO_LIBC = !LIBC && !CUSTOM_LIBC;
const bool CUSTOM_LIBC = $$CUSTOM_LIBC;
const bool OLD_IO = $feature(OLD_IO);
const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel.from_ordinal($$COMPILER_OPT_LEVEL);
const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED;

View File

@@ -6,22 +6,22 @@ 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;
const LogCategory CATEGORY_APPLICATION = (LogCategory)0;
const LogCategory CATEGORY_SYSTEM = (LogCategory)1;
const LogCategory CATEGORY_KERNEL = (LogCategory)2;
const LogCategory CATEGORY_AUDIO = (LogCategory)3;
const LogCategory CATEGORY_VIDEO = (LogCategory)4;
const LogCategory CATEGORY_RENDER = (LogCategory)5;
const LogCategory CATEGORY_INPUT = (LogCategory)6;
const LogCategory CATEGORY_NETWORK = (LogCategory)7;
const LogCategory CATEGORY_SOCKET = (LogCategory)8;
const LogCategory CATEGORY_SECURITY = (LogCategory)9;
const LogCategory CATEGORY_TEST = (LogCategory)10;
const LogCategory CATEGORY_ERROR = (LogCategory)11;
const LogCategory CATEGORY_ASSERT = (LogCategory)12;
const LogCategory CATEGORY_CRASH = (LogCategory)13;
const LogCategory CATEGORY_STATS = (LogCategory)14;
const LogCategory CATEGORY_CUSTOM_START = (LogCategory)100;
tlocal LogCategory default_category = CATEGORY_APPLICATION;
tlocal LogTag current_tag;

View File

@@ -404,6 +404,28 @@ macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
return mem;
}
<*
@require bytes > 0
@require alignment > 0
@require bytes <= isz.max
*>
macro void*? @aligned_alloc_fn(context, #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(context, bytes)) == OPTIONAL:
void* data = #alloc_fn(context, alignsize)!;
$else
void* data = #alloc_fn(context, 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;
@@ -420,6 +442,16 @@ macro void? @aligned_free(#free_fn, void* old_pointer)
$endif
}
macro void? @aligned_free_fn(context, #free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if $kindof(#free_fn(context, desc.start)) == OPTIONAL:
#free_fn(context, desc.start)!;
$else
#free_fn(context, desc.start);
$endif
}
<*
@require bytes > 0
@require alignment > 0
@@ -438,6 +470,23 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
return new_data;
}
<*
@require bytes > 0
@require alignment > 0
*>
macro void*? @aligned_realloc_fn(context, #calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
void* new_data = @aligned_alloc_fn(context, #calloc_fn, bytes, alignment)!;
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
$if $kindof(#free_fn(context, data_start)) == OPTIONAL:
#free_fn(context, data_start)!;
$else
#free_fn(context, data_start);
$endif
return new_data;
}
// All allocators
alias mem @builtin = thread_allocator ;

View File

@@ -54,7 +54,7 @@ struct FixedBlockPool
@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)
fn FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_size, usz capacity = INITIAL_CAPACITY, usz alignment = 0)
{
self.allocator = allocator;
self.tail = &self.head;
@@ -64,7 +64,7 @@ macro FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_
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();
self.head.buffer = fixedblockpool_allocate_page(self);
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
asan::poison_memory_region(self.head.buffer, self.page_size);
$endif
@@ -119,7 +119,7 @@ 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);
fixedblockpool_free_page(self, self.head.buffer);
FixedBlockPoolNode* iter = self.head.next;
while (iter)
@@ -127,7 +127,7 @@ fn void FixedBlockPool.free(&self)
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
asan::unpoison_memory_region(iter.buffer, self.page_size);
$endif
self.free_page(iter.buffer);
fixedblockpool_free_page(self, iter.buffer);
FixedBlockPoolNode* current = iter;
iter = iter.next;
allocator::free(self.allocator, current);
@@ -158,7 +158,7 @@ fn void* FixedBlockPool.alloc(&self)
}
void* end = self.tail.buffer + (self.tail.capacity * self.block_size);
if (self.next_free >= end) self.new_node();
if (self.next_free >= end) fixedblockpool_new_node(self);
void* ptr = self.next_free;
self.next_free += self.block_size;
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
@@ -172,7 +172,7 @@ fn void* FixedBlockPool.alloc(&self)
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"
@require fixedblockpool_check_ptr(self, ptr) : "The pointer should be part of the pool"
*>
fn void FixedBlockPool.dealloc(&self, void* ptr)
{
@@ -193,7 +193,7 @@ fn void FixedBlockPool.dealloc(&self, void* ptr)
<*
@require self.initialized : "The block pool must be initialized"
*>
fn bool FixedBlockPool.check_ptr(&self, void *ptr) @local
fn bool fixedblockpool_check_ptr(FixedBlockPool* self, void *ptr) @local
{
FixedBlockPoolNode* iter = &self.head;
@@ -210,10 +210,10 @@ fn bool FixedBlockPool.check_ptr(&self, void *ptr) @local
<*
@require self.grow_capacity > 0 : "How many blocks will it store"
*>
fn void FixedBlockPool.new_node(&self) @local
fn void fixedblockpool_new_node(FixedBlockPool* self) @local
{
FixedBlockPoolNode* node = allocator::new(self.allocator, FixedBlockPoolNode);
node.buffer = self.allocate_page();
node.buffer = fixedblockpool_allocate_page(self);
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
asan::poison_memory_region(node.buffer, self.page_size);
$endif
@@ -224,14 +224,14 @@ fn void FixedBlockPool.new_node(&self) @local
self.allocated += node.capacity;
}
macro void* FixedBlockPool.allocate_page(&self) @private
macro void* fixedblockpool_allocate_page(FixedBlockPool* 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
macro void fixedblockpool_free_page(FixedBlockPool* self, void* page) @private
{
if (self.alignment > mem::DEFAULT_MEM_ALIGNMENT)
{

View File

@@ -321,7 +321,7 @@ fn void? VirtualMemory.destroy(&self)
return release(self.ptr, self.size);
}
fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX) @private
fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX)
{
switch (self)
{
@@ -336,7 +336,7 @@ fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX) @private
}
}
fn Win32_Protect VirtualMemoryAccess.to_win32(self) @if(env::WIN32) @private
fn Win32_Protect VirtualMemoryAccess.to_win32(self) @if(env::WIN32)
{
switch (self)
{

View File

@@ -7,19 +7,26 @@ macro usz _strlen(ptr) @private
return len;
}
macro int @main_to_err_main(#m, int, char**)
macro int @main_no_args(#m, int, char**)
{
if (catch #m()) return 1;
return 0;
$if $typeof(#m()) == void:
#m();
return 0;
$else
return #m();
$endif
}
macro int @main_to_int_main(#m, int, char**)
macro int @main_args(#m, int argc, char** argv)
{
return #m();
}
macro int @main_to_void_main(#m, int, char**)
{
#m();
return 0;
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
$if $typeof(#m(list)) == void:
#m(list);
return 0;
$else
return #m(list);
$endif
}
macro String[] args_to_strings(int argc, char** argv) @private
@@ -35,21 +42,6 @@ macro String[] args_to_strings(int argc, char** argv) @private
}
macro int @main_to_err_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
if (catch #m(list)) return 1;
return 0;
}
macro int @main_to_int_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
return #m(list);
}
macro int @_main_runner(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
@@ -57,14 +49,6 @@ macro int @_main_runner(#m, int argc, char** argv)
return #m(list) ? 0 : 1;
}
macro int @main_to_void_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
#m(list);
return 0;
}
module std::core::main_stub @if(env::WIN32);
import std::os::win32;
@@ -105,92 +89,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_main_no_args(#m, void* handle, void* prev_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)
{
win32_set_utf8_codepage();
return #m();
$if $typeof(#m()) == void:
#m();
return 0;
$else
return #m();
$endif
}
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_main_args(#m, void* handle, void* prev_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;
$if $typeof(#m(args)) == void:
#m(args);
return 0;
$else
return #m(args);
$endif
}
macro int @win_to_int_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_main(#m, void* handle, void* prev_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);
$if $typeof(#m(handle, prev_handle, args, show_cmd)) == void:
#m(handle, prev_handle, args, show_cmd);
return 0;
$else
return #m(handle, prev_handle, args, show_cmd);
$endif
}
macro int @win_to_void_main_args(#m, void* handle, void* prev_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)
{
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;
return 0;
}
macro int @win_to_int_main(#m, void* handle, void* prev_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);
}
macro int @win_to_void_main(#m, void* handle, void* prev_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);
return 0;
}
macro int @wmain_to_err_main_args(#m, int argc, Char16** argv)
macro int @wmain_main(#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;
return 1;
$if $typeof(#m(args)) == void:
#m(args);
return 0;
$else
return #m(args);
$endif
}
macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
macro int @wmain_main_no_args(#m, int argc, Char16** argv)
{
win32_set_utf8_codepage();
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
return #m(args);
$if $typeof(#m()) == void:
#m();
return 0;
$else
return #m();
$endif
}
macro int @_wmain_runner(#m, int argc, Char16** argv)
@@ -200,12 +158,3 @@ macro int @_wmain_runner(#m, int argc, Char16** 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);
return 0;
}

View File

@@ -125,10 +125,11 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
char[] perc_str = { [0..19] = ' ', [20] = 0 };
int perc = 0;
uint print_step = current_benchmark_iterations / 100;
if (print_step == 0) print_step = 1;
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
if (this_iteration % print_step == 0) // 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));

View File

@@ -142,7 +142,7 @@ fn void mute_output() @local
File* stderr = io::stderr();
*stderr = test_context.fake_stdout;
*stdout = test_context.fake_stdout;
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
(void)test_context.fake_stdout.set_cursor(0)!!;
}
fn void unmute_output(bool has_error) @local
@@ -155,7 +155,7 @@ fn void unmute_output(bool has_error) @local
*stderr = test_context.stored.stderr;
*stdout = test_context.stored.stdout;
usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
ulong log_size = test_context.fake_stdout.cursor()!!;
if (has_error)
{
io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
@@ -165,7 +165,7 @@ fn void unmute_output(bool has_error) @local
{
test_context.fake_stdout.write_byte('\n')!!;
test_context.fake_stdout.write_byte('\0')!!;
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
test_context.fake_stdout.set_cursor(0)!!;
io::printfn("\n========== TEST LOG ============");
io::printfn("%s\n", test_context.current_test_name);

View File

@@ -2,16 +2,16 @@ 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_LINKER_INIT = (MutexFlags)1 << 0;
const MutexFlags MUTEX_WRITE_REENTRANT = (MutexFlags)1 << 1;
const MutexFlags MUTEX_READ_REENTRANT = (MutexFlags)1 << 2;
const MutexFlags MUTEX_NOT_STATIC = (MutexFlags)1 << 8;
const MutexFlags MUTEX_READ_LOCK = (MutexFlags)1 << 3;
const MutexFlags MUTEX_TRY_LOCK = (MutexFlags)1 << 4;
const MutexFlags MUTEX_TRY_LOCK_FAILED = (MutexFlags)1 << 5;
const MutexFlags MUTEX_RECURSIVE_LOCK = (MutexFlags)1 << 6;
const MutexFlags MUTEX_RECURSIVE_UNLOCK = (MutexFlags)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 }

View File

@@ -1,14 +1,16 @@
module std::core::string;
import std::io, std::ascii;
import std::core::mem::allocator;
typedef String @if(!$defined(String)) = inline char[];
typedef String @constinit @if(!$defined(String)) = inline char[];
<*
ZString is a pointer to a zero terminated array of chars.
Use ZString when you need to interop with C zero terminated strings.
*>
typedef ZString = inline char*;
typedef ZString @constinit = inline char*;
<*
WString is a pointer to a zero terminated array of Char16.
@@ -96,7 +98,8 @@ fn ZString tformat_zstr(String fmt, args...) @format(0)
}
<*
Return a new String created using the formatting function.
Return a new String created using the formatting function, this function will implicitly
use the temp allocator.
@param [inout] allocator : `The allocator to use`
@param [in] fmt : `The formatting string`
@@ -156,31 +159,44 @@ macro bool char_in_set(char c, String set)
return false;
}
<*
Join together an array of strings via a "joiner" sequence, which is inserted between each element.
@param [&inout] allocator : "The allocator to use."
@param [in] s : "An array of strings to join in sequence."
@param [in] joiner : "The string used to join each element of `s`."
@return "A single string containing the result, allocated via `allocator`, safe to convert to a ZString."
*>
fn String join(Allocator allocator, String[] s, String joiner)
{
if (!s)
{
return (String)allocator::new_array(allocator, char, 2)[:0];
}
usz total_size = joiner.len * s.len;
usz joiner_len = joiner.len;
usz total_size = joiner_len * (s.len - 1) + 1;
foreach (String* &str : s)
{
total_size += str.len;
}
@pool()
char[] data = allocator::alloc_array(allocator, char, total_size);
usz offset = s[0].len;
data[:offset] = s[0][:offset];
foreach (String* &str : s[1..])
{
DString res = dstring::temp_with_capacity(total_size);
res.append(s[0]);
foreach (String* &str : s[1..])
{
res.append(joiner);
res.append(*str);
}
return res.copy_str(allocator);
data[offset:joiner_len] = joiner[:joiner_len];
offset += joiner_len;
usz len = str.len;
data[offset:len] = str.[:len];
offset += len;
};
data[offset] = 0;
return (String)data[:offset];
}
<* Alias for `string::join` using the temp allocator. *>
macro String tjoin(String[] s, String joiner) => join(tmem, s, joiner);
<*
Replace all instances of one substring with a different string.

View File

@@ -158,7 +158,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
if (rp % 9)
{
long rpm9 = rp >= 0 ? rp % 9 : rp % 9 + 9;
int p10 = P10S[8 - rpm9];
uint p10 = P10S[8 - rpm9];
uint carry = 0;
for (k = a; k != z; k++)
{

View File

@@ -97,8 +97,8 @@ macro bool is_subtype_of($Type, $OtherType)
macro bool is_numerical($Type)
{
$switch $Type.kindof:
$case DISTINCT:
$case CONST_ENUM:
$case TYPEDEF:
$case CONSTDEF:
return is_numerical($Type.inner);
$case SIGNED_INT:
$case UNSIGNED_INT:
@@ -112,7 +112,12 @@ macro bool is_numerical($Type)
fn bool TypeKind.is_int(kind) @inline
{
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
return kind == SIGNED_INT || kind == UNSIGNED_INT;
}
macro bool TypeKind.@is_int($kind) @const
{
return $kind == SIGNED_INT ||| $kind == UNSIGNED_INT;
}
macro bool is_slice_convertable($Type) @deprecated("Use is_slice_convertible") => is_slice_convertible($Type);
@@ -165,7 +170,7 @@ macro bool is_unsigned($Type) @const
macro typeid flat_type($Type) @const
{
$if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF:
return flat_type($Type.inner);
$else
return $Type.typeid;
@@ -174,7 +179,7 @@ macro typeid flat_type($Type) @const
macro TypeKind flat_kind($Type) @const
{
$if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF:
return flat_type($Type.inner);
$else
return $Type.kindof;
@@ -198,8 +203,8 @@ macro bool is_flat_intlike($Type) @const
$case UNSIGNED_INT:
return true;
$case VECTOR:
$case DISTINCT:
$case CONST_ENUM:
$case TYPEDEF:
$case CONSTDEF:
return is_flat_intlike($Type.inner);
$default:
return false;
@@ -225,7 +230,7 @@ macro bool is_underlying_int($Type) @const
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
$case DISTINCT:
$case TYPEDEF:
return is_underlying_int($Type.inner);
$default:
return false;
@@ -253,7 +258,7 @@ macro bool is_vector($Type) @const
macro typeid inner_type($Type) @const
{
$if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF:
return inner_type($Type.inner);
$else
return $Type.typeid;
@@ -293,7 +298,7 @@ macro bool may_load_atomic($Type) @const
$case POINTER:
$case FLOAT:
return true;
$case DISTINCT:
$case TYPEDEF:
$case ENUM:
return may_load_atomic($Type.inner);
$default:
@@ -308,8 +313,8 @@ macro lower_to_atomic_compatible_type($Type) @const
$case SIGNED_INT:
$case UNSIGNED_INT:
return $Type.typeid;
$case DISTINCT:
$case CONST_ENUM:
$case TYPEDEF:
$case CONSTDEF:
return lower_to_atomic_compatible_type($Type.inner);
$case FLOAT:
$switch $Type:
@@ -374,6 +379,9 @@ macro bool @comparable_value(#value) @const
$endif
}
const CONST_ENUM @builtin @deprecated("Use TypeKind.CONSTDEF instead") = TypeKind.CONSTDEF;
const DISTINCT @builtin @deprecated("Use TypeKind.TYPEDEF instead") = TypeKind.TYPEDEF;
enum TypeKind : char
{
VOID,
@@ -385,7 +393,7 @@ enum TypeKind : char
FAULT,
ANY,
ENUM,
CONST_ENUM,
CONSTDEF,
STRUCT,
UNION,
BITSTRUCT,
@@ -394,7 +402,7 @@ enum TypeKind : char
ARRAY,
SLICE,
VECTOR,
DISTINCT,
TYPEDEF,
POINTER,
INTERFACE,
}

View File

@@ -56,9 +56,9 @@ enum BlockMode
<* AES type: 128, 192 or 256 bits *>
enum AesType : (AesKey key)
{
AES128 = { 128, 16, 176, 4, 10 },
AES192 = { 192, 24, 208, 6, 12 },
AES256 = { 256, 32, 240, 8, 14 }
AES128 {{ 128, 16, 176, 4, 10 }},
AES192 {{ 192, 24, 208, 6, 12 }},
AES256 {{ 256, 32, 240, 8, 14 }}
}
struct AesKey

View File

@@ -63,7 +63,7 @@ macro quarter_round(uint* x, int a, int b, int c, int d) @local
}
<* Process the next (or final) chunk of ingested data. *>
fn void ChaCha20.mutate_keystream(&self) @local @inline
fn void chacha20_mutate_keystream(ChaCha20* self) @local @inline
{
self.key_stream[..] = self.state[..];
@@ -137,7 +137,7 @@ fn void ChaCha20.transform(&self, char[] data)
for (usz x = offset; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..], x = offset)
{
self.mutate_keystream();
chacha20_mutate_keystream(self);
if (offset) foreach (i, &b : data[:offset]) *b ^= key_stream[i];
char[] aligned_data = data[offset..];
for (; x <= (BLOCK_SIZE - usz.sizeof); x += usz.sizeof)
@@ -150,7 +150,7 @@ fn void ChaCha20.transform(&self, char[] data)
// 3. Process any remaining bytes.
if (data.len > 0)
{
self.mutate_keystream();
chacha20_mutate_keystream(self);
for (usz i = 0; i < data.len; i++) data[i] ^= key_stream[i];
self.position = data.len;
}

View File

@@ -332,7 +332,7 @@ fn Projection Projection.mul(&s, char[] n) @operator(*)
module std::crypto::ed25519 @private;
typedef F25519Int = inline char[32];
typedef F25519Int @constinit = inline char[32];
const F25519Int ZERO = {};
const F25519Int ONE = {[0] = 1};
@@ -567,7 +567,7 @@ fn F25519Int F25519Int.inv(&s)
@param [&in] s
*>
fn F25519Int F25519Int.pow_2523(&s) @local
fn F25519Int pow_2523(F25519Int* s) @local
{
F25519Int r = *s;
@@ -587,7 +587,7 @@ fn F25519Int F25519Int.pow_2523(&s) @local
fn F25519Int F25519Int.sqrt(&s)
{
F25519Int twice = s.mul_s(2);
F25519Int pow = twice.pow_2523();
F25519Int pow = pow_2523(&twice);
return (twice * pow * pow - ONE) * s * pow;
}
@@ -601,7 +601,7 @@ fn F25519Int F25519Int.sqrt(&s)
module std::crypto::ed25519 @private;
import std::math;
typedef FBaseInt = inline char[32];
typedef FBaseInt @constinit = inline char[32];
// Order of the field : 2^252+0x14def9dea2f79cd65812631a5cf5d3ed
const FBaseInt ORDER = x"edd3f55c1a631258 d69cf7a2def9de14 0000000000000000 0000000000000010";

View File

@@ -242,7 +242,7 @@ const char INVALID @private = 0xff;
const int STD_PADDING = '=';
const int NO_PADDING = -1;
typedef Alphabet = char[32];
typedef Alphabet @constinit = char[32];
// Standard base32 Alphabet
const Alphabet STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
// Extended Hex Alphabet

View File

@@ -0,0 +1,243 @@
// Copyright (c) 2026 Koni Marti. All rights reserved.
// Use of this source code is governed by the MIT license.
<*
Module providing generic singlebyte code page to UTF8 conversion.
This module implements a compact, tabledriven approach for single-byte
(8bit) encodings (e.g. CP437, CP850, CP866, CP125x). It is designed so
that each concrete code page only needs to supply a small, static
mapping table; the conversion logic is shared.
The design has two main goals:
- Fast decode from code page to UTF8 with a single table lookup per byte.
- Memoryefficient encode from UTF8 to code page without a large
Unicodetobyte array (no 64k reverse map per code page).
The design of CodePageTable and the packed reverse mapping is conceptually
similar to golang.org/x/text/encoding/charmap.
*>
module std::encoding::codepage;
import std::sort;
<*
Default replacement byte used when encoding from UTF8 to a singlebyte
code page and a Unicode scalar cannot be represented.
By convention, 0x1A is the ASCII/IBM SUB (substitute) control character.
*>
const char REPLACEMENT_CHAR = 0x1a;
<*
CodePageTable contains the bidirectional mapping tables for a singlebyte code
page in a compact packed form.
to_codepoint is the forward map from codepage byte (0x000xFF) to its UTF8
sequence. The array index is the raw byte value, each entry occupying 4 bytes:
- Byte 0 is the length of the UTF8 sequence (04)
- Bytes 1:len are the UTF8 bytes for the mapped Unicode scalar
The table therefore uses 256 * 4 bytes and is stored as a flat
char[1024] array, where entry i starts at offset i*4.
from_codepoint is the reverse map from Unicode scalar value to codepage byte,
also stored as a packed char[1024] array. It contains 256 entries of 4 bytes
each, where each 4byte chunk is interpreted as a littleendian uint
with the following packing scheme:
high 8 bits = codepage byte value (0x000xFF)
low 24 bits = Unicode scalar value (code point)
In other words:
entry = (byte_value << 24) | codepoint;
Ordering:
The 256 packed uint entries in from_codepoint are sorted by the low 24 bits
(code points). This allows binary search over Unicode scalar values without
a 64k reverselookup table. For any given code page, there are at most
256 mappings, so a log2(256) or 8 step search is sufficient.
*>
struct CodePageTable
{
char[1024] to_codepoint;
char[1024] from_codepoint;
}
enum CodePage : (String name, CodePageTable* table)
{
CP1250 { "cp1250", &codepage::CP1250 },
CP1251 { "cp1251", &codepage::CP1251 },
CP1252 { "cp1252", &codepage::CP1252 },
CP1253 { "cp1253", &codepage::CP1253 },
CP1254 { "cp1254", &codepage::CP1254 },
CP1255 { "cp1255", &codepage::CP1255 },
CP1256 { "cp1256", &codepage::CP1256 },
CP1257 { "cp1257", &codepage::CP1257 },
CP1258 { "cp1258", &codepage::CP1258 },
CP437 { "cp437", &codepage::CP437 },
CP737 { "cp737", &codepage::CP737 },
CP775 { "cp775", &codepage::CP775 },
CP850 { "cp850", &codepage::CP850 },
CP852 { "cp852", &codepage::CP852 },
CP855 { "cp855", &codepage::CP855 },
CP857 { "cp857", &codepage::CP857 },
CP860 { "cp860", &codepage::CP860 },
CP861 { "cp861", &codepage::CP861 },
CP862 { "cp862", &codepage::CP862 },
CP863 { "cp863", &codepage::CP863 },
CP864 { "cp864", &codepage::CP864 },
CP865 { "cp865", &codepage::CP865 },
CP866 { "cp866", &codepage::CP866 },
CP869 { "cp869", &codepage::CP869 },
CP874 { "cp874", &codepage::CP874 },
ISO_8859_1 { "iso-8859-1", &codepage::ISO_8859_1 },
ISO_8859_10 { "iso-8859-10", &codepage::ISO_8859_10 },
ISO_8859_11 { "iso-8859-11", &codepage::ISO_8859_11 },
ISO_8859_13 { "iso-8859-13", &codepage::ISO_8859_13 },
ISO_8859_14 { "iso-8859-14", &codepage::ISO_8859_14 },
ISO_8859_15 { "iso-8859-15", &codepage::ISO_8859_15 },
ISO_8859_16 { "iso-8859-16", &codepage::ISO_8859_16 },
ISO_8859_2 { "iso-8859-2", &codepage::ISO_8859_2 },
ISO_8859_3 { "iso-8859-3", &codepage::ISO_8859_3 },
ISO_8859_4 { "iso-8859-4", &codepage::ISO_8859_4 },
ISO_8859_5 { "iso-8859-5", &codepage::ISO_8859_5 },
ISO_8859_6 { "iso-8859-6", &codepage::ISO_8859_6 },
ISO_8859_7 { "iso-8859-7", &codepage::ISO_8859_7 },
ISO_8859_8 { "iso-8859-8", &codepage::ISO_8859_8 },
ISO_8859_9 { "iso-8859-9", &codepage::ISO_8859_9 },
US_ASCII { "us-ascii", &codepage::US_ASCII },
}
<*
Returns a CodePage for the given charset name.
@param [in] charset_name : "A name, case insensitive, using _ or - for separator"
@return "The CodePage for the name"
@return? NOT_FOUND : "If the charset is unknown or unsupported"
*>
fn CodePage? by_name(String charset_name) => @pool()
{
String name = charset_name.treplace("_","-");
name.convert_to_lower();
foreach (page : CodePage.values)
{
if (page.name == charset_name) return page;
}
return NOT_FOUND~;
}
fn String? decode(Allocator allocator, char[] src, CodePage code_page)
{
char[] dst = allocator::alloc_array(allocator, char, decode_len(src, code_page));
return decode_buffer(src, dst, code_page);
}
<*
Decode a code-page byte buffer into a UTF8 string.
@param src : "Input byte array in the given code page."
@param dst : "Destination output string in UTF-8."
@param code_page : "Code page for this encoding."
@return "String in UTF-8."
*>
fn String? decode_buffer(char[] src, char[] dst, CodePage code_page)
{
usz n = 0;
CodePageTable *table = code_page.table;
foreach (c: src)
{
usz pos = (usz)c * 4;
char len = table.to_codepoint[pos];
dst[n:len] = table.to_codepoint[pos+1:len];
n += len;
}
return (String)dst[:n];
}
fn char[]? encode(Allocator allocator, char[] src, CodePage code_page, char replacement = REPLACEMENT_CHAR)
{
char[] dst = allocator::alloc_array(allocator, char, encode_len(src));
return encode_buffer(src, dst, code_page, replacement);
}
const uint MASK @private = (1u << 24) - 1;
<*
Encode a UTF8 string into a singlebyte code page.
@param src : "Input byte array in UTF-8"
@param dst : "Destination output byte array in the target code page"
@param code_page : "Code page for this encoding."
@param replacement : "Byte to emit when Unicode scalar cannot be represented in the target code page."
@return "Byte array in the given code page."
*>
fn char[]? encode_buffer(char[] src, char[] dst, CodePage code_page, char replacement = REPLACEMENT_CHAR)
{
// Unpack the packed reverse table once into a local uint[256] view.
uint[256] from_map;
CodePageTable *table = code_page.table;
for (usz i = 0; i < 256; i++)
{
UIntLE *val = (UIntLE*)&table.from_codepoint[i * 4];
from_map[i] = mem::load(val, 1).val;
}
usz out = 0;
usz n = src.len;
for (usz i = 0; i < n; )
{
usz rem = n - i;
if (rem > 4) rem = 4;
Char32 codepoint = conv::utf8_to_char32(&src[i], &rem)!;
i += rem;
// Binary search for codepoint in low 24 bits of each entry.
// Returned index is between [0..from_map.len).
usz index = sort::binarysearch(from_map[..], (uint)codepoint, fn int(uint lhs, uint rhs) => (int)(lhs & MASK) - (int)(rhs & MASK));
uint entry = from_map[index];
if ((entry & MASK) == (uint)codepoint)
{
char b = (char)(entry >> 24);
dst[out++] = b;
}
else
{
dst[out++] = replacement;
}
}
return dst[:out];
}
<*
Compute the number of UTF8 bytes produced when decoding src with the given
code page table.
@param src : "Input byte array in the given code page."
@param code_page : "Code page for this encoding."
*>
fn usz decode_len(char[] src, CodePage code_page) @inline
{
usz n;
CodePageTable *table = code_page.table;
foreach (usz c: src) n += table.to_codepoint[c *4 ];
return n;
}
<*
Compute the number of output bytes produced when
encoding src from UTF8 to a singlebyte code page.
@param src : "Input byte array in UTF-8"
*>
fn usz encode_len(char[] src) @inline
{
return conv::utf8_codepoints((String)src);
}

File diff suppressed because it is too large Load Diff

355
lib/std/encoding/pem.c3 Normal file
View File

@@ -0,0 +1,355 @@
// Copyright (c) 2026 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.
//
// A module for encoding or decoding PEM blobs [mostly] in accordance with RFCs 1421-1424.
// This implementation retains a lot of flexibility in parsing input PEM blobs.
//
module std::encoding::pem;
import std::collections, std::encoding::base64;
<* A safe, default tag to use per RFC 1421's rules. *>
const String DEFAULT_TAG = "PRIVACY-ENHANCED MESSAGE";
<* The set of characters which are considered valid for PEM tags (which appear inside of Encapsulation Boundaries). *>
const AsciiCharset TAG_SET @local = ascii::@combine_sets(ascii::ALPHA_UPPER_SET, ascii::NUMBER_SET, ascii::@create_set(" _-/+()"));
<* The set of characters which are considered valid for optional PEM headers used. *>
const AsciiCharset HEADER_KEY_SET @local = ascii::@combine_sets(ascii::ALPHANUMERIC_SET, ascii::@create_set("!#$%&'*+-.^_`|~"));
<* All PEM Encapsulation Boundaries must use this delimiter to demarcate the PEM from its surrounding content, if any. *>
const String EB_DELIMITER @local = "-----";
<* All PEM blobs will start with this Encapsulation Boundary prefix. *>
const String PRE_EB_PREFIX @local = EB_DELIMITER +++ "BEGIN ";
<* All PEM blobs will terminate with this Encapsulation Boundary prefix. *>
const String POST_EB_PREFIX @local = EB_DELIMITER +++ "END ";
alias PemHeader = String[2];
<* Specify a set of possible PEM en/decoding faults. *>
faultdef
BODY_REQUIRED, // encoding: no body given (or too few of them)
HEADERS_REQUIRED, // encoding: no headers given (or too few of them)
HEADER_KEY_REQUIRED, // encoding: blank header keys are not allowed
HEADER_VALUE_REQUIRED, // encoding: blank header values are not allowed
INVALID_BODY, // decoding: invalid body, likely bad base64
INVALID_FORMAT, // decoding: invalid input formatting - no pre-EB or just plain wrong
INVALID_HEADER, // decoding: invalid headers
INVALID_HEADER_KEY, // decoding: invalid or empty header key
INVALID_PRE_EB, // decoding: invalid pre-EncapsBoundary BEFORE the PEM body
INVALID_POST_EB, // decoding: invalid post-EncapsBoundary AFTER the PEM body
INVALID_TAG, // decoding: invalid tag within an EB
MISMATCHED_TAG, // decoding: the tag from the pre-EB doesn't match that of the post-EB
MISSING_BODY, // decoding: missing PEM body base64
MISSING_HEADER_KEY, // decoding: the header is missing its key
MISSING_HEADER_VALUE, // decoding: the header is missing its value
MISSING_POST_EB, // decoding: no post-EB was found to close off the PEM
MISSING_TAG, // decoding: no tag was defined or parsed from the EB
TAG_REQUIRED, // encoding: no/empty tag given (or too few of them)
;
<* Represents a PEM object in memory, with a reference to the body data, tag value, and optional headers. *>
struct Pem
{
<* The allocator associated with the PEM's creation and destruction. *>
Allocator allocator;
<* A flexible 'tag' value used within the Encapsulation Boundary to denote the type of the PEM. *>
String tag;
<* A set of optional headers used to provide more context or information about the body of the PEM object. *>
LinkedHashMap{String, String} headers;
<* The core boy data of the PEM itself - the main values to be transmitted in this format. *>
char[] data;
}
<*
Create a new PEM object from a few inputs. Each input (i.e., tag, data, and headers) is copied to a new memory location.
The PEM object itself is not allocated in-memory, but is a simple container that points to each value that _is_.
Key-Value pairs for headers are provided in sequence as variadic arguments: `"key", "value", "key2", "value2", ...`
Created PEMs that are not temporary should be destroyed with `Pem.free`.
@param [&inout] allocator : "The allocator to use when copying the provided PEM object's fields."
@param [in] data : "The body data of the PEM."
@param [in] tag : "The tag value to use within the PEM's Encapsulation Boundary."
@return "A new PEM object."
*>
fn Pem create(Allocator allocator, char[] data, String tag, PemHeader... args)
{
Pem result = {
.allocator = allocator,
.tag = tag.copy(allocator),
.data = allocator::clone_slice(allocator, data),
};
result.headers.init(allocator, capacity: max(args.len, 16));
foreach (arg : args)
{
result.add_header(arg[0], arg[1]);
}
return result;
}
<*
Duplicate a `Pem` container and allocate copies of its members using the given allocator.
@param [&inout] allocator : "The allocator to use when copying the `Pem` members."
*>
fn Pem Pem.copy(&self, Allocator allocator)
{
Pem result = create(allocator, self.data, self.tag);
self.headers.@each(;String key, String value)
{
result.add_header(key, value);
};
return result;
}
<*
Safely destroys a `Pem` and deallocate all of its members. This should always be explicitly called when not using `tmem`.
*>
fn void Pem.free(&self)
{
mem::zero_volatile(self.data);
if (self.allocator != tmem)
{
self.headers.@each(;String key, String value)
{
allocator::free(self.allocator, value);
};
self.headers.free();
self.tag.free(self.allocator);
allocator::free(self.allocator, self.data);
}
mem::zero_volatile(@as_char_view(*self));
}
fn void Pem.add_header(&self, String key, String value)
{
(void)self.headers[key].free(self.allocator);
self.headers[key] = value.copy(self.allocator);
}
<*
Attempt to decode an input string into one or more `Pem` objects. If the input contains any non-PEM or otherwise
invalid data, then this will throw an error. Ideally, this function is used to decode PEM files explicitly, lest
the caller need to be sure they're only providing PEM data +/- some intermediate whitespace.
@param [&inout] allocator : "The allocator to use when creating the `Pem` outputs and members."
@param [in] input : "The string to parse one or more PEM blobs from."
@return "An array of decoded `Pem` objects, depending on how many were present in the input (separated optionally by whitespace)."
*>
fn Pem[]? decode(Allocator allocator, String input) => @pool()
{
List{Pem} pem_list;
pem_list.tinit();
String[] lines = input.treplace("\r\n", "\n").tsplit("\n");
foreach (&line : lines) *line = (*line).trim_right(); // remove any trailing whitespace as this can disrupt parsing (but shouldn't)
while (lines.len > 0)
{
pem_list.push(_decode_single(allocator, &lines)!);
while (lines.len > 0 && lines[0].trim().len == 0) lines = lines[1..]; // skip all empty lines in between or after PEM boundaries
}
return pem_list.to_array(allocator);
}
<*
INTERNAL ONLY: Decode one PEM at a time, from pre-EB to its discovered post-EB.
@param [&inout] allocator : "The allocator to use during decoding to return the result."
@param [&inout] lines_io : "A pointer to an input slice to modify as the single PEM is parsed from it."
*>
fn Pem? _decode_single(Allocator allocator, String[]* lines_io) @local
{
String[] lines = *lines_io; // copy to local var
Pem result = { .allocator = allocator };
result.headers.init(allocator);
defer catch result.free();
// Remove any preceding whitespace-only lines.
while (lines[0].trim().len == 0) lines = lines[1..];
if (lines.len < 3) return INVALID_FORMAT~; // at least 3 lines (pre-EB, body, post-EB) are always required
// The Pre-Encapsulation-Boundary must be of the format: -----BEGIN TAG-----, where "TAG" can be any upper-case identifier [A-Z_ -/]
String pre_eb = lines[0];
if (pre_eb[0:11] != PRE_EB_PREFIX || pre_eb[^5..] != EB_DELIMITER) return INVALID_PRE_EB~;
String tag = pre_eb[PRE_EB_PREFIX.len..^6];
if (!tag.len || !tag.trim().len) return MISSING_TAG~;
foreach (c : tag) if (!TAG_SET.contains(c)) return INVALID_TAG~;
result.tag = tag.copy(allocator);
// The Post-Encapsulation-Boundary is the same, but uses "END", and the extracted tag must match.
// Since the input might contain more than one PEM unit, we need to search for the ending encapsulation boundary dynamically.
String post_eb;
usz endl;
for SEARCH_EB: (endl = 1; endl < lines.len; endl++)
{
if (lines[endl].len > POST_EB_PREFIX.len && lines[endl][0:EB_DELIMITER.len] == EB_DELIMITER)
{
post_eb = lines[endl];
break SEARCH_EB;
}
}
if (!post_eb.len) return MISSING_POST_EB~;
if (post_eb[0:9] != POST_EB_PREFIX || post_eb[^5..] != EB_DELIMITER) return INVALID_POST_EB~;
String post_tag = post_eb[POST_EB_PREFIX.len..^6];
if (post_tag.len != tag.len || post_tag != tag) return MISMATCHED_TAG~;
// Now that the inner portion is decapsulated, tag is, strip off the boundaries.
*lines_io = lines[endl+1..]; // update the iterated slice of lines from the calling context - see: `decode`
lines = lines[1:endl-1];
// while there's a colon+space in the current line, we should assume that this is a key-value header pair
while (lines[0].contains(": "))
{
if (!HEADER_KEY_SET.contains(lines[0][0])) return INVALID_HEADER~; // not a multiline header? error out if the first char is not appropriate
String[] marker = lines; // temporary marker
usz span = 1; // how many lines this header spans
// Search for multi-line key-value pairs, indicated by a whitespace character beginning the current line.
for (lines = lines[1..]; lines[0].len > 0 && ascii::WHITESPACE_SET.contains(lines[0][0]); lines = lines[1..], span++);
foreach (&line : marker[:span]) *line = (*line).trim(); // always trim on both sides
String full_header = string::tjoin(marker[:span], " "); // join the lines with a single space
if (!full_header.contains(": ")) return INVALID_HEADER~; // reassert the presence of this
// Extract the key and value from the message, then validate.
// The header name should match a valid set of characters, but the value doesn't need to conform to anything other than existing
String[] kv = full_header.tsplit(": ", max: 2);
if (!kv[0].len) return MISSING_HEADER_KEY~;
if (!kv[1].len) return MISSING_HEADER_VALUE~;
foreach (c : kv[0]) if (!HEADER_KEY_SET.contains(c)) return INVALID_HEADER_KEY~;
result.add_header(kv[0], kv[1]); // finally, push the values
}
// if any headers were present, the line after the headers MUST BE EMPTY
if (result.headers.len() > 0)
{
if (lines[0].trim().len > 0) return INVALID_FORMAT~; // but we are forgiving about whitespace here
lines = lines[1..];
}
// Here, we assume lines[0] is the start of base64 data. This means there must be at least 1 line, of course.
if (lines.len < 1) return MISSING_BODY~;
// ... While the PEM format specifies a 64-character width on all but the last line of the base64 body,
// this parser doesn't need to be particular about that as long as the base64 is ok
// In this case, the rest of the lines in the set should be base64 and should decode accordingly
String to_decode = string::tjoin(lines, "");
if (!to_decode.len) return MISSING_BODY~; // paranoia
result.data = (base64::decode(allocator, to_decode) ?? INVALID_BODY~)!;
return result;
}
<*
Encodes a single `Pem` object into a new PEM-formatted string.
@param pem : "The pem object to encode"
@param [&inout] allocator : "The allocator to use for allocating the final encoded string."
*>
fn String? encode_pem(Pem pem, Allocator allocator, bool use_crlf = false)
{
if (!pem.data.len) return BODY_REQUIRED~;
if (!pem.tag.len) return TAG_REQUIRED~;
DString out;
out.tinit();
String line_ending = use_crlf ? "\r\n" : "\n";
@pool()
{
out.appendf(PRE_EB_PREFIX +++ "%s" +++ EB_DELIMITER +++ "%s", pem.tag, line_ending);
foreach KEY_ITER: (key : pem.headers.tkeys())
{
if (!key.len) return HEADER_KEY_REQUIRED~;
String value = pem.headers[key]!!;
if (!value.len) return HEADER_VALUE_REQUIRED~;
usz first_line_length = 64 - 2 - key.len;
if (value.len <= first_line_length)
{
out.appendf("%s: %s%s", key, value, line_ending);
continue KEY_ITER;
}
out.appendf("%s: %s%s", key, value[:first_line_length].trim(), line_ending);
value = value[first_line_length..];
while (value.len > 0)
{
out.appendf(" %s%s", (value.len >= 63 ? value[:63] : value[..]).trim(), line_ending);
value = value.len >= 63 ? value[63..] : {};
}
}
if (pem.headers.len() > 0) out.append(line_ending);
String body = base64::tencode(pem.data);
while (body.len > 0)
{
out.appendf("%s%s", body.len >= 64 ? body[:64] : body[..], line_ending);
body = body.len >= 64 ? body[64..] : {};
}
out.appendf(POST_EB_PREFIX +++ "%s" +++ EB_DELIMITER +++ "%s", pem.tag, line_ending);
};
return allocator == tmem ? out.str_view() : out.copy_str(allocator);
}
<*
Encodes a set of input data into a `String` containing the PEM-encoded data.
@param [&inout] allocator : "The allocator to use when creating the final output string."
@param [in] data : "The body data for the output PEM."
@param [in] tag : "The tag "
*>
fn String? encode(Allocator allocator, char[] data, String tag, PemHeader... headers, bool use_crlf = false) => @pool()
{
if (!data.len) return BODY_REQUIRED~;
return encode_pem(create(tmem, data, tag, ...headers), allocator, use_crlf);
}
<*
Encode many inputs to a single output string that represents chained/sequential PEM objects in the order they were provided.
The length of the `bodies` and `tags` array must be equal.
If headers are provided, they must be arrays of String objects, matching both the number of tags and the number of bodies.
@param [&inout] allocator : "The allocator to use when creating the final output string."
@param [in] bodies : "An ordered array of binary arrays, each representing the body of a single PEM."
@param [in] tags : "An ordered array of tag strings, each representing the tag of a single PEM."
@return "A new `String`, allocated with `allocator`, that contains all PEM objects in the order they were given."
*>
fn String? encode_many(Allocator allocator, char[][] bodies, String[] tags, PemHeader[]... pem_headers, bool use_crlf = false)
{
usz entries = max(bodies.len, tags.len, pem_headers.len);
switch
{
case bodies.len < entries: return BODY_REQUIRED~;
case tags.len < entries: return TAG_REQUIRED~;
case pem_headers.len > 0 && pem_headers.len < entries: return HEADERS_REQUIRED~;
}
DString out;
out.tinit();
if (!pem_headers.len)
{
foreach (x, body : bodies) @pool()
{
out.append(encode(tmem, body, tags[x], use_crlf: use_crlf)!);
};
}
else
{
foreach (i, headers : pem_headers) @pool()
{
out.append(encode(tmem, bodies[i], tags[i], ...headers, use_crlf: use_crlf)!);
};
}
return allocator == tmem ? out.str_view() : out.copy_str(allocator);
}

View File

@@ -7,7 +7,7 @@ struct DelayedSchedulerEvent @local
Clock execution_time;
}
fn int DelayedSchedulerEvent.compare_to(self, DelayedSchedulerEvent other) @local
fn int DelayedSchedulerEvent.compare_to(self, DelayedSchedulerEvent other)
{
switch
{

View File

@@ -4,7 +4,8 @@
module std::hash::adler32;
const uint ADLER_CONST @private = 65521;
const uint ADLER32_CONST @private = 65521;
struct Adler32
{
@@ -19,19 +20,79 @@ fn void Adler32.init(&self)
fn void Adler32.updatec(&self, char c)
{
self.a = (self.a + c) % ADLER_CONST;
self.b = (self.b + self.a) % ADLER_CONST;
self.a = (self.a + c) % ADLER32_CONST;
self.b = (self.b + self.a) % ADLER32_CONST;
}
fn void Adler32.update(&self, char[] data)
{
// Safe chunking constant which is optimized for L1 cache on most systems 32768 (32 KB).
// 0x8000 ~ (2^32 / 65521 / 2).
// The division is done so that we are guarenteed to never overflow.
const uint SAFE_CHUNKING_SIZE = 0x8000;
// In order
const uint UNROLL_SIZE = 8;
uint a = self.a;
uint b = self.b;
foreach (char x : data)
char* buf = data;
usz len = data.len;
// Align pointer traversing buffer pointer to the unroll alignment size.
if (len % UNROLL_SIZE != 0)
{
a = (a + x) % ADLER_CONST;
b = (b + a) % ADLER_CONST;
do
{
a += *buf;
b += a;
buf++;
len--;
} while (len % UNROLL_SIZE != 0);
if (a >= ADLER32_CONST)
{
a -= ADLER32_CONST;
}
b %= ADLER32_CONST;
}
// Calculate rest of adler32 checksum.
while (len > 0)
{
$for var $i = 0; $i < UNROLL_SIZE; $i++:
a += buf[$i]; b += a;
$endfor
len -= UNROLL_SIZE;
buf += UNROLL_SIZE;
// Even with 8 max value (0xFF) bytes being additioned to a (0xFF * 8 = 2040 for worst case).
// There is no chance that a will be > 2 * ADLER32_CONST, so modulo is not needed here.
// So its more performant to use subtraction.
if (a >= ADLER32_CONST)
{
a -= ADLER32_CONST;
}
// We need to periodically chunk b because it accumulates a which is a sum, so it grows rapidly.
// So every 4K of bytes we modulo in order to prevent uint integer overflow.
if (len % SAFE_CHUNKING_SIZE == 0)
{
b %= ADLER32_CONST;
}
}
// No need to explicitely modulo after loop end with ADLER32_CONST.
// As a and b are guarenteed to be under ADLER32_CONST.
// Do assert on debug.
assert(a < ADLER32_CONST);
assert(b < ADLER32_CONST);
*self = { a, b };
}
@@ -42,12 +103,10 @@ fn uint Adler32.final(&self)
fn uint hash(char[] data)
{
uint a = 1;
uint b = 0;
foreach (char x : data)
{
a = (a + x) % ADLER_CONST;
b = (b + a) % ADLER_CONST;
}
return (b << 16) | a;
Adler32 adler;
adler.init();
adler.update(data);
return adler.final();
}

View File

@@ -49,43 +49,43 @@ macro @round(r, m, $s, #v) @local
@g(r, m, $s, 7, #v[ 3], #v[ 4], #v[ 9], #v[14]);
}
macro @common_compress(#instance, $rounds, $iv, $sigma, block) @local
macro common_compress(instance, $rounds, $iv, $sigma, block) @local
{
$typeof(#instance.h[0])[16] m, v;
$typeof(instance.h[0])[16] m, v;
((char*)&m)[:$sizeof(block)] = block[..];
v[:8] = #instance.h[..];
v[:8] = instance.h[..];
v[ 8] = $iv[0];
v[ 9] = $iv[1];
v[10] = $iv[2];
v[11] = $iv[3];
v[12] = $iv[4] ^ #instance.t[0];
v[13] = $iv[5] ^ #instance.t[1];
v[14] = $iv[6] ^ #instance.f[0];
v[15] = $iv[7] ^ #instance.f[1];
v[12] = $iv[4] ^ instance.t[0];
v[13] = $iv[5] ^ instance.t[1];
v[14] = $iv[6] ^ instance.f[0];
v[15] = $iv[7] ^ instance.f[1];
$for usz $i = 0; $i < $rounds; $i++:
@round($i, m, $sigma, v);
$endfor
$for usz $i = 0; $i < 8; $i++:
#instance.h[$i] ^= v[$i] ^ v[$i + 8];
instance.h[$i] ^= v[$i] ^ v[$i + 8];
$endfor
}
macro @add_ctr(#instance, usz amount) @local
macro _add_ctr(instance, usz amount) @local
{
#instance.t[0] += ($typeof(#instance.t[0]))amount;
#instance.t[1] += ($typeof(#instance.t[0]))(#instance.t[0] < amount); // adds 1 on overflow of [0]
instance.t[0] += ($typeof(instance.t[0]))amount;
instance.t[1] += ($typeof(instance.t[0]))(instance.t[0] < amount); // adds 1 on overflow of [0]
}
macro @common_init(#instance, $ParamType, $iv, usz out_len, char[] key = {}, char[] salt = {}, char[] personal = {}) @local
macro common_init(instance, $ParamType, $iv, usz out_len, char[] key = {}, char[] salt = {}, char[] personal = {}) @local
{
mem::zero_volatile(@as_char_view(*#instance)); // explicitly because habits around hash init usually involve @noinit
mem::zero_volatile(@as_char_view(*instance)); // explicitly because habits around hash init usually involve @noinit
#instance.h[..] = $iv[..];
#instance.outlen = out_len;
instance.h[..] = $iv[..];
instance.outlen = out_len;
$ParamType p = {
.digest_length = (char)out_len,
@@ -96,59 +96,60 @@ macro @common_init(#instance, $ParamType, $iv, usz out_len, char[] key = {}, cha
if (salt.len) p.salt[:salt.len] = salt[..];
if (personal.len) p.personal[:personal.len] = personal[..];
array::@zip_into(((char*)&#instance.h)[:$sizeof(p)], ((char*)&p)[:$sizeof(p)], fn (a, b) => a ^ b); // bytes(self.h) ^= bytes(p)
array::@zip_into(((char*)&instance.h)[:$sizeof(p)], ((char*)&p)[:$sizeof(p)], fn (a, b) => a ^ b); // bytes(self.h) ^= bytes(p)
if (key.len)
{
char[$sizeof($iv[0])*16] dummy = {};
dummy[:key.len] = key[..];
#instance.update(dummy[..]); // consume a FULL block
instance.update(dummy[..]); // consume a FULL block
mem::zero_volatile(dummy[..]); // do not optimize clearing this from the stack
}
}
macro @common_update(#instance, $block_size, char[] data) @local
macro common_update(instance, $block_size, char[] data) @local
{
if (@unlikely(!data.len)) return;
usz fill = $block_size - #instance.buflen;
usz fill = $block_size - instance.buflen;
if (data.len > fill)
{
#instance.buf[#instance.buflen:fill] = data[:fill];
#instance.buflen = 0;
instance.buf[instance.buflen:fill] = data[:fill];
instance.buflen = 0;
@add_ctr(#instance, $block_size);
#instance.compress(#instance.buf);
_add_ctr(instance, $block_size);
instance._compress(instance.buf);
data = data[fill..];
for (; data.len > $block_size; data = data[$block_size..])
{
@add_ctr(#instance, $block_size);
#instance.compress(data[:$block_size]);
_add_ctr(instance, $block_size);
instance._compress(data[:$block_size]);
}
}
#instance.buf[#instance.buflen:data.len] = data[..];
#instance.buflen += data.len;
instance.buf[instance.buflen:data.len] = data[..];
instance.buflen += data.len;
}
macro @common_final(#instance, $output_length) @local
macro common_final(instance, $output_length) @local
{
char[$output_length] result = {};
if ($output_length != #instance.outlen) return result;
if ($output_length != instance.outlen) return result;
@add_ctr(#instance, #instance.buflen);
if (#instance.f[0]) return result; // technically an error return
_add_ctr(instance, instance.buflen);
if (instance.f[0]) return result; // technically an error return
if (#instance.last_node) #instance.f[1] = $typeof(#instance.h[0]).max;
#instance.f[0] = $typeof(#instance.h[0]).max;
var $max = $typeof(instance.h[0]).max;
if (instance.last_node) instance.f[1] = $max;
instance.f[0] = $max;
mem::zero_volatile(#instance.buf[#instance.buflen..]); // pad buffer with zeroes
#instance.compress(#instance.buf);
mem::zero_volatile(instance.buf[instance.buflen..]); // pad buffer with zeroes
instance._compress(instance.buf);
defer mem::zero_volatile(@as_char_view(*#instance)); // destroy the current context implicitly
defer mem::zero_volatile(@as_char_view(*instance)); // destroy the current context implicitly
result[:#instance.outlen] = @as_char_view(#instance.h)[:#instance.outlen];
result[:instance.outlen] = @as_char_view(instance.h)[:instance.outlen];
return result;
}
@@ -244,13 +245,13 @@ alias b_512 = blake2b_512;
@require !personal.ptr || (personal.len > 0 && personal.len <= BLAKE2B_PERSONALBYTES) : "A specified personalization's length must be within the proper range."
*>
fn void Blake2b.init(&self, usz out_len, char[] key = {}, char[] salt = {}, char[] personal = {})
=> @common_init(self, Blake2bParam, BLAKE2B_IV, out_len, key, salt, personal);
=> common_init(self, Blake2bParam, BLAKE2B_IV, out_len, key, salt, personal);
<*
Core compression inline function for Blake2b.
*>
fn void Blake2b.compress(&self, char[BLAKE2B_BLOCKBYTES] block) @local @inline
=> @common_compress(self, 12, BLAKE2B_IV, BLAKE2B_SIGMA, block);
fn void Blake2b._compress(&self, char[BLAKE2B_BLOCKBYTES] block) @inline
=> common_compress(self, 12, BLAKE2B_IV, BLAKE2B_SIGMA, block);
<*
Add more data to the hash context or stream.
@@ -258,7 +259,7 @@ fn void Blake2b.compress(&self, char[BLAKE2B_BLOCKBYTES] block) @local @inline
@param[in] data : "The data to ingest into the hash context."
*>
fn void Blake2b.update(&self, char[] data)
=> @common_update(self, BLAKE2B_BLOCKBYTES, data);
=> common_update(self, BLAKE2B_BLOCKBYTES, data);
<*
Finalize the hash context and return the hash result at the given size.
@@ -267,9 +268,12 @@ fn void Blake2b.update(&self, char[] data)
@require $output_length == self.outlen : "The specified compile-time output size MUST be equal to the initialized output size."
*>
macro char[*] Blake2b.final(&self, $output_length)
=> @common_final(self, $output_length);
macro char[*] Blake2b.final(&self, $output_length) => _blake2b_final{$output_length}(self);
fn char[OUTPUT_LENGTH] _blake2b_final(Blake2b* self) <OUTPUT_LENGTH> @local
{
return common_final(self, OUTPUT_LENGTH);
}
// ======================================================================================
// BEGIN Blake2s contents. Do not separate this from Blake2b: there's not really a point.
@@ -356,13 +360,13 @@ alias s_256 = blake2s_256;
@require !personal.ptr || (personal.len > 0 && personal.len <= BLAKE2B_PERSONALBYTES) : "A specified personalization's length must be within the proper range."
*>
fn void Blake2s.init(&self, usz out_len, char[] key = {}, char[] salt = {}, char[] personal = {})
=> @common_init(self, Blake2sParam, BLAKE2S_IV, out_len, key, salt, personal);
=> common_init(self, Blake2sParam, BLAKE2S_IV, out_len, key, salt, personal);
<*
Core compression inline function for Blake2s.
*>
fn void Blake2s.compress(&self, char[BLAKE2S_BLOCKBYTES] block) @local @inline
=> @common_compress(self, 10, BLAKE2S_IV, BLAKE2S_SIGMA, block);
fn void Blake2s._compress(&self, char[BLAKE2S_BLOCKBYTES] block) @inline
=> common_compress(self, 10, BLAKE2S_IV, BLAKE2S_SIGMA, block);
<*
Add more data to the hash context or stream.
@@ -370,7 +374,7 @@ fn void Blake2s.compress(&self, char[BLAKE2S_BLOCKBYTES] block) @local @inline
@param[in] data : "The data to ingest into the hash context."
*>
fn void Blake2s.update(&self, char[] data)
=> @common_update(self, BLAKE2S_BLOCKBYTES, data);
=> common_update(self, BLAKE2S_BLOCKBYTES, data);
<*
Finalize the hash context and return the hash result at the given size.
@@ -380,4 +384,4 @@ fn void Blake2s.update(&self, char[] data)
@require $output_length == self.outlen : "The specified compile-time output size MUST be equal to the initialized output size."
*>
macro char[*] Blake2s.final(&self, $output_length)
=> @common_final(self, $output_length);
=> common_final(self, $output_length);

View File

@@ -6,7 +6,7 @@
// https://github.com/BLAKE3-team/BLAKE3/blob/master
//
module std::hash::blake3;
import std::thread;
const BLOCK_SIZE = 64;
const CHUNK_SIZE = 1024;
@@ -55,12 +55,15 @@ bool cpuinfo_initd @local = false;
<*
Cache some information at runtime about the current processor and platform, as needed for optimizations.
*>
fn void init_blake3() @local @init
fn void init_blake3() @local
{
$if IS_X86:
cpudetect::x86_initialize_cpu_features(); // query all x86 feature flags, one time
$endif
cpuinfo_initd = true;
static OnceFlag run_once;
run_once.call(fn() {
$if IS_X86:
cpudetect::x86_initialize_cpu_features(); // query all x86 feature flags, one time
$endif
cpuinfo_initd = true;
});
}
<* Check whether a given CPU flag is set (x86/x86_64 only). *>
@@ -88,7 +91,7 @@ macro @simd_degree() @local
}
<* Flags used during hash computation based on its state. *>
enum Blake3Flags : const inline char
constdef Blake3Flags : inline char
{
CHUNK_START = 1 << 0,
CHUNK_END = 1 << 1,
@@ -243,7 +246,7 @@ fn void Blake3.init(&self, char[] key = {}, char explicit_flags = 0)
<*
Reset the state of the hashing context, in case it should be reused without reloading the key value.
*>
fn void Blake3.reset(&self) @local @inline
fn void _reset(Blake3* self) @local @inline
{
self.chunk.reset(self.key[..], 0);
self.cv_stack_len = 0;
@@ -252,7 +255,7 @@ fn void Blake3.reset(&self) @local @inline
<*
Private function to merge tree results.
*>
fn void Blake3.merge_cv_stack(&self, ulong total_len) @local @inline
fn void _merge_cv_stack(Blake3* self, ulong total_len) @local @inline
{
usz post_merge_stack_len = (usz)@popcnt(total_len);
for (; self.cv_stack_len > post_merge_stack_len; self.cv_stack_len--)
@@ -266,9 +269,9 @@ fn void Blake3.merge_cv_stack(&self, ulong total_len) @local @inline
<*
Private function to add a new tree onto the stack.
*>
fn void Blake3.push_cv(&self, char* new_cv, ulong chunk_counter) @local @inline
fn void Blake3.push_cv(&self, char* new_cv, ulong chunk_counter) @inline
{
self.merge_cv_stack(chunk_counter);
_merge_cv_stack(self, chunk_counter);
self.cv_stack[self.cv_stack_len * OUT_SIZE : OUT_SIZE] = new_cv[:OUT_SIZE];
self.cv_stack_len++;
}
@@ -331,7 +334,7 @@ fn void Blake3.update(&self, char[] input, bool use_tbb = false)
if (input.len > 0)
{
self.chunk.update(input);
self.merge_cv_stack(self.chunk.chunk_counter);
_merge_cv_stack(self, self.chunk.chunk_counter);
}
}
@@ -397,7 +400,7 @@ fn void Blake3.destroy(&self) @inline
@param [in] key
@param flags
*>
fn void Blake3ChunkState.init(&self, uint[] key, char flags) @local @inline
fn void Blake3ChunkState.init(&self, uint[] key, char flags) @inline
{
mem::zero_volatile(@as_char_view(*self));
self.cv[..] = key[..];
@@ -410,7 +413,7 @@ fn void Blake3ChunkState.init(&self, uint[] key, char flags) @local @inline
@param [in] key
@param chunk_counter
*>
fn void Blake3ChunkState.reset(&self, uint[] key, ulong chunk_counter) @local @inline
fn void Blake3ChunkState.reset(&self, uint[] key, ulong chunk_counter) @inline
{
self.init(key, self.flags); // maintain its own flags
self.chunk_counter = chunk_counter; // update chunk counter
@@ -419,7 +422,7 @@ fn void Blake3ChunkState.reset(&self, uint[] key, ulong chunk_counter) @local @i
<*
Get bytes length of consumed data.
*>
fn usz Blake3ChunkState.len(&self) @operator(len) @local @inline
fn usz Blake3ChunkState.len(&self) @operator(len) @inline
=> (BLOCK_SIZE * (usz)self.blocks_compressed) + (usz)self.buf_len;
<*
@@ -427,7 +430,7 @@ fn usz Blake3ChunkState.len(&self) @operator(len) @local @inline
@param [in] data : "Data to ingest."
*>
fn usz Blake3ChunkState.fill_buf(&self, char[] data) @local @inline
fn usz Blake3ChunkState.fill_buf(&self, char[] data) @inline
{
usz take = min(BLOCK_SIZE - (usz)self.buf_len, data.len);
self.buf[self.buf_len:take] = data[:take];
@@ -438,7 +441,7 @@ fn usz Blake3ChunkState.fill_buf(&self, char[] data) @local @inline
<*
Determine whether to set the CHUNK_START flag.
*>
fn char Blake3ChunkState.maybe_start_flag(&self) @local @inline
fn char Blake3ChunkState.maybe_start_flag(&self) @inline
=> !self.blocks_compressed ? Blake3Flags.CHUNK_START : 0;
<*
@@ -446,7 +449,7 @@ fn char Blake3ChunkState.maybe_start_flag(&self) @local @inline
@param [in] input : "Incoming bytes to update with."
*>
fn void Blake3ChunkState.update(&self, char[] input) @local
fn void Blake3ChunkState.update(&self, char[] input)
{
if (self.buf_len)
{
@@ -470,7 +473,7 @@ fn void Blake3ChunkState.update(&self, char[] input) @local
<*
Convert the chunk state to an "output" type with the right flags.
*>
fn Blake3Output Blake3ChunkState.output(&self) @local @inline
fn Blake3Output Blake3ChunkState.output(&self) @inline
=> make_output(self.cv[..], &self.buf, self.buf_len, self.chunk_counter, self.flags | self.maybe_start_flag() | Blake3Flags.CHUNK_END);
<*
@@ -508,7 +511,7 @@ macro Blake3Output parent_output(char* block, uint[] key, char flags) @local
@param [&inout] cv
*>
macro void Blake3Output.chaining_value(&self, char* cv) @local
macro void Blake3Output.chaining_value(&self, char* cv)
{
uint[KEY_SIZE_WORDS] cv_words;
cv_words[..] = self.input_cv[..];
@@ -522,7 +525,7 @@ macro void Blake3Output.chaining_value(&self, char* cv) @local
@param seek
@param [inout] into
*>
fn void Blake3Output.root_bytes(&self, usz seek, char[] into) @local
fn void Blake3Output.root_bytes(&self, usz seek, char[] into)
{
if (!into.len) return;
@@ -606,13 +609,11 @@ fn void compress_pre(uint[] state, uint[] cv, char[BLOCK_SIZE] block, usz block_
state[13] = (uint)(counter >> 32);
state[14] = (uint)block_len;
state[15] = (uint)flags;
@round(state, &block_words[0], 0);
@round(state, &block_words[0], 1);
@round(state, &block_words[0], 2);
@round(state, &block_words[0], 3);
@round(state, &block_words[0], 4);
@round(state, &block_words[0], 5);
@round(state, &block_words[0], 6);
for (int i = 0; i < 7; i++)
{
@round(state, &block_words[0], (usz)i);
}
}
macro compress_in_place(uint[] cv, char[BLOCK_SIZE] block, usz block_len, ulong counter, char flags) @local

View File

@@ -5,8 +5,8 @@ module std::hash::fnv32a;
typedef Fnv32a = uint;
const FNV32A_START @private = 0x811c9dc5;
const FNV32A_MUL @private = 0x01000193;
const FNV32A_START @private = (Fnv32a)0x811c9dc5;
const FNV32A_MUL @private = (Fnv32a)0x01000193;
macro void update(h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV32A_MUL;
@@ -32,10 +32,10 @@ macro void Fnv32a.update_char(&self, char c)
fn uint hash(char[] data)
{
uint h = FNV32A_START;
Fnv32a h = FNV32A_START;
foreach (char x : data)
{
update(&h, x);
}
return h;
return (uint)h;
}

View File

@@ -5,10 +5,10 @@ module std::hash::fnv64a;
typedef Fnv64a = ulong;
const FNV64A_START @private = 0xcbf29ce484222325;
const FNV64A_MUL @private = 0x00000100000001b3;
const Fnv64a FNV64A_START @private = (Fnv64a)0xcbf29ce484222325;
const Fnv64a FNV64A_MUL @private = (Fnv64a)0x00000100000001b3;
macro void update(h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV64A_MUL;
macro Fnv64a update(Fnv64a h, char x) @nodiscard @private => (h ^ (Fnv64a)x) * FNV64A_MUL;
fn void Fnv64a.init(&self)
{
@@ -20,22 +20,22 @@ fn void Fnv64a.update(&self, char[] data)
Fnv64a h = *self;
foreach (char x : data)
{
update(&h, x);
h = update(h, x);
}
*self = h;
}
macro void Fnv64a.update_char(&self, char c)
{
update(self, c);
*self = update(*self, c);
}
fn ulong hash(char[] data)
{
ulong h = FNV64A_START;
Fnv64a h = FNV64A_START;
foreach (char x : data)
{
update(&h, x);
h = update(h, x);
}
return h;
return (ulong)h;
}

View File

@@ -8,7 +8,7 @@
module std::hash::streebog;
enum StreebogLength : const inline uint
constdef StreebogLength : inline uint
{
SIZE_256 = 32,
SIZE_512 = 64,
@@ -147,11 +147,19 @@ fn void Streebog.update(&self, char[] data)
macro char[*] Streebog.final(&self, StreebogLength $hash_size)
{
char[$hash_size] result;
streebog_final_private(self);
defer mem::zero_volatile(@as_char_view(*self)); // implicitly clear the structure when finalized
result[..] = @as_char_view(self.h)[(BLOCK_SIZE - $hash_size)..];
return result;
}
fn void streebog_final_private(Streebog* self) @local
{
ulong[8] unprocessed_bits_count;
usz index = self.index >> 3;
usz shift = (self.index & 0b111) * 8;
unprocessed_bits_count[0] = self.index * 8;
unprocessed_bits_count[0] = self.index * 8ul;
self.message[index] &= ~(ulong.max << shift);
self.message[index++] ^= 1ul << shift;
if (index < 8) self.message[index..] = {};
@@ -161,9 +169,4 @@ macro char[*] Streebog.final(&self, StreebogLength $hash_size)
@add_512(self.s, self.message);
@g_n(ZERO_512, self.h, self.n);
@g_n(ZERO_512, self.h, self.s);
defer mem::zero_volatile(@as_char_view(*self)); // implicitly clear the structure when finalized
result[..] = @as_char_view(self.h)[(BLOCK_SIZE - $hash_size)..];
return result;
}
}

View File

@@ -99,6 +99,32 @@ fn char[HASH_BYTES] Md5.final(&ctx)
module std::hash::md5 @private;
const uint[64] MD5_T @private = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};
const int[16] MD5_S @private = {
7, 12, 17, 22,
5, 9, 14, 20,
4, 11, 16, 23,
6, 10, 15, 21
};
// Implementation
macro @f(x, y, z) => z ^ (x & (y ^ z));
macro @g(x, y, z) => y ^ (z & (x ^ y));
@@ -116,93 +142,54 @@ macro void @step(#f, a, b, c, d, ptr, n, t, s)
fn char* body(Md5* ctx, void* data, usz size)
{
char* ptr;
uint a, b, c, d;
uint saved_a, saved_b, saved_c, saved_d;
ptr = data;
a = ctx.a;
b = ctx.b;
c = ctx.c;
d = ctx.d;
char* ptr = data;
uint a = ctx.a;
uint b = ctx.b;
uint c = ctx.c;
uint d = ctx.d;
do
{
saved_a = a;
saved_b = b;
saved_c = c;
saved_d = d;
uint saved_a = a;
uint saved_b = b;
uint saved_c = c;
uint saved_d = d;
/* Round 1 */
@step(@f, &a, b, c, d, ptr, 0, 0xd76aa478, 7) ;
@step(@f, &d, a, b, c, ptr, 1, 0xe8c7b756, 12) ;
@step(@f, &c, d, a, b, ptr, 2, 0x242070db, 17) ;
@step(@f, &b, c, d, a, ptr, 3, 0xc1bdceee, 22) ;
@step(@f, &a, b, c, d, ptr, 4, 0xf57c0faf, 7) ;
@step(@f, &d, a, b, c, ptr, 5, 0x4787c62a, 12) ;
@step(@f, &c, d, a, b, ptr, 6, 0xa8304613, 17) ;
@step(@f, &b, c, d, a, ptr, 7, 0xfd469501, 22) ;
@step(@f, &a, b, c, d, ptr, 8, 0x698098d8, 7) ;
@step(@f, &d, a, b, c, ptr, 9, 0x8b44f7af, 12) ;
@step(@f, &c, d, a, b, ptr, 10, 0xffff5bb1, 17);
@step(@f, &b, c, d, a, ptr, 11, 0x895cd7be, 22);
@step(@f, &a, b, c, d, ptr, 12, 0x6b901122, 7) ;
@step(@f, &d, a, b, c, ptr, 13, 0xfd987193, 12);
@step(@f, &c, d, a, b, ptr, 14, 0xa679438e, 17);
@step(@f, &b, c, d, a, ptr, 15, 0x49b40821, 22);
/* Round 1 */
for (int i = 0; i < 16; i += 4)
{
@step(@f, &a, b, c, d, ptr, i + 0, MD5_T[i + 0], MD5_S[0]);
@step(@f, &d, a, b, c, ptr, i + 1, MD5_T[i + 1], MD5_S[1]);
@step(@f, &c, d, a, b, ptr, i + 2, MD5_T[i + 2], MD5_S[2]);
@step(@f, &b, c, d, a, ptr, i + 3, MD5_T[i + 3], MD5_S[3]);
}
/* Round 2 */
@step(@g, &a, b, c, d, ptr, 1, 0xf61e2562, 5) ;
@step(@g, &d, a, b, c, ptr, 6, 0xc040b340, 9) ;
@step(@g, &c, d, a, b, ptr, 11, 0x265e5a51, 14);
@step(@g, &b, c, d, a, ptr, 0, 0xe9b6c7aa, 20) ;
@step(@g, &a, b, c, d, ptr, 5, 0xd62f105d, 5) ;
@step(@g, &d, a, b, c, ptr, 10, 0x02441453, 9) ;
@step(@g, &c, d, a, b, ptr, 15, 0xd8a1e681, 14);
@step(@g, &b, c, d, a, ptr, 4, 0xe7d3fbc8, 20) ;
@step(@g, &a, b, c, d, ptr, 9, 0x21e1cde6, 5) ;
@step(@g, &d, a, b, c, ptr, 14, 0xc33707d6, 9) ;
@step(@g, &c, d, a, b, ptr, 3, 0xf4d50d87, 14) ;
@step(@g, &b, c, d, a, ptr, 8, 0x455a14ed, 20) ;
@step(@g, &a, b, c, d, ptr, 13, 0xa9e3e905, 5) ;
@step(@g, &d, a, b, c, ptr, 2, 0xfcefa3f8, 9) ;
@step(@g, &c, d, a, b, ptr, 7, 0x676f02d9, 14) ;
@step(@g, &b, c, d, a, ptr, 12, 0x8d2a4c8a, 20);
/* Round 2 */
for (int i = 0; i < 16; i += 4)
{
@step(@g, &a, b, c, d, ptr, (1 + 5 * (i + 0)) % 16, MD5_T[16 + i + 0], MD5_S[4]);
@step(@g, &d, a, b, c, ptr, (1 + 5 * (i + 1)) % 16, MD5_T[16 + i + 1], MD5_S[5]);
@step(@g, &c, d, a, b, ptr, (1 + 5 * (i + 2)) % 16, MD5_T[16 + i + 2], MD5_S[6]);
@step(@g, &b, c, d, a, ptr, (1 + 5 * (i + 3)) % 16, MD5_T[16 + i + 3], MD5_S[7]);
}
/* Round 3 */
@step(@h, &a, b, c, d, ptr, 5, 0xfffa3942, 4);
@step(@h2, &d, a, b, c, ptr, 8, 0x8771f681, 11);
@step(@h, &c, d, a, b, ptr, 11, 0x6d9d6122, 16);
@step(@h2, &b, c, d, a, ptr, 14, 0xfde5380c, 23);
@step(@h, &a, b, c, d, ptr, 1, 0xa4beea44, 4);
@step(@h2, &d, a, b, c, ptr, 4, 0x4bdecfa9, 11);
@step(@h, &c, d, a, b, ptr, 7, 0xf6bb4b60, 16);
@step(@h2, &b, c, d, a, ptr, 10, 0xbebfbc70, 23);
@step(@h, &a, b, c, d, ptr, 13, 0x289b7ec6, 4) ;
@step(@h2, &d, a, b, c, ptr, 0, 0xeaa127fa, 11) ;
@step(@h, &c, d, a, b, ptr, 3, 0xd4ef3085, 16) ;
@step(@h2, &b, c, d, a, ptr, 6, 0x04881d05, 23) ;
@step(@h, &a, b, c, d, ptr, 9, 0xd9d4d039, 4) ;
@step(@h2, &d, a, b, c, ptr, 12, 0xe6db99e5, 11) ;
@step(@h, &c, d, a, b, ptr, 15, 0x1fa27cf8, 16) ;
@step(@h2, &b, c, d, a, ptr, 2, 0xc4ac5665, 23) ;
/* Round 3 */
for (int i = 0; i < 16; i += 4)
{
@step(@h, &a, b, c, d, ptr, (5 + 3 * (i + 0)) % 16, MD5_T[32 + i + 0], MD5_S[8]);
@step(@h, &d, a, b, c, ptr, (5 + 3 * (i + 1)) % 16, MD5_T[32 + i + 1], MD5_S[9]);
@step(@h, &c, d, a, b, ptr, (5 + 3 * (i + 2)) % 16, MD5_T[32 + i + 2], MD5_S[10]);
@step(@h, &b, c, d, a, ptr, (5 + 3 * (i + 3)) % 16, MD5_T[32 + i + 3], MD5_S[11]);
}
/* Round 4 */
@step(@i, &a, b, c, d, ptr, 0, 0xf4292244, 6) ;
@step(@i, &d, a, b, c, ptr, 7, 0x432aff97, 10) ;
@step(@i, &c, d, a, b, ptr, 14, 0xab9423a7, 15) ;
@step(@i, &b, c, d, a, ptr, 5, 0xfc93a039, 21) ;
@step(@i, &a, b, c, d, ptr, 12, 0x655b59c3, 6) ;
@step(@i, &d, a, b, c, ptr, 3, 0x8f0ccc92, 10) ;
@step(@i, &c, d, a, b, ptr, 10, 0xffeff47d, 15) ;
@step(@i, &b, c, d, a, ptr, 1, 0x85845dd1, 21) ;
@step(@i, &a, b, c, d, ptr, 8, 0x6fa87e4f, 6) ;
@step(@i, &d, a, b, c, ptr, 15, 0xfe2ce6e0, 10) ;
@step(@i, &c, d, a, b, ptr, 6, 0xa3014314, 15) ;
@step(@i, &b, c, d, a, ptr, 13, 0x4e0811a1, 21) ;
@step(@i, &a, b, c, d, ptr, 4, 0xf7537e82, 6) ;
@step(@i, &d, a, b, c, ptr, 11, 0xbd3af235, 10) ;
@step(@i, &c, d, a, b, ptr, 2, 0x2ad7d2bb, 15) ;
@step(@i, &b, c, d, a, ptr, 9, 0xeb86d391, 21) ;
/* Round 4 */
for (int i = 0; i < 16; i += 4)
{
@step(@i, &a, b, c, d, ptr, (7 * (i + 0)) % 16, MD5_T[48 + i + 0], MD5_S[12]);
@step(@i, &d, a, b, c, ptr, (7 * (i + 1)) % 16, MD5_T[48 + i + 1], MD5_S[13]);
@step(@i, &c, d, a, b, ptr, (7 * (i + 2)) % 16, MD5_T[48 + i + 2], MD5_S[14]);
@step(@i, &b, c, d, a, ptr, (7 * (i + 3)) % 16, MD5_T[48 + i + 3], MD5_S[15]);
}
a += saved_a;
b += saved_b;

243
lib/std/hash/murmur.c3 Normal file
View File

@@ -0,0 +1,243 @@
module std::hash::murmur3;
<*
@param [in] data : "The data to hash"
@param seed : "The seed to use for hashing"
@require (data.len / 4) <= int.max : "Too much data"
*>
fn uint hash32(char[] data, uint seed)
{
int nblocks = (int)data.len / 4;
uint h1 = seed;
const uint C1 = 0xcc9e2d51;
const uint C2 = 0x1b873593;
uint* blocks = (uint *)(data.ptr + nblocks * 4);
for (int i = -nblocks; i != 0; i++)
{
uint k1 = getblock32(blocks, i);
k1 *= C1;
k1 = k1.rotl(15);
k1 *= C2;
h1 ^= k1;
h1 = h1.rotl(13);
h1 = h1 * 5U + 0xe6546b64;
}
char* tail = data.ptr + nblocks * 4;
uint k1;
switch (data.len & 3)
{
case 3: k1 ^= tail[2] << 16; nextcase;
case 2: k1 ^= tail[1] << 8; nextcase;
case 1: k1 ^= tail[0]; k1 *= C1; k1 = k1.rotl(15); k1 *= C2; h1 ^= k1;
}
h1 ^= (uint)data.len;
h1 = fmix32(h1);
return h1;
}
<*
@param [in] data : "The data to hash"
@param seed : "The seed to use for hashing"
@require (data.len / 16) <= int.max : "Too much data"
*>
fn uint128 hash128_64(char[] data, uint seed)
{
ulong len = data.len;
int nblocks = (int)(len / 16);
ulong h1 = seed;
ulong h2 = seed;
const ulong C1 = 0x87c37b91114253d5UL;
const ulong C2 = 0x4cf5ad432745937fUL;
ulong* blocks = (ulong*)data.ptr; // Unaligned!
for (int i = 0; i < nblocks; i++)
{
ulong k1 = getblock64(blocks, i * 2 + 0);
ulong k2 = getblock64(blocks, i * 2 + 1);
k1 *= C1; k1 = k1.rotl(31); k1 *= C2; h1 ^= k1;
h1 = h1.rotl(27); h1 += h2; h1 = h1 * 5U + 0x52dce729;
k2 *= C2; k2 = k2.rotl(33); k2 *= C1; h2 ^= k2;
h2 = h2.rotl(31); h2 += h1; h2 = h2 * 5U + 0x38495ab5;
}
char* tail = data.ptr + nblocks * 16;
ulong k1, k2;
switch (len & 15)
{
case 15: k2 ^= ((ulong)tail[14]) << 48; nextcase;
case 14: k2 ^= ((ulong)tail[13]) << 40; nextcase;
case 13: k2 ^= ((ulong)tail[12]) << 32; nextcase;
case 12: k2 ^= ((ulong)tail[11]) << 24; nextcase;
case 11: k2 ^= ((ulong)tail[10]) << 16; nextcase;
case 10: k2 ^= ((ulong)tail[ 9]) << 8; nextcase;
case 9: k2 ^= ((ulong)tail[ 8]) << 0;
k2 *= C2; k2 = k2.rotl(33); k2 *= C1; h2 ^= k2;
nextcase;
case 8: k1 ^= ((ulong)tail[ 7]) << 56; nextcase;
case 7: k1 ^= ((ulong)tail[ 6]) << 48; nextcase;
case 6: k1 ^= ((ulong)tail[ 5]) << 40; nextcase;
case 5: k1 ^= ((ulong)tail[ 4]) << 32; nextcase;
case 4: k1 ^= ((ulong)tail[ 3]) << 24; nextcase;
case 3: k1 ^= ((ulong)tail[ 2]) << 16; nextcase;
case 2: k1 ^= ((ulong)tail[ 1]) << 8; nextcase;
case 1: k1 ^= ((ulong)tail[ 0]) << 0;
k1 *= C1; k1 = k1.rotl(31); k1 *= C2; h1 ^= k1;
}
h1 ^= len;
h2 ^= len;
h1 += h2;
h2 += h1;
h1 = fmix64(h1);
h2 = fmix64(h2);
h1 += h2;
h2 += h1;
return h1 + (uint128)h2 << 64U;
}
<*
@param [in] data : "The data to hash"
@param seed : "The seed to use for hashing"
@require data.len <= uint.max : "Too much data"
*>
fn uint128 hash128_32(char[] data, uint seed)
{
uint len = data.len;
int nblocks = (int)(len / 16);
uint h1 = seed;
uint h2 = seed;
uint h3 = seed;
uint h4 = seed;
const uint C1 = 0x239b961b;
const uint C2 = 0xab0e9789;
const uint C3 = 0x38b34ae5;
const uint C4 = 0xa1e38b93;
uint* blocks = (uint *)(data.ptr + nblocks * 16);
for (int i = -nblocks; i != 0; i++)
{
uint k1 = getblock32(blocks, i * 4 + 0);
uint k2 = getblock32(blocks, i * 4 + 1);
uint k3 = getblock32(blocks, i * 4 + 2);
uint k4 = getblock32(blocks, i * 4 + 3);
k1 *= C1; k1 = k1.rotl(15); k1 *= C2; h1 ^= k1;
h1 = h1.rotl(19); h1 += h2; h1 = h1 * 5U + 0x561ccd1b;
k2 *= C2; k2 = k2.rotl(16); k2 *= C3; h2 ^= k2;
h2 = h2.rotl(17); h2 += h3; h2 = h2 * 5U + 0x0bcaa747;
k3 *= C3; k3 = k3.rotl(17); k3 *= C4; h3 ^= k3;
h3 = h3.rotl(15); h3 += h4; h3 = h3 * 5U + 0x96cd1c35;
k4 *= C4; k4 = k4.rotl(18); k4 *= C1; h4 ^= k4;
h4 = h4.rotl(13); h4 += h1; h4 = h4 * 5U + 0x32ac3b17;
}
char* tail = data.ptr + nblocks * 16;
uint k1, k2, k3, k4;
switch (len & 15)
{
case 15: k4 ^= tail[14] << 16; nextcase;
case 14: k4 ^= tail[13] << 8; nextcase;
case 13: k4 ^= tail[12] << 0;
k4 *= C4; k4 = k4.rotl(18); k4 *= C1; h4 ^= k4;
nextcase;
case 12: k3 ^= tail[11] << 24; nextcase;
case 11: k3 ^= tail[10] << 16; nextcase;
case 10: k3 ^= tail[ 9] << 8; nextcase;
case 9: k3 ^= tail[ 8] << 0;
k3 *= C3; k3 = k3.rotl(17); k3 *= C4; h3 ^= k3;
nextcase;
case 8: k2 ^= tail[ 7] << 24; nextcase;
case 7: k2 ^= tail[ 6] << 16; nextcase;
case 6: k2 ^= tail[ 5] << 8; nextcase;
case 5: k2 ^= tail[ 4] << 0;
k2 *= C2; k2 = k2.rotl(16); k2 *= C3; h2 ^= k2;
nextcase;
case 4: k1 ^= tail[ 3] << 24; nextcase;
case 3: k1 ^= tail[ 2] << 16; nextcase;
case 2: k1 ^= tail[ 1] << 8; nextcase;
case 1: k1 ^= tail[ 0] << 0;
k1 *= C1; k1 = k1.rotl(15); k1 *= C2; h1 ^= k1;
}
h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len;
h1 += h2; h1 += h3; h1 += h4;
h2 += h1; h3 += h1; h4 += h1;
h1 = fmix32(h1);
h2 = fmix32(h2);
h3 = fmix32(h3);
h4 = fmix32(h4);
h1 += h2; h1 += h3; h1 += h4;
h2 += h1; h3 += h1; h4 += h1;
return h1 + (uint128)h2 << 32U + (uint128)h3 << 64U + (uint128)h4 << 96U;
}
macro uint getblock32(uint* p, int i) @local
{
UIntLE* p_le = (UIntLE*)p + i;
return mem::load(p_le, 1).val;
}
macro ulong getblock64(ulong* p, int i) @local
{
ULongLE* p_le = (ULongLE*)p + i;
return mem::load(p_le, 1).val;
}
macro uint fmix32(uint h) @local
{
h ^= h >> 16UL;
h *= 0x85ebca6b;
h ^= h >> 13UL;
h *= 0xc2b2ae35;
h ^= h >> 16UL;
return h;
}
macro ulong fmix64(ulong k) @local
{
k ^= k >> 33U;
k *= 0xff51afd7ed558ccd;
k ^= k >> 33U;
k *= 0xc4ceb9fe1a85ec53;
k ^= k >> 33U;
return k;
}

View File

@@ -86,14 +86,14 @@ fn void Poly1305.update(&self, char[] input)
}
// ingest up to a block size to finish the partial, then advance the slice ptr
self.temp[self.num:rem] = input[:rem];
self.blocks(self.temp[..]);
_blocks(self, self.temp[..]);
input = input[rem..];
}
usz even_length = input.len - (input.len % BLOCK_SIZE);
if (even_length >= BLOCK_SIZE)
{
self.blocks(input[:even_length]); // consume blocks
_blocks(self, input[:even_length]); // consume blocks
input = input[even_length..]; // scroll to end (remainder)
}
@@ -109,7 +109,7 @@ fn char[TAG_SIZE] Poly1305.final(&self)
{
self.temp[self.num++] = 1; // partial blocks must end with 0x01
self.temp[self.num..] = {}; // explicit zeros on the rest
self.blocks(self.temp[..], 0); // chomp
_blocks(self, self.temp[..], 0); // chomp
}
uint128 t = (uint128)self.h[0] + 5;
@@ -135,7 +135,7 @@ fn char[TAG_SIZE] Poly1305.final(&self)
}
fn void Poly1305.blocks(&self, char[] input, ulong pad_bit = 1) @local
fn void _blocks(Poly1305* self, char[] input, ulong pad_bit = 1) @local
{
for (; input.len >= BLOCK_SIZE; input = input[BLOCK_SIZE..])
{

View File

@@ -68,18 +68,18 @@ fn void Sha256.update(&self, char[] data)
usz len = min(BLOCK_SIZE - buffer_pos, data.len);
self.buffer[buffer_pos:len] = data[:len];
data = data[len..];
if (buffer_pos + len == BLOCK_SIZE) self.transform();
if (buffer_pos + len == BLOCK_SIZE) _transform(self);
}
// When the data pointer is aligned, we can disregard unaligned loading in the `transform` macro.
// We do this here from the outer call to reduce the expense of checking alignment on every single block.
if (0 == (usz)data.ptr % usz.sizeof)
{
for (; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..]) self.transform((uint*)data.ptr);
for (; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..]) _transform(self, (uint*)data.ptr);
}
else
{
for (; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..]) self.transform_unaligned((uint*)data.ptr);
for (; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..]) _transform_unaligned(self, (uint*)data.ptr);
}
// Leftover data just gets stored away for the next update or final.
@@ -102,7 +102,7 @@ fn char[HASH_SIZE] Sha256.final(&self)
if (i > BLOCK_SIZE - 8)
{
self.buffer[i..] = 0x00;
self.transform();
_transform(self);
i = 0; // Reset buffer index after transformation
}
@@ -111,7 +111,7 @@ fn char[HASH_SIZE] Sha256.final(&self)
// Append the bitcount in big-endian format
*(ulong*)(&self.buffer[BLOCK_SIZE - 8]) = env::BIG_ENDIAN ??? self.bitcount : bswap(self.bitcount);
self.transform();
_transform(self);
// Convert state to the final hash
foreach (x, s : self.state) *(uint*)(&hash[x * uint.sizeof]) = env::BIG_ENDIAN ??? s : bswap(s);
@@ -121,10 +121,10 @@ fn char[HASH_SIZE] Sha256.final(&self)
// These wrappers are necessary to significantly reduce code generation from macro expansions.
// Note that transformations on `self.buffer` (when incoming == null) should always be aligned.
fn void Sha256.transform(&self, uint* incoming = null) @local @noinline => self.do_transform(incoming, true);
fn void Sha256.transform_unaligned(&self, uint* incoming = null) @local @noinline => self.do_transform(incoming, false);
fn void _transform(Sha256* self, uint* incoming = null) @local @noinline => _do_transform(self, incoming, true);
fn void _transform_unaligned(Sha256* self, uint* incoming = null) @local @noinline => _do_transform(self, incoming, false);
macro Sha256.do_transform(&self, uint* incoming = null, bool $aligned = true) @local
macro _do_transform(Sha256* self, uint* incoming = null, bool $aligned = true) @local
{
uint a, b, c, d, e, f, g, h, t1, t2 @noinit;
uint[64] m @noinit;

View File

@@ -17,9 +17,9 @@ const HASH_SIZE = 64;
struct Sha512
{
ulong length;
ulong[8] hash_state;
char[BLOCK_SIZE] buffer;
ulong length;
ulong[8] hash_state;
char[BLOCK_SIZE] buffer;
}
alias HmacSha512 = Hmac{Sha512, HASH_SIZE, BLOCK_SIZE};
@@ -36,26 +36,26 @@ macro ulong r0(ulong x) @local => (ror(x, 1) ^ ror(x, 8) ^ (x >> 7));
macro ulong r1(ulong x) @local => (ror(x, 19) ^ ror(x, 61) ^ (x >> 6));
const ulong[80] K @local = {
0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
};
@@ -63,219 +63,211 @@ const ulong[80] K @local = {
// All truncation types are simple to add onto the base SHA512 implementation at a (near) future time.
enum HashTruncationType : uint (uint truncation_width, ulong[8] initial_state)
{
SHA512 = {
512,
{
0x6a09e667f3bcc908,
0xbb67ae8584caa73b,
0x3c6ef372fe94f82b,
0xa54ff53a5f1d36f1,
0x510e527fade682d1,
0x9b05688c2b3e6c1f,
0x1f83d9abfb41bd6b,
0x5be0cd19137e2179
}
},
SHA384 = {
384,
{
0xcbbb9d5dc1059ed8,
0x629a292a367cd507,
0x9159015a3070dd17,
0x152fecd8f70e5939,
0x67332667ffc00b31,
0x8eb44a8768581511,
0xdb0c2e0d64f98fa7,
0x47b5481dbefa4fa4
}
},
SHA512_224 = {
224,
{
0x8C3D37C819544DA2,
0x73E1996689DCD4D6,
0x1DFAB7AE32FF9C82,
0x679DD514582F9FCF,
0x0F6D2B697BD44DA8,
0x77E36F7304C48942,
0x3F9D85A86A1D36C8,
0x1112E6AD91D692A1
}
},
SHA512_256 = {
256,
{
0x22312194FC2BF72C,
0x9F555FA3C84C64C2,
0x2393B86B6F53B151,
0x963877195940EABD,
0x96283EE2A88EFFE3,
0xBE5E1E2553863992,
0x2B0199FC2C85B8AA,
0x0EB72DDC81C52CA2
}
},
SHA512 { 512,
{
0x6a09e667f3bcc908,
0xbb67ae8584caa73b,
0x3c6ef372fe94f82b,
0xa54ff53a5f1d36f1,
0x510e527fade682d1,
0x9b05688c2b3e6c1f,
0x1f83d9abfb41bd6b,
0x5be0cd19137e2179
}},
SHA384 { 384,
{
0xcbbb9d5dc1059ed8,
0x629a292a367cd507,
0x9159015a3070dd17,
0x152fecd8f70e5939,
0x67332667ffc00b31,
0x8eb44a8768581511,
0xdb0c2e0d64f98fa7,
0x47b5481dbefa4fa4
}},
SHA512_224 { 224,
{
0x8C3D37C819544DA2,
0x73E1996689DCD4D6,
0x1DFAB7AE32FF9C82,
0x679DD514582F9FCF,
0x0F6D2B697BD44DA8,
0x77E36F7304C48942,
0x3F9D85A86A1D36C8,
0x1112E6AD91D692A1
}},
SHA512_256 { 256,
{
0x22312194FC2BF72C,
0x9F555FA3C84C64C2,
0x2393B86B6F53B151,
0x963877195940EABD,
0x96283EE2A88EFFE3,
0xBE5E1E2553863992,
0x2B0199FC2C85B8AA,
0x0EB72DDC81C52CA2
}},
}
<*
@param [in] data
@param [in] data
*>
fn char[HASH_SIZE] hash(char[] data)
{
Sha512 s @noinit;
s.init();
s.update(data);
return s.final();
Sha512 s @noinit;
s.init();
s.update(data);
return s.final();
}
fn void Sha512.init(&self)
{
*self = {
.hash_state = HashTruncationType.SHA512.initial_state
};
*self = {
.hash_state = HashTruncationType.SHA512.initial_state
};
}
<*
@param [in] data
@require data.len <= ulong.max
@param [in] data
@require data.len <= ulong.max
*>
fn void Sha512.update(&self, char[] data)
{
char* p = data.ptr;
ulong len = data.len;
ulong l;
ulong r = self.length % 128;
char* p = data.ptr;
ulong len = data.len;
ulong l;
ulong r = self.length % 128;
self.length += len;
self.length += len;
if (r)
{
if (len < (128 - r))
{
for (l = 0; l < len; ++l) self.buffer[r+l] = p[l];
if (r)
{
if (len < (128 - r))
{
for (l = 0; l < len; ++l) self.buffer[r+l] = p[l];
return;
}
return;
}
for (l = 0; l < 128 - r; ++l) self.buffer[r+l] = p[l];
for (l = 0; l < 128 - r; ++l) self.buffer[r+l] = p[l];
len -= (128 - r);
p = &p[128 - r];
len -= (128 - r);
p = &p[128 - r];
sha512_transform(&self.hash_state, &self.buffer);
}
sha512_transform(&self.hash_state, &self.buffer);
}
for (; len >= 128; len -= 128, p = &p[128]) sha512_transform(&self.hash_state, p);
for (; len >= 128; len -= 128, p = &p[128]) sha512_transform(&self.hash_state, p);
for (l = 0; l < len; ++l) self.buffer[l] = p[l];
for (l = 0; l < len; ++l) self.buffer[l] = p[l];
}
fn char[HASH_SIZE] Sha512.final(&self)
{
char[HASH_SIZE] hash;
char[HASH_SIZE] hash;
int i;
ulong r = self.length % 128;
int i;
ulong r = self.length % 128;
self.buffer[r++] = 0x80;
self.buffer[r++] = 0x80;
if (r > 112)
{
for (i = 0; i < 128 - r; ++i) self.buffer[r+i] = 0;
if (r > 112)
{
for (i = 0; i < 128 - r; ++i) self.buffer[r+i] = 0;
r = 0;
r = 0;
sha512_transform(&self.hash_state, &self.buffer);
}
sha512_transform(&self.hash_state, &self.buffer);
}
for (i = 0; i < 120 - r; ++i) self.buffer[r+i] = 0;
for (i = 0; i < 120 - r; ++i) self.buffer[r+i] = 0;
self.length *= 8;
self.length *= 8;
self.buffer[120] = (char)(self.length >> 56);
self.buffer[121] = (char)(self.length >> 48);
self.buffer[122] = (char)(self.length >> 40);
self.buffer[123] = (char)(self.length >> 32);
self.buffer[124] = (char)(self.length >> 24);
self.buffer[125] = (char)(self.length >> 16);
self.buffer[126] = (char)(self.length >> 8);
self.buffer[127] = (char)(self.length);
self.buffer[120] = (char)(self.length >> 56);
self.buffer[121] = (char)(self.length >> 48);
self.buffer[122] = (char)(self.length >> 40);
self.buffer[123] = (char)(self.length >> 32);
self.buffer[124] = (char)(self.length >> 24);
self.buffer[125] = (char)(self.length >> 16);
self.buffer[126] = (char)(self.length >> 8);
self.buffer[127] = (char)(self.length);
sha512_transform(&self.hash_state, &self.buffer);
sha512_transform(&self.hash_state, &self.buffer);
for (i = 0; i < 8; ++i)
{
hash[(8 * i)] = (char)(self.hash_state[i] >> 56);
hash[(8 * i) + 1] = (char)(self.hash_state[i] >> 48);
hash[(8 * i) + 2] = (char)(self.hash_state[i] >> 40);
hash[(8 * i) + 3] = (char)(self.hash_state[i] >> 32);
hash[(8 * i) + 4] = (char)(self.hash_state[i] >> 24);
hash[(8 * i) + 5] = (char)(self.hash_state[i] >> 16);
hash[(8 * i) + 6] = (char)(self.hash_state[i] >> 8);
hash[(8 * i) + 7] = (char)(self.hash_state[i]);
}
for (i = 0; i < 8; ++i)
{
hash[(8 * i)] = (char)(self.hash_state[i] >> 56);
hash[(8 * i) + 1] = (char)(self.hash_state[i] >> 48);
hash[(8 * i) + 2] = (char)(self.hash_state[i] >> 40);
hash[(8 * i) + 3] = (char)(self.hash_state[i] >> 32);
hash[(8 * i) + 4] = (char)(self.hash_state[i] >> 24);
hash[(8 * i) + 5] = (char)(self.hash_state[i] >> 16);
hash[(8 * i) + 6] = (char)(self.hash_state[i] >> 8);
hash[(8 * i) + 7] = (char)(self.hash_state[i]);
}
return hash;
return hash;
}
<*
@param [&inout] state
@param [&in] buf
@param [&inout] state
@param [&in] buf
*>
fn void sha512_transform(ulong *state, char *buf) @local
{
ulong t1, t2, a, b, c, d, e, f, g, h;
ulong[80] w;
int i;
ulong t1, t2, a, b, c, d, e, f, g, h;
ulong[80] w;
int i;
for (i = 0; i < 16; ++i)
{
w[i] = (ulong)buf[(8 * i)] << 56;
w[i] |= (ulong)buf[(8 * i) + 1] << 48;
w[i] |= (ulong)buf[(8 * i) + 2] << 40;
w[i] |= (ulong)buf[(8 * i) + 3] << 32;
w[i] |= (ulong)buf[(8 * i) + 4] << 24;
w[i] |= (ulong)buf[(8 * i) + 5] << 16;
w[i] |= (ulong)buf[(8 * i) + 6] << 8;
w[i] |= buf[(8 * i) + 7];
}
for (i = 0; i < 16; ++i)
{
w[i] = (ulong)buf[(8 * i)] << 56;
w[i] |= (ulong)buf[(8 * i) + 1] << 48;
w[i] |= (ulong)buf[(8 * i) + 2] << 40;
w[i] |= (ulong)buf[(8 * i) + 3] << 32;
w[i] |= (ulong)buf[(8 * i) + 4] << 24;
w[i] |= (ulong)buf[(8 * i) + 5] << 16;
w[i] |= (ulong)buf[(8 * i) + 6] << 8;
w[i] |= buf[(8 * i) + 7];
}
for (; i < 80; ++i) w[i] = r1(w[i - 2]) + w[i - 7] + r0(w[i - 15]) + w[i - 16];
for (; i < 80; ++i) w[i] = r1(w[i - 2]) + w[i - 7] + r0(w[i - 15]) + w[i - 16];
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
f = state[5];
g = state[6];
h = state[7];
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
f = state[5];
g = state[6];
h = state[7];
for (i = 0; i < 80; ++i)
{
t1 = h + s1(e) + ch(e, f, g) + K[i] + w[i];
t2 = s0(a) + maj(a, b, c);
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
for (i = 0; i < 80; ++i)
{
t1 = h + s1(e) + ch(e, f, g) + K[i] + w[i];
t2 = s0(a) + maj(a, b, c);
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
state[5] += f;
state[6] += g;
state[7] += h;
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
state[5] += f;
state[6] += g;
state[7] += h;
}

View File

@@ -100,7 +100,7 @@ fn void SipHash.update(&self, char[] data)
self.v[3] ^= self.m;
$for var $i = 0; $i < BLOCK_ROUNDS; ++$i : // unrolled loop
self.round();
siphash_round(self);
$endfor
self.v[0] ^= self.m;
@@ -123,7 +123,7 @@ fn OutType SipHash.final(&self)
$endif
$for var $i = 0; $i < FINALIZE_ROUNDS; ++$i : // unrolled loop
self.round();
siphash_round(self);
$endfor
$if OutType.typeid == ulong.typeid :
@@ -134,7 +134,7 @@ fn OutType SipHash.final(&self)
self.v[1] ^= 0xDD;
$for var $i = 0; $i < FINALIZE_ROUNDS; ++$i : // unrolled loop
self.round();
siphash_round(self);
$endfor
return lo | ((uint128)(self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3]) << 64);
@@ -142,7 +142,7 @@ fn OutType SipHash.final(&self)
}
fn void SipHash.round(&self) @local
fn void siphash_round(SipHash* self) @local
{
self.v[0] += self.v[1];
self.v[1] = self.v[1].rotl(13);

View File

@@ -66,14 +66,14 @@ fn void Whirlpool.update(&self, char[] data)
// If the algorithm is finalized during a 'partial' block, it has its own padding to fill the remaining gap.
if (data.len < to_pad) return;
self.process_block(&self.block);
_process_block(self, &self.block);
data = data[to_pad..];
}
// Digest blocks wholesale.
while (data.len >= BLOCK_SIZE)
{
self.process_block(data);
_process_block(self, data);
data = (data.len > BLOCK_SIZE) ? data[BLOCK_SIZE..] : {};
}
@@ -100,7 +100,7 @@ fn char[HASH_SIZE] Whirlpool.final(&self)
{
if (remainder < 64) self.block[remainder..63] = 0x00;
self.process_block(&self.block);
_process_block(self, &self.block);
remainder = 0;
}
@@ -113,7 +113,7 @@ fn char[HASH_SIZE] Whirlpool.final(&self)
self.block_128[3] = $$bswap(self.counter_low << 3);
// Process the final block.
self.process_block(&self.block);
_process_block(self, &self.block);
// Each ulong in the resultant hash should be bit-swapped before the final return.
char[HASH_SIZE] hash @align(ulong.alignof);
@@ -128,15 +128,15 @@ fn char[HASH_SIZE] Whirlpool.final(&self)
}
macro ulong @w_op(#src, $shift) @private
=> S_BOX[(0 * 256) + (int)(#src[($shift + 0) & 7] >> 56) ]
^ S_BOX[(1 * 256) + (int)(#src[($shift + 7) & 7] >> 48) & 0xFF]
^ S_BOX[(2 * 256) + (int)(#src[($shift + 6) & 7] >> 40) & 0xFF]
^ S_BOX[(3 * 256) + (int)(#src[($shift + 5) & 7] >> 32) & 0xFF]
^ S_BOX[(4 * 256) + (int)(#src[($shift + 4) & 7] >> 24) & 0xFF]
^ S_BOX[(5 * 256) + (int)(#src[($shift + 3) & 7] >> 16) & 0xFF]
^ S_BOX[(6 * 256) + (int)(#src[($shift + 2) & 7] >> 8) & 0xFF]
^ S_BOX[(7 * 256) + (int)(#src[($shift + 1) & 7] >> 0) & 0xFF];
macro ulong @w_op(#src, shift) @private
=> S_BOX[(0 * 256) + (int)(#src[(shift + 0) & 7] >> 56) ]
^ S_BOX[(1 * 256) + (int)(#src[(shift + 7) & 7] >> 48) & 0xFF]
^ S_BOX[(2 * 256) + (int)(#src[(shift + 6) & 7] >> 40) & 0xFF]
^ S_BOX[(3 * 256) + (int)(#src[(shift + 5) & 7] >> 32) & 0xFF]
^ S_BOX[(4 * 256) + (int)(#src[(shift + 4) & 7] >> 24) & 0xFF]
^ S_BOX[(5 * 256) + (int)(#src[(shift + 3) & 7] >> 16) & 0xFF]
^ S_BOX[(6 * 256) + (int)(#src[(shift + 2) & 7] >> 8) & 0xFF]
^ S_BOX[(7 * 256) + (int)(#src[(shift + 1) & 7] >> 0) & 0xFF];
const ulong[10] RC @private = {
@@ -153,31 +153,38 @@ const ulong[10] RC @private = {
};
const ROUNDS = 10;
fn void Whirlpool.process_block(&self, char* block) @local
fn void _process_block(Whirlpool* self, char* block) @local
{
ulong[2 * 8] k; // key
ulong[2 * 8] state; // state
// NOTE: These loops are unrolled with C3's Chad-tier compile-time evaluation.
$for var $round = 0; $round < 8; $round++:
k[$round] = self.hash[$round];
state[$round] = $$bswap(mem::load((ulong*)block + $round, 1)) ^ self.hash[$round];
self.hash[$round] = state[$round];
// NOTE: These loops are kept as $for to ensure initial setup is unrolled.
$for var $i = 0; $i < 8; $i++:
k[$i] = self.hash[$i];
state[$i] = $$bswap(mem::load((ulong*)block + $i, 1)) ^ self.hash[$i];
self.hash[$i] = state[$i];
$endfor
$for var $round = 0; $round < ROUNDS; ++$round :
var $m = $round % 2;
// Use regular for loops for the rounds to avoid massive code bloat. 80K less instructions.
for (int round = 0; round < ROUNDS; ++round)
{
int m = round % 2;
int next_m = m ^ 1;
ulong* pk = &k[m * 8];
ulong* nk = &k[next_m * 8];
ulong* ps = &state[m * 8];
ulong* ns = &state[next_m * 8];
k[(($m ^ 1) * 8) + 0] = @w_op((&k[$m * 8]), 0) ^ RC[$round];
nk[0] = @w_op(pk, 0) ^ RC[round];
$for var $i = 1; $i < 8; $i++ :
k[(($m ^ 1) * 8) + $i] = @w_op((&k[$m * 8]), $i);
nk[$i] = @w_op(pk, $i);
$endfor
$for var $i = 0; $i < 8; $i++ :
state[(($m ^ 1) * 8) + $i] = @w_op(&(state[$m * 8]), $i) ^ k[(($m ^ 1) * 8) + $i];
ns[$i] = @w_op(ps, $i) ^ nk[$i];
$endfor
$endfor
}
$for var $x = 0; $x < 8; $x++:
self.hash[$x] ^= state[$x];

View File

@@ -39,11 +39,16 @@ fn bool is_dir(String path)
return os::native_is_dir(path);
}
fn usz? get_size(String path)
fn ulong? get_size(String path)
{
return os::native_file_size(path);
}
fn void? set_modified_time(String path, Time_t time)
{
return os::native_set_modified_time(path, time);
}
fn void? delete(String filename)
{
return os::native_remove(filename) @inline;
@@ -63,10 +68,25 @@ fn void? File.reopen(&self, String filename, String mode)
*>
fn usz? File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
{
os::native_fseek(self.file, offset, seek_mode)!;
return os::native_ftell(self.file);
os::native_fseek(self.file, offset, (SeekOrigin)seek_mode.ordinal)!;
return (usz)os::native_ftell(self.file);
}
<*
@require self.file != null
*>
fn void? File.set_cursor(&self, long offset, SeekOrigin whence = FROM_START) @dynamic
{
return os::native_fseek(self.file, offset, whence);
}
<*
@require self.file != null
*>
fn long? File.cursor(&self) @dynamic
{
return os::native_ftell(self.file);
}
/*
Implement later
@@ -118,6 +138,14 @@ fn void? File.close(&self) @inline @dynamic
self.file = null;
}
fn ulong? File.size(&self) @dynamic
{
long curr = self.cursor()!;
defer (void)self.set_cursor(curr);
self.set_cursor(0, FROM_END)!;
return self.cursor()!;
}
<*
@require self.file != null
*>
@@ -171,9 +199,8 @@ fn char[]? load_buffer(String filename, char[] buffer)
{
File file = open(filename, "rb")!;
defer (void)file.close();
usz len = file.seek(0, END)!;
long len = file.size()!;
if (len > buffer.len) return io::OVERFLOW~;
file.seek(0, SET)!;
usz read = 0;
while (read < len)
{
@@ -187,16 +214,16 @@ fn char[]? load(Allocator allocator, String filename)
{
File file = open(filename, "rb")!;
defer (void)file.close();
usz len = file.seek(0, END)!;
file.seek(0, SET)!;
char* data = allocator::malloc_try(allocator, len)!;
ulong len = file.size()!;
if (len > usz.max) return io::OUT_OF_SPACE~;
char* data = allocator::malloc_try(allocator, (usz)len)!;
defer catch allocator::free(allocator, data);
usz read = 0;
while (read < len)
while (read < (usz)len)
{
read += file.read(data[read:len - read])!;
read += file.read(data[read:(usz)len - read])!;
}
return data[:len];
return data[:(usz)len];
}
fn char[]? load_path(Allocator allocator, Path path) => load(allocator, path.str_view());

View File

@@ -45,10 +45,9 @@ fn FileMmap? mmap_file(File file, usz offset = 0, usz len = 0, VirtualMemoryAcce
{
if (len == 0)
{
usz cur = file.seek(0, CURSOR)!;
defer file.seek(cur, SET)!!;
usz file_size = file.seek(0, END)!;
len = file_size - offset;
ulong new_len = file.size()! - offset;
if (new_len > (ulong)isz.max) return mem::OUT_OF_MEMORY~;
len = (usz)new_len;
}
// get the page size

View File

@@ -13,8 +13,7 @@ interface Printable
fn usz? to_format(Formatter* formatter) @optional;
}
faultdef BUFFER_EXCEEDED, INTERNAL_BUFFER_EXCEEDED, INVALID_FORMAT,
NOT_ENOUGH_ARGUMENTS, INVALID_ARGUMENT;
faultdef BUFFER_EXCEEDED, INTERNAL_BUFFER_EXCEEDED, INVALID_FORMAT, NOT_ENOUGH_ARGUMENTS, INVALID_ARGUMENT;
alias OutputFn = fn void?(void* buffer, char c);
alias FloatType = double;
@@ -34,14 +33,14 @@ macro bool is_struct_with_default_print($Type)
*>
macro usz? struct_to_format(value, Formatter* f, bool $force_dump)
{
var $Type = $typeof(value);
usz total = f.print("{ ")!;
$foreach $i, $member : $Type.membersof:
$if $i > 0:
total += f.print(", ")!;
$endif
$if $member.nameof != "":
total += f.printf("%s: ", $member.nameof)!;
var $Type = $typeof(value);
usz total = f.print("{ ")!;
$foreach $i, $member : $Type.membersof:
$if $i > 0:
total += f.print(", ")!;
$endif
$if $member.nameof != "":
total += f.printf("%s: ", $member.nameof)!;
$endif
$if ($force_dump &&& ($member.typeid.kindof == STRUCT || $member.typeid.kindof == BITSTRUCT)) |||
is_struct_with_default_print($member.typeid):
@@ -92,16 +91,7 @@ fn void Formatter.init(&self, OutputFn out_fn, void* data = null)
*self = { .data = data, .out_fn = out_fn};
}
fn usz? Formatter.out(&self, char c) @private
{
if (catch err = self.out_fn(self.data, c))
{
if (self.first_fault) return self.first_fault~;
self.first_fault = err;
return err~;
}
return 1;
}
fn usz? Formatter.print_with_function(&self, Printable arg)
{
@@ -116,7 +106,7 @@ fn usz? Formatter.print_with_function(&self, Printable arg)
self.width = old_width;
self.prec = old_prec;
}
if (!arg) return self.out_substr("(null)");
if (!arg) return formatter_out_substr(self, "(null)");
return arg.to_format(self);
}
if (&arg.to_constant_string)
@@ -130,208 +120,12 @@ fn usz? Formatter.print_with_function(&self, Printable arg)
self.width = old_width;
self.prec = old_prec;
}
if (!arg) return self.out_substr("(null)");
return self.out_substr(arg.to_constant_string());
if (!arg) return formatter_out_substr(self, "(null)");
return formatter_out_substr(self, arg.to_constant_string());
}
return NOT_FOUND~;
}
fn usz? Formatter.out_unknown(&self, String category, any arg) @private
{
return self.out_substr("<") + self.out_substr(category) + self.out_substr(" type:") + self.ntoa((iptr)arg.type, false, 16) + self.out_substr(", addr:") + self.ntoa((iptr)arg.ptr, false, 16) + self.out_substr(">");
}
fn usz? Formatter.out_str(&self, any arg) @private
{
switch (arg.type.kindof)
{
case VOID:
return self.out_substr("void");
case FAULT:
fault f = *(fault*)arg.ptr;
return self.out_substr(f ? f.nameof : "(empty-fault)");
case INTERFACE:
any a = *(any*)arg;
return a ? self.out_str(a) : self.out_substr("(empty-interface)");
case ANY:
any a = *(any*)arg;
return a ? self.out_str(a) : self.out_substr("(empty-any)");
case OPTIONAL:
unreachable();
case SIGNED_INT:
case UNSIGNED_INT:
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
return self.ntoa_any(arg, 10) ?? self.out_substr("<INVALID>");
case FLOAT:
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
return self.ftoa(float_from_any(arg)) ?? self.out_substr("ERR");
case BOOL:
return self.out_substr(*(bool*)arg.ptr ? "true" : "false");
default:
}
usz? n = self.print_with_function((Printable)arg);
if (try n) return n;
if (@catch(n) != NOT_FOUND) n!;
switch (arg.type.kindof)
{
case TYPEID:
return self.out_substr("typeid[")! + self.ntoa((iptr)*(typeid*)arg, false, 16)! + self.out_substr("]")!;
case ENUM:
usz i = types::any_to_enum_ordinal(arg, usz)!!;
assert(i < arg.type.names.len, "Illegal enum value found, numerical value was %d.", i);
return self.out_substr(arg.type.names[i]);
case STRUCT:
return self.out_unknown("struct", arg);
case UNION:
return self.out_unknown("union", arg);
case BITSTRUCT:
return self.out_unknown("bitstruct", arg);
case FUNC:
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.width = 0;
return self.out_substr("0x")! + self.ntoa_any(arg, 16);
case CONST_ENUM:
case DISTINCT:
if (arg.type == String.typeid)
{
return self.out_substr(*(String*)arg);
}
if (arg.type == ZString.typeid)
{
return self.out_substr(*(ZString*)arg ? ((ZString*)arg).str_view() : "(null)");
}
if (arg.type == DString.typeid)
{
return self.out_substr(*(DString*)arg ? ((DString*)arg).str_view() : "(null)");
}
return self.out_str(arg.as_inner());
case POINTER:
typeid inner = arg.type.inner;
void** pointer = arg.ptr;
if (arg.type.inner != void.typeid)
{
any deref = any_make(*pointer, inner);
n = self.print_with_function((Printable)deref);
if (try n) return n;
if (@catch(n) != NOT_FOUND) n!;
}
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.width = 0;
return self.out_substr("0x")! + self.ntoa_any(arg, 16);
case ARRAY:
// this is SomeType[*] so grab the "SomeType"
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
typeid inner = arg.type.inner;
usz size = inner.sizeof;
usz alen = arg.type.len;
// Pretend this is a String
void* ptr = (void*)arg.ptr;
usz len = self.out('[')!;
for (usz i = 0; i < alen; i++)
{
if (i != 0) len += self.out_substr(", ")!;
len += self.out_str(any_make(ptr, inner))!;
ptr += size;
}
len += self.out(']')!;
return len;
case VECTOR:
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
// this is SomeType[*] so grab the "SomeType"
typeid inner = arg.type.inner;
usz size = inner.sizeof;
usz vlen = arg.type.len;
// Pretend this is a String
void* ptr = (void*)arg.ptr;
usz len = self.out_substr("[<")!;
for (usz i = 0; i < vlen; i++)
{
if (i != 0) len += self.out_substr(", ")!;
len += self.out_str(any_make(ptr, inner))!;
ptr += size;
}
len += self.out_substr(">]")!;
return len;
case SLICE:
// this is SomeType[] so grab the "SomeType"
typeid inner = arg.type.inner;
if (inner == void.typeid) inner = char.typeid;
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
usz size = inner.sizeof;
// Pretend this is a String
String* temp = (void*)arg.ptr;
void* ptr = (void*)temp.ptr;
usz slen = temp.len;
usz len = self.out('[')!;
for (usz i = 0; i < slen; i++)
{
if (i != 0) len += self.out_substr(", ")!;
len += self.out_str(any_make(ptr, inner))!;
ptr += size;
}
len += self.out(']')!;
return len;
case ANY:
case INTERFACE:
unreachable("Already handled");
default:
}
return self.out_substr("Invalid type");
}
fn void? out_null_fn(void* data @unused, char c @unused) @private
{
@@ -339,7 +133,7 @@ fn void? out_null_fn(void* data @unused, char c @unused) @private
macro usz? @report_fault(Formatter* f, $fault)
{
(void)f.out_substr($fault);
(void)formatter_out_substr(f, $fault);
return INVALID_FORMAT~;
}
@@ -351,17 +145,19 @@ macro usz? @wrap_bad(Formatter* f, #action)
switch (err)
{
case BUFFER_EXCEEDED:
case INTERNAL_BUFFER_EXCEEDED:
return f.first_err(err)~;
default:
case INTERNAL_BUFFER_EXCEEDED:
return f.first_err(err)~;
default:
err = f.first_err(INVALID_ARGUMENT);
f.out_substr("<INVALID>")!;
return err~;
formatter_out_substr(f, "<INVALID>")!;
return err~;
}
}
return len;
}
fn usz? Formatter.vprintf(&self, String format, any[] anys)
{
self.first_fault = {};
@@ -380,7 +176,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
if (c != '%')
{
// no
total_len += self.out(c)!;
total_len += self.print_char(c)!;
continue;
}
i++;
@@ -388,7 +184,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
c = format[i];
if (c == '%')
{
total_len += self.out(c)!;
total_len += self.print_char(c)!;
continue;
}
// evaluate flags
@@ -416,7 +212,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
self.flags.left = true;
w = -w;
}
self.width = w;
self.width = (uint)w;
// evaluate precision field
self.prec = 0;
if (c == '.')
@@ -425,7 +221,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
if (++i >= format_len) return @report_fault(self, "<BAD FORMAT>");
int? prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
if (catch prec) return @report_fault(self, "<BAD FORMAT>");
self.prec = prec < 0 ? 0 : prec;
self.prec = (uint)(prec < 0 ? 0 : prec);
c = format[i];
}
@@ -434,7 +230,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
if (variant_index >= anys.len)
{
self.first_err(NOT_ENOUGH_ARGUMENTS);
total_len += self.out_substr("<MISSING>")!;
total_len += formatter_out_substr(self, "<MISSING>")!;
continue;
}
any current = anys[variant_index++];
@@ -462,83 +258,37 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
self.flags.uppercase = true;
nextcase;
case 'a':
total_len += @wrap_bad(self, self.atoa(float_from_any(current)))!;
total_len += @wrap_bad(self, formatter_atoa(self, float_from_any(current)))!;
continue;
case 'F' :
self.flags.uppercase = true;
nextcase;
case 'f':
total_len += @wrap_bad(self, self.ftoa(float_from_any(current)))!;
total_len += @wrap_bad(self, formatter_ftoa(self, float_from_any(current)))!;
continue;
case 'E':
self.flags.uppercase = true;
nextcase;
case 'e':
total_len += @wrap_bad(self, self.etoa(float_from_any(current)))!;
total_len += @wrap_bad(self, formatter_etoa(self, float_from_any(current)))!;
continue;
case 'G':
self.flags.uppercase = true;
nextcase;
case 'g':
total_len += @wrap_bad(self, self.gtoa(float_from_any(current)))!;
total_len += @wrap_bad(self, formatter_gtoa(self, float_from_any(current)))!;
continue;
case 'c':
total_len += self.out_char(current)!;
total_len += formatter_out_char(self, current)!;
continue;
case 'H':
self.flags.uppercase = true;
nextcase;
case 'h':
char[] out @noinit;
switch (current.type)
{
case char[]:
case ichar[]:
out = *(char[]*)current;
default:
if (current.type.kindof == ARRAY && (current.type.inner == char.typeid || current.type.inner == ichar.typeid))
{
out = ((char*)current.ptr)[:current.type.sizeof];
break;
}
if (current.type.kindof == POINTER)
{
out = ((*(char**)current.ptr))[:current.type.inner.sizeof];
break;
}
total_len += self.out_substr("<INVALID>")!;
continue;
}
if (self.flags.left)
{
usz len = print_hex_chars(self, out, self.flags.uppercase)!;
total_len += len;
total_len += self.pad(' ', self.width, len)!;
continue;
}
if (self.width)
{
total_len += self.pad(' ', self.width, out.len * 2)!;
}
total_len += print_hex_chars(self, out, self.flags.uppercase)!;
total_len += formatter_out_hex_buffer(self, current)!;
continue;
case 's':
if (self.flags.left)
{
usz len = self.out_str(current)!;
total_len += len;
total_len += self.pad(' ', self.width, len)!;
continue;
}
if (self.width)
{
OutputFn out_fn = self.out_fn;
self.out_fn = (OutputFn)&out_null_fn;
usz len = self.out_str(current)!;
self.out_fn = out_fn;
total_len += self.pad(' ', self.width, len)!;
}
total_len += self.out_str(current)!;
total_len += formatter_out_str_pad(self, current)!;
continue;
case 'p':
self.flags.zeropad = true;
@@ -546,7 +296,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
base = 16;
default:
self.first_err(INVALID_FORMAT);
total_len += self.out_substr("<BAD FORMAT>")!;
total_len += formatter_out_substr(self, "<BAD FORMAT>")!;
continue;
}
if (base != 10)
@@ -558,16 +308,25 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
if (self.flags.precision) self.flags.zeropad = false;
bool is_neg;
total_len += @wrap_bad(self, self.ntoa(int_from_any(current, &is_neg), is_neg, base))!;
total_len += @wrap_bad(self, formatter_ntoa(self, int_from_any(current, &is_neg), is_neg, base))!;
}
// termination
// out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
// return written chars without terminating \0
if (self.first_fault) return self.first_fault~;
return total_len;
}
fn usz? Formatter.out(&self, char c) @deprecated("Use print_char") => self.print_char(c);
fn usz? Formatter.print_char(&self, char c)
{
if (catch err = self.out_fn(self.data, c))
{
if (self.first_fault) return self.first_fault~;
self.first_fault = err;
return err~;
}
return 1;
}
fn usz? Formatter.print(&self, String str)
{
@@ -576,6 +335,6 @@ fn usz? Formatter.print(&self, String str)
// use null output function
self.out_fn = &out_null_fn;
}
foreach (c : str) self.out(c)!;
foreach (c : str) self.print_char(c)!;
return str.len;
}

View File

@@ -13,10 +13,10 @@ fn usz? print_hex_chars(Formatter* f, char[] out, bool uppercase) @inline
foreach (c : out)
{
char digit = c >> 4;
f.out(digit + (digit < 10 ? '0' : past_10))!;
f.print_char(digit + (digit < 10 ? '0' : past_10))!;
len++;
digit = c & 0xf;
f.out(digit + (digit < 10 ? '0' : past_10))!;
f.print_char(digit + (digit < 10 ? '0' : past_10))!;
len++;
}
return len;
@@ -29,10 +29,10 @@ macro fault Formatter.first_err(&self, fault f)
return f;
}
fn usz? Formatter.adjust(&self, usz len) @local
fn usz? formatter_adjust(Formatter* f, usz len) @local
{
if (!self.flags.left) return 0;
return self.pad(' ', self.width, len);
if (!f.flags.left) return 0;
return formatter_pad(f, ' ', f.width, len);
}
fn uint128? int_from_any(any arg, bool *is_neg) @private
@@ -43,8 +43,8 @@ fn uint128? int_from_any(any arg, bool *is_neg) @private
case POINTER:
*is_neg = false;
return (uint128)(uptr)*(void**)arg.ptr;
case DISTINCT:
case CONST_ENUM:
case TYPEDEF:
case CONSTDEF:
return int_from_any(arg.as_inner(), is_neg);
default:
break;
@@ -95,7 +95,7 @@ fn FloatType? float_from_any(any arg) @private
$if env::F128_SUPPORT:
if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr);
$endif
if (arg.type.kindof == DISTINCT || arg.type.kindof == CONST_ENUM)
if (arg.type.kindof == TYPEDEF || arg.type.kindof == CONSTDEF)
{
return float_from_any(arg.as_inner());
}
@@ -156,11 +156,11 @@ fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
return i;
}
fn usz? Formatter.out_substr(&self, String str) @private
fn usz? formatter_out_substr(Formatter* f, String str) @private
{
usz l = conv::utf8_codepoints(str);
uint prec = self.prec;
if (self.flags.precision && l < prec) l = prec;
uint prec = f.prec;
if (f.flags.precision && l < prec) l = prec;
usz index = 0;
usz chars = str.len;
char* ptr = str.ptr;
@@ -168,17 +168,17 @@ fn usz? Formatter.out_substr(&self, String str) @private
{
char c = ptr[index];
// Break if we have precision set and we ran out...
if (c & 0xC0 != 0x80 && self.flags.precision && !prec--) break;
self.out(c)!;
if (c & 0xC0 != 0x80 && f.flags.precision && !prec--) break;
f.print_char(c)!;
index++;
}
return index;
}
fn usz? Formatter.pad(&self, char c, isz width, isz len) @inline
fn usz? formatter_pad(Formatter* f, char c, isz width, isz len) @inline
{
isz delta = width - len;
for (isz i = 0; i < delta; i++) self.out(c)!;
for (isz i = 0; i < delta; i++) f.print_char(c)!;
return max(0, delta);
}
@@ -191,7 +191,7 @@ fn char* fmt_u(uint128 x, char* s)
fn usz? Formatter.out_chars(&self, char[] s)
{
foreach (c : s) self.out(c)!;
foreach (c : s) self.print_char(c)!;
return s.len;
}
@@ -203,12 +203,76 @@ enum FloatFormatting
HEX
}
fn usz? Formatter.etoa(&self, double y) => self.floatformat(EXPONENTIAL, y);
fn usz? Formatter.ftoa(&self, double y) => self.floatformat(FLOAT, y);
fn usz? Formatter.gtoa(&self, double y) => self.floatformat(ADAPTIVE, y);
fn usz? Formatter.atoa(&self, double y) => self.floatformat(HEX, y);
fn usz? formatter_etoa(Formatter* self, double y) => formatter_floatformat(self, EXPONENTIAL, y);
fn usz? formatter_ftoa(Formatter* self, double y) => formatter_floatformat(self, FLOAT, y);
fn usz? formatter_gtoa(Formatter* self, double y) => formatter_floatformat(self, ADAPTIVE, y);
fn usz? formatter_atoa(Formatter* self, double y) => formatter_floatformat(self, HEX, y);
fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @private
fn usz? formatter_floatformat_hex(Formatter* self, double y, bool is_neg, isz pl, isz p) @private @inline
{
double round = 8.0;
// 0x / 0X
pl += 2;
if (p > 0 && p < math::DOUBLE_MANT_DIG / 4 - 1)
{
int re = math::DOUBLE_MANT_DIG / 4 - 1 - (int)p;
round *= 1 << (math::DOUBLE_MANT_DIG % 4);
while (re--) round *= 16;
if (is_neg)
{
y = -y;
y -= round;
y += round;
y = -y;
}
else
{
y += round;
y -= round;
}
}
int e2;
y = math::frexp(y, &e2) * 2;
if (y) e2--;
char[12] ebuf0;
char* ebuf = &ebuf0[0] + 12;
char[9 + math::DOUBLE_MANT_DIG / 4] buf_array;
char* buf = &buf_array[0];
// Reverse print
char* estr = fmt_u(e2 < 0 ? (int128)-e2 : (int128)e2, ebuf);
if (estr == ebuf) *--estr = '0';
*--estr = (e2 < 0 ? '-' : '+');
*--estr = self.flags.uppercase ? 'P' : 'p';
char* s = buf;
char* xdigits = self.flags.uppercase ? &XDIGITS_H : &XDIGITS_L;
do
{
int x = (int)y;
*s++ = xdigits[x];
y = 16 * (y - x);
if (s - buf == 1 && (y || p > 0 || self.flags.hash)) *s++ = '.';
if (p >= 0 && (s - buf) >= (2 + p)) break;
} while (y);
isz outlen = s - buf;
isz explen = ebuf - estr;
if (p > int.max - 2 - explen - pl) return INTERNAL_BUFFER_EXCEEDED~;
usz len;
usz l = (usz)(p && outlen - 2 < p
? p + 2 + explen
: outlen + explen);
if (!self.flags.left && !self.flags.zeropad) len += formatter_pad(self, ' ', self.width, pl + l)!;
if (is_neg || self.flags.plus) len += self.print_char(is_neg ? '-' : '+')!;
len += self.out_chars(self.flags.uppercase ? "0X" : "0x")!;
if (self.flags.zeropad) len += formatter_pad(self, '0', self.width, pl + l)!;
len += self.out_chars(buf[:outlen])!;
len += formatter_pad(self, '0', (isz)l - outlen - explen, 0)!;
len += self.out_chars(estr[:explen])!;
if (self.flags.left) len += formatter_pad(self, ' ', self.width, pl + (isz)l)!;
return len;
}
fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, double y) @private
{
// This code is heavily based on musl's printf code
const BUF_SIZE = (math::DOUBLE_MANT_DIG + 28) / 29 + 1
@@ -226,78 +290,23 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
{
usz len;
// Add padding
if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
if (!self.flags.left) len += formatter_pad(self, ' ', self.width, 3 + pl)!;
String s = self.flags.uppercase ? "INF" : "inf";
if (math::is_nan(y)) s = self.flags.uppercase ? "NAN" : "nan";
if (pl) len += self.out(is_neg ? '-' : '+')!;
if (pl) len += self.print_char(is_neg ? '-' : '+')!;
len += self.out_chars(s)!;
if (self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
if (self.flags.left) len += formatter_pad(self, ' ', self.width, 3 + pl)!;
return len;
}
isz p = self.flags.precision ? self.prec : -1;
if (formatting == HEX) return formatter_floatformat_hex(self, y, is_neg, pl, p);
// Rescale
int e2;
y = math::frexp(y, &e2) * 2;
if (y) e2--;
char[12] ebuf0;
char* ebuf = 12 + (char*)&ebuf0;
char[9 + math::DOUBLE_MANT_DIG / 4] buf_array;
char* buf = &buf_array;
isz p = self.flags.precision ? self.prec : -1;
if (formatting == HEX)
{
double round = 8.0;
// 0x / 0X
pl += 2;
if (p > 0 && p < math::DOUBLE_MANT_DIG / 4 - 1)
{
int re = math::DOUBLE_MANT_DIG / 4 - 1 - (int)p;
round *= 1 << (math::DOUBLE_MANT_DIG % 4);
while (re--) round *= 16;
if (is_neg)
{
y = -y;
y -= round;
y += round;
y = -y;
}
else
{
y += round;
y -= round;
}
}
// Reverse print
char* estr = fmt_u(e2 < 0 ? (int128)-e2 : (int128)e2, ebuf);
if (estr == ebuf) *--estr = '0';
*--estr = (e2 < 0 ? '-' : '+');
*--estr = self.flags.uppercase ? 'P' : 'p';
char* s = buf;
char* xdigits = self.flags.uppercase ? &XDIGITS_H : &XDIGITS_L;
do
{
int x = (int)y;
*s++ = xdigits[x];
y = 16 * (y - x);
if (s - buf == 1 && (y || p > 0 || self.flags.hash)) *s++ = '.';
} while (y);
isz outlen = s - buf;
isz explen = ebuf - estr;
if (p > int.max - 2 - explen - pl) return INTERNAL_BUFFER_EXCEEDED~;
usz len;
usz l = p && outlen - 2 < p
? p + 2 + explen
: outlen + explen;
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
len += self.out_chars(self.flags.uppercase ? "0X" : "0x")!;
if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
len += self.out_chars(buf[:outlen])!;
len += self.pad('0', l - outlen - explen, 0)!;
len += self.out_chars(estr[:explen])!;
if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;
return len;
}
if (p < 0) p = 6;
if (y)
{
@@ -344,7 +353,6 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
int need = (int)(1 + (p + math::DOUBLE_MANT_DIG / 3u + 8) / 9);
for (uint* d = a; d < z; d++)
{
// CHECK THIS
uint rm = *d & ((1 << sh) - 1);
*d = (*d >> sh) + carry;
carry = (1000000000 >> sh) * rm;
@@ -377,13 +385,13 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
j %= 9;
int i;
for (i = 10, j++; j < 9; i *= 10, j++);
x = *d % i;
x = *d % (uint)i;
// Are there any significant digits past j?
if (x || (d + 1) != z)
{
double round = 2 / math::DOUBLE_EPSILON;
double small;
if (((*d / i) & 1) || (i == 1000000000 && d > a && (d[-1] & 1)))
if (((*d / (uint)i) & 1) || (i == 1000000000 && d > a && (d[-1] & 1)))
{
round += 2;
}
@@ -437,7 +445,7 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
// Count trailing zeros in last place
if (z > a && z[-1])
{
for (int i = 10, j = 0; z[-1] % i == 0; i *= 10, j++);
for (int i = 10, j = 0; z[-1] % (uint)i == 0; i *= 10, j++);
}
else
{
@@ -456,6 +464,8 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
}
if (p > int.max - 1 - (isz)(p || self.flags.hash)) return INTERNAL_BUFFER_EXCEEDED~;
int l = (int)(1 + p + (isz)(p || self.flags.hash));
char[12] ebuf0;
char* ebuf = &ebuf0[0] + 12;
char* estr @noinit;
if (formatting == FLOAT)
{
@@ -473,9 +483,13 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
}
if (l > int.max - pl) return INTERNAL_BUFFER_EXCEEDED~;
usz len;
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
if (!self.flags.left && !self.flags.zeropad) len += formatter_pad(self, ' ', self.width, pl + l)!;
if (is_neg || self.flags.plus) len += self.print_char(is_neg ? '-' : '+')!;
if (self.flags.zeropad) len += formatter_pad(self, '0', self.width, pl + l)!;
char[9] buf_array;
char* buf = &buf_array[0];
if (formatting == FLOAT)
{
if (a > r) a = r;
@@ -492,14 +506,14 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
}
len += self.out_chars(s[:buf + 9 - s])!;
}
if (p || self.flags.hash) len += self.out('.')!;
if (p || self.flags.hash) len += self.print_char('.')!;
for (; d < z && p > 0; d++, p -= 9)
{
char* s = fmt_u(*d, buf + 9);
while (s > buf) *--s = '0';
len += self.out_chars(s[:math::min((isz)9, p)])!;
}
len += self.pad('0', p + 9, 9)!;
len += formatter_pad(self, '0', p + 9, 9)!;
}
else
{
@@ -514,21 +528,42 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
}
else
{
len += self.out(s++[0])!;
if (p > 0 || self.flags.hash) len += self.out('.')!;
len += self.print_char(s++[0])!;
if (p > 0 || self.flags.hash) len += self.print_char('.')!;
}
len += self.out_chars(s[:math::min(buf + 9 - s, p)])!;
p -= buf + 9 - s;
}
len += self.pad('0', p + 18, 18)!;
len += formatter_pad(self, '0', p + 18, 18)!;
len += self.out_chars(estr[:ebuf - estr])!;
}
if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;
if (self.flags.left) len += formatter_pad(self, ' ', self.width, pl + l)!;
return len;
}
fn usz? formatter_out_str_pad(Formatter* self, any arg) @private @inline
{
usz total;
if (self.width && !self.flags.left)
{
OutputFn out_fn = self.out_fn;
self.out_fn = (OutputFn)&out_null_fn;
usz len = formatter_out_str(self, arg)!;
self.out_fn = out_fn;
total += formatter_pad(self, ' ', self.width, (isz)len)!;
}
usz len = formatter_out_str(self, arg)!;
total += len;
if (self.flags.left)
{
total += formatter_pad(self, ' ', self.width, (isz)len)!;
}
return total;
}
const char[201] DIGIT_PAIRS @private =
"00102030405060708090"
"01112131415161718191"
@@ -541,19 +576,18 @@ const char[201] DIGIT_PAIRS @private =
"08182838485868788898"
"09192939495969798999";
fn usz? Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
fn usz? formatter_ntoa(Formatter* f, uint128 value, bool negative, uint base) @private
{
char[PRINTF_NTOA_BUFFER_SIZE] buf @noinit;
usz len;
// no hash for 0 values
if (!value) self.flags.hash = false;
if (!value) f.flags.hash = false;
// write if precision != 0 or value is != 0
if (!self.flags.precision || value)
if (!f.flags.precision || value)
{
char past_10 = (self.flags.uppercase ? 'A' : 'a') - 10;
char past_10 = (f.flags.uppercase ? 'A' : 'a') - 10;
switch (base)
{
case 2:
@@ -574,10 +608,10 @@ fn usz? Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
while (value >= 10)
{
if (len + 1 >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
char digit = (char)(value % 100);
char digit = (char)(value % 100U);
buf[len:2] = DIGIT_PAIRS[2 * digit:2];
len += 2;
value /= 100;
value /= 100U;
}
if (value > 0)
{
@@ -593,33 +627,32 @@ fn usz? Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
value >>= 4;
}
while (value);
case 8:
do
{
if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
buf[len++] = '0' + (char)value & 0x7;
value >>= 3;
}
while (value);
default:
unreachable();
case 8:
do
{
if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
buf[len++] = '0' + (char)value & 0x7;
value >>= 3;
} while (value);
default:
unreachable();
}
}
return self.ntoa_format((String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
return formatter_ntoa_format(f, (String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
}
fn usz? Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint base) @private
fn usz? formatter_ntoa_format(Formatter* f, String buf, usz len, bool negative, uint base) @private
{
// pad leading zeros
if (!self.flags.left)
if (!f.flags.left)
{
if (self.width && self.flags.zeropad && (negative || self.flags.plus || self.flags.space)) self.width--;
while (len < self.prec)
if (f.width && f.flags.zeropad && (negative || f.flags.plus || f.flags.space)) f.width--;
while (len < f.prec)
{
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED~;
buf[len++] = '0';
}
while (self.flags.zeropad && len < self.width)
while (f.flags.zeropad && len < f.width)
{
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED~;
buf[len++] = '0';
@@ -627,9 +660,9 @@ fn usz? Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
}
// handle hash
if (self.flags.hash && base != 10)
if (f.flags.hash && base != 10)
{
if (!self.flags.precision && len && len == self.prec && len == self.width)
if (!f.flags.precision && len && len == f.prec && len == f.width)
{
len--;
if (len) len--;
@@ -640,11 +673,11 @@ fn usz? Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
switch (base)
{
case 16:
buf[len++] = self.flags.uppercase ? 'X' : 'x';
buf[len++] = f.flags.uppercase ? 'X' : 'x';
case 8:
buf[len++] = self.flags.uppercase ? 'O' : 'o';
buf[len++] = f.flags.uppercase ? 'O' : 'o';
case 2:
buf[len++] = self.flags.uppercase ? 'B' : 'b';
buf[len++] = f.flags.uppercase ? 'B' : 'b';
default:
unreachable();
}
@@ -657,78 +690,78 @@ fn usz? Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
case negative:
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED~;
buf[len++] = '-';
case self.flags.plus:
case f.flags.plus:
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED~;
buf[len++] = '+';
case self.flags.space:
case f.flags.space:
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED~;
buf[len++] = ' ';
}
if (len) self.out_reverse(buf[:len])!;
if (len) formatter_out_reverse(f, buf[:len])!;
return len;
}
fn usz? Formatter.ntoa_any(&self, any arg, uint base) @private
fn usz? formatter_ntoa_any(Formatter* f, any arg, uint base) @private
{
bool is_neg;
return self.ntoa(int_from_any(arg, &is_neg)!!, is_neg, base) @inline;
return formatter_ntoa(f, int_from_any(arg, &is_neg)!!, is_neg, base) @inline;
}
fn usz? Formatter.out_char(&self, any arg) @private
fn usz? formatter_out_char(Formatter* f, any arg) @private
{
if (!arg.type.kindof.is_int())
{
return self.out_substr("<NOT CHAR>");
return formatter_out_substr(f, "<NOT CHAR>");
}
usz len = 1;
// pre padding
if (!self.flags.left)
{
len += self.pad(' ', self.width, len)!;
}
if (!f.flags.left)
{
len += formatter_pad(f, ' ', f.width, len)!;
}
// char output
Char32 c = types::any_to_int(arg, uint) ?? 0xFFFD;
switch (true)
{
case c < 0x7f:
self.out((char)c)!;
f.print_char((char)c)!;
case c < 0x7ff:
self.out((char)(0xC0 | c >> 6))!;
self.out((char)(0x80 | (c & 0x3F)))!;
f.print_char((char)(0xC0 | c >> 6))!;
f.print_char((char)(0x80 | (c & 0x3F)))!;
case c < 0xffff:
self.out((char)(0xE0 | c >> 12))!;
self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
self.out((char)(0x80 | (c & 0x3F)))!;
f.print_char((char)(0xE0 | c >> 12))!;
f.print_char((char)(0x80 | (c >> 6 & 0x3F)))!;
f.print_char((char)(0x80 | (c & 0x3F)))!;
default:
self.out((char)(0xF0 | c >> 18))!;
self.out((char)(0x80 | (c >> 12 & 0x3F)))!;
self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
self.out((char)(0x80 | (c & 0x3F)))!;
f.print_char((char)(0xF0 | c >> 18))!;
f.print_char((char)(0x80 | (c >> 12 & 0x3F)))!;
f.print_char((char)(0x80 | (c >> 6 & 0x3F)))!;
f.print_char((char)(0x80 | (c & 0x3F)))!;
}
if (self.flags.left)
{
len += self.pad(' ', self.width, len)!;
if (f.flags.left)
{
len += formatter_pad(f, ' ', f.width, len)!;
}
return len;
}
fn usz? Formatter.out_reverse(&self, char[] buf) @private
fn usz? formatter_out_reverse(Formatter* f, char[] buf) @private
{
usz n;
usz len = buf.len;
// pad spaces up to given width
if (!self.flags.zeropad && !self.flags.left)
if (!f.flags.zeropad && !f.flags.left)
{
n += self.pad(' ', self.width, len)!;
n += formatter_pad(f, ' ', f.width, len)!;
}
// reverse string
while (len) n += self.out(buf[--len])!;
while (len) n += f.print_char(buf[--len])!;
// append pad spaces up to given width
n += self.adjust(n)!;
n += formatter_adjust(f, n)!;
return n;
}
@@ -748,3 +781,197 @@ fn int? printf_parse_format_field(
uint? intval = types::any_to_int(val, int);
return intval ?? BAD_FORMAT~;
}
fn usz? formatter_out_hex_buffer(Formatter* self, any arg) @private @inline
{
char[] out @noinit;
switch (arg.type)
{
case char[]:
case ichar[]:
out = *(char[]*)arg;
default:
if (arg.type.kindof == ARRAY && (arg.type.inner == char.typeid || arg.type.inner == ichar.typeid))
{
out = ((char*)arg.ptr)[:arg.type.sizeof];
break;
}
if (arg.type.kindof == POINTER)
{
// Maybe there is a more idiomatic way here
out = ((*(char**)arg.ptr))[:arg.type.inner.sizeof];
break;
}
return formatter_out_substr(self, "<INVALID>");
}
usz len = out.len * 2;
usz total;
if (self.flags.left)
{
total += print_hex_chars(self, out, self.flags.uppercase)!;
total += formatter_pad(self, ' ', self.width, (isz)total)!;
}
else
{
if (self.width) total += formatter_pad(self, ' ', self.width, (isz)len)!;
total += print_hex_chars(self, out, self.flags.uppercase)!;
}
return total;
}
fn usz? formatter_out_unknown(Formatter* self, String category, any arg) @private
{
return formatter_out_substr(self, "<")
+ formatter_out_substr(self, category)
+ formatter_out_substr(self, " type:")
+ formatter_ntoa(self, (iptr)arg.type, false, 16)
+ formatter_out_substr(self, ", addr:")
+ formatter_ntoa(self, (iptr)arg.ptr, false, 16)
+ formatter_out_substr(self, ">");
}
fn usz? formatter_out_collection(Formatter* self, any arg, String open, String close) @private
{
typeid inner = arg.type.inner;
if (inner == void.typeid) inner = char.typeid;
usz size = inner.sizeof;
usz alen;
void* data_ptr;
if (arg.type.kindof == SLICE)
{
String* temp = arg.ptr;
data_ptr = temp.ptr;
alen = temp.len;
}
else
{
data_ptr = arg.ptr;
alen = arg.type.len;
}
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
usz len = formatter_out_substr(self, open)!;
for (usz i = 0; i < alen; i++)
{
if (i != 0) len += formatter_out_substr(self, ", ")!;
len += formatter_out_str(self, any_make(data_ptr, inner))!;
data_ptr += size;
}
len += formatter_out_substr(self, close)!;
return len;
}
fn usz? formatter_out_str(Formatter* self, any arg) @private
{
switch (arg.type.kindof)
{
case VOID:
return formatter_out_substr(self, "void");
case FAULT:
fault f = *(fault*)arg.ptr;
return formatter_out_substr(self, f ? f.nameof : "(empty-fault)");
case INTERFACE:
any a = *(any*)arg;
return a ? formatter_out_str(self, a) : formatter_out_substr(self, "(empty-interface)");
case ANY:
any a = *(any*)arg;
return a ? formatter_out_str(self, a) : formatter_out_substr(self, "(empty-any)");
case OPTIONAL:
unreachable();
case SIGNED_INT:
case UNSIGNED_INT:
case FLOAT:
case FUNC:
case POINTER:
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
switch (arg.type.kindof)
{
case SIGNED_INT:
case UNSIGNED_INT:
return formatter_ntoa_any(self, arg, 10) ?? formatter_out_substr(self, "<INVALID>");
case FLOAT:
return formatter_ftoa(self, float_from_any(arg)) ?? formatter_out_substr(self, "ERR");
case FUNC:
case POINTER:
if (arg.type.kindof == POINTER && arg.type.inner != void.typeid)
{
void** pointer = arg.ptr;
any deref = any_make(*pointer, arg.type.inner);
usz? n = self.print_with_function((Printable)deref);
if (try n) return n;
if (@catch(n) != NOT_FOUND) n!;
}
return formatter_out_substr(self, "0x")! + formatter_ntoa_any(self, arg, 16);
default:
unreachable();
}
case BOOL:
return formatter_out_substr(self, *(bool*)arg.ptr ? "true" : "false");
default:
}
usz? n = self.print_with_function((Printable)arg);
if (try n) return n;
if (@catch(n) != NOT_FOUND) n!;
switch (arg.type.kindof)
{
case TYPEID:
return formatter_out_substr(self, "typeid[")! + formatter_ntoa(self, (iptr)*(typeid*)arg, false, 16)! + formatter_out_substr(self, "]")!;
case ENUM:
usz i = types::any_to_enum_ordinal(arg, usz)!!;
assert(i < arg.type.names.len, "Illegal enum value found, numerical value was %d.", i);
return formatter_out_substr(self, arg.type.names[i]);
case STRUCT:
return formatter_out_unknown(self, "struct", arg);
case UNION:
return formatter_out_unknown(self, "union", arg);
case BITSTRUCT:
return formatter_out_unknown(self, "bitstruct", arg);
case CONSTDEF:
case TYPEDEF:
if (arg.type == String.typeid)
{
return formatter_out_substr(self, *(String*)arg);
}
if (arg.type == ZString.typeid)
{
return formatter_out_substr(self, *(ZString*)arg ? ((ZString*)arg).str_view() : "(null)");
}
if (arg.type == DString.typeid)
{
return formatter_out_substr(self, *(DString*)arg ? ((DString*)arg).str_view() : "(null)");
}
return formatter_out_str(self, arg.as_inner());
case ARRAY:
return formatter_out_collection(self, arg, "[", "]");
case VECTOR:
return formatter_out_collection(self, arg, "[<", ">]");
case SLICE:
return formatter_out_collection(self, arg, "[", "]");
case ANY:
case INTERFACE:
unreachable("Already handled");
default:
}
return formatter_out_substr(self, "Invalid type");
}

View File

@@ -11,6 +11,14 @@ enum Seek
END
}
enum SeekOrigin
{
FROM_START,
FROM_CURSOR,
FROM_END
}
faultdef
ALREADY_EXISTS,
BUSY,
@@ -50,31 +58,17 @@ faultdef
or to the end of the stream, whatever comes first.
"\r" will be filtered from the String.
@param [&inout] allocator : "The allocator used to allocate the read string."
@param stream : `The stream to read from.`
@param limit : `Optionally limits the amount of bytes to read in a single line. Will NOT discard the remaining line data.`
@require @is_not_instream_if_ptr(stream) : "The value for 'stream' should have been passed as a pointer and not as a value, please add '&'."
@require @is_instream(stream) : `Make sure that the stream is actually an InStream.`
@param [inout] allocator : `the allocator to use.`
@return `The string containing the data read.`
*>
macro String? readline(Allocator allocator, stream = io::stdin())
macro String? readline(Allocator allocator, stream = io::stdin(), usz limit = 0)
{
return readline_impl{$typeof(stream)}(allocator, stream);
}
fn String? readline_impl(Allocator allocator, Stream stream) <Stream> @private
{
if (allocator == tmem)
{
DString str = dstring::temp_with_capacity(256);
readline_to_stream(&str, stream)!;
return str.str_view();
}
@pool()
{
DString str = dstring::temp_with_capacity(256);
readline_to_stream(&str, stream)!;
return str.copy_str(allocator);
};
return readline_impl{$typeof(stream)}(allocator, stream, limit);
}
<*
@@ -82,13 +76,30 @@ fn String? readline_impl(Allocator allocator, Stream stream) <Stream> @private
on the temporary allocator and does not need to be freed.
@param stream : `The stream to read from.`
@param limit : `Optionally limits the amount of bytes to read in a single line. Will NOT discard the remaining line data.`
@require @is_not_instream_if_ptr(stream) : "The value for 'stream' should have been passed as a pointer and not as a value, please add '&'."
@require @is_instream(stream) : `The stream must implement InStream.`
@return `The temporary string containing the data read.`
*>
macro String? treadline(stream = io::stdin())
macro String? treadline(stream = io::stdin(), usz limit = 0)
{
return readline(tmem, stream) @inline;
return readline(tmem, stream, limit) @inline;
}
fn String? readline_impl(Allocator allocator, Stream stream, usz limit) <Stream> @private
{
if (allocator == tmem)
{
DString str = dstring::temp_with_capacity(256);
readline_to_stream(&str, stream, limit)!;
return str.str_view();
}
@pool()
{
DString str = dstring::temp_with_capacity(256);
readline_to_stream(&str, stream, limit)!;
return str.copy_str(allocator);
};
}
<*
@@ -96,18 +107,19 @@ macro String? treadline(stream = io::stdin())
@param out_stream : `The stream to write to`
@param in_stream : `The stream to read from.`
@param limit : `Optionally limits the byte-length of the allocated output string.`
@require @is_not_instream_if_ptr(in_stream) : "The value for 'in_stream' should have been passed as a pointer and not as a value, please add '&'."
@require @is_not_outstream_if_ptr(out_stream) : "The value for 'out_stream' should have been passed as a pointer and not as a value, please add '&'."
@require @is_instream(in_stream) : `The in_stream must implement InStream.`
@require @is_outstream(out_stream) : `The out_stream must implement OutStream.`
@return `The number of bytes written`
@return `The number of bytes written. When a 'limit' is provided and the return value is equal to it, there may be more to read on the current line.`
*>
macro usz? readline_to_stream(out_stream, in_stream = io::stdin())
macro usz? readline_to_stream(out_stream, in_stream = io::stdin(), usz limit = 0)
{
return readline_to_stream_impl{$typeof(in_stream), $typeof(out_stream)}(out_stream, in_stream);
return readline_to_stream_impl{$typeof(in_stream), $typeof(out_stream)}(out_stream, in_stream, limit);
}
fn usz? readline_to_stream_impl(OStream out_stream, IStream in_stream) <IStream, OStream> @private
fn usz? readline_to_stream_impl(OStream out_stream, IStream in_stream, usz limit) <IStream, OStream> @private
{
bool $is_stream = IStream == InStream;
$if $is_stream:
@@ -132,7 +144,7 @@ fn usz? readline_to_stream_impl(OStream out_stream, IStream in_stream) <IStream,
$endif
len++;
}
while (1)
while (!limit || len < limit)
{
$if $is_stream:
char? c = func((void*)in_stream);
@@ -354,7 +366,7 @@ fn usz? printfn(String format, args...) @format(0) @maydiscard
PutcharBuffer buff;
formatter.init(&out_putchar_buffer_fn, &buff);
usz? len = formatter.vprintf(format, args);
formatter.out('\n')!;
formatter.print_char('\n')!;
write_putchar_buffer(&buff, true)!;
return len + 1;
}

View File

@@ -5,22 +5,22 @@ import libc;
@require mode.len > 0
@require filename.len > 0
*>
fn void*? native_fopen(String filename, String mode) @inline => @pool()
fn void*? native_fopen(String filename, String mode) @inline => @stack_mem(256; Allocator smem)
{
$if env::WIN32:
void* file = libc::_wfopen(filename.to_temp_wstring(), mode.to_temp_wstring())!;
void* file = libc::_wfopen(filename.to_wstring(smem), mode.to_wstring(smem))!;
$else
void* file = libc::fopen(filename.zstr_tcopy(), mode.zstr_tcopy());
void* file = libc::fopen(filename.zstr_copy(smem), mode.zstr_copy(smem));
$endif
return file ?: file_open_errno()~;
return file ?: file_open_errno()~;
}
fn void? native_remove(String filename) => @pool()
fn void? native_remove(String filename) => @stack_mem(256; Allocator smem)
{
$if env::WIN32:
CInt result = libc::_wremove(filename.to_temp_wstring())!;
CInt result = libc::_wremove(filename.to_wstring(smem))!;
$else
CInt result = libc::remove(filename.zstr_tcopy());
CInt result = libc::remove(filename.zstr_copy(smem));
$endif
if (result)
{
@@ -39,26 +39,26 @@ fn void? native_remove(String filename) => @pool()
@require mode.len > 0
@require filename.len > 0
*>
fn void*? native_freopen(void* file, String filename, String mode) @inline => @pool()
fn void*? native_freopen(void* file, String filename, String mode) @inline => @stack_mem(256; Allocator smem)
{
$if env::WIN32:
file = libc::_wfreopen(filename.to_temp_wstring(), mode.to_temp_wstring(), file)!;
file = libc::_wfreopen(filename.to_wstring(smem), mode.to_wstring(smem), file)!;
$else
file = libc::freopen(filename.zstr_tcopy(), mode.zstr_tcopy(), file);
file = libc::freopen(filename.zstr_copy(smem), mode.zstr_copy(smem), file);
$endif
return file ?: file_open_errno()~;
}
fn void? native_fseek(void* file, isz offset, Seek seek_mode) @inline
fn void? native_fseek(void* file, long offset, SeekOrigin seek_mode) @inline
{
if (libc::fseek(file, (SeekIndex)offset, seek_mode.ordinal)) return file_seek_errno()~;
}
fn usz? native_ftell(CFile file) @inline
fn long? native_ftell(CFile file) @inline
{
long index = libc::ftell(file);
return index >= 0 ? (usz)index : file_seek_errno()~;
return index >= 0 ? index : file_seek_errno()~;
}
fn usz? native_fwrite(CFile file, char[] buffer) @inline
@@ -76,6 +76,11 @@ fn usz? native_fread(CFile file, char[] buffer) @inline
return libc::fread(buffer.ptr, 1, buffer.len, file);
}
fn void? native_fflush(CFile file) @inline @maydiscard
{
if (libc::fflush(file) != 0) return io::GENERAL_ERROR~;
}
macro fault file_open_errno() @local
{
switch (libc::errno())
@@ -123,3 +128,22 @@ macro fault file_seek_errno() @local
}
}
struct Utimbuf
{
Time_t actime;
Time_t modtime;
}
extern fn int utime(char* filename, void* times) @if(!env::WIN32);
extern fn int _wutime(WChar* filename, void* times) @if(env::WIN32);
fn void? native_set_modified_time(String filename, libc::Time_t time) => @stack_mem(256; Allocator smem)
{
Utimbuf times = { time, time };
$if env::WIN32:
if (_wutime(filename.to_wstring(smem)!, &times)) return io::GENERAL_ERROR~;
$else
if (utime(filename.zstr_copy(smem), &times)) return io::GENERAL_ERROR~;
$endif
}

View File

@@ -4,12 +4,14 @@ import libc;
alias FopenFn = fn void*?(String, String);
alias FreopenFn = fn void*?(void*, String, String);
alias FcloseFn = fn void?(void*);
alias FseekFn = fn void?(void*, isz, Seek);
alias FtellFn = fn usz?(void*);
alias FseekFn = fn void?(void*, long, SeekOrigin);
alias FtellFn = fn long?(void*);
alias FwriteFn = fn usz?(void*, char[] buffer);
alias FreadFn = fn usz?(void*, char[] buffer);
alias FflushFn = fn void?(void*);
alias RemoveFn = fn void?(String);
alias FputcFn = fn void?(int, void*);
alias SetModifiedTimeFn = fn void?(String, libc::Time_t);
FopenFn native_fopen_fn @weak @if(!$defined(native_fopen_fn));
FcloseFn native_fclose_fn @weak @if(!$defined(native_fclose_fn));
@@ -18,8 +20,10 @@ FseekFn native_fseek_fn @weak @if(!$defined(native_fseek_fn));
FtellFn native_ftell_fn @weak @if(!$defined(native_ftell_fn));
FwriteFn native_fwrite_fn @weak @if(!$defined(native_fwrite_fn));
FreadFn native_fread_fn @weak @if(!$defined(native_fread_fn));
FflushFn native_fflush_fn @weak @if(!$defined(native_fflush_fn));
RemoveFn native_remove_fn @weak @if(!$defined(native_remove_fn));
FputcFn native_fputc_fn @weak @if(!$defined(native_fputc_fn));
SetModifiedTimeFn native_set_modified_time_fn @weak @if(!$defined(native_set_modified_time_fn));
<*
@require mode.len > 0
@@ -52,13 +56,13 @@ fn void*? native_freopen(void* file, String filename, String mode) @inline
return io::UNSUPPORTED_OPERATION~;
}
fn void? native_fseek(void* file, isz offset, Seek seek_mode) @inline
fn void? native_fseek(void* file, long offset, SeekOrigin whence) @inline
{
if (native_fseek_fn) return native_fseek_fn(file, offset, seek_mode);
if (native_fseek_fn) return native_fseek_fn(file, offset, whence);
return io::UNSUPPORTED_OPERATION~;
}
fn usz? native_ftell(CFile file) @inline
fn ulong? native_ftell(CFile file) @inline
{
if (native_ftell_fn) return native_ftell_fn(file);
return io::UNSUPPORTED_OPERATION~;
@@ -76,8 +80,20 @@ fn usz? native_fread(CFile file, char[] buffer) @inline
return io::UNSUPPORTED_OPERATION~;
}
fn void? native_fflush(CFile file) @inline @maydiscard
{
if (native_fflush_fn) return native_fflush_fn(file);
return io::UNSUPPORTED_OPERATION~;
}
fn void? native_fputc(CInt c, CFile stream) @inline
{
if (native_fputc_fn) return native_fputc_fn(c, stream);
return io::UNSUPPORTED_OPERATION~;
}
fn void? native_set_modified_time(String filename, libc::Time_t time) @inline
{
if (native_set_modified_time_fn) return native_set_modified_time_fn(filename, time);
return io::UNSUPPORTED_OPERATION~;
}

View File

@@ -47,14 +47,15 @@ fn usz? native_file_size(String path) @if(env::WIN32) => @pool()
return (usz)size.quadPart;
}
fn usz? native_file_size(String path) @if(!env::WIN32 && !env::DARWIN && !env::LINUX && !env::ANDROID && !env::BSD_FAMILY)
fn ulong? native_file_size(String path) @if(!env::WIN32 && !env::DARWIN && !env::LINUX && !env::ANDROID && !env::BSD_FAMILY)
{
File f = file::open(path, "r")!;
defer (void)f.close();
return f.seek(0, Seek.END)!;
f.set_cursor(0, FROM_END)!;
return f.cursor();
}
fn usz? native_file_size(String path) @if(env::DARWIN || env::LINUX || env::ANDROID || env::BSD_FAMILY)
fn ulong? native_file_size(String path) @if(env::DARWIN || env::LINUX || env::ANDROID || env::BSD_FAMILY)
{
Stat stat;
native_stat(&stat, path)!;

View File

@@ -36,7 +36,7 @@ fn Path? cwd(Allocator allocator)
fn bool is_dir(Path path) => os::native_is_dir(path.str_view());
fn bool is_file(Path path) => os::native_is_file(path.str_view());
fn usz? file_size(Path path) => os::native_file_size(path.str_view());
fn ulong? file_size(Path path) => os::native_file_size(path.str_view());
fn bool exists(Path path) => os::native_file_or_dir_exists(path.str_view());
fn Path? tcwd() => cwd(tmem) @inline;

View File

@@ -1,12 +1,20 @@
module std::io;
import std::math;
alias SetCursorFn = fn void?(void*, long offset, SeekOrigin whence = START);
interface InStream
{
fn void? close() @optional;
fn long? cursor() @optional;
fn void? set_cursor(long offset, SeekOrigin whence = FROM_START) @optional;
fn usz? seek(isz offset, Seek seek) @optional;
fn usz len() @optional;
fn usz? available() @optional;
fn ulong? size() @optional;
fn ulong? available() @optional;
fn usz? read(char[] buffer);
fn char? read_byte();
fn usz? write_to(OutStream out) @optional;
@@ -24,15 +32,23 @@ interface OutStream
fn usz? read_to(InStream in) @optional;
}
fn usz? available(InStream s)
fn ulong? available(InStream s)
{
if (&s.available) return s.available();
if (&s.set_cursor && &s.cursor)
{
long curr = s.cursor()!;
s.set_cursor(0, FROM_END)!;
ulong len = s.cursor()!;
s.set_cursor(curr)!;
return len - curr;
}
if (&s.seek)
{
usz curr = s.seek(0, Seek.CURSOR)!;
usz len = s.seek(0, Seek.END)!;
s.seek(curr, Seek.SET)!;
return len - curr;
return (ulong)len - (ulong)curr;
}
return io::UNSUPPORTED_OPERATION~;
}
@@ -177,6 +193,11 @@ macro usz? write_using_write_byte(s, char[] bytes)
macro void? pushback_using_seek(s)
{
if (&s.set_cursor)
{
s.set_cursor(-1, FROM_CURSOR)!;
return;
}
s.seek(-1, CURSOR)!;
}
@@ -407,11 +428,11 @@ macro ulong? read_le_ulong(stream)
{
ulong val = (ulong)stream.read_byte()!;
val += (ulong)stream.read_byte()! << 8;
val += (ulong)stream.read_byte()! << 16;
val += (ulong)stream.read_byte()! << 16;
val += (ulong)stream.read_byte()! << 24;
val += (ulong)stream.read_byte()! << 32;
val += (ulong)stream.read_byte()! << 40;
val += (ulong)stream.read_byte()! << 48;
val += (ulong)stream.read_byte()! << 48;
return val + (ulong)stream.read_byte()! << 56;
}
@@ -621,24 +642,30 @@ macro void? skip(stream, usz bytes)
{
if (!bytes) return;
$switch:
$case !$defined(stream.seek):
for (usz i = 0; i < bytes; i++)
{
stream.read()!;
}
return;
$case $typeof(stream) == InStream:
if (!&stream.seek)
{
for (usz i = 0; i < bytes; i++)
{
stream.read()!;
}
return;
}
if (!&stream.seek && !&stream.set_cursor)
{
for (usz i = 0; i < bytes; i++)
{
stream.read()!;
}
return;
}
if (!&stream.set_cursor)
{
stream.seek(bytes, CURSOR)!;
return;
}
stream.set_cursor(bytes, FROM_CURSOR)!;
$case $defined(stream.set_cursor):
stream.set_cursor(bytes, FROM_CURSOR)!;
$case $defined(stream.seek):
stream.seek(bytes, CURSOR)!;
$default:
stream.seek(bytes, CURSOR)!;
for (usz i = 0; i < bytes; i++)
{
stream.read()!;
}
$endswitch
}

View File

@@ -38,7 +38,7 @@ fn usz? ReadBuffer.read(&self, char[] bytes) @dynamic
// Read directly into the input buffer.
return self.wrapped_stream.read(bytes)!;
}
self.refill()!;
readbuffer_refill(self)!;
}
usz n = min(self.write_idx - self.read_idx, bytes.len);
bytes[:n] = self.bytes[self.read_idx:n];
@@ -48,14 +48,14 @@ fn usz? ReadBuffer.read(&self, char[] bytes) @dynamic
fn char? ReadBuffer.read_byte(&self) @dynamic
{
if (self.read_idx == self.write_idx) self.refill()!;
if (self.read_idx == self.write_idx) readbuffer_refill(self)!;
if (self.read_idx == self.write_idx) return io::EOF~;
char c = self.bytes[self.read_idx];
self.read_idx++;
return c;
}
fn void? ReadBuffer.refill(&self) @local @inline
fn void? readbuffer_refill(ReadBuffer* self) @local @inline
{
self.read_idx = 0;
self.write_idx = self.wrapped_stream.read(self.bytes)!;
@@ -92,7 +92,7 @@ fn void? WriteBuffer.close(&self) @dynamic
fn void? WriteBuffer.flush(&self) @dynamic
{
self.write_pending()!;
write_buffer_write_pending(self)!;
if (&self.wrapped_stream.flush) self.wrapped_stream.flush()!;
}
@@ -106,7 +106,7 @@ fn usz? WriteBuffer.write(&self, char[] bytes) @dynamic
self.index += bytes.len;
return bytes.len;
}
self.write_pending()!;
write_buffer_write_pending(self)!;
if (bytes.len >= self.bytes.len)
{
// Write directly to the stream.
@@ -123,13 +123,13 @@ fn void? WriteBuffer.write_byte(&self, char c) @dynamic
usz n = self.bytes.len - self.index;
if (n == 0)
{
self.write_pending()!;
write_buffer_write_pending(self)!;
}
self.bytes[self.index] = c;
self.index += 1;
}
fn void? WriteBuffer.write_pending(&self) @local
fn void? write_buffer_write_pending(WriteBuffer* self) @local
{
self.index -= self.wrapped_stream.write(self.bytes[:self.index])!;
if (self.index != 0) return INCOMPLETE_WRITE~;

View File

@@ -104,28 +104,37 @@ fn void? ByteBuffer.pushback_byte(&self) @dynamic
self.has_last = false;
}
fn usz? ByteBuffer.seek(&self, isz offset, Seek seek) @dynamic
fn long? ByteBuffer.cursor(&self) @dynamic
{
switch (seek)
{
case SET:
if (offset < 0 || offset > self.write_idx) return INVALID_POSITION~;
self.read_idx = offset;
return offset;
case CURSOR:
if ((offset < 0 && self.read_idx < -offset) ||
(offset > 0 && self.read_idx + offset > self.write_idx)) return INVALID_POSITION~;
self.read_idx += offset;
case END:
if (offset < 0 || offset > self.write_idx) return INVALID_POSITION~;
self.read_idx = self.write_idx - offset;
}
return self.read_idx;
}
fn usz? ByteBuffer.available(&self) @inline @dynamic
fn void? ByteBuffer.set_cursor(&self, long offset, SeekOrigin whence = FROM_START) @dynamic
{
return self.write_idx - self.read_idx;
switch (whence)
{
case FROM_START:
if (offset < 0 || offset > self.write_idx) return INVALID_POSITION~;
self.read_idx = (usz)offset;
case FROM_CURSOR:
if ((offset < 0 && self.read_idx < -offset) ||
(offset > 0 && self.read_idx + offset > self.write_idx)) return INVALID_POSITION~;
self.read_idx += (usz)offset;
case FROM_END:
if (offset < 0 || offset > self.write_idx) return INVALID_POSITION~;
self.read_idx = self.write_idx - (usz)offset;
}
}
fn usz? ByteBuffer.seek(&self, isz offset, Seek seek) @dynamic
{
self.set_cursor(offset, (SeekOrigin)seek.ordinal)!;
return (usz)self.cursor();
}
fn ulong? ByteBuffer.available(&self) @inline @dynamic
{
return (ulong)self.write_idx - self.read_idx;
}
fn void ByteBuffer.grow(&self, usz n)

View File

@@ -41,16 +41,26 @@ fn void? ByteReader.pushback_byte(&self) @dynamic
fn usz? ByteReader.seek(&self, isz offset, Seek seek) @dynamic
{
isz new_index;
switch (seek)
self.set_cursor((long)offset, (SeekOrigin)seek.ordinal)!;
return (usz)self.cursor();
}
fn long? ByteReader.cursor(&self) @dynamic
{
return self.index;
}
fn void? ByteReader.set_cursor(&self, long offset, SeekOrigin whence = FROM_START) @dynamic
{
long new_index;
switch (whence)
{
case SET: new_index = offset;
case CURSOR: new_index = self.index + offset;
case END: new_index = self.bytes.len + offset;
case FROM_START: new_index = offset;
case FROM_CURSOR: new_index = self.index + offset;
case FROM_END: new_index = self.bytes.len + offset;
}
if (new_index < 0) return INVALID_POSITION~;
self.index = new_index;
return new_index;
if (new_index < 0 || new_index > self.bytes.len) return INVALID_POSITION~;
self.index = (usz)new_index;
}
fn usz? ByteReader.write_to(&self, OutStream writer) @dynamic
@@ -62,7 +72,7 @@ fn usz? ByteReader.write_to(&self, OutStream writer) @dynamic
return written;
}
fn usz? ByteReader.available(&self) @inline @dynamic
fn ulong? ByteReader.available(&self) @inline @dynamic
{
return max(0, self.bytes.len - self.index);
}
}

View File

@@ -86,9 +86,10 @@ fn usz? ByteWriter.read_from(&self, InStream reader) @dynamic
usz start_index = self.index;
if (&reader.available)
{
while (usz available = reader.available()!)
while (ulong available = reader.available()!)
{
self.ensure_capacity(self.index + available)!;
if (available > usz.max) return OUT_OF_SPACE~;
self.ensure_capacity(self.index + (usz)available)!;
usz read = reader.read(self.bytes[self.index..])!;
self.index += read;
}

View File

@@ -38,7 +38,7 @@ fn char? LimitReader.read_byte(&self) @dynamic
return self.wrapped_stream.read_byte();
}
fn usz? LimitReader.available(&self) @inline @dynamic
fn ulong? LimitReader.available(&self) @inline @dynamic
{
return self.limit;
}

View File

@@ -49,7 +49,7 @@ fn void errno_set(Errno e)
os::errno_set((int)e);
}
typedef Errno = inline CInt;
typedef Errno @constinit = inline CInt;
alias TerminateFunction = fn void();
alias CompareFunction = fn int(void*, void*);
alias JmpBuf = uptr[$$JMP_BUF_SIZE];
@@ -126,6 +126,7 @@ extern fn CInt memcmp(void* buf1, void* buf2, usz count);
extern fn void* memcpy(void* dest, void* src, usz n);
extern fn void* memmove(void* dest, void* src, usz n);
extern fn void* memset(void* dest, CInt value, usz n);
extern fn ZString mktemp(char* template) @cname(env::WIN32 ??? "_mktemp" : "mktemp");
extern fn Time_t* mktime(Tm* time) @if(!env::WIN32);
extern fn void perror(ZString string);
extern fn CInt printf(ZString format, ...);
@@ -177,6 +178,7 @@ extern fn CLong strtol(char* str, char** endptr, CInt base);
extern fn CULong strtoul(char* str, char** endptr, CInt base);
extern fn usz strxfrm(char* dest, ZString src, usz n);
extern fn CInt system(ZString str);
extern fn ZString tempnam(ZString dir, ZString prefix) @cname(env::WIN32 ??? "_tempnam" : "tempnam") @nodiscard;
extern fn Time_t timegm(Tm* timeptr) @if(!env::WIN32 && !env::NETBSD);
extern fn ZString tmpnam(char *buffer);
extern fn CFile tmpfile();

View File

@@ -22,18 +22,20 @@ alias Pid_t = int;
alias Uid_t = uint;
alias Gid_t = uint;
const CUInt SA_ONSTACK = env::LINUX ? 0x08000000 : 0x0001;
const CUInt SA_RESTART = env::LINUX ? 0x10000000 : 0x0002;
const CUInt SA_RESETHAND = env::LINUX ? 0x80000000 : 0x0004;
const CUInt SA_SIGINFO = env::LINUX ? 0x00000004 : 0x0040;
const CUInt SA_ONSTACK = env::LINUX || env::ANDROID ? 0x08000000 : 0x0001;
const CUInt SA_RESTART = env::LINUX || env::ANDROID ? 0x10000000 : 0x0002;
const CUInt SA_RESETHAND = env::LINUX || env::ANDROID ? 0x80000000 : 0x0004;
const CUInt SA_SIGINFO = env::LINUX || env::ANDROID ? 0x00000004 : 0x0040;
alias Sigset_t @if((env::DARWIN || env::BSD_FAMILY) && !env::NETBSD) = uint;
alias Sigset_t @if(env::NETBSD) = uint[4];
alias Sigset_t @if(env::LINUX) = ulong[16];
alias Sigset_t @if(env::ANDROID) = ulong;
alias SigActionFunction = fn void(CInt, void*, void*);
struct Sigaction
{
CInt sa_flags @if(env::ANDROID);
union
{
SignalFunction sa_handler;
@@ -43,20 +45,20 @@ struct Sigaction
CInt sa_flags @if(env::DARWIN || env::BSD_FAMILY );
Sigset_t sa_mask @if(env::DARWIN || env::BSD_FAMILY);
Sigset_t sa_mask @if(env::LINUX);
Sigset_t sa_mask @if(env::LINUX || env::ANDROID);
CInt sa_flags @if(env::LINUX);
void* sa_restorer @if(env::LINUX);
void* sa_restorer @if(env::LINUX || env::ANDROID);
}
struct Stack_t
{
void* ss_sp;
struct @if(!env::LINUX)
struct @if(!env::LINUX && !env::ANDROID)
{
usz ss_size;
CInt ss_flags;
}
struct @if(env::LINUX)
struct @if(env::LINUX || env::ANDROID)
{
CInt ss_flags;
usz ss_size;
@@ -166,13 +168,13 @@ bitstruct Tc_lflags : CUInt
bool extproc : 16;
}
enum T_nldly : const char
constdef T_nldly : char
{
NL0 = 0b0,
NL1 = 0b1,
}
enum T_crdly : const char
constdef T_crdly : char
{
CR0 = 0b00,
CR1 = 0b01,
@@ -180,7 +182,7 @@ enum T_crdly : const char
CR3 = 0b11,
}
enum T_tabdly : const char
constdef T_tabdly : char
{
TAB0 = 0b00,
TAB1 = 0b01,
@@ -189,25 +191,25 @@ enum T_tabdly : const char
XTABS = TAB3,
}
enum T_bsdly : const char
constdef T_bsdly : char
{
BS0 = 0b0,
BS1 = 0b1,
}
enum T_ffdly : const char
constdef T_ffdly : char
{
FF0 = 0b0,
FF1 = 0b1,
}
enum T_vtdly : const char
constdef T_vtdly : char
{
VT0 = 0b0,
VT1 = 0b1,
}
enum T_csize : const char
constdef T_csize : char
{
CS5 = 0b00,
CS6 = 0b01,
@@ -215,31 +217,31 @@ enum T_csize : const char
CS8 = 0b11,
}
enum Speed : const CUInt
constdef Speed : CUInt
{
B0 = 0o0000000,
B50 = 0o0000001,
B75 = 0o0000002,
B110 = 0o0000003,
B134 = 0o0000004,
B150 = 0o0000005,
B200 = 0o0000006,
B300 = 0o0000007,
B600 = 0o0000010,
B1200 = 0o0000011,
B1800 = 0o0000012,
B2400 = 0o0000013,
B4800 = 0o0000014,
B9600 = 0o0000015,
B19200 = 0o0000016,
B38400 = 0o0000017,
B57600 = 0o0010001,
B115200 = 0o0010002,
B230400 = 0o0010003,
B460800 = 0o0010004,
B500000 = 0o0010005,
B576000 = 0o0010006,
B921600 = 0o0010007,
B0 = 0o0000000,
B50 = 0o0000001,
B75 = 0o0000002,
B110 = 0o0000003,
B134 = 0o0000004,
B150 = 0o0000005,
B200 = 0o0000006,
B300 = 0o0000007,
B600 = 0o0000010,
B1200 = 0o0000011,
B1800 = 0o0000012,
B2400 = 0o0000013,
B4800 = 0o0000014,
B9600 = 0o0000015,
B19200 = 0o0000016,
B38400 = 0o0000017,
B57600 = 0o0010001,
B115200 = 0o0010002,
B230400 = 0o0010003,
B460800 = 0o0010004,
B500000 = 0o0010005,
B576000 = 0o0010006,
B921600 = 0o0010007,
B1000000 = 0o0010010,
B1152000 = 0o0010011,
B1500000 = 0o0010012,
@@ -251,37 +253,37 @@ enum Speed : const CUInt
MAX_BAUD = B4000000,
}
enum Cc : const inline char
constdef Cc : inline char
{
VINTR = 0,
VQUIT = 1,
VERASE = 2,
VKILL = 3,
VEOF = 4,
VTIME = 5,
VMIN = 6,
VSWTC = 7,
VSTART = 8,
VSTOP = 9,
VSUSP = 10,
VEOL = 11,
VINTR = 0,
VQUIT = 1,
VERASE = 2,
VKILL = 3,
VEOF = 4,
VTIME = 5,
VMIN = 6,
VSWTC = 7,
VSTART = 8,
VSTOP = 9,
VSUSP = 10,
VEOL = 11,
VREPRINT = 12,
VDISCARD = 13,
VWERASE = 14,
VLNEXT = 15,
VEOL2 = 16,
VWERASE = 14,
VLNEXT = 15,
VEOL2 = 16,
}
enum Tcactions : const CInt
constdef Tcactions : CInt
{
TCOOFF = 0,
TCOON = 1,
TCIOFF = 2,
TCION = 3,
TCIFLUSH = 0,
TCOFLUSH = 1,
TCOOFF = 0,
TCOON = 1,
TCIOFF = 2,
TCION = 3,
TCIFLUSH = 0,
TCOFLUSH = 1,
TCIOFLUSH = 2,
TCSANOW = 0,
TCSANOW = 0,
TCSADRAIN = 1,
TCSAFLUSH = 2,
}
@@ -310,65 +312,65 @@ struct Termios
Speed c_ospeed;
}
const Tcactions TCOOFF @deprecated = 0;
const Tcactions TCOON @deprecated = 1;
const Tcactions TCIOFF @deprecated = 2;
const Tcactions TCION @deprecated = 3;
const Tcactions TCIFLUSH @deprecated = 0;
const Tcactions TCOFLUSH @deprecated = 1;
const Tcactions TCIOFLUSH @deprecated = 2;
const Tcactions TCSANOW @deprecated = 0;
const Tcactions TCSADRAIN @deprecated = 1;
const Tcactions TCSAFLUSH @deprecated = 2;
const Speed B0 @deprecated = 0o0000000;
const Speed B50 @deprecated = 0o0000001;
const Speed B75 @deprecated = 0o0000002;
const Speed B110 @deprecated = 0o0000003;
const Speed B134 @deprecated = 0o0000004;
const Speed B150 @deprecated = 0o0000005;
const Speed B200 @deprecated = 0o0000006;
const Speed B300 @deprecated = 0o0000007;
const Speed B600 @deprecated = 0o0000010;
const Speed B1200 @deprecated = 0o0000011;
const Speed B1800 @deprecated = 0o0000012;
const Speed B2400 @deprecated = 0o0000013;
const Speed B4800 @deprecated = 0o0000014;
const Speed B9600 @deprecated = 0o0000015;
const Speed B19200 @deprecated = 0o0000016;
const Speed B38400 @deprecated = 0o0000017;
const Speed B57600 @deprecated = 0o0010001;
const Speed B115200 @deprecated = 0o0010002;
const Speed B230400 @deprecated = 0o0010003;
const Speed B460800 @deprecated = 0o0010004;
const Speed B500000 @deprecated = 0o0010005;
const Speed B576000 @deprecated = 0o0010006;
const Speed B921600 @deprecated = 0o0010007;
const Speed B1000000 @deprecated = 0o0010010;
const Speed B1152000 @deprecated = 0o0010011;
const Speed B1500000 @deprecated = 0o0010012;
const Speed B2000000 @deprecated = 0o0010013;
const Speed B2500000 @deprecated = 0o0010014;
const Speed B3000000 @deprecated = 0o0010015;
const Speed B3500000 @deprecated = 0o0010016;
const Speed B4000000 @deprecated = 0o0010017;
const Speed MAX_BAUD @deprecated = B4000000;
const Cc VINTR @deprecated = 0;
const Cc VQUIT @deprecated = 1;
const Cc VERASE @deprecated = 2;
const Cc VKILL @deprecated = 3;
const Cc VEOF @deprecated = 4;
const Cc VTIME @deprecated = 5;
const Cc VMIN @deprecated = 6;
const Cc VSWTC @deprecated = 7;
const Cc VSTART @deprecated = 8;
const Cc VSTOP @deprecated = 9;
const Cc VSUSP @deprecated = 10;
const Cc VEOL @deprecated = 11;
const Cc VREPRINT @deprecated = 12;
const Cc VDISCARD @deprecated = 13;
const Cc VWERASE @deprecated = 14;
const Cc VLNEXT @deprecated = 15;
const Cc VEOL2 @deprecated = 16;
const Tcactions TCOOFF @deprecated = (Tcactions)0;
const Tcactions TCOON @deprecated = (Tcactions)1;
const Tcactions TCIOFF @deprecated = (Tcactions)2;
const Tcactions TCION @deprecated = (Tcactions)3;
const Tcactions TCIFLUSH @deprecated = (Tcactions)0;
const Tcactions TCOFLUSH @deprecated = (Tcactions)1;
const Tcactions TCIOFLUSH @deprecated = (Tcactions)2;
const Tcactions TCSANOW @deprecated = (Tcactions)0;
const Tcactions TCSADRAIN @deprecated = (Tcactions)1;
const Tcactions TCSAFLUSH @deprecated = (Tcactions)2;
const Speed B0 @deprecated = (Speed)0o0000000;
const Speed B50 @deprecated = (Speed)0o0000001;
const Speed B75 @deprecated = (Speed)0o0000002;
const Speed B110 @deprecated = (Speed)0o0000003;
const Speed B134 @deprecated = (Speed)0o0000004;
const Speed B150 @deprecated = (Speed)0o0000005;
const Speed B200 @deprecated = (Speed)0o0000006;
const Speed B300 @deprecated = (Speed)0o0000007;
const Speed B600 @deprecated = (Speed)0o0000010;
const Speed B1200 @deprecated = (Speed)0o0000011;
const Speed B1800 @deprecated = (Speed)0o0000012;
const Speed B2400 @deprecated = (Speed)0o0000013;
const Speed B4800 @deprecated = (Speed)0o0000014;
const Speed B9600 @deprecated = (Speed)0o0000015;
const Speed B19200 @deprecated = (Speed)0o0000016;
const Speed B38400 @deprecated = (Speed)0o0000017;
const Speed B57600 @deprecated = (Speed)0o0010001;
const Speed B115200 @deprecated = (Speed)0o0010002;
const Speed B230400 @deprecated = (Speed)0o0010003;
const Speed B460800 @deprecated = (Speed)0o0010004;
const Speed B500000 @deprecated = (Speed)0o0010005;
const Speed B576000 @deprecated = (Speed)0o0010006;
const Speed B921600 @deprecated = (Speed)0o0010007;
const Speed B1000000 @deprecated = (Speed)0o0010010;
const Speed B1152000 @deprecated = (Speed)0o0010011;
const Speed B1500000 @deprecated = (Speed)0o0010012;
const Speed B2000000 @deprecated = (Speed)0o0010013;
const Speed B2500000 @deprecated = (Speed)0o0010014;
const Speed B3000000 @deprecated = (Speed)0o0010015;
const Speed B3500000 @deprecated = (Speed)0o0010016;
const Speed B4000000 @deprecated = (Speed)0o0010017;
const Speed MAX_BAUD @deprecated = (Speed)0o0010017;
const Cc VINTR @deprecated = (Cc)0;
const Cc VQUIT @deprecated = (Cc)1;
const Cc VERASE @deprecated = (Cc)2;
const Cc VKILL @deprecated = (Cc)3;
const Cc VEOF @deprecated = (Cc)4;
const Cc VTIME @deprecated = (Cc)5;
const Cc VMIN @deprecated = (Cc)6;
const Cc VSWTC @deprecated = (Cc)7;
const Cc VSTART @deprecated = (Cc)8;
const Cc VSTOP @deprecated = (Cc)9;
const Cc VSUSP @deprecated = (Cc)10;
const Cc VEOL @deprecated = (Cc)11;
const Cc VREPRINT @deprecated = (Cc)12;
const Cc VDISCARD @deprecated = (Cc)13;
const Cc VWERASE @deprecated = (Cc)14;
const Cc VLNEXT @deprecated = (Cc)15;
const Cc VEOL2 @deprecated = (Cc)16;
const Tc_lflags ISIG @deprecated = {.isig};
const Tc_lflags ICANON @deprecated = {.icanon};
const Tc_lflags ECHO @deprecated = {.echo};

View File

@@ -37,7 +37,7 @@ fn BigInt* BigInt.init(&self, int128 value)
assert(value < 0 || tmp == 0 || !self.is_negative());
assert(value >= 0 || tmp == -1 || self.is_negative());
self.len = len;
self.reduce_len();
reduce_len(self);
return self;
}
@@ -153,15 +153,11 @@ fn void BigInt.add_this(&self, BigInt other) @operator(+=)
self.data[self.len++] = (uint)carry;
}
self.reduce_len();
reduce_len(self);
assert(sign != sign_arg || sign == self.is_negative(), "Overflow in addition");
}
fn void BigInt.reduce_len(&self) @private
{
self.len = max(find_length(&self.data, self.len), 1);
}
macro uint find_length(uint* data, uint length)
{
@@ -226,7 +222,7 @@ fn void BigInt.mult_this(&self, BigInt bi2) @operator(*=)
res.len = min(self.len + bi2.len, (uint)MAX_LEN);
res.reduce_len();
reduce_len(&res);
// overflow check (result is -ve)
assert(!res.is_negative(), "Multiplication overflow");
@@ -265,7 +261,7 @@ fn void BigInt.negate(&self)
assert(self.is_negative() != was_negative, "Overflow in negation");
self.len = MAX_LEN;
self.reduce_len();
reduce_len(self);
}
macro bool BigInt.is_zero(&self) => self.len == 1 && self.data[0] == 0;
@@ -301,7 +297,7 @@ fn BigInt* BigInt.sub_this(&self, BigInt other) @operator(-=)
self.len = MAX_LEN;
}
self.reduce_len();
reduce_len(self);
// overflow check
@@ -311,7 +307,7 @@ fn BigInt* BigInt.sub_this(&self, BigInt other) @operator(-=)
fn int BigInt.bitcount(&self)
{
self.reduce_len();
reduce_len(self);
uint val = self.data[self.len - 1];
uint mask = 0x80000000;
int bits = 32;
@@ -364,11 +360,11 @@ fn void BigInt.div_this(&self, BigInt other) @operator(/=)
if (other.len == 1)
{
self.single_byte_divide(&other, &quotient, &remainder);
single_byte_divide(self, &other, &quotient, &remainder);
}
else
{
self.multi_byte_divide(&other, &quotient, &remainder);
multi_byte_divide(self, &other, &quotient, &remainder);
}
if (negate_answer)
{
@@ -407,11 +403,11 @@ fn void BigInt.mod_this(&self, BigInt bi2) @operator(%=)
if (bi2.len == 1)
{
self.single_byte_divide(&bi2, &quotient, &remainder);
single_byte_divide(self, &bi2, &quotient, &remainder);
}
else
{
self.multi_byte_divide(&bi2, &quotient, &remainder);
multi_byte_divide(self, &bi2, &quotient, &remainder);
}
if (negate_answer)
{
@@ -425,7 +421,7 @@ fn void BigInt.bit_negate_this(&self)
foreach (&r : self.data) *r = ~*r;
self.len = MAX_LEN;
self.reduce_len();
reduce_len(self);
}
fn BigInt BigInt.bit_negate(self) @operator(~)
@@ -523,7 +519,7 @@ fn usz? BigInt.to_format(&self, Formatter* format) @dynamic
usz len;
if (negative)
{
format.out('-')!;
format.print_char('-')!;
len++;
a.negate();
}
@@ -580,7 +576,7 @@ fn String BigInt.to_string_with_radix(&self, int radix, Allocator allocator)
while (!a.is_zero())
{
a.single_byte_divide(&big_radix, &quotient, &remainder);
single_byte_divide(&a, &big_radix, &quotient, &remainder);
if (remainder.data[0] < 10)
{
@@ -740,7 +736,7 @@ fn BigInt barrett_reduction(BigInt x, BigInt n, BigInt constant)
}
r2.len = k_plus_one;
r2.reduce_len();
reduce_len(&r2);
r1.sub_this(r2);
if (r1.is_negative())
@@ -816,7 +812,7 @@ fn void BigInt.bit_and_this(&self, BigInt bi2)
}
self.len = MAX_LEN;
self.reduce_len();
reduce_len(self);
}
fn BigInt BigInt.bit_or(self, BigInt bi2) @operator(|)
@@ -834,7 +830,7 @@ fn void BigInt.bit_or_this(&self, BigInt bi2)
}
self.len = MAX_LEN;
self.reduce_len();
reduce_len(self);
}
fn BigInt BigInt.bit_xor(self, BigInt bi2) @operator(^)
@@ -852,7 +848,7 @@ fn void BigInt.bit_xor_this(&self, BigInt bi2)
}
self.len = MAX_LEN;
self.reduce_len();
reduce_len(self);
}
fn void BigInt.shl_this(&self, int shift) @operator(<<=)
@@ -928,13 +924,18 @@ fn void BigInt.randomize_bits(&self, Random random, int bits)
module std::math::bigint @private;
fn void reduce_len(BigInt* self) @private
{
self.len = max(find_length(&self.data, self.len), 1);
}
<*
@param [&inout] quotient
@param [&in] bi2
@param [&inout] remainder
*>
fn void BigInt.single_byte_divide(&self, BigInt* bi2, BigInt* quotient, BigInt* remainder)
fn void single_byte_divide(BigInt* self, BigInt* bi2, BigInt* quotient, BigInt* remainder) @private
{
uint[MAX_LEN] result;
int result_pos = 0;
@@ -977,8 +978,8 @@ fn void BigInt.single_byte_divide(&self, BigInt* bi2, BigInt* quotient, BigInt*
}
quotient.data[j..] = 0;
quotient.reduce_len();
remainder.reduce_len();
reduce_len(quotient);
reduce_len(remainder);
}
<*
@@ -986,7 +987,7 @@ fn void BigInt.single_byte_divide(&self, BigInt* bi2, BigInt* quotient, BigInt*
@param [&in] other
@param [&inout] remainder
*>
fn void BigInt.multi_byte_divide(&self, BigInt* other, BigInt* quotient, BigInt* remainder)
fn void multi_byte_divide(BigInt* self, BigInt* other, BigInt* quotient, BigInt* remainder)
{
uint[MAX_LEN] result;
uint[MAX_LEN] r;
@@ -1081,7 +1082,7 @@ fn void BigInt.multi_byte_divide(&self, BigInt* other, BigInt* quotient, BigInt*
quotient.data[y] = 0;
}
quotient.reduce_len();
reduce_len(quotient);
remainder.len = shift_right(&r, remainder_len, shift);

View File

@@ -0,0 +1,589 @@
// Copyright (c) 2026 Koni Marti. All rights reserved.
// Use of this source code is governed by the MIT license.
<*
This module provides a comprehensive set of continuous and discrete
probability distributions with support for PDF, CDF, inverse CDF,
random sampling, mean, and variance calculations.
*>
module std::math::distributions;
import std::math::random;
// Distribution interface defining common statistical operations
interface Distribution
{
<* Calculate the mean (expected value) of the distribution *>
fn double mean();
<* Calculate the variance of the distribution *>
fn double variance();
}
interface ContinuousDistribution : Distribution
{
<* Probability density function (PDF) *>
fn double pdf(double x);
<* Cumulative distribution function (CDF) *>
fn double cdf(double x);
<* Inverse cumulative distribution function (quantile function) *>
fn double quantile(double p);
<* Generate a random sample from the distribution *>
fn double random(Random rand);
}
interface DiscreteDistribution : Distribution
{
<* Probability mass function (PMF) *>
fn double pmf(int k);
<* Cumulative distribution function (CDF) *>
fn double cdf(int k);
<* Inverse cumulative distribution function *>
fn int quantile(double p);
<* Generate a random sample from the distribution *>
fn int random(Random rand);
}
<*
Uniform distribution over [a, b]
*>
struct UniformDist (ContinuousDistribution)
{
double a;
double b;
}
<*
@require b > a : "Upper bound must be greater than lower bound."
*>
fn UniformDist uniform(double a, double b)
{
return (UniformDist){ a, b };
}
fn double UniformDist.mean(&self) @dynamic
{
return (self.a + self.b) / 2.0;
}
fn double UniformDist.variance(&self) @dynamic
{
double range = self.b - self.a;
return range * range / 12.0;
}
fn double UniformDist.pdf(&self, double x) @dynamic
{
if (x < self.a || x > self.b) return 0;
return 1.0 / (self.b - self.a);
}
fn double UniformDist.cdf(&self, double x) @dynamic
{
if (x < self.a) return 0.0;
if (x > self.b) return 1.0;
return (x - self.a) / (self.b - self.a);
}
<*
@require p >= 0.0 && p <= 1.0 : "Probability must be between 0 and 1."
*>
fn double UniformDist.quantile(&self, double p) @dynamic
{
return self.a + p * (self.b - self.a);
}
fn double UniformDist.random(&self, Random rand) @dynamic
{
return self.a + random::next_double(rand) * (self.b - self.a);
}
<*
Normal (Gaussian) distribution
*>
struct NormalDist (ContinuousDistribution)
{
double mu;
double sigma;
}
<*
@require sigma > 0.0 : "Standard deviation must be positive"
*>
fn NormalDist normal(double mu = 0.0, double sigma = 1.0)
{
return (NormalDist){ mu, sigma };
}
fn double NormalDist.mean(&self) @dynamic
{
return self.mu;
}
fn double NormalDist.variance(&self) @dynamic
{
return self.sigma * self.sigma;
}
fn double NormalDist.pdf(&self, double x) @dynamic
{
double z = (x - self.mu) / self.sigma;
return math::exp(-0.5 * z * z) / (self.sigma * math::sqrt(2.0 * math::PI));
}
fn double NormalDist.cdf(&self, double x) @dynamic
{
double z = (x - self.mu) / self.sigma;
return math::clamp(0.5 * (1.0 + math::erf(z / math::SQRT2)), 0.0, 1.0);
}
<*
@require p >= 0.0 && p <= 1.0 : "Probability must be between 0 and 1."
*>
fn double NormalDist.quantile(&self, double p) @dynamic
{
double z = inverse_erf(2.0 * p - 1.0) * math::SQRT2;
return self.mu + self.sigma * z;
}
fn double NormalDist.random(&self, Random rand) @dynamic
{
// Box-Muller transform.
double u1 = random::next_double(rand);
double u2 = random::next_double(rand);
double z = math::sqrt(-2.0 * math::ln(u1)) * math::cos(2.0 * math::PI * u2);
return self.mu + self.sigma * z;
}
<*
Exponential distribution
*>
struct ExponentialDist (ContinuousDistribution)
{
double lambda;
}
<*
@require lambda > 0.0 : "Rate parameter must be positive."
*>
fn ExponentialDist exponential(double lambda = 1.0)
{
return (ExponentialDist){ lambda };
}
<*
@require self.lambda > 0.0 : "Rate parameter must be positive."
*>
fn double ExponentialDist.mean(&self) @dynamic
{
return 1.0 / self.lambda;
}
<*
@require self.lambda > 0.0 : "Rate parameter must be positive."
*>
fn double ExponentialDist.variance(&self) @dynamic
{
return 1.0 / (self.lambda * self.lambda);
}
fn double ExponentialDist.pdf(&self, double x) @dynamic
{
if (x < 0.0) return 0.0;
return self.lambda * math::exp(-self.lambda * x);
}
fn double ExponentialDist.cdf(&self, double x) @dynamic
{
if (x < 0.0) return 0.0;
return math::clamp(1.0 - math::exp(-self.lambda * x), 0.0, 1.0);
}
<*
@require p >= 0.0 && p <= 1.0 : "Probability must be between 0 and 1."
@require self.lambda > 0.0 : "Rate parameter must be positive."
*>
fn double ExponentialDist.quantile(&self, double p) @dynamic
{
return -math::ln(1.0 - p) / self.lambda;
}
<*
@require self.lambda > 0.0 : "Rate parameter must be positive."
*>
fn double ExponentialDist.random(&self, Random rand) @dynamic
{
return -math::ln(1.0 - random::next_double(rand)) / self.lambda;
}
<*
Student's t-distribution
*>
struct TDist (ContinuousDistribution)
{
double df;
}
<*
@require df > 0.0 : "Degrees of freedom must be positive."
*>
fn TDist t_distribution(double df)
{
return (TDist){ df };
}
fn double TDist.mean(&self) @dynamic
{
if (self.df <= 1.0) return double.nan;
return 0.0;
}
fn double TDist.variance(&self) @dynamic
{
if (self.df <= 1.0) return double.nan;
if (self.df <= 2.0) return double.inf;
return self.df / (self.df - 2.0);
}
fn double TDist.pdf(&self, double x) @dynamic
{
double v = self.df;
double coef = math::tgamma((v + 1.0) / 2.0) /
(math::sqrt(v * math::PI) * math::tgamma(v / 2.0));
return coef * math::pow(1.0 + x * x / v, -(v + 1.0) / 2.0);
}
fn double TDist.cdf(&self, double x) @dynamic
{
double v = self.df;
if (x == 0.0) return 0.5;
double t = v / (v + x * x);
double a = v / 2.0;
double b = 0.5;
// Using regularized incomplete beta function.
double beta_cdf = incomplete_beta(t, a, b);
double p = x >= 0.0 ? 1.0 - 0.5 * beta_cdf : 0.5 * beta_cdf;
return math::clamp(p, 0.0, 1.0);
}
<*
@require p >= 0.0 && p <= 1.0 : "Probability must be between 0 and 1."
*>
fn double TDist.quantile(&self, double p) @dynamic
{
if (p == 0.5) return 0.0;
double x = (p < 0.5) ? -1.0 : 1.0;
return newton_raphson(self, x, p) ?? double.nan;
}
fn double TDist.random(&self, Random rand) @dynamic
{
// Generate using relationship with normal and chi-squared
NormalDist std_normal = normal(0.0, 1.0);
double z = std_normal.random(rand);
double v = chi_squared_sample(self.df, rand);
return z / math::sqrt(v / self.df);
}
<*
F-distribution
*>
struct FDist (ContinuousDistribution)
{
double d1;
double d2;
}
<*
@require d1 > 0.0 && d2 > 0.0 : "Degrees of freedom must be positive."
*>
fn FDist f_distribution(double d1, double d2)
{
return (FDist){ d1, d2 };
}
fn double FDist.mean(&self) @dynamic
{
if (self.d2 <= 2.0) return double.nan;
return self.d2 / (self.d2 - 2.0);
}
fn double FDist.variance(&self) @dynamic
{
if (self.d2 <= 4.0) return double.nan;
double d1 = self.d1;
double d2 = self.d2;
return 2.0 * d2 * d2 * (d1 + d2 - 2.0) /
(d1 * (d2 - 2.0) * (d2 - 2.0) * (d2 - 4.0));
}
fn double FDist.pdf(&self, double x) @dynamic
{
if (x < 0.0) return 0.0;
double d1 = self.d1;
double d2 = self.d2;
double num = math::pow(d1 * x, d1) * math::pow(d2, d2);
double denom = math::pow(d1 * x + d2, d1 + d2);
double beta_term = x * beta_function(d1 / 2.0, d2 / 2.0);
return math::sqrt(num / denom) / beta_term;
}
fn double FDist.cdf(&self, double x) @dynamic
{
if (x <= 0.0) return 0.0;
double d1 = self.d1;
double d2 = self.d2;
double t = d1 * x / (d1 * x + d2);
double p = incomplete_beta(t, d1 / 2.0, d2 / 2.0);
return math::clamp(p, 0.0, 1.0);
}
<*
@require p >= 0.0 && p <= 1.0 : "Probability must be between 0 and 1."
*>
fn double FDist.quantile(&self, double p) @dynamic
{
return find_quantile(self, 0.0, 1000.0, p);
}
fn double FDist.random(&self, Random rand) @dynamic
{
// Generate using ratio of chi-squared variables.
double u1 = chi_squared_sample(self.d1, rand);
double u2 = chi_squared_sample(self.d2, rand);
return (u1 / self.d1) / (u2 / self.d2);
}
<*
Chi-squared distribution
*>
struct ChiSquaredDist (ContinuousDistribution)
{
double k;
}
<*
@require k > 0.0 : "Degrees of freedom must be positive"
*>
fn ChiSquaredDist chi_squared(double k)
{
return (ChiSquaredDist){ k };
}
fn double ChiSquaredDist.mean(&self) @dynamic
{
return self.k;
}
fn double ChiSquaredDist.variance(&self) @dynamic
{
return 2.0 * self.k;
}
fn double ChiSquaredDist.pdf(&self, double x) @dynamic
{
if (x < 0.0) return 0.0;
if (x == 0.0 && self.k < 2.0) return double.inf;
if (x == 0.0) return 0.0;
double k = self.k;
return math::pow(x, k / 2.0 - 1.0) * math::exp(-x / 2.0) /
(math::pow(2.0, k / 2.0) * math::tgamma(k / 2.0));
}
fn double ChiSquaredDist.cdf(&self, double x) @dynamic
{
if (x <= 0.0) return 0.0;
double p = lower_incomplete_gamma(self.k / 2.0, x / 2.0);
return math::clamp(p, 0.0, 1.0);
}
<*
@require p >= 0.0 && p <= 1.0 : "Probability must be between 0 and 1."
*>
fn double ChiSquaredDist.quantile(&self, double p) @dynamic
{
double low = 0.0;
double high = self.k + 10.0 * math::sqrt(2.0 * self.k);
return find_quantile(self, low, high, p);
}
fn double ChiSquaredDist.random(&self, Random rand) @dynamic
{
return chi_squared_sample(self.k, rand);
}
<*
Binomial distribution
*>
struct BinomialDist (DiscreteDistribution)
{
int n;
double p;
}
<*
@require n >= 0 : "Number of trials must be non-negative."
@require p >= 0.0 && p <= 1.0 : "Probability must be between 0 and 1."
*>
fn BinomialDist binomial(int n, double p)
{
return (BinomialDist){ n, p };
}
fn double BinomialDist.mean(&self) @dynamic
{
return (double)self.n * self.p;
}
fn double BinomialDist.variance(&self) @dynamic
{
return (double)self.n * self.p * (1.0 - self.p);
}
fn double BinomialDist.pmf(&self, int k) @dynamic
{
if (k < 0 || k > self.n) return 0.0;
return binomial_coefficient(self.n, k) *
math::pow(self.p, (double)k) *
math::pow(1.0 - self.p, (double)(self.n - k));
}
fn double BinomialDist.cdf(&self, int k) @dynamic
{
if (k < 0) return 0.0;
if (k >= self.n) return 1.0;
double sum = 0.0;
for (int i = 0; i <= k; i++)
{
sum += self.pmf(i);
}
return sum;
}
<*
@require p >= 0.0 && p <= 1.0 : "Probability must be between 0 and 1."
*>
fn int BinomialDist.quantile(&self, double p) @dynamic
{
double cumulative = 0.0;
for (int k = 0; k <= self.n; k++)
{
cumulative += self.pmf(k);
if (cumulative >= p) return k;
}
return self.n;
}
fn int BinomialDist.random(&self, Random rand) @dynamic
{
// Generate using Bernoulli trials.
int successes = 0;
for (int i = 0; i < self.n; i++)
{
if (random::next_double(rand) < self.p)
{
successes++;
}
}
return successes;
}
<*
Poisson distribution
*>
struct PoissonDist (DiscreteDistribution)
{
double lambda;
}
<*
@require lambda > 0.0 : "Rate parameter must be positive."
*>
fn PoissonDist poisson(double lambda)
{
return (PoissonDist){ lambda };
}
fn double PoissonDist.mean(&self) @dynamic
{
return self.lambda;
}
fn double PoissonDist.variance(&self) @dynamic
{
return self.lambda;
}
fn double PoissonDist.pmf(&self, int k) @dynamic
{
if (k < 0) return 0.0;
return math::exp(-self.lambda + (double)k * math::ln(self.lambda) - ln_factorial(k));
}
fn double PoissonDist.cdf(&self, int k) @dynamic
{
if (k < 0) return 0.0;
double sum = 0.0;
for (int i = 0; i <= k; i++)
{
sum += self.pmf(i);
}
return sum;
}
<*
@require p >= 0.0 && p <= 1.0 : "Probability must be between 0 and 1."
*>
fn int PoissonDist.quantile(&self, double p) @dynamic
{
double cumulative = 0.0;
int k = 0;
while (cumulative < p)
{
cumulative += self.pmf(k);
if (cumulative >= p) return k;
k++;
if (k > 1_000_000) break; // Safety limit
}
return k;
}
fn int PoissonDist.random(&self, Random rand) @dynamic
{
// Knuth's algorithm for small lambda.
if (self.lambda < 30.0)
{
double l = math::exp(-self.lambda);
int k = 0;
double p = 1.0;
do
{
k++;
p *= random::next_double(rand);
} while (p > l);
return (k - 1);
}
else
{
// Use normal approximation for large lambda
NormalDist approx = normal(self.lambda, math::sqrt(self.lambda));
return (int)math::max(0.0, math::round(approx.random(rand)));
}
}

View File

@@ -0,0 +1,407 @@
// Copyright (c) 2026 Koni Marti. All rights reserved.
// Use of this source code is governed by the MIT license.
module std::math::distributions @private;
import std::math::random;
struct ConvergenceControl
{
usz max_iter;
double epsilon;
}
const DEFAULT_CONV = (ConvergenceControl){ 600, 1e-12 };
const RELAXED_CONV = (ConvergenceControl){ 600, 1e-6 };
faultdef NOT_CONVERGED;
<*
Compute binomial coefficient C(n, k)
*>
fn double binomial_coefficient(int n, int k)
{
if (k < 0 || k > n) return 0.0;
if (k == 0 || k == n) return 1.0;
// Use symmetry.
if (k > n - k) k = n - k;
double result = 1.0;
for (int i = 0; i < k; i++)
{
result *= (double)(n - i);
result /= (double)(i + 1);
}
return result;
}
<*
Natural logarithm of factorial.
*>
fn double ln_factorial(int n)
{
if (n < 0) return double.nan;
if (n <= 1) return 0.0;
return math::lgamma((double)(n + 1));
}
<*
Beta function B(a, b)
@require a > 0
@require b > 0
*>
fn double beta_function(double a, double b)
{
return math::exp(math::lgamma(a) + math::lgamma(b) - math::lgamma(a + b));
}
<*
Regularized incomplete beta function I_x(a, b)
Based on https://github.com/codeplea/incbeta/blob/master/incbeta.c
*>
fn double incomplete_beta(double x, double a, double b, ConvergenceControl conv = DEFAULT_CONV)
{
if (x < 0.0 || x > 1.0) return double.nan;
if (x == 0.0) return 0.0;
if (x == 1.0) return 1.0;
if (x > (a + 1.0)/(a + b + 2.0)) return 1.0 - incomplete_beta(1.0 - x, b,a);
const double TINY = 1e-30;
// Find the first part before the continued fraction.
double lbeta_ab = math::lgamma(a) + math::lgamma(b) - math::lgamma(a + b);
double front = math::exp(math::ln(x) * a + math::ln(1.0 - x) * b - lbeta_ab) / a;
// Use Lentz's algorithm to evaluate the continued fraction.
double f = 1.0;
double c = 1.0;
double d = 0.0;
usz m;
for (usz i = 0; i <= conv.max_iter; ++i)
{
m = i/2;
double numerator;
if (i == 0)
{
numerator = 1.0;
}
else if (i % 2 == 0)
{
numerator = (m * (b - m) * x) / ((a + 2.0 * m - 1.0) * (a + 2.0 * m));
}
else
{
numerator = -((a + m) * (a + b + m) * x) / ((a + 2.0 * m) * (a + 2.0 * m + 1));
}
d = 1.0 + numerator * d;
if (math::abs(d) < TINY) d = TINY;
d = 1.0 / d;
c = 1.0 + numerator / c;
if (math::abs(c) < TINY) c = TINY;
double cd = c*d;
f *= cd;
if (math::abs(1.0 - cd) < conv.epsilon) return front * (f - 1.0);
}
return double.nan; // Not convered.
}
<*
Calculates the p-th quantile for a continuous distribution using bisection.
@return? NOT_CONVERGED
*>
fn double? bisection_search(ContinuousDistribution dist, double low, double high, double p,
ConvergenceControl conv = DEFAULT_CONV)
{
// Expand upper bound if needed.
while (dist.cdf(high) < p) high *= 2.0;
// Bisection search.
for (usz i = 0; i < conv.max_iter; i++)
{
double mid = (low + high) * 0.5;
if (high - low < conv.epsilon) return mid;
if (dist.cdf(mid) < p)
{
low = mid;
}
else
{
high = mid;
}
}
return NOT_CONVERGED~;
}
<*
Calculates the p-th quantile for a continuous distribution using Newton-Raphson.
@return? NOT_CONVERGED
*>
fn double? newton_raphson(ContinuousDistribution dist, double x, double p,
ConvergenceControl conv = DEFAULT_CONV)
{
double delta, pdf;
for (usz i = 0; i < conv.max_iter; i++)
{
pdf = dist.pdf(x);
if (pdf < 1e-300) break;
delta = (dist.cdf(x) - p) / pdf;
x -= delta;
if (math::abs(delta) < conv.epsilon) return x;
}
return NOT_CONVERGED~;
}
<*
Calculates the p-th quantile for a continuous distribution.
@require p >= 0.0 && p <= 1.0
@require low < high
*>
fn double find_quantile(ContinuousDistribution dist, double low, double high, double p)
{
double mid = bisection_search(dist, low, high, p, RELAXED_CONV) ?? (low + high) * 0.5;
return newton_raphson(dist, mid, p, DEFAULT_CONV) ?? mid;
}
<*
Generate a chi-squared random sample.
@param k : "Degrees of freedom"
@require k > 0.0
*>
fn double chi_squared_sample(double k, Random rand)
{
// Sum of k squared standard normals.
NormalDist std_normal = normal(0.0, 1.0);
double sum = 0.0;
int k_int = (int)k;
for (int i = 0; i < k_int; i++)
{
double z = std_normal.random(rand);
sum += z * z;
}
// Handle fractional degrees of freedom.
double frac = k - (double)k_int;
if (frac > 0.0)
{
double z = std_normal.random(rand);
sum += frac * z * z;
}
return sum;
}
<*
Inverse of the error function (math::erf).
Based on Golang's math.Erfinv.
*>
fn double inverse_erf(double x)
{
if (x < -1 || x > 1)
{
return double.nan;
}
else if (x == 1.0)
{
return double.inf;
}
else if (x == -1.0)
{
return -double.inf;
}
const double LN2 = 6.931471805599453094172321214581e-1;
const double A0 = 1.1975323115670912564578e0;
const double A1 = 4.7072688112383978012285e1;
const double A2 = 6.9706266534389598238465e2;
const double A3 = 4.8548868893843886794648e3;
const double A4 = 1.6235862515167575384252e4;
const double A5 = 2.3782041382114385731252e4;
const double A6 = 1.1819493347062294404278e4;
const double A7 = 8.8709406962545514830200e2;
const double B0 = 1.0000000000000000000e0;
const double B1 = 4.2313330701600911252e1;
const double B2 = 6.8718700749205790830e2;
const double B3 = 5.3941960214247511077e3;
const double B4 = 2.1213794301586595867e4;
const double B5 = 3.9307895800092710610e4;
const double B6 = 2.8729085735721942674e4;
const double B7 = 5.2264952788528545610e3;
const double C0 = 1.42343711074968357734e0;
const double C1 = 4.63033784615654529590e0;
const double C2 = 5.76949722146069140550e0;
const double C3 = 3.64784832476320460504e0;
const double C4 = 1.27045825245236838258e0;
const double C5 = 2.41780725177450611770e-1;
const double C6 = 2.27238449892691845833e-2;
const double C7 = 7.74545014278341407640e-4;
const double D0 = 1.4142135623730950488016887e0;
const double D1 = 2.9036514445419946173133295e0;
const double D2 = 2.3707661626024532365971225e0;
const double D3 = 9.7547832001787427186894837e-1;
const double D4 = 2.0945065210512749128288442e-1;
const double D5 = 2.1494160384252876777097297e-2;
const double D6 = 7.7441459065157709165577218e-4;
const double D7 = 1.4859850019840355905497876e-9;
const double E0 = 6.65790464350110377720e0;
const double E1 = 5.46378491116411436990e0;
const double E2 = 1.78482653991729133580e0;
const double E3 = 2.96560571828504891230e-1;
const double E4 = 2.65321895265761230930e-2;
const double E5 = 1.24266094738807843860e-3;
const double E6 = 2.71155556874348757815e-5;
const double E7 = 2.01033439929228813265e-7;
const double F0 = 1.414213562373095048801689e0;
const double F1 = 8.482908416595164588112026e-1;
const double F2 = 1.936480946950659106176712e-1;
const double F3 = 2.103693768272068968719679e-2;
const double F4 = 1.112800997078859844711555e-3;
const double F5 = 2.611088405080593625138020e-5;
const double F6 = 2.010321207683943062279931e-7;
const double F7 = 2.891024605872965461538222e-15;
double sign = 1.0;
if (x < 0)
{
x = -x;
sign = -1.0;
}
double ans;
if (x <= 0.85)
{
double r = 0.180625 - 0.25 * x *x;
double z1 = ((((((A7 * r + A6) * r + A5) * r + A4) * r + A3) * r + A2) * r + A1) * r + A0;
double z2 = ((((((B7 * r + B6) * r + B5) * r + B4) * r + B3) * r + B2) * r + B1) * r + B0;
ans = (x * z1) / z2;
}
else
{
double z1, z2;
double r = math::sqrt(LN2 - math::ln(1.0 - x));
if (r <= 5.0)
{
r -= 1.6;
z1 = ((((((C7 * r + C6) * r + C5) * r + C4) * r + C3) * r + C2) * r + C1) * r + C0;
z2 = ((((((D7 * r + D6) * r + D5) * r + D4) * r + D3) * r + D2) * r + D1) * r + D0;
}
else
{
r -= 5.0;
z1 = ((((((E7 * r + E6) * r + E5) * r + E4) * r + E3) * r + E2) * r + E1) * r + E0;
z2 = ((((((F7 * r + F6) * r + F5) * r + F4) * r + F3) * r + F2) * r + F1) * r + F0;
}
ans = z1 / z2;
}
return sign * ans;
}
<*
Regularized Lower incomplete gamma function.
Returns nan when not converged.
@param s : "Shape parameter"
@param x : "Upper limit of integration"
@require s > 0.0 : "s must be positive."
@require x >= 0.0 : "x must be non-negative."
*>
fn double lower_incomplete_gamma(double s, double x)
{
if (x == 0.0) return 0.0;
if (x == double.inf) return 1.0;
// Use series expansion for x < s+1
if (x < s + 1.0)
{
return incomplete_gamma_series_expansion(s, x) ?? double.nan;
}
else
{
return 1.0 - incomplete_gamma_continued_fraction(s, x) ?? double.nan;
}
}
<*
Lower incomplete gamma series expansion.
@return? NOT_CONVERGED
*>
fn double? incomplete_gamma_series_expansion(double s, double x, ConvergenceControl conv = DEFAULT_CONV)
{
double lnpre = s * math::ln(x) - x - math::lgamma(s + 1.0);
if (lnpre < -708.0) return 0.0; // result underflows to zero
double term = 1.0;
double sum = 1.0;
double ap = s;
for (int n = 1; n <= conv.max_iter; n++)
{
ap += 1.0;
term *= x / ap;
sum += term;
if (math::abs(term) < math::abs(sum) * conv.epsilon)
{
return math::exp(lnpre) * sum;
}
}
// Non-convergence.
return NOT_CONVERGED~;
}
<*
Modified Lentz continued fraction.
@return? NOT_CONVERGED
*>
fn double? incomplete_gamma_continued_fraction(double s, double x, ConvergenceControl conv = DEFAULT_CONV)
{
double lnpre = s * math::ln(x) - x - math::lgamma(s);
// Lentz initialisation: fpmin guards against division by zero.
double fpmin = 1e-300;
double b = x + 1.0 - s;
double c = 1.0 / fpmin;
double d = 1.0 / b;
double h = d;
for (int i = 1; i <= conv.max_iter; i++)
{
double an = (double)i * (s - (double)i);
b += 2.0;
d = an * d + b;
if (math::abs(d) < fpmin) d = fpmin;
c = b + an / c;
if (math::abs(c) < fpmin) c = fpmin;
d = 1.0 / d;
double delta = d * c;
h *= delta;
if (math::abs(delta - 1.0) < conv.epsilon)
{
return math::exp(lnpre) * h;
}
}
// Non-convergence.
return NOT_CONVERGED~;
}

View File

@@ -89,9 +89,12 @@ macro abs(x) => $$abs(x);
*>
macro bool is_approx(x, y, eps)
{
if (x == y) return true;
if (is_nan(x) || is_nan(y)) return false;
return abs(x - y) <= eps;
return fn bool($typeof(x) a, $typeof(y) b, $typeof(eps) eps)
{
if (a == b) return true;
if (is_nan(a) || is_nan(b)) return false;
return abs(a - b) <= eps;
}(x, y, eps);
}
<*
@@ -100,9 +103,12 @@ macro bool is_approx(x, y, eps)
*>
macro bool is_approx_rel(x, y, eps)
{
if (x == y) return true;
if (is_nan(x) || is_nan(y)) return false;
return abs(x - y) <= eps * max(abs(x), abs(y));
return fn bool($typeof(x) a, $typeof(y) b, $typeof(eps) eps)
{
if (a == b) return true;
if (is_nan(a) || is_nan(b)) return false;
return abs(a - b) <= eps * max(abs(a), abs(b));
}(x, y, eps);
}
<*
@@ -118,6 +124,42 @@ macro sign(x)
$endif
}
<*
@require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value"
*>
macro erf(x)
{
$if $typeof(x) == float:
return _erff(x);
$else
return _erf(x);
$endif
}
<*
@require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value"
*>
macro tgamma(x)
{
$if $typeof(x) == float:
return _tgammaf(x);
$else
return _tgamma(x);
$endif
}
<*
@require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value"
*>
macro lgamma(x)
{
$if $typeof(x) == float:
return _lgammaf(x);
$else
return _lgamma(x);
$endif
}
<*
@require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value"
@require values::@is_int(y) || values::@is_float(y) : "Expected an integer or floating point value"
@@ -474,7 +516,7 @@ macro round(x) => $$round(x);
macro round_to_decimals(x, int decimal_places)
{
var div = $$pow_int(($typeof(x))10, decimal_places);
return round(div * x) / div;
return $$round(div * x) / div;
}
<*
@@ -997,11 +1039,29 @@ macro bool is_power_of_2(x)
return x != 0 && (x & (x - 1)) == 0;
}
<*
Returns the next power of two that is greater than or equal to x.
@require types::is_int($typeof(x)) : "Input must be an integer type"
@require x >= 0 : "Input must be non-negative"
*>
macro next_power_of_2(x)
{
$typeof(x) y = 1;
while (y < x) y += y;
return y;
if (x <= 1) return 1;
if (x == 2) return 2;
$typeof(x) v = x - 1;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
$if ($sizeof(x) >= 2): v |= v >> 8; $endif;
$if ($sizeof(x) >= 4): v |= v >> 16; $endif;
$if ($sizeof(x) >= 8): v |= v >> 32; $endif;
$if ($sizeof(x) >= 16): v |= v >> 64; $endif;
return v + 1;
}
macro equals_vec(v1, v2) @private
@@ -1064,6 +1124,14 @@ extern fn float _acoshf(float x) @MathLibc("acoshf");
extern fn float _asinhf(float x) @MathLibc("asinhf");
extern fn float _atanhf(float x) @MathLibc("atanhf");
extern fn double _erf(double x) @MathLibc("erf");
extern fn double _lgamma(double x) @MathLibc("lgamma");
extern fn double _tgamma(double x) @MathLibc("tgamma");
extern fn float _erff(float x) @MathLibc("erf");
extern fn float _lgammaf(float x) @MathLibc("lgammaf");
extern fn float _tgammaf(float x) @MathLibc("tgammaf");
fn double _frexp(double x, int* e)
{

View File

@@ -0,0 +1,77 @@
module std::math::nolibc @if(env::NO_LIBC || $feature(C3_MATH));
<*
Error function for float.
*>
fn float _erff(float x)
{
// Handle special cases.
if (x == 0.0) return 0.0;
if (x == float.inf) return 1.0;
if (x == -float.inf) return -1.0;
// Use symmetry: erf(-x) = -erf(x)
float sign = 1.0;
if (x < 0.0)
{
x = -x;
sign = -1.0;
}
const float P1 = 0.4067420160f;
const float P2 = 0.0072279182f;
const float A1 = 0.3168798904f;
const float A2 = -0.1383293141f;
const float A3 = 1.0868083034f;
const float A4 = -1.1169415512f;
const float A5 = 1.2064490307f;
const float A6 = -0.3931277152f;
const float A7 = 0.0382613542f;
float t = 1.0f / (1.0f + P1 * x + P2 * x * x);
float t2 = t * t;
float sum = A1 * t + A2 * t2 + A3 * t2 * t + A4 * t2 * t2;
sum += A5 * t2 * t2 * t + A6 * t2 * t2 * t2 + A7 * t2 * t2 * t2 * t;
// For intermediate x, use continued fraction.
return sign * (1.0f - sum * math::exp(-x * x));
}
<*
Error function for double.
*>
fn double _erf(double x)
{
// Handle special cases.
if (x == 0.0) return 0.0;
if (x == double.inf) return 1.0;
if (x == -double.inf) return -1.0;
// Use symmetry: erf(-x) = -erf(x)
double sign = 1.0;
if (x < 0.0)
{
x = -x;
sign = -1.0;
}
const double P1 = 0.406742016006509;
const double P2 = 0.0072279182302319;
const double A1 = 0.316879890481381;
const double A2 = -0.138329314150635;
const double A3 = 1.08680830347054;
const double A4 = -1.11694155120396;
const double A5 = 1.20644903073232;
const double A6 = -0.393127715207728;
const double A7 = 0.0382613542530727;
double t = 1.0 / (1.0 + P1 * x + P2 * x * x);
double t2 = t * t;
double sum = A1 * t + A2 * t2 + A3 * t2 * t + A4 * t2 * t2;
sum += A5 * t2 * t2 * t + A6 * t2 * t2 * t2 + A7 * t2 * t2 * t2 * t;
// For intermediate x, use continued fraction.
return sign * (1.0 - sum * math::exp(-x * x));
}

View File

@@ -0,0 +1,70 @@
module std::math::nolibc @if(env::NO_LIBC || $feature(C3_MATH));
// Constants
const double SQRT_PI = 1.7724538509055160272981674833411451;
const double LN_SQRT_2PI = 0.9189385332046727417803297364056176;
<*
Natural logarithm of the gamma function using the Lanczos approximation.
@require x > 0.0 : "x must be positive."
*>
fn double lgamma(double x)
{
// Lanczos approximation coefficients (g = 7, n = 9)
const double[*] LANCZOS_COEF = {
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7
};
const double LANCZOS_G = 7.0;
// For small x, use the reflection formula.
if (x < 0.5)
{
return math::ln(math::PI) - math::ln(math::sin(math::PI * x)) - lgamma(1.0 - x);
}
// Shift to use approximation around x >= 1.5
x -= 1.0;
double base = x + LANCZOS_G + 0.5;
double sum = LANCZOS_COEF[0];
for (int i = 1; i < 9; i++)
{
sum += LANCZOS_COEF[i] / (x + (double)i);
}
return LN_SQRT_2PI + math::ln(sum) + (x + 0.5) * math::ln(base) - base;
}
<*
Gamma function.
Valid for x > 0 and some negative non-integer values.
*>
fn double tgamma(double x)
{
// Handle special cases.
if (x == 0.0) return double.inf;
// Check for negative integers (poles).
if (x < 0.0 && x == math::floor(x))
{
return double.nan;
}
// For positive values, use exp(lgamma(x)).
if (x > 0.0)
{
return math::exp(lgamma(x));
}
// For negative non-integer values, use the reflection formula.
return math::PI / (math::sin(math::PI * x) * tgamma(1.0 - x));
}

View File

@@ -0,0 +1,42 @@
module std::math::random;
struct Xorshiro128PPRandom (Random)
{
uint[4] state;
}
<*
@require seed.len > 0
*>
fn void Xorshiro128PPRandom.set_seed(&self, char[] seed) @dynamic
{
self.state = random::make_seed(uint[4], seed);
}
// Xorshiro128++ implementation
fn uint Xorshiro128PPRandom.next_int(&self) @dynamic
{
uint result = (self.state[0] + self.state[3]).rotl(7) + self.state[0];
uint t = self.state[1] << 9U;
self.state[2] ^= self.state[0];
self.state[3] ^= self.state[1];
self.state[1] ^= self.state[2];
self.state[0] ^= self.state[3];
self.state[2] ^= t;
self.state[3] = self.state[3].rotl(11);
return result;
}
<*
@require bytes.len > 0
*>
fn void Xorshiro128PPRandom.next_bytes(&self, char[] bytes) @dynamic => @random_value_to_bytes(self.next_int, bytes);
fn uint128 Xorshiro128PPRandom.next_int128(&self) @dynamic => @long_to_int128(self.next_long());
fn ulong Xorshiro128PPRandom.next_long(&self) @dynamic => @int_to_long(self.next_int());
fn ushort Xorshiro128PPRandom.next_short(&self) @dynamic => (ushort)self.next_int();
fn char Xorshiro128PPRandom.next_byte(&self) @dynamic => (char)self.next_int();

View File

@@ -3,9 +3,9 @@ import std::io;
enum IpProtocol : char (AIFamily ai_family)
{
UNSPECIFIED = os::AF_UNSPEC,
IPV4 = os::AF_INET,
IPV6 = os::AF_INET6,
UNSPECIFIED { os::AF_UNSPEC },
IPV4 { os::AF_INET },
IPV6 { os::AF_INET6 },
}
struct InetAddress (Printable)

View File

@@ -34,22 +34,22 @@ struct AddrInfo
const PLATFORM_O_NONBLOCK @if(!$defined(PLATFORM_O_NONBLOCK)) = 0;
const AISockType SOCK_STREAM = 1; // Stream
const AISockType SOCK_DGRAM = 2; // Datagram
const AISockType SOCK_RAW = 3; // Raw
const AISockType SOCK_RDM = 4; // Reliably delivered
const AISockType SOCK_SEQPACKET = 5; // Sequential packet
const AISockType SOCK_STREAM = (AISockType)1; // Stream
const AISockType SOCK_DGRAM = (AISockType)2; // Datagram
const AISockType SOCK_RAW = (AISockType)3; // Raw
const AISockType SOCK_RDM = (AISockType)4; // Reliably delivered
const AISockType SOCK_SEQPACKET = (AISockType)5; // Sequential packet
const AIFlags AI_PASSIVE = 0x1;
const AIFlags AI_CANONNAME = 0x2;
const AIFlags AI_NUMERICHOST = 0x4;
const AIFlags AI_PASSIVE = (AIFlags)0x1;
const AIFlags AI_CANONNAME = (AIFlags)0x2;
const AIFlags AI_NUMERICHOST = (AIFlags)0x4;
const AIFamily AF_UNSPEC = 0;
const AIFamily AF_UNIX = 1;
const AIFamily AF_INET = 2;
const AIFamily AF_INET6 = PLATFORM_AF_INET6;
const AIFamily AF_IPX = PLATFORM_AF_IPX;
const AIFamily AF_APPLETALK = PLATFORM_AF_APPLETALK;
const AIFamily AF_UNSPEC = (AIFamily)0;
const AIFamily AF_UNIX = (AIFamily)1;
const AIFamily AF_INET = (AIFamily)2;
const AIFamily AF_INET6 = (AIFamily)PLATFORM_AF_INET6;
const AIFamily AF_IPX = (AIFamily)PLATFORM_AF_IPX;
const AIFamily AF_APPLETALK = (AIFamily)PLATFORM_AF_APPLETALK;
const O_NONBLOCK = PLATFORM_O_NONBLOCK;

View File

@@ -1,50 +1,50 @@
module std::net::os @if(env::DARWIN);
import libc;
const AIFlags AI_NUMERICSERV = 0x1000;
const AIFlags AI_ALL = 0x100;
const AIFlags AI_V4MAPPED_CFG = 0x200;
const AIFlags AI_ADDRCONFIG = 0x400;
const AIFlags AI_V4MAPPED = 0x800;
const AIFlags AI_UNUSABLE = 0x10000000;
const AIFlags AI_DEFAULT = AI_V4MAPPED_CFG | AI_ADDRCONFIG;
const AIFlags AI_NUMERICSERV = (AIFlags)0x1000;
const AIFlags AI_ALL = (AIFlags)0x100;
const AIFlags AI_V4MAPPED_CFG = (AIFlags)0x200;
const AIFlags AI_ADDRCONFIG = (AIFlags)0x400;
const AIFlags AI_V4MAPPED = (AIFlags)0x800;
const AIFlags AI_UNUSABLE = (AIFlags)0x10000000;
const AIFlags AI_DEFAULT = AI_V4MAPPED_CFG | AI_ADDRCONFIG;
const AIFamily PLATFORM_AF_IMPLINK = 3;
const AIFamily PLATFORM_AF_PUP = 4;
const AIFamily PLATFORM_AF_CHAOS = 5;
const AIFamily PLATFORM_AF_NS = 6;
const AIFamily PLATFORM_AF_ISO = 7;
const AIFamily PLATFORM_AF_ECMA = 8;
const AIFamily PLATFORM_AF_DATAKIT = 9;
const AIFamily PLATFORM_AF_CCITT = 10;
const AIFamily PLATFORM_AF_SNA = 11;
const AIFamily PLATFORM_AF_DECNET = 12;
const AIFamily PLATFORM_AF_DLI = 13;
const AIFamily PLATFORM_AF_LAT = 14;
const AIFamily PLATFORM_AF_HYLINK = 15;
const AIFamily PLATFORM_AF_APPLETALK = 16;
const AIFamily PLATFORM_AF_ROUTE = 17;
const AIFamily PLATFORM_AF_LINK = 18;
const AIFamily PLATFORM_PSEUDO_AF_XTP = 19;
const AIFamily PLATFORM_AF_COIP = 20;
const AIFamily PLATFORM_AF_CNT = 21;
const AIFamily PLATFORM_PSEUDO_AF_RTIP = 22;
const AIFamily PLATFORM_AF_IPX = 23;
const AIFamily PLATFORM_AF_SIP = 24;
const AIFamily PLATFORM_PSEUDO_AF_PIP = 25;
const AIFamily PLATFORM_AF_NDRV = 27;
const AIFamily PLATFORM_AF_ISDN = 28;
const AIFamily PLATFORM_PSEUDO_AF_KEY = 29;
const AIFamily PLATFORM_AF_INET6 = 30;
const AIFamily PLATFORM_AF_NATM = 31;
const AIFamily PLATFORM_AF_SYSTEM = 32;
const AIFamily PLATFORM_AF_NETBIOS = 33;
const AIFamily PLATFORM_AF_PPP = 34;
const AIFamily PLATFORM_PSEUDO_AF_HDRCMPLT = 35;
const AIFamily PLATFORM_AF_IEEE80211 = 37;
const AIFamily PLATFORM_AF_UTUN = 38;
const AIFamily PLATFORM_AF_VSOCK = 40;
const AIFamily PLATFORM_AF_MAX = 41;
const AIFamily PLATFORM_AF_IMPLINK = (AIFamily)3;
const AIFamily PLATFORM_AF_PUP = (AIFamily)4;
const AIFamily PLATFORM_AF_CHAOS = (AIFamily)5;
const AIFamily PLATFORM_AF_NS = (AIFamily)6;
const AIFamily PLATFORM_AF_ISO = (AIFamily)7;
const AIFamily PLATFORM_AF_ECMA = (AIFamily)8;
const AIFamily PLATFORM_AF_DATAKIT = (AIFamily)9;
const AIFamily PLATFORM_AF_CCITT = (AIFamily)10;
const AIFamily PLATFORM_AF_SNA = (AIFamily)11;
const AIFamily PLATFORM_AF_DECNET = (AIFamily)12;
const AIFamily PLATFORM_AF_DLI = (AIFamily)13;
const AIFamily PLATFORM_AF_LAT = (AIFamily)14;
const AIFamily PLATFORM_AF_HYLINK = (AIFamily)15;
const AIFamily PLATFORM_AF_APPLETALK = (AIFamily)16;
const AIFamily PLATFORM_AF_ROUTE = (AIFamily)17;
const AIFamily PLATFORM_AF_LINK = (AIFamily)18;
const AIFamily PLATFORM_PSEUDO_AF_XTP = (AIFamily)19;
const AIFamily PLATFORM_AF_COIP = (AIFamily)20;
const AIFamily PLATFORM_AF_CNT = (AIFamily)21;
const AIFamily PLATFORM_PSEUDO_AF_RTIP = (AIFamily)22;
const AIFamily PLATFORM_AF_IPX = (AIFamily)23;
const AIFamily PLATFORM_AF_SIP = (AIFamily)24;
const AIFamily PLATFORM_PSEUDO_AF_PIP = (AIFamily)25;
const AIFamily PLATFORM_AF_NDRV = (AIFamily)27;
const AIFamily PLATFORM_AF_ISDN = (AIFamily)28;
const AIFamily PLATFORM_PSEUDO_AF_KEY = (AIFamily)29;
const AIFamily PLATFORM_AF_INET6 = (AIFamily)30;
const AIFamily PLATFORM_AF_NATM = (AIFamily)31;
const AIFamily PLATFORM_AF_SYSTEM = (AIFamily)32;
const AIFamily PLATFORM_AF_NETBIOS = (AIFamily)33;
const AIFamily PLATFORM_AF_PPP = (AIFamily)34;
const AIFamily PLATFORM_PSEUDO_AF_HDRCMPLT = (AIFamily)35;
const AIFamily PLATFORM_AF_IEEE80211 = (AIFamily)37;
const AIFamily PLATFORM_AF_UTUN = (AIFamily)38;
const AIFamily PLATFORM_AF_VSOCK = (AIFamily)40;
const AIFamily PLATFORM_AF_MAX = (AIFamily)41;
const int PLATFORM_O_NONBLOCK = 0x04;

View File

@@ -25,7 +25,7 @@ macro void @loop_over_ai(AddrInfo* ai; @body(NativeSocket fd, AddrInfo* ai))
const Duration POLL_FOREVER = (Duration)-1;
enum PollSubscribe : const ushort
constdef PollSubscribe
{
ANY_READ = os::POLLIN,
PRIO_READ = os::POLLPRI,
@@ -44,7 +44,7 @@ const PollSubscribe SUBSCRIBE_ANY_WRITE = (PollSubscribe)os::POLLOUT;
const PollSubscribe SUBSCRIBE_OOB_WRITE = (PollSubscribe)os::POLLWRBAND;
const PollSubscribe SUBSCRIBE_WRITE = (PollSubscribe)os::POLLWRNORM;
enum PollEvent : const ushort
constdef PollEvent : ushort
{
READ_PRIO = os::POLLPRI,
READ_OOB = os::POLLRDBAND,
@@ -109,12 +109,12 @@ macro Socket new_socket(fd, ai)
enum SocketOption : char (CInt value)
{
REUSEADDR = os::SO_REUSEADDR,
REUSEPORT @if(!env::WIN32) = os::SO_REUSEPORT,
KEEPALIVE = os::SO_KEEPALIVE,
BROADCAST = os::SO_BROADCAST,
OOBINLINE = os::SO_OOBINLINE,
DONTROUTE = os::SO_DONTROUTE,
REUSEADDR { os::SO_REUSEADDR },
REUSEPORT @if(!env::WIN32) { os::SO_REUSEPORT },
KEEPALIVE { os::SO_KEEPALIVE },
BROADCAST { os::SO_BROADCAST },
OOBINLINE { os::SO_OOBINLINE },
DONTROUTE { os::SO_DONTROUTE },
}
fn bool? Socket.get_broadcast(&self) => self.get_option(BROADCAST);
@@ -193,9 +193,9 @@ fn usz? Socket.peek(&self, char[] bytes) @dynamic
enum SocketShutdownHow : (CInt native_value)
{
RECEIVE = env::WIN32 ??? libc::SD_RECEIVE : libc::SHUT_RD,
SEND = env::WIN32 ??? libc::SD_SEND : libc::SHUT_WR,
BOTH = env::WIN32 ??? libc::SD_BOTH : libc::SHUT_RDWR,
RECEIVE { env::WIN32 ??? libc::SD_RECEIVE : libc::SHUT_RD },
SEND { env::WIN32 ??? libc::SD_SEND : libc::SHUT_WR },
BOTH { env::WIN32 ??? libc::SD_BOTH : libc::SHUT_RDWR },
}
fn void? Socket.shutdown(&self, SocketShutdownHow how)

View File

@@ -47,7 +47,7 @@ fn Socket? connect_with_timeout_from_addrinfo(AddrInfo* addrinfo, SocketOption[]
$if env::WIN32:
os::start_wsa()!;
$endif
Clock c = 0;
Clock c;
@loop_over_ai(addrinfo; NativeSocket sockfd, AddrInfo* ai)
{
apply_sockoptions(sockfd, options)!;
@@ -75,7 +75,7 @@ fn Socket? connect_with_timeout_from_addrinfo(AddrInfo* addrinfo, SocketOption[]
{
c = clock::now();
}
Poll poll_request = { sockfd, SUBSCRIBE_ANY_WRITE, 0 };
Poll poll_request = { sockfd, SUBSCRIBE_ANY_WRITE, (PollEvent)0 };
if (!poll((&poll_request)[:1], timeout_left)!)
{
return CONNECTION_TIMED_OUT~;

View File

@@ -7,12 +7,12 @@ import std::encoding::hex;
enum UrlEncodingMode : char (String allowed)
{
UNRESERVED = "-_.~", // section 2.3
PATH = "$&+,/:;=@", // section 3.3
HOST = "!$&'()*+,;=:[]", // section 3.2.2 (also include ':', '[', ']' for ipv6 hosts)
USERPASS = ";:&=+$,", // section 3.2.1
QUERY = "", // section 3.4
FRAGMENT = "$&+,/:;=?@!()*", // section 4.1
UNRESERVED { "-_.~" }, // section 2.3
PATH { "$&+,/:;=@" }, // section 3.3
HOST { "!$&'()*+,;=:[]" }, // section 3.2.2 (also include ':', '[', ']' for ipv6 hosts)
USERPASS { ";:&=+$," }, // section 3.2.1
QUERY { "" }, // section 3.4
FRAGMENT { "$&+,/:;=?@!()*" }, // section 4.1
}
faultdef INVALID_HEX;

View File

@@ -1,35 +1,35 @@
module std::os::android @if(env::ANDROID);
enum LogPriority : (CInt val)
enum AndroidLogPriority : (CInt val)
{
ANDROID_LOG_UNKNOWN = 0,
ANDROID_LOG_DEFAULT = 1,
ANDROID_LOG_VERBOSE = 2,
ANDROID_LOG_DEBUG = 3,
ANDROID_LOG_INFO = 4,
ANDROID_LOG_WARN = 5,
ANDROID_LOG_ERROR = 6,
ANDROID_LOG_FATAL = 7,
ANDROID_LOG_SILENT = 8
ANDROID_LOG_UNKNOWN { 0 },
ANDROID_LOG_DEFAULT { 1 },
ANDROID_LOG_VERBOSE { 2 },
ANDROID_LOG_DEBUG { 3 },
ANDROID_LOG_INFO { 4 },
ANDROID_LOG_WARN { 5 },
ANDROID_LOG_ERROR { 6 },
ANDROID_LOG_FATAL { 7 },
ANDROID_LOG_SILENT { 8 }
}
enum LogId : (CInt val)
enum AndroidLogId : (CInt val)
{
LOG_ID_MIN = 0,
LOG_ID_MAIN = 0,
LOG_ID_RADIO = 1,
LOG_ID_EVENTS = 2,
LOG_ID_SYSTEM = 3,
LOG_ID_CRASH = 4,
LOG_ID_STATS = 5,
LOG_ID_SECURITY = 6,
LOG_ID_KERNEL = 7,
LOG_ID_MAX = 7,
LOG_ID_DEFAULT = 0x7FFFFFFF
LOG_ID_MIN { 0 },
LOG_ID_MAIN { 0 },
LOG_ID_RADIO { 1 },
LOG_ID_EVENTS { 2 },
LOG_ID_SYSTEM { 3 },
LOG_ID_CRASH { 4 },
LOG_ID_STATS { 5 },
LOG_ID_SECURITY { 6 },
LOG_ID_KERNEL { 7 },
LOG_ID_MAX { 7 },
LOG_ID_DEFAULT { 0x7FFFFFFF }
}
struct LogMessage @packed
struct AndroidLogMessage @packed
{
usz struct_size;
CInt buffer_id;
@@ -40,11 +40,11 @@ struct LogMessage @packed
ZString message;
}
extern fn CInt log_write(LogPriority prio, ZString tag, ZString text) @cname("__android_log_write");
extern fn CInt log_print(LogPriority prio, ZString tag, ZString fmt, ...) @cname("__android_log_print");
extern fn CInt log_write(AndroidLogPriority prio, ZString tag, ZString text) @cname("__android_log_write");
extern fn CInt log_print(AndroidLogPriority prio, ZString tag, ZString fmt, ...) @cname("__android_log_print");
extern fn void log_assert(ZString cond, ZString tag, ZString fmt, ...) @cname("__android_log_assert");
fn bool log_id_is_valid(LogId id)
fn bool log_id_is_valid(AndroidLogId id)
{
return id >= LOG_ID_MIN && id < LOG_ID_MAX;
}
@@ -52,13 +52,13 @@ fn bool log_id_is_valid(LogId id)
extern fn CInt log_buf_write(CInt bufID, CInt prio, ZString tag, ZString text) @cname("__android_log_buf_write");
extern fn CInt log_buf_print(CInt bufID, CInt prio, ZString tag, ZString fmt, ...) @cname("__android_log_buf_print");
alias LoggerFunction = fn void(LogMessage* log_message);
alias LoggerFunction = fn void(AndroidLogMessage* log_message);
alias AborterFunction = fn void(ZString abort_message);
extern fn void log_write_log_message(LogMessage log_message) @cname("__android_log_write_log_message");
extern fn void log_write_log_message(AndroidLogMessage log_message) @cname("__android_log_write_log_message");
extern fn void log_set_logger(LoggerFunction logger) @cname("__android_log_set_logger");
extern fn void log_logd_logger(LogMessage log_message) @cname("__android_log_logd_logger");
extern fn void log_stderr_logger(LogMessage log_message) @cname("__android_log_stderr_logger");
extern fn void log_logd_logger(AndroidLogMessage log_message) @cname("__android_log_logd_logger");
extern fn void log_stderr_logger(AndroidLogMessage log_message) @cname("__android_log_stderr_logger");
extern fn void log_set_aborter(AborterFunction aborter) @cname("__android_log_set_aborter");
extern fn void log_call_aborter(ZString abort_message) @cname("__android_log_call_aborter");
extern fn void log_default_aborter(ZString abort_message) @cname("__android_log_default_aborter");

View File

@@ -15,7 +15,7 @@ fn uint num_cpu()
return count;
}
module std::os @if(env::LINUX);
module std::os @if(env::LINUX || env::ANDROID);
import std::os::posix;
fn uint num_cpu()

View File

@@ -18,23 +18,23 @@ const uint EPOLLWAKEUP = EpollEvents.EPOLLWAKEUP;
const uint EPOLLONESHOT = EpollEvents.EPOLLONESHOT;
const uint EPOLLET = EpollEvents.EPOLLET;
enum EpollEvents: const inline uint
constdef EpollEvents : inline uint
{
EPOLLIN = 0x001,
EPOLLPRI = 0x002,
EPOLLOUT = 0x004,
EPOLLRDNORM = 0x040,
EPOLLRDBAND = 0x080,
EPOLLWRNORM = 0x100,
EPOLLWRBAND = 0x200,
EPOLLMSG = 0x400,
EPOLLERR = 0x008,
EPOLLHUP = 0x010,
EPOLLRDHUP = 0x2000,
EPOLLIN = 0x001,
EPOLLPRI = 0x002,
EPOLLOUT = 0x004,
EPOLLRDNORM = 0x040,
EPOLLRDBAND = 0x080,
EPOLLWRNORM = 0x100,
EPOLLWRBAND = 0x200,
EPOLLMSG = 0x400,
EPOLLERR = 0x008,
EPOLLHUP = 0x010,
EPOLLRDHUP = 0x2000,
EPOLLEXCLUSIVE = 1u << 28,
EPOLLWAKEUP = 1u << 29,
EPOLLONESHOT = 1u << 30,
EPOLLET = 1u << 31
EPOLLWAKEUP = 1u << 29,
EPOLLONESHOT = 1u << 30,
EPOLLET = 1u << 31
}
/* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */

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