Compare commits

..

369 Commits
v0.7.1 ... log

Author SHA1 Message Date
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
Christoffer Lerno
9285dfefad - $member.get supports bitstructs.
- $member.set for setting members without the *& trick.
- io::struct_to_format now supports bitstructs.
2025-06-29 01:19:09 +02:00
Book-reader
5246ef83e7 Fix typo in libc_allocator.c3 2025-06-28 14:33:18 +02:00
Zack Puhl
0448e50b3d Fix Incorrect SHA256 Hashes for Large Inputs (#2247)
* fix >256MiB sha256 bitcount computation overflow
2025-06-28 03:20:33 +02:00
Christoffer Lerno
2d535aaa25 Default assert() message drops parens #2249. 2025-06-28 00:07:26 +02:00
Christoffer Lerno
dc1e5323ab Segfault in the compiler when using a bitstruct constant defined using a cast with an operator #2248. 2025-06-27 23:00:14 +02:00
Christoffer Lerno
63abf1c2f8 - Compiler null pointer when building a static-lib with -o somedir/... #2246 2025-06-27 15:46:17 +02:00
Christoffer Lerno
df8904909b Fix bugs relating to optional interface addr-of #2244. 2025-06-27 15:02:12 +02:00
Christoffer Lerno
e986e3a8c0 Refactoring. 2025-06-25 15:08:57 +02:00
Christoffer Lerno
f67da4f315 Refactoring, optimize negation in if statement. 2025-06-25 12:33:17 +02:00
Christoffer Lerno
8a4e7b6ce8 Fix regression causing text output. 2025-06-25 02:48:22 +02:00
Christoffer Lerno
2b0fb52f65 Fix regression: Enum inference with compare operators #2241 2025-06-25 00:55:29 +02:00
Christoffer Lerno
faf073885f Updated fix of #2218 2025-06-24 22:28:14 +02:00
Christoffer Lerno
11b8a9808d Fix overeager constant folding. 2025-06-24 16:17:04 +02:00
Christoffer Lerno
1ef9c73342 Compile-time comparison of constant vectors. #1575. 2025-06-24 16:04:24 +02:00
Christoffer Lerno
92d56b7a35 Updated project suggestions 2025-06-24 14:19:41 +02:00
Christoffer Lerno
a894adbdd6 Assert comparing untyped lists #2240. 2025-06-24 12:47:08 +02:00
Christoffer Lerno
605a7c4091 Improve error message on pointer diff #2239. 2025-06-23 23:47:26 +02:00
Zack Puhl
adabae2a24 add 'strdup' reference to libc 2025-06-23 15:37:25 +02:00
Christoffer Lerno
affb722b23 @tag didn't work with members #2236. 2025-06-23 14:45:16 +02:00
Christoffer Lerno
1b2f5989e1 Assert casting bitstruct to short/char #2237 2025-06-23 14:12:34 +02:00
Christoffer Lerno
e0b6c83a62 Aaaand let's get that last test. 2025-06-23 02:05:31 +02:00
Christoffer Lerno
839f835845 Last test fix. 2025-06-23 00:58:16 +02:00
Christoffer Lerno
2636a855c4 Forgotten test directives. 2025-06-23 00:38:12 +02:00
Christoffer Lerno
0d147a48b2 Another fix to #2226 2025-06-23 00:36:15 +02:00
Christoffer Lerno
aff3a3f746 Compiler segfault when using distinct type in attribute imported from other module #2234. 2025-06-22 23:58:38 +02:00
Christoffer Lerno
c95204c3f7 Fix inc/dec vector ptr. 2025-06-22 18:09:08 +02:00
Christoffer Lerno
794e8371c8 Rename test. 2025-06-21 23:47:49 +02:00
Christoffer Lerno
2bbc6cbbca Further #2226 fixes. 2025-06-21 23:18:17 +02:00
Christoffer Lerno
07bd37da43 Further #2226 fixes. 2025-06-21 16:50:52 +02:00
Christoffer Lerno
a0497e9274 math::overflow_* wrappers incorrectly don't allow distinct integers #2221. 2025-06-21 13:28:45 +02:00
Christoffer Lerno
fa730e7ec2 Overload resolution fixes to inline typedef #2226. 2025-06-21 13:03:16 +02:00
Christoffer Lerno
b4a6e3704f Update grammar to interpret $Foo = int as a statement. 2025-06-21 00:30:43 +02:00
Christoffer Lerno
dd80e8b799 Compile time type assignment (eg $Foo = int) is no longer an expression. 2025-06-20 23:31:40 +02:00
Zack Puhl
5efc721b0c Add SHA512 Module to stdlib (#2227)
* add sha512 module to stdlib with passing unit tests

* fix release notes PR ref num for this

* deduplicate const SHA512 hash info
2025-06-20 19:08:45 +02:00
Christoffer Lerno
20c13c0bb4 - Incorrect handling of constant null fault causing compiler crash #2232. 2025-06-20 15:29:52 +02:00
Christoffer Lerno
cd3e924d1e Fix test for LLVM 20 2025-06-20 02:01:32 +02:00
Estanislao Pérez Nartallo
2e42868467 Fix error maybe-uninitialized (#2230)
* Add build instructions for Arch Linux

* Fix error maybe-uninitialized in llvm_codegen_expr.c when compiling with clang 20.1.6
2025-06-20 00:55:06 +02:00
Christoffer Lerno
8d698b5e40 Lambda C-style vaargs were not properly rejected, leading to crash #2229. 2025-06-20 00:52:03 +02:00
Christoffer Lerno
2f45beecbe @pool now has an optional reserve parameter, some minor changes to the temp_allocator API 2025-06-19 01:13:43 +02:00
Christoffer Lerno
1b4b9bca94 Linking fails on operator method imported as @public #2224. 2025-06-18 23:34:39 +02:00
Tanis Pérez Nartallo
40ae9d2e55 Add build instructions for Arch Linux 2025-06-18 23:34:16 +02:00
Christoffer Lerno
0df538d0e2 Test LLVM 20 compatibility 2025-06-18 22:41:35 +02:00
Christoffer Lerno
aa425a0886 Fixes to x += { 1, 1 } for enum and pointer vectors #2222. 2025-06-18 22:27:30 +02:00
Christoffer Lerno
842788e59d x += 1 and x -= 1 works propertly on pointer vectors #2222. 2025-06-18 17:02:56 +02:00
Christoffer Lerno
2b97d7d59c x++ and x-- works on pointer vectors #2222. 2025-06-18 13:17:21 +02:00
Christoffer Lerno
75f78551cf Rename TypdefDecl to TypeAliasDecl 2025-06-18 11:59:44 +02:00
Christoffer Lerno
01ef53a090 Bug when offsetting pointers of large structs using ++ and --. 2025-06-18 10:13:48 +02:00
Christoffer Lerno
a55f56a88f Linker errors when shadowing @local with public function #2198 2025-06-18 02:07:07 +02:00
Christoffer Lerno
eb75d8f82a Method ambiguity when importing parent module publicly in private submodule. #2208 2025-06-18 00:18:56 +02:00
Christoffer Lerno
f07bd3cbc6 $defined(#expr) broken with binary. #2219 2025-06-17 18:00:16 +02:00
Christoffer Lerno
93640699be Support distrinct types as the base type of bitstructs. #2218 2025-06-17 16:49:46 +02:00
Christoffer Lerno
99e29bff8d Bug in AST copying would make operator overloading like += compile incorrectly #2217 2025-06-17 16:02:43 +02:00
Ero Mrinin
95137db64b 'next_float' macro patch (#2213)
More optimized implementation.
2025-06-17 09:54:27 +02:00
Christoffer Lerno
e7ce79e731 Fix error for named arguments-order with compile-time arguments #2212 2025-06-16 23:56:03 +02:00
Christoffer Lerno
779f548a00 Allow generics over distinct types #2216. 2025-06-16 23:16:35 +02:00
Christoffer Lerno
f0bd93d1f0 Additional #2210 fixes. 2025-06-16 22:56:34 +02:00
Christoffer Lerno
3ce15bd7af Incorrect codegen if a macro ends with unreachable and is assigned to something. #2210 2025-06-15 22:35:44 +02:00
Christoffer Lerno
07eee04e94 In some cases, the compiler would dereference a compile time null. #2215 2025-06-15 21:58:39 +02:00
Christoffer Lerno
1f7b62b248 Fix to is_array_or_slice_of_char #2214.
`is_array_or_slice_of_char` and `is_arrayptr_or_slice_of_char` are replaced by constant `@` variants.
2025-06-15 21:27:47 +02:00
Christoffer Lerno
b2c994618f Fix to is_array_or_slice_of_char #2214.
`is_array_or_slice_of_char` and `is_arrayptr_or_slice_of_char` are replaced by constant `@` variants.
2025-06-15 16:54:20 +02:00
Christoffer Lerno
2afa544d7d Correctly format '%c' when given a width. #2199 2025-06-15 02:27:36 +02:00
Christoffer Lerno
dda2d2ecbe Show code that caused unreachable code #2207
`$echo` would suppress warning about unreachable code. #2205
2025-06-15 00:37:28 +02:00
Christoffer Lerno
f79f6d4001 - cflags additions for targets was not handed properly. #2209 2025-06-14 23:40:54 +02:00
Christoffer Lerno
cf167c9446 Make unreachable() only panic in safe mode. 2025-06-14 18:37:49 +02:00
Christoffer Lerno
f0201f971e Fix NULL -> false. 2025-06-13 22:51:51 +02:00
Christoffer Lerno
a3abea1a33 Reorder fields. 2025-06-13 22:08:07 +02:00
Christoffer Lerno
5f6f52838c The form-feed character '\f' is no longer valid white space. 2025-06-13 21:37:06 +02:00
Christoffer Lerno
e0237096d6 - Support untyped second argument for operator overloading.
- Distinct versions of builtin types ignore @operator overloads #2204.
- @operator macro using untyped parameter causes compiler segfault #2200.
- Add comparison with `==` for ZString types.
2025-06-13 17:12:39 +02:00
Christoffer Lerno
82491a6f85 - Fixes to @format checking #2199. 2025-06-12 02:26:39 +02:00
Christoffer Lerno
1aacb1fa60 Fixed regression compiler crash when using && for untyped parameters #2197. 2025-06-10 16:26:08 +02:00
Christoffer Lerno
bbd9f6dc96 Add --sources build option to add additional files to compile. #2097 2025-06-10 14:09:15 +02:00
Christoffer Lerno
496d23e93f Fix some @require comments. 2025-06-10 01:54:45 +02:00
Christoffer Lerno
e936b999d2 Update Android ABI lowering 2025-06-08 23:14:07 +02:00
Christoffer Lerno
becda6ea1d Improve Android linking 2025-06-08 15:06:19 +02:00
Christoffer Lerno
2ad17a04d4 Improve android detection. 2025-06-08 12:18:55 +02:00
Christoffer Lerno
1617792a35 Fix Android detection. 2025-06-08 11:53:47 +02:00
Christoffer Lerno
c7b3ae0cf9 Fix Android errno 2025-06-08 11:33:28 +02:00
Christoffer Lerno
1dcd40aa5f --lsp sometimes does not emit end tag #2194. 2025-06-08 00:30:11 +02:00
Christoffer Lerno
40554192b1 - Make accepting arguments for main a bit more liberal, accepting main(int argc, ZString* argv)
- Make `$echo` and `@sprintf` correctly stringify compile time initializers and slices.
- Fixes methodsof to pick up all sorts of extension methods. #2192
- Fix regression accidentally allowing `$assert $foo, $bar`
2025-06-08 00:23:04 +02:00
Book-reader
9bc5e259d2 fix types::may_load_atomic with enums 2025-06-07 13:54:27 +02:00
Christoffer Lerno
f66cadccd2 Add printf format to $assert and $error #2183. 2025-06-06 23:50:55 +02:00
Christoffer Lerno
be511b26cd Additional fixes on $define 2025-06-06 20:11:58 +02:00
Christoffer Lerno
4cfa5441d2 Additional fixes on $define 2025-06-06 15:38:46 +02:00
Christoffer Lerno
5e45c34f21 - Deprecate String.is_zstr and String.quick_zstr #2188. 2025-06-06 15:30:46 +02:00
Awang
d7a11903c7 Add external __errno() function for env::ANDROID (#2182) 2025-06-06 14:47:16 +02:00
Christoffer Lerno
b893697a87 Various fixes for $defined 2025-06-06 14:41:20 +02:00
Christoffer Lerno
f2daf2e11e @sprintf macro (based on the $$sprintf builtin) allows compile time format strings #1874. 2025-06-06 03:18:28 +02:00
Christoffer Lerno
9baeca3a8e $eval now also works with @foo, #foo, $Foo and $foo parameters #2114. 2025-06-06 01:23:23 +02:00
Christian Brendlin
ef649050c4 Add support for custom file extensions in project.json targets (Resolves #1315) (#2169)
* [Feat] add support for custom output file extensions in build process

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-06-05 23:04:09 +02:00
Christoffer Lerno
d6d0e08906 Allow inference across && #2172. 2025-06-05 14:20:40 +02:00
Christoffer Lerno
c9d9127da6 Deprecate foo.#bar. 2025-06-05 12:51:35 +02:00
Christoffer Lerno
7f85534414 - Implicitly convert from constant typeid to Type in $Type assignment, and $assignable.
- Make $Type parameters accept constant typeid values.
2025-06-05 00:37:16 +02:00
Christoffer Lerno
ba1332dc2a Splatting const slices would not be const. #2185 2025-06-04 16:09:15 +02:00
Christoffer Lerno
45a0895c39 Fix fmod regression. 2025-06-04 15:25:18 +02:00
Christoffer Lerno
72cc8e430a -0xFF will now be a signed integer.
- `-2147483648`, MIN literals work correctly.
2025-06-04 15:20:49 +02:00
Christoffer Lerno
9645bd3289 - $typefrom now also accepts a constant string, and so works like $evaltype.
- `$evaltype` is deprecated in favour of `$typefrom`.
2025-06-03 14:51:56 +02:00
Christoffer Lerno
8fc01d4e1a Simplify contract macros. 2025-06-02 22:53:06 +02:00
Bram Windey
a48e2274e5 Update options.run_dir from target.run_dir if options.run_dir is null 2025-06-02 14:09:29 +02:00
Christoffer Lerno
786e47408a Update compiler version 2025-06-02 13:40:16 +02:00
Christoffer Lerno
6e348d1e71 Update compiler version 2025-06-02 10:12:04 +02:00
Christoffer Lerno
d697b910ba Removed the naive check for compile time modification, which fixes #1997 but regresses in detection. 2025-06-01 23:50:13 +02:00
Christoffer Lerno
4d848f1707 Incorrect ensure on String.split. 2025-06-01 20:28:32 +02:00
Christoffer Lerno
6377f0573d Typo 2025-06-01 20:06:34 +02:00
Christoffer Lerno
c3d2b2824c Bug using #foo arguments with $defined #2173 2025-05-31 17:35:29 +02:00
Christoffer Lerno
18e408ead4 Fix example. 2025-05-30 19:22:17 +02:00
Christoffer Lerno
08c63108a1 Release candidate 0.7.2 2025-05-30 19:13:19 +02:00
Christoffer Lerno
da25a411f9 Generic faults is disallowed. 2025-05-30 19:12:26 +02:00
Christian Brendlin
e685414829 Fix #1718: Add --header-output option to specify header file directory (#2161)
* Fix #1718: Add --header-output option to specify header file directory

- Add header_out field to BuildOptions struct
- Add header_file_dir field to BuildTarget struct
- Add --header-output command line option parsing with help text
- Modify header_gen() to use configured output directory instead of hardcoded root
- Add default behavior to use build directory when no custom path specified
- Add directory creation for header output paths
- Resolves issue where generated C headers were always output to root directory

* Fix directory creation timing for header output

- Move header output directory creation before header_gen() call
- Ensures custom header output directories are created before files are written
- Fixes issue where --header-output would fail if directory doesn't exist

* Fix style

* Fix Style

* Add to releasenotes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-05-30 10:19:46 +02:00
BWindey
ae5a74bc41 [Feat] add quiet to project.json settings (#2166)
* Add quiet to BuildTarget struct and set default to false

* Link BuildTarget.quiet with BuildOptions.verbosity_level (like --quiet flag)

* Parse quiet from project.json, and sort the entries alphabetically

* Add changes to release-notes

* Only set options.verbosity_level if it wasn't set on the commandline

* Formatting

* Added small fix.
2025-05-30 10:16:14 +02:00
Christoffer Lerno
76374d31c4 Spelling 2025-05-30 10:08:04 +02:00
Christoffer Lerno
ffd7a5e483 Fix incorrect percentage 2025-05-30 01:47:05 +02:00
BWindey
d143ec227c Libc ioctl extern function (#2162)
* Add barebones extern ioctl() binding in libc
* Update release notes
2025-05-29 23:00:35 +02:00
Christoffer Lerno
f2703508f2 Fixed test. 2025-05-29 21:14:50 +02:00
Christoffer Lerno
bb96dc931e Add deprecation for @param foo "abc". 2025-05-29 00:45:11 +02:00
Christoffer Lerno
a5a2b00ec8 Too strict project view #2163. 2025-05-28 22:44:00 +02:00
Christoffer Lerno
00f1206f3c Compiler didn't check foreach over flexible array member, and folding a flexible array member was allowed #2164. 2025-05-28 22:21:06 +02:00
Christoffer Lerno
349d9ef3cf Allow recursive generic modules. 2025-05-28 15:39:35 +02:00
Christoffer Lerno
9f30b56e13 Deprecate f32, f64 and f128 suffixes. 2025-05-28 13:01:49 +02:00
Christoffer Lerno
83d6b35afe Add d as floating point suffix for double types. 2025-05-28 12:02:24 +02:00
Christoffer Lerno
f4b9f375e0 Add run-dir to project.json 2025-05-27 23:22:32 +02:00
Christoffer Lerno
be3f9007c9 Check pointer/slice/etc on [out] and & params. #2156. 2025-05-27 23:03:43 +02:00
Christian Brendlin
b665e2cbe5 change releasenotes entry to reflect to correct issue (#2159)
I changed the reference #2138 to point to the issue #2012 instead of the pull request.
2025-05-27 14:15:36 +02:00
Christoffer Lerno
0ed68f94cf Update matrix pass. 2025-05-27 13:59:12 +02:00
Christoffer Lerno
966e8107f8 Add $$matrix_mul and $$matrix_transpose builtins. 2025-05-27 00:50:21 +02:00
Book-reader
61a4dcc807 add macro wrappers for $$overflow_add, $$overflow_sub, and $$overflow_mul builtins 2025-05-26 20:58:16 +02:00
Christoffer Lerno
52541a03eb @jump now included in --list-attributes #2155. 2025-05-26 16:18:08 +02:00
Christoffer Lerno
972c84b65b for with incorrect var declaration caused crash #2154. 2025-05-26 15:56:51 +02:00
Christoffer Lerno
f668b96cc9 $$sat_mul was missing. 2025-05-26 12:23:19 +02:00
Christoffer Lerno
9461873b4c Distinct types could not be used with tagof #2152 2025-05-26 00:57:20 +02:00
Christoffer Lerno
8d563eba7a Implicit casting from struct to interface failure for inheriting interfaces #2151. Fix second bug in #2148 2025-05-24 17:10:11 +02:00
Christoffer Lerno
fe98225f0a Remove superfluous cleanup parameter in os::exit and os::fastexit. 2025-05-23 22:04:54 +02:00
Christoffer Lerno
bae3e59217 Add missing @noreturn to os::exit 2025-05-23 21:41:33 +02:00
Christoffer Lerno
b5ddc36d7f Limit vector max size, default is 4096 bits, but may be increased using --max-vector-size. 2025-05-23 21:40:14 +02:00
Christoffer Lerno
c2c0ecded8 - --path does not interact correctly with relative path arguments #2149. 2025-05-23 19:17:04 +02:00
Christoffer Lerno
9d5b31dad5 Missing error on default values for body with default arguments #2148. 2025-05-23 18:57:21 +02:00
Christoffer Lerno
6c0e94cad9 Fix indent 2025-05-23 16:45:57 +02:00
Christian Brendlin
84aee6a25b Feature: Add inherit_stdio Option for SubProcess (#2138)
* add inherit_stdio option
2025-05-22 11:06:23 +02:00
Matthew Brush
71a765c66e Update CODESTYLE.md
Fix a couple typos and wording.
2025-05-22 11:03:50 +02:00
Gregory Oakes
5c3b637cf6 Add Maybe.equals when inner type is equatable. 2025-05-22 00:06:11 +02:00
Christoffer Lerno
bd1de1e7dc &&& was accidentally available as a valid prefix operator. 2025-05-21 23:36:33 +02:00
Christoffer Lerno
3cd2267b0a Update error message. 2025-05-20 23:00:31 +02:00
Christoffer Lerno
7fcc91edc8 Improve error message when encountering recursively defined structs. #2146 2025-05-19 21:36:47 +02:00
Christoffer Lerno
9052f07c19 Empty default case in @jump switch does not fallthrough #2147. 2025-05-19 21:18:23 +02:00
Christoffer Lerno
c7f0d54328 Designated const initializers with {} would overwrite the parent field. 2025-05-18 23:40:52 +02:00
Christoffer Lerno
498803e9ba Error when using named argument on trailing macro body expansion #2139. 2025-05-17 23:50:15 +02:00
Christoffer Lerno
082457c5fb Incorrect parsing of call attributes #2144. 2025-05-17 22:10:03 +02:00
Christoffer Lerno
23897bc9a4 - Incorrect parsing of ad hoc generic types, like Foo{int}**** #2140.
- $define did not correctly handle generic types #2140.
2025-05-17 21:14:10 +02:00
Christoffer Lerno
8ada2a70d9 Using a non-const as the end range for a bitstruct would trigger an assert. 2025-05-17 18:55:58 +02:00
mr6r4y
a91330b7d1 Fix typo causing segmentation fault 2025-05-17 15:41:49 +02:00
Christoffer Lerno
2f3954a7d9 Deprecate SomeFn.params 2025-05-16 21:57:18 +02:00
Christoffer Lerno
b7ae5dce8b Deprecate MyEnum.elements. 2025-05-16 16:12:37 +02:00
Christoffer Lerno
91db6ceeda Defining an enum like ABC = { 1 2 } was accidentally allowed. 2025-05-16 09:56:08 +02:00
Christoffer Lerno
fc2f718d9e Update error message. 2025-05-15 23:34:01 +02:00
Christoffer Lerno
64ef3fc756 Some folding was missing in binary op compile time resolution #2135. 2025-05-15 16:04:55 +02:00
Christoffer Lerno
93dd432b62 Improve error message when using keywords as functions/macros/variables #2133. 2025-05-15 15:27:14 +02:00
Christoffer Lerno
6c822e5aa3 Add math::@ceil() compile time ceil function. #2134 2025-05-15 12:46:46 +02:00
Christoffer Lerno
8c741c617c Variable aliases of aliases would not resolve correctly. #2131
Variable aliases could not be assigned to.
2025-05-15 09:36:16 +02:00
Christoffer Lerno
b83e57b952 Added @rnd() compile time random function (using the $$rnd() builtin). #2078 2025-05-15 00:51:33 +02:00
Christoffer Lerno
24ebe975d8 Allow the right hand side of ||| and &&& be runtime values. 2025-05-14 23:40:36 +02:00
Christoffer Lerno
511ae0da00 Contract on trying to use Object without initializing it. 2025-05-14 23:22:34 +02:00
Christoffer Lerno
36eb650228 Correctly error on @attrdef Foo = ;. 2025-05-14 12:15:48 +02:00
DragonFriend
50b4d7aa35 Add replace and treplace to String (#2127)
* Add replace and treplace functions to String
2025-05-14 11:00:20 +02:00
Christoffer Lerno
abe4727c3a Deprecate uXX and iXX bit suffixes.
Add experimental LL / ULL suffixes for int128 and uint128 literals.
2025-05-13 23:48:59 +02:00
Christoffer Lerno
c528f53d58 - attrdef with any invalid name causes compiler assert #2128. 2025-05-12 01:41:19 +02:00
Christoffer Lerno
83955ea5b5 Add --run-dir, to specify directory for running executable using compile-run and run #2121. 2025-05-12 01:24:51 +02:00
Christoffer Lerno
fc5c70a628 Update links. 2025-05-11 22:46:43 +02:00
Christoffer Lerno
5287640140 Fix link 2025-05-11 22:38:50 +02:00
Christoffer Lerno
634438eb82 Cleanup. 2025-05-08 21:05:43 +02:00
Christoffer Lerno
164c901ae6 More comments on the allocators. 2025-05-07 12:52:19 +02:00
Christoffer Lerno
54e70cae0f Add DateTime + Duration overloads. 2025-05-07 10:49:30 +02:00
Lucian Feroiu
30ec200492 Add support for default Homebrew-installed LLD (#2119) 2025-05-06 22:38:48 +02:00
Christoffer Lerno
584a8a2e60 - Fix regression in Time diff due to operator overloading #2124
- Add `Duration * Int` and `Clock - Clock` overload.
2025-05-06 22:33:39 +02:00
Christoffer Lerno
3f07d1c7b8 Fix No index OOB check for [:^n] #2123 2025-05-06 16:53:14 +02:00
Christoffer Lerno
125436d23e Better default assert messages when no message is specified #2122 2025-05-05 00:01:36 +02:00
Christoffer Lerno
900365c25e Fix stringify for compound initializers #2120. 2025-05-04 15:31:55 +02:00
Christoffer Lerno
d313afa487 Add String.count to count the number of instances of a string. 2025-05-02 21:48:04 +02:00
Christoffer Lerno
a411f20762 Assert when a macro with compile time value is discarded, e.g. foo(); where foo() returns an untyped list. #2117 2025-05-02 21:16:56 +02:00
Christoffer Lerno
8a0907cb70 Add String.tokenize_all to replace the now deprecated String.splitter 2025-05-02 20:51:15 +02:00
Christoffer Lerno
8a09b2e5f7 std::ascii moved into std::core::ascii. Old _m variants are deprecated, as is uint methods. 2025-05-02 18:06:28 +02:00
Christoffer Lerno
bfccc303d1 Added comments. 2025-05-02 13:36:46 +02:00
Christoffer Lerno
0d3299f267 Added String.quick_ztr and String.is_zstr 2025-05-02 13:22:13 +02:00
Christoffer Lerno
11bb8b49da - Assert triggered when casting from int[2] to uint[2] #2115 2025-05-01 18:23:48 +02:00
Christoffer Lerno
f0d2b0eff0 Update links in example code 2025-05-01 02:14:15 +02:00
Christoffer Lerno
005cc08118 0.7.2 bump 2025-04-30 18:03:00 +02:00
508 changed files with 25902 additions and 5222 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()
@@ -165,6 +183,10 @@ if(C3_WITH_LLVM)
endif()
endif()
if (EXISTS /opt/homebrew/lib)
list(APPEND LLVM_LIBRARY_DIRS /opt/homebrew/lib)
endif()
if (EXISTS /usr/lib)
# Some systems (such as Alpine Linux) seem to put some of the relevant
# LLVM files in /usr/lib, but this doesn't seem to be included in the
@@ -172,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)
@@ -227,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}
@@ -289,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)
@@ -408,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)
@@ -491,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)
@@ -499,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
@@ -536,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)
@@ -553,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
@@ -572,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

@@ -151,8 +151,9 @@ one with related functions when working on temporary strings.
# C3 Standard library style guide.
When contributing to the standard librairy please to your best to follow the following style requirements
as to ensure a consistent style in the stdlib and also make accepting PRs more quickly.
When contributing to the standard library please try your best to adhere to the
following style requirements to ensure a consistent style in the stdlib and to
facilitate accepting PRs more quickly.
### Braces are placed on the next line
@@ -256,4 +257,4 @@ argument.
## Add tests to your changes
If you add or fix things, then there should always be tests in `test/unit/stdlib` to verify
the functionality.
the functionality.

139
README.md
View File

@@ -8,10 +8,11 @@ for programmers who like C.
Precompiled binaries for the following operating systems are available:
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
- 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).
@@ -35,7 +36,7 @@ whole new language.
### Example code
The following code shows [generic modules](https://c3-lang.org/references/docs/generics/) (more examples can be found at https://c3-lang.org/references/docs/examples/).
The following code shows [generic modules](https://c3-lang.org/generic-programming/generics/) (more examples can be found at https://c3-lang.org/language-overview/examples/).
```cpp
module stack {Type};
@@ -141,7 +142,7 @@ fn void main()
### Current status
The current stable version of the compiler is **version 0.7.1**.
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.
@@ -197,34 +200,44 @@ More platforms will be supported in the future.
### Installing
This installs the latest prerelease build, as opposed to the latest released version.
#### Installing on Windows with precompiled binaries
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-windows-debug.zip))
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows-debug.zip))
2. Unzip exe and standard lib.
3. If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
4. Run `c3c.exe`.
#### Installing on Debian with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-linux-debug.tar.gz))
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux-debug.tar.gz))
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on Ubuntu with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20-debug.tar.gz))
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20-debug.tar.gz))
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on MacOS with precompiled binaries
1. Make sure you have XCode with command line tools installed.
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-macos-debug.zip))
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos-debug.zip))
3. Unzip executable and standard lib.
4. Run `./c3c`.
(*Note that there is a known issue with debug symbol generation on MacOS 13, see [issue #1086](https://github.com/c3lang/c3c/issues/1086))
#### Installing on OpenBSD with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/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:
@@ -263,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
@@ -311,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.
@@ -345,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
@@ -361,12 +381,29 @@ 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 libxml2`
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
3. Enter the C3C directory: `cd c3c`
4. Create the CMake build cache:
```bash
cmake -B build \
-D C3_LINK_DYNAMIC=ON \
-D CMAKE_BUILD_TYPE=Release
```
5. Build the project: `cmake --build build`.
After compilation, the `c3c` binary will be located in the `build` directory. You can test it by compiling an example: `./build/c3c compile resources/examples/ls.c3`.
6. To install the compiler globally: `sudo cmake --install build`
#### Compiling on other Linux / Unix variants
@@ -374,11 +411,10 @@ The c3c binary should be created in the build directory. You can try it out by r
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
@@ -406,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

@@ -1,77 +1,42 @@
<* This module is scheduled for removal, use std::core::ascii *>
module std::ascii;
macro bool in_range_m(c, start, len) => (uint)(c - start) < len;
macro bool is_lower_m(c) => in_range_m(c, 0x61, 26);
macro bool is_upper_m(c) => in_range_m(c, 0x41, 26);
macro bool is_digit_m(c) => in_range_m(c, 0x30, 10);
macro bool is_lower_m(c) => in_range_m(c, 0x61, 26);
macro bool is_upper_m(c) => in_range_m(c, 0x41, 26);
macro bool is_digit_m(c) => in_range_m(c, 0x30, 10);
macro bool is_bdigit_m(c) => in_range_m(c, 0x30, 2);
macro bool is_odigit_m(c) => in_range_m(c, 0x30, 8);
macro bool is_xdigit_m(c) => in_range_m(c | 32, 0x61, 6) || is_digit_m(c);
macro bool is_alpha_m(c) => in_range_m(c | 32, 0x61, 26);
macro bool is_print_m(c) => in_range_m(c, 0x20, 95);
macro bool is_graph_m(c) => in_range_m(c, 0x21, 94);
macro bool is_space_m(c) => in_range_m(c, 0x9, 5) || c == 0x20;
macro bool is_alnum_m(c) => is_alpha_m(c) || is_digit_m(c);
macro bool is_punct_m(c) => !is_alnum_m(c) && is_graph_m(c);
macro bool is_blank_m(c) => c == 0x20 || c == 0x9;
macro bool is_cntrl_m(c) => c < 0x20 || c == 0x7f;
macro bool is_alpha_m(c) => in_range_m(c | 32, 0x61, 26);
macro bool is_print_m(c) => in_range_m(c, 0x20, 95);
macro bool is_graph_m(c) => in_range_m(c, 0x21, 94);
macro bool is_space_m(c) => in_range_m(c, 0x9, 5) || c == 0x20;
macro bool is_alnum_m(c) => is_alpha_m(c) || is_digit_m(c);
macro bool is_punct_m(c) => !is_alnum_m(c) && is_graph_m(c);
macro bool is_blank_m(c) => c == 0x20 || c == 0x9;
macro bool is_cntrl_m(c) => c < 0x20 || c == 0x7f;
macro to_lower_m(c) => is_upper_m(c) ? c + 0x20 : c;
macro to_upper_m(c) => is_lower_m(c) ? c - 0x20 : c;
fn bool in_range(char c, char start, char len) => in_range_m(c, start, len);
fn bool is_lower(char c) => is_lower_m(c);
fn bool is_upper(char c) => is_upper_m(c);
fn bool is_digit(char c) => is_digit_m(c);
fn bool is_bdigit(char c) => is_bdigit_m(c);
fn bool is_odigit(char c) => is_odigit_m(c);
fn bool is_xdigit(char c) => is_xdigit_m(c);
fn bool is_alpha(char c) => is_alpha_m(c);
fn bool is_print(char c) => is_print_m(c);
fn bool is_graph(char c) => is_graph_m(c);
fn bool is_space(char c) => is_space_m(c);
fn bool is_alnum(char c) => is_alnum_m(c);
fn bool is_punct(char c) => is_punct_m(c);
fn bool is_blank(char c) => is_blank_m(c);
fn bool is_cntrl(char c) => is_cntrl_m(c);
fn char to_lower(char c) => (char)to_lower_m(c);
fn char to_upper(char c) => (char)to_upper_m(c);
fn bool char.in_range(char c, char start, char len) => in_range_m(c, start, len);
fn bool char.is_lower(char c) => is_lower_m(c);
fn bool char.is_upper(char c) => is_upper_m(c);
fn bool char.is_digit(char c) => is_digit_m(c);
fn bool char.is_bdigit(char c) => is_bdigit_m(c);
fn bool char.is_odigit(char c) => is_odigit_m(c);
fn bool char.is_xdigit(char c) => is_xdigit_m(c);
fn bool char.is_alpha(char c) => is_alpha_m(c);
fn bool char.is_print(char c) => is_print_m(c);
fn bool char.is_graph(char c) => is_graph_m(c);
fn bool char.is_space(char c) => is_space_m(c);
fn bool char.is_alnum(char c) => is_alnum_m(c);
fn bool char.is_punct(char c) => is_punct_m(c);
fn bool char.is_blank(char c) => is_blank_m(c);
fn bool char.is_cntrl(char c) => is_cntrl_m(c);
fn char char.to_lower(char c) => (char)to_lower_m(c);
fn char char.to_upper(char c) => (char)to_upper_m(c);
<*
@require c.is_xdigit()
*>
fn char char.from_hex(char c) => c.is_digit() ? c - '0' : 10 + (c | 0x20) - 'a';
fn bool uint.in_range(uint c, uint start, uint len) => in_range_m(c, start, len);
fn bool uint.is_lower(uint c) => is_lower_m(c);
fn bool uint.is_upper(uint c) => is_upper_m(c);
fn bool uint.is_digit(uint c) => is_digit_m(c);
fn bool uint.is_bdigit(uint c) => is_bdigit_m(c);
fn bool uint.is_odigit(uint c) => is_odigit_m(c);
fn bool uint.is_xdigit(uint c) => is_xdigit_m(c);
fn bool uint.is_alpha(uint c) => is_alpha_m(c);
fn bool uint.is_print(uint c) => is_print_m(c);
fn bool uint.is_graph(uint c) => is_graph_m(c);
fn bool uint.is_space(uint c) => is_space_m(c);
fn bool uint.is_alnum(uint c) => is_alnum_m(c);
fn bool uint.is_punct(uint c) => is_punct_m(c);
fn bool uint.is_blank(uint c) => is_blank_m(c);
fn bool uint.is_cntrl(uint c) => is_cntrl_m(c);
fn uint uint.to_lower(uint c) => (uint)to_lower_m(c);
fn uint uint.to_upper(uint c) => (uint)to_upper_m(c);
fn bool uint.is_lower(uint c) @deprecated => is_lower_m(c);
fn bool uint.is_upper(uint c) @deprecated => is_upper_m(c);
fn bool uint.is_digit(uint c) @deprecated => is_digit_m(c);
fn bool uint.is_bdigit(uint c) @deprecated => is_bdigit_m(c);
fn bool uint.is_odigit(uint c) @deprecated => is_odigit_m(c);
fn bool uint.is_xdigit(uint c) @deprecated => is_xdigit_m(c);
fn bool uint.is_alpha(uint c) @deprecated => is_alpha_m(c);
fn bool uint.is_print(uint c) @deprecated => is_print_m(c);
fn bool uint.is_graph(uint c) @deprecated => is_graph_m(c);
fn bool uint.is_space(uint c) @deprecated => is_space_m(c);
fn bool uint.is_alnum(uint c) @deprecated => is_alnum_m(c);
fn bool uint.is_punct(uint c) @deprecated => is_punct_m(c);
fn bool uint.is_blank(uint c) @deprecated => is_blank_m(c);
fn bool uint.is_cntrl(uint c) @deprecated => is_cntrl_m(c);
fn uint uint.to_lower(uint c) @deprecated => (uint)to_lower_m(c);
fn uint uint.to_upper(uint c) @deprecated => (uint)to_upper_m(c);

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,11 +171,13 @@ macro bool is_native_atomic_type($Type)
$case SIGNED_INT:
$case UNSIGNED_INT:
$case POINTER:
$case FUNC:
$case FLOAT:
$case BOOL:
return true;
$case DISTINCT:
return is_native_atomic_type($typefrom($Type.inner));
$case CONST_ENUM:
return is_native_atomic_type($Type.inner);
$default:
return false;
$endswitch
@@ -183,7 +185,7 @@ macro bool is_native_atomic_type($Type)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be added to ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -192,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)
{
@@ -203,7 +205,7 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be subtracted from ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -212,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)
{
@@ -223,7 +225,7 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be multiplied with ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -231,16 +233,16 @@ 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 = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
$StorageType* storage_ptr = ($StorageType*)ptr;
@@ -263,7 +265,7 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to divide ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -271,16 +273,16 @@ 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 = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
$StorageType* storage_ptr = ($StorageType*)ptr;
@@ -303,7 +305,7 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise or with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -312,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)
{
@@ -320,7 +322,7 @@ macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise xor with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -329,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)
{
@@ -337,7 +339,7 @@ macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise and with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -346,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)
{
@@ -354,7 +356,7 @@ macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to shift ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -363,16 +365,16 @@ 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 = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
$StorageType* storage_ptr = ($StorageType*)ptr;
@@ -396,7 +398,7 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to shift ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -405,16 +407,16 @@ 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 = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
$StorageType* storage_ptr = ($StorageType*)ptr;
@@ -438,7 +440,7 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -446,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)
{
@@ -454,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
{
@@ -466,14 +468,14 @@ macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
@require $ordering != 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)
{
@@ -481,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
{
@@ -501,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)
{
@@ -520,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

@@ -6,7 +6,7 @@ import std::io;
struct EnumMap (Printable)
{
ValueType[Enum.len] values;
ValueType[Enum.values.len] values;
}
fn void EnumMap.init(&self, ValueType init_value)

View File

@@ -8,9 +8,10 @@
module std::collections::enumset{Enum};
import std::io;
alias EnumSetType @private = $typefrom(type_for_enum_elements(Enum.elements));
const ENUM_COUNT @private = Enum.values.len;
alias EnumSetType @private = $typefrom(type_for_enum_elements(ENUM_COUNT));
const IS_CHAR_ARRAY = Enum.elements > 128;
const IS_CHAR_ARRAY = ENUM_COUNT > 128;
typedef EnumSet (Printable) = EnumSetType;
fn void EnumSet.add(&self, Enum v)

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

@@ -34,3 +34,12 @@ macro Type? Maybe.get(self)
{
return self.has_value ? self.value : NOT_FOUND?;
}
fn bool Maybe.equals(self, Maybe other) @operator(==) @if(types::is_equatable_type(Type))
{
if (self.has_value)
{
return other.has_value && equals(self.value, other.value);
}
return !other.has_value;
}

View File

@@ -178,14 +178,15 @@ 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);
}
<*
@require self.allocator != null : "This object is not properly initialized, was it really created using 'new'"
@require !@typeis(value, void*) ||| value == null : "void pointers cannot be stored in an object"
*>
macro Object* Object.object_from_value(&self, value) @private
{
var $Type = $typeof(value);
@@ -201,9 +202,8 @@ macro Object* Object.object_from_value(&self, value) @private
$case $Type.typeid == Object*.typeid:
return value;
$case $Type.typeid == void*.typeid:
if (value != null) return TYPE_MISMATCH?;
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

@@ -37,7 +37,7 @@ fn void ArenaAllocator.clear(&self)
<*
Given some memory, create an arena allocator on the stack for it.
@param [inout] bytes : `The bytes to use`
@param [inout] bytes : `The bytes to use, may be empty.`
@return `An arena allocator using the bytes`
*>
@@ -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

@@ -1,12 +1,15 @@
module std::core::mem::allocator;
import std::io, std::math;
struct AllocChunk @local
{
usz size;
char[*] data;
}
<*
The backed arena allocator provides an allocator that will allocate from a pre-allocated chunk of memory
provided by it's backing allocator. The allocator supports mark / reset operations, so it can be used
as a stack (push-pop) allocator. If the initial memory is used up, it will fall back to regular allocations,
that will be safely freed on `reset`.
While this allocator is similar to the dynamic arena, it supports multiple "save points", which the dynamic arena
doesn't.
*>
struct BackedArenaAllocator (Allocator)
{
Allocator backing_allocator;
@@ -16,6 +19,12 @@ struct BackedArenaAllocator (Allocator)
char[*] data;
}
struct AllocChunk @local
{
usz size;
char[*] data;
}
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
struct ExtraPage @local
@@ -128,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

@@ -4,6 +4,17 @@
module std::core::mem::allocator;
import std::math;
<*
The dynamic arena allocator is an arena allocator that can grow by adding additional arena "pages".
It only supports reset, at which point all pages except the first one is released to the backing
allocator.
If you want multiple save points, use the BackedArenaAllocator instead.
The advantage over the BackedArenaAllocator, is that when allocating beyond the first "page", it will
retain the characteristics of an arena allocator (allocating a large piece of memory then handing off
memory from that memory), wheras the BackedArenaAllocator will have heap allocator characteristics.
*>
struct DynamicArenaAllocator (Allocator)
{
Allocator backing_allocator;
@@ -106,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

@@ -5,6 +5,13 @@
module std::core::mem::allocator;
import std::math;
<*
The SimpleHeapAllocator implements a simple heap allocator on top of an allocator function.
It uses the given allocator function to allocate memory from some source, but never frees it.
This allocator is intended to be used in environments where there isn't any native libc malloc,
and it has to be emulated from a memory region, or wrapping linear memory as is the case for plain WASM.
*>
struct SimpleHeapAllocator (Allocator)
{
MemoryAllocFn alloc_fn;

View File

@@ -1,16 +1,15 @@
// Copyright (c) 2021-2024 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::core::mem::allocator @if(env::LIBC);
import std::io;
import libc;
<*
The LibcAllocator is a wrapper around malloc to conform to the Allocator interface.
*>
typedef LibcAllocator (Allocator) = uptr;
const LibcAllocator LIBC_ALLOCATOR = {};
typedef LibcAllocator (Allocator, Printable) = uptr;
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
fn usz? LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
module std::core::mem::allocator @if(env::POSIX);
import std::os;
@@ -123,7 +122,7 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
if (init_type == ZERO)
{
void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1);
return data ?: mem::OUT_OF_MEMORYY?;
return data ?: mem::OUT_OF_MEMORY?;
}
else
{

View File

@@ -1,5 +1,14 @@
module std::core::mem::allocator;
import std::math;
<*
The OnStackAllocator is similar to the ArenaAllocator: it allocates from a chunk of memory
given to it.
The difference is that when it runs out of memory it will go directly to its backing allocator
rather than failing.
It is utilized by the @stack_mem macro as an alternative to the temp allocator.
*>
struct OnStackAllocator (Allocator)
{
Allocator backing_allocator;
@@ -8,7 +17,6 @@ struct OnStackAllocator (Allocator)
OnStackAllocatorExtraChunk* chunk;
}
struct OnStackAllocatorExtraChunk @local
{
bool is_aligned;
@@ -116,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
@@ -35,6 +34,9 @@ struct TempAllocator (Allocator)
TempAllocatorPage* last_page;
TempAllocator* derived;
bool allocated;
usz reserve_size;
usz realloc_size;
usz min_size;
usz used;
usz capacity;
usz original_capacity;
@@ -47,7 +49,7 @@ struct TempAllocatorChunk @local
char[*] data;
}
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
struct TempAllocatorPage
{
@@ -62,15 +64,20 @@ macro usz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
<*
@require size >= 16
@require size >= 64
@require realloc_size >= 64
@require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator."
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
*>
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size)
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)
{
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
temp.last_page = null;
temp.backing_allocator = allocator;
temp.used = 0;
temp.min_size = min_size;
temp.realloc_size = realloc_size;
temp.reserve_size = reserve;
temp.allocated = true;
temp.derived = null;
temp.original_capacity = temp.capacity = size;
@@ -78,19 +85,18 @@ fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size)
}
<*
@require !self.derived
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
@require mult > 0 : "The multiple can never be zero"
*>
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz min_size, usz buffer, usz mult)
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0)
{
if (!reserve) reserve = self.reserve_size;
usz remaining = self.capacity - self.used;
void* mem @noinit;
usz size @noinit;
if (min_size + buffer > remaining)
if (self.min_size + reserve > remaining)
{
return self.derived = new_temp_allocator(self.backing_allocator, min_size * mult)!;
return self.derived = new_temp_allocator(self.backing_allocator, self.realloc_size, self.reserve_size, self.min_size, self.realloc_size)!;
}
usz start = mem::aligned_offset(self.used + buffer, mem::DEFAULT_MEM_ALIGNMENT);
usz start = mem::aligned_offset(self.used + reserve, mem::DEFAULT_MEM_ALIGNMENT);
void* ptr = &self.data[start];
TempAllocator* temp = (TempAllocator*)ptr;
$if env::ADDRESS_SANITIZER:
@@ -99,6 +105,9 @@ fn TempAllocator*? TempAllocator.derive_allocator(&self, usz min_size, usz buffe
temp.last_page = null;
temp.backing_allocator = self.backing_allocator;
temp.used = 0;
temp.min_size = self.min_size;
temp.reserve_size = self.reserve_size;
temp.realloc_size = self.realloc_size;
temp.allocated = false;
temp.derived = null;
temp.original_capacity = temp.capacity = self.capacity - start - TempAllocator.sizeof;
@@ -317,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);
}

114
lib/std/core/ascii.c3 Normal file
View File

@@ -0,0 +1,114 @@
<*
This module contains utils for handling ASCII characters. They only operate on
characters corresponding to 0-127.
*>
module std::core::ascii;
macro bool @is_lower(c) => ASCII_LOOKUP[c].lower; // Is a-z
macro bool @is_upper(c) => ASCII_LOOKUP[c].upper; // Is A-Z
macro bool @is_digit(c) => ASCII_LOOKUP[c].digit; // Is 0-9
macro bool @is_bdigit(c) => ASCII_LOOKUP[c].bin_digit; // Is 0-1
macro bool @is_odigit(c) => ASCII_LOOKUP[c].oct_digit; // Is 0-7
macro bool @is_xdigit(c) => ASCII_LOOKUP[c].hex_digit; // Is 0-9 or a-f or A-F
macro bool @is_alpha(c) => ASCII_LOOKUP[c].alpha; // Is a-z or A-Z
macro bool @is_print(c) => ASCII_LOOKUP[c].printable; // Is a printable character (space or higher and < 127
macro bool @is_graph(c) => ASCII_LOOKUP[c].graph; // Does it show any graphics (printable but not space)
macro bool @is_space(c) => ASCII_LOOKUP[c].space; // Is it a space character: space, tab, linefeed etc
macro bool @is_alnum(c) => ASCII_LOOKUP[c].alphanum; // Is it alpha or digit
macro bool @is_punct(c) => ASCII_LOOKUP[c].punct; // Is it "graph" but not digit or letter
macro bool @is_blank(c) => ASCII_LOOKUP[c].blank; // Is it a blank space: space or tab
macro bool @is_cntrl(c) => ASCII_LOOKUP[c].control; // Is it a control character: before space or 127
macro char @to_lower(c) => c + TO_LOWER[c]; // Convert A-Z to a-z if found
macro char @to_upper(c) => c - TO_UPPER[c]; // Convert a-z to A-Z if found
fn bool is_lower(char c) => @is_lower(c); // Is a-z
fn bool is_upper(char c) => @is_upper(c); // Is A-Z
fn bool is_digit(char c) => @is_digit(c); // Is 0-9
fn bool is_bdigit(char c) => @is_bdigit(c); // Is 0-1
fn bool is_odigit(char c) => @is_odigit(c); // Is 0-7
fn bool is_xdigit(char c) => @is_xdigit(c); // Is 0-9 or a-f or A-F
fn bool is_alpha(char c) => @is_alpha(c); // Is a-z or A-Z
fn bool is_print(char c) => @is_print(c); // Is a printable character (space or higher and < 127
fn bool is_graph(char c) => @is_graph(c); // Does it show any graphics (printable but not space)
fn bool is_space(char c) => @is_space(c); // Is it a space character: space, tab, linefeed etc
fn bool is_alnum(char c) => @is_alnum(c); // Is it alpha or digit
fn bool is_punct(char c) => @is_punct(c); // Is it "graph" but not digit or letter
fn bool is_blank(char c) => @is_blank(c); // Is it a blank space: space or tab
fn bool is_cntrl(char c) => @is_cntrl(c); // Is it a control character: before space or 127
fn char to_lower(char c) => @to_lower(c); // Convert A-Z to a-z if found
fn char to_upper(char c) => @to_upper(c); // Convert a-z to A-Z if found
// The following methods are macro methods for the same functions
macro bool char.is_lower(char c) => @is_lower(c);
macro bool char.is_upper(char c) => @is_upper(c);
macro bool char.is_digit(char c) => @is_digit(c);
macro bool char.is_bdigit(char c) => @is_bdigit(c);
macro bool char.is_odigit(char c) => @is_odigit(c);
macro bool char.is_xdigit(char c) => @is_xdigit(c);
macro bool char.is_alpha(char c) => @is_alpha(c);
macro bool char.is_print(char c) => @is_print(c);
macro bool char.is_graph(char c) => @is_graph(c);
macro bool char.is_space(char c) => @is_space(c);
macro bool char.is_alnum(char c) => @is_alnum(c);
macro bool char.is_punct(char c) => @is_punct(c);
macro bool char.is_blank(char c) => @is_blank(c);
macro bool char.is_cntrl(char c) => @is_cntrl(c);
macro char char.to_lower(char c) => @to_lower(c);
macro char char.to_upper(char c) => @to_upper(c);
<*
Convert a-f/A-F/0-9 to the appropriate hex value.
@require c.is_xdigit()
@ensure return >= 0 && return <= 15
*>
macro char char.from_hex(char c) => HEX_VALUE[c];
<*
Bitstruct containing the different properties of a character
*>
bitstruct CharType : ushort @private
{
bool lower;
bool upper;
bool digit;
bool bin_digit;
bool hex_digit;
bool oct_digit;
bool alpha;
bool alphanum;
bool space;
bool printable;
bool blank;
bool punct;
bool control;
bool graph;
}
const CharType[256] ASCII_LOOKUP @private = {
[0..31] = { .control },
[9..13] = { .control, .space },
['\t'] = { .control, .space, .blank },
[' '] = { .space, .printable, .blank },
[33..126] = { .printable, .graph, .punct },
['0'..'9'] = { .printable, .graph, .alphanum, .hex_digit, .digit },
['2'..'7'] = { .printable, .graph, .alphanum, .hex_digit, .digit, .oct_digit },
['0'..'1'] = { .printable, .graph, .alphanum, .hex_digit, .digit, .oct_digit, .bin_digit },
['A'..'Z'] = { .printable, .graph, .alphanum, .alpha, .upper },
['A'..'F'] = { .printable, .graph, .alphanum, .alpha, .upper, .hex_digit },
['a'..'z'] = { .printable, .graph, .alphanum, .alpha, .lower },
['a'..'f'] = { .printable, .graph, .alphanum, .alpha, .lower, .hex_digit },
[127] = { .control },
};
const char[256] HEX_VALUE = {
['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4,
['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9,
['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14,
['F'] = 15, ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13,
['e'] = 14, ['f'] = 15
};
const char[256] TO_UPPER @private = { ['a'..'z'] = 'a' - 'A' };
const char[256] TO_LOWER @private = { ['A'..'Z'] = 'a' - 'A' };

View File

@@ -88,33 +88,33 @@ bitstruct UInt128LE : uint128 @littleendian
}
<*
@require is_array_or_slice_of_char(bytes) : "argument must be an array, a pointer to an array or a slice of char"
@require @is_array_or_slice_of_char(bytes) : "argument must be an array, a pointer to an array or a slice of char"
@require is_bitorder($Type) : "type must be a bitorder integer"
*>
macro read(bytes, $Type)
{
char[] s;
$switch @typekind(bytes):
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
s = bytes[:$Type.sizeof];
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
s = bytes[:$Type.sizeof];
$endswitch
return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
}
<*
@require is_arrayptr_or_slice_of_char(bytes) : "argument must be a pointer to an array or a slice of char"
@require @is_arrayptr_or_slice_of_char(bytes) : "argument must be a pointer to an array or a slice of char"
@require is_bitorder($Type) : "type must be a bitorder integer"
*>
macro write(x, bytes, $Type)
{
char[] s;
$switch @typekind(bytes):
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
s = bytes[:$Type.sizeof];
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
s = bytes[:$Type.sizeof];
$endswitch
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
}
@@ -122,59 +122,63 @@ macro write(x, bytes, $Type)
macro is_bitorder($Type)
{
$switch $Type:
$case UShortLE:
$case ShortLE:
$case UIntLE:
$case IntLE:
$case ULongLE:
$case LongLE:
$case UInt128LE:
$case Int128LE:
$case UShortBE:
$case ShortBE:
$case UIntBE:
$case IntBE:
$case ULongBE:
$case LongBE:
$case UInt128BE:
$case Int128BE:
return true;
$default:
return false;
$case UShortLE:
$case ShortLE:
$case UIntLE:
$case IntLE:
$case ULongLE:
$case LongLE:
$case UInt128LE:
$case Int128LE:
$case UShortBE:
$case ShortBE:
$case UIntBE:
$case IntBE:
$case ULongBE:
$case LongBE:
$case UInt128BE:
$case Int128BE:
return true;
$default:
return false;
$endswitch
}
macro bool is_array_or_slice_of_char(bytes)
macro bool is_array_or_slice_of_char(bytes) @deprecated("Use @is_array_or_slice_of_char")
{
$switch @typekind(bytes):
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:
var $Inner2 = $typefrom($Inner.inner);
return $Inner2.typeid == char.typeid;
$endif
$case ARRAY:
$case SLICE:
var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid;
$default:
return false;
return @is_array_or_slice_of_char(bytes);
}
macro bool @is_array_or_slice_of_char(#bytes) @const
{
var $Type = $typeof(#bytes);
$switch $Type.kindof:
$case POINTER:
typeid $inner = $Type.inner;
return $inner.kindof == ARRAY &&& $inner.inner == char.typeid;
$case ARRAY:
$case SLICE:
return $Type.inner == char.typeid;
$default:
return false;
$endswitch
}
macro bool is_arrayptr_or_slice_of_char(bytes)
macro bool is_arrayptr_or_slice_of_char(bytes) @deprecated("Use @is_arrayptr_or_slice_of_char")
{
$switch @typekind(bytes):
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:
var $Inner2 = $typefrom($Inner.inner);
return $Inner2.typeid == char.typeid;
$endif
$case SLICE:
var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid;
$default:
return false;
return @is_arrayptr_or_slice_of_char(bytes);
}
macro bool @is_arrayptr_or_slice_of_char(#bytes) @const
{
var $Type = $typeof(#bytes);
$switch $Type.kindof:
$case POINTER:
typeid $inner = $Type.inner;
return $inner.kindof == ARRAY &&& $inner.inner == char.typeid;
$case SLICE:
return $Type.inner == char.typeid;
$default:
return false;
$endswitch
}

View File

@@ -30,21 +30,37 @@ typedef EmptySlot = void*;
macro @is_empty_macro_slot(#arg) @const @builtin => @typeis(#arg, EmptySlot);
macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
<*
Returns a random value at compile time.
@ensure return >= 0.0 && return < 1.0
@return "A compile time random"
*>
macro @rnd() @const @builtin => $$rnd();
/*
Use `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();
@@ -88,58 +104,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();
@@ -147,20 +185,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();
}
@@ -170,21 +210,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
}
<*
@@ -196,8 +238,9 @@ macro void unreachable(String string = "Unreachable statement reached.", ...) @b
{
$if env::COMPILER_SAFE_MODE:
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
$endif;
$else
$$unreachable();
$endif
}
<*
@@ -272,13 +315,12 @@ 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
*>
macro @enum_from_value($Type, #value, value) @builtin
macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.lookup_field and Enum.lookup")
{
usz elements = $Type.elements;
foreach (e : $Type.values)
{
if (e.#value == value) return e;
@@ -326,7 +368,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
@@ -376,6 +418,7 @@ macro swizzle2(v, v2, ...) @builtin
{
return $$swizzle2(v, v2, $vasplat);
}
<*
Return the excuse in the Optional if it is Empty, otherwise
return a null fault.
@@ -400,6 +443,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"
*>
@@ -469,8 +573,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));
<*
@@ -478,7 +582,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
}
<*
@@ -486,7 +596,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

@@ -112,7 +112,7 @@ fn void DString.replace(&self, String needle, String replacement)
};
}
fn DString DString.concat(self, Allocator allocator, DString b)
fn DString DString.concat(self, Allocator allocator, DString b) @nodiscard
{
DString string;
string.init(allocator, self.len() + b.len());
@@ -233,7 +233,7 @@ fn usz DString.append_char32(&self, Char32 c)
fn DString DString.tcopy(&self) => self.copy(tmem);
fn DString DString.copy(self, Allocator allocator)
fn DString DString.copy(self, Allocator allocator) @nodiscard
{
if (!self) return new(allocator);
StringData* data = self.data();
@@ -242,7 +242,7 @@ fn DString DString.copy(self, Allocator allocator)
return new_string;
}
fn ZString DString.copy_zstr(self, Allocator allocator)
fn ZString DString.copy_zstr(self, Allocator allocator) @nodiscard
{
usz str_len = self.len();
if (!str_len)
@@ -256,12 +256,12 @@ fn ZString DString.copy_zstr(self, Allocator allocator)
return (ZString)zstr;
}
fn String DString.copy_str(self, Allocator allocator)
fn String DString.copy_str(self, Allocator allocator) @nodiscard
{
return (String)self.copy_zstr(allocator)[:self.len()];
}
fn String DString.tcopy_str(self) => self.copy_str(tmem) @inline;
fn String DString.tcopy_str(self) @nodiscard => self.copy_str(tmem) @inline;
fn bool DString.equals(self, DString other_string)
{
@@ -571,7 +571,7 @@ fn usz? DString.appendfn(&self, String format, args...) @maydiscard
};
}
fn DString join(Allocator allocator, String[] s, String joiner)
fn DString join(Allocator allocator, String[] s, String joiner) @nodiscard
{
if (!s.len) return new(allocator);
usz total_size = joiner.len * s.len;
@@ -581,10 +581,10 @@ fn DString join(Allocator allocator, String[] s, String joiner)
}
DString res = new_with_capacity(allocator, total_size);
res.append(s[0]);
foreach (String* &str : s[1..])
foreach (String str : s[1..])
{
res.append(joiner);
res.append(*str);
res.append(str);
}
return res;
}

View File

@@ -120,6 +120,7 @@ const String COMPILER_BUILD_HASH = $$BUILD_HASH;
const String COMPILER_BUILD_DATE = $$BUILD_DATE;
const OsType OS_TYPE = OsType.from_ordinal($$OS_TYPE);
const ArchType ARCH_TYPE = ArchType.from_ordinal($$ARCH_TYPE);
const usz MAX_VECTOR_SIZE = $$MAX_VECTOR_SIZE;
const bool ARCH_32_BIT = $$REGISTER_SIZE == 32;
const bool ARCH_64_BIT = $$REGISTER_SIZE == 64;
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
@@ -136,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;
@@ -158,6 +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::OPENBSD || env::DARWIN || env::WIN32;
macro bool os_is_darwin() @const
{
@@ -196,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);
@@ -576,11 +604,20 @@ fn void temp_pop(PoolState old_state)
allocator::pop_pool(old_state) @inline;
}
macro void @pool_init(Allocator allocator, usz pool_size, usz buffer_size; @body) @builtin
<*
@require pool_size >= 64
@require realloc_size >= 64
@require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator."
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
*>
macro void @pool_init(Allocator allocator, usz pool_size,
usz reserve_size = allocator::temp_allocator_reserve_size,
usz min_size = allocator::temp_allocator_min_size,
usz realloc_size = allocator::temp_allocator_realloc_size; @body) @builtin
{
Allocator current = allocator::current_temp;
TempAllocator* top = allocator::top_temp;
allocator::create_temp_allocator(allocator, pool_size, buffer_size);
allocator::create_temp_allocator(allocator, pool_size, reserve_size, min_size, realloc_size);
defer
{
allocator::destroy_temp_allocators();
@@ -589,9 +626,19 @@ macro void @pool_init(Allocator allocator, usz pool_size, usz buffer_size; @body
}
@body();
}
macro void @pool(;@body) @builtin
<*
Create a new temporary allocator.
The `reserve` parameter allows you to determine how many bytes should be reserved for
allocations on the current temporary allocator, if allocations are made inside of the pool scope.
It is made available for optimization, and can usually be ignored.
@param reserve : "The amount of bytes to reserve for out-of-order allocations, 0 gives the default."
*>
macro void @pool(usz reserve = 0; @body) @builtin
{
PoolState state = allocator::push_pool() @inline;
PoolState state = allocator::push_pool(reserve) @inline;
defer
{
allocator::pop_pool(state) @inline;
@@ -681,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
@@ -697,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
@@ -715,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
{
@@ -755,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
{
@@ -770,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
{
@@ -884,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")
@@ -921,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,6 +1,19 @@
module std::core::mem::allocator;
import std::math;
// C3 has several different allocators available:
//
// Name Arena Uses buffer OOM Fallback? Mark? Reset?
// ArenaAllocator Yes Yes No Yes Yes
// BackedArenaAllocator Yes No Yes Yes Yes
// DynamicArenaAllocator Yes No Yes No Yes
// HeapAllocator No No No No No *Note: Not for normal use
// LibcAllocator No No No No No *Note: Wraps malloc
// OnStackAllocator Yes Yes Yes No No *Note: Used by @stack_mem
// TempAllocator Yes No Yes No* No* *Note: Mark/reset using @pool
// TrackingAllocator No No N/A No No *Note: Wraps other heap allocator
// Vmem Yes No No Yes Yes *Note: Can be set to huge sizes
const DEFAULT_SIZE_PREFIX = usz.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
@@ -20,13 +33,18 @@ enum AllocInitType
interface Allocator
{
<*
Acquire memory from the allocator, with the given alignment and initialization type.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require size > 0
@require size > 0 : "The size must be 1 or more"
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? acquire(usz size, AllocInitType init_type, usz alignment = 0);
<*
Resize acquired memory from the allocator, with the given new size and alignment.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require ptr != null
@@ -34,8 +52,11 @@ interface Allocator
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? resize(void* ptr, usz new_size, usz alignment = 0);
<*
@require ptr != null
Release memory acquired using `acquire` or `resize`.
@require ptr != null : "Empty pointers should never be released"
*>
fn void release(void* ptr, bool aligned);
}
@@ -44,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;
}
@@ -146,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
{
@@ -162,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
{
@@ -179,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
{
@@ -387,13 +408,13 @@ tlocal TempAllocator* top_temp;
tlocal bool auto_create_temp = false;
usz temp_allocator_min_size = temp_allocator_default_min_size();
usz temp_allocator_buffer_size = temp_allocator_default_buffer_size();
usz temp_allocator_new_mult = 4;
usz temp_allocator_reserve_size = temp_allocator_default_reserve_size();
usz temp_allocator_realloc_size = temp_allocator_default_min_size() * 4;
fn PoolState push_pool()
fn PoolState push_pool(usz reserve = 0)
{
Allocator old = top_temp ? current_temp : create_temp_allocator_on_demand();
current_temp = ((TempAllocator*)old).derive_allocator(temp_allocator_min_size, temp_allocator_buffer_size, temp_allocator_new_mult)!!;
current_temp = ((TempAllocator*)old).derive_allocator(reserve)!!;
return (PoolState)old.ptr;
}
@@ -433,7 +454,7 @@ macro usz temp_allocator_default_min_size() @local
$endswitch
}
macro usz temp_allocator_default_buffer_size() @local
macro usz temp_allocator_default_reserve_size() @local
{
$switch env::MEMORY_ENV:
$case NORMAL: return 1024;
@@ -443,7 +464,7 @@ macro usz temp_allocator_default_buffer_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"
@@ -455,17 +476,18 @@ fn Allocator create_temp_allocator_on_demand() @private
auto_create_temp = true;
abort("Use '@pool_init()' to enable the temp allocator on a new thread. A temp allocator is only implicitly created on the main thread.");
}
return create_temp_allocator(temp_base_allocator, temp_allocator_size());
return create_temp_allocator(temp_base_allocator, temp_allocator_size(), temp_allocator_reserve_size, temp_allocator_min_size, temp_allocator_realloc_size);
}
<*
@require !top_temp : "This should never be called when temp already exists"
*>
fn Allocator create_temp_allocator(Allocator allocator, usz size, usz buffer = temp_allocator_default_buffer_size()) @private
fn Allocator create_temp_allocator(Allocator allocator, usz size, usz reserve, usz min_size, usz realloc_size) @private
{
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size)!!;
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;
}
}

View File

@@ -143,7 +143,7 @@ uint128 x86_features;
fn void add_feature_if_bit(X86Feature feature, uint register, int bit)
{
if (register & 1U << bit) x86_features |= 1u128 << feature.ordinal;
if (register & 1U << bit) x86_features |= 1ULL << feature.ordinal;
}
fn void x86_initialize_cpu_features()

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

@@ -24,11 +24,17 @@ struct SliceRaw
macro @enum_lookup($Type, #value, value)
{
var $elements = $Type.elements;
$for var $i = 0; $i < $elements; $i++:
var $val = $Type.from_ordinal($i);
$foreach $val : $Type.values:
if ($val.#value == value) return $val;
$endfor
$endforeach
return NOT_FOUND?;
}
macro @enum_lookup_new($Type, $name, value)
{
$foreach $val : $Type.values:
if ($val.$eval($name) == value) return $val;
$endforeach
return NOT_FOUND?;
}

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,46 +1,89 @@
module std::core::string;
import std::ascii;
import std::io;
import std::core::mem::allocator;
typedef String @if(!$defined(String)) = inline char[];
<*
ZString is a pointer to a zero terminated array of chars.
Use ZString when you need to interop with C zero terminated strings.
*>
typedef ZString = inline char*;
<*
WString is a pointer to a zero terminated array of Char16.
Depending on the platform, this may or may not correspond to wchar_t.
For Windows, wchar_t is generally 16 bits, on MacOS it is 32 bits.
However, for both MacOS and Linux, regular C strings (ZString)
will be UTF-8 encoded, so there is no need to use the wchar_t versions
of functions outside of encoding functions.
*>
typedef WString = inline Char16*;
<*
Char32 is a UTF32 codepoint
*>
alias Char32 = uint;
<*
Char16 is a UTF16 "character"
*>
alias Char16 = ushort;
<*
Common faults used with strings
*>
faultdef INVALID_UTF8, INVALID_UTF16, CONVERSION_FAILED,
EMPTY_STRING, NEGATIVE_VALUE, MALFORMED_INTEGER,
INTEGER_OVERFLOW, MALFORMED_FLOAT, FLOAT_OUT_OF_RANGE;
const uint SURROGATE_OFFSET @private = 0x10000;
const uint SURROGATE_GENERIC_MASK @private = 0xF800;
const uint SURROGATE_MASK @private = 0xFC00;
const uint SURROGATE_CODEPOINT_MASK @private = 0x03FF;
const uint SURROGATE_BITS @private = 10;
const uint SURROGATE_LOW_VALUE @private = 0xDC00;
const uint SURROGATE_HIGH_VALUE @private = 0xD800;
<*
Create a pointer to an UTF32 encoded string at compile time.
@param $string : "The string to encode"
*>
macro Char32* @wstring32(String $string) @builtin
{
return (Char32*)&&$$wstr32($string);
}
<*
Create a slice of an UTF32 encoded string at compile time.
@param $string : "The string to encode"
*>
macro Char32[] @char32(String $string) @builtin
{
return $$wstr32($string)[..^2];
}
<*
Create a WString (an UTF16 encoded string) at compile time.
@param $string : "The string to encode"
*>
macro WString @wstring(String $string) @builtin
{
return (WString)&&$$wstr16($string);
}
<*
Create a slice of an UTF32 encoded string at compile time.
@param $string : "The string to encode"
*>
macro Char16[] @char16(String $string) @builtin
{
return $$wstr16($string)[..^2];
}
macro String @sprintf(String $format, ...) @builtin @const
{
return $$sprintf($format, $vasplat);
}
<*
Return a temporary ZString created using the formatting function.
@@ -66,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.
@@ -117,6 +173,39 @@ fn String join(Allocator allocator, String[] s, String joiner)
};
}
<*
Replace all instances of one substring with a different string.
@param [in] self
@param [in] needle : `The string to be replaced`
@param [in] new_str : `The replacement string`
@param [&inout] allocator : `The allocator to use for the String`
@return "The new string with the elements replaced"
*>
fn String String.replace(self, Allocator allocator, String needle, String new_str) @nodiscard
{
@pool()
{
String[] split = self.tsplit(needle);
return dstring::join(tmem, split, new_str).copy_str(mem);
};
}
<*
Replace all instances of one substring with a different string, allocating the new string on the temp allocator.
@param [in] self
@param [in] needle : `The string to be replaced`
@param [in] new_str : `The replacement string`
@return "The new string with the elements replaced"
*>
fn String String.treplace(self, String needle, String new_str)
{
String[] split = self.tsplit(needle);
return dstring::join(tmem, split, new_str).str_view();
}
<*
Remove characters from the front and end of a string.
@@ -231,7 +320,7 @@ fn String String.strip_end(self, String suffix)
@param [&inout] allocator : "The allocator to use for the String[]"
@require delimiter.len > 0 : "The delimiter must be at least 1 character long"
@ensure return.len > 0
@ensure return.len > 0 || skip_empty
*>
fn String[] String.split(self, Allocator allocator, String delimiter, usz max = 0, bool skip_empty = false)
{
@@ -290,7 +379,7 @@ faultdef BUFFER_EXCEEDED;
@param [inout] buffer
@param max : "Max number of elements, 0 means no limit, defaults to 0"
@require delimiter.len > 0 : "The delimiter must be at least 1 character long"
@ensure return.len > 0
@ensure return.len > 0 || skip_empty
@return? BUFFER_EXCEEDED : `If there are more elements than would fit the buffer`
*>
fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz max = 0, bool skip_empty = false)
@@ -338,6 +427,38 @@ fn bool String.contains(s, String substr)
return @ok(s.index_of(substr));
}
<*
Check how many non-overlapping instances of a substring there is.
If the substring has zero length, the number of matches is zero.
@param [in] self : "The string to search"
@param [in] substr : "The string to look for."
@pure
@return "The number of times matched"
*>
fn usz String.count(self, String substr)
{
usz count = 0;
usz needed = substr.len;
if (needed == 0) return 0;
char first = substr[0];
while OUTER: (self.len >= needed)
{
foreach (i, c: self[..^needed])
{
if (c == first && self[i : needed] == substr)
{
count++;
self = self[i + needed..];
continue OUTER;
}
}
break;
}
return count;
}
<*
Find the index of the first incidence of a string.
@@ -471,6 +592,20 @@ fn usz? String.rindex_of(self, String substr)
return NOT_FOUND?;
}
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;
if (c != *b) return false;
if (!c) return true;
}
}
fn String ZString.str_view(self)
{
return (String)(self[:self.len()]);
@@ -489,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)
{
@@ -637,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 };
@@ -692,16 +918,63 @@ fn usz String.utf8_codepoints(s)
return len;
}
<*
Determine whether the current string actually points to a ZString-like string.
This is done by looking at the byte one step after the end of the string. If this
is zero, it is considered zero terminated.
This function can safely be used with data pointing to null. However, it will not
work correctly if the pointer is invalid, for example it is already freed.
*>
fn bool String.is_zstr(self) @deprecated("Unsafe, use copy instead")
{
return self.ptr && *(self.ptr + self.len) == 0;
}
<*
@require (base <= 10 && base > 1) || base == 16 : "Unsupported base"
Return a pointer to the string *iff* it is a pointer
to a zero terminated string, otherwise return a temp allocated zstring copy.
This function is suitable if you are converting strings to ZString on the temp
allocator, but suspect that the String might actually already point to zero
terminated data.
The function looks one step beyond the end of the slice to determine this,
which means that if that data is then modified after this call, this function
might behave incorrectly.
For this reason, try to ensure that the resulting ZString is immediately used.
@ensure return[self.len] == 0
*>
fn ZString String.quick_zstr(self) @deprecated("Unsafe, use zstr_tcopy instead")
{
return self.zstr_tcopy();
}
<*
Convert a number to a given base. If the base is not given, then
it will be inferred from the number if the string starts with 0x 0o or 0b and the
base is given as 10.
Furthermore it will skip any spaces before and after the number.
@param $Type : "The type to convert to"
@param base : "The base to convert to"
@require base > 0 && base <= 16 : "Unsupported base"
@return? MALFORMED_INTEGER : "When the value has some illegal character"
@return? INTEGER_OVERFLOW : "If the value does not fit in the given type"
@return? EMPTY_STRING : "If the string was empty"
@return? NEGATIVE_VALUE : "If the type was unsigned, and the value had a - prefix"
*>
macro String.to_integer(self, $Type, int base = 10)
{
usz len = self.len;
usz index = 0;
char* ptr = self.ptr;
while (index < len && ascii::is_blank_m(ptr[index])) index++;
while (index < len && ptr[index].is_blank()) index++;
if (len == index) return EMPTY_STRING?;
bool is_negative;
switch (self[index])
@@ -746,7 +1019,7 @@ macro String.to_integer(self, $Type, int base = 10)
char c = self[index++];
switch
{
case base_used != 16 || c < 'A': c -= '0';
case base_used < 10 || c < 'A': c -= '0';
case c <= 'F': c -= 'A' - 10;
case c < 'a' || c > 'f': return MALFORMED_INTEGER?;
default: c -= 'a' - 10;
@@ -784,22 +1057,91 @@ fn char? String.to_uchar(self, int base = 10) => self.to_integer(char, base);
fn double? String.to_double(self) => self.to_real(double);
fn float? String.to_float(self) => self.to_real(float);
fn Splitter String.splitter(self, String split)
{
return { .string = self, .split = split };
}
<*
Create a Splitter to track tokenizing of a string. Tokenize will turn "foo:bar::baz" into
"foo", "bar" and "baz", if you want the empty string to be present, use `tokenize_all`
instead.
@param [in] split : "The string to use for splitting"
@return "A Splitter to track the state"
*>
fn Splitter String.tokenize(self, String split)
{
return { .string = self, .split = split, .tokenize = true };
return { .string = self, .split = split, .type = TOKENIZE };
}
<*
Create a Splitter to track tokenizing of a string. Tokenize will turn "foo:bar::baz" into
"foo", "bar" and "baz", if you want the empty string to be present, use `tokenize_all`
instead.
@param [in] split : "The string to use for splitting"
@param skip_last : "Set to true to not include the last empty token if present (default: false)"
@return "A Splitter to track the state"
*>
fn Splitter String.tokenize_all(self, String split, bool skip_last = false)
{
return {
.string = self,
.split = split,
.type = skip_last ? TOKENIZE_ALL_SKIP_LAST : TOKENIZE_ALL
};
}
fn Splitter String.splitter(self, String split) @deprecated("Use tokenize_all instead")
{
return self.tokenize_all(split, skip_last: true);
}
<*
This macro will create a string description of a struct.
@param [&inout] allocator : "The allocator to use"
@param x : "The struct to create a description of"
*>
macro String from_struct(Allocator allocator, x)
{
DString s;
@stack_mem(512; Allocator mem)
{
s.init(allocator: mem);
io::fprint(&s, x)!!;
return s.copy_str(allocator);
};
}
<*
This macro will create a temporary string description of a struct.
@param x : "The struct to create a description of"
*>
macro String tfrom_struct(x) => from_struct(tmem, x);
const uint SURROGATE_OFFSET @private = 0x10000;
const uint SURROGATE_GENERIC_MASK @private = 0xF800;
const uint SURROGATE_MASK @private = 0xFC00;
const uint SURROGATE_CODEPOINT_MASK @private = 0x03FF;
const uint SURROGATE_BITS @private = 10;
const uint SURROGATE_LOW_VALUE @private = 0xDC00;
const uint SURROGATE_HIGH_VALUE @private = 0xD800;
enum SplitterType
{
TOKENIZE,
TOKENIZE_ALL,
TOKENIZE_ALL_SKIP_LAST
}
<*
Splitter is handles tokenizing strings.
*>
struct Splitter
{
String string;
String split;
usz current;
bool tokenize;
SplitterType type;
int last_index;
}
@@ -814,29 +1156,22 @@ fn String? Splitter.next(&self)
{
usz len = self.string.len;
usz current = self.current;
if (current >= len) return NO_MORE_ELEMENT?;
if (current > len) return NO_MORE_ELEMENT?;
if (current == len)
{
if (self.type != TOKENIZE_ALL) return NO_MORE_ELEMENT?;
self.current++;
return self.string[current - 1:0];
}
String remaining = self.string[current..];
usz? next = remaining.index_of(self.split);
if (try next)
{
self.current = current + next + self.split.len;
if (!next && self.tokenize) continue;
if (!next && self.type == TOKENIZE) continue;
return remaining[:next];
}
self.current = len;
return remaining;
}
}
macro String from_struct(Allocator allocator, x)
{
DString s;
@stack_mem(512; Allocator mem)
{
s.init(allocator: mem);
io::fprint(&s, x)!!;
return s.copy_str(allocator);
};
}
macro String tfrom_struct(x) => from_struct(tmem, x);

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

@@ -96,13 +96,18 @@ macro bool is_subtype_of($Type, $OtherType)
}
macro bool is_numerical($Type)
{
var $kind = $Type.kindof;
$if $kind == TypeKind.DISTINCT:
return is_numerical($typefrom($Type.inner));
$else
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|| $kind == TypeKind.VECTOR;
$endif
$switch $Type.kindof:
$case DISTINCT:
$case CONST_ENUM:
return is_numerical($Type.inner);
$case SIGNED_INT:
$case UNSIGNED_INT:
$case FLOAT:
$case VECTOR:
return true;
$default:
return false;
$endswitch
}
fn bool TypeKind.is_int(kind) @inline
@@ -135,7 +140,7 @@ macro bool is_signed($Type) @const
$case FLOAT:
return true;
$case VECTOR:
return is_signed($typefrom($Type.inner));
return is_signed($Type.inner);
$default:
return false;
$endswitch
@@ -150,7 +155,7 @@ macro bool is_unsigned($Type) @const
$case UNSIGNED_INT:
return true;
$case VECTOR:
return is_unsigned($typefrom($Type.inner));
return is_unsigned($Type.inner);
$default:
return false;
$endswitch
@@ -158,8 +163,8 @@ macro bool is_unsigned($Type) @const
macro typeid flat_type($Type) @const
{
$if $Type.kindof == DISTINCT:
return flat_type($typefrom($Type.inner));
$if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
return flat_type($Type.inner);
$else
return $Type.typeid;
$endif
@@ -167,8 +172,8 @@ macro typeid flat_type($Type) @const
macro TypeKind flat_kind($Type) @const
{
$if $Type.kindof == DISTINCT:
return flat_type($typefrom($Type.inner));
$if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
return flat_type($Type.inner);
$else
return $Type.kindof;
$endif
@@ -184,6 +189,21 @@ macro bool is_ref_indexable($Type) @const
return $defined(&($Type){}[0]);
}
macro bool is_flat_intlike($Type) @const
{
$switch $Type.kindof:
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
$case VECTOR:
$case DISTINCT:
$case CONST_ENUM:
return is_flat_intlike($Type.inner);
$default:
return false;
$endswitch
}
macro bool is_intlike($Type) @const
{
$switch $Type.kindof:
@@ -204,7 +224,7 @@ macro bool is_underlying_int($Type) @const
$case UNSIGNED_INT:
return true;
$case DISTINCT:
return is_underlying_int($typefrom($Type.inner));
return is_underlying_int($Type.inner);
$default:
return false;
$endswitch
@@ -231,8 +251,8 @@ macro bool is_vector($Type) @const
macro typeid inner_type($Type) @const
{
$if $Type.kindof == TypeKind.DISTINCT:
return inner_type($typefrom($Type.inner));
$if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
return inner_type($Type.inner);
$else
return $Type.typeid;
$endif
@@ -272,6 +292,7 @@ macro bool may_load_atomic($Type) @const
$case FLOAT:
return true;
$case DISTINCT:
$case ENUM:
return may_load_atomic($Type.inner);
$default:
return false;
@@ -286,6 +307,7 @@ macro lower_to_atomic_compatible_type($Type) @const
$case UNSIGNED_INT:
return $Type.typeid;
$case DISTINCT:
$case CONST_ENUM:
return lower_to_atomic_compatible_type($Type.inner);
$case FLOAT:
$switch $Type:
@@ -317,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):
@@ -359,6 +383,7 @@ enum TypeKind : char
FAULT,
ANY,
ENUM,
CONST_ENUM,
STRUCT,
UNION,
BITSTRUCT,

View File

@@ -1,22 +1,27 @@
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.
*>
macro bool @is_same_type(#value1, #value2) @const => $typeof(#value1).typeid == $typeof(#value2).typeid;
macro bool @is_bool(#value) @const => types::is_bool($typeof(#value));
macro bool @is_int(#value) @const => types::is_int($typeof(#value));
macro bool @is_flat_intlike(#value) @const => types::is_flat_intlike($typeof(#value));
macro bool @is_floatlike(#value) @const => types::is_floatlike($typeof(#value));
macro bool @is_float(#value) @const => types::is_float($typeof(#value));
macro bool @is_promotable_to_floatlike(#value) @const => types::is_promotable_to_floatlike($typeof(#value));
macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_float($typeof(#value));
macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2));
macro bool @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

@@ -3,10 +3,11 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::encoding::json;
import std::io;
import std::ascii;
import std::collections::object;
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, DUPLICATE_MEMBERS, INVALID_NUMBER;
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, INVALID_NUMBER, MAX_DEPTH_REACHED;
int max_depth = 128;
fn Object*? parse_string(Allocator allocator, String s)
{
@@ -25,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;
};
};
}
@@ -63,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;
}
}
@@ -106,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 == '.')
{
@@ -149,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)
{
@@ -157,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();
@@ -181,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)
{
@@ -249,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;
}

View File

@@ -71,7 +71,7 @@ fn void Sha256.update(&self, char[] data) {
uint i = 0;
uint len = data.len;
uint buffer_pos = (uint)(self.bitcount / 8) % BLOCK_SIZE;
self.bitcount += (ulong)(len * 8);
self.bitcount += ((ulong)len * 8);
while (len--) {
self.buffer[buffer_pos++] = data[i++];
@@ -173,4 +173,5 @@ fn void sha256_transform(uint* state, char* buffer) @local {
state[7] += h;
a = b = c = d = e = f = g = h = t1 = t2 = i = 0;
m[:64] = buffer[:64] = 0;
}
}

281
lib/std/hash/sha512.c3 Normal file
View File

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

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

@@ -19,7 +19,7 @@ alias FloatType = double;
macro bool is_struct_with_default_print($Type)
{
return $Type.kindof == STRUCT
return ($Type.kindof == STRUCT ||| $Type.kindof == BITSTRUCT)
&&& !$defined($Type.to_format)
&&& !$defined($Type.to_constant_string);
}
@@ -27,7 +27,7 @@ macro bool is_struct_with_default_print($Type)
<*
Introspect a struct and print it to a formatter
@require @typekind(value) == STRUCT : `This macro is only valid on macros`
@require @typekind(value) == STRUCT || @typekind(value) == BITSTRUCT : `This macro is only valid on macros`
*>
macro usz? struct_to_format(value, Formatter* f, bool $force_dump)
{
@@ -40,8 +40,8 @@ macro usz? struct_to_format(value, Formatter* f, bool $force_dump)
$if $member.nameof != "":
total += f.printf("%s: ", $member.nameof)!;
$endif
$if ($force_dump &&& $member.typeid.kindof == STRUCT) |||
is_struct_with_default_print($typefrom($member.typeid)):
$if ($force_dump &&& ($member.typeid.kindof == STRUCT || $member.typeid.kindof == BITSTRUCT)) |||
is_struct_with_default_print($member.typeid):
total += struct_to_format($member.get(value), f, $force_dump)!;
$else
total += f.printf("%s", $member.get(value))!;
@@ -69,7 +69,6 @@ struct Formatter
PrintFlags flags;
uint width;
uint prec;
usz idx;
fault first_fault;
}
}
@@ -205,6 +204,7 @@ fn usz? Formatter.out_str(&self, any arg) @private
}
self.width = 0;
return self.out_substr("0x")! + self.ntoa_any(arg, 16);
case CONST_ENUM:
case DISTINCT:
if (arg.type == String.typeid)
{
@@ -494,6 +494,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;
}
@@ -565,5 +570,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

@@ -44,6 +44,7 @@ fn uint128? int_from_any(any arg, bool *is_neg) @private
*is_neg = false;
return (uint128)(uptr)*(void**)arg.ptr;
case DISTINCT:
case CONST_ENUM:
return int_from_any(arg.as_inner(), is_neg);
default:
break;
@@ -94,7 +95,7 @@ fn FloatType? float_from_any(any arg) @private
$if env::F128_SUPPORT:
if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr);
$endif
if (arg.type.kindof == TypeKind.DISTINCT)
if (arg.type.kindof == DISTINCT || arg.type.kindof == CONST_ENUM)
{
return float_from_any(arg.as_inner());
}
@@ -623,9 +624,12 @@ fn usz? Formatter.out_char(&self, any arg) @private
return self.out_substr("<NOT CHAR>");
}
usz len = 1;
uint l = 1;
// pre padding
len += self.adjust(l)!;
if (!self.flags.left)
{
len += self.pad(' ', self.width, len)!;
}
// char output
Char32 c = types::any_to_int(arg, uint) ?? 0xFFFD;
switch (true)
@@ -645,7 +649,10 @@ fn usz? Formatter.out_char(&self, any arg) @private
self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
self.out((char)(0x80 | (c & 0x3F)))!;
}
len += self.adjust(l)!;
if (self.flags.left)
{
len += self.pad(' ', self.width, len)!;
}
return len;
}
@@ -653,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,17 +39,17 @@ 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);
}
<*
@param [&out] ref
@require @is_instream(stream)
@require @is_instream(stream) : "Expected a stream"
*>
macro usz? read_any(stream, any ref)
{
@@ -191,7 +191,7 @@ const char[*] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 };
*>
macro usz? read_varint(stream, x_ptr)
{
var $Type = $typefrom($typeof(x_ptr).inner);
var $Type = $typeof(x_ptr).inner;
const MAX = MAX_VARS[$Type.sizeof];
$Type x;
uint shift;

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;
@@ -125,6 +128,7 @@ extern fn ZString getenv(ZString name);
extern fn ZString gets(char* buffer);
extern fn Tm* gmtime(Time_t* timer);
extern fn Tm* gmtime_r(Time_t* timer, Tm* buf) @if(!env::WIN32);
extern fn CInt ioctl(CInt fd, ulong request, ...);
extern fn CInt isatty(Fd fd) @if(!env::WIN32);
extern fn CLong labs(CLong x);
extern fn LongDivResult ldiv(CLong number, CLong denom);
@@ -147,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);
@@ -167,6 +172,7 @@ extern fn CInt strcmp(ZString str1, ZString str2);
extern fn CInt strcoll(ZString str1, ZString str2);
extern fn usz strcspn(ZString str1, ZString str2);
extern fn ZString strcpy(ZString dst, ZString src);
extern fn ZString strdup(ZString s);
extern fn ZString strerror(CInt errn);
extern fn usz strftime(char* dest, usz maxsize, ZString format, Tm* timeptr);
extern fn usz strlen(ZString str);

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

@@ -1,10 +1,21 @@
module libc::os @if(env::LIBC);
import std::core::env;
// Linux
extern fn int* __errno_location() @if(env::LINUX);
macro int errno() @if(env::LINUX) => *__errno_location();
macro void errno_set(int err) @if(env::LINUX) => *(__errno_location()) = err;
// Android 9
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();
@@ -22,7 +33,7 @@ extern fn void _get_errno(int* result) @if(env::WIN32);
extern fn void _set_errno(int err) @if(env::WIN32);
// Default
module libc::os @if(!env::LIBC || !(env::LINUX || env::DARWIN || env::WIN32));
module libc::os @if(!env::LIBC || !env::HAS_NATIVE_ERRNO);
tlocal int _errno_c3 = 0;
fn void errno_set(int err) => _errno_c3 = err;
fn int errno() => _errno_c3;

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)
{
@@ -234,6 +236,13 @@ macro asinh(x)
*>
macro ceil(x) => $$ceil(x);
<*
Ceil for compile time evaluation.
@require @typeis($input, double) || @typeis($input, float) : "Only float and double may be used"
*>
macro @ceil($input) @const => $$ceil($input);
<*
Constrain the value to lie within the given interval.
@@ -335,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`
*>
@@ -387,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)
{
@@ -507,7 +533,7 @@ macro bool is_finite(x)
$case float16:
return bitcast((float)x, uint) & 0x7fffffff < 0x7f800000;
$default:
return bitcast((double)x, ulong) & (~0u64 >> 1) < 0x7ffu64 << 52;
return bitcast((double)x, ulong) & (~0UL >> 1) < 0x7ffUL << 52;
$endswitch
}
@@ -521,7 +547,7 @@ macro is_nan(x)
$case float16:
return bitcast((float)x, uint) & 0x7fffffff > 0x7f800000;
$default:
return bitcast((double)x, ulong) & (~0u64 >> 1) > 0x7ffu64 << 52;
return bitcast((double)x, ulong) & (~0UL >> 1) > 0x7ffUL << 52;
$endswitch
}
@@ -535,7 +561,7 @@ macro is_inf(x)
$case float16:
return bitcast((float)x, uint) & 0x7fffffff == 0x7f800000;
$default:
return bitcast((double)x, ulong) & (~0u64 >> 1) == 0x7ffu64 << 52;
return bitcast((double)x, ulong) & (~0UL >> 1) == 0x7ffUL << 52;
$endswitch
}
@@ -1005,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)
@@ -1053,8 +1079,8 @@ fn double _frexp(double x, int* e)
return x;
default:
*e = ee - 0x3fe;
i &= 0x800fffffffffffffu64;
i |= 0x3fe0000000000000u64;
i &= 0x800fffffffffffffUL;
i |= 0x3fe0000000000000UL;
return bitcast(i, double);
}
}
@@ -1079,8 +1105,8 @@ fn float _frexpf(float x, int* e)
return x;
default:
*e = ee - 0x7e;
i &= 0x807fffffu32;
i |= 0x3f000000u32;
i &= 0x807fffffU;
i |= 0x3f000000U;
return bitcast(i, float);
}
}
@@ -1106,6 +1132,33 @@ macro overflow_mul_helper(x, y) @local
return res;
}
<*
@param [&out] out : "Where the result of the addition is stored"
@return "Whether the addition resulted in an integer overflow"
@require values::@is_same_type(a, b) : "a and b must be the same type"
@require values::@is_flat_intlike(a) &&& values::@is_flat_intlike(b) : "a and b must both be integer or integer vector based"
@require $defined(*out) &&& values::@is_same_type(*out, a) : "out must be a pointer of the same type as a and b"
*>
macro bool overflow_add(a, b, out) => $$overflow_add(a, b, out);
<*
@param [&out] out : "Where the result of the subtraction is stored"
@return "Whether the subtraction resulted in an integer overflow"
@require values::@is_same_type(a, b) : "a and b must be the same type"
@require values::@is_flat_intlike(a) &&& values::@is_flat_intlike(b) : "a and b must both be integer or integer vector based"
@require $defined(*out) &&& values::@is_same_type(*out, a) : "out must be a pointer of the same type as a and b"
*>
macro bool overflow_sub(a, b, out) => $$overflow_sub(a, b, out);
<*
@param [&out] out : "Where the result of the multiplication is stored"
@return "Whether the multiplication resulted in an integer overflow"
@require values::@is_same_type(a, b) : "a and b must be the same type"
@require values::@is_flat_intlike(a) &&& values::@is_flat_intlike(b) : "a and b must both be integer or integer vector based"
@require $defined(*out) &&& values::@is_same_type(*out, a) : "out must be a pointer of the same type as a and b"
*>
macro bool overflow_mul(a, b, out) => $$overflow_mul(a, b, out);
<*
@require types::is_vector($Type) || ($Type.kindof == ARRAY &&& types::is_numerical($typefrom($Type.inner)))
*>
@@ -1163,31 +1216,31 @@ macro ushort[<*>] ushort[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul
<*
@require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, div) `div must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar`
*>
macro int[<*>] int[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div);
<*
@require @is_same_vector_or_scalar(self, mul) `mul must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, div) `div must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar`
*>
macro uint[<*>] uint[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div);
<*
@require @is_same_vector_or_scalar(self, mul) `mul must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, div) `div must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar`
*>
macro long[<*>] long[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div);
<*
@require @is_same_vector_or_scalar(self, mul) `mul must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, div) `div must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar`
*>
macro ulong[<*>] ulong[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div);
<*
@require types::is_int($typeof(a)) `The input must be an integer`
@require types::is_int($typeof(b)) `The input must be an integer`
@require types::is_int($typeof(a)) : `The input must be an integer`
@require types::is_int($typeof(b)) : `The input must be an integer`
*>
macro _gcd(a, b) @private
{
@@ -1208,9 +1261,9 @@ macro _gcd(a, b) @private
}
<*
Calculate the least common multiple for the provided arguments.
Calculate the least common multiple for the provided arguments.
@require $vacount >= 2 `At least two arguments are required.`
@require $vacount >= 2 : "At least two arguments are required."
*>
macro lcm(...)
{
@@ -1226,9 +1279,9 @@ macro lcm(...)
}
<*
Calculate the greatest common divisor for the provided arguments.
Calculate the greatest common divisor for the provided arguments.
@require $vacount >= 2 `At least two arguments are required.`
@require $vacount >= 2 : "At least two arguments are required."
*>
macro gcd(...)
{

View File

@@ -35,7 +35,7 @@ fn double fmod(double x, double y) @extern("fmod") @weak @nostrip
}
else
{
uxi &= -1UL >> 12;
uxi &= (ulong)-1 >> 12;
uxi |= 1UL << 52;
}
if (!ey)
@@ -45,7 +45,7 @@ fn double fmod(double x, double y) @extern("fmod") @weak @nostrip
}
else
{
uy.i &= -1UL >> 12;
uy.i &= (ulong)-1 >> 12;
uy.i |= 1UL << 52;
}
@@ -105,7 +105,7 @@ fn float fmodf(float x, float y) @extern("fmodf") @weak @nostrip
}
else
{
uxi &= -1U >> 9;
uxi &= (uint)-1 >> 9;
uxi |= 1U << 23;
}
if (!ey)
@@ -115,7 +115,7 @@ fn float fmodf(float x, float y) @extern("fmodf") @weak @nostrip
}
else
{
uy.i &= -1U >> 9;
uy.i &= (uint)-1 >> 9;
uy.i |= 1U << 23;
}

View File

@@ -47,13 +47,13 @@ fn double _exp2_specialcase(double tmp, ulong sbits, ulong ki) @private
if (ki & 0x80000000 == 0)
{
// k > 0, the exponent of scale might have overflowed by 1.
sbits -= 1u64 << 52;
sbits -= 1UL << 52;
double scale = bitcast(sbits, double);
double y = 2 * (scale + scale * tmp);
return y;
}
// k < 0, need special care in the subnormal range.
sbits += 1022u64 << 52;
sbits += 1022UL << 52;
double scale = bitcast(sbits, double);
double y = scale + scale * tmp;
if (y >= 1.0)

View File

@@ -109,7 +109,7 @@ const int[*] IPIO2 = {
0x91615E, 0xE61B08, 0x659985, 0x5F14A0, 0x68408D, 0xFFD880,
0x4D7327, 0x310606, 0x1556CA, 0x73A8C9, 0x60E27B, 0xC08C6B, };
const double[*] PIO2 = {
const double[*] PIO2 @local = {
1.57079625129699707031e+00, /* 0x3FF921FB, 0x40000000 */
7.54978941586159635335e-08, /* 0x3E74442D, 0x00000000 */
5.39030252995776476554e-15, /* 0x3CF84698, 0x80000000 */

View File

@@ -117,8 +117,8 @@ macro bool next_bool(random)
*>
macro float next_float(random)
{
uint val = random.next_int() & (1 << 24 - 1);
return val / (float)(1 << 24);
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

@@ -31,4 +31,4 @@ fn char SimpleRandom.next_byte(&self) @dynamic => (char)self.next_int();
const long SIMPLE_RANDOM_MULTIPLIER @local = 0x5DEECE66D;
const long SIMPLE_RANDOM_ADDEND @local = 0xB;
const long SIMPLE_RANDOM_MASK @local = (1u64 << 48) - 1;
const long SIMPLE_RANDOM_MASK @local = (1UL << 48) - 1;

View File

@@ -315,15 +315,15 @@ macro float_from_i128($Type, a) @private
const MANT_DIG = math::DOUBLE_MANT_DIG;
const SIGNIFICANT_BITS = 52;
const EXP_BIAS = 1023;
const MANTISSA_MASK = 0xFFFFF_FFFF_FFFFu64;
const SIGN_BIT = 1u64 << 63;
const MANTISSA_MASK = 0xFFFFF_FFFF_FFFFUL;
const SIGN_BIT = 1UL << 63;
$case float:
$Rep = uint;
const MANT_DIG = math::FLOAT_MANT_DIG;
const EXP_BIAS = 127;
const SIGNIFICANT_BITS = 23;
const MANTISSA_MASK = 0x7F_FFFFu32;
const SIGN_BIT = 1u32 << 31;
const MANTISSA_MASK = 0x7F_FFFFU;
const SIGN_BIT = 1U << 31;
$case float16:
$Rep = ushort;
const MANT_DIG = math::HALF_MANT_DIG;
@@ -352,7 +352,7 @@ macro float_from_i128($Type, a) @private
a |= (uint128)((a & 4) != 0);
a++;
a >>= 2;
if (a & (1i128 << MANT_DIG))
if (a & (1LL << MANT_DIG))
{
a >>= 1;
e++;
@@ -374,13 +374,13 @@ macro float_from_u128($Type, a) @private
const MANT_DIG = math::DOUBLE_MANT_DIG;
const SIGNIFICANT_BITS = 52;
const EXP_BIAS = 1023;
const MANTISSA_MASK = 0xFFFFF_FFFF_FFFFu64;
const MANTISSA_MASK = 0xFFFFF_FFFF_FFFFUL;
$case float:
$Rep = uint;
const MANT_DIG = math::FLOAT_MANT_DIG;
const EXP_BIAS = 127;
const SIGNIFICANT_BITS = 23;
const MANTISSA_MASK = 0x7F_FFFFu32;
const MANTISSA_MASK = 0x7F_FFFFU;
$case float16:
$Rep = ushort;
const MANT_DIG = math::HALF_MANT_DIG;
@@ -406,7 +406,7 @@ macro float_from_u128($Type, a) @private
a |= (uint128)((a & 4) != 0);
a++;
a >>= 2;
if (a & (1i128 << MANT_DIG))
if (a & (1LL << MANT_DIG))
{
a >>= 1;
e++;
@@ -458,8 +458,8 @@ macro fixuint(a) @private
int sign = rep & SIGN_BIT ? -1 : 1;
int exponent = (int)((abs >> SIGNIFICANT_BITS) - EXPONENT_BIAS);
$Rep significand = (abs & SIGNIFICANT_MASK) | IMPLICIT_BIT;
if (sign == -1 || exponent < 0) return 0u128;
if ((uint)exponent >= uint128.sizeof * 8) return ~0u128;
if (sign == -1 || exponent < 0) return 0ULL;
if ((uint)exponent >= uint128.sizeof * 8) return ~0ULL;
if (exponent < SIGNIFICANT_BITS) return (uint128)significand >> (SIGNIFICANT_BITS - exponent);
return (uint128)significand << (exponent - SIGNIFICANT_BITS);
}

View File

@@ -1,6 +1,5 @@
module std::net;
import std::io;
import std::ascii;
enum IpProtocol : char (AIFamily ai_family)
{

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;
@@ -167,7 +167,7 @@ fn usz? Socket.peek(&self, char[] bytes) @dynamic
return (usz)n;
}
enum SocketShutdownHow : (inline CInt native_value)
enum SocketShutdownHow : (CInt native_value)
{
RECEIVE = @select(env::WIN32, libc::SD_RECEIVE, libc::SHUT_RD),
SEND = @select(env::WIN32, libc::SD_SEND, libc::SHUT_WR),
@@ -176,7 +176,7 @@ enum SocketShutdownHow : (inline CInt native_value)
fn void? Socket.shutdown(&self, SocketShutdownHow how)
{
if (libc::shutdown(self.sock, how) < 0)
if (libc::shutdown(self.sock, how.native_value) < 0)
{
return os::socket_error()?;
}

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

@@ -4,7 +4,7 @@ import libc;
<*
Exit the process with a given exit code. This will typically call 'exit' in LibC
*>
fn void exit(int result, bool cleanup = true) @weak
fn void exit(int result) @weak @noreturn
{
$if env::LIBC:
libc::exit(result);
@@ -17,7 +17,7 @@ fn void exit(int result, bool cleanup = true) @weak
Exit the process with a given exit code. This will typically call '_Exit' in LibC
usually bypassing '@finalizer' functions.
*>
fn void fastexit(int result, bool cleanup = true) @weak
fn void fastexit(int result) @weak @noreturn
{
$if env::LIBC:
libc::_exit(result);

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

@@ -45,6 +45,8 @@ bitstruct SubProcessOptions : int
// Note: this will **not** search for paths in any provided custom environment
// and instead uses the PATH of the spawning process.
bool search_user_path;
// Inherit the parent's stdin, stdout, and stderr handles instead of creating pipes
bool inherit_stdio;
}
fn void? create_named_pipe_helper(void** rd, void **wr) @local @if(env::WIN32)
@@ -53,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
@@ -115,28 +114,38 @@ fn WString convert_command_line_win32(String[] command_line) @inline @if(env::WI
*>
fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, String[] environment = {}) @if(env::WIN32)
{
void* rd, wr;
Win32_DWORD flags = win32::CREATE_UNICODE_ENVIRONMENT;
Win32_PROCESS_INFORMATION process_info;
Win32_SECURITY_ATTRIBUTES sa_attr = { Win32_SECURITY_ATTRIBUTES.sizeof, null, 1 };
Win32_STARTUPINFOW start_info = {
.cb = Win32_STARTUPINFOW.sizeof,
.dwFlags = win32::STARTF_USESTDHANDLES
};
if (options.no_window) flags |= win32::CREATE_NO_WINDOW;
if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return FAILED_TO_CREATE_PIPE?;
// TODO defer catch
if (!win32::setHandleInformation(wr, win32::HANDLE_FLAG_INHERIT, 0)) return FAILED_TO_CREATE_PIPE?;
// Only set STARTF_USESTDHANDLES if we're not inheriting stdio
if (!options.inherit_stdio) start_info.dwFlags = win32::STARTF_USESTDHANDLES;
CFile stdin;
CFile stdout;
CFile stderr;
@pool()
void* rd = null;
void* wr = null;
// Only create pipes if not inheriting stdio
if (!options.inherit_stdio)
{
if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return FAILED_TO_CREATE_PIPE?;
// TODO defer catch
if (!win32::setHandleInformation(wr, win32::HANDLE_FLAG_INHERIT, 0)) return FAILED_TO_CREATE_PIPE?;
}
@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");
@@ -147,41 +156,21 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
env.append("\0");
}
env.append("\0");
used_environment = env.str_view().to_temp_wstring()!;
}
int fd = win32::_open_osfhandle((iptr)wr, 0);
if (fd != -1)
{
stdin = win32::_fdopen(fd, "wb");
if (!stdin) return FAILED_TO_OPEN_STDIN?;
}
start_info.hStdInput = rd;
if (options.read_async)
{
create_named_pipe_helper(&rd, &wr)!;
}
else
{
if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return FAILED_TO_CREATE_PIPE?;
}
if (!win32::setHandleInformation(rd, win32::HANDLE_FLAG_INHERIT, 0)) return FAILED_TO_CREATE_PIPE?;
fd = win32::_open_osfhandle((iptr)rd, 0);
if (fd != -1)
{
stdout = win32::_fdopen(fd, "rb");
if (!stdout) return FAILED_TO_OPEN_STDOUT?;
used_environment = env.str_view().to_wstring(mem)!;
}
start_info.hStdOutput = wr;
do
// Handle stdin pipe if not inheriting
if (!options.inherit_stdio)
{
if (options.combined_stdout_stderr)
int fd = win32::_open_osfhandle((iptr)wr, 0);
if (fd != -1)
{
stderr = stdout;
start_info.hStdError = start_info.hStdOutput;
break;
stdin = win32::_fdopen(fd, "wb");
if (!stdin) return FAILED_TO_OPEN_STDIN?;
}
start_info.hStdInput = rd;
// Handle stdout pipe
if (options.read_async)
{
create_named_pipe_helper(&rd, &wr)!;
@@ -195,18 +184,49 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
fd = win32::_open_osfhandle((iptr)rd, 0);
if (fd != -1)
{
stderr = win32::_fdopen(fd, "rb");
if (!stderr) return FAILED_TO_OPEN_STDERR?;
stdout = win32::_fdopen(fd, "rb");
if (!stdout) return FAILED_TO_OPEN_STDOUT?;
}
start_info.hStdError = wr;
};
void *event_output;
void *event_error;
if (options.read_async)
start_info.hStdOutput = wr;
// Handle stderr pipe or combine with stdout
do
{
if (options.combined_stdout_stderr)
{
stderr = stdout;
start_info.hStdError = start_info.hStdOutput;
break;
}
if (options.read_async)
{
create_named_pipe_helper(&rd, &wr)!;
}
else
{
if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return FAILED_TO_CREATE_PIPE?;
}
if (!win32::setHandleInformation(rd, win32::HANDLE_FLAG_INHERIT, 0)) return FAILED_TO_CREATE_PIPE?;
fd = win32::_open_osfhandle((iptr)rd, 0);
if (fd != -1)
{
stderr = win32::_fdopen(fd, "rb");
if (!stderr) return FAILED_TO_OPEN_STDERR?;
}
start_info.hStdError = wr;
};
}
void *event_output = null;
void *event_error = null;
if (!options.inherit_stdio && options.read_async)
{
event_output = win32::createEventA(&sa_attr, 1, 1, null);
event_error = win32::createEventA(&sa_attr, 1, 1, null);
}
if (!win32::createProcessW(
null,
convert_command_line_win32(command_line),
@@ -214,14 +234,17 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
null, // primary thread security attributes
1, // handles are inherited
flags, // creation flags
used_environment, // environment
used_environment, // environment
null, // use parent dir
&start_info, // startup info ptr
&process_info)) return FAILED_TO_START_PROCESS?;
};
// We don't need the handle of the primary thread in the called process.
win32::closeHandle(process_info.hThread);
if (start_info.hStdOutput)
// Close handles if not inheriting stdio
if (!options.inherit_stdio && start_info.hStdOutput)
{
win32::closeHandle(start_info.hStdOutput);
if (start_info.hStdOutput != start_info.hStdError) win32::closeHandle(start_info.hStdError);
@@ -229,7 +252,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
return {
.hProcess = process_info.hProcess,
.hStdInput = start_info.hStdInput,
.hStdInput = options.inherit_stdio ? null : start_info.hStdInput,
.stdin_file = stdin,
.stdout_file = stdout,
.stderr_file = stderr,
@@ -240,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;
}
@@ -280,32 +303,42 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
CInt[2] stdinfd;
CInt[2] stdoutfd;
CInt[2] stderrfd;
if (posix::pipe(&stdinfd)) return FAILED_TO_OPEN_STDIN?;
if (posix::pipe(&stdoutfd)) return FAILED_TO_OPEN_STDOUT?;
if (!options.combined_stdout_stderr && posix::pipe(&stderrfd)) return FAILED_TO_OPEN_STDERR?;
CFile stdin = null;
CFile stdout = null;
CFile stderr = null;
Posix_spawn_file_actions_t actions;
if (posix::spawn_file_actions_init(&actions)) return FAILED_TO_INITIALIZE_ACTIONS?;
defer posix::spawn_file_actions_destroy(&actions);
if (posix::spawn_file_actions_addclose(&actions, stdinfd[1])) return FAILED_TO_OPEN_STDIN?;
if (posix::spawn_file_actions_adddup2(&actions, stdinfd[0], libc::STDIN_FD)) return FAILED_TO_OPEN_STDIN?;
if (posix::spawn_file_actions_addclose(&actions, stdoutfd[0])) return FAILED_TO_OPEN_STDOUT?;
if (posix::spawn_file_actions_adddup2(&actions, stdoutfd[1], libc::STDOUT_FD)) return FAILED_TO_OPEN_STDOUT?;
if (options.combined_stdout_stderr)
// Only set up pipes if not inheriting stdio
if (!options.inherit_stdio)
{
if (posix::spawn_file_actions_adddup2(&actions, libc::STDOUT_FD, libc::STDERR_FD)) return FAILED_TO_OPEN_STDERR?;
}
else
{
if (posix::spawn_file_actions_addclose(&actions, stderrfd[0])) return FAILED_TO_OPEN_STDERR?;
if (posix::spawn_file_actions_adddup2(&actions, stderrfd[1], libc::STDERR_FD)) return FAILED_TO_OPEN_STDERR?;
if (posix::pipe(&stdinfd)) return FAILED_TO_OPEN_STDIN?;
if (posix::pipe(&stdoutfd)) return FAILED_TO_OPEN_STDOUT?;
if (!options.combined_stdout_stderr && posix::pipe(&stderrfd)) return FAILED_TO_OPEN_STDERR?;
if (posix::spawn_file_actions_addclose(&actions, stdinfd[1])) return FAILED_TO_OPEN_STDIN?;
if (posix::spawn_file_actions_adddup2(&actions, stdinfd[0], libc::STDIN_FD)) return FAILED_TO_OPEN_STDIN?;
if (posix::spawn_file_actions_addclose(&actions, stdoutfd[0])) return FAILED_TO_OPEN_STDOUT?;
if (posix::spawn_file_actions_adddup2(&actions, stdoutfd[1], libc::STDOUT_FD)) return FAILED_TO_OPEN_STDOUT?;
if (options.combined_stdout_stderr)
{
if (posix::spawn_file_actions_adddup2(&actions, libc::STDOUT_FD, libc::STDERR_FD)) return FAILED_TO_OPEN_STDERR?;
}
else
{
if (posix::spawn_file_actions_addclose(&actions, stderrfd[0])) return FAILED_TO_OPEN_STDERR?;
if (posix::spawn_file_actions_adddup2(&actions, stderrfd[1], libc::STDERR_FD)) return FAILED_TO_OPEN_STDERR?;
}
}
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?;
@@ -315,21 +348,27 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
if (posix::spawnp(&child, command_line_copy[0], &actions, null, command_line_copy, used_environment)) return FAILED_TO_START_PROCESS?;
}
};
libc::close(stdinfd[0]);
CFile stdin = libc::fdopen(stdinfd[1], "wb");
libc::close(stdoutfd[1]);
CFile stdout = libc::fdopen(stdoutfd[0], "rb");
CFile stderr @noinit;
do
// Only set up file handles if not inheriting stdio
if (!options.inherit_stdio)
{
if (options.combined_stdout_stderr)
libc::close(stdinfd[0]);
stdin = libc::fdopen(stdinfd[1], "wb");
libc::close(stdoutfd[1]);
stdout = libc::fdopen(stdoutfd[0], "rb");
do
{
stderr = stdout;
break;
}
libc::close(stderrfd[1]);
stderr = libc::fdopen(stderrfd[0], "rb");
};
if (options.combined_stdout_stderr)
{
stderr = stdout;
break;
}
libc::close(stderrfd[1]);
stderr = libc::fdopen(stderrfd[0], "rb");
};
}
return {
.stdin_file = stdin,
.stdout_file = stdout,
@@ -357,11 +396,13 @@ fn CInt? SubProcess.join(&self) @if(env::POSIX)
fn File SubProcess.stdout(&self)
{
if (!self.stdout_file) return (File){}; // Return an empty File struct
return file::from_handle(self.stdout_file);
}
fn File SubProcess.stderr(&self)
{
if (!self.stderr_file) return (File){}; // Return an empty File struct
return file::from_handle(self.stderr_file);
}
@@ -450,6 +491,8 @@ fn usz? read_from_file_posix(CFile file, char* buffer, usz size) @if(env::POSIX)
fn usz? SubProcess.read_stdout(&self, char* buffer, usz size)
{
if (!self.stdout_file) return 0; // No output available when inheriting stdio
$if env::WIN32:
return read_from_file_win32(self.stdout_file, self.hEventOutput, buffer, size);
$else
@@ -457,8 +500,11 @@ fn usz? SubProcess.read_stdout(&self, char* buffer, usz size)
$endif
}
fn usz? SubProcess.read_stderr(&self, char* buffer, usz size)
{
if (!self.stderr_file) return 0; // No output available when inheriting stdio
$if env::WIN32:
return read_from_file_win32(self.stderr_file, self.hEventError, buffer, size);
$else

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