Compare commits

..

191 Commits

Author SHA1 Message Date
Christoffer Lerno
91a457850b Hack import rules 2025-08-06 20:20:19 +02:00
Christoffer Lerno
1d25197bfd Bitstructs no longer overloadable with bitops. #2374 2025-08-06 14:51:37 +02:00
Zack Puhl
aae873c044 Add bitsizeof to Builtins (#2376)
* Add `bitsizeof` to Builtins

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-06 13:55:39 +02:00
Zack Puhl
6471728ee5 Add @clz CT macro (#2367)
* Add `@clz` CT macro
2025-08-06 13:42:52 +02:00
Christoffer Lerno
29bae1fbd6 Updated like the PR #2375 2025-08-06 11:28:36 +02:00
Christoffer Lerno
9c770f360e Formatter did not properly handle "null" for any, and null for empty faults. #2375 2025-08-06 11:23:56 +02:00
Christoffer Lerno
3b6d68ef21 Compiler segfault when using bitwise not on number literal cast to bitstruct #2373. 2025-08-06 00:55:56 +02:00
Christoffer Lerno
24c03f9800 Fixed bug generating $c += 1 when $c was derived from a pointer but behind a cast. 2025-08-06 00:44:22 +02:00
Christoffer Lerno
ed61b51489 Adding MultiLogger 2025-08-05 18:49:41 +02:00
Christoffer Lerno
0205ee8688 Added the std::core::log for logging. 2025-08-05 18:30:46 +02:00
Christoffer Lerno
abd3585c44 Parsing difference between "0x00." and "0X00." literals #2371 2025-08-05 13:09:53 +02:00
Christoffer Lerno
aa910a1c44 Compiler assert when using generic parameters list without any parameters. #2369 2025-08-05 12:50:36 +02:00
Christoffer Lerno
00b88a8027 Compiler assertion when defining a function with return type untyped_list #2368. In general, improve error message when ct types are return types. 2025-08-05 04:11:54 +02:00
Christoffer Lerno
229fdd6193 Detect recursive creation of generics #2366. 2025-08-05 02:55:32 +02:00
Christoffer Lerno
5292e08cd6 Remove lambda code that should never happen. 2025-08-04 22:13:37 +02:00
Christoffer Lerno
90990ed2f3 Support partial const enums despite use-old-enums enabled. 2025-08-04 17:06:17 +02:00
Christoffer Lerno
c99284103d Fix issue with naked functions #2365 2025-08-04 15:51:49 +02:00
sudokit
b463358add Added dl_iterate_phdr 2025-08-04 14:56:51 +02:00
LowByteFox
0e10b71cbf Stdlib: SingleSizeObjectPool implementation (#2360)
* implement working single size object pool
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-04 14:54:26 +02:00
Christoffer Lerno
cb2d8133e0 Fixed: regression in comments for @deprecated and @pure. 2025-08-04 13:25:30 +02:00
Christoffer Lerno
f2d27229d2 Bug causing a compiler error when parsing a broken lambda inside of an expression. 2025-08-04 12:25:19 +02:00
Zack Puhl
604661b12c Increase Primitive Type Hash Speeds (#2329)
* simplify and add much faster hash functions in key locations
* add benchmark runtime @start and @end macros for better control
* update benchmark reporting and hashmap tests

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-04 11:50:17 +02:00
hamkoroke
440df8415e Support memory mapped files and add File.map (#2321)
* Support memory mapped files and add File.map

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-03 23:26:52 +02:00
Christoffer Lerno
c31c423386 Reduce allocated Vmem for the compiler on 32 bit machines. 2025-08-03 22:58:51 +02:00
Velikiy Kirill
8358af2240 Add LinkedBlockingQueue (#2328)
* Add LinkedBlockingQueue

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-03 22:47:21 +02:00
Christoffer Lerno
4625b457fb Add additional checks for phi to prevent miscompilation. 2025-08-03 17:46:03 +02:00
Christoffer Lerno
151a28a92a Codegen error in if (try x = (false ? io::EOF? : 1)), i.e. using if-try with a CT known value. 2025-08-03 16:28:11 +02:00
Christoffer Lerno
9fe6c77d28 Codegen error in if (try x = (true ? io::EOF? : 1)), i.e. using if-try with a known Empty. 2025-08-03 13:33:53 +02:00
waveproc
1c4f7a4b61 change cmake build instructions in README.md (#2300)
* change cmake build instructions in README.md


---------

Co-authored-by: Your Name <you@example.com>
2025-08-03 00:11:57 +02:00
Velikiy Kirill
f23bbb342c Update release notes to include HashSet and Linked containers 2025-08-02 23:19:55 +02:00
Zack Puhl
91b866c967 Immediately skip empty tests (#2352) 2025-08-02 23:17:34 +02:00
Zack Puhl
483fe62750 OpenBSD Sockets (#2353)
* native file testing for BSD
* basic OpenBSD socket port
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-02 23:15:53 +02:00
Zack Puhl
2a47cc2ca9 Pair and Triple Compare w/ Unit Tests (#2359)
* Pair and Triple Compare w/ Unit Tests
* scope creep myself by adding date-time eq op
* make Pair and Triple printable
* Update releasenotes. Restrict equals on tuples to when underlying type supports `==`. Remove unnecessary Time.eq.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-02 23:11:27 +02:00
Zack Puhl
e707190539 Add @intlog2 for Floored CT log-base2 (#2355)
* Add `@intlog2` for Floored CT log-base2
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-02 22:53:36 +02:00
Christoffer Lerno
cb62554a26 Fix version. 2025-08-02 16:57:39 +02:00
Christoffer Lerno
9c58db99af With avx512, passing a 512 bit vector in a union would be lowered incorrectly, causing an assert. #2362 2025-08-02 16:56:43 +02:00
Christoffer Lerno
90c339ebdb List.remove_at would incorrectly trigger ASAN. 2025-08-02 16:56:43 +02:00
Christoffer Lerno
e3a8a3ec02 Support alias foo = module std::io module aliasing. 2025-08-02 16:56:43 +02:00
Christoffer Lerno
bdbe81fedd Fix libm regression. 2025-08-02 13:22:48 +02:00
Christoffer Lerno
f0142e3b1a Fix json check. 2025-08-01 17:07:37 +02:00
Christoffer Lerno
2c6ff00261 Fix alignment. 2025-08-01 10:55:04 +02:00
Christoffer Lerno
61a21203f4 Assigning string literal to char[<*>] stores pointer rather than characters. #2357 2025-08-01 10:32:53 +02:00
Christoffer Lerno
d7affc5028 Fix regression in file output placement. 2025-08-01 10:13:22 +02:00
Christoffer Lerno
eb9549a818 Release candidate 0.7.4 2025-08-01 00:06:16 +02:00
Christoffer Lerno
6d9906db0a Fix implicit linking from macros when it's not valid to add dependencies. 2025-07-31 16:21:50 +02:00
Christoffer Lerno
334ee975b9 Assignment evaluation order now right->left, following C++17. 2025-07-31 11:49:42 +02:00
Christoffer Lerno
d600d0898c Fix regression, not permitting macro parameters inlined into naked functions. 2025-07-30 10:38:37 +02:00
Christoffer Lerno
44f4efa5aa Do not allow parameters in naked functions. 2025-07-30 01:01:56 +02:00
Christoffer Lerno
8151305701 Fix so that rethrow is detected as well. 2025-07-29 20:50:32 +02:00
Christoffer Lerno
3ac9bfc387 Allow accessing parameters in a naked function, just disallow return, this fixes #1955. 2025-07-29 20:38:53 +02:00
BWindey
51b9cb85bc Fix get_config_dir() implementation (#2348)
* Fix get_config_dir() implementation

* Formatting and fallback to home if the path is empty.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-29 19:11:56 +02:00
Christoffer Lerno
d805ff9782 Lambda deduplication would be incorrect when generated at the global scope. 2025-07-29 17:45:11 +02:00
Christoffer Lerno
fa9ba3f607 Not setting android-ndk resulted in a "set ndk-path" error. 2025-07-29 11:33:40 +02:00
Zack Puhl
9d2be2851b Add @try Builtin (#2333)
* Add @try macro
* Add @try_catch. Update release notes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-29 00:50:56 +02:00
Zack Puhl
2639338426 Support full paths with $embed (#2335)
* Support full paths with `$embed`

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-28 23:44:17 +02:00
Christoffer Lerno
d8daa4ac83 Fix dir check Win32 CI 2025-07-28 23:23:09 +02:00
Christoffer Lerno
6641155892 Lambdas now properly follow its attributes #2346. 2025-07-28 19:49:40 +02:00
Christoffer Lerno
86034353ec Remove unnecessary "ret" in naked functions #2344. 2025-07-28 18:36:33 +02:00
Christoffer Lerno
944ef0fc8d Fix incorrect LLVM output folder. 2025-07-28 18:07:47 +02:00
Christoffer Lerno
194b7c4772 Crash when parsing recursive type declaration #2345. 2025-07-28 18:02:25 +02:00
m0tholith
6963e143a1 Add gitignore template to project creation 2025-07-28 10:39:55 +02:00
Christoffer Lerno
4977bd1d78 @format did not work correctly with macros #2341. 2025-07-28 10:38:18 +02:00
Velikiy Kirill
a21e641748 LinkedHashMap: Fix head being null when initializing from map (#2334)
* LinkedHashMap: Fix head being null when initializing from map
2025-07-28 00:26:14 +02:00
Christoffer Lerno
208b0f6d0e Fix issue where recursively creating a dir would be incorrectly marked as a failure the first time.
Place output in `out` by default for projects. Use temp folder for building at the command line.
2025-07-28 00:19:12 +02:00
Christoffer Lerno
0bc546595d - Using @noreturn in a trailing body macro would not work properly #2326.
- Bug when reporting error in a macro return would crash the compiler #2326.
- Short body return expression would not have the correct span.
2025-07-26 02:03:02 +02:00
Velikiy Kirill
a673b4ad66 Add LinkedHashMap and LinkedHashSet implementations (#2324)
* Add LinkedHashMap and LinkedHashSet implementations

Add two new ordered collection types to std::collections:
- LinkedHashMap: insertion-order preserving hash map
- LinkedHashSet: insertion-order preserving hash set
2025-07-25 23:28:10 +02:00
Velikiy Kirill
9a68a5c063 HashSet: Contract message fixes 2025-07-25 23:06:18 +02:00
Velikiy Kirill
e790df539d Add HashSet implementation (#2322)
* Add HashSet implementation
Add a generic HashSet with full allocator support and standard set operations.
- Basic operations: add/remove/contains/clear
- Set operations:union_set/intersection/symmetric_difference/difference/is_subset
- Memory management with allocator support
- Iteration support
- Automatic resizing with load factor control
* Add "add_all" "add_all_from" "remove_all" "remove_all_from"
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-25 19:53:39 +02:00
Christoffer Lerno
fbeb779335 Fix typo in wait_timeout 2025-07-24 14:12:39 +02:00
Christoffer Lerno
625bfa5713 Updated releasenotes. 2025-07-23 17:04:33 +02:00
Christoffer Lerno
943a294900 Fix missing const cast when casting a const int expression. 2025-07-23 17:03:43 +02:00
Christoffer Lerno
3400dd5e42 char[*] b = *(char[*]*)&a; would crash the compiler if a was a slice. #2320 2025-07-23 16:10:31 +02:00
Christoffer Lerno
e8b3c44de3 Add a "wchar" type. 2025-07-23 15:09:55 +02:00
Christoffer Lerno
9575698fa4 Add String conversion functions snake_case -> PascalCase and vice versa. 2025-07-23 00:26:44 +02:00
Christoffer Lerno
5f0a7dd63e Rename AlignedRef -> UnalignedRef 2025-07-22 15:55:43 +02:00
Kiana
38bc11b7b8 Add libc::readlink() (#2316)
* Add libc::readlink()

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-22 15:46:23 +02:00
LowByteFox
04528aee1f fix openbsd CI not uploading artifacts 2025-07-22 15:44:58 +02:00
LowByteFox
5c82747970 Improved OpenBSD support (#2317)
* add openbsd support, compiles and passses all tests
* fix backtrace
* gh actions should include openbsd artifacts
2025-07-22 15:15:20 +02:00
Christoffer Lerno
b45c337515 Contract fix. 2025-07-22 15:14:10 +02:00
Christoffer Lerno
9dc6b0e660 Added AlignedRef generic type. 2025-07-22 15:11:14 +02:00
Christoffer Lerno
428165590e Add Volatile type. 2025-07-22 14:13:48 +02:00
Christoffer Lerno
53051e04a3 Add thread::fence (from $$fence builtin). Ref and RefCounted types. 2025-07-22 00:37:41 +02:00
Christoffer Lerno
869bcf8b2b Removing use of $assignable and deprecate it. Fix regression for stacktraces on MacOS. Added readline_to_stream. Regression: Chaining an optional together with contracts could in some cases lose the optional. 2025-07-21 03:20:40 +02:00
hamkoroke
382a65abcd Support asm x86 popcnt (#2314)
* Support asm x86 popcnt
2025-07-21 02:40:17 +02:00
Christoffer Lerno
908d705669 Removing use of $assignable and deprecate it. 2025-07-20 20:07:06 +02:00
Airtz
d422fb699f More Ed25519 cleanup (#2315)
* Cleanup Ed25519
2025-07-20 19:57:37 +02:00
Kiana
506e63284b Tidy up path.c3 (#2309)
* Tidy up path.c3
2025-07-20 12:26:44 +02:00
Zack Puhl
ed92476916 Add wyhash2 and metro64/128 modern hashing (#2293)
* add wyhash2, metro64, and metro128 hashes; best performing non-crypto hash functions
* add superfast 64-bit a5hash; not streamed, no 128-bit impl
* add komihash and associated tests/benchmarks
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-20 00:06:10 +02:00
LowByteFox
1218afd51f Add ability to compile on OpenBSD (#2310)
* fix curl in cmake, make c3c compile on OpenBSD, fix typo in OS_TYPE_FREEBSD
2025-07-19 21:09:39 +02:00
Christoffer Lerno
b88722b4a6 Copying const enums and regular enums incorrect #2313. 2025-07-19 21:07:25 +02:00
Christoffer Lerno
694d297eb8 "poison" the current function early when a declaration can't be correctly resolved. 2025-07-19 20:49:26 +02:00
Christian Buttner
2053f2767b Add ConditionVariable.wait_until and ConditionVariable.wait_for (#2302)
* Add `ConditionVariable.wait_until` and `ConditionVariable.wait_for`
* Add "@structlike" for typedefs.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-19 13:12:14 +02:00
Christoffer Lerno
448176b0b7 Rename ed25519 keys/signature. 2025-07-19 10:06:51 +02:00
Airtz
b1a22b5002 Implement Ed25519 (#2297)
* Implement Ed25519
* Overloading addition with a pointer would not work.
* Added @addr macro.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-19 01:13:15 +02:00
Christoffer Lerno
e9aee55714 A distinct type based on an array would yield .len == 0 2025-07-18 21:24:09 +02:00
Christoffer Lerno
2acf3c57c7 Check unaligned array access. 2025-07-18 20:45:57 +02:00
Christoffer Lerno
f2babb6063 Remove requirement of license. 2025-07-18 16:49:04 +02:00
Zack Puhl
b8d07474fe Add SipHash Family of Keyed PRFs (#2287)
* implement SipHash family of keyed PRFs
2025-07-18 16:16:47 +02:00
Christian Buttner
cf913b41c6 Fix Formatter.print returning incorrect size (#2306)
* Fix `Formatter.print` returning incorrect size
2025-07-18 15:54:07 +02:00
Christoffer Lerno
adb3df05c6 Allow even smaller memory limits. 2025-07-18 10:54:03 +02:00
Christoffer Lerno
34bded30eb Fix typo 2025-07-17 15:19:02 +02:00
Christoffer Lerno
774a375ce7 @links on macros would not be added to calling functions. 2025-07-17 15:17:58 +02:00
Christoffer Lerno
ee35001732 Suppress codegen of panic printing with when panic messages are set to "off".
Implicit linking of libc math when libc math functions are used.
2025-07-17 14:45:02 +02:00
Christoffer Lerno
da4105ffb1 Whereami fix 2025-07-16 22:34:14 +02:00
Christoffer Lerno
5c5692ae98 Dev (#2307)
* Refactor whereami
2025-07-16 15:56:36 +02:00
Christoffer Lerno
379d16abe7 Some refactoring of ranges. env::AUTHORS and env::AUTHOR_EMAILS 2025-07-16 12:23:24 +02:00
Christoffer Lerno
078ce38c57 Test fix. 2025-07-16 00:22:28 +02:00
Christoffer Lerno
f99b903d78 - Fix unexpected display of macro definition when passing a poisoned expression #2305. 2025-07-15 23:31:44 +02:00
Christoffer Lerno
3650b81970 Segfault when failing to cast subexpression to 'isz' in pointer subtraction #2305. 2025-07-15 21:47:49 +02:00
Christian Buttner
bb6fcdfa6f Improve atomic.c3
- Change `Atomic.xor` from function to macro
- Allow function pointers as native atomic type
- Use enum inference
2025-07-15 20:32:18 +02:00
Christian Buttner
8df112e157 Add WString.len 2025-07-15 20:30:14 +02:00
Christoffer Lerno
af91f35017 Fix stringify of $vaexpr #2301. 2025-07-15 17:13:10 +02:00
Zack Puhl
3cce90bba1 fix hashmap tinit_with_key_values 2025-07-15 00:26:59 +02:00
Christoffer Lerno
1a351bdb6d Error message for missing arg incorrect for methods with zero args #2296. 2025-07-14 23:21:15 +02:00
Christoffer Lerno
efaac43248 - Virtual memory library.
- New virtual emory arena allocator.
- Fixed resize bug when resizing memory down in ArenaAllocator, DynamicArenaAllocator, BackedArenaAllocator.
- Added feature flag "SLOW_TESTS"
2025-07-14 22:36:43 +02:00
Christoffer Lerno
f082cac762 Updates to API 2025-07-14 03:44:52 +02:00
Christoffer Lerno
2bd289ebd6 Added VirtualMemory 2025-07-14 03:07:03 +02:00
Christoffer Lerno
aba9baf207 Added Vmem allocator 2025-07-13 17:26:57 +02:00
Christoffer Lerno
e755c36ea2 Removed the use of temp allocator in backtrace printing.
Added string::bformat.
2025-07-13 02:58:57 +02:00
Christoffer Lerno
6c7dc2a28e Improved error on using cast expression as lvalue. 2025-07-11 13:47:52 +02:00
Christoffer Lerno
cdd530d807 Fixed bug splatting constants into constants. 2025-07-11 01:55:09 +02:00
Christoffer Lerno
02c0db7b8b Multiline contract comments #2113 2025-07-11 01:18:58 +02:00
Christoffer Lerno
8a62c12089 Update releasenotes with whirlpool 2025-07-10 18:32:26 +02:00
Christoffer Lerno
988549599d $is_const is deprecated in favour of @is_const based on $defined.
`$foo` variables could be assigned non-compile time values.
`$foo[0] = ...` was incorrectly requiring that the assigned values were compile time constants.
2025-07-10 18:31:44 +02:00
Zack Puhl
70159c00cc Add WHIRLPOOL hashing to stdlib (#2273)
* add WHIRLPOOL hashing to stdlib
2025-07-10 16:39:42 +02:00
Christoffer Lerno
2dfbdea889 Update error message for struct initialization #2286 2025-07-10 16:29:49 +02:00
Christoffer Lerno
299d1f530f Correctly poison the analysis after a failed $assert or $error. #2284 2025-07-09 16:51:43 +02:00
Christoffer Lerno
bf0ff8abbc Splat 8 arguments can sometimes cause incorrect behaviour in the compiler. #2283 2025-07-09 16:36:02 +02:00
Ero Mrinin
123b1c8f44 Added unpack macro for Triple (#2277) 2025-07-09 03:11:13 +02:00
Christoffer Lerno
a314e05826 Use hex consistently for .max is 64 bits or more. 2025-07-09 03:09:10 +02:00
Christoffer Lerno
83fd24faa2 Improve error on unsigned implicit conversion to signed. 2025-07-09 02:49:19 +02:00
Christoffer Lerno
1d4ad5f1d5 Function pointers are now compile time constants.
const enum cannot be set to function pointer unless it's a lambda #2282.
2025-07-08 12:42:16 +02:00
Christoffer Lerno
26d5cc694a Formatting option "%h" now supports pointers. 2025-07-08 11:43:49 +02:00
Christoffer Lerno
a2122e0153 Update error message. 2025-07-08 00:24:29 +02:00
Christoffer Lerno
10fc94aaa7 Add RISCV asm print. 2025-07-07 23:52:43 +02:00
Christoffer Lerno
0835bada39 Add --list-asm to view all supported asm instructions. 2025-07-07 23:49:39 +02:00
Christoffer Lerno
277af1a2b6 Fix rdtsc instruction. 2025-07-07 19:02:13 +02:00
Christoffer Lerno
5b835bec3e Fix to codegen when using a bitstruct constant defined using a cast with an operator #2248. 2025-07-07 17:09:32 +02:00
Christoffer Lerno
dc23cef59a LLVM 20 compatibility for test. 2025-07-07 03:01:09 +02:00
Christoffer Lerno
098079d317 Fix analysis error compiling. Fix $define for updated shift. 2025-07-07 02:50:17 +02:00
Christoffer Lerno
1ab57ecf20 Improve contract for readline. #2280 2025-07-07 02:42:48 +02:00
Christoffer Lerno
808ab56545 - Bit shift operation fails with inline uint enum despite matching underlying type #2279. 2025-07-07 02:30:54 +02:00
Christoffer Lerno
19acdc7a19 Do not do certain implicit conversions on enums in binary expression. 2025-07-06 21:44:06 +02:00
Christoffer Lerno
e15fdc709f Improve error message when doing a rethrow in a function that doesn't return an optional.
Array indices are now using int64 internally.
2025-07-06 20:20:42 +02:00
Disheng Su
457244e3de Fix json parser (#2272)
* Fix json parser number

* Fix json parser leading zero

* Fix json parser with duplicated keys

* Fix json parser with trailing characters

* Fix json parser: set recursive depth to 128

* Fix json parser: skip comment to false

* Fix json parser: reject number trailing with null

* Make max depth configurable. Simplify with defer catch. Accept `2.`

* Make max depth configurable. Simplify with defer catch. Accept `2.`

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-06 03:07:46 +02:00
Ero Mrinin
d5559ecafd Tuple update (#2235)
* 'next_float' macro patch

More optimized implementation.

* tuple-type update

* U suffix in 'next_float'

* Do not add triplet, quadruplet, keep Tuple but deprecate. Add unpack

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-06 02:08:59 +02:00
Kiana
802fbfcf1e Add ansi.c3 for AnsiColor support (#2261)
* Add ansi.c3 for AnsiColor support

* Added tests

updated functions to macros
added formatting codes

* Fixed indentation

* Update names. Add plain rgb version. Add runtime colors.

* Update indentation, add 21-29

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-06 01:37:54 +02:00
Christoffer Lerno
a20e74c401 Fixes to thread local pointer handling. 2025-07-06 00:22:21 +02:00
Christoffer Lerno
7cdb1ce9eb Catch accidental foo == BAR; where foo = BAR; was most likely intended. #2274 2025-07-05 20:40:55 +02:00
Christoffer Lerno
0d170a70b6 Make to_float more tolerant to spaces. 2025-07-05 19:42:44 +02:00
Christoffer Lerno
b19cd0b87d Casting a fault to a pointer would trigger an assert. 2025-07-05 01:38:44 +02:00
Christoffer Lerno
50efc95c83 Update sponsors 2025-07-05 00:57:23 +02:00
Christoffer Lerno
fa50268b4e Update max memory. 2025-07-04 17:46:02 +02:00
Christoffer Lerno
ae1d51d089 --max-mem now works correctly again. 2025-07-04 17:06:52 +02:00
Christoffer Lerno
1b8355ff07 Update error message on invalid operator. 2025-07-04 14:33:06 +02:00
Christoffer Lerno
f32afb70b8 Add contract to create thread. 2025-07-04 11:02:23 +02:00
Christoffer Lerno
60d96ca7b7 Initialize pool correctly in print_backtrace. 2025-07-04 02:43:49 +02:00
Christoffer Lerno
014f734260 Remove unused code. 2025-07-04 02:26:01 +02:00
Christoffer Lerno
de4963ef95 Add --echo-prefix to edit the prefix with $echo statements. Supports {FILE} and {LINE} 2025-07-04 01:09:02 +02:00
ryuukk
e7d3e60ebd Disable libxml2 dependency on linux (#2268)
* Disable libxml2 depdendency

* All the linuxes
2025-07-04 00:14:10 +02:00
Christoffer Lerno
a46f73ad24 Const slice indexing was not bounds checked. 2025-07-03 23:53:01 +02:00
Christoffer Lerno
759bc1d909 Const slice lengths were not always detected as constant. 2025-07-03 23:32:02 +02:00
Christoffer Lerno
c79c9dac8d Inline r / complex for complex numbers fixed. 2025-07-03 23:04:16 +02:00
Christoffer Lerno
635d4babc4 Fix tests. 2025-07-03 22:37:23 +02:00
Christoffer Lerno
9b3b4ae8be $for ct-state not properly popped. 2025-07-03 22:20:14 +02:00
Christoffer Lerno
b3e7f074e9 Forgot the fix... 2025-07-03 15:46:35 +02:00
Christoffer Lerno
ee1ed73fc5 Non-const macros may not return untyped lists. 2025-07-03 15:45:14 +02:00
Christoffer Lerno
10e11fb742 Better detect offending cast. 2025-07-03 15:14:24 +02:00
Christoffer Lerno
8b47317ec7 Fix issue with labelled break inside of a $switch. 2025-07-03 13:11:12 +02:00
Christoffer Lerno
04626b72cd Check up the hierarchy when considering if an interface cast is valid #2267. 2025-07-03 12:36:35 +02:00
Christoffer Lerno
2151cd0929 Missing bounds check on upper bound with const ranges foo[1:3]. 2025-07-02 13:12:49 +02:00
Christoffer Lerno
93ded9c1e0 Switch case with const non-int / enum would be treated as ints and crash. #2263 2025-07-02 12:26:26 +02:00
Christoffer Lerno
20964b43ce Fix of const enum resolution order #2264 2025-07-02 12:01:52 +02:00
Christoffer Lerno
af192354fd Comparing a null ZString with a non-null ZString would crash. 2025-07-02 01:22:34 +02:00
Christoffer Lerno
ad48637cbb Correctly reject interface methods type and ptr. 2025-07-01 20:08:46 +02:00
Christoffer Lerno
89507bd335 Improved error messages on missing qualifier on enum value. #2260 2025-07-01 17:08:12 +02:00
Christoffer Lerno
21533ffee4 Update sponsors. 2025-07-01 16:15:38 +02:00
Christoffer Lerno
4502a9286c Fix typeid on compile time types. 2025-07-01 00:27:17 +02:00
Christoffer Lerno
ba11511c69 Update release notes. 2025-07-01 00:03:28 +02:00
Christoffer Lerno
965ef19a5b Allow $typeof to return a compile time type. 2025-07-01 00:02:12 +02:00
Christoffer Lerno
8f86b331c1 Fix --use-old-enums 2025-06-30 23:10:32 +02:00
Christoffer Lerno
59a1590955 Hex string formatter check incorrectly rejected slices. 2025-06-30 21:41:52 +02:00
Christoffer Lerno
fad87b294b mkdir/rmdir would not work properly with substring paths on non-windows platforms. 2025-06-30 21:41:52 +02:00
Christoffer Lerno
13bb2b6690 Const Enums From / to ordinal using casts is back. Add "--use-old-enums", deprecating lookup. 2025-06-30 21:41:52 +02:00
Christoffer Lerno
4a803ed0cf Bump version 2025-06-30 19:33:41 +02:00
Christoffer Lerno
9e80f1b26c Release candidate. 2025-06-30 17:21:32 +02:00
Christoffer Lerno
9299c78747 Detect when a slice on the stack is accidentally returned from a function. 2025-06-30 15:56:19 +02:00
Christoffer Lerno
e1a125e326 - Initial support for #1925, does not affect C compilation yet, and doesn't try to link etc. Using "--emit-only" 2025-06-29 23:50:17 +02:00
cubedium
a13eb99962 Added colored error and warning compiler messages. (#2253)
* Added colored error and warning compiler messages.
* Fixed the warning messages to be colored yellow instead of blue.
* Made the use_ansi function public with compiler_internal.h and toggleable colored error messages with the --ansi flag
* Moved use_ansi declaration. No ansi on test/lsp output.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-06-29 20:22:09 +02:00
Disheng Su
d46733e11a Add string escaping and unescaping functionality (#2243)
* Add `String.escape`, `String.unescape` for escaping and unescaping a string.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-06-29 20:11:11 +02:00
Christian Buttner
ce569462f6 Improve CMakeLists.txt and Windows build 2025-06-29 18:21:11 +02:00
295 changed files with 16544 additions and 2845 deletions

View File

@@ -10,6 +10,7 @@ env:
LLVM_RELEASE_VERSION_WINDOWS: 18
LLVM_RELEASE_VERSION_MAC: 17
LLVM_RELEASE_VERSION_LINUX: 17
LLVM_RELEASE_VERSION_OPENBSD: 19
LLVM_RELEASE_VERSION_UBUNTU22: 17
LLVM_DEV_VERSION: 21
jobs:
@@ -52,9 +53,9 @@ jobs:
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
cd resources/testproject
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32 --trust=full
dir build\llvm\windows-x64
dir out\llvm\windows-x64
..\..\build\${{ matrix.build_type }}\c3c.exe clean
dir build\llvm\windows-x64
dir out\llvm\windows-x64
- name: Build testproject lib
@@ -90,7 +91,7 @@ jobs:
- name: Compile run unit tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1 -D SLOW_TESTS
- name: run compiler tests
run: |
@@ -273,6 +274,7 @@ jobs:
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: CMake18
@@ -287,6 +289,7 @@ jobs:
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
@@ -345,7 +348,7 @@ jobs:
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit
../build/c3c compile-test unit -D SLOW_TESTS
- name: Build testproject
run: |
@@ -439,6 +442,7 @@ jobs:
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: CMake
@@ -453,6 +457,7 @@ jobs:
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
- name: Compile and run some examples
@@ -489,7 +494,7 @@ jobs:
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit --sanitize=address
../build/c3c compile-test unit --sanitize=address -D SLOW_TESTS
- name: Build testproject
run: |
@@ -583,7 +588,7 @@ jobs:
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit
../build/c3c compile-test unit -D SLOW_TESTS
- name: Build testproject
run: |
@@ -665,7 +670,7 @@ jobs:
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit -O1
../build/c3c compile-test unit -O1 -D SLOW_TESTS
- name: Test WASM
run: |
@@ -747,6 +752,119 @@ jobs:
nix build -L ".#c3c-checks"
fi
build-openbsd:
runs-on: ubuntu-latest
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [Release, Debug]
steps:
- uses: actions/checkout@v4
- name: OpenBSD VM
uses: vmactions/openbsd-vm@main
with:
prepare: |
pkg_add cmake llvm-19.1.7p3 ninja
run: |
echo "CMake"
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LLVM_VERSION=${LLVM_RELEASE_VERSION_OPENBSD}
cmake --build build
echo "Compile and run some examples"
cd resources
../build/c3c compile examples/base64.c3
../build/c3c compile examples/binarydigits.c3
../build/c3c compile examples/brainfk.c3
../build/c3c compile examples/factorial_macro.c3
../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile examples/load_world.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
cd ..
echo "Compile and run dynlib-test"
cd resources/examples/dynlib-test
../../../build/c3c -vv dynamic-lib add.c3
mv add.so libadd.so
cc test.c -L. -ladd -Wl,-rpath=.
./a.out
../../../build/c3c compile-run test.c3 -L . -l add -z -Wl,-rpath=.
cd ../../../
echo "Compile and run staticlib-test"
cd resources/examples/staticlib-test
../../../build/c3c -vv static-lib add.c3
mv add.a libadd.a
cc test.c -L. -ladd
./a.out
../../../build/c3c compile-run test.c3 -L . -l add
cd ../../../
echo "Compile run unit tests"
cd test
../build/c3c --max-mem 128 compile-test unit -D SLOW_TESTS
cd ..
echo "Build testproject"
cd resources/testproject
../../build/c3c run -vvv --trust=full
cd ../../
echo "Test WASM"
cd resources/testfragments
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
cd ../../
echo "Build testproject direct linker"
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
cd ../../
echo "Init a library & a project"
./build/c3c init-lib mylib
ls mylib.c3l
./build/c3c init myproject
ls myproject
echo "run compiler tests"
cd test
../build/c3c --max-mem 128 compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
cd ..
- name: bundle_output
run: |
mkdir c3
cp -r lib c3
cp msvc_build_libraries.py c3
cp build/c3c c3
cp README.md c3
cp releasenotes.md c3
tar czf c3-openbsd-${{matrix.build_type}}.tar.gz c3
- name: upload artifacts
uses: actions/upload-artifact@v4
with:
name: c3-openbsd-${{matrix.build_type}}
path: c3-openbsd-${{matrix.build_type}}.tar.gz
release:
runs-on: ubuntu-22.04
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu22]
@@ -769,6 +887,8 @@ jobs:
- run: zip -r c3-windows-debug.zip c3-windows-Debug
- run: mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux-Release/c3-linux.tar.gz
- run: mv c3-linux-Debug/c3-linux-Debug.tar.gz c3-linux-Debug/c3-linux-debug.tar.gz
- run: mv c3-openbsd-Release/c3-openbsd-Release.tar.gz c3-openbsd-Release/c3-openbsd.tar.gz
- run: mv c3-openbsd-Debug/c3-openbsd-Debug.tar.gz c3-openbsd-Debug/c3-openbsd-debug.tar.gz
- run: mv c3-ubuntu-22-Release/c3-ubuntu-22-Release.tar.gz c3-ubuntu-22-Release/c3-ubuntu-22.tar.gz
- run: mv c3-ubuntu-22-Debug/c3-ubuntu-22-Debug.tar.gz c3-ubuntu-22-Debug/c3-ubuntu-22-debug.tar.gz
- run: mv c3-macos-Release/c3-macos-Release.zip c3-macos-Release/c3-macos.zip
@@ -790,6 +910,8 @@ jobs:
c3-windows-debug.zip
c3-linux-Release/c3-linux.tar.gz
c3-linux-Debug/c3-linux-debug.tar.gz
c3-openbsd-Release/c3-openbsd.tar.gz
c3-openbsd-Debug/c3-openbsd-debug.tar.gz
c3-ubuntu-22-Release/c3-ubuntu-22.tar.gz
c3-ubuntu-22-Debug/c3-ubuntu-22-debug.tar.gz
c3-macos-Release/c3-macos.zip

View File

@@ -1,5 +1,13 @@
cmake_minimum_required(VERSION 3.20)
set(C3_LLVM_MIN_VERSION 17)
set(C3_LLVM_MAX_VERSION 21)
set(C3_LLVM_DEFAULT_VERSION 19)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
message(FATAL_ERROR "In-tree build detected, please build in a separate directory")
endif()
# Grab the version
file(READ "src/version.h" ver)
if (NOT ${ver} MATCHES "COMPILER_VERSION \"([0-9]+.[0-9]+.[0-9]+)\"")
@@ -7,8 +15,20 @@ if (NOT ${ver} MATCHES "COMPILER_VERSION \"([0-9]+.[0-9]+.[0-9]+)\"")
endif()
# Set the project and version
project(c3c VERSION ${CMAKE_MATCH_1})
message("C3C version: ${CMAKE_PROJECT_VERSION}")
project(c3c VERSION ${CMAKE_MATCH_1} LANGUAGES C CXX)
message("Configuring C3C ${CMAKE_PROJECT_VERSION} for ${CMAKE_SYSTEM_NAME}")
# Helper functions
function(c3_print_variables)
set(msg "")
foreach(var ${ARGN})
if(msg)
string(APPEND msg " ; ")
endif()
string(APPEND msg "${c3_print_prefix}${var}=\"${${var}}\"")
endforeach()
message(STATUS "${msg}")
endfunction()
# Avoid warning for FetchContent
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
@@ -16,7 +36,7 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
endif()
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
if (MSVC)
if (WIN32)
set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib")
set(CMAKE_INSTALL_BINDIR "c:\\c3c")
else ()
@@ -36,36 +56,41 @@ set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
# Use /MT or /MTd
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
if(MSVC)
message(STATUS "MSVC version ${MSVC_VERSION}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
add_compile_options(/utf-8)
else()
if (true)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O0 -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O0 -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined,address -fno-exceptions")
endif()
add_compile_options(-gdwarf-3 -fno-exceptions)
# add_compile_options(-fsanitize=address,undefined)
# add_link_options(-fsanitize=address,undefined)
endif()
option(C3_LINK_DYNAMIC "link dynamically with LLVM/LLD libs")
# Options
set(C3_LINK_DYNAMIC OFF CACHE BOOL "Link dynamically with LLVM/LLD libs")
set(C3_WITH_LLVM ON CACHE BOOL "Build with LLVM")
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
set(C3_USE_MIMALLOC OFF CACHE BOOL "Use built-in mimalloc")
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
set(C3_USE_TB OFF CACHE BOOL "Use TB")
set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory")
set(C3_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation")
set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF)
option(C3_USE_TB "Use TB" OFF)
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
option(C3_WITH_LLVM "Build with LLVM" ON)
option(C3_LLD_DIR "Use custom LLD directory" "")
option(LLVM_CRT_LIBRARY_DIR "Use custom llvm's compiler-rt directory" "")
set(C3_OPTIONS
C3_LINK_DYNAMIC
C3_WITH_LLVM
C3_LLVM_VERSION
C3_USE_MIMALLOC
C3_MIMALLOC_TAG
C3_USE_TB
C3_LLD_DIR
C3_ENABLE_CLANGD_LSP
LLVM_CRT_LIBRARY_DIR
)
set(C3_USE_MIMALLOC OFF)
if(C3_USE_MIMALLOC)
@@ -83,13 +108,6 @@ endif()
if (NOT WIN32)
find_package(CURL)
endif()
if(C3_WITH_LLVM)
if (NOT C3_LLVM_VERSION STREQUAL "auto")
if (${C3_LLVM_VERSION} VERSION_LESS 17 OR ${C3_LLVM_VERSION} VERSION_GREATER 21)
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
endif()
endif()
endif()
find_package(Git QUIET)
if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
@@ -107,7 +125,6 @@ if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
endif()
# Clangd LSP support
option(C3_ENABLE_CLANGD_LSP "Enable/Disable output of compile commands during generation." OFF)
if(C3_ENABLE_CLANGD_LSP)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
execute_process(
@@ -120,7 +137,7 @@ endif(C3_ENABLE_CLANGD_LSP)
if(C3_WITH_LLVM)
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
if (C3_LLVM_VERSION STREQUAL "auto")
set(C3_LLVM_VERSION "19")
set(C3_LLVM_VERSION ${C3_LLVM_DEFAULT_VERSION})
endif()
FetchContent_Declare(
LLVM_Windows
@@ -139,6 +156,7 @@ if(C3_WITH_LLVM)
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)
@@ -149,7 +167,7 @@ if(C3_WITH_LLVM)
#
# Because of CMAKE_FIND_PACKAGE_SORT_ORDER CMAKE_FIND_PACKAGE_SORT_DIRECTION,
# the newest version will always be found first.
message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}")
c3_print_variables(CMAKE_PREFIX_PATH)
if (DEFINED LLVM_DIR)
message(STATUS "Looking for LLVM CMake files in user-specified directory ${LLVM_DIR}")
else()
@@ -176,12 +194,15 @@ if(C3_WITH_LLVM)
list(APPEND LLVM_LIBRARY_DIRS /usr/lib)
endif()
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
message(STATUS "LLVM libraries located in: ${LLVM_LIBRARY_DIRS}")
if (NOT LLVM_PACKAGE_VERSION VERSION_GREATER_EQUAL 15.0)
message(FATAL_ERROR "LLVM version 15.0 or later is required.")
if (${LLVM_PACKAGE_VERSION} VERSION_LESS C3_LLVM_MIN_VERSION OR
${LLVM_PACKAGE_VERSION} VERSION_GREATER C3_LLVM_MAX_VERSION)
message(FATAL_ERROR "LLVM ${LLVM_PACKAGE_VERSION} is not supported! LLVM version between ${C3_LLVM_MIN_VERSION} and ${C3_LLVM_MAX_VERSION} is required.")
endif()
if(LLVM_ENABLE_RTTI)
@@ -231,44 +252,43 @@ if(C3_WITH_LLVM)
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
if(NOT ${C3_LLD_DIR} EQUAL "" AND EXISTS ${C3_LLD_DIR})
message("C3_LLD_DIR: " ${C3_LLD_DIR})
set(LLVM_LIBRARY_DIRS
"${LLVM_LIBRARY_DIRS}"
"${C3_LLD_DIR}"
)
list(APPEND LLVM_LIBRARY_DIRS ${C3_LLD_DIR})
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
endif()
message(STATUS "Looking for static lld libraries in ${LLVM_LIBRARY_DIRS}")
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.dll.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.dll.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.dll.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.dll.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.dll.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COFF NAMES liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.dll.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.dll.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.dll.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
else()
set(LLD_MACHO "")
endif()
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.dll.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.dll.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
else()
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
message(STATUS "Looking for shared lld libraries in ${LLVM_LIBRARY_DIRS}")
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
set(llvm_libs ${LLVM})
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
else()
set(LLD_MACHO "")
endif()
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
endif()
endif()
if (NOT(${CMAKE_BINARY_DIR} EQUAL ${CMAKE_SOURCE_DIR}))
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
endif()
if(C3_WITH_LLVM)
find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
# find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(lld_libs
${LLD_COFF}
${LLD_WASM}
@@ -293,8 +313,8 @@ if(C3_WITH_LLVM)
)
endif()
message(STATUS "linking to llvm libs ${lld_libs}")
message(STATUS "Found lld libs ${lld_libs}")
message(STATUS "Linking to llvm libs ${llvm_libs}")
message(STATUS "Linking to lld libs ${lld_libs}")
endif()
add_library(miniz STATIC dependencies/miniz/miniz.c)
@@ -412,6 +432,11 @@ if(C3_WITH_LLVM)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=1)
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
if (MSVC)
target_compile_options(c3c PRIVATE
"$<$<CONFIG:Debug>:/EHa>"
"$<$<CONFIG:Release>:/EHsc>")
endif()
else()
target_sources(c3c PRIVATE src/utils/hostinfo.c)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0)
@@ -495,7 +520,7 @@ endif ()
if (CURL_FOUND)
target_link_libraries(c3c ${CURL_LIBRARIES})
target_include_directories(c3c PRIVATE ${CURL_INCLUDES})
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)
@@ -503,34 +528,27 @@ endif()
if(MSVC)
message("Adding MSVC options")
target_compile_options(c3c PRIVATE /wd4068 /wd4090 /WX /Wv:18)
target_compile_options(c3c PRIVATE
/wd4068
/wd4090
/WX
/Wv:18
)
if(C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /wd4624 /wd4267 /wd4244 /WX /Wv:18)
target_compile_options(c3c_wrappers PUBLIC
/wd4624
/wd4267
/wd4244
/WX
/Wv:18
)
if(NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PUBLIC /GR-)
endif()
target_link_options(c3c_wrappers PUBLIC /ignore:4099)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(c3c PUBLIC /MTd)
if (C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /MTd)
endif()
target_compile_options(miniz PUBLIC /MTd)
if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MTd)
endif()
else()
target_compile_options(c3c PUBLIC /MT)
if (C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /MT)
endif()
target_compile_options(miniz PUBLIC /MT)
if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MT)
endif()
endif()
if(C3_WITH_LLVM)
set(clang_lib_dir ${llvm_dir}/lib/clang/${C3_LLVM_VERSION}/lib/windows)
set(sanitizer_runtime_libraries
@@ -540,13 +558,20 @@ if(MSVC)
${clang_lib_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
endif()
else()
message(STATUS "using gcc/clang warning switches")
target_link_options(c3c PRIVATE -pthread)
if (C3_WITH_LLVM AND NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PRIVATE -fno-rtti)
endif()
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)
target_compile_options(c3c PRIVATE
-pthread
-Wall
-Werror
-Wno-unknown-pragmas
-Wno-unused-result
-Wno-unused-function
-Wno-unused-variable
-Wno-unused-parameter
)
target_link_options(c3c PRIVATE -pthread)
endif()
install(TARGETS c3c DESTINATION bin)
@@ -557,6 +582,12 @@ if (NOT WIN32)
install(FILES c3c.1 DESTINATION "share/man/man1")
endif()
# Copy stdlib
if (NOT ${CMAKE_BINARY_DIR} EQUAL ${CMAKE_SOURCE_DIR})
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
endif()
if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
add_custom_command(TARGET c3c POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E rm -rf -- $<TARGET_FILE_DIR:c3c>/c3c_rt
@@ -576,3 +607,35 @@ if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
endif()
feature_summary(WHAT ALL)
message(STATUS "Building ${CMAKE_PROJECT_NAME} with the following configuration:")
set(c3_print_prefix " ")
foreach(option IN LISTS C3_OPTIONS)
if (DEFINED ${option})
c3_print_variables(${option})
endif()
endforeach()
foreach(flag_var
CMAKE_BUILD_TYPE
CMAKE_C_COMPILER
CMAKE_CXX_COMPILER
CMAKE_LINKER
CMAKE_OBJCOPY
CMAKE_STRIP
CMAKE_DLLTOOL)
c3_print_variables(${flag_var})
endforeach()
message(STATUS "Build flags:")
foreach(flag_var
CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
c3_print_variables(${flag_var})
endforeach()
message(STATUS "Output to: \"${CMAKE_BINARY_DIR}\"")

57
CMakePresets.json Normal file
View File

@@ -0,0 +1,57 @@
{
"version": 3,
"configurePresets": [
{
"name": "windows-base",
"hidden": true,
"architecture": {
"value": "x64"
},
"toolset": {
"value": "host=x64"
}
},
{
"name": "windows-vs-2022-release",
"generator": "Visual Studio 17 2022",
"displayName": "Windows x64 Visual Studio 17 2022",
"inherits": "windows-base",
"binaryDir": "build",
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Release;RelWithDebInfo",
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "windows-vs-2022-debug",
"generator": "Visual Studio 17 2022",
"displayName": "Windows x64 Visual Studio 17 2022 (Debug)",
"inherits": "windows-base",
"binaryDir": "build-debug",
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Debug",
"CMAKE_BUILD_TYPE": "Debug"
}
}
],
"buildPresets": [
{
"name": "windows-vs-2022-debug",
"displayName": "Debug",
"configurePreset": "windows-vs-2022-debug",
"configuration": "Debug"
},
{
"name": "windows-vs-2022-release",
"displayName": "Release",
"configurePreset": "windows-vs-2022-release",
"configuration": "Release"
},
{
"name": "windows-vs-2022-release-with-debug-info",
"displayName": "RelWithDebInfo",
"configurePreset": "windows-vs-2022-release",
"configuration": "RelWithDebInfo"
}
]
}

View File

@@ -12,6 +12,7 @@ Precompiled binaries for the following operating systems are available:
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
- OpenBSD x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-openbsd.tar.gz), [install instructions](#installing-on-openbsd-with-precompiled-binaries).
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
@@ -141,7 +142,7 @@ fn void main()
### Current status
The current stable version of the compiler is **version 0.7.2**.
The current stable version of the compiler is **version 0.7.4**.
The upcoming 0.7.x releases will focus on expanding the standard library,
fixing bugs and improving compile time analysis.
@@ -176,12 +177,14 @@ The compiler is currently verified to compile on Linux, Windows and MacOS.
| NetBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| NetBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x64 | Yes* | Yes | Yes* | Yes | Untested | Yes* |
| MCU x86 | No | Untested | No | No | No | Yes* |
| Wasm32 | No | Yes | No | No | No | No |
| Wasm64 | No | Untested | No | No | No | No |
*\* Inline asm is still a work in progress*
*\* Inline asm is still a work in progress*<br>
*\* OpenBSD 7.7 is the only tested version*<br>
*\* OpenBSD has limited stacktrace, needs to be tested further*
More platforms will be supported in the future.
@@ -227,6 +230,14 @@ This installs the latest prerelease build, as opposed to the latest released ver
(*Note that there is a known issue with debug symbol generation on MacOS 13, see [issue #1086](https://github.com/c3lang/c3c/issues/1086))
#### Installing on OpenBSD with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-openbsd.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-openbsd.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-openbsd-debug.tar.gz))
2. Unpack executable and standard lib.
3. Run `./c3c`.
(*Note that this is specifically for OpenBSD 7.7, running it on any other version is prone to ABI breaks)
#### Installing on Arch Linux
Arch includes c3c in the official 'extra' repo. It can be easily installed the usual way:
@@ -265,14 +276,15 @@ See the `build-with-docker.sh` script for more information on other configurable
#### Installing on OS X using Homebrew
2. Install CMake: `brew install cmake`
3. Install LLVM 17+: `brew install llvm`
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
5. Enter the C3C directory `cd c3c`.
6. Create a build directory `mkdir build`
7. Change directory to the build directory `cd build`
8. Set up CMake build for debug: `cmake ..`
9. Build: `cmake --build .`
1. Install [Homebrew](https://brew.sh/)
2. Install LLVM 17+: `brew install llvm`
3. Install lld: `brew install lld`
4. Install CMake: `brew install cmake`
5. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
6. Enter the C3C directory `cd c3c`.
7. Set up CMake build for debug: `cmake -B build -S .`
8. Build: `cmake --build build`
9. Change directory to the build directory `cd build`
#### Installing on Windows using Scoop
@@ -313,28 +325,35 @@ called `hello_world` or `hello_world.exe`depending on platform.
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++"
2. Install CMake
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Set up the CMake build `cmake -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release`
6. Build: `cmake --build build --config Release`
7. You should now have the c3c.exe
4. Enter the C3C directory: `cd c3c`.
5. Set up the CMake build: `cmake --preset windows-vs-2022-release`
6. Build: `cmake --build --preset windows-vs-2022-release`
You should now have a `c3c` executable.
You should now have a `c3c` executable in `build\Release`.
You can try it out by running some sample code: `c3c.exe compile ../resources/examples/hash.c3`
You can try it out by running some sample code: `c3c.exe compile ../../resources/examples/hash.c3`
Building `c3c` using Visual Studio Code is also supported when using the `CMake Tools` extension. Simply select the `Windows x64 Visual Studio 17 2022` configure preset and build.
*Note that if you run into linking issues when building, make sure that you are using the latest version of VS17.*
#### Compiling on Windows (Debug)
Debug build requires a different set of LLVM libraries to be loaded for which a separate CMake configuration is used to avoid conflicts.
1. Configure: `cmake --preset windows-vs-2022-debug`
2. Build: `cmake --build --preset windows-vs-2022-debug`
You should now have a `c3c` executable in `build-debug\Debug`.
#### Compiling on Ubuntu 24.04 LTS
1. Make sure you have a C compiler that handles C11 and a C++ compiler, such as GCC or Clang. Git also needs to be installed.
2. Install LLVM 18 `sudo apt-get install cmake git clang zlib1g zlib1g-dev libllvm18 llvm llvm-dev llvm-runtime liblld-dev liblld-18 libpolly-18-dev`
2. Install LLVM 18 `sudo apt-get install cmake git clang zlib1g zlib1g-dev libllvm18 llvm llvm-dev llvm-runtime liblld-dev liblld-18 libpolly-18-dev`. If you're using Ubuntu 25.04, also install `libpolly-20-dev`.
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build`
6. Change directory to the build directory `cd build`
7. Set up CMake build: `cmake ..`
8. Build: `cmake --build .`
5. Set up CMake build: `cmake -B build -S .`
6. Build: `cmake --build build`
7. Change directory to the build directory `cd build`
You should now have a `c3c` executable.
@@ -347,13 +366,12 @@ You can try it out by running some sample code: `./c3c compile ../resources/exam
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone instead: `git clone https://github.com/c3lang/c3c.git --depth=1`
3. Enter the directory: `cd c3c`
4. Create a build directory: `mkdir build`
5. Enter the build directory: `cd build`
6. Create the CMake build cache: `cmake ..`
7. Build: `cmake --build .`
4. Create the CMake build cache: `cmake -B build -S .`
5. Build: `cmake --build build`
6. Enter the build directory: `cd build`
Your c3c executable should have compiled properly. You may want to test it: `./c3c compile ../resources/examples/hash.c3`
For a sytem-wide installation, run the following as root: `cmake --install .`
For a system-wide installation, run the following as root: `cmake --install .`
#### Compiling on Fedora
@@ -363,15 +381,15 @@ For a sytem-wide installation, run the following as root: `cmake --install .`
3. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
4. Enter the C3C directory: `cd c3c`
5. Create a build directory and navigate into it: `mkdir build && cd build`
6. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake .. -DC3_LINK_DYNAMIC=1`
7. Build the project: `cmake --build .`
5. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake -B build -S . -DC3_LINK_DYNAMIC=1`
6. Build the project: `cmake --build build`
7. Enter the build directory: `cd build`
The c3c binary should be created in the build directory. You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
#### Compiling on Arch Linux
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm`
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm libxml2`
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
3. Enter the C3C directory: `cd c3c`
@@ -381,7 +399,7 @@ cmake -B build \
-D C3_LINK_DYNAMIC=ON \
-D CMAKE_BUILD_TYPE=Release
```
5. Build the project: `make -C build`.
5. Build the project: `cmake --build build`.
After compilation, the `c3c` binary will be located in the `build` directory. You can test it by compiling an example: `./build/c3c compile resources/examples/ls.c3`.
@@ -393,11 +411,10 @@ After compilation, the `c3c` binary will be located in the `build` directory. Yo
2. Install or compile LLVM and LLD *libraries* (version 17+ or higher)
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build`
6. Change directory to the build directory `cd build`
7. Set up CMake build for debug: `cmake ..`. At this point you may need to manually
provide the link path to the LLVM CMake directories, e.g. `cmake -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/ ..`
8. Build: `cmake --build .`
5. Set up CMake build for debug: `cmake -B build -S .`. At this point you may need to manually
provide the link path to the LLVM CMake directories, e.g. `cmake -B build -S . -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/`
6. Build: `cmake --build build`
7. Change directory to the build directory `cd build`
*A note on compiling for Linux/Unix/MacOS: to be able to fetch vendor libraries
libcurl is needed. The CMake script should detect it if it is available. Note that
@@ -425,7 +442,12 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
A huge **THANK YOU** goes out to all contributors and sponsors.
A special thank you to sponsors [Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad) for going the extra mile.
A special thank you to sponsors [Zack Puhl](https://github.com/NotsoanoNimus) and [konimarti](https://github.com/konimarti) for going the extra mile.
And honorable mention goes to past sponsors:
[Ygor Pontelo](https://github.com/ygorpontelo), [Simone Raimondi](https://github.com/SRaimondi),
[Jan Válek](https://github.com/jan-valek), [Pierre Curto](https://github.com/pierrec),
[Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad)
## Star History

View File

@@ -0,0 +1,218 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// Some benchmark test ideas are sourced from this article on C++ hashmap benchmarking:
// https://martin.ankerl.com/2022/08/27/hashmap-bench-01/
//
module hashmap_benchmarks;
import std::collections::map;
import std::math::random;
const DEFAULT_ITERATIONS = 16384;
Lcg64Random rand;
HashMap { int, int } modifying_numbers_random;
fn void bench_setup() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(DEFAULT_ITERATIONS);
// TODO: Cannot take the address of a @benchmark function. If we could, we could pass &insert_erase as a fn ptr and use the $qnameof CT eval internally.
set_benchmark_func_iterations($qnameof(insert_erase), 32);
set_benchmark_func_iterations($qnameof(random_access), 1024);
random::seed(&rand, 0x4528_21e6_38d0_1377);
for (usz i = 0; i < 1_000; ++i) modifying_numbers_random.set(rand.next_int(), rand.next_int());
}
// ==============================================================================================
module hashmap_benchmarks @benchmark;
import std::collections::map;
import std::math::random;
import std::encoding::base64;
fn void generic_hash_speeds()
{
(char){}.hash();
(char[<100>]){}.hash();
(char[100]){}.hash();
(ichar){}.hash();
(ichar[<100>]){}.hash();
(ichar[100]){}.hash();
(short){}.hash();
(short[<100>]){}.hash();
(short[100]){}.hash();
(ushort){}.hash();
(ushort[<100>]){}.hash();
(ushort[100]){}.hash();
(int){}.hash();
(int[<100>]){}.hash();
(int[100]){}.hash();
(uint){}.hash();
(uint[<100>]){}.hash();
(uint[100]){}.hash();
(long){}.hash();
(long[<20>]){}.hash();
(long[100]){}.hash();
(ulong){}.hash();
(ulong[<20>]){}.hash();
(ulong[100]){}.hash();
(int128){}.hash();
(int128[<20>]){}.hash();
(int128[100]){}.hash();
(uint128){}.hash();
(uint128[<20>]){}.hash();
(uint128[100]){}.hash();
(bool){}.hash();
(bool[<100>]){}.hash();
(bool[100]){}.hash();
String x = "abc";
char[] y = "abc";
assert(x.hash() == y.hash());
String z1 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
char[] z2 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
assert(z1.hash() == z2.hash());
assert(int.typeid.hash());
}
fn void hash_speeds_of_many_random_values() => @pool()
{
var $arrsz = 10_000;
uint fake_checksum;
char[] chars = allocator::new_array(tmem, char, $arrsz)[:$arrsz];
foreach (&v : chars) *v = (char)random::next(&rand, uint.max);
ushort[] shorts = allocator::new_array(tmem, ushort, $arrsz)[:$arrsz];
foreach (&v : shorts) *v = (ushort)random::next(&rand, uint.max);
uint[] ints = allocator::new_array(tmem, uint, $arrsz)[:$arrsz];
foreach (&v : ints) *v = random::next(&rand, uint.max);
ulong[] longs = allocator::new_array(tmem, ulong, $arrsz)[:$arrsz];
foreach (&v : longs) *v = (ulong)random::next(&rand, uint.max);
uint128[] vwideints = allocator::new_array(tmem, uint128, $arrsz)[:$arrsz];
foreach (&v : vwideints) *v = (uint128)random::next(&rand, uint.max);
char[48][] zstrs = allocator::new_array(tmem, char[48], $arrsz)[:$arrsz];
String[$arrsz] strs;
foreach (x, &v : zstrs)
{
foreach (&c : (*v)[:random::next(&rand, 48)]) *c = (char)random::next(&rand, char.max);
strs[x] = ((ZString)&v[0]).str_view();
}
runtime::@start_benchmark();
foreach (v : chars) fake_checksum += v.hash();
foreach (v : shorts) fake_checksum += v.hash();
foreach (v : ints) fake_checksum += v.hash();
foreach (v : longs) fake_checksum += v.hash();
foreach (v : vwideints) fake_checksum += v.hash();
foreach (v : strs) fake_checksum += v.hash();
runtime::@end_benchmark();
}
fn void modifying_numbers_init_from_map() => @pool()
{
HashMap { int, int } v;
v.tinit_from_map(&modifying_numbers_random);
v.free();
}
fn void insert_erase() => @pool()
{
uint iters = 1_000_000;
HashMap { int, int } v;
v.tinit();
runtime::@start_benchmark();
for (int i = 0; i < iters; ++i) v[i] = i;
for (int i = 0; i < iters; ++i) v.remove(i);
runtime::@end_benchmark();
v.free();
}
fn void random_access() => @pool()
{
HashMap { int, int } v;
v.tinit();
uint bound = 10_000;
usz pseudo_checksum = 0;
for (uint i = 0; i < bound; ++i) v[i] = i;
runtime::@start_benchmark();
for (uint i = 0; i < 1_000_000; ++i) pseudo_checksum += (v[i.hash() % bound] ?? 0);
runtime::@end_benchmark();
v.free();
}
fn void random_access_erase() => @pool()
{
HashMap { int, int } v;
v.tinit();
uint bound = 10_000;
for (uint i = 0; i < bound; ++i) v[i] = i;
runtime::@start_benchmark();
for (uint i = 0; i < bound; ++i)
{
v[i.hash() % bound] = i; // supplant an entry
v.remove(random::next(&rand, bound)); // remove a random entry
}
runtime::@end_benchmark();
v.free();
}
fn void random_access_string_keys() => @pool()
{
HashMap { String, ulong } v;
v.tinit();
usz pseudo_checksum = 0;
String[5_000] saved;
for (usz i = 0; i < saved.len; ++i)
{
ulong hash = i.hash();
String b64key = base64::tencode(@as_char_view(hash));
v[b64key] = hash;
if (i < saved.len) saved[i] = b64key;
}
runtime::@start_benchmark();
for (usz i = 0; i < saved.len; ++i)
{
pseudo_checksum += v[ saved[random::next(&rand, saved.len)] ]!! % 512;
}
runtime::@end_benchmark();
v.free();
}

View File

@@ -0,0 +1,94 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module non_crypto_benchmarks;
const usz COMMON_ITERATIONS = 1 << 18;
const char[] COMMON_1 = { 0xA5 };
const char[] COMMON_4 = { 0xA5, 0xA5, 0xA5, 0xA5, };
const char[] COMMON_8 = { [0..7] = 0xA5 };
const char[] COMMON_16 = { [0..15] = 0xA5 };
const char[] COMMON_32 = { [0..31] = 0xA5 };
const char[] COMMON_64 = { [0..63] = 0xA5 };
const char[] COMMON_128 = { [0..127] = 0xA5 };
const char[] COMMON_1024 = { [0..1023] = 0xA5 };
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(COMMON_ITERATIONS + 3);
}
// =======================================================================================
module non_crypto_benchmarks @benchmark;
import std::hash;
fn void fnv64a_1() => fnv64a::hash(COMMON_1);
fn void fnv32a_1() => fnv32a::hash(COMMON_1);
fn void wyhash2_1() => wyhash2::hash(COMMON_1);
fn void metro64_1() => metro64::hash(COMMON_1);
fn void metro128_1() => metro128::hash(COMMON_1);
fn void a5hash_1() => a5hash::hash(COMMON_1);
fn void komi_1() => komi::hash(COMMON_1);
fn void fnv64a_4() => fnv64a::hash(COMMON_4);
fn void fnv32a_4() => fnv32a::hash(COMMON_4);
fn void wyhash2_4() => wyhash2::hash(COMMON_4);
fn void metro64_4() => metro64::hash(COMMON_4);
fn void metro128_4() => metro128::hash(COMMON_4);
fn void a5hash_4() => a5hash::hash(COMMON_4);
fn void komi_4() => komi::hash(COMMON_4);
fn void fnv64a_8() => fnv64a::hash(COMMON_8);
fn void fnv32a_8() => fnv32a::hash(COMMON_8);
fn void wyhash2_8() => wyhash2::hash(COMMON_8);
fn void metro64_8() => metro64::hash(COMMON_8);
fn void metro128_8() => metro128::hash(COMMON_8);
fn void a5hash_8() => a5hash::hash(COMMON_8);
fn void komi_8() => komi::hash(COMMON_8);
fn void fnv64a_16() => fnv64a::hash(COMMON_16);
fn void fnv32a_16() => fnv32a::hash(COMMON_16);
fn void wyhash2_16() => wyhash2::hash(COMMON_16);
fn void metro64_16() => metro64::hash(COMMON_16);
fn void metro128_16() => metro128::hash(COMMON_16);
fn void a5hash_16() => a5hash::hash(COMMON_16);
fn void komi_16() => komi::hash(COMMON_16);
fn void fnv64a_32() => fnv64a::hash(COMMON_32);
fn void fnv32a_32() => fnv32a::hash(COMMON_32);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_32() => metro64::hash(COMMON_32);
fn void metro128_32() => metro128::hash(COMMON_32);
fn void a5hash_32() => a5hash::hash(COMMON_32);
fn void komi_32() => komi::hash(COMMON_32);
fn void fnv64a_64() => fnv64a::hash(COMMON_64);
fn void fnv32a_64() => fnv32a::hash(COMMON_64);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_64() => metro64::hash(COMMON_64);
fn void metro128_64() => metro128::hash(COMMON_64);
fn void a5hash_64() => a5hash::hash(COMMON_64);
fn void komi_64() => komi::hash(COMMON_64);
fn void fnv64a_128() => fnv64a::hash(COMMON_128);
fn void fnv32a_128() => fnv32a::hash(COMMON_128);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_128() => metro64::hash(COMMON_128);
fn void metro128_128() => metro128::hash(COMMON_128);
fn void a5hash_128() => a5hash::hash(COMMON_128);
fn void komi_128() => komi::hash(COMMON_128);
fn void fnv64a_1024() => fnv64a::hash(COMMON_1024);
fn void fnv32a_1024() => fnv32a::hash(COMMON_1024);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_1024() => metro64::hash(COMMON_1024);
fn void metro128_1024() => metro128::hash(COMMON_1024);
fn void a5hash_1024() => a5hash::hash(COMMON_1024);
fn void komi_1024() => komi::hash(COMMON_1024);

View File

@@ -91,7 +91,7 @@ macro Type Atomic.or(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT
return @atomic_exec(atomic::fetch_or, data, value, ordering);
}
fn Type Atomic.xor(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
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);
@@ -171,6 +171,7 @@ macro bool is_native_atomic_type($Type)
$case SIGNED_INT:
$case UNSIGNED_INT:
$case POINTER:
$case FUNC:
$case FLOAT:
$case BOOL:
return true;
@@ -193,7 +194,7 @@ macro bool is_native_atomic_type($Type)
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr + y) : "+ must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -213,7 +214,7 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr - y) : "- must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -232,13 +233,13 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr * y) : "* must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
@@ -272,13 +273,13 @@ 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 $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
@@ -313,7 +314,7 @@ macro fetch_div(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 $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -330,7 +331,7 @@ macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "^ must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -347,7 +348,7 @@ macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "& must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -364,13 +365,13 @@ macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift right must be an integer"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
@@ -406,13 +407,13 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift left must be an integer"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
@@ -447,7 +448,7 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -455,7 +456,7 @@ macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
$typeof(*ptr) new_value = true;
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
do
{
@@ -474,7 +475,7 @@ macro flag_set(ptr, 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 types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -482,7 +483,7 @@ macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
$typeof(*ptr) new_value = false;
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
do
{
@@ -502,7 +503,7 @@ macro flag_clear(ptr, 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) : "Only values that are comparable with > may be used"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -521,7 +522,7 @@ macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr > y) : "Only values that are comparable with > may be used"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{

View File

@@ -90,7 +90,7 @@ macro HashMap* HashMap.init_with_key_values(&self, Allocator allocator, ..., uin
*>
macro HashMap* HashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.tinit_with_key_values(tmem, capacity, load_factor);
return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
}
<*
@@ -195,7 +195,7 @@ fn Entry*? HashMap.get_entry(&map, Key key)
<*
Get the value or update and
@require $assignable(#expr, Value)
@require @assignable_to(#expr, Value)
*>
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
{
@@ -229,15 +229,15 @@ fn bool HashMap.has_key(&map, Key key)
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
switch (map.allocator.ptr)
{
case &dummy:
map.init(mem);
case null:
map.tinit();
default:
break;
}
switch (map.allocator.ptr)
{
case &dummy:
map.init(mem);
case null:
map.tinit();
default:
break;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
@@ -421,7 +421,7 @@ fn usz? HashMap.to_format(&self, Formatter* f) @dynamic
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s: %s", entry.key, entry.value)!;
};
};
return len + f.print(" }");
}

View File

@@ -0,0 +1,636 @@
<*
@require $defined((Value){}.hash()) : `No .hash function found on the value`
*>
module std::collections::set {Value};
import std::math;
import std::io @norecurse;
const uint DEFAULT_INITIAL_CAPACITY = 16;
const uint MAXIMUM_CAPACITY = 1u << 31;
const float DEFAULT_LOAD_FACTOR = 0.75;
const Allocator SET_HEAP_ALLOCATOR = (Allocator)&dummy;
<* Copy the ONHEAP allocator to initialize to a set that is heap allocated *>
const HashSet ONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
struct Entry
{
uint hash;
Value value;
Entry* next;
}
struct HashSet (Printable)
{
Entry*[] table;
Allocator allocator;
usz count; // Number of elements
usz threshold; // Resize limit
float load_factor;
}
fn int HashSet.len(&self) @operator(len) => (int) self.count;
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
self.threshold = (usz) (capacity * load_factor);
self.load_factor = load_factor;
self.table = allocator::new_array(allocator, Entry*, capacity);
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(tmem, capacity, load_factor) @inline;
}
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro HashSet* HashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
$for var $i = 0; $i < $vacount; $i++:
self.add($vaarg[$i]);
$endfor
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro HashSet* HashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
}
<*
@param [in] values : "The values for the HashSet"
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
foreach (v : values) self.add(v);
return self;
}
<*
@param [in] values : "The values for the HashSet entries"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_from_values(tmem, values, capacity, load_factor);
}
<*
Has this hash set been initialized yet?
@param [&in] set : "The hash set we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool HashSet.is_initialized(&set)
{
return set.allocator && set.allocator.ptr != &dummy;
}
<*
@param [&inout] allocator : "The allocator to use"
@param [&in] other_set : "The set to copy from."
@require !self.is_initialized() : "Set was already initialized"
*>
fn HashSet* HashSet.init_from_set(&self, Allocator allocator, HashSet* other_set)
{
self.init(allocator, other_set.table.len, other_set.load_factor);
self.put_all_for_create(other_set);
return self;
}
<*
@param [&in] other_set : "The set to copy from."
@require !set.is_initialized() : "Set was already initialized"
*>
fn HashSet* HashSet.tinit_from_set(&set, HashSet* other_set)
{
return set.init_from_set(tmem, other_set) @inline;
}
<*
Check if the set is empty
@return "true if it is empty"
@pure
*>
fn bool HashSet.is_empty(&set) @inline
{
return !set.count;
}
<*
Add all elements in the slice to the set.
@param [in] list
@return "The number of new elements added"
@ensure total <= list.len
*>
fn usz HashSet.add_all(&set, Value[] list)
{
usz total;
foreach (v : list)
{
if (set.add(v)) total++;
}
return total;
}
<*
@param [&in] other
@return "The number of new elements added"
@ensure return <= other.count
*>
fn usz HashSet.add_all_from(&set, HashSet* other)
{
usz total;
other.@each(;Value value)
{
if (set.add(value)) total++;
};
return total;
}
<*
@param value : "The value to add"
@return "true if the value didn't exist in the set"
*>
fn bool HashSet.add(&set, Value value)
{
// If the set isn't initialized, use the defaults to initialize it.
switch (set.allocator.ptr)
{
case &dummy:
set.init(mem);
case null:
set.tinit();
default:
break;
}
uint hash = rehash(value.hash());
uint index = index_for(hash, set.table.len);
for (Entry *e = set.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return false;
}
set.add_entry(hash, value, index);
return true;
}
<*
Iterate over all the values in the set
*>
macro HashSet.@each(set; @body(value))
{
if (!set.count) return;
foreach (Entry* entry : set.table)
{
while (entry)
{
@body(entry.value);
entry = entry.next;
}
}
}
<*
Check if the set contains the given value.
@param value : "The value to check"
@return "true if it exists in the set"
*>
fn bool HashSet.contains(&set, Value value)
{
if (!set.count) return false;
uint hash = rehash(value.hash());
for (Entry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return true;
}
return false;
}
<*
Remove a single value from the set.
@param value : "The value to remove"
@return? NOT_FOUND : "If the entry is not found"
*>
fn void? HashSet.remove(&set, Value value) @maydiscard
{
if (!set.remove_entry_for_value(value)) return NOT_FOUND?;
}
fn usz HashSet.remove_all(&set, Value[] values)
{
usz total;
foreach (v : values)
{
if (set.remove_entry_for_value(v)) total++;
}
return total;
}
<*
@param [&in] other : "Other set"
*>
fn usz HashSet.remove_all_from(&set, HashSet* other)
{
usz total;
other.@each(;Value val)
{
if (set.remove_entry_for_value(val)) total++;
};
return total;
}
<*
Free all memory allocated by the hash set.
*>
fn void HashSet.free(&set)
{
if (!set.is_initialized()) return;
set.clear();
set.free_internal(set.table.ptr);
*set = {};
}
<*
Clear all elements from the set while keeping the underlying storage
@ensure set.count == 0
*>
fn void HashSet.clear(&set)
{
if (!set.count) return;
foreach (Entry** &entry_ref : set.table)
{
Entry* entry = *entry_ref;
if (!entry) continue;
Entry *next = entry.next;
while (next)
{
Entry *to_delete = next;
next = next.next;
set.free_entry(to_delete);
}
set.free_entry(entry);
*entry_ref = null;
}
set.count = 0;
}
fn void HashSet.reserve(&set, usz capacity)
{
if (capacity > set.threshold)
{
set.resize(math::next_power_of_2(capacity));
}
}
// --- Set Operations ---
<*
Returns the union of two sets (A | B)
@param [&in] other : "The other set to union with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the union of both sets"
*>
fn HashSet HashSet.set_union(&self, Allocator allocator, HashSet* other)
{
usz new_capacity = math::next_power_of_2(self.count + other.count);
HashSet result;
result.init(allocator, new_capacity, self.load_factor);
result.add_all_from(self);
result.add_all_from(other);
return result;
}
fn HashSet HashSet.tset_union(&self, HashSet* other) => self.set_union(tmem, other);
<*
Returns the intersection of the two sets (A & B)
@param [&in] other : "The other set to intersect with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the intersection of both sets"
*>
fn HashSet HashSet.intersection(&self, Allocator allocator, HashSet* other)
{
HashSet result;
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
// Iterate through the smaller set for efficiency
HashSet* smaller = self.count <= other.count ? self : other;
HashSet* larger = self.count > other.count ? self : other;
smaller.@each(;Value value)
{
if (larger.contains(value)) result.add(value);
};
return result;
}
fn HashSet HashSet.tintersection(&self, HashSet* other) => self.intersection(tmem, other);
<*
Return this set - other, so (A & ~B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set but not in the other"
*>
fn HashSet HashSet.difference(&self, Allocator allocator, HashSet* other)
{
HashSet result;
result.init(allocator, self.table.len, self.load_factor);
self.@each(;Value value)
{
if (!other.contains(value))
{
result.add(value);
}
};
return result;
}
fn HashSet HashSet.tdifference(&self, HashSet* other) => self.difference(tmem, other) @inline;
<*
Return (A ^ B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set or the other, but not both"
*>
fn HashSet HashSet.symmetric_difference(&self, Allocator allocator, HashSet* other)
{
HashSet result;
result.init(allocator, self.table.len, self.load_factor);
result.add_all_from(self);
other.@each(;Value value)
{
if (!result.add(value))
{
result.remove(value);
}
};
return result;
}
fn HashSet HashSet.tsymmetric_difference(&self, HashSet* other) => self.symmetric_difference(tmem, other) @inline;
<*
Check if this hash set is a subset of another set.
@param [&in] other : "The other set to check against"
@return "True if all elements of this set are in the other set"
*>
fn bool HashSet.is_subset(&self, HashSet* other)
{
if (self.count == 0) return true;
if (self.count > other.count) return false;
self.@each(;Value value)
{
if (!other.contains(value)) return false;
};
return true;
}
// --- private methods
fn void HashSet.add_entry(&set, uint hash, Value value, uint bucket_index) @private
{
Entry* entry = allocator::new(set.allocator, Entry, { .hash = hash, .value = value, .next = set.table[bucket_index] });
set.table[bucket_index] = entry;
if (set.count++ >= set.threshold)
{
set.resize(set.table.len * 2);
}
}
fn void HashSet.resize(&self, usz new_capacity) @private
{
Entry*[] old_table = self.table;
usz old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
self.threshold = uint.max;
return;
}
Entry*[] new_table = allocator::new_array(self.allocator, Entry*, new_capacity);
self.transfer(new_table);
self.table = new_table;
self.free_internal(old_table.ptr);
self.threshold = (uint)(new_capacity * self.load_factor);
}
fn usz? HashSet.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each(; Value value)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s", value)!;
};
return len + f.print(" }");
}
fn void HashSet.transfer(&self, Entry*[] new_table) @private
{
Entry*[] src = self.table;
uint new_capacity = new_table.len;
foreach (uint j, Entry *e : src)
{
if (!e) continue;
do
{
Entry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void HashSet.put_all_for_create(&set, HashSet* other_set) @private
{
if (!other_set.count) return;
foreach (Entry *e : other_set.table)
{
while (e)
{
set.put_for_create(e.value);
e = e.next;
}
}
}
fn void HashSet.put_for_create(&set, Value value) @private
{
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
for (Entry *e = set.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value))
{
// Value already exists, no need to do anything
return;
}
}
set.create_entry(hash, value, i);
}
fn void HashSet.free_internal(&self, void* ptr) @inline @private
{
allocator::free(self.allocator, ptr);
}
fn void HashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
{
Entry* entry = allocator::new(set.allocator, Entry, {
.hash = hash,
.value = value,
.next = set.table[bucket_index]
});
set.table[bucket_index] = entry;
set.count++;
}
<*
Removes the entry for the specified value if present
@return "true if found and removed, false otherwise"
*>
fn bool HashSet.remove_entry_for_value(&set, Value value) @private
{
if (!set.count) return false;
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
Entry* prev = set.table[i];
Entry* e = prev;
while (e)
{
Entry *next = e.next;
if (e.hash == hash && equals(value, e.value))
{
set.count--;
if (prev == e)
{
set.table[i] = next;
}
else
{
prev.next = next;
}
set.free_entry(e);
return true;
}
prev = e;
e = next;
}
return false;
}
fn void HashSet.free_entry(&set, Entry *entry) @private
{
allocator::free(set.allocator, entry);
}
struct HashSetIterator
{
HashSet* set;
usz bucket_index;
Entry* current;
}
fn HashSetIterator HashSet.iter(&set) => { .set = set, .bucket_index = 0, .current = null };
fn Value? HashSetIterator.next(&self)
{
if (self.current)
{
Value value = self.current.value;
self.current = self.current.next;
return value;
}
while (self.bucket_index < self.set.table.len)
{
self.current = self.set.table[self.bucket_index++];
if (self.current)
{
Value value = self.current.value;
self.current = self.current.next;
return value;
}
}
return NOT_FOUND?;
}
fn usz HashSetIterator.len(&self) @operator(len)
{
return self.set.count;
}
<* @pure *>
fn uint rehash(uint hash) @inline @private
{
hash ^= (hash >> 20) ^ (hash >> 12);
return hash ^ ((hash >> 7) ^ (hash >> 4));
}
macro uint index_for(uint hash, uint capacity) @private => hash & (capacity - 1);
int dummy @local;

View File

@@ -0,0 +1,327 @@
module std::collections::blockingqueue { Value };
import std::thread, std::time;
const INITIAL_CAPACITY = 16;
struct QueueEntry
{
Value value;
QueueEntry* next; // Next in queue order
QueueEntry* prev; // Previous in queue order
}
struct LinkedBlockingQueue
{
QueueEntry* head; // First element in queue
QueueEntry* tail; // Last element in queue
usz count; // Current number of elements
usz capacity; // Maximum capacity (0 for unbounded)
Mutex lock;
ConditionVariable not_empty;
ConditionVariable not_full;
Allocator allocator;
}
<*
@param [&inout] allocator : "The allocator to use"
@param capacity : "Maximum capacity (0 for unbounded)"
@require !self.is_initialized() : "Queue was already initialized"
*>
fn LinkedBlockingQueue* LinkedBlockingQueue.init(&self, Allocator allocator, usz capacity = 0)
{
self.allocator = allocator;
self.capacity = capacity;
self.count = 0;
self.head = null;
self.tail = null;
self.lock.init()!!;
self.not_empty.init()!!;
self.not_full.init()!!;
return self;
}
fn LinkedBlockingQueue* LinkedBlockingQueue.tinit(&self, usz capacity = 0)
{
return self.init(tmem, capacity) @inline;
}
<*
@require self.is_initialized() : "Queue must be initialized"
*>
fn void LinkedBlockingQueue.free(&self)
{
self.lock.@in_lock()
{
// Free all remaining entries
QueueEntry* entry = self.head;
while (entry != null)
{
QueueEntry* next = entry.next;
allocator::free(self.allocator, entry);
entry = next;
}
};
(void)self.lock.destroy();
(void)self.not_empty.destroy();
(void)self.not_full.destroy();
}
fn void LinkedBlockingQueue.link_entry(&self, QueueEntry* entry) @private
{
entry.next = null;
entry.prev = self.tail;
if (self.tail == null)
{
// First element in queue
self.head = entry;
}
else
{
// Append to tail
self.tail.next = entry;
}
self.tail = entry;
self.count++;
}
fn QueueEntry* LinkedBlockingQueue.unlink_head(&self) @private
{
if (self.head == null) return null;
QueueEntry* entry = self.head;
self.head = entry.next;
if (self.head != null)
{
self.head.prev = null;
}
else
{
// Queue is now empty
self.tail = null;
}
self.count--;
return entry;
}
<*
@param value : "Value to add to the queue"
@require self.is_initialized() : "Queue must be initialized"
*>
fn void LinkedBlockingQueue.push(&self, Value value)
{
self.lock.@in_lock()
{
while (self.capacity > 0 && self.count >= self.capacity)
{
self.not_full.wait(&self.lock)!!;
}
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
.value = value,
.next = null,
.prev = null
});
self.link_entry(entry);
// Signal that queue is no longer empty
self.not_empty.signal()!!;
};
}
<*
Get a value from the queue, blocking if there is no element in the queue.
@require self.is_initialized() : "Queue must be initialized"
@return "The removed value"
*>
fn Value LinkedBlockingQueue.poll(&self)
{
self.lock.@in_lock()
{
while (self.count == 0)
{
self.not_empty.wait(&self.lock)!!;
}
QueueEntry* entry = self.unlink_head();
Value value = entry.value;
allocator::free(self.allocator, entry);
if (self.capacity > 0)
{
self.not_full.signal()!!;
}
return value;
};
}
<*
Pop an element from the queue, fail is it is empty.
@require self.is_initialized() : "Queue must be initialized"
@return "The removed value"
@return? NO_MORE_ELEMENT : "If the queue is empty"
*>
fn Value? LinkedBlockingQueue.pop(&self)
{
self.lock.@in_lock()
{
if (self.count == 0) return NO_MORE_ELEMENT?;
QueueEntry* entry = self.unlink_head();
Value value = entry.value;
allocator::free(self.allocator, entry);
if (self.capacity > 0)
{
self.not_full.signal()!!;
}
return value;
};
}
<*
Poll with a timeout.
@param timeout : "Timeout in microseconds"
@require self.is_initialized() : "Queue must be initialized"
@return "The removed value or null if timeout occurred"
@return? NO_MORE_ELEMENT : "If we reached the timeout"
*>
fn Value? LinkedBlockingQueue.poll_timeout(&self, Duration timeout)
{
self.lock.@in_lock()
{
// Use while loop to handle spurious wakeups
if (!self.count)
{
Time start = time::now();
Time end = start + timeout;
while (!self.count)
{
if (end <= time::now()) break;
if (catch self.not_empty.wait_until(&self.lock, end)) break;
}
if (!self.count) return NO_MORE_ELEMENT?;
}
QueueEntry* entry = self.unlink_head();
Value value = entry.value;
allocator::free(self.allocator, entry);
// Must signal not_full after removing an item
if (self.capacity > 0)
{
self.not_full.signal()!!;
}
return value;
};
}
<*
@require self.is_initialized() : "Queue must be initialized"
@return "Current size of the queue"
*>
fn usz LinkedBlockingQueue.size(&self)
{
self.lock.@in_lock()
{
return self.count;
};
}
<*
@require self.is_initialized() : "Queue must be initialized"
@return "True if queue is empty"
*>
fn bool LinkedBlockingQueue.is_empty(&self)
{
self.lock.@in_lock()
{
return self.count == 0;
};
}
<*
Try to push, return CAPACITY_EXCEEDED if the queue is full.
@param value : "Value to add to the queue"
@require self.is_initialized() : "Queue must be initialized"
@return? CAPACITY_EXCEEDED : "If the queue is full"
*>
fn void? LinkedBlockingQueue.try_push(&self, Value value)
{
self.lock.@in_lock()
{
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED?;
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
.value = value,
.next = null,
.prev = null
});
self.link_entry(entry);
self.not_empty.signal()!!;
};
}
<*
Try to push, return CAPACITY_EXCEEDED if the queue is still full after timeout is reached.
@param value : "Value to add to the queue"
@param timeout : "Timeout in microseconds"
@require self.is_initialized() : "Queue must be initialized"
@return? CAPACITY_EXCEEDED : "If the queue is full"
*>
fn void? LinkedBlockingQueue.push_timeout(&self, Value value, Duration timeout)
{
self.lock.@in_lock()
{
if (self.capacity > 0 && self.count >= self.capacity)
{
Time start = time::now();
Time end = start + timeout;
while (self.capacity > 0 && self.count >= self.capacity)
{
if (end <= time::now()) break;
if (catch self.not_empty.wait_until(&self.lock, end)) break;
}
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED?;
}
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
.value = value,
.next = null,
.prev = null
});
self.link_entry(entry);
self.not_empty.signal()!!;
};
}
<*
@require self.is_initialized() : "Queue must be initialized"
@return "The head value or NO_MORE_ELEMENT? if queue is empty"
*>
fn Value? LinkedBlockingQueue.peek(&self)
{
self.lock.@in_lock()
{
return (self.head != null) ? self.head.value : NO_MORE_ELEMENT?;
};
}
<*
@return "True if queue is initialized"
*>
fn bool LinkedBlockingQueue.is_initialized(&self)
{
return self.allocator && self.lock.initialized;
}

View File

@@ -0,0 +1,645 @@
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@require $defined((Key){}.hash()) : `No .hash function found on the key`
*>
module std::collections::map{Key, Value};
import std::math;
import std::io @norecurse;
const LinkedHashMap LINKEDONHEAP = { .allocator = MAP_HEAP_ALLOCATOR };
struct LinkedEntry
{
uint hash;
Key key;
Value value;
LinkedEntry* next; // For bucket chain
LinkedEntry* before; // Previous in insertion order
LinkedEntry* after; // Next in insertion order
}
struct LinkedHashMap (Printable)
{
LinkedEntry*[] table;
Allocator allocator;
usz count;
usz threshold;
float load_factor;
LinkedEntry* head; // First inserted LinkedEntry
LinkedEntry* tail; // Last inserted LinkedEntry
}
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
self.load_factor = load_factor;
self.threshold = (usz)(capacity * load_factor);
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
self.head = null;
self.tail = null;
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(tmem, capacity, load_factor) @inline;
}
<*
@param [&inout] allocator : "The allocator to use"
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashMap* LinkedHashMap.init_with_key_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
$for var $i = 0; $i < $vacount; $i += 2:
self.set($vaarg[$i], $vaarg[$i + 1]);
$endfor
return self;
}
<*
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashMap* LinkedHashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
}
<*
@param [in] keys : "The keys for the LinkedHashMap entries"
@param [in] values : "The values for the LinkedHashMap entries"
@param [&inout] allocator : "The allocator to use"
@require keys.len == values.len : "Both keys and values arrays must be the same length"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
assert(keys.len == values.len);
self.init(allocator, capacity, load_factor);
for (usz i = 0; i < keys.len; i++)
{
self.set(keys[i], values[i]);
}
return self;
}
<*
@param [in] keys : "The keys for the LinkedHashMap entries"
@param [in] values : "The values for the LinkedHashMap entries"
@require keys.len == values.len : "Both keys and values arrays must be the same length"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_from_keys_and_values(tmem, keys, values, capacity, load_factor);
}
<*
Has this hash map been initialized yet?
@param [&in] map : "The hash map we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool LinkedHashMap.is_initialized(&map)
{
return map.allocator && map.allocator.ptr != &dummy;
}
<*
@param [&inout] allocator : "The allocator to use"
@param [&in] other_map : "The map to copy from."
@require !self.is_initialized() : "Map was already initialized"
*>
fn LinkedHashMap* LinkedHashMap.init_from_map(&self, Allocator allocator, LinkedHashMap* other_map)
{
self.init(allocator, other_map.table.len, other_map.load_factor);
self.put_all_for_create(other_map);
return self;
}
<*
@param [&in] other_map : "The map to copy from."
@require !map.is_initialized() : "Map was already initialized"
*>
fn LinkedHashMap* LinkedHashMap.tinit_from_map(&map, LinkedHashMap* other_map)
{
return map.init_from_map(tmem, other_map) @inline;
}
fn bool LinkedHashMap.is_empty(&map) @inline
{
return !map.count;
}
fn usz LinkedHashMap.len(&map) @inline => map.count;
fn Value*? LinkedHashMap.get_ref(&map, Key key)
{
if (!map.count) return NOT_FOUND?;
uint hash = rehash(key.hash());
for (LinkedEntry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
return NOT_FOUND?;
}
fn LinkedEntry*? LinkedHashMap.get_entry(&map, Key key)
{
if (!map.count) return NOT_FOUND?;
uint hash = rehash(key.hash());
for (LinkedEntry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return NOT_FOUND?;
}
<*
Get the value or update and
@require @assignable_to(#expr, Value)
*>
macro Value LinkedHashMap.@get_or_set(&map, Key key, Value #expr)
{
if (!map.count)
{
Value val = #expr;
map.set(key, val);
return val;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (LinkedEntry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e.value;
}
Value val = #expr;
map.add_entry(hash, key, val, index);
return val;
}
fn Value? LinkedHashMap.get(&map, Key key) @operator([]) => *map.get_ref(key) @inline;
fn bool LinkedHashMap.has_key(&map, Key key) => @ok(map.get_ref(key));
fn bool LinkedHashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
switch (map.allocator.ptr)
{
case &dummy:
map.init(mem);
case null:
map.tinit();
default:
break;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (LinkedEntry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return true;
}
}
map.add_entry(hash, key, value, index);
return false;
}
fn void? LinkedHashMap.remove(&map, Key key) @maydiscard
{
if (!map.remove_entry_for_key(key)) return NOT_FOUND?;
}
fn void LinkedHashMap.clear(&map)
{
if (!map.count) return;
LinkedEntry* entry = map.head;
while (entry)
{
LinkedEntry* next = entry.after;
map.free_entry(entry);
entry = next;
}
foreach (LinkedEntry** &bucket : map.table)
{
*bucket = null;
}
map.count = 0;
map.head = null;
map.tail = null;
}
fn void LinkedHashMap.free(&map)
{
if (!map.is_initialized()) return;
map.clear();
map.free_internal(map.table.ptr);
map.table = {};
}
fn Key[] LinkedHashMap.tkeys(&self)
{
return self.keys(tmem) @inline;
}
fn Key[] LinkedHashMap.keys(&self, Allocator allocator)
{
if (!self.count) return {};
Key[] list = allocator::alloc_array(allocator, Key, self.count);
usz index = 0;
LinkedEntry* entry = self.head;
while (entry)
{
$if COPY_KEYS:
list[index++] = entry.key.copy(allocator);
$else
list[index++] = entry.key;
$endif
entry = entry.after;
}
return list;
}
macro LinkedHashMap.@each(map; @body(key, value))
{
map.@each_entry(; LinkedEntry* entry)
{
@body(entry.key, entry.value);
};
}
macro LinkedHashMap.@each_entry(map; @body(entry))
{
LinkedEntry* entry = map.head;
while (entry)
{
@body(entry);
entry = entry.after;
}
}
fn Value[] LinkedHashMap.tvalues(&map) => map.values(tmem) @inline;
fn Value[] LinkedHashMap.values(&self, Allocator allocator)
{
if (!self.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, self.count);
usz index = 0;
LinkedEntry* entry = self.head;
while (entry)
{
list[index++] = entry.value;
entry = entry.after;
}
return list;
}
fn bool LinkedHashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
{
if (!map.count) return false;
LinkedEntry* entry = map.head;
while (entry)
{
if (equals(v, entry.value)) return true;
entry = entry.after;
}
return false;
}
fn LinkedHashMapIterator LinkedHashMap.iter(&self) => { .map = self, .current = self.head, .started = false };
fn LinkedHashMapValueIterator LinkedHashMap.value_iter(&self) => { .map = self, .current = self.head, .started = false };
fn LinkedHashMapKeyIterator LinkedHashMap.key_iter(&self) => { .map = self, .current = self.head, .started = false };
fn bool LinkedHashMapIterator.next(&self)
{
if (!self.started)
{
self.current = self.map.head;
self.started = true;
}
else if (self.current)
{
self.current = self.current.after;
}
return self.current != null;
}
fn LinkedEntry*? LinkedHashMapIterator.get(&self)
{
return self.current ? self.current : NOT_FOUND?;
}
fn Value*? LinkedHashMapValueIterator.get(&self)
{
return self.current ? &self.current.value : NOT_FOUND?;
}
fn Key*? LinkedHashMapKeyIterator.get(&self)
{
return self.current ? &self.current.key : NOT_FOUND?;
}
fn bool LinkedHashMapIterator.has_next(&self)
{
if (!self.started) return self.map.head != null;
return self.current && self.current.after != null;
}
// --- private methods
fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
{
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, {
.hash = hash,
.key = key,
.value = value,
.next = map.table[bucket_index],
.before = map.tail,
.after = null
});
// Update bucket chain
map.table[bucket_index] = entry;
// Update linked list
if (map.tail)
{
map.tail.after = entry;
entry.before = map.tail;
}
else
{
map.head = entry;
}
map.tail = entry;
if (map.count++ >= map.threshold)
{
map.resize(map.table.len * 2);
}
}
fn void LinkedHashMap.resize(&map, uint new_capacity) @private
{
LinkedEntry*[] old_table = map.table;
uint old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
map.threshold = uint.max;
return;
}
LinkedEntry*[] new_table = allocator::new_array(map.allocator, LinkedEntry*, new_capacity);
map.table = new_table;
map.threshold = (uint)(new_capacity * map.load_factor);
// Rehash all entries - linked list order remains unchanged
foreach (uint i, LinkedEntry *e : old_table)
{
if (!e) continue;
// Split the bucket chain into two chains based on new bit
LinkedEntry* lo_head = null;
LinkedEntry* lo_tail = null;
LinkedEntry* hi_head = null;
LinkedEntry* hi_tail = null;
do
{
LinkedEntry* next = e.next;
if ((e.hash & old_capacity) == 0)
{
if (!lo_tail)
{
lo_head = e;
}
else
{
lo_tail.next = e;
}
lo_tail = e;
}
else
{
if (!hi_tail)
{
hi_head = e;
}
else
{
hi_tail.next = e;
}
hi_tail = e;
}
e.next = null;
e = next;
}
while (e);
if (lo_tail)
{
lo_tail.next = null;
new_table[i] = lo_head;
}
if (hi_tail)
{
hi_tail.next = null;
new_table[i + old_capacity] = hi_head;
}
}
map.free_internal(old_table.ptr);
}
fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each_entry(; LinkedEntry* entry)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s: %s", entry.key, entry.value)!;
};
return len + f.print(" }");
}
fn void LinkedHashMap.transfer(&map, LinkedEntry*[] new_table) @private
{
LinkedEntry*[] src = map.table;
uint new_capacity = new_table.len;
foreach (uint j, LinkedEntry *e : src)
{
if (!e) continue;
do
{
LinkedEntry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void LinkedHashMap.put_all_for_create(&map, LinkedHashMap* other_map) @private
{
if (!other_map.count) return;
other_map.@each(; Key key, Value value) {
map.set(key, value);
};
}
fn void LinkedHashMap.put_for_create(&map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
for (LinkedEntry *e = map.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return;
}
}
map.create_entry(hash, key, value, i);
}
fn void LinkedHashMap.free_internal(&map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
{
if (!map.count) return false;
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
LinkedEntry* prev = null;
LinkedEntry* e = map.table[i];
while (e)
{
if (e.hash == hash && equals(key, e.key))
{
if (prev)
{
prev.next = e.next;
}
else
{
map.table[i] = e.next;
}
if (e.before)
{
e.before.after = e.after;
}
else
{
map.head = e.after;
}
if (e.after)
{
e.after.before = e.before;
}
else
{
map.tail = e.before;
}
map.count--;
map.free_entry(e);
return true;
}
prev = e;
e = e.next;
}
return false;
}
fn void LinkedHashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
{
LinkedEntry *e = map.table[bucket_index];
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry;
map.count++;
}
fn void LinkedHashMap.free_entry(&self, LinkedEntry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
$endif
self.free_internal(entry);
}
struct LinkedHashMapIterator
{
LinkedHashMap* map;
LinkedEntry* current;
bool started;
}
typedef LinkedHashMapValueIterator = inline LinkedHashMapIterator;
typedef LinkedHashMapKeyIterator = inline LinkedHashMapIterator;
fn usz LinkedHashMapValueIterator.len(self) @operator(len) => self.map.count;
fn usz LinkedHashMapKeyIterator.len(self) @operator(len) => self.map.count;
fn usz LinkedHashMapIterator.len(self) @operator(len) => self.map.count;
int dummy @local;

View File

@@ -0,0 +1,723 @@
<*
@require $defined((Value){}.hash()) : `No .hash function found on the value`
*>
module std::collections::set {Value};
import std::math;
import std::io @norecurse;
const LinkedHashSet LINKEDONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
struct LinkedEntry
{
uint hash;
Value value;
LinkedEntry* next; // For bucket chain
LinkedEntry* before; // Previous in insertion order
LinkedEntry* after; // Next in insertion order
}
struct LinkedHashSet (Printable)
{
LinkedEntry*[] table;
Allocator allocator;
usz count; // Number of elements
usz threshold; // Resize limit
float load_factor;
LinkedEntry* head; // First inserted LinkedEntry
LinkedEntry* tail; // Last inserted LinkedEntry
}
fn int LinkedHashSet.len(&self) @operator(len) => (int) self.count;
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
self.threshold = (usz)(capacity * load_factor);
self.load_factor = load_factor;
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
self.head = null;
self.tail = null;
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(tmem, capacity, load_factor) @inline;
}
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashSet* LinkedHashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
$for var $i = 0; $i < $vacount; $i++:
self.add($vaarg[$i]);
$endfor
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashSet* LinkedHashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
}
<*
@param [in] values : "The values for the LinkedHashSet"
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
foreach (v : values) self.add(v);
return self;
}
<*
@param [in] values : "The values for the LinkedHashSet entries"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_from_values(tmem, values, capacity, load_factor);
}
<*
Has this linked hash set been initialized yet?
@param [&in] set : "The linked hash set we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool LinkedHashSet.is_initialized(&set)
{
return set.allocator && set.allocator.ptr != &dummy;
}
<*
@param [&inout] allocator : "The allocator to use"
@param [&in] other_set : "The set to copy from."
@require !self.is_initialized() : "Set was already initialized"
*>
fn LinkedHashSet* LinkedHashSet.init_from_set(&self, Allocator allocator, LinkedHashSet* other_set)
{
self.init(allocator, other_set.table.len, other_set.load_factor);
LinkedEntry* entry = other_set.head;
while (entry) // Save insertion order
{
self.put_for_create(entry.value);
entry = entry.after;
}
return self;
}
<*
@param [&in] other_set : "The set to copy from."
@require !set.is_initialized() : "Set was already initialized"
*>
fn LinkedHashSet* LinkedHashSet.tinit_from_set(&set, LinkedHashSet* other_set)
{
return set.init_from_set(tmem, other_set) @inline;
}
<*
Check if the set is empty
@return "true if it is empty"
@pure
*>
fn bool LinkedHashSet.is_empty(&set) @inline
{
return !set.count;
}
<*
Add all elements in the slice to the set.
@param [in] list
@return "The number of new elements added"
@ensure total <= list.len
*>
fn usz LinkedHashSet.add_all(&set, Value[] list)
{
usz total;
foreach (v : list)
{
if (set.add(v)) total++;
}
return total;
}
<*
@param [&in] other
@return "The number of new elements added"
@ensure return <= other.count
*>
fn usz LinkedHashSet.add_all_from(&set, LinkedHashSet* other)
{
usz total;
other.@each(;Value value)
{
if (set.add(value)) total++;
};
return total;
}
<*
@param value : "The value to add"
@return "true if the value didn't exist in the set"
*>
fn bool LinkedHashSet.add(&set, Value value)
{
// If the set isn't initialized, use the defaults to initialize it.
switch (set.allocator.ptr)
{
case &dummy:
set.init(mem);
case null:
set.tinit();
default:
break;
}
uint hash = rehash(value.hash());
uint index = index_for(hash, set.table.len);
for (LinkedEntry *e = set.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return false;
}
set.add_entry(hash, value, index);
return true;
}
<*
Iterate over all the values in the set
*>
macro LinkedHashSet.@each(set; @body(value))
{
if (!set.count) return;
LinkedEntry* entry = set.head;
while (entry)
{
@body(entry.value);
entry = entry.after;
}
}
<*
Check if the set contains the given value.
@param value : "The value to check"
@return "true if it exists in the set"
*>
fn bool LinkedHashSet.contains(&set, Value value)
{
if (!set.count) return false;
uint hash = rehash(value.hash());
for (LinkedEntry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return true;
}
return false;
}
<*
Remove a single value from the set.
@param value : "The value to remove"
@return? NOT_FOUND : "If the entry is not found"
*>
fn void? LinkedHashSet.remove(&set, Value value) @maydiscard
{
if (!set.remove_entry_for_value(value)) return NOT_FOUND?;
}
fn usz LinkedHashSet.remove_all(&set, Value[] values)
{
usz total;
foreach (v : values)
{
if (set.remove_entry_for_value(v)) total++;
}
return total;
}
<*
@param [&in] other : "Other set"
*>
fn usz LinkedHashSet.remove_all_from(&set, LinkedHashSet* other)
{
usz total;
other.@each(;Value val)
{
if (set.remove_entry_for_value(val)) total++;
};
return total;
}
<*
Free all memory allocated by the hash set.
*>
fn void LinkedHashSet.free(&set)
{
if (!set.is_initialized()) return;
set.clear();
set.free_internal(set.table.ptr);
set.table = {};
}
<*
Clear all elements from the set while keeping the underlying storage
@ensure set.count == 0
*>
fn void LinkedHashSet.clear(&set)
{
if (!set.count) return;
LinkedEntry* entry = set.head;
while (entry)
{
LinkedEntry* next = entry.after;
set.free_entry(entry);
entry = next;
}
foreach (LinkedEntry** &bucket : set.table)
{
*bucket = null;
}
set.count = 0;
set.head = null;
set.tail = null;
}
fn void LinkedHashSet.reserve(&set, usz capacity)
{
if (capacity > set.threshold)
{
set.resize(math::next_power_of_2(capacity));
}
}
// --- Set Operations ---
<*
Returns the union of two sets (A | B)
@param [&in] other : "The other set to union with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the union of both sets"
*>
fn LinkedHashSet LinkedHashSet.set_union(&self, Allocator allocator, LinkedHashSet* other)
{
usz new_capacity = math::next_power_of_2(self.count + other.count);
LinkedHashSet result;
result.init(allocator, new_capacity, self.load_factor);
result.add_all_from(self);
result.add_all_from(other);
return result;
}
fn LinkedHashSet LinkedHashSet.tset_union(&self, LinkedHashSet* other) => self.set_union(tmem, other) @inline;
<*
Returns the intersection of the two sets (A & B)
@param [&in] other : "The other set to intersect with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the intersection of both sets"
*>
fn LinkedHashSet LinkedHashSet.intersection(&self, Allocator allocator, LinkedHashSet* other)
{
LinkedHashSet result;
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
// Iterate through the smaller set for efficiency
LinkedHashSet* smaller = self.count <= other.count ? self : other;
LinkedHashSet* larger = self.count > other.count ? self : other;
smaller.@each(;Value value)
{
if (larger.contains(value)) result.add(value);
};
return result;
}
fn LinkedHashSet LinkedHashSet.tintersection(&self, LinkedHashSet* other) => self.intersection(tmem, other) @inline;
<*
Return this set - other, so (A & ~B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set but not in the other"
*>
fn LinkedHashSet LinkedHashSet.difference(&self, Allocator allocator, LinkedHashSet* other)
{
LinkedHashSet result;
result.init(allocator, self.table.len, self.load_factor);
self.@each(;Value value)
{
if (!other.contains(value))
{
result.add(value);
}
};
return result;
}
fn LinkedHashSet LinkedHashSet.tdifference(&self, LinkedHashSet* other) => self.difference(tmem, other) @inline;
<*
Return (A ^ B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set or the other, but not both"
*>
fn LinkedHashSet LinkedHashSet.symmetric_difference(&self, Allocator allocator, LinkedHashSet* other)
{
LinkedHashSet result;
result.init(allocator, self.table.len, self.load_factor);
result.add_all_from(self);
other.@each(;Value value)
{
if (!result.add(value))
{
result.remove(value);
}
};
return result;
}
fn LinkedHashSet LinkedHashSet.tsymmetric_difference(&self, LinkedHashSet* other) => self.symmetric_difference(tmem, other) @inline;
<*
Check if this hash set is a subset of another set.
@param [&in] other : "The other set to check against"
@return "True if all elements of this set are in the other set"
*>
fn bool LinkedHashSet.is_subset(&self, LinkedHashSet* other)
{
if (self.count == 0) return true;
if (self.count > other.count) return false;
self.@each(; Value value) {
if (!other.contains(value)) return false;
};
return true;
}
// --- private methods
fn void LinkedHashSet.add_entry(&set, uint hash, Value value, uint bucket_index) @private
{
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
.hash = hash,
.value = value,
.next = set.table[bucket_index],
.before = set.tail,
.after = null
});
// Update bucket chain
set.table[bucket_index] = entry;
// Update linked list
if (set.tail)
{
set.tail.after = entry;
entry.before = set.tail;
}
else
{
set.head = entry;
}
set.tail = entry;
if (set.count++ >= set.threshold)
{
set.resize(set.table.len * 2);
}
}
fn void LinkedHashSet.resize(&set, usz new_capacity) @private
{
LinkedEntry*[] old_table = set.table;
usz old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
set.threshold = uint.max;
return;
}
LinkedEntry*[] new_table = allocator::new_array(set.allocator, LinkedEntry*, new_capacity);
set.table = new_table;
set.threshold = (uint)(new_capacity * set.load_factor);
// Rehash all entries - linked list order remains unchanged
foreach (uint i, LinkedEntry *e : old_table)
{
if (!e) continue;
// Split the bucket chain into two chains based on new bit
LinkedEntry* lo_head = null;
LinkedEntry* lo_tail = null;
LinkedEntry* hi_head = null;
LinkedEntry* hi_tail = null;
do
{
LinkedEntry* next = e.next;
if ((e.hash & old_capacity) == 0)
{
if (!lo_tail)
{
lo_head = e;
}
else
{
lo_tail.next = e;
}
lo_tail = e;
}
else
{
if (!hi_tail)
{
hi_head = e;
}
else
{
hi_tail.next = e;
}
hi_tail = e;
}
e.next = null;
e = next;
}
while (e);
if (lo_tail)
{
lo_tail.next = null;
new_table[i] = lo_head;
}
if (hi_tail)
{
hi_tail.next = null;
new_table[i + old_capacity] = hi_head;
}
}
set.free_internal(old_table.ptr);
}
fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each(; Value value)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s", value)!;
};
return len + f.print(" }");
}
fn void LinkedHashSet.transfer(&set, LinkedEntry*[] new_table) @private
{
LinkedEntry*[] src = set.table;
uint new_capacity = new_table.len;
foreach (uint j, LinkedEntry *e : src)
{
if (!e) continue;
do
{
LinkedEntry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void LinkedHashSet.put_for_create(&set, Value value) @private
{
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
for (LinkedEntry *e = set.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value))
{
// Value already exists, no need to do anything
return;
}
}
set.create_entry(hash, value, i);
}
fn void LinkedHashSet.free_internal(&set, void* ptr) @inline @private
{
allocator::free(set.allocator, ptr);
}
fn void LinkedHashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
{
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
.hash = hash,
.value = value,
.next = set.table[bucket_index],
.before = set.tail,
.after = null
});
set.table[bucket_index] = entry;
// Update linked list
if (set.tail)
{
set.tail.after = entry;
entry.before = set.tail;
}
else
{
set.head = entry;
}
set.tail = entry;
set.count++;
}
fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
{
if (!set.count) return false;
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
LinkedEntry* prev = null;
LinkedEntry* e = set.table[i];
while (e)
{
if (e.hash == hash && equals(value, e.value))
{
if (prev)
{
prev.next = e.next;
}
else
{
set.table[i] = e.next;
}
if (e.before)
{
e.before.after = e.after;
}
else
{
set.head = e.after;
}
if (e.after)
{
e.after.before = e.before;
}
else
{
set.tail = e.before;
}
set.count--;
set.free_entry(e);
return true;
}
prev = e;
e = e.next;
}
return false;
}
fn void LinkedHashSet.free_entry(&set, LinkedEntry *entry) @private
{
allocator::free(set.allocator, entry);
}
struct LinkedHashSetIterator
{
LinkedHashSet* set;
LinkedEntry* current;
bool started;
}
fn LinkedHashSetIterator LinkedHashSet.iter(&set) => { .set = set, .current = set.head, .started = false };
fn bool LinkedHashSetIterator.next(&self)
{
if (!self.started)
{
self.current = self.set.head;
self.started = true;
}
else if (self.current)
{
self.current = self.current.after;
}
return self.current != null;
}
fn Value*? LinkedHashSetIterator.get(&self)
{
return self.current ? &self.current.value : NOT_FOUND?;
}
fn bool LinkedHashSetIterator.has_next(&self)
{
if (!self.started) return self.set.head != null;
return self.current && self.current.after != null;
}
fn usz LinkedHashSetIterator.len(&self) @operator(len)
{
return self.set.count;
}
int dummy @local;

View File

@@ -137,9 +137,10 @@ fn Type? List.pop_first(&self)
*>
fn void List.remove_at(&self, usz index)
{
self.set_size(self.size - 1);
if (!self.size || index == self.size) return;
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
usz new_size = self.size - 1;
defer self.set_size(new_size);
if (!new_size || index == new_size) return;
self.entries[index .. new_size - 1] = self.entries[index + 1 .. new_size];
}
fn void List.add_all(&self, List* other_list)

View File

@@ -178,11 +178,8 @@ fn void Object.init_array_if_needed(&self) @private
fn void Object.set_object(&self, String key, Object* new_object) @private
{
self.init_map_if_needed();
ObjectInternalMapEntry*? entry = self.map.get_entry(key);
defer
{
(void)entry.value.free();
}
Object*? val = self.map.get_entry(key).value;
defer (void)val.free();
self.map.set(key, new_object);
}
@@ -206,7 +203,7 @@ macro Object* Object.object_from_value(&self, value) @private
return value;
$case $Type.typeid == void*.typeid:
return &NULL_OBJECT;
$case $assignable(value, String):
$case @assignable_to(value, String):
return new_string(value, self.allocator);
$default:
$error "Unsupported object type.";

View File

@@ -1,16 +1,75 @@
module std::collections::tuple{Type1, Type2};
module std::collections::pair{Type1, Type2};
import std::io;
struct Tuple
struct Pair (Printable)
{
Type1 first;
Type2 second;
}
module std::collections::triple{Type1, Type2, Type3};
fn usz? Pair.to_format(&self, Formatter* f) @dynamic
{
return f.printf("{ %s, %s }", self.first, self.second);
}
struct Triple
<*
@param [&out] a
@param [&out] b
@require @assignable_to(self.first, $typeof(*a)) : "You cannot assign the first value to a"
@require @assignable_to(self.second, $typeof(*b)) : "You cannot assign the second value to b"
*>
macro void Pair.unpack(&self, a, b)
{
*a = self.first;
*b = self.second;
}
fn bool Pair.equal(self, Pair other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2))
{
return self.first == other.first && self.second == other.second;
}
module std::collections::triple{Type1, Type2, Type3};
import std::io;
struct Triple (Printable)
{
Type1 first;
Type2 second;
Type3 third;
}
}
fn usz? Triple.to_format(&self, Formatter* f) @dynamic
{
return f.printf("{ %s, %s, %s }", self.first, self.second, self.third);
}
<*
@param [&out] a
@param [&out] b
@param [&out] c
@require @assignable_to(self.first, $typeof(*a)) : "You cannot assign the first value to a"
@require @assignable_to(self.second, $typeof(*b)) : "You cannot assign the second value to b"
@require @assignable_to(self.third, $typeof(*c)) : "You cannot assign the second value to c"
*>
macro void Triple.unpack(&self, a, b, c)
{
*a = self.first;
*b = self.second;
*c = self.third;
}
fn bool Triple.equal(self, Triple other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2) &&& types::has_equals(Type3))
{
return self.first == other.first && self.second == other.second && self.third == other.third;
}
module std::collections::tuple{Type1, Type2};
struct Tuple @deprecated("Use 'Pair' instead")
{
Type1 first;
Type2 second;
}

View File

@@ -138,7 +138,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
}
// Otherwise just allocate new memory.
void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(mem, old_pointer, math::min(size, old_size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}

View File

@@ -137,7 +137,7 @@ fn void*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignm
}
AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(data, pointer, math::min(size, chunk.size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}

View File

@@ -117,7 +117,7 @@ fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a
return old_pointer;
}
void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(new_mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT);
return new_mem;
}

View File

@@ -8,12 +8,9 @@ import libc;
<*
The LibcAllocator is a wrapper around malloc to conform to the Allocator interface.
*>
typedef LibcAllocator (Allocator, Printable) = uptr;
typedef LibcAllocator (Allocator) = uptr;
const LibcAllocator LIBC_ALLOCATOR = {};
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
fn usz? LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
module std::core::mem::allocator @if(env::POSIX);
import std::os;
import libc;

View File

@@ -1,5 +1,5 @@
module std::core::mem::allocator;
import std::math;
<*
The OnStackAllocator is similar to the ArenaAllocator: it allocates from a chunk of memory
given to it.
@@ -124,7 +124,7 @@ fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignm
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
usz old_size = header.size;
void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}

View File

@@ -1,6 +1,5 @@
module std::core::mem::allocator;
module std::core::mem::allocator @if(!(env::POSIX || env::WIN32) || !$feature(VMEM_TEMP));
import std::io, std::math;
import std::core::sanitizer::asan;
// This implements the temp allocator.
// The temp allocator is a specialized allocator only intended for use where
@@ -327,3 +326,81 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
return &page.data[0];
}
module std::core::mem::allocator @if((env::POSIX || env::WIN32) && $feature(VMEM_TEMP));
import std::math;
tlocal VmemOptions temp_allocator_default_options = {
.shrink_on_reset = env::MEMORY_ENV != NORMAL,
.protect_unused_pages = env::COMPILER_OPT_LEVEL <= O1 || env::COMPILER_SAFE_MODE,
.scratch_released_data = env::COMPILER_SAFE_MODE
};
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve = temp_allocator_reserve_size, usz min_size = temp_allocator_min_size, usz realloc_size = temp_allocator_realloc_size)
{
Vmem mem;
TempAllocator* t = allocator::new(allocator, TempAllocator);
defer catch allocator::free(allocator, t);
t.vmem.init(preferred_size: isz.sizeof > 4 ? 4 * mem::GB : 512 * mem::MB,
reserve_page_size: isz.sizeof > 4 ? 256 * mem::KB : 0,
options: temp_allocator_default_options)!;
t.allocator = allocator;
return t;
}
struct TempAllocator (Allocator)
{
Vmem vmem;
TempAllocator* derived;
Allocator allocator;
}
<*
@require size > 0
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
*>
fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
return self.vmem.acquire(size, init_type, alignment) @inline;
}
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0)
{
if (self.derived) return self.derived;
return self.derived = new_temp_allocator(self.allocator, 0)!;
}
<*
Reset the entire temp allocator, destroying all children
*>
fn void TempAllocator.reset(&self)
{
TempAllocator* child = self.derived;
if (!child) return;
child.reset();
child.vmem.reset(0);
}
fn void TempAllocator.free(&self)
{
self.destroy();
}
fn void TempAllocator.destroy(&self) @local
{
TempAllocator* child = self.derived;
if (!child) return;
child.destroy();
self.vmem.free() @inline;
allocator::free(self.allocator, self) @inline;
}
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
{
return self.vmem.resize(pointer, size, alignment) @inline;
}
fn void TempAllocator.release(&self, void* old_pointer, bool b) @dynamic
{
self.vmem.release(old_pointer, b) @inline;
}

View File

@@ -0,0 +1,252 @@
module std::core::mem::allocator @if(env::POSIX || env::WIN32);
import std::math, std::os::posix, libc, std::bits;
import std::core::mem;
import std::core::env;
// Virtual Memory allocator
faultdef VMEM_RESERVE_FAILED;
struct Vmem (Allocator)
{
VirtualMemory memory;
usz allocated;
usz pagesize;
usz page_pot;
usz last_page;
usz high_water;
VmemOptions options;
}
bitstruct VmemOptions : int
{
bool shrink_on_reset; // Release memory on reset
bool protect_unused_pages; // Protect unused pages on reset
bool scratch_released_data; // Overwrite released data with 0xAA
}
<*
Implements the Allocator interface method.
@require !reserve_page_size || math::is_power_of_2(reserve_page_size)
@require reserve_page_size <= preferred_size : "The min reserve_page_size size must be less or equal to the preferred size"
@require preferred_size >= 1 * mem::KB : "The preferred size must exceed 1 KB"
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY, VMEM_RESERVE_FAILED
*>
fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOptions options = { true, true, env::COMPILER_SAFE_MODE }, usz min_size = 0)
{
static usz page_size = 0;
if (!page_size) page_size = mem::os_pagesize();
if (page_size < reserve_page_size) page_size = reserve_page_size;
preferred_size = mem::aligned_offset(preferred_size, page_size);
if (!min_size) min_size = max(preferred_size / 1024, 1);
VirtualMemory? memory = mem::OUT_OF_MEMORY?;
while (preferred_size >= min_size)
{
memory = vm::virtual_alloc(preferred_size, PROTECTED);
// It worked?
if (try memory) break;
switch (@catch(memory))
{
case mem::OUT_OF_MEMORY:
case vm::RANGE_OVERFLOW:
// Try a smaller size.
preferred_size /= 2;
continue;
default:
break;
}
}
if (catch memory) return VMEM_RESERVE_FAILED?;
if (page_size > preferred_size) page_size = preferred_size;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(memory.ptr, memory.size);
$endif
*self = { .memory = memory,
.high_water = 0,
.pagesize = page_size,
.page_pot = page_size.ctz(),
.options = options,
};
}
<*
Implements the Allocator interface method.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? Vmem.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
usz total_len = self.memory.size;
if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
void* start_mem = self.memory.ptr;
void* unaligned_pointer_to_offset = start_mem + self.allocated + VmemHeader.sizeof;
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz after = (usz)(mem - start_mem) + size;
if (after > total_len) return mem::OUT_OF_MEMORY?;
if (init_type == ZERO && self.high_water <= self.allocated)
{
init_type = NO_ZERO;
}
protect(self, after)!;
VmemHeader* header = mem - VmemHeader.sizeof;
header.size = size;
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
fn bool Vmem.owns_pointer(&self, void* ptr) @inline
{
return (uptr)ptr >= (uptr)self.memory.ptr && (uptr)ptr < (uptr)self.memory.ptr + self.memory.size;
}
<*
Implements the Allocator interface method.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require old_pointer != null
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
{
if (size > self.memory.size) return mem::INVALID_ALLOC_SIZE?;
alignment = alignment_for_allocation(alignment);
assert(self.owns_pointer(old_pointer), "Pointer originates from a different allocator: %p, not in %p - %p", old_pointer, self.memory.ptr, self.memory.ptr + self.allocated);
VmemHeader* header = old_pointer - VmemHeader.sizeof;
usz old_size = header.size;
if (old_size == size) return old_pointer;
// Do last allocation and alignment match?
if (self.memory.ptr + self.allocated == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
{
if (old_size > size)
{
unprotect(self, self.allocated + size - old_size);
}
else
{
usz allocated = self.allocated + size - old_size;
if (allocated > self.memory.size) return mem::OUT_OF_MEMORY?;
protect(self, allocated)!;
}
header.size = size;
return old_pointer;
}
if (old_size > size)
{
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(old_pointer + size, old_size - size);
$endif
header.size = size;
return old_pointer;
}
// Otherwise just allocate new memory.
void* mem = self.acquire(size, NO_ZERO, alignment)!;
assert(size > old_size);
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
<*
Implements the Allocator interface method.
@require ptr != null
*>
fn void Vmem.release(&self, void* ptr, bool) @dynamic
{
assert(self.owns_pointer(ptr), "Pointer originates from a different allocator %p.", ptr);
VmemHeader* header = ptr - VmemHeader.sizeof;
// Reclaim memory if it's the last element.
if (ptr + header.size == self.memory.ptr + self.allocated)
{
unprotect(self, self.allocated - header.size - VmemHeader.sizeof);
}
}
fn usz Vmem.mark(&self)
{
return self.allocated;
}
<*
@require mark <= self.allocated : "Invalid mark"
*>
fn void Vmem.reset(&self, usz mark)
{
if (mark == self.allocated) return;
unprotect(self, mark);
}
fn void Vmem.free(&self)
{
if (!self.memory.ptr) return;
$switch:
$case env::ADDRESS_SANITIZER:
asan::poison_memory_region(self.memory.ptr, self.memory.size);
$case env::COMPILER_SAFE_MODE:
((char*)self.memory.ptr)[0:self.allocated] = 0xAA;
$endswitch
(void)self.memory.destroy();
*self = {};
}
// Internal data
struct VmemHeader @local
{
usz size;
char[*] data;
}
macro void? protect(Vmem* mem, usz after) @local
{
usz shift = mem.page_pot;
usz page_after = (after + mem.pagesize - 1) >> shift;
usz last_page = mem.last_page;
bool over_high_water = mem.high_water < after;
if (page_after > last_page)
{
usz page_start = last_page << shift;
usz page_len = (page_after - last_page) << shift;
mem.memory.commit(page_start, page_len)!;
if (mem.options.protect_unused_pages || over_high_water)
{
mem.memory.protect(page_start, page_len, READWRITE)!;
}
mem.last_page = page_after;
}
$if env::ADDRESS_SANITIZER:
asan::unpoison_memory_region(mem.memory.ptr + mem.allocated, after - mem.allocated);
$endif
mem.allocated = after;
if (over_high_water) mem.high_water = after;
}
macro void unprotect(Vmem* mem, usz after) @local
{
usz shift = mem.page_pot;
usz last_page = mem.last_page;
usz page_after = mem.last_page = (after + mem.pagesize - 1) >> shift;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(mem.memory.ptr + after, mem.allocated - after);
$else
if (mem.options.scratch_released_data)
{
mem::set(mem.memory.ptr + after, 0xAA, mem.allocated - after);
}
$endif
if ((mem.options.shrink_on_reset || mem.options.protect_unused_pages) && page_after < last_page)
{
usz start = page_after << shift;
usz len = (last_page - page_after) << shift;
if (mem.options.shrink_on_reset) (void)mem.memory.decommit(start, len, false);
if (mem.options.protect_unused_pages) (void)mem.memory.protect(start, len, PROTECTED);
}
mem.allocated = after;
}

133
lib/std/core/ansi.c3 Normal file
View File

@@ -0,0 +1,133 @@
module std::core::string::ansi;
enum Ansi : const inline String
{
RESET = "\e[0m",
BOLD = "\e[1m",
DIM = "\e[2m",
ITALIC = "\e[3m",
UNDERLINE = "\e[4m",
BLINK = "\e[5m",
BLINK_FAST = "\e[6m",
INVERT = "\e[7m",
HIDDEN = "\e[8m",
STRIKETHROUGH = "\e[9m",
DOUBLE_UNDER = "\e[21m",
NO_DIM = "\e[22m",
NO_ITALIC = "\e[23m",
NO_UNDERLINE = "\e[24m",
NO_BLINK = "\e[25m",
NO_INVERT = "\e[27m",
NO_HIDDEN = "\e[28m",
NO_STRIKETHROUGH = "\e[29m",
BLACK = "\e[30m",
RED = "\e[31m",
GREEN = "\e[32m",
YELLOW = "\e[33m",
BLUE = "\e[34m",
MAGENTA = "\e[35m",
CYAN = "\e[36m",
WHITE = "\e[37m",
DEFAULT = "\e[39m",
BRIGHT_BLACK = "\e[90m",
BRIGHT_RED = "\e[91m",
BRIGHT_GREEN = "\e[92m",
BRIGHT_YELLOW = "\e[93m",
BRIGHT_BLUE = "\e[94m",
BRIGHT_MAGENTA = "\e[95m",
BRIGHT_CYAN = "\e[96m",
BRIGHT_WHITE = "\e[97m",
BG_BLACK = "\e[40m",
BG_RED = "\e[41m",
BG_GREEN = "\e[42m",
BG_YELLOW = "\e[43m",
BG_BLUE = "\e[44m",
BG_MAGENTA = "\e[45m",
BG_CYAN = "\e[46m",
BG_WHITE = "\e[47m",
BG_DEFAULT = "\e[49m",
BG_BRIGHT_BLACK = "\e[100m",
BG_BRIGHT_RED = "\e[101m",
BG_BRIGHT_GREEN = "\e[102m",
BG_BRIGHT_YELLOW = "\e[103m",
BG_BRIGHT_BLUE = "\e[104m",
BG_BRIGHT_MAGENTA = "\e[105m",
BG_BRIGHT_CYAN = "\e[106m",
BG_BRIGHT_WHITE = "\e[107m",
}
<*
8-bit color code
@return `the formatting char for the given background color`
*>
macro String color_8bit(char $index, bool $bg = false) @const
{
int $mode = $bg ? 4 : 3;
return @sprintf("\e[%s8;5;%sm", $mode, $index);
}
<*
24-bit color code
@return `the string for the given foreground color`
*>
macro String color_rgb(char $r, char $g, char $b, bool $bg = false) @const
{
int $mode = $bg ? 4 : 3;
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $r, $g, $b);
}
<*
24-bit color code rgb
@require $rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
@return `the string char for the given foreground color`
*>
macro String color(uint $rgb, bool $bg = false) @const
{
int $mode = $bg ? 4 : 3;
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $rgb >> 16, ($rgb & 0xFF00) >> 8, $rgb & 0xFF);
}
<*
24-bit color code rgb
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
@return `the string char for the given foreground color`
*>
fn String make_color(Allocator mem, uint rgb, bool bg = false)
{
return make_color_rgb(mem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
}
<*
24-bit color code rgb
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
@return `the string char for the given foreground color`
*>
fn String make_tcolor(uint rgb, bool bg = false)
{
return make_color_rgb(tmem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
}
<*
24-bit color code rgb
@return `the string char for the given foreground color`
*>
fn String make_color_rgb(Allocator mem, char r, char g, char b, bool bg = false)
{
return string::format(mem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
}
<*
24-bit color code rgb
@return `the string char for the given foreground color`
*>
fn String make_tcolor_rgb(char r, char g, char b, bool bg = false)
{
return string::format(tmem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
}

View File

@@ -18,10 +18,10 @@ import libc, std::hash, std::io, std::os::backtrace;
macro foo(a, #b = EMPTY_MACRO_SLOT)
{
$if @is_valid_macro_slot(#b):
return invoke_foo2(a, #b);
$else
return invoke_foo1(a);
$endif
return invoke_foo2(a, #b);
$else
return invoke_foo1(a);
$endif
}
*>
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
@@ -39,20 +39,28 @@ macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
macro @rnd() @const @builtin => $$rnd();
/*
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
Use `NO_MORE_ELEMENT` when reading the end of an iterator, or accessing a result out of bounds.
*/
faultdef NO_MORE_ELEMENT @builtin;
/*
Use `SearchResult` when trying to return a value from some collection but the element is missing.
Use `NOT_FOUND` when trying to return a value from some collection but the element is missing.
*/
faultdef NOT_FOUND @builtin;
/*
Use `CastResult` when an attempt at conversion fails.
Use `TYPE_MISMATCH` when an attempt at conversion fails.
*/
faultdef TYPE_MISMATCH @builtin;
/*
Use `CAPACITY_EXCEEDED` when trying to add to a bounded list or similar.
*/
faultdef CAPACITY_EXCEEDED @builtin;
/*
Use `NOT_IMPLEMENTED` when something is conditionally available.
*/
faultdef NOT_IMPLEMENTED @builtin;
alias VoidFn = fn void();
@@ -81,6 +89,10 @@ macro void @swap(#a, #b) @builtin
#b = temp;
}
macro usz bitsizeof($Type) @builtin @const => $Type.sizeof * 8u;
macro usz @bitsizeof(#expr) @builtin @const => $sizeof(#expr) * 8u;
<*
Convert an `any` type to a type, returning an failure if there is a type mismatch.
@@ -96,58 +108,80 @@ macro anycast(any v, $Type) @builtin
return ($Type*)v.ptr;
}
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
macro bool @assignable_to(#foo, $Type) @const @builtin => $defined(*&&($Type){} = #foo);
macro @addr(#val) @builtin
{
$if $defined(&#val):
return &#val;
$else
return &&#val;
$endif
}
macro typeid @typeid(#value) @const @builtin
{
return $typeof(#value).typeid;
}
macro TypeKind @typekind(#value) @const @builtin
{
return $typeof(#value).kindof;
}
macro bool @typeis(#value, $Type) @const @builtin
{
return $typeof(#value).typeid == $Type.typeid;
}
fn bool print_backtrace(String message, int backtraces_to_ignore) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
{
Allocator t = allocator::current_temp;
TempAllocator* new_t = allocator::new_temp_allocator(smem, 0x1000)!!;
allocator::current_temp = new_t;
defer
{
allocator::current_temp = t;
new_t.free();
}
void*[256] buffer;
void*[] backtraces = backtrace::capture_current(&buffer);
backtraces_to_ignore++;
BacktraceList? backtrace = backtrace::symbolize_backtrace(tmem, backtraces);
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
@stack_mem(2048; Allocator mem)
{
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
BacktraceList? backtrace = backtrace::symbolize_backtrace(mem, backtraces);
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
{
io::eprintfn(" in ???%s", inline_suffix);
continue;
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
{
io::eprintfn(" in ???%s", inline_suffix);
continue;
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
}
};
return true;
}
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
{
$if $defined(io::stderr):
if (!print_backtrace(message, 2))
{
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
}
$if $defined(io::stderr) && env::PANIC_MSG:
if (!print_backtrace(message, 2))
{
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
}
$endif
$$trap();
}
macro void abort(String string = "Unrecoverable error reached", ...) @builtin @noreturn
macro void abort(String string = "Unrecoverable error reached", ...) @format(0) @builtin @noreturn
{
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
$$trap();
@@ -155,20 +189,22 @@ macro void abort(String string = "Unrecoverable error reached", ...) @builtin @n
bool in_panic @local = false;
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
fn void default_panic(String message, String file, String function, uint line) @if (!env::NATIVE_STACKTRACE)
{
if (in_panic)
{
io::eprintn("Panic inside of panic.");
return;
}
in_panic = true;
$if $defined(io::stderr):
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintfn("', in %s (%s:%d)", function, file, line);
$if $defined(io::stderr) && env::PANIC_MSG:
if (in_panic)
{
io::eprintn("Panic inside of panic.");
return;
}
in_panic = true;
$if $defined(io::stderr):
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintfn("', in %s (%s:%d)", function, file, line);
$endif
in_panic = false;
$endif
in_panic = false;
$$trap();
}
@@ -178,21 +214,23 @@ PanicFn panic = &default_panic;
fn void panicf(String fmt, String file, String function, uint line, args...)
{
if (in_panic)
{
io::eprint("Panic inside of panic: ");
io::eprintn(fmt);
return;
}
in_panic = true;
@stack_mem(512; Allocator allocator)
{
DString s;
s.init(allocator);
s.appendf(fmt, ...args);
in_panic = false;
panic(s.str_view(), file, function, line);
};
$if $defined(io::stderr) && env::PANIC_MSG:
if (in_panic)
{
io::eprint("Panic inside of panic: ");
io::eprintn(fmt);
return;
}
in_panic = true;
@stack_mem(512; Allocator allocator)
{
DString s;
s.init(allocator);
s.appendf(fmt, ...args);
in_panic = false;
panic(s.str_view(), file, function, line);
};
$endif
}
<*
@@ -281,7 +319,7 @@ macro enum_by_name($Type, String enum_name) @builtin
@param $Type : `The type of the enum`
@require $Type.kindof == ENUM : `Only enums may be used`
@require $defined($Type.#value) : `Expected '#value' to match an enum associated value`
@require $assignable(value, $typeof(($Type){}.#value)) : `Expected the value to match the type of the associated value`
@require @assignable_to(value, $typeof(($Type){}.#value)) : `Expected the value to match the type of the associated value`
@ensure @typeis(return, $Type)
@return? NOT_FOUND
*>
@@ -334,7 +372,7 @@ macro bool @unlikely(bool #value, $probability = 1.0) @builtin
<*
@require values::@is_int(#value) || values::@is_bool(#value)
@require $assignable(expected, $typeof(#value))
@require @assignable_to(expected, $typeof(#value))
@require $probability >= 0 && $probability <= 1.0
*>
macro @expect(#value, expected, $probability = 1.0) @builtin
@@ -384,6 +422,34 @@ macro swizzle2(v, v2, ...) @builtin
{
return $$swizzle2(v, v2, $vasplat);
}
<*
Returns the count of leading zero bits from an integer at compile-time.
@require types::is_int($typeof($value)) : "Input value must be an integer"
@require $sizeof($value) * 8 <= 128 : "Input value must be 128 bits wide or lower"
*>
macro @clz($value) @builtin @const
{
$if $value == 0:
return $sizeof($value) * 8; // it's all leading zeroes
$endif
usz $n = 0;
uint128 $x = (uint128)$value;
$if $x <= 0x0000_0000_0000_0000_FFFF_FFFF_FFFF_FFFF: $n += 64; $x <<= 64; $endif
$if $x <= 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 32; $x <<= 32; $endif
$if $x <= 0x0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 16; $x <<= 16; $endif
$if $x <= 0x00FF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 8; $x <<= 8; $endif
$if $x <= 0x0FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 4; $x <<= 4; $endif
$if $x <= 0x3FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 2; $x <<= 2; $endif
$if $x <= 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 1; $endif
return $n % ($sizeof($value) * 8); // mod by the bitsize of the input value to go back from uint128 -> it's-type
}
<*
Return the excuse in the Optional if it is Empty, otherwise
return a null fault.
@@ -408,6 +474,67 @@ macro bool @ok(#expr) @builtin
return true;
}
<*
Check if an Optional expression evaluates to a fault. If so, return it;
else, assign the result to an expression.
@require $defined(#v = #v) : "#v must be a variable"
@require $defined(#expr!) : "Expected an optional expression"
@require @assignable_to(#expr!!, $typeof(#v)) : `Type of #expr must be an optional of #v's type`
*>
macro void? @try(#v, #expr) @builtin
{
var res = #expr;
if (catch err = res) return err?;
#v = res;
}
<*
Check if an Optional expression evaluates to a fault. If so, return true if it is the
expected fault, the optional if it is unexpected, or false if there was no fault and
the assign happened.
This can be used in like this:
while (true)
{
char[] data;
// Read until end of file
if (@try_catch(data, load_line(), io::EOF)) break;
.. use data ..
}
In this example we read until we reach an EOF, which is expected. However, if we encounter some other
fault, we rethrow is. Without this macro, the code is instead written like:
while (true)
{
char[]? data;
data = load_line();
if (catch err = data)
{
if (err = io::EOF) break;
return err?
}
.. use data ..
}
@require $defined(#v = #v) : "#v must be a variable"
@require $defined(#expr!) : "Expected an optional expression"
@require @assignable_to(#expr!!, $typeof(#v)) : `Type of #expr must be an optional of #v's type`
@return "True if it was the expected fault, false if the variable was assigned, otherwise returns an optional."
*>
macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin
{
var res = #expr;
if (catch err = res)
{
return err == expected_fault ? true : err?;
}
#v = res;
return false;
}
<*
@require $defined(&#value, (char*)&#value) : "This must be a value that can be viewed as a char array"
*>
@@ -477,8 +604,8 @@ macro uint ichar[<*>].hash(self) => hash_vec(self);
macro uint bool[<*>].hash(self) => hash_vec(self);
macro uint typeid.hash(typeid t) => @generic_hash(((ulong)(uptr)t));
macro uint String.hash(String c) => (uint)fnv32a::hash(c);
macro uint char[].hash(char[] c) => (uint)fnv32a::hash(c);
macro uint String.hash(String c) => (uint)a5hash::hash(c);
macro uint char[].hash(char[] c) => (uint)a5hash::hash(c);
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
<*
@@ -486,7 +613,13 @@ macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
*>
macro uint hash_array(array_ptr) @local
{
return (uint)fnv32a::hash(((char*)array_ptr)[:$sizeof(*array_ptr)]);
var $len = $sizeof(*array_ptr);
$if $len > 16:
return (uint)komi::hash(((char*)array_ptr)[:$len]);
$else
return (uint)wyhash2::hash(((char*)array_ptr)[:$len]);
$endif
}
<*
@@ -494,7 +627,13 @@ macro uint hash_array(array_ptr) @local
*>
macro uint hash_vec(vec) @local
{
return (uint)fnv32a::hash(((char*)&&vec)[:$sizeof(vec.len * $typeof(vec).inner.sizeof)]);
var $len = $sizeof(vec.len * $typeof(vec).inner.sizeof);
$if $len > 16:
return (uint)komi::hash(((char*)&&vec)[:$len]);
$else
return (uint)wyhash2::hash(((char*)&&vec)[:$len]);
$endif
}
const MAX_FRAMEADDRESS = 128;

View File

@@ -137,12 +137,13 @@ const bool BACKTRACE = $$BACKTRACE;
const usz LLVM_VERSION = $$LLVM_VERSION;
const bool BENCHMARKING = $$BENCHMARKING;
const bool TESTING = $$TESTING;
const bool PANIC_MSG = $$PANIC_MSG;
const MemoryEnvironment MEMORY_ENV = MemoryEnvironment.from_ordinal($$MEMORY_ENVIRONMENT);
const bool TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING);
const bool X86_64 = ARCH_TYPE == X86_64;
const bool X86 = ARCH_TYPE == X86;
const bool AARCH64 = ARCH_TYPE == AARCH64;
const bool NATIVE_STACKTRACE = LINUX || DARWIN || WIN32;
const bool NATIVE_STACKTRACE = LINUX || DARWIN || OPENBSD || WIN32;
const bool LINUX = LIBC && OS_TYPE == LINUX;
const bool DARWIN = LIBC && os_is_darwin();
const bool WIN32 = LIBC && OS_TYPE == WIN32;
@@ -159,7 +160,7 @@ const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
const int LANGUAGE_DEV_VERSION = $$LANGUAGE_DEV_VERSION;
const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::DARWIN || env::WIN32;
const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::OPENBSD || env::DARWIN || env::WIN32;
macro bool os_is_darwin() @const
{
@@ -198,6 +199,7 @@ macro bool os_is_posix() @const
return false;
$endswitch
}
const String[] AUTHORS = $$AUTHORS;
const String[] AUTHOR_EMAILS = $$AUTHOR_EMAILS;
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);

230
lib/std/core/logging.c3 Normal file
View File

@@ -0,0 +1,230 @@
module std::core::log;
import std::io, std::thread, std::time, std::math::random;
const FULL_LOG = env::COMPILER_SAFE_MODE || $feature(FULL_LOG);
typedef LogCategory = inline char;
typedef LogTag = char[12];
const LogCategory CATEGORY_APPLICATION = 0;
const LogCategory CATEGORY_SYSTEM = 1;
const LogCategory CATEGORY_KERNEL = 2;
const LogCategory CATEGORY_AUDIO = 3;
const LogCategory CATEGORY_VIDEO = 4;
const LogCategory CATEGORY_RENDER = 5;
const LogCategory CATEGORY_INPUT = 6;
const LogCategory CATEGORY_NETWORK = 7;
const LogCategory CATEGORY_SOCKET = 8;
const LogCategory CATEGORY_SECURITY = 9;
const LogCategory CATEGORY_TEST = 10;
const LogCategory CATEGORY_ERROR = 11;
const LogCategory CATEGORY_ASSERT = 12;
const LogCategory CATEGORY_CRASH = 13;
const LogCategory CATEGORY_STATS = 14;
const LogCategory CATEGORY_CUSTOM_START = 100;
tlocal LogCategory default_category = CATEGORY_APPLICATION;
tlocal LogTag current_tag;
enum LogPriority : int
{
VERBOSE,
DEBUG,
INFO,
WARN,
ERROR,
CRITICAL,
}
interface Logger
{
fn void log(LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
}
macro void verbose(String fmt, ..., LogCategory category = default_category) => call_log(VERBOSE, category, fmt, $vasplat);
macro void debug(String fmt, ..., LogCategory category = default_category) => call_log(DEBUG, category, fmt, $vasplat);
macro void info(String fmt, ..., LogCategory category = default_category) => call_log(INFO, category, fmt, $vasplat);
macro void warn(String fmt, ..., LogCategory category = default_category) => call_log(WARN, category, fmt, $vasplat);
macro void error(String fmt, ..., LogCategory category = default_category) => call_log(ERROR, category, fmt, $vasplat);
macro void critical(String fmt, ..., LogCategory category = default_category) => call_log(CRITICAL, category, fmt, $vasplat);
macro void @category_scope(LogCategory new_category; @body)
{
LogCategory old = default_category;
default_category = new_category;
defer default_category = old;
@body();
}
<*
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
*>
macro void @tag_scope(String tag_prefix = ""; @body)
{
LogTag old = current_tag;
push_tag(tag_prefix);
defer current_tag = old;
@body();
}
<*
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
*>
macro void push_tag(String tag_prefix = "")
{
current_tag = create_tag(tag_prefix);
}
<*
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
*>
fn LogTag create_tag(String tag_prefix)
{
LogTag tag @noinit;
int start = 0;
foreach (int i, c : tag_prefix)
{
if (c == 0) break;
tag[start++] = c;
}
if (start > 0) tag[start++] = '_';
for (int i = start; i < tag.len; i++)
{
tag[i] = (char)rand_in_range('a', 'z');
}
return tag;
}
fn void set_priority_for_category(LogCategory category, LogPriority new_priority)
{
@atomic_store(config_priorities[category], new_priority, UNORDERED);
}
fn LogPriority get_priority_for_category(LogCategory category)
{
return @atomic_load(config_priorities[category], UNORDERED);
}
fn void set_priority_all(LogPriority new_priority)
{
for (int i = 0; i < config_priorities.len; i++)
{
@atomic_store(config_priorities[i], new_priority, UNORDERED);
}
}
fn void set_logger(Logger logger)
{
init();
if (!logger_mutex.is_initialized())
{
current_logger = logger;
current_logfn = &logger.log;
return;
}
logger_mutex.@in_lock()
{
current_logger = logger;
current_logfn = &logger.log;
};
}
macro void init()
{
log_init.call(fn () => (void)logger_mutex.init());
}
fn void call_log(LogPriority prio, LogCategory category, String fmt, args...)
{
LogPriority priority = mem::@atomic_load(config_priorities[category], UNORDERED);
if (priority < prio) return;
init();
bool locked = logger_mutex.is_initialized() && @ok(logger_mutex.lock());
Logger logger = current_logger;
LogFn logfn = current_logfn;
defer if (locked) (void)logger_mutex.unlock();
$if FULL_LOG:
logfn(logger.ptr, prio, category, current_tag, $$FILE, $$FUNC, $$LINE, fmt, args);
$else
logfn(logger.ptr, prio, category, current_tag, "", "", 0, fmt, args);
$endif
}
fn String? get_category_name(LogCategory category)
{
String val = category_names[category];
return val ?: NOT_FOUND?;
}
fn void set_category_name(LogCategory category, String name)
{
category_names[category] = name;
}
struct NullLogger (Logger)
{
void* dummy;
}
fn void NullLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
{}
struct MultiLogger (Logger)
{
Logger[] loggers;
}
fn void MultiLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
{
foreach (logger : self.loggers)
{
logger.log(priority, category, tag, file, function, line, fmt, args);
}
}
module std::core::log @private;
import std::io, std::thread, std::time;
struct StderrLogger (Logger) @if(env::LIBC)
{
void* dummy;
}
fn void StderrLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic @if(env::LIBC)
{
@stack_mem(256 + 64; Allocator mem)
{
DString str;
str.init(mem, 256);
str.appendf(fmt, ...args);
TzDateTime time = datetime::now().to_local();
io::eprintfn("[%02d:%02d:%02d:%04d] [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), priority, str);
};
}
alias LogFn = fn void(void*, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
LogFn current_logfn = @select(env::LIBC, (LogFn)&StderrLogger.log, (LogFn)&NullLogger.log);
OnceFlag log_init;
Mutex logger_mutex;
Logger current_logger = @select(env::LIBC, &stderr_logger, &null_logger);
StderrLogger stderr_logger @if (env::LIBC);
NullLogger null_logger;
LogPriority[256] config_priorities = { [0..255] = ERROR, [CATEGORY_APPLICATION] = INFO, [CATEGORY_TEST] = VERBOSE, [CATEGORY_ASSERT] = WARN};
String[256] category_names = {
[CATEGORY_APPLICATION] = "APP",
[CATEGORY_SYSTEM] = "SYSTEM",
[CATEGORY_KERNEL] = "KERNEL",
[CATEGORY_AUDIO] = "AUDIO",
[CATEGORY_VIDEO] = "VIDEO",
[CATEGORY_RENDER] = "RENDER",
[CATEGORY_INPUT] = "INPUT",
[CATEGORY_NETWORK] = "NETWORD",
[CATEGORY_SOCKET] = "SOCKET",
[CATEGORY_SECURITY] = "SECURITY",
[CATEGORY_TEST] = "TEST",
[CATEGORY_ERROR] = "ERROR",
[CATEGORY_ASSERT] = "ASSERT",
[CATEGORY_CRASH] = "CRASH",
[CATEGORY_STATS] = "STATS"
};

View File

@@ -3,10 +3,15 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem;
import std::core::mem::allocator @public;
import std::os::posix, std::os::win32;
import std::math;
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
const ulong KB = 1024;
const ulong MB = KB * 1024;
const ulong GB = MB * 1024;
const ulong TB = GB * 1024;
faultdef OUT_OF_MEMORY, INVALID_ALLOC_SIZE;
@@ -15,13 +20,31 @@ macro bool @constant_is_power_of_2($x) @const @private
return $x != 0 && ($x & ($x - 1)) == 0;
}
fn usz os_pagesize()
{
$switch:
$case env::POSIX:
static usz pagesize;
if (pagesize) return pagesize;
return pagesize = posix::getpagesize();
$case env::WIN32:
static usz pagesize;
if (pagesize) return pagesize;
Win32_SYSTEM_INFO info;
win32::getSystemInfo(&info);
return pagesize = info.dwPageSize;
$default:
return 4096;
$endswitch
}
<*
Load a vector from memory according to a mask assuming default alignment.
@param ptr : "The pointer address to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @assignable_to(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@@ -40,7 +63,7 @@ macro masked_load(ptr, bool[<*>] mask, passthru)
@param passthru : "The value to use for non masked values"
@param $alignment : "The alignment to assume for the pointer"
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @assignable_to(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -61,7 +84,7 @@ macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require @assignable_to(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@@ -83,7 +106,7 @@ macro gather(ptrvec, bool[<*>] mask, passthru)
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require @assignable_to(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -103,7 +126,7 @@ macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
@param value : "The value to store masked"
@param mask : "The mask for the store"
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @assignable_to(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require value.len == mask.len : "Mask and value must have the same length"
*>
@@ -118,7 +141,7 @@ macro masked_store(ptr, value, bool[<*>] mask)
@param mask : "The mask for the store"
@param $alignment : "The alignment of the pointer"
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @assignable_to(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require value.len == mask.len : "Mask and value must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -135,7 +158,7 @@ macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
@param mask : "The mask for the store"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require @assignable_to(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require value.len == mask.len : "Mask and value must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@@ -153,7 +176,7 @@ macro scatter(ptrvec, value, bool[<*>] mask)
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require @assignable_to(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require value.len == mask.len : "Mask and value must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -282,7 +305,7 @@ macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = S
*>
fn usz aligned_offset(usz offset, usz alignment)
{
return alignment * ((offset + alignment - 1) / alignment);
return (offset + alignment - 1) & ~(alignment - 1);
}
macro void* aligned_pointer(void* ptr, usz alignment)
@@ -298,6 +321,11 @@ fn bool ptr_is_aligned(void* ptr, usz alignment) @inline
return (uptr)ptr & ((uptr)alignment - 1) == 0;
}
fn bool ptr_is_page_aligned(void* ptr) @inline
{
return (uptr)ptr & ((uptr)os_pagesize() - 1) == 0;
}
macro void zero_volatile(char[] data)
{
$$memset(data.ptr, (char)0, data.len, true, (usz)1);
@@ -700,7 +728,7 @@ fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro new($Type, ...) @nodiscard
@@ -716,7 +744,7 @@ macro new($Type, ...) @nodiscard
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro new_with_padding($Type, usz padding, ...) @nodiscard
@@ -734,7 +762,7 @@ macro new_with_padding($Type, usz padding, ...) @nodiscard
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new_aligned($Type, ...) @nodiscard
{
@@ -774,7 +802,7 @@ macro alloc_aligned($Type) @nodiscard
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro tnew($Type, ...) @nodiscard
{
@@ -789,7 +817,7 @@ macro tnew($Type, ...) @nodiscard
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro temp_with_padding($Type, usz padding, ...) @nodiscard
{
@@ -903,6 +931,18 @@ fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMEN
return tmem.resize(ptr, size, alignment)!!;
}
<*
Takes the address of a possibly unaligned variable or member,
and offers safe access to that member, by constructing an UnalignedRef.
@require $defined(&#arg) : "It must be possible to take the address of the argument."
@return "An 'UnalignedRef' with the proper type and alignment, with a pointer to argument"
*>
macro @unaligned_addr(#arg) @builtin
{
return (UnalignedRef{$typeof(#arg), $alignof(#arg)})&#arg;
}
module std::core::mem @if(env::NO_LIBC);
fn CInt __memcmp(void* s1, void* s2, usz n) @weak @export("memcmp")
@@ -940,3 +980,39 @@ fn void* __memcpy(void* dst, void* src, usz n) @weak @export("memcpy")
}
return dst;
}
module std::core::mem::volatile { Type };
typedef Volatile @structlike = Type;
macro Type Volatile.get(&self)
{
return @volatile_load(*(Type*)self);
}
macro Type Volatile.set(&self, Type val)
{
return @volatile_store(*(Type*)self, val);
}
<*
@require mem::@constant_is_power_of_2(ALIGNMENT) : "The alignment must be a power of 2"
*>
module std::core::mem::alignment { Type, ALIGNMENT };
import std::core::mem @public;
<*
An UnalignedRef offers correctly aligned access to addresses that may be unaligned or overaligned.
*>
typedef UnalignedRef = Type*;
macro Type UnalignedRef.get(self)
{
return @unaligned_load(*(Type*)self, ALIGNMENT);
}
macro Type UnalignedRef.set(&self, Type val)
{
return @unaligned_store(*(Type*)self, val, ALIGNMENT);
}

View File

@@ -1,7 +1,7 @@
module std::core::mem::allocator;
import std::math;
// C3 has multiple different allocators available:
// C3 has several different allocators available:
//
// Name Arena Uses buffer OOM Fallback? Mark? Reset?
// ArenaAllocator Yes Yes No Yes Yes
@@ -12,6 +12,7 @@ import std::math;
// OnStackAllocator Yes Yes Yes No No *Note: Used by @stack_mem
// TempAllocator Yes No Yes No* No* *Note: Mark/reset using @pool
// TrackingAllocator No No N/A No No *Note: Wraps other heap allocator
// Vmem Yes No No Yes Yes *Note: Can be set to huge sizes
const DEFAULT_SIZE_PREFIX = usz.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
@@ -64,7 +65,7 @@ alias MemoryAllocFn = fn char[]?(usz);
fn usz alignment_for_allocation(usz alignment) @inline @private
fn usz alignment_for_allocation(usz alignment) @inline
{
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
}
@@ -166,7 +167,7 @@ macro void free_aligned(Allocator allocator, void* ptr)
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new(Allocator allocator, $Type, ...) @nodiscard
{
@@ -182,7 +183,7 @@ macro new(Allocator allocator, $Type, ...) @nodiscard
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new_try(Allocator allocator, $Type, ...) @nodiscard
{
@@ -199,7 +200,7 @@ macro new_try(Allocator allocator, $Type, ...) @nodiscard
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
{
@@ -463,7 +464,7 @@ macro usz temp_allocator_default_reserve_size() @local
$endswitch
}
macro Allocator heap() => thread_allocator;
macro Allocator heap() @deprecated("Use 'mem' instead.") => thread_allocator;
<*
@require !top_temp : "This should never be called when temp already exists"
@@ -486,7 +487,7 @@ fn Allocator create_temp_allocator(Allocator allocator, usz size, usz reserve, u
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size, reserve, min_size, realloc_size)!!;
}
macro Allocator temp()
macro Allocator temp() @deprecated("Use 'tmem' instead")
{
return current_temp;
}

234
lib/std/core/mem_mempool.c3 Normal file
View File

@@ -0,0 +1,234 @@
module std::core::mem::mempool;
import std::core::mem, std::core::mem::allocator, std::math;
const INITIAL_CAPACITY = 0;
struct FixedBlockPoolNode
{
void* buffer;
FixedBlockPoolNode *next;
usz capacity;
}
struct FixedBlockPoolEntry
{
void *previous;
}
<*
Fixed blocks pool pre-allocating blocks backed by an Allocator which are then reserved for the user,
blocks deallocated by the user are later re-used by future blocks allocations
`grow_capacity` can be changed in order to affect how many blocks will be allocated by next pool allocation,
it has to be greater than 0
`allocated` number of allocated blocks
`used` number of used blocks by the user
*>
struct FixedBlockPool
{
Allocator allocator;
FixedBlockPoolNode head;
FixedBlockPoolNode *tail;
void *next_free;
void *freelist;
usz block_size;
usz grow_capacity;
usz allocated;
usz page_size;
usz alignment;
usz used;
bool initialized;
}
<*
Initialize an block pool
@param [in] allocator : "The allocator to use"
@param block_size : "The block size to use"
@param capacity : "The amount of blocks to be pre-allocated"
@param alignment : "The alignment of the buffer"
@require !alignment || math::is_power_of_2(alignment)
@require !self.initialized : "The block pool must not be initialized"
@require block_size > 0 : "Block size must be non zero"
@require calculate_actual_capacity(capacity, block_size) * block_size >= block_size
: "Total memory would overflow"
*>
macro FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_size, usz capacity = INITIAL_CAPACITY, usz alignment = 0)
{
self.allocator = allocator;
self.tail = &self.head;
self.head.next = null;
self.block_size = math::max(block_size, FixedBlockPoolEntry.sizeof);
capacity = calculate_actual_capacity(capacity, self.block_size);
self.alignment = allocator::alignment_for_allocation(alignment);
self.page_size = capacity * self.block_size;
assert(self.page_size >= self.block_size, "Total memory would overflow %d %d", block_size, capacity);
self.head.buffer = self.allocate_page();
self.head.capacity = capacity;
self.next_free = self.head.buffer;
self.freelist = null;
self.grow_capacity = capacity;
self.initialized = true;
self.allocated = capacity;
self.used = 0;
return self;
}
<*
Initialize an block pool
@param [in] allocator : "The allocator to use"
@param $Type : "The type used for setting the block size"
@param capacity : "The amount of blocks to be pre-allocated"
@require !self.initialized : "The block pool must not be initialized"
*>
macro FixedBlockPool* FixedBlockPool.init_for_type(&self, Allocator allocator, $Type, usz capacity = INITIAL_CAPACITY)
{
return self.init(allocator, $Type.sizeof, capacity, $Type.alignof);
}
<*
Initialize an block pool using Temporary allocator
@param $Type : "The type used for setting the block size"
@param capacity : "The amount of blocks to be pre-allocated"
@require !self.initialized : "The block pool must not be initialized"
*>
macro FixedBlockPool* FixedBlockPool.tinit_for_type(&self, $Type, usz capacity = INITIAL_CAPACITY) => self.init_for_type(tmem, $Type, capacity);
<*
Initialize an block pool using Temporary allocator
@param block_size : "The block size to use"
@param capacity : "The amount of blocks to be pre-allocated"
@require !self.initialized : "The block pool must not be initialized"
*>
macro FixedBlockPool* FixedBlockPool.tinit(&self, usz block_size, usz capacity = INITIAL_CAPACITY) => self.init(tmem, block_size, capacity);
<*
Free up the entire block pool
@require self.initialized : "The block pool must be initialized"
*>
fn void FixedBlockPool.free(&self)
{
self.free_page(self.head.buffer);
FixedBlockPoolNode* iter = self.head.next;
while (iter)
{
self.free_page(iter.buffer);
FixedBlockPoolNode* current = iter;
iter = iter.next;
allocator::free(self.allocator, current);
}
self.initialized = false;
self.allocated = 0;
self.used = 0;
}
<*
Allocate an block on the block pool, re-uses previously deallocated blocks
@require self.initialized : "The block pool must be initialized"
*>
fn void* FixedBlockPool.alloc(&self)
{
defer self.used++;
if (self.freelist)
{
FixedBlockPoolEntry* entry = self.freelist;
self.freelist = entry.previous;
mem::clear(entry, self.block_size);
return entry;
}
void* end = self.tail.buffer + (self.tail.capacity * self.block_size);
if (self.next_free >= end) self.new_node();
void* ptr = self.next_free;
self.next_free += self.block_size;
return ptr;
}
<*
Deallocate a block from the block pool
@require self.initialized : "The block pool must be initialized"
@require self.check_ptr(ptr) : "The pointer should be part of the pool"
*>
fn void FixedBlockPool.dealloc(&self, void* ptr)
{
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
if (self.block_size > FixedBlockPoolEntry.sizeof)
{
mem::set(ptr + FixedBlockPoolEntry.sizeof, 0xAA, self.block_size);
}
$else
// POINT FOR IMPROVEMENT, something like:
// asan::poison_memory_region(&ptr, self.block_size);
$endif
FixedBlockPoolEntry* entry = ptr;
entry.previous = self.freelist;
self.freelist = entry;
self.used--;
}
<*
@require self.initialized : "The block pool must be initialized"
*>
fn bool FixedBlockPool.check_ptr(&self, void *ptr) @local
{
FixedBlockPoolNode* iter = &self.head;
while (iter)
{
void* end = iter.buffer + (iter.capacity * self.block_size);
if (ptr >= iter.buffer && ptr < end) return true;
iter = iter.next;
}
return false;
}
<*
@require self.grow_capacity > 0 : "How many blocks will it store"
*>
fn void FixedBlockPool.new_node(&self) @local
{
FixedBlockPoolNode* node = allocator::new(self.allocator, FixedBlockPoolNode);
node.buffer = self.allocate_page();
node.capacity = self.grow_capacity;
self.tail.next = node;
self.tail = node;
self.next_free = node.buffer;
self.allocated += node.capacity;
}
macro void* FixedBlockPool.allocate_page(&self) @private
{
return self.alignment > mem::DEFAULT_MEM_ALIGNMENT
? allocator::calloc_aligned(self.allocator, self.page_size, self.alignment)!!
: allocator::calloc(self.allocator, self.page_size);
}
macro void FixedBlockPool.free_page(&self, void* page) @private
{
if (self.alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
allocator::free_aligned(self.allocator, page);
}
else
{
allocator::free(self.allocator, page);
}
}
macro usz calculate_actual_capacity(usz capacity, usz block_size) @private
{
// Assume some overhead
if (capacity) return capacity;
capacity = (mem::os_pagesize() - 128) / block_size;
return capacity ?: 1;
}

353
lib/std/core/os/mem_vm.c3 Normal file
View File

@@ -0,0 +1,353 @@
<*
The VM module holds code for working with virtual memory on supported platforms (currently Win32 and Posix)
*>
module std::core::mem::vm;
import std::io, std::os::win32, std::os::posix, libc;
<*
VirtualMemory is an abstraction for working with an allocated virtual memory area. It will invoke vm:: functions
but will perform more checks and track its size (required to unmap the memory on Posix)
*>
struct VirtualMemory
{
void* ptr;
usz size;
VirtualMemoryAccess default_access;
}
faultdef RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, UNMAPPED_ACCESS, UNALIGNED_ADDRESS, RELEASE_FAILED, UPDATE_FAILED, INVALID_ARGS;
enum VirtualMemoryAccess
{
PROTECTED,
READ,
WRITE,
READWRITE,
EXEC,
EXECREAD,
EXECWRITE,
ANY
}
fn usz aligned_alloc_size(usz size)
{
$if env::WIN32:
return size > 0 ? mem::aligned_offset(size, win32::allocation_granularity()) : win32::allocation_granularity();
$else
return size > 0 ? mem::aligned_offset(size, mem::os_pagesize()) : mem::os_pagesize();
$endif
}
<*
Allocate virtual memory, size is rounded up to platform granularity (Win32) / page size (Posix).
@param size : "The size of the memory to allocate, will be rounded up"
@param access : "The initial access permissions."
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
@return "Pointer to the allocated memory, page aligned"
*>
fn void*? alloc(usz size, VirtualMemoryAccess access)
{
$switch:
$case env::POSIX:
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), posix::MAP_PRIVATE | posix::MAP_ANONYMOUS, -1, 0);
if (ptr != posix::MAP_FAILED) return ptr;
switch (libc::errno())
{
case errno::ENOMEM: return mem::OUT_OF_MEMORY?;
case errno::EOVERFLOW: return RANGE_OVERFLOW?;
case errno::EPERM: return ACCESS_DENIED?;
case errno::EINVAL: return INVALID_ARGS?;
default: return UNKNOWN_ERROR?;
}
$case env::WIN32:
void* ptr = win32::virtualAlloc(null, aligned_alloc_size(size), MEM_RESERVE, access.to_win32());
if (ptr) return ptr;
switch (win32::getLastError())
{
case win32::ERROR_NOT_ENOUGH_MEMORY:
case win32::ERROR_COMMITMENT_LIMIT: return mem::OUT_OF_MEMORY?;
default: return UNKNOWN_ERROR?;
}
$default:
unsupported("Virtual alloc only available on Win32 and Posix");
$endswitch
}
<*
Release memory allocated with "alloc".
@param [&inout] ptr : "Pointer to page to release, should be allocated using vm::alloc"
@param size : "The size of the allocated pointer"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
*>
fn void? release(void* ptr, usz size)
{
$switch:
$case env::POSIX:
if (posix::munmap(ptr, aligned_alloc_size(size)))
{
switch (libc::errno())
{
case errno::EINVAL: return INVALID_ARGS?; // Not a valid mapping or size
case errno::ENOMEM: return UNMAPPED_ACCESS?; // Address not mapped
default: return RELEASE_FAILED?;
}
}
$case env::WIN32:
if (win32::virtualFree(ptr, 0, MEM_RELEASE)) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return INVALID_ARGS?;
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY?;
default: return RELEASE_FAILED?;
}
$default:
unsupported("Virtual free only available on Win32 and Posix");
$endswitch
}
<*
Change the access protection of a region in memory. The region must be page aligned.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to update, must be page aligned"
@param access : "The new access"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? protect(void* ptr, usz len, VirtualMemoryAccess access)
{
$switch:
$case env::POSIX:
if (!posix::mprotect(ptr, len, access.to_posix())) return;
switch (libc::errno())
{
case errno::EACCES: return ACCESS_DENIED?;
case errno::EINVAL: return UNALIGNED_ADDRESS?;
case errno::EOVERFLOW: return RANGE_OVERFLOW?;
case errno::ENOMEM: return UNMAPPED_ACCESS?;
default: return UPDATE_FAILED?;
}
$case env::WIN32:
Win32_Protect old;
if (win32::virtualProtect(ptr, len, access.to_win32(), &old)) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
default: return UPDATE_FAILED?;
}
$default:
unsupported("'virtual_protect' is only available on Win32 and Posix.");
$endswitch
}
<*
Makes a region of memory available that was previously retrieved using 'alloc'. This is necessary on Win32,
but optional on Posix.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to commit, must be page aligned"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? UNKNOWN_ERROR, mem::OUT_OF_MEMORY, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? commit(void* ptr, usz len, VirtualMemoryAccess access = READWRITE)
{
$switch:
$case env::POSIX:
return protect(ptr, len, READWRITE) @inline;
$case env::WIN32:
void* result = win32::virtualAlloc(ptr, len, MEM_COMMIT, access.to_win32());
if (result) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
case win32::ERROR_COMMITMENT_LIMIT:
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
default: return UNKNOWN_ERROR?;
}
$default:
unsupported("'virtual_commit' is only available on Win32 and Posix.");
$endswitch
}
<*
Notifies that the memory in the region can be released back to the OS. On Win32 this decommits the region,
whereas on Posix it tells the system that it may be reused using madvise. The "block" parameter is only
respected on Posix, and protects the region from read/write/exec. On Win32 this always happens.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to commit, must be page aligned"
@param block : "Set the released memory to protected"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? decommit(void* ptr, usz len, bool block = true)
{
$switch:
$case env::POSIX:
if (posix::madvise(ptr, len, posix::MADV_DONTNEED))
{
switch (libc::errno())
{
case errno::EINVAL: return UNALIGNED_ADDRESS?;
case errno::ENOMEM: return UNMAPPED_ACCESS?;
default: return UPDATE_FAILED?;
}
}
if (block) (void)protect(ptr, len, PROTECTED) @inline;
$case env::WIN32:
if (!win32::virtualFree(ptr, len, MEM_DECOMMIT))
{
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
default: return UPDATE_FAILED?;
}
}
$default:
unsupported("'virtual_decommit' is only available on Win32 and Posix.");
$endswitch
}
<*
Map a portion of an already-opened file into memory.
@param fd : "File descriptor"
@param size : "Number of bytes to map, will be rounded up to page size"
@param offset : "Byte offset in file, must be page size aligned"
@param access : "The initial access permissions"
@param shared : "if True then MAP_SHARED else MAP_PRIVATE"
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
@return "Pointer to the mapped region"
*>
fn void*? mmap_file(Fd fd, usz size, usz offset = 0, VirtualMemoryAccess access = READ, bool shared = false) @if (env::POSIX)
{
CInt flags = shared ? posix::MAP_SHARED : posix::MAP_PRIVATE;
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), flags, fd, offset);
if (ptr != posix::MAP_FAILED) return ptr;
switch (libc::errno())
{
case errno::ENOMEM: return mem::OUT_OF_MEMORY?;
case errno::EOVERFLOW: return RANGE_OVERFLOW?;
case errno::EPERM: return ACCESS_DENIED?;
case errno::EINVAL: return INVALID_ARGS?;
case errno::EACCES: return io::NO_PERMISSION?;
case errno::EBADF: return io::FILE_NOT_VALID?;
case errno::EAGAIN: return io::WOULD_BLOCK?;
case errno::ENXIO: return io::FILE_NOT_FOUND?;
default: return UNKNOWN_ERROR?;
}
}
<*
Create a VirtualMemory using
@param size : "The size of the memory to allocate."
@require size > 0 : "The size must be non-zero"
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
*>
fn VirtualMemory? virtual_alloc(usz size, VirtualMemoryAccess access = PROTECTED)
{
size = aligned_alloc_size(size);
void* ptr = alloc(size, access)!;
return { ptr, size, access };
}
<*
Commits memory, using vm::commit
@param offset : "Starting from what offset to commit"
@param len : "To what len to commit"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len <= self.size : "Length out of range"
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
*>
macro void? VirtualMemory.commit(self, usz offset, usz len)
{
return commit(self.ptr + offset, len, self.default_access);
}
<*
Changes protection of a part of memory using vm::protect
@param offset : "Starting from what offset to update"
@param len : "To what len to update"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
*>
macro void? VirtualMemory.protect(self, usz offset, usz len, VirtualMemoryAccess access)
{
return protect(self.ptr + offset, len, access);
}
<*
Decommits a part of memory using vm::decommit
@param offset : "Starting from what offset to decommit"
@param len : "To what len to decommit"
@param block : "Should the memory be blocked from access after decommit"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@return? UPDATE_FAILED
*>
fn void? VirtualMemory.decommit(self, usz offset, usz len, bool block = true)
{
return decommit(self.ptr + offset, len, block);
}
<*
Releases the memory region
@require self.ptr != null : "Virtual memory must be initialized to call destroy"
*>
fn void? VirtualMemory.destroy(&self)
{
return release(self.ptr, self.size);
}
fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX) @private
{
switch (self)
{
case PROTECTED: return posix::PROT_NONE;
case READ: return posix::PROT_READ;
case WRITE: return posix::PROT_WRITE;
case EXEC: return posix::PROT_EXEC;
case READWRITE: return posix::PROT_READ | posix::PROT_WRITE;
case EXECREAD: return posix::PROT_READ | posix::PROT_EXEC;
case EXECWRITE: return posix::PROT_WRITE | posix::PROT_EXEC;
case ANY: return posix::PROT_WRITE | posix::PROT_READ | posix::PROT_EXEC;
}
}
fn Win32_Protect VirtualMemoryAccess.to_win32(self) @if(env::WIN32) @private
{
switch (self)
{
case PROTECTED: return PAGE_NOACCESS;
case READ: return PAGE_READONLY;
case WRITE: return PAGE_READWRITE;
case EXEC: return PAGE_EXECUTE;
case READWRITE: return PAGE_READWRITE;
case EXECWRITE: return PAGE_EXECUTE_READWRITE;
case EXECREAD: return PAGE_EXECUTE_READ;
case ANY: return PAGE_EXECUTE_READWRITE;
}
}

132
lib/std/core/refcount.c3 Normal file
View File

@@ -0,0 +1,132 @@
<*
Ref provides a general *external* ref counted wrapper for a pointer. For convenience, a ref count of 0
means the reference is still valid.
When the rc drops to -1, it will first run the dealloc function on the underlying pointer (if it exists),
then free the pointer and the atomic variable assuming that they are allocated using the Allocator in the Ref.
@require !$defined(Type.dealloc) ||| $defined(Type.dealloc(&&(Type){})) : "'dealloc' must only take a pointer to the underlying type"
@require !$defined(Type.dealloc) ||| @typeis((Type){}.dealloc(), void) : "'dealloc' must return 'void'"
*>
module std::core::mem::ref { Type };
import std::thread, std::atomic;
const OVERALIGNED @private = Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
alias DeallocFn = fn void(void*);
fn Ref wrap(Type* ptr, Allocator allocator = mem)
{
return { .refcount = allocator::new(allocator, Atomic{int}), .ptr = ptr, .allocator = allocator };
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| @assignable_to($vaexpr[0], Type) : "The first argument must be an initializer for the type"
*>
macro Ref new(..., Allocator allocator = mem)
{
$switch:
$case OVERALIGNED && !$vacount:
Type* ptr = allocator::calloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
$case OVERALIGNED:
Type* ptr = allocator::malloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
*ptr = $vaexpr[0];
$case !$vacount:
Type* ptr = allocator::calloc(allocator, Type.sizeof);
$default:
Type* ptr = allocator::malloc(allocator, Type.sizeof);
*ptr = $vaexpr[0];
$endswitch
return { .refcount = allocator::new(allocator, Atomic{int}),
.ptr = ptr,
.allocator = allocator };
}
struct Ref
{
Atomic{int}* refcount;
Type* ptr;
Allocator allocator;
}
fn Ref* Ref.retain(&self)
{
assert(self.refcount != null, "Reference already released");
assert(self.refcount.load(RELAXED) >= 0, "Retaining zombie");
self.refcount.add(1, RELAXED);
return self;
}
fn void Ref.release(&self)
{
assert(self.refcount != null, "Reference already released");
assert(self.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
if (self.refcount.sub(1, RELAXED) == 0)
{
thread::fence(ACQUIRE);
$if $defined(Type.dealloc):
self.ptr.dealloc();
$endif
$if OVERALIGNED:
allocator::free_aligned(self.allocator, self.ptr);
$else
allocator::free(self.allocator, self.ptr);
$endif
allocator::free(self.allocator, self.refcount);
*self = {};
}
}
module std::core::mem::rc;
import std::thread, std::atomic;
<*
A RefCounted struct should be an inline base of a struct.
If a `dealloc` is defined, then it will be called rather than `free`
For convenience, a ref count of 0 is still valid, and the struct is
only freed when when ref count drops to -1.
The macros rc::retain and rc::release must be used on the full pointer,
not on the RefCounted substruct.
So `Foo* f = ...; RefCounted* rc = f; rc::release(rc);` will not do the right thing.
*>
struct RefCounted
{
Atomic{int} refcount;
}
<*
@require @assignable_to(refcounted, RefCounted*) : "Expected a ref counted value"
*>
macro retain(refcounted)
{
if (refcounted)
{
assert(refcounted.refcount.load(RELAXED) >= 0, "Retaining zombie");
refcounted.refcount.add(1, RELAXED);
}
return refcounted;
}
<*
@require @assignable_to(refcounted, RefCounted*) : "Expected a ref counted value"
@require !$defined(refcounted.dealloc()) ||| @typeis(refcounted.dealloc(), void)
: "Expected refcounted type to have a valid dealloc"
*>
macro void release(refcounted)
{
if (!refcounted) return;
assert(refcounted.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
if (refcounted.refcount.sub(1, RELAXED) == 0)
{
thread::fence(ACQUIRE);
$if $defined(refcounted.dealloc):
refcounted.dealloc();
$else
free(refcounted);
$endif
}
}

View File

@@ -1,7 +1,9 @@
module std::core::runtime;
import libc, std::time, std::io, std::sort;
import libc, std::time, std::io, std::sort, std::math, std::collections::map;
alias BenchmarkFn = fn void();
alias BenchmarkFn = fn void ();
HashMap { String, uint } bench_fn_iters @local;
struct BenchmarkUnit
{
@@ -17,6 +19,7 @@ fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator)
foreach (i, benchmark : fns)
{
benchmarks[i] = { names[i], fns[i] };
if (!bench_fn_iters.has_key(names[i])) bench_fn_iters[names[i]] = benchmark_max_iterations;
}
return benchmarks;
}
@@ -36,6 +39,30 @@ fn void set_benchmark_max_iterations(uint value) @builtin
{
assert(value > 0);
benchmark_max_iterations = value;
foreach (k : bench_fn_iters.key_iter()) bench_fn_iters[k] = value;
}
fn void set_benchmark_func_iterations(String func, uint value) @builtin
{
assert(value > 0);
bench_fn_iters[func] = value;
}
Clock benchmark_clock @local;
NanoDuration benchmark_nano_seconds @local;
DString benchmark_log @local;
bool benchmark_warming @local;
uint this_iteration @local;
macro @start_benchmark() => benchmark_clock = std::time::clock::now();
macro @end_benchmark() => benchmark_nano_seconds = benchmark_clock.mark();
macro @log_benchmark(msg, args...) => @pool()
{
if (benchmark_warming) return;
benchmark_log.appendf("%s [%d]: ", $$FUNC, this_iteration);
benchmark_log.appendfn(msg, ...args);
}
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
@@ -61,33 +88,58 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
long sys_clock_started;
long sys_clock_finished;
long sys_clocks;
Clock clock;
foreach(unit : benchmarks)
foreach (unit : benchmarks)
{
defer name.clear();
name.appendf("Benchmarking %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
benchmark_warming = true;
for (uint i = 0; i < benchmark_warmup_iterations; i++)
{
unit.func() @inline;
}
benchmark_warming = false;
clock = std::time::clock::now();
NanoDuration running_timer;
sys_clock_started = $$sysclock();
benchmark_nano_seconds = {};
for (uint i = 0; i < benchmark_max_iterations; i++)
uint current_benchmark_iterations = bench_fn_iters[unit.name] ?? benchmark_max_iterations;
char[] perc_str = { [0..19] = ' ', [20] = 0 };
int perc = 0;
for (this_iteration = 0; this_iteration < current_benchmark_iterations; ++this_iteration)
{
perc_str[0..(uint)math::floor((this_iteration / (float)current_benchmark_iterations) * 20)] = '#';
perc = (uint)math::ceil(100 * (this_iteration / (float)current_benchmark_iterations));
io::printf("\r%s [%s] %d / %d (%d%%)", name.str_view(), (ZString)perc_str, this_iteration, current_benchmark_iterations, perc);
io::stdout().flush()!!;
@start_benchmark(); // can be overridden by calls inside the unit's func
unit.func() @inline;
if (!benchmark_nano_seconds) @end_benchmark();
running_timer += benchmark_nano_seconds;
}
sys_clock_finished = $$sysclock();
NanoDuration nano_seconds = clock.mark();
sys_clocks = sys_clock_finished - sys_clock_started;
io::printfn("[COMPLETE] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
float clock_cycles = (float)sys_clocks / current_benchmark_iterations;
float measurement = (float)running_timer / current_benchmark_iterations;
String[] units = { "nanoseconds", "microseconds", "milliseconds", "seconds" };
float adjusted_measurement = measurement;
while (adjusted_measurement > 1_000) adjusted_measurement /= 1_000;
io::printf("\r%s ", name.str_view());
io::printfn("[COMPLETE] %.2f %s, %.2f CPU clocks, %d iterations",
adjusted_measurement, units[math::min(3, (int)math::floor(math::log(measurement, 1_000)))], clock_cycles, current_benchmark_iterations);
}
io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : "");
@@ -96,5 +148,12 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
fn bool default_benchmark_runner(String[] args) => @pool()
{
benchmark_log.init(mem);
defer
{
if (benchmark_log.len()) io::printfn("\n---------- BENCHMARK LOG ----------\n%s\n", benchmark_log.str_view());
benchmark_log.free();
}
return run_benchmarks(benchmark_collection_create(tmem));
}

View File

@@ -171,6 +171,11 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
usz max_name;
bool sort_tests = true;
bool check_leaks = true;
if (!tests.len)
{
io::printn("There are no test units to run.");
return true; // no tests == technically a pass
}
foreach (&unit : tests)
{
if (max_name < unit.name.len) max_name = unit.name.len;
@@ -305,7 +310,7 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
}
mem.free();
}
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count > 1 ? "s" : "");
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count != 1 ? "s" : "");
int n_failed = test_count - tests_passed - tests_skipped;
io::printf("Test Result: %s%s%s: ",

View File

@@ -1,5 +1,7 @@
module std::core::string;
import std::io;
import std::core::mem::allocator;
typedef String @if(!$defined(String)) = inline char[];
<*
@@ -107,6 +109,19 @@ fn String format(Allocator allocator, String fmt, args...) @format(1) => @pool()
return str.copy_str(allocator);
}
<*
Return a new String created using the formatting function.
@param [inout] buffer : `The buffer to use`
@param [in] fmt : `The formatting string`
*>
fn String bformat(char[] buffer, String fmt, args...) @format(1)
{
DString str = dstring::new_with_capacity(allocator::wrap(buffer), fmt.len + args.len * 8);
str.appendf(fmt, ...args);
return str.str_view();
}
<*
Return a temporary String created using the formatting function.
@@ -582,6 +597,7 @@ fn bool ZString.eq(self, ZString other) @operator(==)
char* a = self;
char* b = other;
if (a == b) return true;
if (!a || !b) return false;
for (;; a++, b++)
{
char c = *a;
@@ -608,12 +624,17 @@ fn usz ZString.char_len(str)
fn usz ZString.len(self)
{
usz len = 0;
char* ptr = (char*)self;
while (char c = ptr++[0]) len++;
usz len;
for (char* ptr = (char*)self; *ptr; ptr++) len++;
return len;
}
fn usz WString.len(self)
{
usz len;
for (Char16* ptr = (Char16*)self; *ptr; ptr++) len++;
return len;
}
fn ZString String.zstr_copy(self, Allocator allocator)
{
@@ -756,6 +777,92 @@ fn String String.to_upper_copy(self, Allocator allocator)
return copy;
}
fn String String.capitalize_copy(self, Allocator allocator)
{
String s = self.copy(allocator);
if (s.len > 0 && s[0].is_lower())
{
s[0] &= (char)~0x20;
}
return s;
}
<*
Convert a string from `snake_case` to PascalCase.
@param [in] self
@return `"FooBar" from "foo_bar" the resulting pointer may safely be cast to ZString.`
*>
fn String String.snake_to_pascal_copy(self, Allocator allocator)
{
Splitter splitter = self.tokenize("_");
char[] new_string = allocator::alloc_array(allocator, char, self.len + 1);
usz index = 0;
while (try s = splitter.next())
{
assert(s.len > 0);
char c = s[0];
if (c.is_lower()) c = c.to_upper();
new_string[index++] = c;
s = s[1..];
new_string[index:s.len] = s[..];
index += s.len;
}
new_string[index] = 0;
return (String)new_string[:index];
}
<*
Movifies the current string from `snake_case` to PascalCase.
@param [inout] self
*>
fn void String.convert_snake_to_pascal(&self)
{
Splitter splitter = self.tokenize("_");
String new_string = *self;
usz index = 0;
while (try s = splitter.next())
{
assert(s.len > 0);
char c = s[0];
if (c.is_lower()) c = c.to_upper();
new_string[index++] = c;
s = s[1..];
new_string[index:s.len] = s[..];
index += s.len;
}
*self = new_string[:index];
}
<*
Convert a string from `PascalCase` to `snake_case`.
@param [in] self
@return `"foo_bar" from "FooBar" the resulting pointer may safely be cast to ZString.`
*>
fn String String.pascal_to_snake_copy(self, Allocator allocator) => @pool()
{
DString d;
d.init(tmem, (usz)(self.len * 1.5));
usz index = 0;
foreach (i, c : self)
{
if (c.is_upper())
{
if (i > 0 && ((self[i - 1].is_lower() || self[i - 1].is_digit()) || (i < self.len - 1 && self[i + 1].is_lower())))
{
d.append_char('_');
}
d.append_char(c.to_lower());
continue;
}
d.append_char(c);
}
return d.copy_str(allocator);
}
fn StringIterator String.iterator(self)
{
return { self, 0 };

View File

@@ -0,0 +1,233 @@
// Copyright (c) 2024 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
This module provides functionality for escaping and unescaping strings
with standard C-style escape sequences, similar to what's used in JSON
and other string literals.
*>
module std::core::string;
import std::io;
faultdef INVALID_ESCAPE_SEQUENCE, UNTERMINATED_STRING, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE;
<*
Escape a string by adding quotes and converting special characters to escape sequences.
@param allocator : "The allocator to use for the result"
@param s : "The string to escape"
@param strip_quotes : "Do not include beginning and end quotes, defaults to false"
@return "The escaped string with surrounding quotes, can safely be cast to ZString"
*>
fn String String.escape(String s, Allocator allocator, bool strip_quotes = true)
{
// Conservative allocation: most strings need minimal escaping
usz initial_capacity = s.len + s.len / 5 + 2; // ~1.2x + quotes
DString result = dstring::new_with_capacity(allocator, initial_capacity);
if (!strip_quotes) result.append_char('"');
foreach (char c : s)
{
switch (c)
{
case '"': result.append(`\"`);
case '\\': result.append(`\\`);
case '\b': result.append(`\b`);
case '\f': result.append(`\f`);
case '\n': result.append(`\n`);
case '\r': result.append(`\r`);
case '\t': result.append(`\t`);
case '\v': result.append(`\v`);
case '\0': result.append(`\0`);
default:
if (c >= 32 && c <= 126)
{
// Printable ASCII
result.append_char(c);
}
else
{
// Non-printable, use hex escape
result.appendf("\\x%02x", (uint)c);
}
}
}
if (!strip_quotes) result.append_char('"');
return result.copy_str(allocator);
}
<*
Escape a string using the temp allocator.
@param s : "The string to escape"
@param strip_quotes : "Do not include beginning and end quotes, defaults to false"
@return "The escaped string with surrounding quotes"
*>
fn String String.tescape(String s, bool strip_quotes = false) => s.escape(tmem, strip_quotes);
<*
Calculate the length needed for an escaped string (including quotes).
@param s : "The string to check"
@return "The length needed for the escaped version"
*>
fn usz escape_len(String s)
{
usz len = 2; // For quotes
foreach (char c : s)
{
switch (c)
{
case '"':
case '\\':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
case '\v':
case '\0':
len += 2; // \X
default:
if (c >= 32 && c <= 126)
{
len += 1;
}
else
{
len += 4; // \xHH
}
}
}
return len;
}
<*
Unescape a quoted string by parsing escape sequences.
@param allocator : "The allocator to use for the result"
@param s : "The quoted string to unescape"
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
@return "The unescaped string without quotes, safe to convert to ZString"
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
*>
fn String? String.unescape(String s, Allocator allocator, bool allow_unquoted = false)
{
if (s.len >= 2 && s[0] == '"' && s[^1] == '"')
{
// Remove quotes.
s = s[1:^2];
}
else if (!allow_unquoted) return UNTERMINATED_STRING?;
// Handle empty string case
if (!s.len)
{
return "".copy(allocator);
}
DString result = dstring::new_with_capacity(allocator, s.len);
usz len = s.len;
for (usz i = 0; i < len; i++)
{
char c = s[i];
if (c != '\\')
{
result.append_char(c);
continue;
}
// Handle escape sequence
if (i + 1 >= len) return INVALID_ESCAPE_SEQUENCE?;
char escape_char = s[++i];
switch (escape_char)
{
case '"': result.append_char('"');
case '\\': result.append_char('\\');
case '/': result.append_char('/');
case 'b': result.append_char('\b');
case 'f': result.append_char('\f');
case 'n': result.append_char('\n');
case 'r': result.append_char('\r');
case 't': result.append_char('\t');
case 'v': result.append_char('\v');
case '0': result.append_char('\0');
case 'x':
// Hex escape \xHH
if (i + 2 >= len) return INVALID_HEX_ESCAPE?;
char h1 = s[++i];
char h2 = s[++i];
if (!h1.is_xdigit() || !h2.is_xdigit()) return INVALID_HEX_ESCAPE?;
uint val = h1 > '9' ? (h1 | 32) - 'a' + 10 : h1 - '0';
val = val << 4;
val += h2 > '9' ? (h2 | 32) - 'a' + 10 : h2 - '0';
result.append_char((char)val);
case 'u':
// Unicode escape \uHHHH
if (i + 4 >= len) return INVALID_UNICODE_ESCAPE?;
uint val;
for (int j = 0; j < 4; j++)
{
char hex_char = s[++i];
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE?;
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
}
result.append_char32(val);
case 'U':
// Unicode escape \UHHHHHHHH
if (i + 8 >= len) return INVALID_UNICODE_ESCAPE?;
uint val;
for (int j = 0; j < 8; j++)
{
char hex_char = s[++i];
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE?;
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
}
result.append_char32(val);
default:
return INVALID_ESCAPE_SEQUENCE?;
}
}
return result.copy_str(allocator);
}
<*
Unescape a quoted string using the temp allocator.
@param s : "The quoted string to unescape"
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
@return "The unescaped string without quotes"
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
*>
fn String? String.tunescape(String s, bool allow_unquoted = false) => s.unescape(tmem, allow_unquoted);
<*
Check if a character needs to be escaped in a string literal.
@param c : "The character to check"
@return "True if the character needs escaping"
*>
fn bool needs_escape(char c)
{
switch (c)
{
case '"':
case '\\':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
case '\v':
case '\0':
return true;
default:
return c < 32 || c > 126;
}
}

View File

@@ -462,7 +462,7 @@ macro String.to_real(chars, $Type) @private
$error "Unexpected type";
$endswitch
while (chars.len && chars[0] == ' ') chars = chars[1..];
chars = chars.trim();
if (!chars.len) return MALFORMED_FLOAT?;
if (chars.len != 1)
@@ -476,6 +476,9 @@ macro String.to_real(chars, $Type) @private
chars = chars[1..];
}
}
chars = chars.trim();
if (!chars.len) return MALFORMED_FLOAT?;
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
if (chars == "NAN" || chars == "nan") return $Type.nan;

View File

@@ -339,6 +339,8 @@ macro bool is_same_vector_type($Type1, $Type2) @const
$endif
}
macro bool has_equals($Type) @const => $defined(($Type){} == ($Type){});
macro bool is_equatable_type($Type) @const
{
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):

View File

@@ -2,9 +2,6 @@ module std::core::values;
import std::core::types;
macro typeid @typeid(#value) @const @builtin => $typeof(#value).typeid;
macro TypeKind @typekind(#value) @const @builtin => $typeof(#value).kindof;
macro bool @typeis(#value, $Type) @const @builtin => $typeof(#value).typeid == $Type.typeid;
<*
Return true if two values have the same type before any conversions.
*>
@@ -18,8 +15,13 @@ macro bool @is_promotable_to_floatlike(#value) @const => types::is_promotable_to
macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_float($typeof(#value));
macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2));
macro bool @assign_to(#value1, #value2) @const => @assignable_to(#value1, $typeof(#value2));
macro bool @is_lvalue(#value) => $defined(#value = #value);
macro bool @is_const(#foo) @const @builtin
{
var $v;
return $defined($v = #foo);
}
macro promote_int(x)
{

736
lib/std/crypto/ed25519.c3 Normal file
View File

@@ -0,0 +1,736 @@
/*
Ed25519 Digital Signature Algorithm
*/
module std::crypto::ed25519;
import std::hash::sha512;
alias Ed25519PrivateKey = char[32];
alias Ed25519PublicKey = char[Ed25519PrivateKey.len];
alias Ed25519Signature = char[2 * Ed25519PublicKey.len];
<*
Generate a public key from a private key.
@param [in] private_key : "32 bytes of cryptographically secure random data"
@require private_key.len == Ed25519PrivateKey.len
*>
fn Ed25519PublicKey public_keygen(char[] private_key)
{
return pack(&&unproject(&&(BASE * expand_private_key(private_key)[:FBaseInt.len])));
}
<*
Sign a message.
@param [in] message
@param [in] private_key
@param [in] public_key
@require private_key.len == Ed25519PrivateKey.len
@require public_key.len == Ed25519PublicKey.len
*>
fn Ed25519Signature sign(char[] message, char[] private_key, char[] public_key)
{
Ed25519Signature r @noinit;
char[*] exp = expand_private_key(private_key);
Sha512 sha @noinit;
sha.init();
sha.update(exp[FBaseInt.len..]);
sha.update(message);
FBaseInt k = from_bytes(&&sha.final());
r[:F25519Int.len] = pack(&&unproject(&&(BASE * k[..])))[..];
sha.init();
sha.update(r[:F25519Int.len]);
sha.update(public_key);
sha.update(message);
FBaseInt z = from_bytes(&&sha.final());
FBaseInt e = from_bytes(exp[:FBaseInt.len]);
r[F25519Int.len..] = (z * e + k)[..];
return r;
}
<*
Verify the signature of a message.
@param [in] message
@param [in] signature
@param [in] public_key
@require signature.len == Ed25519Signature.len
@require public_key.len == Ed25519PublicKey.len
*>
fn bool verify(char[] message, char[] signature, char[] public_key)
{
char ok = 1;
F25519Int lhs = pack(&&unproject(&&(BASE * signature[F25519Int.len..])));
Unpacking unp_p = unpack_on_curve((F25519Int*)public_key);
Projection p = project(&unp_p.point);
ok &= unp_p.on_curve;
Sha512 sha @noinit;
sha.init();
sha.update(signature[:F25519Int.len]);
sha.update(public_key);
sha.update(message);
FBaseInt z = from_bytes(&&sha.final());
p = p * z[..];
Unpacking unp_q = unpack_on_curve((F25519Int*)signature[:F25519Int.len]);
Projection q = project(&unp_q.point);
ok &= unp_q.on_curve;
p = p + q;
F25519Int rhs = pack(&&unproject(&p));
return (bool)(ok & eq(&lhs, &rhs));
}
// Base point for Ed25519. Generate a subgroup of order 2^252+0x14def9dea2f79cd65812631a5cf5d3ed
const Projection BASE @private =
{
x"1ad5258f602d56c9 b2a7259560c72c69 5cdcd6fd31e2a4c0 fe536ecdd3366921",
x"5866666666666666 6666666666666666 6666666666666666 6666666666666666",
x"a3ddb7a5b38ade6d f5525177809ff020 7de3ab648e4eea66 65768bd70f5f8767",
ONE
};
<*
Compute the pruned SHA-512 hash of a private key.
@param [in] private_key
@require private_key.len == Ed25519PrivateKey.len
*>
fn char[sha512::HASH_SIZE] expand_private_key(char[] private_key) @local
{
char[*] r = sha512::hash(private_key);
r[0] &= 0b11111000;
r[FBaseInt.len - 1] &= 0b01111111;
r[FBaseInt.len - 1] |= 0b01000000;
return r;
}
/*
Operations on the twisted Edwards curve -x^2+y^2=1-121665/121666*x^2*y^2 over the prime field F_(2^255-19) (edwards25519)
The set of F_(2^255-19)-rational curve points is a group of order 2^3*(2^252+0x14def9dea2f79cd65812631a5cf5d3ed)
*/
module std::crypto::ed25519 @private;
// Affine coordinates.
struct Point
{
F25519Int x;
F25519Int y;
}
// Projective coordinates.
struct Projection
{
F25519Int x;
F25519Int y;
F25519Int t;
F25519Int z;
}
// Neutral.
const Projection NEUTRAL =
{
ZERO,
ONE,
ZERO,
ONE
};
<*
Convert affine to projective coordinates.
@param [&in] p
*>
fn Projection project(Point* p) => { p.x, p.y, p.x * p.y, ONE };
<*
Convert projective to affine coordinates.
@param [&in] p
*>
fn Point unproject(Projection* p)
{
Point r @noinit;
F25519Int inv = p.z.inv();
r.x = p.x * inv;
r.y = p.y * inv;
r.x.normalize();
r.y.normalize();
return r;
}
// d parameter for edwards25519 : -121665/121666
const F25519Int D = x"a3785913ca4deb75 abd841414d0a7000 98e879777940c78c 73fe6f2bee6c0352";
// 2*d
const F25519Int DD = x"59f1b226949bd6eb 56b183829a14e000 30d1f3eef2808e19 e7fcdf56dcd90624";
<*
Compress a point.
@param [&in] p
*>
fn F25519Int pack(Point* p)
{
Point r = *p;
r.x.normalize();
r.y.normalize();
r.y[^1] |= (r.x[0] & 1) << 7;
return r.y;
}
struct Unpacking
{
Point point;
char on_curve; // Non-zero if true.
}
<*
Uncompress a point. Check if it is on the curve.
@param [&in] encoding
*>
fn Unpacking unpack_on_curve(F25519Int* encoding)
{
Point p @noinit;
char parity = (*encoding)[^1] >> 7;
p.y = *encoding;
p.y[^1] &= 0b01111111;
F25519Int y2 = p.y * p.y;
F25519Int x2 = (D * y2 + ONE).inv() * (y2 - ONE);
F25519Int x = x2.sqrt();
p.x = f25519_select(&x, &&-x, (x[0] ^ parity) & 1);
F25519Int _x2 = p.x * p.x;
x2.normalize();
_x2.normalize();
return {p, eq(&x2, &_x2)};
}
macro Projection Projection.@add(&s, Projection #p) @operator(+) => s.add(@addr(#p));
<*
Addition.
@param [&in] s
*>
fn Projection Projection.add(&s, Projection* p) @operator(+)
{
Projection r @noinit;
F25519Int a = (s.y - s.x) * (p.y - p.x);
F25519Int b = (s.y + s.x) * (p.y + p.x);
F25519Int c = s.t * DD * p.t;
F25519Int d = (s.z * p.z).mul_s(2);
F25519Int e = b - a;
F25519Int f = d - c;
F25519Int g = d + c;
F25519Int h = b + a;
r.x = e * f;
r.y = g * h;
r.t = e * h;
r.z = f * g;
return r;
}
<*
Double a point.
@param [&in] s
*>
fn Projection Projection.twice(&s)
{
Projection r @noinit;
F25519Int a = s.x * s.x;
F25519Int b = s.y * s.y;
F25519Int c = (s.z * s.z).mul_s(2);
F25519Int d = s.x + s.y;
F25519Int e = d * d - a - b;
F25519Int g = b - a;
F25519Int f = g - c;
F25519Int h = -b - a;
r.x = e * f;
r.y = g * h;
r.t = e * h;
r.z = f * g;
return r;
}
<*
Variable base scalar multiplication.
@param [&in] s
@param [in] n
*>
fn Projection Projection.mul(&s, char[] n) @operator(*)
{
Projection r = NEUTRAL;
for (isz i = n.len << 3 - 1; i >= 0; i--)
{
r = r.twice();
Projection t = r + s;
char bit = n[i >> 3] >> (i & 7) & 1;
r.x = f25519_select(&r.x, &t.x, bit);
r.y = f25519_select(&r.y, &t.y, bit);
r.z = f25519_select(&r.z, &t.z, bit);
r.t = f25519_select(&r.t, &t.t, bit);
}
return r;
}
/*
Modular arithmetic over the prime field F_(2^255-19)
*/
module std::crypto::ed25519 @private;
typedef F25519Int = inline char[32];
const F25519Int ZERO = {};
const F25519Int ONE = {[0] = 1};
<*
Reduce an element with carry to at most 2^255+18 (32 bytes)
@param [&inout] s
*>
fn void F25519Int.reduce_carry(&s, uint carry)
{
// Reduce using 2^255 = 19 mod p
(*s)[^1] &= 0b01111111;
carry *= 19;
foreach (i, &v : s)
{
carry += *v;
*v = (char)carry;
carry >>= 8;
}
}
<*
Reduce an element to at most 2^255-19
@param [&inout] s
*>
fn void F25519Int.normalize(&s)
{
s.reduce_carry((*s)[^1] >> 7);
// Substract p
F25519Int sub @noinit;
ushort c = 19;
foreach (i, v : (*s)[:^1])
{
c += v;
sub[i] = (char)c;
c >>= 8;
}
c += (*s)[^1] - 0b10000000;
sub[^1] = (char)c;
*s = f25519_select(&sub, s, (char)(c >> 15));
}
<*
Constant-time equality comparison. Return is non-zero if true.
@param [&in] a
@param [&in] b
*>
fn char eq(F25519Int* a, F25519Int* b)
{
char e;
foreach (i, v : a) e |= v ^ (*b)[i];
e |= (e >> 4);
e |= (e >> 2);
e |= (e >> 1);
return e ^ 1;
}
<*
Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
@param [&in] zero : "selected if condition is 0"
@param [&in] one : "selected if condition is 1"
*>
fn F25519Int f25519_select(F25519Int* zero, F25519Int* one, char condition)
{
F25519Int r @noinit;
foreach (i, z : zero) r[i] = z ^ (-condition & ((*one)[i] ^ z));
return r;
}
macro F25519Int F25519Int.@add(&s, F25519Int #n) @operator(+) => s.add(@addr(#n));
<*
Addition.
@param [&in] s
@param [&in] n
*>
fn F25519Int F25519Int.add(&s, F25519Int* n) @operator(+)
{
F25519Int r @noinit;
ushort c;
foreach (i, v : s)
{
c >>= 8;
c += v + (*n)[i];
r[i] = (char)c;
}
r.reduce_carry(c >> 7);
return r;
}
macro F25519Int F25519Int.@sub(&s, F25519Int #n) @operator(-) => s.sub(@addr(#n));
<*
Substraction.
@param [&in] s
@param [&in] n
*>
fn F25519Int F25519Int.sub(&s, F25519Int* n) @operator(-)
{
// Compute s+2*p-n instead of s-n to avoid underflow.
F25519Int r @noinit;
uint c = (char)~(2 * 19 - 1);
foreach (i, v : (*s)[:^1])
{
c += 0b11111111_00000000 + v - (*n)[i];
r[i] = (char)c;
c >>= 8;
}
c += (*s)[^1] - (*n)[^1];
r[^1] = (char)c;
r.reduce_carry(c >> 7);
return r;
}
<*
Negation.
@param [&in] s
*>
fn F25519Int F25519Int.neg(&s) @operator(-)
{
// Compute 2*p-s instead of -s to avoid underflow.
F25519Int r @noinit;
uint c = (char)~(2 * 19 - 1);
foreach (i, v : (*s)[:^1])
{
c += 0b11111111_00000000 - v;
r[i] = (char)c;
c >>= 8;
}
c -= (*s)[^1];
r[^1] = (char)c;
r.reduce_carry(c >> 7);
return r;
}
macro F25519Int F25519Int.@mul(&s, F25519Int #n) @operator(*) => s.mul(@addr(#n));
<*
Multiplication.
@param [&in] s
@param [&in] n
*>
fn F25519Int F25519Int.mul(&s, F25519Int* n) @operator(*)
{
F25519Int r @noinit;
uint c;
for (usz i = 0; i < F25519Int.len; i++)
{
c >>= 8;
for (usz j; j <= i; j++) c += (*s)[j] * (*n)[i - j];
// Reduce using 2^256 = 2*19 mod p
for (usz j = i + 1; j < F25519Int.len; j++) c += (*s)[j] * (*n)[^j - i] * 2 * 19;
r[i] = (char)c;
}
r.reduce_carry(c >> 7);
return r;
}
<*
Multiplication by a small element.
@param [&in] s
*>
fn F25519Int F25519Int.mul_s(&s, uint n)
{
F25519Int r @noinit;
uint c;
foreach (i, v : s)
{
c >>= 8;
c += v * n;
r[i] = (char)c;
}
r.reduce_carry(c >> 7);
return r;
}
<*
Inverse an element.
@param [&in] s
*>
fn F25519Int F25519Int.inv(&s)
{
//Compute s^(p-2)
F25519Int r = *s;
for (usz i; i < 255 - 1 - 5; i++) r = r * r * s;
r *= r;
r = r * r * s;
r *= r;
r = r * r * s;
r = r * r * s;
return r;
}
<*
Raise an element to the power of 2^252-3
@param [&in] s
*>
fn F25519Int F25519Int.pow_2523(&s) @local
{
F25519Int r = *s;
for (usz i; i < 252 - 1 - 2; i++) r = r * r * s;
r *= r;
r = r * r * s;
return r;
}
<*
Compute the square root of an element.
@param [&in] s
*>
fn F25519Int F25519Int.sqrt(&s)
{
F25519Int twice = s.mul_s(2);
F25519Int pow = twice.pow_2523();
return (twice * pow * pow - ONE) * s * pow;
}
/*
Modular arithmetic over the prime field F_(2^252+0x14def9dea2f79cd65812631a5cf5d3ed)
*/
module std::crypto::ed25519 @private;
import std::math;
typedef FBaseInt = inline char[32];
// Order of the field : 2^252+0x14def9dea2f79cd65812631a5cf5d3ed
const FBaseInt ORDER = x"edd3f55c1a631258 d69cf7a2def9de14 0000000000000000 0000000000000010";
<*
Interpret bytes as a normalized element.
@param [in] bytes
*>
fn FBaseInt from_bytes(char[] bytes)
{
FBaseInt r;
usz bitc = min(252 - 1, bytes.len << 3);
usz bytec = bitc >> 3;
usz mod = bitc & 7;
usz rem = bytes.len << 3 - bitc;
r[:bytec] = bytes[^bytec..];
if (mod)
{
r <<= mod;
r[0] |= bytes[^bytec + 1] >> (8 - mod);
}
for (isz i = rem - 1; i >= 0; i--)
{
r <<= 1;
r[0] |= bytes[i >> 3] >> (i & 7) & 1;
r = r.sub_l(&ORDER);
}
return r;
}
<*
Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
@param [&in] zero : "selected if condition is 0"
@param [&in] one : "selected if condition is 1"
*>
fn FBaseInt fbase_select(FBaseInt* zero, FBaseInt* one, char condition)
{
FBaseInt r @noinit;
foreach (i, z : zero) r[i] = z ^ (-condition & ((*one)[i] ^ z));
return r;
}
macro FBaseInt FBaseInt.@add(&s, FBaseInt #n) @operator(+) => s.add(@addr(#n));
<*
Addition.
@param [&in] s
@param [&in] n
*>
fn FBaseInt FBaseInt.add(&s, FBaseInt* n) @operator(+)
{
FBaseInt r @noinit;
ushort c;
foreach (i, v : s)
{
c += v + (*n)[i];
r[i] = (char)c;
c >>= 8;
}
return r.sub_l(&ORDER);
}
<*
Substraction if RHS is less than LHS else identity.
@param [&in] s
@param [&in] n
*>
fn FBaseInt FBaseInt.sub_l(&s, FBaseInt* n)
{
FBaseInt sub @noinit;
ushort c;
foreach (i, v : s)
{
c = v - (*n)[i] - c;
sub[i] = (char)c;
c = (c >> 8) & 1;
}
return fbase_select(&sub, s, (char)c);
}
<*
Left shift.
@param [&in] s
*>
fn FBaseInt FBaseInt.shl(&s, usz n) @operator(<<)
{
FBaseInt r @noinit;
ushort c;
foreach (i, v : s)
{
c |= v << n;
r[i] = (char)c;
c >>= 8;
}
return r;
}
macro FBaseInt FBaseInt.@mul(&s, FBaseInt #n) @operator(*) => s.mul(@addr(#n));
<*
Multiplication.
@param [&in] s
@param [&in] n
*>
fn FBaseInt FBaseInt.mul(&s, FBaseInt* n) @operator(*)
{
FBaseInt r;
for (isz i = 252; i >= 0; i--)
{
r = (r << 1).sub_l(&ORDER);
r = fbase_select(&r, &&(r + s), (*n)[i >> 3] >> (i & 7) & 1);
}
return r;
}

View File

@@ -5,7 +5,9 @@ module std::encoding::json;
import std::io;
import std::collections::object;
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, DUPLICATE_MEMBERS, INVALID_NUMBER;
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, INVALID_NUMBER, MAX_DEPTH_REACHED;
int max_depth = 128;
fn Object*? parse_string(Allocator allocator, String s)
{
@@ -24,7 +26,15 @@ fn Object*? parse(Allocator allocator, InStream s)
JsonContext context = { .last_string = dstring::new_with_capacity(smem, 64), .stream = s, .allocator = allocator };
@pool()
{
return parse_any(&context);
Object* o = parse_any(&context)!;
defer catch o.free();
while (char c = read_next(&context)!, c != 0)
{
if (c.is_space()) continue;
return UNEXPECTED_CHARACTER?;
}
if (!@catch(context.stream.read_byte())) return UNEXPECTED_CHARACTER?;
return o;
};
};
}
@@ -62,11 +72,14 @@ struct JsonContext @local
DString last_string;
double last_number;
char current;
bitstruct : char {
int depth;
bitstruct : char
{
bool skip_comments;
bool reached_end;
bool pushed_back;
}
}
@@ -105,10 +118,16 @@ fn JsonTokenType? lex_number(JsonContext *context, char c) @local
t.append(c);
c = read_next(context)!;
}
bool leading_zero = c == '0';
while (c.is_digit())
{
t.append(c);
c = read_next(context)!;
if (leading_zero)
{
if (c.is_digit()) return INVALID_NUMBER?;
leading_zero = false;
}
}
if (c == '.')
{
@@ -148,6 +167,8 @@ fn Object*? parse_map(JsonContext* context) @local
Object* map = object::new_obj(context.allocator);
defer catch map.free();
JsonTokenType token = advance(context)!;
defer context.depth--;
if (++context.depth >= max_depth) return json::MAX_DEPTH_REACHED?;
@stack_mem(256; Allocator mem)
{
@@ -156,7 +177,6 @@ fn Object*? parse_map(JsonContext* context) @local
{
if (token != JsonTokenType.STRING) return UNEXPECTED_CHARACTER?;
DString string = context.last_string;
if (map.has_key(string.str_view())) return DUPLICATE_MEMBERS?;
// Copy the key to our temp holder, since our
// last_string may be used in parse_any
temp_key.clear();
@@ -180,6 +200,8 @@ fn Object*? parse_array(JsonContext* context) @local
{
Object* list = object::new_obj(context.allocator);
defer catch list.free();
defer context.depth--;
if (++context.depth >= max_depth) return json::MAX_DEPTH_REACHED?;
JsonTokenType token = advance(context)!;
while (token != JsonTokenType.RBRACKET)
{
@@ -248,7 +270,7 @@ fn JsonTokenType? advance(JsonContext* context) @local
case '\v':
continue;
case '/':
if (!context.skip_comments) break;
if (!context.skip_comments) break WS;
c = read_next(context)!;
if (c != '*')
{

96
lib/std/hash/a5hash.c3 Normal file
View File

@@ -0,0 +1,96 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// An implementation of Aleksey Vaneev's a5hash, version 5.16, in C3:
// https://github.com/avaneev/komihash
//
// The license for komihash from the above repository at the time of writing is as follows:
//
// >> MIT License
// >>
// >> Copyright (c) 2025 Aleksey Vaneev
// >>
// >> Permission is hereby granted, free of charge, to any person obtaining a copy
// >> of this software and associated documentation files (the "Software"), to deal
// >> in the Software without restriction, including without limitation the rights
// >> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// >> copies of the Software, and to permit persons to whom the Software is
// >> furnished to do so, subject to the following conditions:
// >>
// >> The above copyright notice and this permission notice shall be included in all
// >> copies or substantial portions of the Software.
// >>
// >> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// >> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// >> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// >> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// >> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// >> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// >> SOFTWARE.
//
//
module std::hash::a5hash;
macro @a5mul(#u, #v, #lo, #hi) @local
{
uint128 imd = (uint128)#u * (uint128)#v;
#lo = (ulong)imd;
#hi = (ulong)(imd >> 64);
}
fn ulong hash(char[] data, ulong seed = 0)
{
ulong seed1 = 0x243F_6A88_85A3_08D3 ^ data.len;
ulong seed2 = 0x4528_21E6_38D0_1377 ^ data.len;
ulong val10 = 0xAAAA_AAAA_AAAA_AAAA;
ulong val01 = 0x5555_5555_5555_5555;
ulong a, b;
@a5mul(seed2 ^ (seed & val10), seed1 ^ (seed & val01), seed1, seed2);
val10 ^= seed2;
if (@likely(data.len > 3))
{
if (data.len > 16)
{
val01 ^= seed1;
for (; data.len > 16; data = data[16..])
{
@a5mul(
@unaligned_load(((ulong*)data.ptr)[0], 1) ^ seed1,
@unaligned_load(((ulong*)data.ptr)[1], 1) ^ seed2,
seed1, seed2
);
seed1 += val01;
seed2 += val10;
}
a = @unaligned_load(*(ulong*)(data.ptr + (uptr)data.len - 16), 1);
b = @unaligned_load(*(ulong*)(data.ptr + (uptr)data.len - 8), 1);
}
else
{
a = ((ulong)@unaligned_load(*(uint*)&data[0], 1) << 32)
| @unaligned_load(*(uint*)&data[^4], 1);
b = ((ulong)@unaligned_load(*(uint*)&data[(data.len >> 3) * 4], 1) << 32)
| @unaligned_load(*(uint*)(data.ptr + data.len - 4 - (data.len >> 3) * 4), 1);
}
}
else
{
a = data.len ? (data[0] | (data.len > 1 ? ((ulong)data[1] << 8) : 0) | (data.len > 2 ? ((ulong)data[2] << 16) : 0)) : 0;
b = 0;
}
@a5mul(a ^ seed1, b ^ seed2, seed1, seed2);
@a5mul(val01 ^ seed1, seed2, a, b);
return a ^ b;
}

156
lib/std/hash/komi.c3 Normal file
View File

@@ -0,0 +1,156 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// An implementation of Aleksey Vaneev's komihash, version 5.27, in C3:
// https://github.com/avaneev/komihash
//
// The license for komihash from the above repository at the time of writing is as follows:
//
// >> MIT License
// >>
// >> Copyright (c) 2021-2025 Aleksey Vaneev
// >>
// >> Permission is hereby granted, free of charge, to any person obtaining a copy
// >> of this software and associated documentation files (the "Software"), to deal
// >> in the Software without restriction, including without limitation the rights
// >> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// >> copies of the Software, and to permit persons to whom the Software is
// >> furnished to do so, subject to the following conditions:
// >>
// >> The above copyright notice and this permission notice shall be included in all
// >> copies or substantial portions of the Software.
// >>
// >> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// >> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// >> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// >> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// >> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// >> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// >> SOFTWARE.
//
//
module std::hash::komi;
macro @komimul(#u, #v, #lo, #hi) @local
{
uint128 imd = (uint128)#u * (uint128)#v;
#lo = (ulong)imd;
#hi += (ulong)(imd >> 64);
}
fn ulong hash(char[] data, ulong seed = 0)
{
ulong seed1 = 0x243F_6A88_85A3_08D3 ^ (seed & 0x5555_5555_5555_5555);
ulong seed5 = 0x4528_21E6_38D0_1377 ^ (seed & 0xAAAA_AAAA_AAAA_AAAA);
ulong r1h, r2h;
// HASHROUND
@komimul(seed1, seed5, seed1, seed5);
seed1 ^= seed5;
if (@likely(data.len < 16))
{
r1h = seed1;
r2h = seed5;
if (@likely(data.len >= 8))
{
r1h ^= @unaligned_load(*(ulong*)data.ptr, 1);
r2h ^= (data.len < 12)
? ((data[data.len - 3] | ((ulong)data[data.len - 2] << 8) | ((ulong)data[data.len - 1] << 16) | ((ulong)1 << 24)) >> ((data.len * 8) ^ 88))
: (((@unaligned_load(*(uint*)&data[^4], 1) | ((ulong)1 << 32)) >> (128 - data.len * 8)) << 32 | @unaligned_load(*(uint*)&data[8], 1));
}
else if (data.len != 0)
{
r1h ^= (data.len < 4)
? (((ulong)1 << (data.len * 8)) ^ data[0] ^ (data.len > 1 ? (ulong)data[1] << 8 : 0) ^ (data.len > 2 ? (ulong)data[2] << 16 : 0))
: (((@unaligned_load(*(uint*)&data[^4], 1) | ((ulong)1 << 32)) >> (64 - data.len * 8)) << 32 | @unaligned_load(*(uint*)&data[0], 1));
}
}
else if (data.len < 32)
{
// HASH16
@komimul(
@unaligned_load(*(ulong*)&data[0], 1) ^ seed1,
@unaligned_load(*(ulong*)&data[8], 1) ^ seed5,
seed1, seed5
);
seed1 ^= seed5;
if (data.len < 24)
{
r1h = (((@unaligned_load(*(ulong*)&data[^8], 1) >> 8) | ((ulong)1 << 56)) >> (((int)(data.len * 8) ^ 184))) ^ seed1;
r2h = seed5;
}
else
{
r1h = @unaligned_load(*(ulong*)&data[16], 1) ^ seed1;
r2h = (((@unaligned_load(*(ulong*)&data[^8], 1) >> 8) | ((ulong)1 << 56)) >> (((int)(data.len * 8) ^ 248))) ^ seed5;
}
}
else
{
if (data.len >= 64)
{
ulong[8] seeds = {
seed1, 0x1319_8A2E_0370_7344 ^ seed1, 0xA409_3822_299F_31D0 ^ seed1, 0x082E_FA98_EC4E_6C89 ^ seed1,
seed5, 0xBE54_66CF_34E9_0C6C ^ seed5, 0xC0AC_29B7_C97C_50DD ^ seed5, 0x3F84_D5B5_B547_0917 ^ seed5,
};
// HASHLOOP64
for (; data.len >= 64; data = data[64:^64])
{
$for var $x = 0; $x < 4; ++$x :
@komimul(
@unaligned_load(*(ulong*)&data[0 + ($x * 8)], 1) ^ seeds[$x],
@unaligned_load(*(ulong*)&data[32 + ($x * 8)], 1) ^ seeds[4 + $x],
seeds[$x], seeds[4 + $x]
);
$endfor
seeds[3] ^= seeds[6];
seeds[0] ^= seeds[7];
seeds[2] ^= seeds[5];
seeds[1] ^= seeds[4];
}
seed1 = seeds[0] ^ seeds[1] ^ seeds[2] ^ seeds[3];
seed5 = seeds[4] ^ seeds[5] ^ seeds[6] ^ seeds[7];
}
for (; data.len >= 16; data = data[16:^16])
{
@komimul(
@unaligned_load(*(ulong*)&data[0], 1) ^ seed1,
@unaligned_load(*(ulong*)&data[8], 1) ^ seed5,
seed1, seed5
);
seed1 ^= seed5;
}
if (data.len < 8)
{
// NOTE: This is translated from the original code. It grabs the last ulong off the buffer even though the
// data slice is less than 8 bytes. This is possible because this branch only occurs in a loop where
// the original data slice length is >= 32.
r1h = (((@unaligned_load(*(ulong*)(data.ptr + data.len - 8), 1) >> 8) | ((ulong)1 << 56)) >> ((data.len * 8) ^ 0x38)) ^ seed1;
r2h = seed5;
}
else
{
r1h = @unaligned_load(*(ulong*)data.ptr, 1) ^ seed1;
r2h = (((@unaligned_load(*(ulong*)&data[^8], 1) >> 8) | ((ulong)1 << 56)) >> ((data.len * 8) ^ 0x78)) ^ seed5;
}
}
// HASHFIN
@komimul(r1h, r2h, seed1, seed5);
seed1 ^= seed5;
@komimul(seed1, seed5, seed1, seed5);
seed1 ^= seed5;
return seed1;
}

149
lib/std/hash/metro128.c3 Normal file
View File

@@ -0,0 +1,149 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// MetroHash64 and MetroHash128 are different enough to warrant their own
// modules, and there would be no reason to create a generic module just
// for the two. If you inspect the differences, the only shared portion
// of the entire process is the `update` method.
//
module std::hash::metro128;
const ulong[4] K @local = {
0xc83a91e1,
0x8648dbdb,
0x7bdec03b,
0x2f5870a5,
};
struct MetroHash128
{
union
{
ulong[4] state;
uint128 result;
}
union
{
ulong[4] stomach_64;
char[32] stomach;
}
ulong bytes;
}
fn uint128 hash(char[] data, ulong seed = 0)
{
MetroHash128 m;
m.init(seed);
m.update(data);
return m.final();
}
fn void MetroHash128.init(&self, ulong seed = 0)
{
self.state = {
(seed - K[0]) * K[3],
(seed + K[1]) * K[2],
(seed + K[0]) * K[2],
(seed - K[1]) * K[3],
};
}
fn void MetroHash128.update(&self, char[] data)
{
if (self.bytes % 32) // partial buffer
{
ulong to_fill = min(data.len, (32 - (self.bytes % 32)));
self.stomach[(self.bytes % 32):to_fill] = data[:to_fill];
data = data[to_fill..];
self.bytes += to_fill;
if (self.bytes % 32) return; // still awaiting more input, or final
self.state[0] += self.stomach_64[0] * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
self.state[1] += self.stomach_64[1] * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
self.state[2] += self.stomach_64[2] * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
self.state[3] += self.stomach_64[3] * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
}
self.bytes += data.len;
for (; data.len >= 32; data = data[32:^32])
{
self.state[0] += @unaligned_load(((ulong*)data.ptr)[0], 1) * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
self.state[1] += @unaligned_load(((ulong*)data.ptr)[1], 1) * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
self.state[2] += @unaligned_load(((ulong*)data.ptr)[2], 1) * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
self.state[3] += @unaligned_load(((ulong*)data.ptr)[3], 1) * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
}
// Gobble up the leftover bytes. Nom nom.
if (data.len > 0) self.stomach[:data.len] = data[..];
}
fn uint128 MetroHash128.final(&self)
{
if (self.bytes >= 32)
{
self.state[2] ^= (((self.state[0] + self.state[3]) * K[0]) + self.state[1]).rotr(21) * K[1];
self.state[3] ^= (((self.state[1] + self.state[2]) * K[1]) + self.state[0]).rotr(21) * K[0];
self.state[0] ^= (((self.state[0] + self.state[2]) * K[0]) + self.state[3]).rotr(21) * K[1];
self.state[1] ^= (((self.state[1] + self.state[3]) * K[1]) + self.state[2]).rotr(21) * K[0];
}
char[] final_data = self.stomach[:(self.bytes % 32)];
if (final_data.len >= 16)
{
self.state[0] += ((ulong*)final_data.ptr)[0] * K[2]; self.state[0] = self.state[0].rotr(33) * K[3];
self.state[1] += ((ulong*)final_data.ptr)[1] * K[2]; self.state[1] = self.state[1].rotr(33) * K[3];
self.state[0] ^= ((self.state[0] * K[2]) + self.state[1]).rotr(45) * K[1];
self.state[1] ^= ((self.state[1] * K[3]) + self.state[0]).rotr(45) * K[0];
final_data = final_data[16:^16];
}
if (final_data.len >= 8)
{
self.state[0] += @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[2]; self.state[0] = self.state[0].rotr(33) * K[3];
self.state[0] ^= ((self.state[0] * K[2]) + self.state[1]).rotr(27) * K[1];
final_data = final_data[8:^8];
}
if (final_data.len >= 4)
{
self.state[1] += @unaligned_load(((uint*)final_data.ptr)[0], 1) * K[2]; self.state[1] = self.state[1].rotr(33) * K[3];
self.state[1] ^= ((self.state[1] * K[3]) + self.state[0]).rotr(46) * K[0];
final_data = final_data[4:^4];
}
if (final_data.len >= 2)
{
self.state[0] += @unaligned_load(((ushort*)final_data.ptr)[0], 1) * K[2]; self.state[0] = self.state[0].rotr(33) * K[3];
self.state[0] ^= ((self.state[0] * K[2]) + self.state[1]).rotr(22) * K[1];
final_data = final_data[2:^2];
}
if (final_data.len >= 1)
{
self.state[1] += ((char*)final_data.ptr)[0] * K[2]; self.state[1] = self.state[1].rotr(33) * K[3];
self.state[1] ^= ((self.state[1] * K[3]) + self.state[0]).rotr(58) * K[0];
}
self.state[0] += ((self.state[0] * K[0]) + self.state[1]).rotr(13);
self.state[1] += ((self.state[1] * K[1]) + self.state[0]).rotr(37);
self.state[0] += ((self.state[0] * K[2]) + self.state[1]).rotr(13);
self.state[1] += ((self.state[1] * K[3]) + self.state[0]).rotr(37);
return self.result;
}

152
lib/std/hash/metro64.c3 Normal file
View File

@@ -0,0 +1,152 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// MetroHash64 and MetroHash128 are different enough to warrant their own
// modules, and there would be no reason to create a generic module just
// for the two. If you inspect the differences, the only shared portion
// of the entire process is the `update` method.
//
module std::hash::metro64;
const ulong[4] K @local = {
0xd6d018f5,
0xa2aa033b,
0x62992fc1,
0x30bc5b29,
};
struct MetroHash64
{
union
{
ulong[4] state;
ulong result;
}
union
{
ulong[4] stomach_64;
char[32] stomach;
}
ulong bytes;
ulong vseed;
}
fn ulong hash(char[] data, ulong seed = 0)
{
MetroHash64 m;
m.init(seed);
m.update(data);
return m.final();
}
fn void MetroHash64.init(&self, ulong seed = 0)
{
self.vseed = (seed + K[2]) * K[0];
self.state[0] = self.vseed;
self.state[1] = self.vseed;
self.state[2] = self.vseed;
self.state[3] = self.vseed;
}
fn void MetroHash64.update(&self, char[] data)
{
if (self.bytes % 32) // partial buffer
{
ulong to_fill = min(data.len, (32 - (self.bytes % 32)));
self.stomach[(self.bytes % 32):to_fill] = data[:to_fill];
data = data[to_fill..];
self.bytes += to_fill;
if (self.bytes % 32) return; // still awaiting more input, or final
self.state[0] += self.stomach_64[0] * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
self.state[1] += self.stomach_64[1] * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
self.state[2] += self.stomach_64[2] * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
self.state[3] += self.stomach_64[3] * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
}
self.bytes += data.len;
for (; data.len >= 32; data = data[32:^32])
{
self.state[0] += @unaligned_load(((ulong*)data.ptr)[0], 1) * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
self.state[1] += @unaligned_load(((ulong*)data.ptr)[1], 1) * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
self.state[2] += @unaligned_load(((ulong*)data.ptr)[2], 1) * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
self.state[3] += @unaligned_load(((ulong*)data.ptr)[3], 1) * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
}
// Gobble up the leftover bytes. Nom nom.
if (data.len > 0) self.stomach[:data.len] = data[..];
}
fn ulong MetroHash64.final(&self)
{
if (self.bytes >= 32)
{
self.state[2] ^= (((self.state[0] + self.state[3]) * K[0]) + self.state[1]).rotr(37) * K[1];
self.state[3] ^= (((self.state[1] + self.state[2]) * K[1]) + self.state[0]).rotr(37) * K[0];
self.state[0] ^= (((self.state[0] + self.state[2]) * K[0]) + self.state[3]).rotr(37) * K[1];
self.state[1] ^= (((self.state[1] + self.state[3]) * K[1]) + self.state[2]).rotr(37) * K[0];
self.state[0] = self.vseed + (self.state[0] ^ self.state[1]);
}
char[] final_data = self.stomach[:(self.bytes % 32)];
if (final_data.len >= 16)
{
self.state[1] = self.state[0] + @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[2]; self.state[1] = self.state[1].rotr(29) * K[3];
self.state[2] = self.state[0] + @unaligned_load(((ulong*)final_data.ptr)[1], 1) * K[2]; self.state[2] = self.state[2].rotr(29) * K[3];
self.state[1] ^= (self.state[1] * K[0]).rotr(21) + self.state[2];
self.state[2] ^= (self.state[2] * K[3]).rotr(21) + self.state[1];
self.state[0] += self.state[2];
final_data = final_data[16:^16];
}
if (final_data.len >= 8)
{
self.state[0] += @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[3];
self.state[0] ^= self.state[0].rotr(55) * K[1];
final_data = final_data[8:^8];
}
if (final_data.len >= 4)
{
self.state[0] += @unaligned_load(((uint*)final_data.ptr)[0], 1) * K[3];
self.state[0] ^= self.state[0].rotr(26) * K[1];
final_data = final_data[4:^4];
}
if (final_data.len >= 2)
{
self.state[0] += @unaligned_load(((ushort*)final_data.ptr)[0], 1) * K[3];
self.state[0] ^= self.state[0].rotr(48) * K[1];
final_data = final_data[2:^2];
}
if (final_data.len >= 1)
{
self.state[0] += ((char*)final_data.ptr)[0] * K[3];
self.state[0] ^= self.state[0].rotr(37) * K[1];
}
self.state[0] ^= self.state[0].rotr(28);
self.state[0] *= K[0];
self.state[0] ^= self.state[0].rotr(29);
return self.result;
}

164
lib/std/hash/siphash.c3 Normal file
View File

@@ -0,0 +1,164 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// SipHash is a secure pseudorandom function (PRF) which digests a 128-bit key
// and a variable-length message to produce a 64- or 128-bit hash value.
//
// SipHash can be employed in numerous useful ways and structures, e.g.:
// - Hash Tables
// - Message Authentication Codes
// - Denial of Service (hash flooding) resistance
// - Bloom filters
// - Keyed runtime identifier derivation
//
// Read more: https://en.wikipedia.org/wiki/SipHash
//
//
// COMMON HASH VARIANTS.
// These two forms of SipHash (24 and 48) are the most widely
// used by many implementations.
// These provide typical 64-bit hash results.
// -- Best for performance-critical applications.
module std::hash::siphash24;
import std::hash::siphash;
alias SipHash24 = SipHash { ulong, 2, 4 };
alias hash = siphash::hash { ulong, 2, 4 };
// -- Best for conservative security applications.
module std::hash::siphash48;
import std::hash::siphash;
alias SipHash48 = SipHash { ulong, 4, 8 };
alias hash = siphash::hash { ulong, 4, 8 };
// Exact same as above, but for 128-bit outputs. Algorithm internally changes slightly.
module std::hash::siphash24_128;
import std::hash::siphash;
alias SipHash24_128 = SipHash { uint128, 2, 4 };
alias hash = siphash::hash { uint128, 2, 4 };
module std::hash::siphash48_128;
import std::hash::siphash;
alias SipHash48_128 = SipHash { uint128, 4, 8 };
alias hash = siphash::hash { uint128, 4, 8 };
<*
@require OutType.typeid == uint128.typeid || OutType.typeid == ulong.typeid : "Module OutType must be either uint128 or ulong."
*>
module std::hash::siphash { OutType, BLOCK_ROUNDS, FINALIZE_ROUNDS };
struct SipHash
{
ulong[4] v;
long m;
int m_idx;
usz len;
}
fn OutType hash(char[] data, uint128 key)
{
SipHash s;
s.init(key);
s.update(data);
return s.final();
}
fn void SipHash.init(&self, uint128 key)
{
ulong[2] key_64 = bitcast(key, ulong[2]);
self.v = {
0x736f_6d65_7073_6575 ^ key_64[0],
0x646f_7261_6e64_6f6d ^ key_64[1],
0x6c79_6765_6e65_7261 ^ key_64[0],
0x7465_6462_7974_6573 ^ key_64[1],
};
$if OutType.typeid == uint128.typeid :
self.v[1] ^= 0xEE;
$endif
}
<*
@param [in] data : "Bytes to hash"
*>
fn void SipHash.update(&self, char[] data)
{
self.len += data.len;
foreach (byte : data)
{
self.m |= (long)byte << (self.m_idx++ << 3);
if (self.m_idx < 8) continue;
// This runs every time the m_idx is 8, then it resets to 0.
self.v[3] ^= self.m;
$for var $i = 0; $i < BLOCK_ROUNDS; ++$i : // unrolled loop
self.round();
$endfor
self.v[0] ^= self.m;
self.m_idx = 0;
self.m = 0;
}
}
fn OutType SipHash.final(&self)
{
char[8] last = { [7] = (char)self.len };
self.update(last[(self.m_idx < 7 ? self.m_idx : 7)..]);
$if OutType.typeid == uint128.typeid :
self.v[2] ^= 0xEE;
$else
self.v[2] ^= 0xFF;
$endif
$for var $i = 0; $i < FINALIZE_ROUNDS; ++$i : // unrolled loop
self.round();
$endfor
$if OutType.typeid == ulong.typeid :
return self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3];
$else
ulong lo = self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3];
self.v[1] ^= 0xDD;
$for var $i = 0; $i < FINALIZE_ROUNDS; ++$i : // unrolled loop
self.round();
$endfor
return lo | ((uint128)(self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3]) << 64);
$endif
}
fn void SipHash.round(&self) @local
{
self.v[0] += self.v[1];
self.v[1] = self.v[1].rotl(13);
self.v[1] ^= self.v[0];
self.v[0] = self.v[0].rotl(32);
self.v[2] += self.v[3];
self.v[3] = self.v[3].rotl(16);
self.v[3] ^= self.v[2];
self.v[0] += self.v[3];
self.v[3] = self.v[3].rotl(21);
self.v[3] ^= self.v[0];
self.v[2] += self.v[1];
self.v[1] = self.v[1].rotl(17);
self.v[1] ^= self.v[2];
self.v[2] = self.v[2].rotl(32);
}

View File

@@ -0,0 +1,185 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// Dedicated from repo: https://github.com/NotsoanoNimus/whirlpool.c3l
module std::hash::whirlpool;
import std::hash::hmac;
const BLOCK_SIZE = 64;
const HASH_SIZE = 64;
const BLOCK_128 = 64 / int128.sizeof;
struct Whirlpool
{
ulong[8] hash;
union
{
char[BLOCK_SIZE] block;
int128[BLOCK_128] block_128;
}
// Using these two integers with a funnel shift permits the original WHIRLPOOL 2^256 length limit.
uint128 counter_high;
uint128 counter_low;
}
alias HmacWhirlpool = Hmac { Whirlpool, HASH_SIZE, BLOCK_SIZE };
alias hmac = hmac::hash { Whirlpool, HASH_SIZE, BLOCK_SIZE };
alias pbkdf2 = hmac::pbkdf2 { Whirlpool, HASH_SIZE, BLOCK_SIZE };
fn char[HASH_SIZE] hash(char[] data)
{
Whirlpool w;
w.update(data);
return w.final();
}
// Added to satisfy HMAC
macro void Whirlpool.init(&self) => *self = { };
<*
@require data.len <= isz.max : "Update with smaller slices"
*>
fn void Whirlpool.update(&self, char[] data)
{
char remainder = (char)self.counter_low & 0x3F; // if not at an even BLOCK_SIZE, how many bytes are over it
uint to_pad = BLOCK_SIZE - remainder; // how many bytes must be filled to get the BLOCK_SIZE
uint128 prev_ctr = self.counter_low;
self.counter_low += data.len;
// Handle counter rollover by just adding 1 onto 'high'. The overflow amount in 'low' remains valid.
// Since 'data' is limited to INT64_MAX, there are no edge cases where 'high' must be incremented by more than 1.
// This is so much data (> 2^128 bytes) that this will likely NEVER happen in practice...
if (@unlikely(self.counter_low < prev_ctr)) ++self.counter_high;
// Pad partial blocks of input data.
if (remainder > 0)
{
usz span = to_pad < data.len ? to_pad : data.len;
self.block[remainder:span] = data[:span];
// When there wasn't enough data ingested to fill a block, just return to allow more incoming data to fill in.
// 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);
data = data[to_pad..];
}
// Digest blocks wholesale.
while (data.len >= BLOCK_SIZE)
{
self.process_block(data);
data = (data.len > BLOCK_SIZE) ? data[BLOCK_SIZE..] : {};
}
// Any leftovers should be swept into the 'block' buffer in the context for the next update or for 'final'.
if (data.len)
{
usz leftover_span = (BLOCK_SIZE < data.len ? BLOCK_SIZE : data.len);
self.block[:leftover_span] = data[:leftover_span];
}
}
fn char[HASH_SIZE] Whirlpool.final(&self)
{
char remainder = (char)self.counter_low & 0x3F; // if not at an even BLOCK_SIZE, how many bytes are over it
// Terminating byte gets added to the block.
self.block[remainder++] = 0x80;
// When fewer than 256 bits are available to store the counter into, pad the block with zeroes and process it.
if (remainder > 32)
{
if (remainder < 64) self.block[remainder..63] = 0x00;
self.process_block(&self.block);
remainder = 0;
}
// Zero out the rest of the chunk (or a new chunk if the above 'if' was true), up to byte 32.
if (remainder < 32) self.block[remainder..31] = 0x00;
// Insert the big-endian values of the counter as a suffix of the block.
// This 'counter' value is actually the number of BITS processed, hence the << 3 (or x8).
self.block_128[2] = $$bswap(self.counter_high << 3 | (self.counter_low >> 125 & 0x07));
self.block_128[3] = $$bswap(self.counter_low << 3);
// Process the final block.
self.process_block(&self.block);
// Each ulong in the resultant hash should be bit-swapped before the final return.
char[HASH_SIZE] hash @align(ulong.alignof);
ulong* hash_ref = (ulong*)&hash;
$for var $i = 0; $i < 8; ++$i :
hash_ref[$i] = $$bswap(self.hash[$i]);
$endfor
// All done!
return hash;
}
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 = {
0x1823c6e887b8014f,
0x36a6d2f5796f9152,
0x60bc9b8ea30c7b35,
0x1de0d7c22e4bfe57,
0x157737e59ff04ada,
0x58c9290ab1a06b85,
0xbd5d10f4cb3e0567,
0xe427418ba77d95d8,
0xfbee7c66dd17479e,
0xca2dbf07ad5a8333
};
const ROUNDS = 10;
fn void Whirlpool.process_block(&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(@unaligned_load(((ulong*)block)[$round], 1)) ^ self.hash[$round];
self.hash[$round] = state[$round];
$endfor
$for var $round = 0; $round < ROUNDS; ++$round :
var $m = $round % 2;
k[(($m ^ 1) * 8) + 0] = @w_op((&k[$m * 8]), 0) ^ RC[$round];
$for var $i = 1; $i < 8; $i++ :
k[(($m ^ 1) * 8) + $i] = @w_op((&k[$m * 8]), $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];
$endfor
$endfor
$for var $x = 0; $x < 8; $x++:
self.hash[$x] ^= state[$x];
$endfor
}

View File

@@ -0,0 +1,539 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// Dedicated from repo: https://github.com/NotsoanoNimus/whirlpool.c3l
module std::hash::whirlpool @private;
// =========================================================================
// WHIRLPOOL s-box data.
const ulong[8 * 256] S_BOX = {
// C0
0x18186018c07830d8, 0x23238c2305af4626, 0xc6c63fc67ef991b8, 0xe8e887e8136fcdfb,
0x878726874ca113cb, 0xb8b8dab8a9626d11, 0x0101040108050209, 0x4f4f214f426e9e0d,
0x3636d836adee6c9b, 0xa6a6a2a6590451ff, 0xd2d26fd2debdb90c, 0xf5f5f3f5fb06f70e,
0x7979f979ef80f296, 0x6f6fa16f5fcede30, 0x91917e91fcef3f6d, 0x52525552aa07a4f8,
0x60609d6027fdc047, 0xbcbccabc89766535, 0x9b9b569baccd2b37, 0x8e8e028e048c018a,
0xa3a3b6a371155bd2, 0x0c0c300c603c186c, 0x7b7bf17bff8af684, 0x3535d435b5e16a80,
0x1d1d741de8693af5, 0xe0e0a7e05347ddb3, 0xd7d77bd7f6acb321, 0xc2c22fc25eed999c,
0x2e2eb82e6d965c43, 0x4b4b314b627a9629, 0xfefedffea321e15d, 0x575741578216aed5,
0x15155415a8412abd, 0x7777c1779fb6eee8, 0x3737dc37a5eb6e92, 0xe5e5b3e57b56d79e,
0x9f9f469f8cd92313, 0xf0f0e7f0d317fd23, 0x4a4a354a6a7f9420, 0xdada4fda9e95a944,
0x58587d58fa25b0a2, 0xc9c903c906ca8fcf, 0x2929a429558d527c, 0x0a0a280a5022145a,
0xb1b1feb1e14f7f50, 0xa0a0baa0691a5dc9, 0x6b6bb16b7fdad614, 0x85852e855cab17d9,
0xbdbdcebd8173673c, 0x5d5d695dd234ba8f, 0x1010401080502090, 0xf4f4f7f4f303f507,
0xcbcb0bcb16c08bdd, 0x3e3ef83eedc67cd3, 0x0505140528110a2d, 0x676781671fe6ce78,
0xe4e4b7e47353d597, 0x27279c2725bb4e02, 0x4141194132588273, 0x8b8b168b2c9d0ba7,
0xa7a7a6a7510153f6, 0x7d7de97dcf94fab2, 0x95956e95dcfb3749, 0xd8d847d88e9fad56,
0xfbfbcbfb8b30eb70, 0xeeee9fee2371c1cd, 0x7c7ced7cc791f8bb, 0x6666856617e3cc71,
0xdddd53dda68ea77b, 0x17175c17b84b2eaf, 0x4747014702468e45, 0x9e9e429e84dc211a,
0xcaca0fca1ec589d4, 0x2d2db42d75995a58, 0xbfbfc6bf9179632e, 0x07071c07381b0e3f,
0xadad8ead012347ac, 0x5a5a755aea2fb4b0, 0x838336836cb51bef, 0x3333cc3385ff66b6,
0x636391633ff2c65c, 0x02020802100a0412, 0xaaaa92aa39384993, 0x7171d971afa8e2de,
0xc8c807c80ecf8dc6, 0x19196419c87d32d1, 0x494939497270923b, 0xd9d943d9869aaf5f,
0xf2f2eff2c31df931, 0xe3e3abe34b48dba8, 0x5b5b715be22ab6b9, 0x88881a8834920dbc,
0x9a9a529aa4c8293e, 0x262698262dbe4c0b, 0x3232c8328dfa64bf, 0xb0b0fab0e94a7d59,
0xe9e983e91b6acff2, 0x0f0f3c0f78331e77, 0xd5d573d5e6a6b733, 0x80803a8074ba1df4,
0xbebec2be997c6127, 0xcdcd13cd26de87eb, 0x3434d034bde46889, 0x48483d487a759032,
0xffffdbffab24e354, 0x7a7af57af78ff48d, 0x90907a90f4ea3d64, 0x5f5f615fc23ebe9d,
0x202080201da0403d, 0x6868bd6867d5d00f, 0x1a1a681ad07234ca, 0xaeae82ae192c41b7,
0xb4b4eab4c95e757d, 0x54544d549a19a8ce, 0x93937693ece53b7f, 0x222288220daa442f,
0x64648d6407e9c863, 0xf1f1e3f1db12ff2a, 0x7373d173bfa2e6cc, 0x12124812905a2482,
0x40401d403a5d807a, 0x0808200840281048, 0xc3c32bc356e89b95, 0xecec97ec337bc5df,
0xdbdb4bdb9690ab4d, 0xa1a1bea1611f5fc0, 0x8d8d0e8d1c830791, 0x3d3df43df5c97ac8,
0x97976697ccf1335b, 0x0000000000000000, 0xcfcf1bcf36d483f9, 0x2b2bac2b4587566e,
0x7676c57697b3ece1, 0x8282328264b019e6, 0xd6d67fd6fea9b128, 0x1b1b6c1bd87736c3,
0xb5b5eeb5c15b7774, 0xafaf86af112943be, 0x6a6ab56a77dfd41d, 0x50505d50ba0da0ea,
0x45450945124c8a57, 0xf3f3ebf3cb18fb38, 0x3030c0309df060ad, 0xefef9bef2b74c3c4,
0x3f3ffc3fe5c37eda, 0x55554955921caac7, 0xa2a2b2a2791059db, 0xeaea8fea0365c9e9,
0x656589650fecca6a, 0xbabad2bab9686903, 0x2f2fbc2f65935e4a, 0xc0c027c04ee79d8e,
0xdede5fdebe81a160, 0x1c1c701ce06c38fc, 0xfdfdd3fdbb2ee746, 0x4d4d294d52649a1f,
0x92927292e4e03976, 0x7575c9758fbceafa, 0x06061806301e0c36, 0x8a8a128a249809ae,
0xb2b2f2b2f940794b, 0xe6e6bfe66359d185, 0x0e0e380e70361c7e, 0x1f1f7c1ff8633ee7,
0x6262956237f7c455, 0xd4d477d4eea3b53a, 0xa8a89aa829324d81, 0x96966296c4f43152,
0xf9f9c3f99b3aef62, 0xc5c533c566f697a3, 0x2525942535b14a10, 0x59597959f220b2ab,
0x84842a8454ae15d0, 0x7272d572b7a7e4c5, 0x3939e439d5dd72ec, 0x4c4c2d4c5a619816,
0x5e5e655eca3bbc94, 0x7878fd78e785f09f, 0x3838e038ddd870e5, 0x8c8c0a8c14860598,
0xd1d163d1c6b2bf17, 0xa5a5aea5410b57e4, 0xe2e2afe2434dd9a1, 0x616199612ff8c24e,
0xb3b3f6b3f1457b42, 0x2121842115a54234, 0x9c9c4a9c94d62508, 0x1e1e781ef0663cee,
0x4343114322528661, 0xc7c73bc776fc93b1, 0xfcfcd7fcb32be54f, 0x0404100420140824,
0x51515951b208a2e3, 0x99995e99bcc72f25, 0x6d6da96d4fc4da22, 0x0d0d340d68391a65,
0xfafacffa8335e979, 0xdfdf5bdfb684a369, 0x7e7ee57ed79bfca9, 0x242490243db44819,
0x3b3bec3bc5d776fe, 0xabab96ab313d4b9a, 0xcece1fce3ed181f0, 0x1111441188552299,
0x8f8f068f0c890383, 0x4e4e254e4a6b9c04, 0xb7b7e6b7d1517366, 0xebeb8beb0b60cbe0,
0x3c3cf03cfdcc78c1, 0x81813e817cbf1ffd, 0x94946a94d4fe3540, 0xf7f7fbf7eb0cf31c,
0xb9b9deb9a1676f18, 0x13134c13985f268b, 0x2c2cb02c7d9c5851, 0xd3d36bd3d6b8bb05,
0xe7e7bbe76b5cd38c, 0x6e6ea56e57cbdc39, 0xc4c437c46ef395aa, 0x03030c03180f061b,
0x565645568a13acdc, 0x44440d441a49885e, 0x7f7fe17fdf9efea0, 0xa9a99ea921374f88,
0x2a2aa82a4d825467, 0xbbbbd6bbb16d6b0a, 0xc1c123c146e29f87, 0x53535153a202a6f1,
0xdcdc57dcae8ba572, 0x0b0b2c0b58271653, 0x9d9d4e9d9cd32701, 0x6c6cad6c47c1d82b,
0x3131c43195f562a4, 0x7474cd7487b9e8f3, 0xf6f6fff6e309f115, 0x464605460a438c4c,
0xacac8aac092645a5, 0x89891e893c970fb5, 0x14145014a04428b4, 0xe1e1a3e15b42dfba,
0x16165816b04e2ca6, 0x3a3ae83acdd274f7, 0x6969b9696fd0d206, 0x09092409482d1241,
0x7070dd70a7ade0d7, 0xb6b6e2b6d954716f, 0xd0d067d0ceb7bd1e, 0xeded93ed3b7ec7d6,
0xcccc17cc2edb85e2, 0x424215422a578468, 0x98985a98b4c22d2c, 0xa4a4aaa4490e55ed,
0x2828a0285d885075, 0x5c5c6d5cda31b886, 0xf8f8c7f8933fed6b, 0x8686228644a411c2,
// C1
0xd818186018c07830, 0x2623238c2305af46, 0xb8c6c63fc67ef991, 0xfbe8e887e8136fcd,
0xcb878726874ca113, 0x11b8b8dab8a9626d, 0x0901010401080502, 0x0d4f4f214f426e9e,
0x9b3636d836adee6c, 0xffa6a6a2a6590451, 0x0cd2d26fd2debdb9, 0x0ef5f5f3f5fb06f7,
0x967979f979ef80f2, 0x306f6fa16f5fcede, 0x6d91917e91fcef3f, 0xf852525552aa07a4,
0x4760609d6027fdc0, 0x35bcbccabc897665, 0x379b9b569baccd2b, 0x8a8e8e028e048c01,
0xd2a3a3b6a371155b, 0x6c0c0c300c603c18, 0x847b7bf17bff8af6, 0x803535d435b5e16a,
0xf51d1d741de8693a, 0xb3e0e0a7e05347dd, 0x21d7d77bd7f6acb3, 0x9cc2c22fc25eed99,
0x432e2eb82e6d965c, 0x294b4b314b627a96, 0x5dfefedffea321e1, 0xd5575741578216ae,
0xbd15155415a8412a, 0xe87777c1779fb6ee, 0x923737dc37a5eb6e, 0x9ee5e5b3e57b56d7,
0x139f9f469f8cd923, 0x23f0f0e7f0d317fd, 0x204a4a354a6a7f94, 0x44dada4fda9e95a9,
0xa258587d58fa25b0, 0xcfc9c903c906ca8f, 0x7c2929a429558d52, 0x5a0a0a280a502214,
0x50b1b1feb1e14f7f, 0xc9a0a0baa0691a5d, 0x146b6bb16b7fdad6, 0xd985852e855cab17,
0x3cbdbdcebd817367, 0x8f5d5d695dd234ba, 0x9010104010805020, 0x07f4f4f7f4f303f5,
0xddcbcb0bcb16c08b, 0xd33e3ef83eedc67c, 0x2d0505140528110a, 0x78676781671fe6ce,
0x97e4e4b7e47353d5, 0x0227279c2725bb4e, 0x7341411941325882, 0xa78b8b168b2c9d0b,
0xf6a7a7a6a7510153, 0xb27d7de97dcf94fa, 0x4995956e95dcfb37, 0x56d8d847d88e9fad,
0x70fbfbcbfb8b30eb, 0xcdeeee9fee2371c1, 0xbb7c7ced7cc791f8, 0x716666856617e3cc,
0x7bdddd53dda68ea7, 0xaf17175c17b84b2e, 0x454747014702468e, 0x1a9e9e429e84dc21,
0xd4caca0fca1ec589, 0x582d2db42d75995a, 0x2ebfbfc6bf917963, 0x3f07071c07381b0e,
0xacadad8ead012347, 0xb05a5a755aea2fb4, 0xef838336836cb51b, 0xb63333cc3385ff66,
0x5c636391633ff2c6, 0x1202020802100a04, 0x93aaaa92aa393849, 0xde7171d971afa8e2,
0xc6c8c807c80ecf8d, 0xd119196419c87d32, 0x3b49493949727092, 0x5fd9d943d9869aaf,
0x31f2f2eff2c31df9, 0xa8e3e3abe34b48db, 0xb95b5b715be22ab6, 0xbc88881a8834920d,
0x3e9a9a529aa4c829, 0x0b262698262dbe4c, 0xbf3232c8328dfa64, 0x59b0b0fab0e94a7d,
0xf2e9e983e91b6acf, 0x770f0f3c0f78331e, 0x33d5d573d5e6a6b7, 0xf480803a8074ba1d,
0x27bebec2be997c61, 0xebcdcd13cd26de87, 0x893434d034bde468, 0x3248483d487a7590,
0x54ffffdbffab24e3, 0x8d7a7af57af78ff4, 0x6490907a90f4ea3d, 0x9d5f5f615fc23ebe,
0x3d202080201da040, 0x0f6868bd6867d5d0, 0xca1a1a681ad07234, 0xb7aeae82ae192c41,
0x7db4b4eab4c95e75, 0xce54544d549a19a8, 0x7f93937693ece53b, 0x2f222288220daa44,
0x6364648d6407e9c8, 0x2af1f1e3f1db12ff, 0xcc7373d173bfa2e6, 0x8212124812905a24,
0x7a40401d403a5d80, 0x4808082008402810, 0x95c3c32bc356e89b, 0xdfecec97ec337bc5,
0x4ddbdb4bdb9690ab, 0xc0a1a1bea1611f5f, 0x918d8d0e8d1c8307, 0xc83d3df43df5c97a,
0x5b97976697ccf133, 0x0000000000000000, 0xf9cfcf1bcf36d483, 0x6e2b2bac2b458756,
0xe17676c57697b3ec, 0xe68282328264b019, 0x28d6d67fd6fea9b1, 0xc31b1b6c1bd87736,
0x74b5b5eeb5c15b77, 0xbeafaf86af112943, 0x1d6a6ab56a77dfd4, 0xea50505d50ba0da0,
0x5745450945124c8a, 0x38f3f3ebf3cb18fb, 0xad3030c0309df060, 0xc4efef9bef2b74c3,
0xda3f3ffc3fe5c37e, 0xc755554955921caa, 0xdba2a2b2a2791059, 0xe9eaea8fea0365c9,
0x6a656589650fecca, 0x03babad2bab96869, 0x4a2f2fbc2f65935e, 0x8ec0c027c04ee79d,
0x60dede5fdebe81a1, 0xfc1c1c701ce06c38, 0x46fdfdd3fdbb2ee7, 0x1f4d4d294d52649a,
0x7692927292e4e039, 0xfa7575c9758fbcea, 0x3606061806301e0c, 0xae8a8a128a249809,
0x4bb2b2f2b2f94079, 0x85e6e6bfe66359d1, 0x7e0e0e380e70361c, 0xe71f1f7c1ff8633e,
0x556262956237f7c4, 0x3ad4d477d4eea3b5, 0x81a8a89aa829324d, 0x5296966296c4f431,
0x62f9f9c3f99b3aef, 0xa3c5c533c566f697, 0x102525942535b14a, 0xab59597959f220b2,
0xd084842a8454ae15, 0xc57272d572b7a7e4, 0xec3939e439d5dd72, 0x164c4c2d4c5a6198,
0x945e5e655eca3bbc, 0x9f7878fd78e785f0, 0xe53838e038ddd870, 0x988c8c0a8c148605,
0x17d1d163d1c6b2bf, 0xe4a5a5aea5410b57, 0xa1e2e2afe2434dd9, 0x4e616199612ff8c2,
0x42b3b3f6b3f1457b, 0x342121842115a542, 0x089c9c4a9c94d625, 0xee1e1e781ef0663c,
0x6143431143225286, 0xb1c7c73bc776fc93, 0x4ffcfcd7fcb32be5, 0x2404041004201408,
0xe351515951b208a2, 0x2599995e99bcc72f, 0x226d6da96d4fc4da, 0x650d0d340d68391a,
0x79fafacffa8335e9, 0x69dfdf5bdfb684a3, 0xa97e7ee57ed79bfc, 0x19242490243db448,
0xfe3b3bec3bc5d776, 0x9aabab96ab313d4b, 0xf0cece1fce3ed181, 0x9911114411885522,
0x838f8f068f0c8903, 0x044e4e254e4a6b9c, 0x66b7b7e6b7d15173, 0xe0ebeb8beb0b60cb,
0xc13c3cf03cfdcc78, 0xfd81813e817cbf1f, 0x4094946a94d4fe35, 0x1cf7f7fbf7eb0cf3,
0x18b9b9deb9a1676f, 0x8b13134c13985f26, 0x512c2cb02c7d9c58, 0x05d3d36bd3d6b8bb,
0x8ce7e7bbe76b5cd3, 0x396e6ea56e57cbdc, 0xaac4c437c46ef395, 0x1b03030c03180f06,
0xdc565645568a13ac, 0x5e44440d441a4988, 0xa07f7fe17fdf9efe, 0x88a9a99ea921374f,
0x672a2aa82a4d8254, 0x0abbbbd6bbb16d6b, 0x87c1c123c146e29f, 0xf153535153a202a6,
0x72dcdc57dcae8ba5, 0x530b0b2c0b582716, 0x019d9d4e9d9cd327, 0x2b6c6cad6c47c1d8,
0xa43131c43195f562, 0xf37474cd7487b9e8, 0x15f6f6fff6e309f1, 0x4c464605460a438c,
0xa5acac8aac092645, 0xb589891e893c970f, 0xb414145014a04428, 0xbae1e1a3e15b42df,
0xa616165816b04e2c, 0xf73a3ae83acdd274, 0x066969b9696fd0d2, 0x4109092409482d12,
0xd77070dd70a7ade0, 0x6fb6b6e2b6d95471, 0x1ed0d067d0ceb7bd, 0xd6eded93ed3b7ec7,
0xe2cccc17cc2edb85, 0x68424215422a5784, 0x2c98985a98b4c22d, 0xeda4a4aaa4490e55,
0x752828a0285d8850, 0x865c5c6d5cda31b8, 0x6bf8f8c7f8933fed, 0xc28686228644a411,
// C2
0x30d818186018c078, 0x462623238c2305af, 0x91b8c6c63fc67ef9, 0xcdfbe8e887e8136f,
0x13cb878726874ca1, 0x6d11b8b8dab8a962, 0x0209010104010805, 0x9e0d4f4f214f426e,
0x6c9b3636d836adee, 0x51ffa6a6a2a65904, 0xb90cd2d26fd2debd, 0xf70ef5f5f3f5fb06,
0xf2967979f979ef80, 0xde306f6fa16f5fce, 0x3f6d91917e91fcef, 0xa4f852525552aa07,
0xc04760609d6027fd, 0x6535bcbccabc8976, 0x2b379b9b569baccd, 0x018a8e8e028e048c,
0x5bd2a3a3b6a37115, 0x186c0c0c300c603c, 0xf6847b7bf17bff8a, 0x6a803535d435b5e1,
0x3af51d1d741de869, 0xddb3e0e0a7e05347, 0xb321d7d77bd7f6ac, 0x999cc2c22fc25eed,
0x5c432e2eb82e6d96, 0x96294b4b314b627a, 0xe15dfefedffea321, 0xaed5575741578216,
0x2abd15155415a841, 0xeee87777c1779fb6, 0x6e923737dc37a5eb, 0xd79ee5e5b3e57b56,
0x23139f9f469f8cd9, 0xfd23f0f0e7f0d317, 0x94204a4a354a6a7f, 0xa944dada4fda9e95,
0xb0a258587d58fa25, 0x8fcfc9c903c906ca, 0x527c2929a429558d, 0x145a0a0a280a5022,
0x7f50b1b1feb1e14f, 0x5dc9a0a0baa0691a, 0xd6146b6bb16b7fda, 0x17d985852e855cab,
0x673cbdbdcebd8173, 0xba8f5d5d695dd234, 0x2090101040108050, 0xf507f4f4f7f4f303,
0x8bddcbcb0bcb16c0, 0x7cd33e3ef83eedc6, 0x0a2d050514052811, 0xce78676781671fe6,
0xd597e4e4b7e47353, 0x4e0227279c2725bb, 0x8273414119413258, 0x0ba78b8b168b2c9d,
0x53f6a7a7a6a75101, 0xfab27d7de97dcf94, 0x374995956e95dcfb, 0xad56d8d847d88e9f,
0xeb70fbfbcbfb8b30, 0xc1cdeeee9fee2371, 0xf8bb7c7ced7cc791, 0xcc716666856617e3,
0xa77bdddd53dda68e, 0x2eaf17175c17b84b, 0x8e45474701470246, 0x211a9e9e429e84dc,
0x89d4caca0fca1ec5, 0x5a582d2db42d7599, 0x632ebfbfc6bf9179, 0x0e3f07071c07381b,
0x47acadad8ead0123, 0xb4b05a5a755aea2f, 0x1bef838336836cb5, 0x66b63333cc3385ff,
0xc65c636391633ff2, 0x041202020802100a, 0x4993aaaa92aa3938, 0xe2de7171d971afa8,
0x8dc6c8c807c80ecf, 0x32d119196419c87d, 0x923b494939497270, 0xaf5fd9d943d9869a,
0xf931f2f2eff2c31d, 0xdba8e3e3abe34b48, 0xb6b95b5b715be22a, 0x0dbc88881a883492,
0x293e9a9a529aa4c8, 0x4c0b262698262dbe, 0x64bf3232c8328dfa, 0x7d59b0b0fab0e94a,
0xcff2e9e983e91b6a, 0x1e770f0f3c0f7833, 0xb733d5d573d5e6a6, 0x1df480803a8074ba,
0x6127bebec2be997c, 0x87ebcdcd13cd26de, 0x68893434d034bde4, 0x903248483d487a75,
0xe354ffffdbffab24, 0xf48d7a7af57af78f, 0x3d6490907a90f4ea, 0xbe9d5f5f615fc23e,
0x403d202080201da0, 0xd00f6868bd6867d5, 0x34ca1a1a681ad072, 0x41b7aeae82ae192c,
0x757db4b4eab4c95e, 0xa8ce54544d549a19, 0x3b7f93937693ece5, 0x442f222288220daa,
0xc86364648d6407e9, 0xff2af1f1e3f1db12, 0xe6cc7373d173bfa2, 0x248212124812905a,
0x807a40401d403a5d, 0x1048080820084028, 0x9b95c3c32bc356e8, 0xc5dfecec97ec337b,
0xab4ddbdb4bdb9690, 0x5fc0a1a1bea1611f, 0x07918d8d0e8d1c83, 0x7ac83d3df43df5c9,
0x335b97976697ccf1, 0x0000000000000000, 0x83f9cfcf1bcf36d4, 0x566e2b2bac2b4587,
0xece17676c57697b3, 0x19e68282328264b0, 0xb128d6d67fd6fea9, 0x36c31b1b6c1bd877,
0x7774b5b5eeb5c15b, 0x43beafaf86af1129, 0xd41d6a6ab56a77df, 0xa0ea50505d50ba0d,
0x8a5745450945124c, 0xfb38f3f3ebf3cb18, 0x60ad3030c0309df0, 0xc3c4efef9bef2b74,
0x7eda3f3ffc3fe5c3, 0xaac755554955921c, 0x59dba2a2b2a27910, 0xc9e9eaea8fea0365,
0xca6a656589650fec, 0x6903babad2bab968, 0x5e4a2f2fbc2f6593, 0x9d8ec0c027c04ee7,
0xa160dede5fdebe81, 0x38fc1c1c701ce06c, 0xe746fdfdd3fdbb2e, 0x9a1f4d4d294d5264,
0x397692927292e4e0, 0xeafa7575c9758fbc, 0x0c3606061806301e, 0x09ae8a8a128a2498,
0x794bb2b2f2b2f940, 0xd185e6e6bfe66359, 0x1c7e0e0e380e7036, 0x3ee71f1f7c1ff863,
0xc4556262956237f7, 0xb53ad4d477d4eea3, 0x4d81a8a89aa82932, 0x315296966296c4f4,
0xef62f9f9c3f99b3a, 0x97a3c5c533c566f6, 0x4a102525942535b1, 0xb2ab59597959f220,
0x15d084842a8454ae, 0xe4c57272d572b7a7, 0x72ec3939e439d5dd, 0x98164c4c2d4c5a61,
0xbc945e5e655eca3b, 0xf09f7878fd78e785, 0x70e53838e038ddd8, 0x05988c8c0a8c1486,
0xbf17d1d163d1c6b2, 0x57e4a5a5aea5410b, 0xd9a1e2e2afe2434d, 0xc24e616199612ff8,
0x7b42b3b3f6b3f145, 0x42342121842115a5, 0x25089c9c4a9c94d6, 0x3cee1e1e781ef066,
0x8661434311432252, 0x93b1c7c73bc776fc, 0xe54ffcfcd7fcb32b, 0x0824040410042014,
0xa2e351515951b208, 0x2f2599995e99bcc7, 0xda226d6da96d4fc4, 0x1a650d0d340d6839,
0xe979fafacffa8335, 0xa369dfdf5bdfb684, 0xfca97e7ee57ed79b, 0x4819242490243db4,
0x76fe3b3bec3bc5d7, 0x4b9aabab96ab313d, 0x81f0cece1fce3ed1, 0x2299111144118855,
0x03838f8f068f0c89, 0x9c044e4e254e4a6b, 0x7366b7b7e6b7d151, 0xcbe0ebeb8beb0b60,
0x78c13c3cf03cfdcc, 0x1ffd81813e817cbf, 0x354094946a94d4fe, 0xf31cf7f7fbf7eb0c,
0x6f18b9b9deb9a167, 0x268b13134c13985f, 0x58512c2cb02c7d9c, 0xbb05d3d36bd3d6b8,
0xd38ce7e7bbe76b5c, 0xdc396e6ea56e57cb, 0x95aac4c437c46ef3, 0x061b03030c03180f,
0xacdc565645568a13, 0x885e44440d441a49, 0xfea07f7fe17fdf9e, 0x4f88a9a99ea92137,
0x54672a2aa82a4d82, 0x6b0abbbbd6bbb16d, 0x9f87c1c123c146e2, 0xa6f153535153a202,
0xa572dcdc57dcae8b, 0x16530b0b2c0b5827, 0x27019d9d4e9d9cd3, 0xd82b6c6cad6c47c1,
0x62a43131c43195f5, 0xe8f37474cd7487b9, 0xf115f6f6fff6e309, 0x8c4c464605460a43,
0x45a5acac8aac0926, 0x0fb589891e893c97, 0x28b414145014a044, 0xdfbae1e1a3e15b42,
0x2ca616165816b04e, 0x74f73a3ae83acdd2, 0xd2066969b9696fd0, 0x124109092409482d,
0xe0d77070dd70a7ad, 0x716fb6b6e2b6d954, 0xbd1ed0d067d0ceb7, 0xc7d6eded93ed3b7e,
0x85e2cccc17cc2edb, 0x8468424215422a57, 0x2d2c98985a98b4c2, 0x55eda4a4aaa4490e,
0x50752828a0285d88, 0xb8865c5c6d5cda31, 0xed6bf8f8c7f8933f, 0x11c28686228644a4,
// C3
0x7830d818186018c0, 0xaf462623238c2305, 0xf991b8c6c63fc67e, 0x6fcdfbe8e887e813,
0xa113cb878726874c, 0x626d11b8b8dab8a9, 0x0502090101040108, 0x6e9e0d4f4f214f42,
0xee6c9b3636d836ad, 0x0451ffa6a6a2a659, 0xbdb90cd2d26fd2de, 0x06f70ef5f5f3f5fb,
0x80f2967979f979ef, 0xcede306f6fa16f5f, 0xef3f6d91917e91fc, 0x07a4f852525552aa,
0xfdc04760609d6027, 0x766535bcbccabc89, 0xcd2b379b9b569bac, 0x8c018a8e8e028e04,
0x155bd2a3a3b6a371, 0x3c186c0c0c300c60, 0x8af6847b7bf17bff, 0xe16a803535d435b5,
0x693af51d1d741de8, 0x47ddb3e0e0a7e053, 0xacb321d7d77bd7f6, 0xed999cc2c22fc25e,
0x965c432e2eb82e6d, 0x7a96294b4b314b62, 0x21e15dfefedffea3, 0x16aed55757415782,
0x412abd15155415a8, 0xb6eee87777c1779f, 0xeb6e923737dc37a5, 0x56d79ee5e5b3e57b,
0xd923139f9f469f8c, 0x17fd23f0f0e7f0d3, 0x7f94204a4a354a6a, 0x95a944dada4fda9e,
0x25b0a258587d58fa, 0xca8fcfc9c903c906, 0x8d527c2929a42955, 0x22145a0a0a280a50,
0x4f7f50b1b1feb1e1, 0x1a5dc9a0a0baa069, 0xdad6146b6bb16b7f, 0xab17d985852e855c,
0x73673cbdbdcebd81, 0x34ba8f5d5d695dd2, 0x5020901010401080, 0x03f507f4f4f7f4f3,
0xc08bddcbcb0bcb16, 0xc67cd33e3ef83eed, 0x110a2d0505140528, 0xe6ce78676781671f,
0x53d597e4e4b7e473, 0xbb4e0227279c2725, 0x5882734141194132, 0x9d0ba78b8b168b2c,
0x0153f6a7a7a6a751, 0x94fab27d7de97dcf, 0xfb374995956e95dc, 0x9fad56d8d847d88e,
0x30eb70fbfbcbfb8b, 0x71c1cdeeee9fee23, 0x91f8bb7c7ced7cc7, 0xe3cc716666856617,
0x8ea77bdddd53dda6, 0x4b2eaf17175c17b8, 0x468e454747014702, 0xdc211a9e9e429e84,
0xc589d4caca0fca1e, 0x995a582d2db42d75, 0x79632ebfbfc6bf91, 0x1b0e3f07071c0738,
0x2347acadad8ead01, 0x2fb4b05a5a755aea, 0xb51bef838336836c, 0xff66b63333cc3385,
0xf2c65c636391633f, 0x0a04120202080210, 0x384993aaaa92aa39, 0xa8e2de7171d971af,
0xcf8dc6c8c807c80e, 0x7d32d119196419c8, 0x70923b4949394972, 0x9aaf5fd9d943d986,
0x1df931f2f2eff2c3, 0x48dba8e3e3abe34b, 0x2ab6b95b5b715be2, 0x920dbc88881a8834,
0xc8293e9a9a529aa4, 0xbe4c0b262698262d, 0xfa64bf3232c8328d, 0x4a7d59b0b0fab0e9,
0x6acff2e9e983e91b, 0x331e770f0f3c0f78, 0xa6b733d5d573d5e6, 0xba1df480803a8074,
0x7c6127bebec2be99, 0xde87ebcdcd13cd26, 0xe468893434d034bd, 0x75903248483d487a,
0x24e354ffffdbffab, 0x8ff48d7a7af57af7, 0xea3d6490907a90f4, 0x3ebe9d5f5f615fc2,
0xa0403d202080201d, 0xd5d00f6868bd6867, 0x7234ca1a1a681ad0, 0x2c41b7aeae82ae19,
0x5e757db4b4eab4c9, 0x19a8ce54544d549a, 0xe53b7f93937693ec, 0xaa442f222288220d,
0xe9c86364648d6407, 0x12ff2af1f1e3f1db, 0xa2e6cc7373d173bf, 0x5a24821212481290,
0x5d807a40401d403a, 0x2810480808200840, 0xe89b95c3c32bc356, 0x7bc5dfecec97ec33,
0x90ab4ddbdb4bdb96, 0x1f5fc0a1a1bea161, 0x8307918d8d0e8d1c, 0xc97ac83d3df43df5,
0xf1335b97976697cc, 0x0000000000000000, 0xd483f9cfcf1bcf36, 0x87566e2b2bac2b45,
0xb3ece17676c57697, 0xb019e68282328264, 0xa9b128d6d67fd6fe, 0x7736c31b1b6c1bd8,
0x5b7774b5b5eeb5c1, 0x2943beafaf86af11, 0xdfd41d6a6ab56a77, 0x0da0ea50505d50ba,
0x4c8a574545094512, 0x18fb38f3f3ebf3cb, 0xf060ad3030c0309d, 0x74c3c4efef9bef2b,
0xc37eda3f3ffc3fe5, 0x1caac75555495592, 0x1059dba2a2b2a279, 0x65c9e9eaea8fea03,
0xecca6a656589650f, 0x686903babad2bab9, 0x935e4a2f2fbc2f65, 0xe79d8ec0c027c04e,
0x81a160dede5fdebe, 0x6c38fc1c1c701ce0, 0x2ee746fdfdd3fdbb, 0x649a1f4d4d294d52,
0xe0397692927292e4, 0xbceafa7575c9758f, 0x1e0c360606180630, 0x9809ae8a8a128a24,
0x40794bb2b2f2b2f9, 0x59d185e6e6bfe663, 0x361c7e0e0e380e70, 0x633ee71f1f7c1ff8,
0xf7c4556262956237, 0xa3b53ad4d477d4ee, 0x324d81a8a89aa829, 0xf4315296966296c4,
0x3aef62f9f9c3f99b, 0xf697a3c5c533c566, 0xb14a102525942535, 0x20b2ab59597959f2,
0xae15d084842a8454, 0xa7e4c57272d572b7, 0xdd72ec3939e439d5, 0x6198164c4c2d4c5a,
0x3bbc945e5e655eca, 0x85f09f7878fd78e7, 0xd870e53838e038dd, 0x8605988c8c0a8c14,
0xb2bf17d1d163d1c6, 0x0b57e4a5a5aea541, 0x4dd9a1e2e2afe243, 0xf8c24e616199612f,
0x457b42b3b3f6b3f1, 0xa542342121842115, 0xd625089c9c4a9c94, 0x663cee1e1e781ef0,
0x5286614343114322, 0xfc93b1c7c73bc776, 0x2be54ffcfcd7fcb3, 0x1408240404100420,
0x08a2e351515951b2, 0xc72f2599995e99bc, 0xc4da226d6da96d4f, 0x391a650d0d340d68,
0x35e979fafacffa83, 0x84a369dfdf5bdfb6, 0x9bfca97e7ee57ed7, 0xb44819242490243d,
0xd776fe3b3bec3bc5, 0x3d4b9aabab96ab31, 0xd181f0cece1fce3e, 0x5522991111441188,
0x8903838f8f068f0c, 0x6b9c044e4e254e4a, 0x517366b7b7e6b7d1, 0x60cbe0ebeb8beb0b,
0xcc78c13c3cf03cfd, 0xbf1ffd81813e817c, 0xfe354094946a94d4, 0x0cf31cf7f7fbf7eb,
0x676f18b9b9deb9a1, 0x5f268b13134c1398, 0x9c58512c2cb02c7d, 0xb8bb05d3d36bd3d6,
0x5cd38ce7e7bbe76b, 0xcbdc396e6ea56e57, 0xf395aac4c437c46e, 0x0f061b03030c0318,
0x13acdc565645568a, 0x49885e44440d441a, 0x9efea07f7fe17fdf, 0x374f88a9a99ea921,
0x8254672a2aa82a4d, 0x6d6b0abbbbd6bbb1, 0xe29f87c1c123c146, 0x02a6f153535153a2,
0x8ba572dcdc57dcae, 0x2716530b0b2c0b58, 0xd327019d9d4e9d9c, 0xc1d82b6c6cad6c47,
0xf562a43131c43195, 0xb9e8f37474cd7487, 0x09f115f6f6fff6e3, 0x438c4c464605460a,
0x2645a5acac8aac09, 0x970fb589891e893c, 0x4428b414145014a0, 0x42dfbae1e1a3e15b,
0x4e2ca616165816b0, 0xd274f73a3ae83acd, 0xd0d2066969b9696f, 0x2d12410909240948,
0xade0d77070dd70a7, 0x54716fb6b6e2b6d9, 0xb7bd1ed0d067d0ce, 0x7ec7d6eded93ed3b,
0xdb85e2cccc17cc2e, 0x578468424215422a, 0xc22d2c98985a98b4, 0x0e55eda4a4aaa449,
0x8850752828a0285d, 0x31b8865c5c6d5cda, 0x3fed6bf8f8c7f893, 0xa411c28686228644,
// C4
0xc07830d818186018, 0x05af462623238c23, 0x7ef991b8c6c63fc6, 0x136fcdfbe8e887e8,
0x4ca113cb87872687, 0xa9626d11b8b8dab8, 0x0805020901010401, 0x426e9e0d4f4f214f,
0xadee6c9b3636d836, 0x590451ffa6a6a2a6, 0xdebdb90cd2d26fd2, 0xfb06f70ef5f5f3f5,
0xef80f2967979f979, 0x5fcede306f6fa16f, 0xfcef3f6d91917e91, 0xaa07a4f852525552,
0x27fdc04760609d60, 0x89766535bcbccabc, 0xaccd2b379b9b569b, 0x048c018a8e8e028e,
0x71155bd2a3a3b6a3, 0x603c186c0c0c300c, 0xff8af6847b7bf17b, 0xb5e16a803535d435,
0xe8693af51d1d741d, 0x5347ddb3e0e0a7e0, 0xf6acb321d7d77bd7, 0x5eed999cc2c22fc2,
0x6d965c432e2eb82e, 0x627a96294b4b314b, 0xa321e15dfefedffe, 0x8216aed557574157,
0xa8412abd15155415, 0x9fb6eee87777c177, 0xa5eb6e923737dc37, 0x7b56d79ee5e5b3e5,
0x8cd923139f9f469f, 0xd317fd23f0f0e7f0, 0x6a7f94204a4a354a, 0x9e95a944dada4fda,
0xfa25b0a258587d58, 0x06ca8fcfc9c903c9, 0x558d527c2929a429, 0x5022145a0a0a280a,
0xe14f7f50b1b1feb1, 0x691a5dc9a0a0baa0, 0x7fdad6146b6bb16b, 0x5cab17d985852e85,
0x8173673cbdbdcebd, 0xd234ba8f5d5d695d, 0x8050209010104010, 0xf303f507f4f4f7f4,
0x16c08bddcbcb0bcb, 0xedc67cd33e3ef83e, 0x28110a2d05051405, 0x1fe6ce7867678167,
0x7353d597e4e4b7e4, 0x25bb4e0227279c27, 0x3258827341411941, 0x2c9d0ba78b8b168b,
0x510153f6a7a7a6a7, 0xcf94fab27d7de97d, 0xdcfb374995956e95, 0x8e9fad56d8d847d8,
0x8b30eb70fbfbcbfb, 0x2371c1cdeeee9fee, 0xc791f8bb7c7ced7c, 0x17e3cc7166668566,
0xa68ea77bdddd53dd, 0xb84b2eaf17175c17, 0x02468e4547470147, 0x84dc211a9e9e429e,
0x1ec589d4caca0fca, 0x75995a582d2db42d, 0x9179632ebfbfc6bf, 0x381b0e3f07071c07,
0x012347acadad8ead, 0xea2fb4b05a5a755a, 0x6cb51bef83833683, 0x85ff66b63333cc33,
0x3ff2c65c63639163, 0x100a041202020802, 0x39384993aaaa92aa, 0xafa8e2de7171d971,
0x0ecf8dc6c8c807c8, 0xc87d32d119196419, 0x7270923b49493949, 0x869aaf5fd9d943d9,
0xc31df931f2f2eff2, 0x4b48dba8e3e3abe3, 0xe22ab6b95b5b715b, 0x34920dbc88881a88,
0xa4c8293e9a9a529a, 0x2dbe4c0b26269826, 0x8dfa64bf3232c832, 0xe94a7d59b0b0fab0,
0x1b6acff2e9e983e9, 0x78331e770f0f3c0f, 0xe6a6b733d5d573d5, 0x74ba1df480803a80,
0x997c6127bebec2be, 0x26de87ebcdcd13cd, 0xbde468893434d034, 0x7a75903248483d48,
0xab24e354ffffdbff, 0xf78ff48d7a7af57a, 0xf4ea3d6490907a90, 0xc23ebe9d5f5f615f,
0x1da0403d20208020, 0x67d5d00f6868bd68, 0xd07234ca1a1a681a, 0x192c41b7aeae82ae,
0xc95e757db4b4eab4, 0x9a19a8ce54544d54, 0xece53b7f93937693, 0x0daa442f22228822,
0x07e9c86364648d64, 0xdb12ff2af1f1e3f1, 0xbfa2e6cc7373d173, 0x905a248212124812,
0x3a5d807a40401d40, 0x4028104808082008, 0x56e89b95c3c32bc3, 0x337bc5dfecec97ec,
0x9690ab4ddbdb4bdb, 0x611f5fc0a1a1bea1, 0x1c8307918d8d0e8d, 0xf5c97ac83d3df43d,
0xccf1335b97976697, 0x0000000000000000, 0x36d483f9cfcf1bcf, 0x4587566e2b2bac2b,
0x97b3ece17676c576, 0x64b019e682823282, 0xfea9b128d6d67fd6, 0xd87736c31b1b6c1b,
0xc15b7774b5b5eeb5, 0x112943beafaf86af, 0x77dfd41d6a6ab56a, 0xba0da0ea50505d50,
0x124c8a5745450945, 0xcb18fb38f3f3ebf3, 0x9df060ad3030c030, 0x2b74c3c4efef9bef,
0xe5c37eda3f3ffc3f, 0x921caac755554955, 0x791059dba2a2b2a2, 0x0365c9e9eaea8fea,
0x0fecca6a65658965, 0xb9686903babad2ba, 0x65935e4a2f2fbc2f, 0x4ee79d8ec0c027c0,
0xbe81a160dede5fde, 0xe06c38fc1c1c701c, 0xbb2ee746fdfdd3fd, 0x52649a1f4d4d294d,
0xe4e0397692927292, 0x8fbceafa7575c975, 0x301e0c3606061806, 0x249809ae8a8a128a,
0xf940794bb2b2f2b2, 0x6359d185e6e6bfe6, 0x70361c7e0e0e380e, 0xf8633ee71f1f7c1f,
0x37f7c45562629562, 0xeea3b53ad4d477d4, 0x29324d81a8a89aa8, 0xc4f4315296966296,
0x9b3aef62f9f9c3f9, 0x66f697a3c5c533c5, 0x35b14a1025259425, 0xf220b2ab59597959,
0x54ae15d084842a84, 0xb7a7e4c57272d572, 0xd5dd72ec3939e439, 0x5a6198164c4c2d4c,
0xca3bbc945e5e655e, 0xe785f09f7878fd78, 0xddd870e53838e038, 0x148605988c8c0a8c,
0xc6b2bf17d1d163d1, 0x410b57e4a5a5aea5, 0x434dd9a1e2e2afe2, 0x2ff8c24e61619961,
0xf1457b42b3b3f6b3, 0x15a5423421218421, 0x94d625089c9c4a9c, 0xf0663cee1e1e781e,
0x2252866143431143, 0x76fc93b1c7c73bc7, 0xb32be54ffcfcd7fc, 0x2014082404041004,
0xb208a2e351515951, 0xbcc72f2599995e99, 0x4fc4da226d6da96d, 0x68391a650d0d340d,
0x8335e979fafacffa, 0xb684a369dfdf5bdf, 0xd79bfca97e7ee57e, 0x3db4481924249024,
0xc5d776fe3b3bec3b, 0x313d4b9aabab96ab, 0x3ed181f0cece1fce, 0x8855229911114411,
0x0c8903838f8f068f, 0x4a6b9c044e4e254e, 0xd1517366b7b7e6b7, 0x0b60cbe0ebeb8beb,
0xfdcc78c13c3cf03c, 0x7cbf1ffd81813e81, 0xd4fe354094946a94, 0xeb0cf31cf7f7fbf7,
0xa1676f18b9b9deb9, 0x985f268b13134c13, 0x7d9c58512c2cb02c, 0xd6b8bb05d3d36bd3,
0x6b5cd38ce7e7bbe7, 0x57cbdc396e6ea56e, 0x6ef395aac4c437c4, 0x180f061b03030c03,
0x8a13acdc56564556, 0x1a49885e44440d44, 0xdf9efea07f7fe17f, 0x21374f88a9a99ea9,
0x4d8254672a2aa82a, 0xb16d6b0abbbbd6bb, 0x46e29f87c1c123c1, 0xa202a6f153535153,
0xae8ba572dcdc57dc, 0x582716530b0b2c0b, 0x9cd327019d9d4e9d, 0x47c1d82b6c6cad6c,
0x95f562a43131c431, 0x87b9e8f37474cd74, 0xe309f115f6f6fff6, 0x0a438c4c46460546,
0x092645a5acac8aac, 0x3c970fb589891e89, 0xa04428b414145014, 0x5b42dfbae1e1a3e1,
0xb04e2ca616165816, 0xcdd274f73a3ae83a, 0x6fd0d2066969b969, 0x482d124109092409,
0xa7ade0d77070dd70, 0xd954716fb6b6e2b6, 0xceb7bd1ed0d067d0, 0x3b7ec7d6eded93ed,
0x2edb85e2cccc17cc, 0x2a57846842421542, 0xb4c22d2c98985a98, 0x490e55eda4a4aaa4,
0x5d8850752828a028, 0xda31b8865c5c6d5c, 0x933fed6bf8f8c7f8, 0x44a411c286862286,
// C5
0x18c07830d8181860, 0x2305af462623238c, 0xc67ef991b8c6c63f, 0xe8136fcdfbe8e887,
0x874ca113cb878726, 0xb8a9626d11b8b8da, 0x0108050209010104, 0x4f426e9e0d4f4f21,
0x36adee6c9b3636d8, 0xa6590451ffa6a6a2, 0xd2debdb90cd2d26f, 0xf5fb06f70ef5f5f3,
0x79ef80f2967979f9, 0x6f5fcede306f6fa1, 0x91fcef3f6d91917e, 0x52aa07a4f8525255,
0x6027fdc04760609d, 0xbc89766535bcbcca, 0x9baccd2b379b9b56, 0x8e048c018a8e8e02,
0xa371155bd2a3a3b6, 0x0c603c186c0c0c30, 0x7bff8af6847b7bf1, 0x35b5e16a803535d4,
0x1de8693af51d1d74, 0xe05347ddb3e0e0a7, 0xd7f6acb321d7d77b, 0xc25eed999cc2c22f,
0x2e6d965c432e2eb8, 0x4b627a96294b4b31, 0xfea321e15dfefedf, 0x578216aed5575741,
0x15a8412abd151554, 0x779fb6eee87777c1, 0x37a5eb6e923737dc, 0xe57b56d79ee5e5b3,
0x9f8cd923139f9f46, 0xf0d317fd23f0f0e7, 0x4a6a7f94204a4a35, 0xda9e95a944dada4f,
0x58fa25b0a258587d, 0xc906ca8fcfc9c903, 0x29558d527c2929a4, 0x0a5022145a0a0a28,
0xb1e14f7f50b1b1fe, 0xa0691a5dc9a0a0ba, 0x6b7fdad6146b6bb1, 0x855cab17d985852e,
0xbd8173673cbdbdce, 0x5dd234ba8f5d5d69, 0x1080502090101040, 0xf4f303f507f4f4f7,
0xcb16c08bddcbcb0b, 0x3eedc67cd33e3ef8, 0x0528110a2d050514, 0x671fe6ce78676781,
0xe47353d597e4e4b7, 0x2725bb4e0227279c, 0x4132588273414119, 0x8b2c9d0ba78b8b16,
0xa7510153f6a7a7a6, 0x7dcf94fab27d7de9, 0x95dcfb374995956e, 0xd88e9fad56d8d847,
0xfb8b30eb70fbfbcb, 0xee2371c1cdeeee9f, 0x7cc791f8bb7c7ced, 0x6617e3cc71666685,
0xdda68ea77bdddd53, 0x17b84b2eaf17175c, 0x4702468e45474701, 0x9e84dc211a9e9e42,
0xca1ec589d4caca0f, 0x2d75995a582d2db4, 0xbf9179632ebfbfc6, 0x07381b0e3f07071c,
0xad012347acadad8e, 0x5aea2fb4b05a5a75, 0x836cb51bef838336, 0x3385ff66b63333cc,
0x633ff2c65c636391, 0x02100a0412020208, 0xaa39384993aaaa92, 0x71afa8e2de7171d9,
0xc80ecf8dc6c8c807, 0x19c87d32d1191964, 0x497270923b494939, 0xd9869aaf5fd9d943,
0xf2c31df931f2f2ef, 0xe34b48dba8e3e3ab, 0x5be22ab6b95b5b71, 0x8834920dbc88881a,
0x9aa4c8293e9a9a52, 0x262dbe4c0b262698, 0x328dfa64bf3232c8, 0xb0e94a7d59b0b0fa,
0xe91b6acff2e9e983, 0x0f78331e770f0f3c, 0xd5e6a6b733d5d573, 0x8074ba1df480803a,
0xbe997c6127bebec2, 0xcd26de87ebcdcd13, 0x34bde468893434d0, 0x487a75903248483d,
0xffab24e354ffffdb, 0x7af78ff48d7a7af5, 0x90f4ea3d6490907a, 0x5fc23ebe9d5f5f61,
0x201da0403d202080, 0x6867d5d00f6868bd, 0x1ad07234ca1a1a68, 0xae192c41b7aeae82,
0xb4c95e757db4b4ea, 0x549a19a8ce54544d, 0x93ece53b7f939376, 0x220daa442f222288,
0x6407e9c86364648d, 0xf1db12ff2af1f1e3, 0x73bfa2e6cc7373d1, 0x12905a2482121248,
0x403a5d807a40401d, 0x0840281048080820, 0xc356e89b95c3c32b, 0xec337bc5dfecec97,
0xdb9690ab4ddbdb4b, 0xa1611f5fc0a1a1be, 0x8d1c8307918d8d0e, 0x3df5c97ac83d3df4,
0x97ccf1335b979766, 0x0000000000000000, 0xcf36d483f9cfcf1b, 0x2b4587566e2b2bac,
0x7697b3ece17676c5, 0x8264b019e6828232, 0xd6fea9b128d6d67f, 0x1bd87736c31b1b6c,
0xb5c15b7774b5b5ee, 0xaf112943beafaf86, 0x6a77dfd41d6a6ab5, 0x50ba0da0ea50505d,
0x45124c8a57454509, 0xf3cb18fb38f3f3eb, 0x309df060ad3030c0, 0xef2b74c3c4efef9b,
0x3fe5c37eda3f3ffc, 0x55921caac7555549, 0xa2791059dba2a2b2, 0xea0365c9e9eaea8f,
0x650fecca6a656589, 0xbab9686903babad2, 0x2f65935e4a2f2fbc, 0xc04ee79d8ec0c027,
0xdebe81a160dede5f, 0x1ce06c38fc1c1c70, 0xfdbb2ee746fdfdd3, 0x4d52649a1f4d4d29,
0x92e4e03976929272, 0x758fbceafa7575c9, 0x06301e0c36060618, 0x8a249809ae8a8a12,
0xb2f940794bb2b2f2, 0xe66359d185e6e6bf, 0x0e70361c7e0e0e38, 0x1ff8633ee71f1f7c,
0x6237f7c455626295, 0xd4eea3b53ad4d477, 0xa829324d81a8a89a, 0x96c4f43152969662,
0xf99b3aef62f9f9c3, 0xc566f697a3c5c533, 0x2535b14a10252594, 0x59f220b2ab595979,
0x8454ae15d084842a, 0x72b7a7e4c57272d5, 0x39d5dd72ec3939e4, 0x4c5a6198164c4c2d,
0x5eca3bbc945e5e65, 0x78e785f09f7878fd, 0x38ddd870e53838e0, 0x8c148605988c8c0a,
0xd1c6b2bf17d1d163, 0xa5410b57e4a5a5ae, 0xe2434dd9a1e2e2af, 0x612ff8c24e616199,
0xb3f1457b42b3b3f6, 0x2115a54234212184, 0x9c94d625089c9c4a, 0x1ef0663cee1e1e78,
0x4322528661434311, 0xc776fc93b1c7c73b, 0xfcb32be54ffcfcd7, 0x0420140824040410,
0x51b208a2e3515159, 0x99bcc72f2599995e, 0x6d4fc4da226d6da9, 0x0d68391a650d0d34,
0xfa8335e979fafacf, 0xdfb684a369dfdf5b, 0x7ed79bfca97e7ee5, 0x243db44819242490,
0x3bc5d776fe3b3bec, 0xab313d4b9aabab96, 0xce3ed181f0cece1f, 0x1188552299111144,
0x8f0c8903838f8f06, 0x4e4a6b9c044e4e25, 0xb7d1517366b7b7e6, 0xeb0b60cbe0ebeb8b,
0x3cfdcc78c13c3cf0, 0x817cbf1ffd81813e, 0x94d4fe354094946a, 0xf7eb0cf31cf7f7fb,
0xb9a1676f18b9b9de, 0x13985f268b13134c, 0x2c7d9c58512c2cb0, 0xd3d6b8bb05d3d36b,
0xe76b5cd38ce7e7bb, 0x6e57cbdc396e6ea5, 0xc46ef395aac4c437, 0x03180f061b03030c,
0x568a13acdc565645, 0x441a49885e44440d, 0x7fdf9efea07f7fe1, 0xa921374f88a9a99e,
0x2a4d8254672a2aa8, 0xbbb16d6b0abbbbd6, 0xc146e29f87c1c123, 0x53a202a6f1535351,
0xdcae8ba572dcdc57, 0x0b582716530b0b2c, 0x9d9cd327019d9d4e, 0x6c47c1d82b6c6cad,
0x3195f562a43131c4, 0x7487b9e8f37474cd, 0xf6e309f115f6f6ff, 0x460a438c4c464605,
0xac092645a5acac8a, 0x893c970fb589891e, 0x14a04428b4141450, 0xe15b42dfbae1e1a3,
0x16b04e2ca6161658, 0x3acdd274f73a3ae8, 0x696fd0d2066969b9, 0x09482d1241090924,
0x70a7ade0d77070dd, 0xb6d954716fb6b6e2, 0xd0ceb7bd1ed0d067, 0xed3b7ec7d6eded93,
0xcc2edb85e2cccc17, 0x422a578468424215, 0x98b4c22d2c98985a, 0xa4490e55eda4a4aa,
0x285d8850752828a0, 0x5cda31b8865c5c6d, 0xf8933fed6bf8f8c7, 0x8644a411c2868622,
// C6
0x6018c07830d81818, 0x8c2305af46262323, 0x3fc67ef991b8c6c6, 0x87e8136fcdfbe8e8,
0x26874ca113cb8787, 0xdab8a9626d11b8b8, 0x0401080502090101, 0x214f426e9e0d4f4f,
0xd836adee6c9b3636, 0xa2a6590451ffa6a6, 0x6fd2debdb90cd2d2, 0xf3f5fb06f70ef5f5,
0xf979ef80f2967979, 0xa16f5fcede306f6f, 0x7e91fcef3f6d9191, 0x5552aa07a4f85252,
0x9d6027fdc0476060, 0xcabc89766535bcbc, 0x569baccd2b379b9b, 0x028e048c018a8e8e,
0xb6a371155bd2a3a3, 0x300c603c186c0c0c, 0xf17bff8af6847b7b, 0xd435b5e16a803535,
0x741de8693af51d1d, 0xa7e05347ddb3e0e0, 0x7bd7f6acb321d7d7, 0x2fc25eed999cc2c2,
0xb82e6d965c432e2e, 0x314b627a96294b4b, 0xdffea321e15dfefe, 0x41578216aed55757,
0x5415a8412abd1515, 0xc1779fb6eee87777, 0xdc37a5eb6e923737, 0xb3e57b56d79ee5e5,
0x469f8cd923139f9f, 0xe7f0d317fd23f0f0, 0x354a6a7f94204a4a, 0x4fda9e95a944dada,
0x7d58fa25b0a25858, 0x03c906ca8fcfc9c9, 0xa429558d527c2929, 0x280a5022145a0a0a,
0xfeb1e14f7f50b1b1, 0xbaa0691a5dc9a0a0, 0xb16b7fdad6146b6b, 0x2e855cab17d98585,
0xcebd8173673cbdbd, 0x695dd234ba8f5d5d, 0x4010805020901010, 0xf7f4f303f507f4f4,
0x0bcb16c08bddcbcb, 0xf83eedc67cd33e3e, 0x140528110a2d0505, 0x81671fe6ce786767,
0xb7e47353d597e4e4, 0x9c2725bb4e022727, 0x1941325882734141, 0x168b2c9d0ba78b8b,
0xa6a7510153f6a7a7, 0xe97dcf94fab27d7d, 0x6e95dcfb37499595, 0x47d88e9fad56d8d8,
0xcbfb8b30eb70fbfb, 0x9fee2371c1cdeeee, 0xed7cc791f8bb7c7c, 0x856617e3cc716666,
0x53dda68ea77bdddd, 0x5c17b84b2eaf1717, 0x014702468e454747, 0x429e84dc211a9e9e,
0x0fca1ec589d4caca, 0xb42d75995a582d2d, 0xc6bf9179632ebfbf, 0x1c07381b0e3f0707,
0x8ead012347acadad, 0x755aea2fb4b05a5a, 0x36836cb51bef8383, 0xcc3385ff66b63333,
0x91633ff2c65c6363, 0x0802100a04120202, 0x92aa39384993aaaa, 0xd971afa8e2de7171,
0x07c80ecf8dc6c8c8, 0x6419c87d32d11919, 0x39497270923b4949, 0x43d9869aaf5fd9d9,
0xeff2c31df931f2f2, 0xabe34b48dba8e3e3, 0x715be22ab6b95b5b, 0x1a8834920dbc8888,
0x529aa4c8293e9a9a, 0x98262dbe4c0b2626, 0xc8328dfa64bf3232, 0xfab0e94a7d59b0b0,
0x83e91b6acff2e9e9, 0x3c0f78331e770f0f, 0x73d5e6a6b733d5d5, 0x3a8074ba1df48080,
0xc2be997c6127bebe, 0x13cd26de87ebcdcd, 0xd034bde468893434, 0x3d487a7590324848,
0xdbffab24e354ffff, 0xf57af78ff48d7a7a, 0x7a90f4ea3d649090, 0x615fc23ebe9d5f5f,
0x80201da0403d2020, 0xbd6867d5d00f6868, 0x681ad07234ca1a1a, 0x82ae192c41b7aeae,
0xeab4c95e757db4b4, 0x4d549a19a8ce5454, 0x7693ece53b7f9393, 0x88220daa442f2222,
0x8d6407e9c8636464, 0xe3f1db12ff2af1f1, 0xd173bfa2e6cc7373, 0x4812905a24821212,
0x1d403a5d807a4040, 0x2008402810480808, 0x2bc356e89b95c3c3, 0x97ec337bc5dfecec,
0x4bdb9690ab4ddbdb, 0xbea1611f5fc0a1a1, 0x0e8d1c8307918d8d, 0xf43df5c97ac83d3d,
0x6697ccf1335b9797, 0x0000000000000000, 0x1bcf36d483f9cfcf, 0xac2b4587566e2b2b,
0xc57697b3ece17676, 0x328264b019e68282, 0x7fd6fea9b128d6d6, 0x6c1bd87736c31b1b,
0xeeb5c15b7774b5b5, 0x86af112943beafaf, 0xb56a77dfd41d6a6a, 0x5d50ba0da0ea5050,
0x0945124c8a574545, 0xebf3cb18fb38f3f3, 0xc0309df060ad3030, 0x9bef2b74c3c4efef,
0xfc3fe5c37eda3f3f, 0x4955921caac75555, 0xb2a2791059dba2a2, 0x8fea0365c9e9eaea,
0x89650fecca6a6565, 0xd2bab9686903baba, 0xbc2f65935e4a2f2f, 0x27c04ee79d8ec0c0,
0x5fdebe81a160dede, 0x701ce06c38fc1c1c, 0xd3fdbb2ee746fdfd, 0x294d52649a1f4d4d,
0x7292e4e039769292, 0xc9758fbceafa7575, 0x1806301e0c360606, 0x128a249809ae8a8a,
0xf2b2f940794bb2b2, 0xbfe66359d185e6e6, 0x380e70361c7e0e0e, 0x7c1ff8633ee71f1f,
0x956237f7c4556262, 0x77d4eea3b53ad4d4, 0x9aa829324d81a8a8, 0x6296c4f431529696,
0xc3f99b3aef62f9f9, 0x33c566f697a3c5c5, 0x942535b14a102525, 0x7959f220b2ab5959,
0x2a8454ae15d08484, 0xd572b7a7e4c57272, 0xe439d5dd72ec3939, 0x2d4c5a6198164c4c,
0x655eca3bbc945e5e, 0xfd78e785f09f7878, 0xe038ddd870e53838, 0x0a8c148605988c8c,
0x63d1c6b2bf17d1d1, 0xaea5410b57e4a5a5, 0xafe2434dd9a1e2e2, 0x99612ff8c24e6161,
0xf6b3f1457b42b3b3, 0x842115a542342121, 0x4a9c94d625089c9c, 0x781ef0663cee1e1e,
0x1143225286614343, 0x3bc776fc93b1c7c7, 0xd7fcb32be54ffcfc, 0x1004201408240404,
0x5951b208a2e35151, 0x5e99bcc72f259999, 0xa96d4fc4da226d6d, 0x340d68391a650d0d,
0xcffa8335e979fafa, 0x5bdfb684a369dfdf, 0xe57ed79bfca97e7e, 0x90243db448192424,
0xec3bc5d776fe3b3b, 0x96ab313d4b9aabab, 0x1fce3ed181f0cece, 0x4411885522991111,
0x068f0c8903838f8f, 0x254e4a6b9c044e4e, 0xe6b7d1517366b7b7, 0x8beb0b60cbe0ebeb,
0xf03cfdcc78c13c3c, 0x3e817cbf1ffd8181, 0x6a94d4fe35409494, 0xfbf7eb0cf31cf7f7,
0xdeb9a1676f18b9b9, 0x4c13985f268b1313, 0xb02c7d9c58512c2c, 0x6bd3d6b8bb05d3d3,
0xbbe76b5cd38ce7e7, 0xa56e57cbdc396e6e, 0x37c46ef395aac4c4, 0x0c03180f061b0303,
0x45568a13acdc5656, 0x0d441a49885e4444, 0xe17fdf9efea07f7f, 0x9ea921374f88a9a9,
0xa82a4d8254672a2a, 0xd6bbb16d6b0abbbb, 0x23c146e29f87c1c1, 0x5153a202a6f15353,
0x57dcae8ba572dcdc, 0x2c0b582716530b0b, 0x4e9d9cd327019d9d, 0xad6c47c1d82b6c6c,
0xc43195f562a43131, 0xcd7487b9e8f37474, 0xfff6e309f115f6f6, 0x05460a438c4c4646,
0x8aac092645a5acac, 0x1e893c970fb58989, 0x5014a04428b41414, 0xa3e15b42dfbae1e1,
0x5816b04e2ca61616, 0xe83acdd274f73a3a, 0xb9696fd0d2066969, 0x2409482d12410909,
0xdd70a7ade0d77070, 0xe2b6d954716fb6b6, 0x67d0ceb7bd1ed0d0, 0x93ed3b7ec7d6eded,
0x17cc2edb85e2cccc, 0x15422a5784684242, 0x5a98b4c22d2c9898, 0xaaa4490e55eda4a4,
0xa0285d8850752828, 0x6d5cda31b8865c5c, 0xc7f8933fed6bf8f8, 0x228644a411c28686,
// C7
0x186018c07830d818, 0x238c2305af462623, 0xc63fc67ef991b8c6, 0xe887e8136fcdfbe8,
0x8726874ca113cb87, 0xb8dab8a9626d11b8, 0x0104010805020901, 0x4f214f426e9e0d4f,
0x36d836adee6c9b36, 0xa6a2a6590451ffa6, 0xd26fd2debdb90cd2, 0xf5f3f5fb06f70ef5,
0x79f979ef80f29679, 0x6fa16f5fcede306f, 0x917e91fcef3f6d91, 0x525552aa07a4f852,
0x609d6027fdc04760, 0xbccabc89766535bc, 0x9b569baccd2b379b, 0x8e028e048c018a8e,
0xa3b6a371155bd2a3, 0x0c300c603c186c0c, 0x7bf17bff8af6847b, 0x35d435b5e16a8035,
0x1d741de8693af51d, 0xe0a7e05347ddb3e0, 0xd77bd7f6acb321d7, 0xc22fc25eed999cc2,
0x2eb82e6d965c432e, 0x4b314b627a96294b, 0xfedffea321e15dfe, 0x5741578216aed557,
0x155415a8412abd15, 0x77c1779fb6eee877, 0x37dc37a5eb6e9237, 0xe5b3e57b56d79ee5,
0x9f469f8cd923139f, 0xf0e7f0d317fd23f0, 0x4a354a6a7f94204a, 0xda4fda9e95a944da,
0x587d58fa25b0a258, 0xc903c906ca8fcfc9, 0x29a429558d527c29, 0x0a280a5022145a0a,
0xb1feb1e14f7f50b1, 0xa0baa0691a5dc9a0, 0x6bb16b7fdad6146b, 0x852e855cab17d985,
0xbdcebd8173673cbd, 0x5d695dd234ba8f5d, 0x1040108050209010, 0xf4f7f4f303f507f4,
0xcb0bcb16c08bddcb, 0x3ef83eedc67cd33e, 0x05140528110a2d05, 0x6781671fe6ce7867,
0xe4b7e47353d597e4, 0x279c2725bb4e0227, 0x4119413258827341, 0x8b168b2c9d0ba78b,
0xa7a6a7510153f6a7, 0x7de97dcf94fab27d, 0x956e95dcfb374995, 0xd847d88e9fad56d8,
0xfbcbfb8b30eb70fb, 0xee9fee2371c1cdee, 0x7ced7cc791f8bb7c, 0x66856617e3cc7166,
0xdd53dda68ea77bdd, 0x175c17b84b2eaf17, 0x47014702468e4547, 0x9e429e84dc211a9e,
0xca0fca1ec589d4ca, 0x2db42d75995a582d, 0xbfc6bf9179632ebf, 0x071c07381b0e3f07,
0xad8ead012347acad, 0x5a755aea2fb4b05a, 0x8336836cb51bef83, 0x33cc3385ff66b633,
0x6391633ff2c65c63, 0x020802100a041202, 0xaa92aa39384993aa, 0x71d971afa8e2de71,
0xc807c80ecf8dc6c8, 0x196419c87d32d119, 0x4939497270923b49, 0xd943d9869aaf5fd9,
0xf2eff2c31df931f2, 0xe3abe34b48dba8e3, 0x5b715be22ab6b95b, 0x881a8834920dbc88,
0x9a529aa4c8293e9a, 0x2698262dbe4c0b26, 0x32c8328dfa64bf32, 0xb0fab0e94a7d59b0,
0xe983e91b6acff2e9, 0x0f3c0f78331e770f, 0xd573d5e6a6b733d5, 0x803a8074ba1df480,
0xbec2be997c6127be, 0xcd13cd26de87ebcd, 0x34d034bde4688934, 0x483d487a75903248,
0xffdbffab24e354ff, 0x7af57af78ff48d7a, 0x907a90f4ea3d6490, 0x5f615fc23ebe9d5f,
0x2080201da0403d20, 0x68bd6867d5d00f68, 0x1a681ad07234ca1a, 0xae82ae192c41b7ae,
0xb4eab4c95e757db4, 0x544d549a19a8ce54, 0x937693ece53b7f93, 0x2288220daa442f22,
0x648d6407e9c86364, 0xf1e3f1db12ff2af1, 0x73d173bfa2e6cc73, 0x124812905a248212,
0x401d403a5d807a40, 0x0820084028104808, 0xc32bc356e89b95c3, 0xec97ec337bc5dfec,
0xdb4bdb9690ab4ddb, 0xa1bea1611f5fc0a1, 0x8d0e8d1c8307918d, 0x3df43df5c97ac83d,
0x976697ccf1335b97, 0x0000000000000000, 0xcf1bcf36d483f9cf, 0x2bac2b4587566e2b,
0x76c57697b3ece176, 0x82328264b019e682, 0xd67fd6fea9b128d6, 0x1b6c1bd87736c31b,
0xb5eeb5c15b7774b5, 0xaf86af112943beaf, 0x6ab56a77dfd41d6a, 0x505d50ba0da0ea50,
0x450945124c8a5745, 0xf3ebf3cb18fb38f3, 0x30c0309df060ad30, 0xef9bef2b74c3c4ef,
0x3ffc3fe5c37eda3f, 0x554955921caac755, 0xa2b2a2791059dba2, 0xea8fea0365c9e9ea,
0x6589650fecca6a65, 0xbad2bab9686903ba, 0x2fbc2f65935e4a2f, 0xc027c04ee79d8ec0,
0xde5fdebe81a160de, 0x1c701ce06c38fc1c, 0xfdd3fdbb2ee746fd, 0x4d294d52649a1f4d,
0x927292e4e0397692, 0x75c9758fbceafa75, 0x061806301e0c3606, 0x8a128a249809ae8a,
0xb2f2b2f940794bb2, 0xe6bfe66359d185e6, 0x0e380e70361c7e0e, 0x1f7c1ff8633ee71f,
0x62956237f7c45562, 0xd477d4eea3b53ad4, 0xa89aa829324d81a8, 0x966296c4f4315296,
0xf9c3f99b3aef62f9, 0xc533c566f697a3c5, 0x25942535b14a1025, 0x597959f220b2ab59,
0x842a8454ae15d084, 0x72d572b7a7e4c572, 0x39e439d5dd72ec39, 0x4c2d4c5a6198164c,
0x5e655eca3bbc945e, 0x78fd78e785f09f78, 0x38e038ddd870e538, 0x8c0a8c148605988c,
0xd163d1c6b2bf17d1, 0xa5aea5410b57e4a5, 0xe2afe2434dd9a1e2, 0x6199612ff8c24e61,
0xb3f6b3f1457b42b3, 0x21842115a5423421, 0x9c4a9c94d625089c, 0x1e781ef0663cee1e,
0x4311432252866143, 0xc73bc776fc93b1c7, 0xfcd7fcb32be54ffc, 0x0410042014082404,
0x515951b208a2e351, 0x995e99bcc72f2599, 0x6da96d4fc4da226d, 0x0d340d68391a650d,
0xfacffa8335e979fa, 0xdf5bdfb684a369df, 0x7ee57ed79bfca97e, 0x2490243db4481924,
0x3bec3bc5d776fe3b, 0xab96ab313d4b9aab, 0xce1fce3ed181f0ce, 0x1144118855229911,
0x8f068f0c8903838f, 0x4e254e4a6b9c044e, 0xb7e6b7d1517366b7, 0xeb8beb0b60cbe0eb,
0x3cf03cfdcc78c13c, 0x813e817cbf1ffd81, 0x946a94d4fe354094, 0xf7fbf7eb0cf31cf7,
0xb9deb9a1676f18b9, 0x134c13985f268b13, 0x2cb02c7d9c58512c, 0xd36bd3d6b8bb05d3,
0xe7bbe76b5cd38ce7, 0x6ea56e57cbdc396e, 0xc437c46ef395aac4, 0x030c03180f061b03,
0x5645568a13acdc56, 0x440d441a49885e44, 0x7fe17fdf9efea07f, 0xa99ea921374f88a9,
0x2aa82a4d8254672a, 0xbbd6bbb16d6b0abb, 0xc123c146e29f87c1, 0x535153a202a6f153,
0xdc57dcae8ba572dc, 0x0b2c0b582716530b, 0x9d4e9d9cd327019d, 0x6cad6c47c1d82b6c,
0x31c43195f562a431, 0x74cd7487b9e8f374, 0xf6fff6e309f115f6, 0x4605460a438c4c46,
0xac8aac092645a5ac, 0x891e893c970fb589, 0x145014a04428b414, 0xe1a3e15b42dfbae1,
0x165816b04e2ca616, 0x3ae83acdd274f73a, 0x69b9696fd0d20669, 0x092409482d124109,
0x70dd70a7ade0d770, 0xb6e2b6d954716fb6, 0xd067d0ceb7bd1ed0, 0xed93ed3b7ec7d6ed,
0xcc17cc2edb85e2cc, 0x4215422a57846842, 0x985a98b4c22d2c98, 0xa4aaa4490e55eda4,
0x28a0285d88507528, 0x5c6d5cda31b8865c, 0xf8c7f8933fed6bf8, 0x86228644a411c286,
};

56
lib/std/hash/wyhash2.c3 Normal file
View File

@@ -0,0 +1,56 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// An implementation of Wang Yi's wyhash(2) algorithm in C3:
// https://github.com/wangyi-fudan/wyhash
//
module std::hash::wyhash2;
fn ulong wyr3(char* in, usz len) @inline
=> ((ulong)in[0] << 16) | ((ulong)in[len >> 1] << 8) | (ulong)in[len - 1];
// See: https://docs.google.com/spreadsheets/d/1HmqDj-suH4wBFNg7etwE8WVBlfCufvD5-gAnIENs94k/edit?gid=1915335726#gid=1915335726
// Credit to article:
// https://medium.com/@tprodanov/benchmarking-non-cryptographic-hash-functions-in-rust-2e6091077d11
//
// wyhash2 has a >90% chance of collisions when its input data is above 16 bytes in length.
// However, it is the fastest performing and most evenly randomized hash for very low-length inputs,
// making it an ideal candidate for hashing primitive data types quickly and making things like hash
// tables even faster. Therefore, a 16-byte input limit is imposed on all calls to the hash function.
//
<*
@require input.len <= 16 : `wyhash2 is not useable for inputs over 16 bytes in length.`
*>
fn ulong hash(char[] input, ulong seed = 0)
{
seed ^= 0xa076_1d64_78bd_642f;
ulong a, b;
if (@likely(input.len <= 8)) // more likely to encounter 8-byte or lower type here
{
if (@likely(input.len >= 4))
{
a = (ulong)@unaligned_load(*(uint*)input.ptr, 1); // first 4 bytes widened to a u64
b = (ulong)@unaligned_load(*(uint*)&input[^4], 1); // a walking 4-byte window based on input.len
}
else if (input.len > 0)
{
a = wyr3(input, input.len);
}
}
else
{
a = @unaligned_load(*(ulong*)input.ptr, 1); // first 8 bytes
b = @unaligned_load(*(ulong*)&input[^8], 1); // a walking 8-byte window based on input.len
}
uint128 r = ((uint128)a ^ 0xe703_7ed1_a0b4_28db) * ((uint128)b ^ seed);
ulong pre_res = (ulong)r ^ (ulong)(r >> 64);
r = ((uint128)0xe703_7ed1_a0b4_28db ^ input.len) * (uint128)pre_res;
return (ulong)r ^ (ulong)(r >> 64);
}

92
lib/std/io/file_mmap.c3 Normal file
View File

@@ -0,0 +1,92 @@
module std::io::file::mmap @if(env::LIBC &&& env::POSIX);
import std::core::mem::vm, std::io::file;
struct FileMmap
{
File file;
VirtualMemory vm;
usz offset;
usz len;
}
<*
Provides a slice of bytes to the expected mapped range discarding the extra bytes due to misaligment of offset and/or size.
@return "Slice of the mapped range where the first byte matches the file's byte at the offset specified to File::file_mmap()"
*>
fn char[] FileMmap.bytes(&self)
{
return self.vm.ptr[self.offset:self.len];
}
<*
Destroys the underlyng VirtualMemory object ie. calls munmap()"
*>
fn void? FileMmap.destroy(&self) @maydiscard
{
fault err1 = @catch(self.file.close());
fault err2 = @catch(self.vm.destroy());
if (err1) return err1?;
if (err2) return err2?;
}
module std::io::file @if(env::LIBC &&& env::POSIX);
<*
Maps a region of an already-opened file into memory
@param file : "Already opened file created on the caller scope"
@param offset : "Byte offset in file, will be rounded down to page size"
@param len : "Size in bytes to map starting from offset, will be rounded up to page size"
@return? mem::OUT_OF_MEMORY, vm::ACCESS_DENIED, vm::RANGE_OVERFLOW, vm::INVALID_ARGS, vm::UNKNOWN_ERROR, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
@return "Memory mapped region. Must be released with FileMmap.destroy(). Provided File will not be closed"
*>
fn mmap::FileMmap? mmap_file(File file, usz offset = 0, usz len = 0, vm::VirtualMemoryAccess access = READ, bool shared = false)
{
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;
}
// get the page size
usz page_size = vm::aligned_alloc_size(0);
// align the offset specified by the user (might be not aligned)
usz page_offset = offset & (page_size - 1);
usz map_offset = offset - page_offset;
// adjust map length (both the region start and the region end might be not aligned)
usz map_len = len + page_offset; // when region start not aligned
map_len = vm::aligned_alloc_size(map_len); // when region end not aligned
void* ptr = vm::mmap_file(file.fd(), map_len, map_offset, access, shared)!;
// FileMmap does not own the supplied file
return {{}, {ptr, map_len, access}, page_offset, len};
}
<*
Maps a region of the given file into memory
@param filename : "File path"
@param mode : "File opening mode"
@param offset : "Byte offset in file, will be rounded down to page size"
@param len : "Size in bytes to map starting from offset, will be rounded up to page size"
@return? mem::OUT_OF_MEMORY, vm::ACCESS_DENIED, vm::RANGE_OVERFLOW, vm::INVALID_ARGS, vm::UNKNOWN_ERROR, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
@return "Memory mapped region. Must be released with FileMmap.destroy()"
*>
fn mmap::FileMmap? mmap_open(String filename, String mode, usz offset = 0, usz len = 0, vm::VirtualMemoryAccess access = READ, bool shared = false)
{
File file = open(filename, mode)!;
defer catch (void)file.close();
FileMmap mm = mmap_file(file, offset, len, access, shared)!;
// FileMmap owns the file and it will close it on destroy()
mm.file = file;
return mm;
}

View File

@@ -69,7 +69,6 @@ struct Formatter
PrintFlags flags;
uint width;
uint prec;
usz idx;
fault first_fault;
}
}
@@ -147,10 +146,14 @@ fn usz? Formatter.out_str(&self, any arg) @private
case VOID:
return self.out_substr("void");
case FAULT:
return self.out_substr((*(fault*)arg.ptr).nameof);
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:
return self.out_str(*(any*)arg);
any a = *(any*)arg;
return a ? self.out_str(a) : self.out_substr("(empty-any)");
case OPTIONAL:
unreachable();
case SIGNED_INT:
@@ -495,6 +498,11 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
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;
}
@@ -566,5 +574,5 @@ fn usz? Formatter.print(&self, String str)
self.out_fn = &out_null_fn;
}
foreach (c : str) self.out(c)!;
return self.idx;
return str.len;
}

View File

@@ -660,7 +660,6 @@ fn usz? Formatter.out_char(&self, any arg) @private
fn usz? Formatter.out_reverse(&self, char[] buf) @private
{
usz n;
usz buffer_start_idx = self.idx;
usz len = buf.len;
// pad spaces up to given width
if (!self.flags.zeropad && !self.flags.left)

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021-2022 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::io;
@@ -16,6 +16,7 @@ faultdef
BUSY,
CANNOT_READ_DIR,
DIR_NOT_EMPTY,
PARENT_DIR_MISSING,
EOF,
FILE_CANNOT_DELETE,
FILE_IS_DIR,
@@ -49,41 +50,23 @@ faultdef
"\r" will be filtered from the String.
@param stream : `The stream to read from.`
@require @is_instream(stream) : `The stream must implement InStream.`
@require !($defined(&stream) &&& @is_instream(&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())
{
bool $is_stream = @typeis(stream, InStream);
$if $is_stream:
$typeof(&stream.read_byte) func = &stream.read_byte;
char val = func((void*)stream)!;
$else
char val = stream.read_byte()!;
$endif
if (val == '\n') return "";
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);
if (val != '\r') str.append(val);
while (1)
{
$if $is_stream:
char? c = func((void*)stream);
$else
char? c = stream.read_byte();
$endif
if (catch err = c)
{
if (err == io::EOF) break;
return err?;
}
if (c == '\r') continue;
if (c == '\n') break;
str.append_char(c);
}
readline_to_stream(&str, stream)!;
return str.copy_str(allocator);
};
}
@@ -93,6 +76,7 @@ macro String? readline(Allocator allocator, stream = io::stdin())
on the temporary allocator and does not need to be freed.
@param stream : `The stream to read from.`
@require !($defined(&stream) &&& @is_instream(&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.`
*>
@@ -101,6 +85,65 @@ macro String? treadline(stream = io::stdin())
return readline(tmem, stream) @inline;
}
<*
Reads a string, see `readline`, the data is passed to an outstream
@param out_stream : `The stream to write to`
@param in_stream : `The stream to read from.`
@require !($defined(&in_stream) &&& @is_instream(&in_stream)) : "The value for 'in_stream' should have been passed as a pointer and not as a value, please add '&'."
@require !($defined(&out_stream) &&& @is_outstream(&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`
*>
macro usz? readline_to_stream(out_stream, in_stream = io::stdin())
{
bool $is_stream = @typeis(in_stream, InStream);
$if $is_stream:
var func = &in_stream.read_byte;
char val = func((void*)in_stream)!;
$else
char val = in_stream.read_byte()!;
$endif
bool $is_out_stream = @typeis(out_stream, OutStream);
$if $is_out_stream:
var out_func = &out_stream.write_byte;
$endif
if (val == '\n') return 0;
usz len;
if (val != '\r')
{
$if $is_out_stream:
out_func((void*)out_stream.ptr, val)!;
$else
out_stream.write_byte(val)!;
$endif
len++;
}
while (1)
{
$if $is_stream:
char? c = func((void*)in_stream);
$else
char? c = in_stream.read_byte();
$endif
if (catch err = c)
{
if (err == io::EOF) break;
return err?;
}
if (c == '\r') continue;
if (c == '\n') break;
$if $is_out_stream:
out_func((void*)out_stream.ptr, c)!;
$else
out_stream.write_byte(c)!;
$endif
len++;
}
return len;
}
<*
Print a value to a stream.
@@ -117,7 +160,7 @@ macro usz? fprint(out, x)
$case ZString: return out.write(x.str_view());
$case DString: return out.write(x.str_view());
$default:
$if $assignable(x, String):
$if @assignable_to(x, String):
return out.write((String)x);
$else
$if is_struct_with_default_print($Type):
@@ -211,7 +254,7 @@ macro void eprint(x)
@param x : "The value to print"
*>
macro void eprintn(x)
macro void eprintn(x = "")
{
(void)fprintn(io::stderr(), x);
}

View File

@@ -3,28 +3,28 @@ import std::io::path, libc, std::os;
macro void? native_chdir(Path path)
{
$switch:
$case env::POSIX:
if (posix::chdir(path.as_zstr()))
{
switch (libc::errno())
@pool()
{
$switch:
$case env::POSIX:
if (posix::chdir(path.str_view().zstr_tcopy()))
{
case errno::EACCES: return io::NO_PERMISSION?;
case errno::ENAMETOOLONG: return io::NAME_TOO_LONG?;
case errno::ENOTDIR: return io::FILE_NOT_DIR?;
case errno::ENOENT: return io::FILE_NOT_FOUND?;
case errno::ELOOP: return io::SYMLINK_FAILED?;
default: return io::GENERAL_ERROR?;
switch (libc::errno())
{
case errno::EACCES: return io::NO_PERMISSION?;
case errno::ENAMETOOLONG: return io::NAME_TOO_LONG?;
case errno::ENOTDIR: return io::FILE_NOT_DIR?;
case errno::ENOENT: return io::FILE_NOT_FOUND?;
case errno::ELOOP: return io::SYMLINK_FAILED?;
default: return io::GENERAL_ERROR?;
}
}
}
$case env::WIN32:
@pool()
{
$case env::WIN32:
// TODO improve with better error handling.
if (win32::setCurrentDirectoryW(path.str_view().to_temp_utf16()!!)) return;
};
return io::GENERAL_ERROR?;
$default:
return io::UNSUPPORTED_OPERATION?;
$endswitch
return io::GENERAL_ERROR?;
$default:
return io::UNSUPPORTED_OPERATION?;
$endswitch
};
}

View File

@@ -5,7 +5,11 @@ fn PathList? native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Al
{
PathList list;
list.init(allocator);
DIRPtr directory = posix::opendir(dir.str_view() ? dir.as_zstr() : (ZString)".");
DIRPtr directory @noinit;
@pool()
{
directory = posix::opendir(dir.str_view() ? dir.str_view().zstr_tcopy() : (ZString)".");
};
defer if (directory) posix::closedir(directory);
if (!directory) return (path::is_dir(dir) ? io::CANNOT_READ_DIR : io::FILE_NOT_DIR)?;
Posix_dirent* entry;

View File

@@ -7,9 +7,11 @@ import std::os::posix;
macro bool? native_mkdir(Path path, MkdirPermissions permissions)
{
@pool()
{
$switch:
$case env::POSIX:
if (!posix::mkdir(path.as_zstr(), permissions == NORMAL ? 0o777 : 0o700)) return true;
if (!posix::mkdir(path.str_view().zstr_tcopy(), permissions == NORMAL ? 0o777 : 0o700)) return true;
switch (libc::errno())
{
case errno::EACCES:
@@ -23,11 +25,11 @@ macro bool? native_mkdir(Path path, MkdirPermissions permissions)
case errno::EEXIST: return false;
case errno::ELOOP: return io::SYMLINK_FAILED?;
case errno::ENOTDIR: return io::FILE_NOT_FOUND?;
default: return io::GENERAL_ERROR?;
case errno::ENOENT: return io::PARENT_DIR_MISSING?;
default:
return io::GENERAL_ERROR?;
}
$case env::WIN32:
@pool()
{
// TODO security attributes
if (win32::createDirectoryW(path.str_view().to_temp_utf16()!!, null)) return true;
switch (win32::getLastError())
@@ -43,8 +45,8 @@ macro bool? native_mkdir(Path path, MkdirPermissions permissions)
default:
return io::GENERAL_ERROR?;
}
};
$default:
return io::UNSUPPORTED_OPERATION?;
$endswitch
};
}

View File

@@ -6,9 +6,11 @@ import std::os::posix;
macro bool? native_rmdir(Path path)
{
@pool()
{
$switch:
$case env::POSIX:
if (!posix::rmdir(path.as_zstr())) return true;
if (!posix::rmdir(path.str_view().zstr_tcopy())) return true;
switch (libc::errno())
{
case errno::EBUSY: return io::BUSY?;
@@ -24,25 +26,23 @@ macro bool? native_rmdir(Path path)
default: return io::GENERAL_ERROR?;
}
$case env::WIN32:
@pool()
if (win32::removeDirectoryW(path.str_view().to_temp_utf16()!!)) return true;
switch (win32::getLastError())
{
if (win32::removeDirectoryW(path.str_view().to_temp_utf16()!!)) return true;
switch (win32::getLastError())
{
case win32::ERROR_ACCESS_DENIED:
return io::NO_PERMISSION?;
case win32::ERROR_CURRENT_DIRECTORY:
return io::BUSY?;
case win32::ERROR_DIR_NOT_EMPTY:
return io::DIR_NOT_EMPTY?;
case win32::ERROR_DIRECTORY:
case win32::ERROR_PATH_NOT_FOUND:
return false;
default:
return io::GENERAL_ERROR?;
}
};
case win32::ERROR_ACCESS_DENIED:
return io::NO_PERMISSION?;
case win32::ERROR_CURRENT_DIRECTORY:
return io::BUSY?;
case win32::ERROR_DIR_NOT_EMPTY:
return io::DIR_NOT_EMPTY?;
case win32::ERROR_DIRECTORY:
case win32::ERROR_PATH_NOT_FOUND:
return false;
default:
return io::GENERAL_ERROR?;
}
$default:
return io::UNSUPPORTED_OPERATION?;
$endswitch
};
}

View File

@@ -6,30 +6,33 @@ import std::io, std::os, libc;
*>
fn void? native_rmtree(Path dir)
{
DIRPtr directory = posix::opendir(dir.as_zstr());
defer if (directory) posix::closedir(directory);
if (!directory) return path::is_dir(dir) ? io::CANNOT_READ_DIR? : io::FILE_NOT_DIR?;
Posix_dirent* entry;
while ((entry = posix::readdir(directory)))
@pool()
{
@pool()
DIRPtr directory = posix::opendir(dir.str_view().zstr_tcopy());
defer if (directory) posix::closedir(directory);
if (!directory) return path::is_dir(dir) ? io::CANNOT_READ_DIR? : io::FILE_NOT_DIR?;
Posix_dirent* entry;
while ((entry = posix::readdir(directory)))
{
String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue;
Path new_path = dir.tappend(name)!;
if (entry.d_type == posix::DT_DIR)
@pool()
{
native_rmtree(new_path)!;
continue;
}
if (libc::remove(new_path.as_zstr()))
{
// TODO improve
return io::GENERAL_ERROR?;
}
};
}
os::native_rmdir(dir)!;
String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue;
Path new_path = dir.tappend(name)!;
if (entry.d_type == posix::DT_DIR)
{
native_rmtree(new_path)!;
continue;
}
if (libc::remove(new_path.str_view().zstr_tcopy()))
{
// TODO improve
return io::GENERAL_ERROR?;
}
};
}
os::native_rmdir(dir)!;
};
}
module std::io::os @if(env::WIN32);

View File

@@ -93,7 +93,7 @@ enum MkdirPermissions
@param recursive : `If directories in between should be created if they're missing, defaults to false`
@param permissions : `The permissions to set on the directory`
*>
macro bool? mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
macro bool? mkdir(path, bool recursive = false, MkdirPermissions permissions = NORMAL)
{
$if @typeis(path, String):
@pool() { return _mkdir(temp(path), recursive, permissions); };
@@ -148,27 +148,20 @@ fn Path? new(Allocator allocator, String path, PathEnv path_env = DEFAULT_ENV)
@return? INVALID_PATH : `if the path was invalid`
*>
fn Path? temp(String path, PathEnv path_env = DEFAULT_ENV)
{
return new(tmem, path, path_env);
}
fn Path? temp(String path, PathEnv path_env = DEFAULT_ENV) => new(tmem, path, path_env);
fn Path? from_win32_wstring(Allocator allocator, WString path) => @pool()
fn Path? from_wstring(Allocator allocator, WString path) => @pool()
{
return path::new(allocator, string::tfrom_wstring(path)!);
}
fn Path? for_windows(Allocator allocator, String path)
{
return new(allocator, path, WIN32);
}
fn Path? from_win32_wstring(Allocator allocator, WString path) @deprecated("Use 'from_wstring' instead") => from_wstring(allocator, path);
fn Path? for_posix(Allocator allocator, String path)
{
return new(allocator, path, POSIX);
}
fn Path? for_windows(Allocator allocator, String path) => new(allocator, path, WIN32);
fn bool Path.equals(self, Path p2)
fn Path? for_posix(Allocator allocator, String path) => new(allocator, path, POSIX);
fn bool Path.equals(self, Path p2) @operator(==)
{
return self.env == p2.env && self.path_string == p2.path_string;
}
@@ -340,15 +333,9 @@ fn String Path.volume_name(self)
return self.path_string[:len];
}
fn Path? String.to_path(self, Allocator allocator)
{
return new(allocator, self);
}
fn Path? String.to_path(self, Allocator allocator) => new(allocator, self);
fn Path? String.to_tpath(self)
{
return new(tmem, self);
}
fn Path? String.to_tpath(self) => new(tmem, self);
fn usz? volume_name_len(String path, PathEnv path_env) @local
{
@@ -527,7 +514,7 @@ fn String? normalize(String path_str, PathEnv path_env = DEFAULT_ENV)
return path_str[:len];
}
fn ZString Path.as_zstr(self) => (ZString)self.path_string.ptr;
fn ZString Path.as_zstr(self) @deprecated => (ZString)self.path_string.ptr;
fn String Path.root_directory(self)
{
@@ -607,29 +594,17 @@ fn bool? traverse(Path path, TraverseCallback callback, any data)
return false;
}
fn String Path.str_view(self) @inline
{
return self.path_string;
}
fn String Path.str_view(self) @inline => self.path_string;
fn bool Path.has_suffix(self, String str)
{
return self.str_view().ends_with(str);
}
fn bool Path.has_suffix(self, String str) => self.str_view().ends_with(str);
<*
@require self.allocator != null : "This Path should never be freed"
*>
fn void Path.free(self)
{
allocator::free(self.allocator, self.path_string.ptr);
}
fn void Path.free(self) => allocator::free(self.allocator, self.path_string.ptr);
fn usz? Path.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.print(self.str_view());
}
fn usz? Path.to_format(&self, Formatter* formatter) @dynamic => formatter.print(self.str_view());
const bool[256] RESERVED_PATH_CHAR_POSIX = {
@@ -649,10 +624,7 @@ const bool[256] RESERVED_PATH_CHAR_WIN32 = {
['*'] = true,
};
macro bool is_reserved_win32_path_char(char c)
{
return RESERVED_PATH_CHAR_WIN32[c];
}
macro bool is_reserved_win32_path_char(char c) => RESERVED_PATH_CHAR_WIN32[c];
macro bool is_reserved_path_char(char c, PathEnv path_env = DEFAULT_ENV)
{

View File

@@ -39,12 +39,12 @@ fn usz? available(InStream s)
macro bool @is_instream(#expr)
{
return $assignable(#expr, InStream);
return @assignable_to(#expr, InStream);
}
macro bool @is_outstream(#expr)
{
return $assignable(#expr, OutStream);
return @assignable_to(#expr, OutStream);
}
<*

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module libc;
@@ -8,6 +8,9 @@ const int EXIT_FAILURE = 1;
const int EXIT_SUCCESS = 0;
const int RAND_MAX = 0x7fffffff;
alias WChar @if(env::WIN32) = Char16;
alias WChar @if(!env::WIN32) = Char32;
struct DivResult
{
CInt quot;
@@ -148,6 +151,7 @@ extern fn void qsort(void* base, usz items, usz size, CompareFunction compare);
extern fn CInt raise(CInt signal);
extern fn CInt rand();
extern fn isz read(Fd fd, void* buf, usz nbyte) @if(!env::WIN32);
extern fn isz readlink(ZString pathname, char* buf, int bufsize) @if(!env::WIN32);
extern fn void* realloc(void* ptr, usz size);
extern fn CInt remove(ZString filename);
extern fn CInt rename(ZString old_name, ZString new_name);

View File

@@ -4,7 +4,7 @@ import std::time;
<*
Return a "timespec" from a duration.
@require self >= 0
@require self >= time::NANO_DURATION_ZERO
*>
fn TimeSpec NanoDuration.to_timespec(self) @inline
{
@@ -16,7 +16,7 @@ fn TimeSpec NanoDuration.to_timespec(self) @inline
<*
Convert a duration to a timespec.
@require self >= 0
@require self >= time::DURATION_ZERO
*>
fn TimeSpec Duration.to_timespec(self) @inline
{
@@ -24,3 +24,11 @@ fn TimeSpec Duration.to_timespec(self) @inline
Time_t sec = (Time_t)(self / time::SEC);
return { .s = sec, .ns = ns };
}
<*
Convert a timestamp to a timespec.
*>
fn TimeSpec Time.to_timespec(self) @inline
{
return ((Duration)self).to_timespec();
}

View File

@@ -11,6 +11,11 @@ extern fn int* __errno() @if(env::ANDROID);
macro int errno() @if(env::ANDROID) => *__errno();
macro void errno_set(int err) @if(env::ANDROID) => *(__errno()) = err;
// OpenBSD
extern fn int* __errno() @if(env::OPENBSD);
macro int errno() @if(env::OPENBSD) => *__errno();
macro void errno_set(int err) @if(env::OPENBSD) => *(__errno()) = err;
// Darwin
extern fn int* __error() @if(env::DARWIN);
macro int errno() @if(env::DARWIN) => *__error();

View File

@@ -0,0 +1,60 @@
module libc @if(env::OPENBSD);
// Checked for x86_64
alias Blksize_t = int;
alias Nlink_t = $typefrom(env::X86_64 ? uint.typeid : CUInt.typeid);
alias Dev_t = int;
alias Ino_t = ulong;
alias Mode_t = uint;
alias Blkcnt_t = long;
alias Fflags_t = uint;
struct Stat @if(env::X86_64)
{
Mode_t st_mode;
Dev_t st_dev;
Ino_t st_ino;
Nlink_t st_nlink;
Uid_t st_uid;
Gid_t st_gid;
Dev_t st_rdev;
TimeSpec st_atime;
TimeSpec st_mtime;
TimeSpec st_ctime;
Off_t st_size;
Blkcnt_t st_blocks;
Blksize_t st_blksize;
Fflags_t st_flags;
uint st_gen;
ulong[10] st_spare;
}
// TODO: Investigate if this needs to be fixed
struct Stat @if(!env::X86_64)
{
Dev_t st_dev;
Ino_t st_ino;
Mode_t st_mode;
Nlink_t st_nlink;
Uid_t st_uid;
Gid_t st_gid;
Dev_t st_rdev;
CInt __pad1;
Off_t st_size;
Blksize_t st_blksize;
CInt __pad2;
Blkcnt_t st_blocks;
Time_t st_atime;
long st_atime_nsec;
Time_t st_mtime;
long st_mtime_nsec;
Time_t st_ctime;
long st_ctime_nsec;
CInt[2] __unused;
}
extern fn CInt stat(ZString path, Stat* stat);
extern fn CInt get_nprocs();
extern fn CInt get_nprocs_conf();

View File

@@ -43,7 +43,7 @@ macro Complex Complex.sub_each(self, Real b) => { .v = self.v - b };
macro Complex Complex.scale(self, Real r) @operator_s(*) => { .v = self.v * r };
macro Complex Complex.mul(self, Complex b)@operator(*) => { self.r * b.r - self.c * b.c, self.r * b.c + b.r * self.c };
macro Complex Complex.div_real(self, Real r) @operator(/) => { .v = self.v / r };
macro Complex Complex.div_real_inverse(Complex c, Real r) @operator_r(/) => ((Complex) { .r = self }).div(c);
macro Complex Complex.div_real_inverse(Complex c, Real r) @operator_r(/) => ((Complex) { .r = r }).div(c);
macro Complex Complex.div(self, Complex b) @operator(/)
{
Real div = b.v.dot(b.v);

View File

@@ -6,6 +6,8 @@ import std::math::complex;
import std::math::matrix;
import std::math::quaternion;
attrdef @MathLibc(name) = @extern(name), @link(env::POSIX, "m");
const E = 2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427427466;
const LOG2E = 1.44269504088896340735992468100189214; // log2(e)
const LOG10E = 0.434294481903251827651128918916605082; // log10(e)
@@ -128,7 +130,7 @@ macro atan2(x, y)
@require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value"
@require @typekind(sinp) == POINTER : "Expected sinp to be a pointer"
@require values::@is_same_type(sinp, cosp) : "Expected sinp and cosp to have the same type"
@require $assignable(x, $typeof(*sinp)) : "Expected x and sinp/cosp to have the same type"
@require @assignable_to(x, $typeof(*sinp)) : "Expected x and sinp/cosp to have the same type"
*>
macro sincos_ref(x, sinp, cosp)
{
@@ -342,6 +344,23 @@ macro log(x, base)
*>
macro log2(x) => $$log2(values::promote_int(x));
<*
@require values::@is_int($x) : `The input value must be an integer.`
@require $x >= 0 : `The input value must be a positive integer.`
@return `A floored base-2 log of an input integer value.`
*>
macro @intlog2($x)
{
$if $x <= 1:
return 0;
$endif
$typeof($x) $z = 0;
$for var $y = $x; $y > 0; $y >>= 1, ++$z: $endfor
return $z - 1;
}
<*
@require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector`
*>
@@ -394,7 +413,7 @@ macro nearbyint(x) => $$nearbyint(x);
<*
@require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector`
@require $assignable(exp, $typeof(values::promote_int(x))) || values::@is_int(exp) : `The input must be an integer, castable to the type of x`
@require @assignable_to(exp, $typeof(values::promote_int(x))) || values::@is_int(exp) : `The input must be an integer, castable to the type of x`
*>
macro pow(x, exp)
{
@@ -1012,33 +1031,33 @@ macro void float.set_word(float* f, uint u) => *f = bitcast(u, float);
macro double scalbn(double x, int n) => _scalbn(x, n);
extern fn double _atan(double x) @extern("atan");
extern fn float _atanf(float x) @extern("atanf");
extern fn double _atan2(double, double) @extern("atan2");
extern fn float _atan2f(float, float) @extern("atan2f");
extern fn double _atan(double x) @MathLibc("atan");
extern fn float _atanf(float x) @MathLibc("atanf");
extern fn double _atan2(double, double) @MathLibc("atan2");
extern fn float _atan2f(float, float) @MathLibc("atan2f");
extern fn void _sincos(double, double*, double*) @extern("__sincos") @link("m") @if(env::DARWIN);
extern fn void _sincosf(float, float*, float*) @extern("__sincosf") @link("m") @if(env::DARWIN);
extern fn void _sincos(double, double*, double*) @MathLibc("__sincos") @if(env::DARWIN);
extern fn void _sincosf(float, float*, float*) @MathLibc("__sincosf") @if(env::DARWIN);
extern fn void _sincos(double, double*, double*) @extern("sincos") @link("m") @if(!env::DARWIN && !env::WIN32);
extern fn void _sincosf(float, float*, float*) @extern("sincosf") @link("m") @if(!env::DARWIN && !env::WIN32);
extern fn void _sincos(double, double*, double*) @MathLibc("sincos") @if(!env::DARWIN && !env::WIN32);
extern fn void _sincosf(float, float*, float*) @MathLibc("sincosf") @if(!env::DARWIN && !env::WIN32);
fn void _sincos(double a, double* s, double* c) @extern("sincos") @if(env::WIN32) { *s = sin(a); *c = cos(a); }
fn void _sincosf(float a, float* s, float* c) @extern("sincosf") @if(env::WIN32) { *s = sin(a); *c = cos(a); }
extern fn double _tan(double x) @extern("tan");
extern fn float _tanf(float x) @extern("tanf");
extern fn double _scalbn(double x, int n) @extern("scalbn");
extern fn double _acos(double x) @extern("acos");
extern fn double _asin(double x) @extern("asin");
extern fn double _acosh(double x) @extern("acosh");
extern fn double _asinh(double x) @extern("asinh");
extern fn double _atanh(double x) @extern("atanh");
extern fn float _acosf(float x) @extern("acosf");
extern fn float _asinf(float x) @extern("asinf");
extern fn float _acoshf(float x) @extern("acoshf");
extern fn float _asinhf(float x) @extern("asinhf");
extern fn float _atanhf(float x) @extern("atanhf");
extern fn double _tan(double x) @MathLibc("tan");
extern fn float _tanf(float x) @MathLibc("tanf");
extern fn double _scalbn(double x, int n) @MathLibc("scalbn");
extern fn double _acos(double x) @MathLibc("acos");
extern fn double _asin(double x) @MathLibc("asin");
extern fn double _acosh(double x) @MathLibc("acosh");
extern fn double _asinh(double x) @MathLibc("asinh");
extern fn double _atanh(double x) @MathLibc("atanh");
extern fn float _acosf(float x) @MathLibc("acosf");
extern fn float _asinf(float x) @MathLibc("asinf");
extern fn float _acoshf(float x) @MathLibc("acoshf");
extern fn float _asinhf(float x) @MathLibc("asinhf");
extern fn float _atanhf(float x) @MathLibc("atanhf");
fn double _frexp(double x, int* e)

View File

@@ -117,7 +117,7 @@ macro bool next_bool(random)
*>
macro float next_float(random)
{
uint val = random.next_int() & (1 << 24 - 1);
uint val = random.next_int() & (1U << 24 - 1);
return val * 0x1.0p-24f;
}
@@ -133,7 +133,7 @@ macro double next_double(random)
}
// True if the value is a Random.
macro bool is_random(random) => $assignable(random, Random);
macro bool is_random(random) => @assignable_to(random, Random);
macro uint128 @long_to_int128(#function) => (uint128)#function << 64 + #function;
macro ulong @int_to_long(#function) => (ulong)#function << 32 + #function;

View File

@@ -1,5 +1,5 @@
module std::math::random;
import std::hash::fnv32a, std::time;
import std::hash::a5hash, std::time;
const ODD_PHI64 @local = 0x9e3779b97f4a7c15;
const MUL_MCG64 @local = 0xf1357aea2e62a9c5;
@@ -69,12 +69,11 @@ fn void seeder(char[] input, char[] out_buffer)
macro uint hash(value) @local
{
return fnv32a::hash(&&bitcast(value, char[$sizeof(value)]));
return (uint)a5hash::hash(&&bitcast(value, char[$sizeof(value)]));
}
fn char[8 * 4] entropy() @if(!env::WASM_NOLIBC)
{
void* addr = malloc(1);
free(addr);
static uint random_int;

View File

@@ -1,5 +1,5 @@
module std::net::os;
const bool SUPPORTS_INET = env::LIBC && (env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID);
const bool SUPPORTS_INET = env::LIBC && (env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID || env::OPENBSD);
typedef AIFamily = CInt;
typedef AIProtocol = CInt;
@@ -23,7 +23,7 @@ struct AddrInfo
ZString ai_canonname;
SockAddrPtr ai_addr;
}
struct @if(env::LINUX)
struct @if(env::LINUX || env::OPENBSD)
{
SockAddrPtr ai_addr;
ZString ai_canonname;
@@ -58,7 +58,7 @@ extern fn void freeaddrinfo(AddrInfo* res) @if(SUPPORTS_INET);
extern fn CInt setsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t optlen) @if(SUPPORTS_INET);
extern fn CInt getsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t optlen) @if(SUPPORTS_INET);
module std::net::os @if(!env::LIBC || !(env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID));
module std::net::os @if(!env::LIBC || !(env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID || env::OPENBSD));
const AIFamily PLATFORM_AF_INET6 = 0;
const AIFamily PLATFORM_AF_IPX = 0;

109
lib/std/net/os/openbsd.c3 Normal file
View File

@@ -0,0 +1,109 @@
module std::net::os @if(env::OPENBSD);
import libc;
// for most of this, see OpenBSD /usr/include/sys/socket.h
const AIFlags AI_EXT = 0x8;
const AIFlags AI_NUMERICSERV = 0x10;
const AIFlags AI_FQDN = 0x20;
const AIFlags AI_ADDRCONFIG = 0x40;
const AIFamily PLATFORM_AF_LOCAL = AF_UNIX; // draft POSIX compatibility
const AIFamily PLATFORM_AF_IMPLINK = 3; // arpanet imp addresses
const AIFamily PLATFORM_AF_PUP = 4; // pup protocols: e.g. BSP
const AIFamily PLATFORM_AF_CHAOS = 5; // mit CHAOS protocols
const AIFamily PLATFORM_AF_NS = 6; // XEROX NS protocols
const AIFamily PLATFORM_AF_ISO = 7; // ISO protocols
const AIFamily PLATFORM_AF_OSI = PLATFORM_AF_ISO;
const AIFamily PLATFORM_AF_ECMA = 8; // european computer manufacturers
const AIFamily PLATFORM_AF_DATAKIT = 9; // datakit protocols
const AIFamily PLATFORM_AF_CCITT = 10; // CCITT protocols, X.25 etc
const AIFamily PLATFORM_AF_SNA = 11; // IBM SNA
const AIFamily PLATFORM_AF_DECNET = 12; // DECnet
const AIFamily PLATFORM_AF_DLI = 13; // DEC Direct data link interface
const AIFamily PLATFORM_AF_LAT = 14; // LAT
const AIFamily PLATFORM_AF_HYLINK = 15; // NSC Hyperchannel
const AIFamily PLATFORM_AF_APPLETALK = 16; // Apple Talk
const AIFamily PLATFORM_AF_ROUTE = 17; // Internal Routing Protocol
const AIFamily PLATFORM_AF_LINK = 18; // Link layer interface
const AIFamily PLATFORM_PSEUDO_AF_XTP = 19; // eXpress Transfer Protocol (no AF)
const AIFamily PLATFORM_AF_COIP = 20; // connection-oriented IP, aka ST II
const AIFamily PLATFORM_AF_CNT = 21; // Computer Network Technology
const AIFamily PLATFORM_PSEUDO_AF_RTIP = 22; // Help Identify RTIP packets
const AIFamily PLATFORM_AF_IPX = 23; // Novell Internet Protocol
const AIFamily PLATFORM_AF_INET6 = 24; // IPv6
const AIFamily PLATFORM_PSEUDO_AF_PIP = 25; // Help Identify PIP packets
const AIFamily PLATFORM_AF_ISDN = 26; // Integrated Services Digital Network*/
const AIFamily PLATFORM_AF_E164 = PLATFORM_AF_ISDN; // CCITT E.164 recommendation
const AIFamily PLATFORM_AF_NATM = 27; // native ATM access
const AIFamily PLATFORM_AF_ENCAP = 28;
const AIFamily PLATFORM_AF_SIP = 29; // Simple Internet Protocol
const AIFamily PLATFORM_AF_KEY = 30;
const AIFamily PLATFORM_PSEUDO_AF_HDRCMPLT = 31; // Used by BPF to not rewrite headers in interface output routine
const AIFamily PLATFORM_AF_BLUETOOTH = 32; // Bluetooth
const AIFamily PLATFORM_AF_MPLS = 33; // MPLS
const AIFamily PLATFORM_PSEUDO_AF_PFLOW = 34; // pflow
const AIFamily PLATFORM_PSEUDO_AF_PIPEX = 35; // PIPEX
const AIFamily PLATFORM_AF_FRAME = 36; // frame (Ethernet) sockets
const AIFamily PLATFORM_AF_MAX = 37;
// see: https://github.com/openbsd/src/blob/1ca4dc792538d17b4c2bd71550f68070505fd7b9/sys/sys/socket.h#L157
const int SOL_SOCKET = 0xFFFF;
const int SO_DEBUG = 0x0001; // turn on debugging info recording
const int SO_ACCEPTCONN = 0x0002; // socket has had listen()
const int SO_REUSEADDR = 0x0004; // allow local address reuse
const int SO_KEEPALIVE = 0x0008; // keep connections alive
const int SO_DONTROUTE = 0x0010; // just use interface addresses
const int SO_BROADCAST = 0x0020; // permit sending of broadcast msgs
const int SO_USELOOPBACK = 0x0040; // bypass hardware when possible
const int SO_LINGER = 0x0080; // linger on close if data present
const int SO_OOBINLINE = 0x0100; // leave received OOB data in line
const int SO_REUSEPORT = 0x0200; // allow local address & port reuse
const int SO_TIMESTAMP = 0x0800; // timestamp received dgram traffic
const int SO_BINDANY = 0x1000; // allow bind to any address
const int SO_ZEROIZE = 0x2000; // zero out all mbufs sent over socket
// additional
const int SO_SNDBUF = 0x1001; // send buffer size
const int SO_RCVBUF = 0x1002; // receive buffer size
const int SO_SNDLOWAT = 0x1003; // send low-water mark
const int SO_RCVLOWAT = 0x1004; // receive low-water mark
const int SO_SNDTIMEO = 0x1005; // send timeout
const int SO_RCVTIMEO = 0x1006; // receive timeout
const int SO_ERROR = 0x1007; // get error status and clear
const int SO_TYPE = 0x1008; // get socket type
const int SO_NETPROC = 0x1020; // multiplex; network processing
const int SO_RTABLE = 0x1021; // routing table to be used
const int SO_PEERCRED = 0x1022; // get connect-time credentials
const int SO_SPLICE = 0x1023; // splice data to other socket
const int SO_DOMAIN = 0x1024; // get socket domain
const int SO_PROTOCOL = 0x1025; // get socket protocol
// POLLIN through POLLNVAL are predefined by lib/std/net/os/posix.c3
const CUShort POLLRDNORM = 0x0040;
const CUShort POLLNORM = POLLRDNORM;
const CUShort POLLWRNORM = POLLOUT;
const CUShort POLLRDBAND = 0x0080;
const CUShort POLLWRBAND = 0x0100;
const CInt MSG_OOB = 0x1; // process out-of-band data
const CInt MSG_PEEK = 0x2; // peek at incoming message
const CInt MSG_DONTROUTE = 0x4; // send without using routing tables
const CInt MSG_EOR = 0x8; // data completes record
const CInt MSG_TRUNC = 0x10; // data discarded before delivery
const CInt MSG_CTRUNC = 0x20; // control data lost before delivery
const CInt MSG_WAITALL = 0x40; // wait for full request or error
const CInt MSG_DONTWAIT = 0x80; // this message should be nonblocking
const CInt MSG_BCAST = 0x100; // this message rec'd as broadcast
const CInt MSG_MCAST = 0x200; // this message rec'd as multicast
const CInt MSG_NOSIGNAL = 0x400; // do not send SIGPIPE
const CInt MSG_CMSG_CLOEXEC = 0x800; // set FD_CLOEXEC on received fds
const CInt MSG_WAITFORONE = 0x1000; // nonblocking but wait for one msg
// socket creation options
const SOCK_CLOEXEC = 0x8000; // set FD_CLOEXEC
const SOCK_NONBLOCK = 0x4000; // set O_NONBLOCK
const SOCK_NONBLOCK_INHERIT = 0x2000; // inherit O_NONBLOCK from listener
const SOCK_DNS = 0x1000; // set SS_DNS
const PLATFORM_O_NONBLOCK = SOCK_NONBLOCK;

View File

@@ -23,7 +23,7 @@ macro void @loop_over_ai(AddrInfo* ai; @body(NativeSocket fd, AddrInfo* ai))
}
}
const Duration POLL_FOREVER = -1;
const Duration POLL_FOREVER = (Duration)-1;
typedef PollSubscribes = ushort;
typedef PollEvents = ushort;

View File

@@ -5,11 +5,11 @@ import std::time, libc;
typedef TcpSocket = inline Socket;
typedef TcpServerSocket = inline Socket;
fn TcpSocket? connect(String host, uint port, Duration timeout = 0, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn TcpSocket? connect(String host, uint port, Duration timeout = time::DURATION_ZERO, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
{
AddrInfo* ai = net::addrinfo(host, port, ip_protocol.ai_family, os::SOCK_STREAM)!;
defer os::freeaddrinfo(ai);
if (timeout > 0)
if (timeout > time::DURATION_ZERO)
{
return (TcpSocket)net::connect_with_timeout_from_addrinfo(ai, options, timeout)!;
}

View File

@@ -87,11 +87,12 @@ fn void*[] capture_current(void*[] buffer)
alias BacktraceList = List{Backtrace};
alias symbolize_backtrace @if(env::LINUX) = linux::symbolize_backtrace;
alias symbolize_backtrace @if(env::WIN32) = win32::symbolize_backtrace;
alias symbolize_backtrace @if(env::DARWIN) = darwin::symbolize_backtrace;
alias symbolize_backtrace @if(env::LINUX) = linux::symbolize_backtrace;
alias symbolize_backtrace @if(env::WIN32) = win32::symbolize_backtrace;
alias symbolize_backtrace @if(env::DARWIN) = darwin::symbolize_backtrace;
alias symbolize_backtrace @if(env::OPENBSD) = openbsd::symbolize_backtrace;
fn BacktraceList? symbolize_backtrace(Allocator allocator, void*[] backtrace) @if(!env::NATIVE_STACKTRACE)
{
return {};
}
}

View File

@@ -79,7 +79,7 @@ fn String? get_home_dir(Allocator allocator)
<*
Returns the current user's config directory.
Returns the current user's config directory.
*>
fn Path? get_config_dir(Allocator allocator) => @pool()
{
@@ -87,13 +87,15 @@ fn Path? get_config_dir(Allocator allocator) => @pool()
return path::new(allocator, tget_var("AppData"));
$else
$if env::DARWIN:
String s = tget_var("HOME")!;
String home_dir = tget_var("HOME")!;
const DIR = "Library/Application Support";
$else
String s = tget_var("XDG_CONFIG_HOME") ?? tget_var("HOME")!;
String? config_path = tget_var("XDG_CONFIG_HOME");
if (try config_path && config_path.len > 0) return path::new(allocator, config_path);
String home_dir = tget_var("HOME")!;
const DIR = ".config";
$endif
return path::temp(s).append(allocator, DIR);
return path::temp(home_dir).append(allocator, DIR);
$endif
}
@@ -115,10 +117,10 @@ fn bool clear_var(String name) => @pool()
$endswitch
}
fn String? executable_path(Allocator allocator)
fn String? executable_path()
{
$if env::DARWIN:
return darwin::executable_path(allocator);
return darwin::executable_path();
$else
return NOT_FOUND?;
$endif

View File

@@ -89,6 +89,35 @@ struct Linux_Dl_info
void* dli_saddr; /* Address of nearest symbol */
}
alias Dl_iterate_phdr_callback64 = fn CInt(Linux_dl_phdr_info_64*, usz, void*);
alias Dl_iterate_phdr_callback32 = fn CInt(Linux_dl_phdr_info_32*, usz, void*);
extern fn CInt dl_iterate_phdr64(Dl_iterate_phdr_callback64 callback, void* data);
extern fn CInt dl_iterate_phdr32(Dl_iterate_phdr_callback32 callback, void* data);
struct Linux_dl_phdr_info_64
{
Elf64_Addr dlpi_addr;
ZString dlpi_name;
Elf64_Phdr* dlpi_phdr;
Elf64_Half dlpi_phnum;
ulong dlpi_adds;
ulong dlpi_subs;
usz dlpi_tsl_modid;
void* dlpi_tls_data;
}
struct Linux_dl_phdr_info_32
{
Elf32_Addr dlpi_addr;
ZString dlpi_name;
Elf32_Phdr* dlpi_phdr;
Elf32_Half dlpi_phnum;
ulong dlpi_adds;
ulong dlpi_subs;
usz dlpi_tsl_modid;
void* dlpi_tls_data;
}
fn ulong? elf_module_image_base(String path) @local
{
File file = file::open(path, "rb")!;
@@ -130,70 +159,76 @@ fn ulong? elf_module_image_base(String path) @local
fn void? backtrace_add_from_exec(Allocator allocator, BacktraceList* list, void* addr) @local
{
char[] buf = mem::talloc_array(char, 1024);
String exec_path = process::execute_stdout_to_buffer(buf, {"realpath", "-e", string::tformat("/proc/%d/exe", posix::getpid())})!;
char[1024] buf @noinit;
String exec_path = process::execute_stdout_to_buffer(&buf, {"realpath", "-e", string::bformat(&&(char[64]){}, "/proc/%d/exe", posix::getpid())})!;
String obj_name = exec_path.copy(allocator);
String addr2line = process::execute_stdout_to_buffer(buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", exec_path, string::tformat("0x%x", addr)})!;
String addr2line = process::execute_stdout_to_buffer(&buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", exec_path, string::bformat(&&(char[64]){}, "0x%x", addr)})!;
return backtrace_add_addr2line(allocator, list, addr, addr2line, obj_name, "???");
}
fn void? backtrace_add_from_dlinfo(Allocator allocator, BacktraceList* list, void* addr, Linux_Dl_info* info) @local
{
char[] buf = mem::talloc_array(char, 1024);
char[1024] buf @noinit;
void* obj_addr = addr - (uptr)info.dli_fbase + (uptr)elf_module_image_base(info.dli_fname.str_view())!;
ZString obj_path = info.dli_fname;
String sname = info.dli_sname ? info.dli_sname.str_view() : "???";
String addr2line = process::execute_stdout_to_buffer(buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", obj_path.str_view(), string::tformat("0x%x", obj_addr - 1)})!;
String addr2line = process::execute_stdout_to_buffer(&buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", obj_path.str_view(), string::bformat(&&(char[64]){}, "0x%x", obj_addr - 1)})!;
return backtrace_add_addr2line(allocator, list, addr, addr2line, info.dli_fname.str_view(), sname);
}
fn Backtrace? backtrace_line_parse(Allocator allocator, String string, String obj_name, String func_name, bool is_inlined)
{
String[] parts = string.trim().tsplit(" at ");
if (parts.len != 2) return NOT_FOUND?;
uint line = 0;
String source = "";
if (!parts[1].contains("?") && parts[1].contains(":"))
@stack_mem(256; Allocator mem)
{
usz index = parts[1].rindex_of_char(':')!;
source = parts[1][:index];
line = parts[1][index + 1..].to_uint()!;
}
return {
.function = parts[0].copy(allocator),
.object_file = obj_name.copy(allocator),
.file = source.copy(allocator),
.line = line,
.allocator = allocator,
.is_inline = is_inlined
String[] parts = string.trim().split(mem, " at ");
if (parts.len != 2) return NOT_FOUND?;
uint line = 0;
String source = "";
if (!parts[1].contains("?") && parts[1].contains(":"))
{
usz index = parts[1].rindex_of_char(':')!;
source = parts[1][:index];
line = parts[1][index + 1..].to_uint()!;
}
return {
.function = parts[0].copy(allocator),
.object_file = obj_name.copy(allocator),
.file = source.copy(allocator),
.line = line,
.allocator = allocator,
.is_inline = is_inlined
};
};
}
fn void? backtrace_add_addr2line(Allocator allocator, BacktraceList* list, void* addr, String addr2line, String obj_name, String func_name) @local
{
String[] inline_parts = addr2line.tsplit("(inlined by)");
usz last = inline_parts.len - 1;
foreach (i, part : inline_parts)
@stack_mem(1024; Allocator mem)
{
bool is_inline = i != last;
Backtrace? trace = backtrace_line_parse(allocator, part, obj_name, func_name, is_inline);
if (catch trace)
String[] inline_parts = addr2line.split(mem, "(inlined by)");
usz last = inline_parts.len - 1;
foreach (i, part : inline_parts)
{
list.push({
.function = func_name.copy(allocator),
.object_file = obj_name.copy(allocator),
.offset = (uptr)addr,
.file = "".copy(allocator),
.line = 0,
.allocator = allocator,
.is_inline = is_inline
});
continue;
bool is_inline = i != last;
Backtrace? trace = backtrace_line_parse(allocator, part, obj_name, func_name, is_inline);
if (catch trace)
{
list.push({
.function = func_name.copy(allocator),
.object_file = obj_name.copy(allocator),
.offset = (uptr)addr,
.file = "".copy(allocator),
.line = 0,
.allocator = allocator,
.is_inline = is_inline
});
continue;
}
list.push(trace);
}
list.push(trace);
}
};
}
fn void? backtrace_add_element(Allocator allocator, BacktraceList *list, void* addr) @local
@@ -204,15 +239,12 @@ fn void? backtrace_add_element(Allocator allocator, BacktraceList *list, void* a
return;
}
@pool()
Linux_Dl_info info;
if (dladdr(addr, &info) == 0)
{
Linux_Dl_info info;
if (dladdr(addr, &info) == 0)
{
return backtrace_add_from_exec(allocator, list, addr);
}
return backtrace_add_from_dlinfo(allocator, list, addr, &info);
};
return backtrace_add_from_exec(allocator, list, addr);
}
return backtrace_add_from_dlinfo(allocator, list, addr, &info);
}
fn BacktraceList? symbolize_backtrace(Allocator allocator, void*[] backtrace)
@@ -227,12 +259,9 @@ fn BacktraceList? symbolize_backtrace(Allocator allocator, void*[] backtrace)
}
list.free();
}
@pool()
foreach (addr : backtrace)
{
foreach (addr : backtrace)
{
backtrace_add_element(allocator, &list, addr)!;
}
};
backtrace_add_element(allocator, &list, addr)!;
}
return list;
}

View File

@@ -79,19 +79,26 @@ alias Darwin_mach_timebase_info_data_t = Darwin_mach_timebase_info;
extern fn void mach_timebase_info(Darwin_mach_timebase_info_data_t* timebase);
extern fn ulong mach_absolute_time();
fn String? executable_path(Allocator allocator)
fn String? executable_path()
{
char[4096] path;
uint len = path.len;
if (darwin_NSGetExecutablePath(&path, &len) < 0) return NOT_FOUND?;
return ((ZString)&path).copy(allocator);
static char[4096] path;
static uint len = 0;
if (!len)
{
char[4096] buf;
uint temp_len = buf.len;
if (darwin_NSGetExecutablePath(&buf, &temp_len) < 0) return NOT_FOUND?;
path[:len] = buf[:len];
len = (int)((ZString)&buf).len();
}
return (String)path[:len];
}
fn uptr? load_address() @local
{
Darwin_segment_command_64* cmd = darwin::getsegbyname("__TEXT");
if (!cmd) return backtrace::SEGMENT_NOT_FOUND?;
String path = env::executable_path(tmem) ?? backtrace::EXECUTABLE_PATH_NOT_FOUND?!;
String path = env::executable_path() ?? backtrace::EXECUTABLE_PATH_NOT_FOUND?!;
uint dyld_count = darwin::_dyld_image_count();
for (uint i = 0; i < dyld_count; i++)
{
@@ -103,23 +110,22 @@ fn uptr? load_address() @local
return backtrace::IMAGE_NOT_FOUND?;
}
fn Backtrace? backtrace_load_element(Allocator allocator, String execpath, void* buffer, void* load_address) @local
{
@pool()
if (buffer)
{
if (buffer)
{
char* buf = tmalloc(1024);
String s = process::execute_stdout_to_buffer(buf[:1024],
{ "atos", "-o", execpath, "-arch", env::AARCH64 ? "arm64" : "x86_64", "-l",
string::tformat("%p", load_address),
string::tformat("%p", buffer - 1),
"-fullPath" })!;
String[] parts = s.tsplit(" ");
char[1024] buf;
String s = process::execute_stdout_to_buffer(&buf,
{ "atos", "-o", execpath, "-arch", env::AARCH64 ? "arm64" : "x86_64", "-l",
string::bformat(&&(char[64]){}, "%p", load_address),
string::bformat(&&(char[64]){}, "%p", buffer - 1),
"-fullPath" })!;
@stack_mem(512; Allocator mem)
{
String[] parts = s.split(mem, " ", 5);
if (parts.len == 4)
{
String[] path_parts = parts[3].tsplit(":");
String[] path_parts = parts[3].split(mem, ":");
return {
.offset = (uptr)buffer,
.function = parts[0].copy(allocator),
@@ -129,17 +135,17 @@ fn Backtrace? backtrace_load_element(Allocator allocator, String execpath, void*
.allocator = allocator
};
}
}
Darwin_Dl_info info;
if (!buffer || !darwin::dladdr(buffer, &info)) return backtrace::BACKTRACE_UNKNOWN;
return {
.offset = (uptr)buffer,
.function = info.dli_sname ? info.dli_sname.copy(allocator) : "???".copy(allocator),
.object_file = info.dli_fname.copy(allocator),
.file = "".copy(allocator),
.line = 0,
.allocator = allocator
};
}
Darwin_Dl_info info;
if (!buffer || !darwin::dladdr(buffer, &info)) return backtrace::BACKTRACE_UNKNOWN;
return {
.offset = (uptr)buffer,
.function = info.dli_sname ? info.dli_sname.copy(allocator) : "???".copy(allocator),
.object_file = info.dli_fname.copy(allocator),
.file = "".copy(allocator),
.line = 0,
.allocator = allocator
};
}
@@ -156,14 +162,11 @@ fn BacktraceList? symbolize_backtrace(Allocator allocator, void*[] backtrace)
}
list.free();
}
@pool()
String execpath = executable_path()!;
foreach (addr : backtrace)
{
String execpath = executable_path(tmem)!;
foreach (addr : backtrace)
{
list.push(backtrace_load_element(allocator, execpath, addr, load_addr) ?? backtrace::BACKTRACE_UNKNOWN);
}
};
list.push(backtrace_load_element(allocator, execpath, addr, load_addr) ?? backtrace::BACKTRACE_UNKNOWN);
}
return list;
}

View File

@@ -1,2 +1,74 @@
module std::os::openbsd @if(env::OPENBSD);
import libc, std::os, std::collections::list;
extern fn ZString* backtrace_symbols_fmt(void **addrlist, usz len, ZString fmt);
fn Backtrace? backtrace_line_parse(Allocator allocator, String obj, String addr2line) @local
{
@stack_mem(256; Allocator mem)
{
String[] parts = addr2line.trim().split(mem, " at ");
if (parts.len != 2) return NOT_FOUND?;
uint line = 0;
String source = "";
if (!parts[1].contains("?") && parts[1].contains(":"))
{
usz index = parts[1].rindex_of_char(':')!;
source = parts[1][:index];
line = parts[1][index + 1..].to_uint()!;
}
return {
.function = parts[0].copy(allocator),
.object_file = obj.copy(allocator),
.file = source.copy(allocator),
.line = line,
.allocator = allocator,
.is_inline = false,
};
};
}
fn void? backtrace_add_addr2line(Allocator allocator, BacktraceList* list, String obj, String addr2line) @local
{
list.push(backtrace_line_parse(allocator, obj, addr2line)!);
}
fn void? backtrace_add_from_exec(Allocator allocator, BacktraceList* list, String fun, String obj) @local
{
char[1024] buf @noinit;
String addr2line = process::execute_stdout_to_buffer(&buf, {"llvm-addr2line", "-fpe", obj, fun})!;
return backtrace_add_addr2line(allocator, list, obj, addr2line);
}
fn void? backtrace_add_element(Allocator allocator, BacktraceList *list, String fun, String obj) @local
{
return backtrace_add_from_exec(allocator, list, fun, obj);
}
fn BacktraceList? symbolize_backtrace(Allocator allocator, void*[] backtrace)
{
BacktraceList list;
list.init(allocator, backtrace.len);
defer catch
{
foreach (trace : list)
{
trace.free();
}
list.free();
}
ZString *strings = backtrace_symbols_fmt(backtrace.ptr, backtrace.len, "%n%D %f");
for (int i = 0; i < backtrace.len; i++) {
String full = strings[i].str_view();
Splitter iter = full.tokenize(" ");
String fun = iter.next()!;
String obj = iter.next()!;
backtrace_add_element(allocator, &list, fun, obj)!;
}
free(strings);
return list;
}

View File

@@ -1,3 +1,8 @@
module std::os::posix @if(env::POSIX);
extern ZString* environ;
extern fn CLong sysconf(CInt name);
const CInt _SC_PAGESIZE @if(env::LINUX) = 30;
const CInt _SC_PAGESIZE @if(env::DARWIN) = 29;

28
lib/std/os/posix/mman.c3 Normal file
View File

@@ -0,0 +1,28 @@
module std::os::posix @if(env::POSIX);
import libc;
const PROT_NONE = 0x00; // no permissions
const PROT_READ = 0x01; // pages can be read
const PROT_WRITE = 0x02; // pages can be written
const PROT_EXEC = 0x04; // pages can be executed
const MAP_SHARED = 0x0001; // share changes
const MAP_PRIVATE = 0x0002; // changes are private
const MAP_FILE = 0x0000; // map from file (default)
const MAP_ANONYMOUS = 0x1000; // allocated from memory, swap space
const void* MAP_FAILED = (void *)(uptr)-1; // mmap failed
const MADV_NORMAL = 0; // no further special treatment
const MADV_RANDOM = 1; // expect random page refs
const MADV_SEQUENTIAL = 2; // expect sequential page refs
const MADV_WILLNEED = 3; // will need these pages
const MADV_DONTNEED = 4; // dont need these pages
extern fn void* mmap(void*, usz, CInt, CInt, CInt, Off_t);
extern fn CInt munmap(void*, usz);
extern fn CInt mprotect(void*, usz, CInt);
extern fn int madvise(void*, usz, CInt);
extern fn CInt getpagesize();

View File

@@ -58,7 +58,9 @@ const CInt WUNTRACES = 2;
JmpBuf backtrace_jmpbuf @local;
alias BacktraceFn = fn CInt(void** buffer, CInt size);
fn CInt backtrace(void** buffer, CInt size)
extern fn CInt backtrace(void** buffer, CInt size) @if(env::OPENBSD);
fn CInt backtrace(void** buffer, CInt size) @if(!env::OPENBSD)
{
if (size < 1) return 0;
void* handle = libc::dlopen("libc.so.6", libc::RTLD_LAZY|libc::RTLD_NODELETE);

View File

@@ -55,21 +55,18 @@ fn void? create_named_pipe_helper(void** rd, void **wr) @local @if(env::WIN32)
tlocal long index = 0;
long unique = index++;
@pool()
{
String s = string::tformat(`\\.\pipe\c3_subprocess.%08x.%08x.%d`, win32::getCurrentProcessId(), win32::getCurrentThreadId(), unique);
Win32_LPCSTR str = (Win32_LPCSTR)s.ptr;
*rd = win32::createNamedPipeA(
str,
win32::PIPE_ACCESS_INBOUND | win32::FILE_FLAG_OVERLAPPED,
win32::PIPE_TYPE_BYTE | win32::PIPE_WAIT,
1, 4096, 4096, 0, &sa_attr);
if (win32::INVALID_HANDLE_VALUE == *rd) return FAILED_TO_CREATE_PIPE?;
*wr = win32::createFileA(
str, win32::GENERIC_WRITE, 0, &sa_attr,
win32::OPEN_EXISTING, win32::FILE_ATTRIBUTE_NORMAL, null);
if (win32::INVALID_HANDLE_VALUE == *wr) return FAILED_TO_CREATE_PIPE?;
};
String s = string::bformat(&&(char[128]){}, `\\.\pipe\c3_subprocess.%08x.%08x.%d`, win32::getCurrentProcessId(), win32::getCurrentThreadId(), unique);
Win32_LPCSTR str = (Win32_LPCSTR)s.ptr;
*rd = win32::createNamedPipeA(
str,
win32::PIPE_ACCESS_INBOUND | win32::FILE_FLAG_OVERLAPPED,
win32::PIPE_TYPE_BYTE | win32::PIPE_WAIT,
1, 4096, 4096, 0, &sa_attr);
if (win32::INVALID_HANDLE_VALUE == *rd) return FAILED_TO_CREATE_PIPE?;
*wr = win32::createFileA(
str, win32::GENERIC_WRITE, 0, &sa_attr,
win32::OPEN_EXISTING, win32::FILE_ATTRIBUTE_NORMAL, null);
if (win32::INVALID_HANDLE_VALUE == *wr) return FAILED_TO_CREATE_PIPE?;
}
fn WString convert_command_line_win32(String[] command_line) @inline @if(env::WIN32) @local
@@ -143,12 +140,12 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
if (!win32::setHandleInformation(wr, win32::HANDLE_FLAG_INHERIT, 0)) return FAILED_TO_CREATE_PIPE?;
}
@pool()
@stack_mem(2048; Allocator mem)
{
WString used_environment = null;
if (!options.inherit_environment)
{
DString env = dstring::temp_with_capacity(64);
DString env = dstring::new_with_capacity(mem, 64);
if (!environment.len)
{
env.append("\0");
@@ -159,7 +156,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
env.append("\0");
}
env.append("\0");
used_environment = env.str_view().to_temp_wstring()!;
used_environment = env.str_view().to_wstring(mem)!;
}
// Handle stdin pipe if not inheriting
@@ -266,26 +263,26 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
<*
@require command_line.len > 0
*>
fn ZString* tcopy_command_line(String[] command_line) @local @inline @if(env::POSIX)
fn ZString* copy_command_line(Allocator mem, String[] command_line) @local @inline @if(env::POSIX)
{
ZString* copy = mem::talloc_array(ZString, command_line.len + 1);
ZString* copy = allocator::alloc_array(mem, ZString, command_line.len + 1);
foreach (i, str : command_line)
{
copy[i] = str.zstr_tcopy();
copy[i] = str.zstr_copy(mem);
}
copy[command_line.len] = null;
return copy;
}
const ZString[1] EMPTY_ENVIRONMENT @if(env::POSIX) = { null };
fn ZString* tcopy_env(String[] environment) @local @inline @if(env::POSIX)
fn ZString* copy_env(Allocator mem, String[] environment) @local @inline @if(env::POSIX)
{
if (!environment.len) return &EMPTY_ENVIRONMENT;
ZString* copy = mem::talloc_array(ZString, environment.len + 1);
ZString* copy = allocator::alloc_array(mem, ZString, environment.len + 1);
copy[environment.len] = null;
foreach (i, str : environment)
{
copy[i] = str.zstr_tcopy();
copy[i] = str.zstr_copy(mem);
}
return copy;
}
@@ -338,10 +335,10 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
}
Pid_t child;
@pool()
@stack_mem(2048; Allocator mem)
{
ZString* command_line_copy = tcopy_command_line(command_line);
ZString* used_environment = options.inherit_environment ? posix::environ : tcopy_env(environment);
ZString* command_line_copy = copy_command_line(mem, command_line);
ZString* used_environment = options.inherit_environment ? posix::environ : copy_env(mem, environment);
if (options.search_user_path)
{
if (posix::spawnp(&child, command_line_copy[0], &actions, null, command_line_copy, used_environment)) return FAILED_TO_START_PROCESS?;

View File

@@ -221,6 +221,8 @@ const Win32_DWORD ERROR_DEVICE_FEATURE_NOT_SUPPORTED = 0x13C;
const Win32_DWORD ERROR_MR_MID_NOT_FOUND = 0x13D;
const Win32_DWORD ERROR_SCOPE_NOT_FOUND = 0x13E;
const Win32_DWORD ERROR_UNDEFINED_SCOPE = 0x13F;
const Win32_DWORD ERROR_INVALID_ADDRESS = 0x1E7;
const Win32_DWORD ERROR_IO_INCOMPLETE = 0x3E4;
const Win32_DWORD ERROR_IO_PENDING = 0x3E5;
const Win32_DWORD ERROR_TIMEOUT = 0x5B4;
const Win32_DWORD ERROR_TIMEOUT = 0x5B4;
const Win32_DWORD ERROR_COMMITMENT_LIMIT = 0x5AF;

View File

@@ -0,0 +1,52 @@
module std::os::win32 @if(env::WIN32);
enum Win32_AllocationType : const Win32_DWORD
{
MEM_COMMIT = 0x00001000,
MEM_RESERVE = 0x00002000,
MEM_RESET = 0x00080000,
MEM_RESET_UNDO = 0x01000000,
MEM_LARGE_PAGES = 0x20000000,
MEM_PHYSICAL = 0x00400000,
MEM_TOP_DOWN = 0x00100000,
MEM_WRITE_WATCH = 0x00200000
}
enum Win32_Protect : const Win32_DWORD
{
PAGE_EXECUTE = 0x10,
PAGE_EXECUTE_READ = 0x20,
PAGE_EXECUTE_READWRITE = 0x40,
PAGE_EXECUTE_WRITECOPY = 0x80,
PAGE_NOACCESS = 0x01,
PAGE_READONLY = 0x02,
PAGE_READWRITE = 0x04,
PAGE_WRITECOPY = 0x08,
PAGE_TARGETS_INVALID = 0x40000000,
PAGE_TARGETS_NO_UPDATE = 0x40000000,
PAGE_GUARD = 0x100,
PAGE_NOCACHE = 0x200,
PAGE_WRITECOMBINE = 0x400,
}
enum Win32_FreeType : const Win32_DWORD
{
MEM_DECOMMIT = 0x00004000,
MEM_RELEASE = 0x00008000,
MEM_COALESCE_PLACEHOLDERS = 0x00000001,
MEM_PRESERVE_PLACEHOLDER = 0x00000002,
}
extern fn Win32_LPVOID virtualAlloc(Win32_LPVOID lpAddres, Win32_SIZE_T dwSize, Win32_AllocationType flAllocationType, Win32_Protect flProtect) @extern("VirtualAlloc");
extern fn Win32_PVOID virtualAlloc2(Win32_HANDLE process, Win32_PVOID baseAddress, Win32_SIZE_T size, Win32_AllocationType allocationType, Win32_ULONG pageProtection, Win32_MEM_EXTENDED_PARAMETER* extendedParameters, Win32_ULONG parameterCount) @extern("VirtualAlloc2");
extern fn Win32_BOOL virtualFree(Win32_LPVOID lpAddress, Win32_SIZE_T dwSize, Win32_FreeType dwFreeType) @extern("VirtualFree");
extern fn Win32_BOOL virtualProtect(Win32_LPVOID lpAddress, Win32_SIZE_T dwSize, Win32_Protect flNewProtect, Win32_Protect* lpflOldProtect) @extern("VirtualProtect");
fn usz allocation_granularity()
{
static usz granularity;
if (granularity) return granularity;
Win32_SYSTEM_INFO info;
win32::getSystemInfo(&info);
return granularity = (usz)info.dwAllocationGranularity;
}

37
lib/std/os/win32/winnt.c3 Normal file
View File

@@ -0,0 +1,37 @@
module std::os::win32 @if(env::WIN32);
enum Win32_MEM_EXTENDED_PARAMETER_TYPE : CInt
{
INVALID_TYPE,
ADDRESS_REQUIREMENTS,
NUMA_NODE,
PARTITION_HANDLE,
USER_PHYSICAL_HANDLE,
ATTRIBUTE_FLAGS,
IMAGE_MACHINE,
MAX
}
alias Win32_PMEM_EXTENDED_PARAMETER_TYPE = Win32_MEM_EXTENDED_PARAMETER_TYPE;
enum Win32_MEM_EXTENDED_PARAMETER_ATTRIBUTE : const Win32_DWORD64
{
NONPAGED = 0x02, // The allocation is non-pageable.
NONPAGED_LARGE = 0x08, // The allocation is mapped using large pages.
NONPAGED_HUGE = 0x10, // The allocation is mapped using huge pages.
EC_CODE = 0x40, // The allocation will contain emulation-compatible (EC) code.
}
struct Win32_MEM_EXTENDED_PARAMETER
{
Win32_MEM_EXTENDED_PARAMETER_TYPE type;
union
{
Win32_MEM_EXTENDED_PARAMETER_ATTRIBUTE attribute; // If type is ATTRIBUTE_FLAGS
Win32_DWORD64 nodeNumber; // If type is NUMA_NODE
Win32_PVOID pointer; // If type is ADDRESS_REQUIREMENTS
Win32_SIZE_T size;
Win32_HANDLE handle;
Win32_DWORD uLong;
}
}
alias Win32_PMEM_EXTENDED_PARAMETER = Win32_MEM_EXTENDED_PARAMETER*;

View File

@@ -32,7 +32,7 @@ alias Indexs @private = char[256];
alias ElementType = $typeof((Type){}[0]);
const bool NO_KEY_FN @private = types::is_same(KeyFn, EmptySlot);
const bool KEY_BY_VALUE @private = NO_KEY_FN ||| $assignable((Type){}[0], KeyFn.paramsof[0].type);
const bool KEY_BY_VALUE @private = NO_KEY_FN ||| @assignable_to((ElementType){}, KeyFn.paramsof[0].type);
const bool LIST_HAS_REF @private = $defined(&(Type){}[0]);
alias KeyFnReturnType @if(!NO_KEY_FN) = $typefrom(KeyFn.returns) ;

View File

@@ -25,7 +25,7 @@ fn void isort(Type list, usz low, usz high, CmpFn comp, Context context)
{
var $has_cmp = @is_valid_macro_slot(comp);
var $has_context = @is_valid_macro_slot(context);
var $cmp_by_value = $has_cmp &&& $assignable(list[0], CmpFn.paramsof[0].type);
var $cmp_by_value = $has_cmp &&& @assignable_to(list[0], CmpFn.paramsof[0].type);
var $has_get_ref = $defined(&list[0]);
for (usz i = low; i < high; ++i)
{

View File

@@ -115,7 +115,7 @@ macro @partition(Type list, isz l, isz h, CmpFn cmp, Context context)
{
var $has_cmp = @is_valid_macro_slot(cmp);
var $has_context = @is_valid_macro_slot(context);
var $cmp_by_value = $has_cmp &&& $assignable(list[0], CmpFn.paramsof[0].type);
var $cmp_by_value = $has_cmp &&& @assignable_to(list[0], CmpFn.paramsof[0].type);
ElementType pivot = list[l];
while (l < h)

View File

@@ -39,7 +39,7 @@ macro int @sort_cmp(list, pos, cmp, ctx) @local
{
var $has_cmp = @is_valid_macro_slot(cmp);
var $has_context = @is_valid_macro_slot(ctx);
var $cmp_by_value = $has_cmp &&& $assignable(list[0], $typeof(cmp).paramsof[0].type);
var $cmp_by_value = $has_cmp &&& @assignable_to(list[0], $typeof(cmp).paramsof[0].type);
var a = list[pos];
var b = list[pos+1];

View File

@@ -4,4 +4,24 @@ typedef NativeMutex = int;
typedef NativeTimedMutex = int;
typedef NativeConditionVariable = int;
typedef NativeOnceFlag = int;
typedef NativeThread = int;
typedef NativeThread = int;
fn void NativeOnceFlag.call_once(&flag, OnceFn func)
{
if (*flag == 0)
{
*flag = 1;
func();
}
}
fn void? NativeMutex.init(&mtx, MutexType type) => NOT_IMPLEMENTED?;
fn bool NativeMutex.is_initialized(&self)
{
return false;
}
macro void? NativeMutex.lock(&mutex) => NOT_IMPLEMENTED?;
macro bool NativeMutex.try_lock(&mutex) => NOT_IMPLEMENTED?;
macro void? NativeMutex.unlock(&mutex) => NOT_IMPLEMENTED?;

View File

@@ -139,15 +139,32 @@ fn void? NativeConditionVariable.wait(&cond, NativeMutex* mtx)
<*
@require mtx.is_initialized()
@return? thread::WAIT_TIMEOUT, thread::WAIT_FAILED
*>
fn void? NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms)
{
TimeSpec now;
if (libc::timespec_get(&now, libc::TIME_UTC) != libc::TIME_UTC) return thread::WAIT_FAILED?;
now.ns += (CLong)((ms % 1000) * 1000_000);
now.s += (Time_t)(ms / 1000 + now.ns / 1000_000_000);
now.ns = now.ns % 1000_000_000;
switch (posix::pthread_cond_timedwait(cond, &mtx.mutex, &now))
Time time = time::now() + time::ms(ms);
return cond.wait_until(mtx, time) @inline;
}
<*
@require mtx.is_initialized()
@return? thread::WAIT_TIMEOUT, thread::WAIT_FAILED
*>
fn void? NativeConditionVariable.wait_timeout_duration(&cond, NativeMutex* mtx, Duration duration)
{
if (duration < time::DURATION_ZERO) return thread::WAIT_TIMEOUT?;
Time time = time::now() + duration;
return cond.wait_until(mtx, time) @inline;
}
<*
@require mtx.is_initialized()
@return? thread::WAIT_TIMEOUT, thread::WAIT_FAILED
*>
fn void? NativeConditionVariable.wait_until(&cond, NativeMutex* mtx, Time time)
{
switch (posix::pthread_cond_timedwait(cond, &mtx.mutex, &&time.to_timespec()))
{
case errno::ETIMEDOUT:
return thread::WAIT_TIMEOUT?;
@@ -216,7 +233,7 @@ fn void native_thread_yield()
fn void? native_sleep_nano(NanoDuration nano)
{
if (nano <= 0) return;
if (nano <= time::NANO_DURATION_ZERO) return;
if (libc::nanosleep(&&nano.to_timespec(), null)) return thread::INTERRUPTED?;
}

View File

@@ -15,6 +15,11 @@ struct NativeMutex
}
}
fn bool NativeMutex.is_initialized(&self)
{
return self.initialized;
}
struct NativeTimedMutex
{
Win32_SRWLOCK srw_lock;
@@ -198,7 +203,7 @@ fn void? NativeTimedMutex.lock_timeout(&mtx, ulong ms)
NanoDuration duration = time::ms(ms).to_nano();
Clock start = clock::now();
for (NanoDuration remaining = duration; remaining > 0; remaining = duration - start.to_now())
for (NanoDuration remaining = duration; remaining > time::NANO_DURATION_ZERO; remaining = duration - start.to_now())
{
ulong remaining_ms = remaining.to_ms();
if (remaining_ms > uint.max) remaining_ms = uint.max;
@@ -311,6 +316,7 @@ fn void? NativeConditionVariable.wait(&cond, NativeMutex* mtx) @inline
<*
@require mtx.initialized : "Mutex was not initialized"
@return? thread::WAIT_TIMEOUT, thread::WAIT_FAILED
*>
fn void? NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms) @inline
{
@@ -318,6 +324,28 @@ fn void? NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms)
return timedwait(cond, mtx, (uint)ms) @inline;
}
<*
@require mtx.initialized : "Mutex was not initialized"
@return? thread::WAIT_TIMEOUT, thread::WAIT_FAILED
*>
fn void? NativeConditionVariable.wait_timeout_duration(&cond, NativeMutex* mtx, Duration duration) @inline
{
if (duration < time::DURATION_ZERO) return thread::WAIT_TIMEOUT?;
long ms = duration.to_ms();
if (ms > uint.max) ms = uint.max;
return timedwait(cond, mtx, (uint)ms) @inline;
}
<*
@require mtx.initialized : "Mutex was not initialized"
@return? thread::WAIT_TIMEOUT, thread::WAIT_FAILED
*>
fn void? NativeConditionVariable.wait_until(&cond, NativeMutex* mtx, Time time) @inline
{
Duration duration = time - time::now();
return cond.wait_timeout_duration(mtx, duration);
}
fn void? NativeThread.create(&thread, ThreadFn func, void* args)
{
if (!(*thread = (NativeThread)win32::createThread(null, 0, func, args, 0, null))) return thread::INIT_FAILED?;

View File

@@ -34,6 +34,7 @@ faultdef
CHANNEL_CLOSED;
macro void? Mutex.init(&mutex) => NativeMutex.init((NativeMutex*)mutex, {});
macro bool Mutex.is_initialized(mutex) => ((NativeMutex*)&mutex).is_initialized();
macro void? RecursiveMutex.init(&mutex) => NativeMutex.init((NativeMutex*)mutex, {.recursive});
macro void? Mutex.destroy(&mutex) => NativeMutex.destroy((NativeMutex*)mutex);
macro void? Mutex.lock(&mutex) => NativeMutex.lock((NativeMutex*)mutex);
@@ -48,6 +49,11 @@ macro void? TimedMutex.lock_timeout(&mutex, ulong ms) => NativeTimedMutex.lock_t
macro bool TimedMutex.try_lock(&mutex) => NativeTimedMutex.try_lock((NativeTimedMutex*)mutex);
macro void? TimedMutex.unlock(&mutex) => NativeTimedMutex.unlock((NativeTimedMutex*)mutex);
macro void fence(AtomicOrdering $ordering) @safemacro
{
$$fence($ordering.ordinal);
}
macro void Mutex.@in_lock(&mutex; @body)
{
(void)mutex.lock();
@@ -63,12 +69,37 @@ macro void? ConditionVariable.wait(&cond, Mutex* mutex)
{
return NativeConditionVariable.wait((NativeConditionVariable*)cond, (NativeMutex*)mutex);
}
macro void? ConditionVariable.wait_timeout(&cond, Mutex* mutex, ulong ms)
<*
@require @assignable_to(#ms_or_duration, Duration) || @assignable_to(#ms_or_duration, ulong)
@return? thread::WAIT_TIMEOUT, thread::WAIT_FAILED
*>
macro void? ConditionVariable.wait_timeout(&cond, Mutex* mutex, #ms_or_duration) @safemacro
{
return NativeConditionVariable.wait_timeout((NativeConditionVariable*)cond, (NativeMutex*)mutex, ms);
$if @assignable_to(#ms_or_duration, Duration):
return NativeConditionVariable.wait_timeout_duration((NativeConditionVariable*)cond, (NativeMutex*)mutex, #ms_or_duration);
$else
return NativeConditionVariable.wait_timeout((NativeConditionVariable*)cond, (NativeMutex*)mutex, #ms_or_duration);
$endif
}
<*
@return? thread::WAIT_TIMEOUT, thread::WAIT_FAILED
*>
macro void? ConditionVariable.wait_until(&cond, Mutex* mutex, Time time)
{
return NativeConditionVariable.wait_until((NativeConditionVariable*)cond, (NativeMutex*)mutex, time);
}
<*
Create and start a thread.
@require thread_fn != null : "A non null thread function is required"
*>
macro void? Thread.create(&thread, ThreadFn thread_fn, void* arg)
{
return NativeThread.create(thread, thread_fn, arg);
}
macro void? Thread.create(&thread, ThreadFn thread_fn, void* arg) => NativeThread.create(thread, thread_fn, arg);
macro void? Thread.detach(thread) => NativeThread.detach(thread);
macro int? Thread.join(thread) => NativeThread.join(thread);
macro bool Thread.equals(thread, Thread other) => NativeThread.equals(thread, other);

View File

@@ -112,6 +112,11 @@ fn TzDateTime TzDateTime.to_gmt_offset(self, int gmt_offset) {
return { dt, gmt_offset };
}
fn bool TzDateTime.eq(self, TzDateTime other) @operator(==) @inline
{
return self.to_gmt_offset(0).time == other.to_gmt_offset(0).time;
}
<*
@require day >= 1 && day < 32
@require hour >= 0 && hour < 24
@@ -257,3 +262,5 @@ fn Duration DateTime.diff_us(self, DateTime from) @operator(-)
{
return self.time.diff_us(from.time);
}
fn bool DateTime.eq(self, DateTime other) @operator(==) @inline => self.time == other.time;

View File

@@ -12,10 +12,10 @@ fn Clock native_clock()
ulong mult = 0;
if (!freq.quadPart)
{
if (!win32::queryPerformanceFrequency(&freq)) return 0;
if (!win32::queryPerformanceFrequency(&freq)) return (Clock)0;
}
Win32_LARGE_INTEGER counter @noinit;
if (!win32::queryPerformanceCounter(&counter)) return 0;
if (!win32::queryPerformanceCounter(&counter)) return (Clock)0;
return (Clock)counter.quadPart.muldiv(1_000_000_000, freq.quadPart);
}

View File

@@ -1,13 +1,15 @@
module std::time;
import std::io, std::time::os;
typedef Time = long;
typedef Duration = long;
typedef Clock = ulong;
typedef NanoDuration (Printable) = long;
typedef Time @structlike = long;
typedef Duration @structlike = long;
typedef Clock @structlike = ulong;
typedef NanoDuration (Printable) @structlike = long;
const Time FAR_FUTURE = long.max;
const Time FAR_PAST = long.min;
const NanoDuration NANO_DURATION_ZERO = 0;
const Duration US = 1;
const Duration MS = 1_000;
const Duration SEC = 1_000_000;
@@ -18,6 +20,7 @@ const Duration WEEK = 7 * DAY;
const Duration MONTH = 30 * DAY;
const Duration YEAR = 36525 * DAY / 100;
const Duration FOREVER = long.max;
const Duration DURATION_ZERO = 0;
fn Duration us(long l) @inline => l * US;
fn Duration ms(long l) @inline => l * MS;
@@ -78,7 +81,7 @@ fn Time now()
$if $defined(native_timestamp):
return os::native_timestamp();
$else
return 0;
return (Time)0;
$endif
}
@@ -114,31 +117,31 @@ macro Duration Duration.mult(#td, long #val) @operator_s(*) @safemacro => (Durat
fn usz? NanoDuration.to_format(&self, Formatter* formatter) @dynamic
{
NanoDuration nd = *self;
if (nd == 0)
if (nd == NANO_DURATION_ZERO)
{
return formatter.printf("0s")!;
}
bool neg = nd < 0;
bool neg = nd < NANO_DURATION_ZERO;
if (neg) nd = -nd;
DString str = dstring::temp_with_capacity(64);
if (nd < 1_000_000_000)
if (nd < (NanoDuration)1_000_000_000)
{
// Less than 1s: print milliseconds, microseconds and nanoseconds.
NanoDuration ms = nd / 1_000_000;
if (ms > 0)
if (ms > NANO_DURATION_ZERO)
{
str.appendf("%dms", ms);
nd -= ms * 1_000_000;
}
NanoDuration us = nd / 1000;
if (us > 0)
if (us > NANO_DURATION_ZERO)
{
str.appendf("%dµs", us);
nd -= us * 1000;
}
if (nd > 0)
if (nd > NANO_DURATION_ZERO)
{
str.appendf("%dns", nd);
}
@@ -150,19 +153,19 @@ fn usz? NanoDuration.to_format(&self, Formatter* formatter) @dynamic
nd /= 1_000_000_000;
NanoDuration hour = nd / 3600;
if (hour > 0)
if (hour > NANO_DURATION_ZERO)
{
str.appendf("%dh", hour);
nd -= hour * 3600;
}
NanoDuration min = nd / 60;
if (min > 0)
if (min > NANO_DURATION_ZERO)
{
str.appendf("%dm", min);
nd -= min * 60;
}
NanoDuration sec = nd;
if (ms > 0)
if (ms > NANO_DURATION_ZERO)
{
// Ignore trailing zeroes.
while (ms / 10 * 10 == ms) ms /= 10;

View File

@@ -141,7 +141,7 @@ resource = first(tools["localizedResources"], lambda x: x["language"] == "en-us"
license = resource["license"]
if not args.accept_license:
accept = input(f"Do you accept Visual Studio license at {license}, and also confirm that you have a valid license Visual Studio license allowing you to download the VS Build Tools [Y/N] ?")
accept = input(f"Do you accept Visual Studio license at {license} [Y/N] ?")
if not accept or accept[0].lower() != "y":
exit(0)

View File

@@ -1,5 +1,148 @@
# C3C Release Notes
## 0.7.5 Change list
### Changes / improvements
- Support `alias foo = module std::io` module aliasing.
- Add compile-time `@intlog2` macro to math.
- Add compile-time `@clz` builtin. #2367
- Add `bitsizeof` macro builtins. #2376
### Fixes
- List.remove_at would incorrectly trigger ASAN.
- With avx512, passing a 512 bit vector in a union would be lowered incorrectly, causing an assert. #2362
- Codegen error in `if (try x = (true ? io::EOF? : 1))`, i.e. using if-try with a known Empty.
- Codegen error in `if (try x = (false ? io::EOF? : 1))`, i.e. using if-try with a CT known value.
- Reduce allocated Vmem for the compiler on 32 bit machines.
- Bug causing a compiler error when parsing a broken lambda inside of an expression.
- Fixed: regression in comments for `@deprecated` and `@pure`.
- Detect recursive creation of generics #2366.
- Compiler assertion when defining a function with return type untyped_list #2368.
- Compiler assert when using generic parameters list without any parameters. #2369
- Parsing difference between "0x00." and "0X00." literals #2371
- Fixed bug generating `$c += 1` when `$c` was derived from a pointer but behind a cast.
- Compiler segfault when using bitwise not on number literal cast to bitstruct #2373.
- Formatter did not properly handle "null" for any, and null for empty faults. #2375
- Bitstructs no longer overloadable with bitops. #2374
### Stdlib changes
- Add `==` to `Pair`, `Triple` and TzDateTime. Add print to `Pair` and `Triple`.
- Add OpenBSD to `env::INET_DEVICES` and add required socket constants.
- Added `FileMmap` to manage memory mapped files.
- Add `vm::mmap_file` to memory map a file.
- Updated hash functions in default hash methods.
- Added `FixedBlockPool` which is a memory pool for fixed size blocks.
- Added the experimental `std::core::log` for logging.
## 0.7.4 Change list
### Changes / improvements
- Added const enums: `enum Foo : const`. Behaves like C enums but may be any type.
- Casting to / from an enum is now possible again. No need to use `.ordinal` and `.from_ordinal`.
- Inline associated enum values are deprecated, use `--use-old-enums` to re-enable them.
- `$typeof` may return a compile time type.
- Improved error messages on missing qualifier on enum value. #2260
- Add `--echo-prefix` to edit the prefix with `$echo` statements. Supports {FILE} and {LINE}
- Catch accidental `foo == BAR;` where `foo = BAR;` was most likely intended. #2274
- Improve error message when doing a rethrow in a function that doesn't return an optional.
- Add `--list-asm` to view all supported `asm` instructions.
- Formatting option "%h" now supports pointers.
- Improve error on unsigned implicit conversion to signed.
- Update error message for struct initialization #2286
- Add SipHash family of keyed PRFs. #2287
- `$is_const` is deprecated in favour of `@is_const` based on `$defined`.
- Multiline contract comments #2113
- Removed the use of temp allocator in backtrace printing.
- `env::AUTHORS` and `env::AUTHOR_EMAILS` added.
- Suppress codegen of panic printing with when panic messages are set to "off".
- Implicit linking of libc math when libc math functions are used.
- Allow even smaller memory limits.
- Check unaligned array access.
- Add "@structlike" for typedefs.
- "poison" the current function early when a declaration can't be correctly resolved.
- Add komihash, a5hash, metrohash64, metrohash128, and wyhash2 variants with tests/benchmark. #2293
- '$assignable' is deprecated.
- Deprecate allocator::heap() and allocator::temp()
- Add `thread::fence` providing a thread fence.
- Place output in `out` by default for projects. Use temp folder for building at the command line.
- Allow absolute paths for `$embed`.
- Add `@try` and `@try_catch`.
- Assignment evaluation order now right->left, following C++17 and possibly C23.
### Fixes
- mkdir/rmdir would not work properly with substring paths on non-windows platforms.
- Hex string formatter check incorrectly rejected slices.
- Correctly reject interface methods `type` and `ptr`.
- Comparing a null ZString with a non-null ZString would crash.
- Switch case with const non-int / enum would be treated as ints and crash. #2263
- Missing bounds check on upper bound with const ranges `foo[1:3]`.
- Check up the hierarchy when considering if an interface cast is valid #2267.
- Fix issue with labelled break inside of a $switch.
- Non-const macros may not return untyped lists.
- `$for` ct-state not properly popped.
- Inline `r / complex` for complex numbers fixed.
- Const slice lengths were not always detected as constant.
- Const slice indexing was not bounds checked.
- Initialize pool correctly in print_backtrace.
- `--max-mem` now works correctly again.
- Casting a fault to a pointer would trigger an assert.
- Make `to_float` more tolerant to spaces.
- Fixes to thread local pointer handling.
- Fixes to JSON parsing and Object.
- Array indices are now using int64 internally.
- Bit shift operation fails with inline uint enum despite matching underlying type #2279.
- Fix to codegen when using a bitstruct constant defined using a cast with an operator #2248.
- Function pointers are now compile time constants.
- Splat 8 arguments can sometimes cause incorrect behaviour in the compiler. #2283
- Correctly poison the analysis after a failed $assert or $error. #2284
- `$foo` variables could be assigned non-compile time values.
- `$foo[0] = ...` was incorrectly requiring that the assigned values were compile time constants.
- "Inlined at" would sometimes show the current location.
- Fixed bug splatting constants into constants.
- Resize bug when resizing memory down in ArenaAllocator, DynamicArenaAllocator, BackedArenaAllocator.
- Error message for missing arg incorrect for methods with zero args #2296.
- Fix stringify of $vaexpr #2301.
- Segfault when failing to cast subexpression to 'isz' in pointer subtraction #2305.
- Fix unexpected display of macro definition when passing a poisoned expression #2305.
- `@links` on macros would not be added to calling functions.
- Fix `Formatter.print` returning incorrect size.
- A distinct type based on an array would yield .len == 0
- Overloading addition with a pointer would not work.
- Copying const enums and regular enums incorrect #2313.
- Regression: Chaining an optional together with contracts could in some cases lose the optional.
- `char[*] b = *(char[*]*)&a;` would crash the compiler if `a` was a slice. #2320
- Implicitly cast const int expressions would sometimes not be detected as compile time const.
- Using @noreturn in a short body macro would not work properly #2326.
- Bug when reporting error in a macro return would crash the compiler #2326.
- Short body return expression would not have the correct span.
- Fix issue where recursively creating a dir would be incorrectly marked as a failure the first time.
- `@format` did not work correctly with macros #2341.
- Crash when parsing recursive type declaration #2345.
- Remove unnecessary "ret" in naked functions #2344.
- Lambdas now properly follow its attributes #2346.
- Not setting android-ndk resulted in a "set ndk-path" error.
- Lambda deduplication would be incorrect when generated at the global scope.
- Disallow accessing parameters in a naked function, as well as `return`, this fixes #1955.
- Assigning string literal to char[<*>] stores pointer rather than characters. #2357
### Stdlib changes
- Improve contract for readline. #2280
- Added Whirlpool hash.
- Added Ed25519.
- Added string::bformat.
- Virtual memory library.
- New virtual emory arena allocator.
- Added `WString.len`.
- Added `@addr` macro.
- Add `ConditionVariable.wait_until` and `ConditionVariable.wait_for`
- Added readline_to_stream that takes a stream.
- Added `Ref` and `RefCounted` experimental functionality.
- Added `Volatile` generic type.
- Added `UnalignedRef` generic type.
- Added `HashSet` generic type.
- Added `LinkedHashSet` and `LinkedHashMap` generic types.
- Add String conversion functions snake_case -> PascalCase and vice versa.
## 0.7.3 Change list
### Changes / improvements
@@ -30,6 +173,7 @@
- Compile-time comparison of constant vectors. #1575.
- $member.get supports bitstructs.
- $member.set for setting members without the *& trick.
- Initial support for #1925, does not affect C compilation yet, and doesn't try to link etc. Using "--emit-only"
### Fixes
- `-2147483648`, MIN literals work correctly.
@@ -79,6 +223,7 @@
- `is_array_or_slice_of_char` and `is_arrayptr_or_slice_of_char` are replaced by constant `@` variants.
- `@pool` now has an optional `reserve` parameter, some minor changes to the temp_allocator API
- io::struct_to_format now supports bitstructs.
- Add `String.escape`, `String.unescape` for escaping and unescaping a string.
## 0.7.2 Change list
@@ -106,6 +251,7 @@
- Add deprecation for `@param foo "abc"`.
- Add `--header-output` and `header-output` options for controlling header output folder.
- Generic faults is disallowed.
- Detect when a slice on the stack is accidentally returned from a function.
### Fixes
- Assert triggered when casting from `int[2]` to `uint[2]` #2115

View File

@@ -3,6 +3,8 @@ import std;
import bar;
import clib;
import clib2;
import std::core::env;
fn int test_doubler(int x) @if(env::WIN32) => x * x;
extern fn int test_doubler(int) @if(!env::WIN32);
@@ -11,6 +13,11 @@ extern fn void printf(char *, ...);
fn int main()
{
printf("Hello World!\n");
printf("Authors:");
io::printn(env::AUTHORS);
printf("Author emails:");
io::printn(env::AUTHOR_EMAILS);
bar::test();
printf("Hello double: %d\n", test_doubler(11));
if ($feature(ABCD)) io::printn("ABCD");

View File

@@ -484,6 +484,7 @@ typedef struct BuildOptions_
int api_version;
} android;
int build_threads;
const char *echo_prefix;
const char **libraries_to_fetch;
const char **files;
const char *test_filter;
@@ -566,6 +567,7 @@ typedef struct BuildOptions_
const char *header_out;
const char *obj_out;
const char *script_dir;
const char **emit_only;
RelocModel reloc_model;
X86VectorCapability x86_vector_capability;
X86CpuSet x86_cpu_set;
@@ -592,10 +594,17 @@ typedef struct BuildOptions_
bool print_precedence;
bool print_linking;
bool print_env;
bool print_asm;
bool benchmarking;
bool testing;
} BuildOptions;
typedef struct
{
const char *author;
const char *email;
} AuthorEntry;
typedef struct
{
struct Library__ *parent;
@@ -648,6 +657,7 @@ typedef struct
const char **linker_libdirs;
const char **linker_libs;
const char *cpu;
const char *echo_prefix;
const char **link_args;
const char *build_dir;
const char *object_file_dir;
@@ -657,6 +667,7 @@ typedef struct
const char *header_file_dir;
const char *script_dir;
const char *run_dir;
const char **emit_only;
bool is_non_project;
bool run_after_compile;
bool delete_after_run;
@@ -687,6 +698,7 @@ typedef struct
bool print_stats;
bool old_slice_copy;
bool old_enums;
bool single_threaded;
int build_threads;
TrustLevel trust_level;
OptimizationSetting optsetting;
@@ -723,6 +735,7 @@ typedef struct
const char **csources;
const char **cinclude_dirs;
const char **exec;
AuthorEntry *authors;
const char **feature_list;
const char *custom_linker_path;
struct

View File

@@ -50,7 +50,7 @@ static const char *optsizes[3] = {
[SIZE_OPTIMIZATION_TINY] = "tiny",
};
static const char *linker[3] = {
static const char *linker_kind[3] = {
[LINKER_TYPE_BUILTIN] = "builtin",
[LINKER_TYPE_CC] = "cc",
[LINKER_TYPE_CUSTOM] = "custom"

View File

@@ -106,6 +106,7 @@ static void usage(bool full)
print_opt("--path <dir>", "Use this as the base directory for the current command.");
print_opt("--template <template>", "Select template for 'init': \"exe\", \"static-lib\", \"dynamic-lib\" or a path.");
print_opt("--symtab <value>", "Sets the preferred symtab size.");
print_opt("--max-mem <value>", "Set the maximum memory size (in MB).");
print_opt("--run-once", "After running the output file, delete it immediately.");
print_opt("--suppress-run", "Build but do not run on test/benchmark options.");
print_opt("--trust=<option>", "Trust level: none (default), include ($include allowed), full ($exec / exec allowed).");
@@ -140,6 +141,7 @@ static void usage(bool full)
{
PRINTF("");
print_opt("--ansi=<yes|no>", "Set colour output using ansi on/off, default is to try to detect it.");
print_opt("--echo-prefix <arg>", "Sets the prefix for any output using '$echo', defaults to 'c3c:'.");
print_opt("--test-filter <arg>", "Set a filter when running tests, running only matching tests.");
print_opt("--test-breakpoint", "When running tests, trigger a breakpoint on failure.");
print_opt("--test-nosort", "Do not sort tests.");
@@ -159,6 +161,7 @@ static void usage(bool full)
print_opt("--use-stdlib=<yes|no>", "Include the standard library (default: yes).");
print_opt("--link-libc=<yes|no>", "Link libc other default libraries (default: yes).");
print_opt("--emit-stdlib=<yes|no>", "Output files for the standard library. (default: yes)");
print_opt("--emit-only <file>", "Output only the file matching <file>.");
print_opt("--panicfn <name>", "Override the panic function name.");
print_opt("--testfn <name>", "Override the test runner function name.");
print_opt("--benchfn <name>", "Override the benchmark runner function name.");
@@ -188,6 +191,7 @@ static void usage(bool full)
print_opt("--list-manifest-properties", "List all available keys used in manifest.json files.");
print_opt("--list-targets", "List all architectures the compiler supports.");
print_opt("--list-type-properties", "List all type properties.");
print_opt("--list-asm", "List all asm instructions for the current target.");
PRINTF("");
print_opt("--print-output", "Print the object files created to stdout.");
print_opt("--print-input", "Print inputted C3 files to stdout.");
@@ -724,6 +728,18 @@ static void parse_option(BuildOptions *options)
options->ansi = parse_opt_select(Ansi, argopt, on_off);
return;
}
if (match_longopt("max-mem"))
{
if (at_end() || next_is_opt())
{
FAIL_WITH_ERR_LONG("'--max-mem' expected a max memory.");
}
if (atoll(next_arg()) < 1)
{
FAIL_WITH_ERR_LONG("'--max-mem' expected a positive integer value.");
}
return;
}
if (match_longopt("sources"))
{
if (at_end() || next_is_opt())
@@ -743,7 +759,7 @@ static void parse_option(BuildOptions *options)
options->old_slice_copy = true;
return;
}
if (match_longopt("use-new-enums"))
if (match_longopt("use-old-enums"))
{
options->old_enums = true;
return;
@@ -862,7 +878,7 @@ static void parse_option(BuildOptions *options)
if ((argopt = match_argopt("linker")))
{
options->custom_linker_path = NULL;
options->linker_type = parse_opt_select(LinkerType, argopt, linker);
options->linker_type = parse_opt_select(LinkerType, argopt, linker_kind);
if (options->linker_type == LINKER_TYPE_CUSTOM)
{
if (at_end() || next_is_opt()) error_exit("error: --linker=custom expects a valid linker name.");
@@ -885,6 +901,12 @@ static void parse_option(BuildOptions *options)
options->emit_stdlib = parse_opt_select(EmitStdlib, argopt, on_off);
return;
}
if (match_longopt("emit-only"))
{
if (at_end() || next_is_opt()) error_exit("error: --emit-only expects an output name, e.g. 'foo', to only output 'foo.o'.");
vec_add(options->emit_only, next_arg());
return;
}
if ((argopt = match_argopt("use-stdlib")))
{
options->use_stdlib = parse_opt_select(UseStdlib, argopt, on_off);
@@ -915,10 +937,10 @@ static void parse_option(BuildOptions *options)
options->riscv_float_capability = parse_opt_select(RiscvFloatCapability, argopt, riscv_capability);
return;
}
if ((argopt = match_argopt("max-vector-size")))
if (match_longopt("max-vector-size"))
{
int size = atoi(next_arg());
if (size < 128) error_exit("Expected a valid positive integer >= 128 or --max-vector-size.");
int size = (at_end() || next_is_opt()) ? 0 : atoi(next_arg());
if (size < 128) error_exit("Expected a valid positive integer >= 128 for --max-vector-size.");
if (size > MAX_VECTOR_WIDTH) error_exit("Expected a valid positive integer <= %u for --max-vector-size.", (unsigned)MAX_VECTOR_WIDTH);
if (size != next_highest_power_of_2(size))
{
@@ -986,6 +1008,11 @@ static void parse_option(BuildOptions *options)
options->command = COMMAND_PRINT_SYNTAX;
return;
}
if (match_longopt("list-asm"))
{
options->print_asm = true;
return;
}
if (match_longopt("list-project-properties"))
{
options->print_project_properties = true;
@@ -1020,6 +1047,12 @@ static void parse_option(BuildOptions *options)
options->build_threads = threads;
return;
}
if (match_longopt("echo-prefix"))
{
if (at_end() || next_is_opt()) error_exit("error: --echo-prefix needs a prefix.");
options->echo_prefix = next_arg();
return;
}
if (match_longopt("target"))
{
if (at_end() || next_is_opt()) error_exit("error: --target needs a arch+os definition.");
@@ -1329,7 +1362,7 @@ static void parse_option(BuildOptions *options)
break;
}
FAIL_WITH_ERR("Cannot process the unknown option \"%s\".", current_arg);
FAIL_WITH_ERR_LONG("Cannot process the unknown option \"%s\".", current_arg);
}
BuildOptions parse_arguments(int argc, const char *argv[])
@@ -1440,7 +1473,7 @@ BuildOptions parse_arguments(int argc, const char *argv[])
const char *ndk_path = getenv("ANDROID_NDK");
if (!ndk_path)
{
FAIL_WITH_ERR("Can't find Android NDK, please set --ndk-path.");
FAIL_WITH_ERR("Can't find Android NDK, please set --android-ndk.");
}
build_options.android.ndk_path = strdup(ndk_path);
}

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