mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Compare commits
712 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
161280ce7d | ||
|
|
4e129d4ae2 | ||
|
|
2b6f1c061d | ||
|
|
cb19c7d9e7 | ||
|
|
224b120745 | ||
|
|
534dd42472 | ||
|
|
163976f85f | ||
|
|
d79a8808d7 | ||
|
|
1cba5a0774 | ||
|
|
6c76a7ce4e | ||
|
|
084d5cbc94 | ||
|
|
32b1df0f86 | ||
|
|
50718cb905 | ||
|
|
414c0c9438 | ||
|
|
362d5680e4 | ||
|
|
fed7a74a75 | ||
|
|
d276d3767f | ||
|
|
a07660f957 | ||
|
|
4d02ce4414 | ||
|
|
c1e3cfaacc | ||
|
|
c11385cf49 | ||
|
|
0b5064683f | ||
|
|
f3ede27f60 | ||
|
|
4fbb42833e | ||
|
|
3e76b7ff1c | ||
|
|
e901a3de55 | ||
|
|
4899ee14e2 | ||
|
|
3c04a326f4 | ||
|
|
a1ff3b05ed | ||
|
|
74e228688a | ||
|
|
75d454b6a6 | ||
|
|
6cffb888ea | ||
|
|
cf03215564 | ||
|
|
5b90743120 | ||
|
|
0fb91265b6 | ||
|
|
3cb7c489ee | ||
|
|
c65c378b7f | ||
|
|
cf9784afee | ||
|
|
8bd942c1b8 | ||
|
|
109e15b5a0 | ||
|
|
0fdd6bdc81 | ||
|
|
378b35265b | ||
|
|
b5e25e3857 | ||
|
|
9b2fc04959 | ||
|
|
ce8167a102 | ||
|
|
397d065a74 | ||
|
|
5e23817a3d | ||
|
|
396263f5c3 | ||
|
|
26d0760c0d | ||
|
|
11f090116f | ||
|
|
5e1c343be4 | ||
|
|
0e69432e3d | ||
|
|
94a26d9483 | ||
|
|
efa5bdc6df | ||
|
|
459969ddb2 | ||
|
|
ae5047b73f | ||
|
|
17f3db835c | ||
|
|
1845a515ca | ||
|
|
bf50178eb3 | ||
|
|
32675161c4 | ||
|
|
e257500e03 | ||
|
|
0add42b0a0 | ||
|
|
b14053df41 | ||
|
|
6a78864b6c | ||
|
|
b1fea45cd1 | ||
|
|
cef48482f1 | ||
|
|
a126a25d66 | ||
|
|
61c939059d | ||
|
|
5fa820a462 | ||
|
|
472124dab3 | ||
|
|
1e11b6c442 | ||
|
|
34a86852c6 | ||
|
|
52afbdbde9 | ||
|
|
888657cc97 | ||
|
|
d7bfddf35e | ||
|
|
9c435352b9 | ||
|
|
3fe55b5e51 | ||
|
|
cdabe8fd9e | ||
|
|
5390ca6250 | ||
|
|
61e84e4d34 | ||
|
|
1b82b7d2f2 | ||
|
|
49f59d82c9 | ||
|
|
e84e23f7ec | ||
|
|
0b9b49673e | ||
|
|
4512c6446d | ||
|
|
0fea6c6056 | ||
|
|
dd8449576f | ||
|
|
c3b2694834 | ||
|
|
d3ebd4a130 | ||
|
|
a326e31e57 | ||
|
|
945a3f3fc0 | ||
|
|
2a8fbb8fec | ||
|
|
c0f1b02d0b | ||
|
|
f2c557c3b7 | ||
|
|
d00a93f195 | ||
|
|
aa07969fc2 | ||
|
|
6dcd91c5ef | ||
|
|
c2e603ddd8 | ||
|
|
70c4b24519 | ||
|
|
51364a9d1b | ||
|
|
d9f4eb46d9 | ||
|
|
cd2d1a04d8 | ||
|
|
3f7a547d8a | ||
|
|
f254c27966 | ||
|
|
8a1c02c840 | ||
|
|
7f297a9d27 | ||
|
|
64ef33f09b | ||
|
|
cd4c586c3f | ||
|
|
0a9d4e398d | ||
|
|
d61beef7e0 | ||
|
|
7136b05019 | ||
|
|
87fa253059 | ||
|
|
04eb6fc451 | ||
|
|
15fc435d92 | ||
|
|
9d54e9e3c4 | ||
|
|
c73c7cb2a3 | ||
|
|
827ad18ef4 | ||
|
|
3fc562af6f | ||
|
|
87c42f1cd3 | ||
|
|
e93f22fbda | ||
|
|
8fa59cbb43 | ||
|
|
527766310f | ||
|
|
a02b0a1f4c | ||
|
|
4d93db51ee | ||
|
|
fa2e6e8189 | ||
|
|
0fc1871ddf | ||
|
|
197f82d829 | ||
|
|
4ad11724d1 | ||
|
|
6d53fd6f6e | ||
|
|
db47b0555d | ||
|
|
ebd7b7243a | ||
|
|
e0771beabc | ||
|
|
824d064710 | ||
|
|
336ddb67c8 | ||
|
|
9ad98beda7 | ||
|
|
702f836b40 | ||
|
|
d820a2356a | ||
|
|
fa1951642b | ||
|
|
42dc2d541a | ||
|
|
fdbbe5c1aa | ||
|
|
292bf1cbbc | ||
|
|
82769669ec | ||
|
|
29b211eefc | ||
|
|
e4965ab408 | ||
|
|
067a4f7cb1 | ||
|
|
739e91efa4 | ||
|
|
26d733ef59 | ||
|
|
32d6025e29 | ||
|
|
04706f2dcd | ||
|
|
d8a7c57b56 | ||
|
|
ad8769580a | ||
|
|
d51dd09a1f | ||
|
|
5ed4f9519f | ||
|
|
493a084745 | ||
|
|
9bd04526e8 | ||
|
|
90f0486334 | ||
|
|
e4f1b57bd0 | ||
|
|
c949bd3108 | ||
|
|
56f8008d85 | ||
|
|
c6a96ad7f3 | ||
|
|
328e6f518c | ||
|
|
8ec4a2cada | ||
|
|
9b318ec233 | ||
|
|
d96624c578 | ||
|
|
bf1d401566 | ||
|
|
a2c886a2d9 | ||
|
|
e76278cfd7 | ||
|
|
1028f85daa | ||
|
|
e706c914a8 | ||
|
|
f3b71ed7eb | ||
|
|
18b246c577 | ||
|
|
c205719563 | ||
|
|
02a5270c5a | ||
|
|
2ba244dd9c | ||
|
|
d33d0a232b | ||
|
|
5c4197debd | ||
|
|
41261d40b2 | ||
|
|
48ecceab54 | ||
|
|
0d9547a388 | ||
|
|
d2f59c5b3f | ||
|
|
85166bc706 | ||
|
|
bf26898645 | ||
|
|
1f2bcd5462 | ||
|
|
4e6cd4283c | ||
|
|
9aec5de105 | ||
|
|
dec49b05b8 | ||
|
|
29bcd2c96e | ||
|
|
97a9cab218 | ||
|
|
436af4dbca | ||
|
|
b08a2a2c0f | ||
|
|
b43dac24c4 | ||
|
|
bae0f0f579 | ||
|
|
8055c340f6 | ||
|
|
00c1210625 | ||
|
|
070b13c81c | ||
|
|
415c9639e7 | ||
|
|
996f8a6a4d | ||
|
|
e49e3e32e7 | ||
|
|
86a05ba6c0 | ||
|
|
651735f9a0 | ||
|
|
759389066f | ||
|
|
3577a2d6b8 | ||
|
|
b63886b879 | ||
|
|
466d3bc1b6 | ||
|
|
2a2c0f5d91 | ||
|
|
0c0d0ace4d | ||
|
|
a0ab10c23e | ||
|
|
0685260454 | ||
|
|
15662bd32f | ||
|
|
475816251b | ||
|
|
bf62d11a73 | ||
|
|
37d42d555d | ||
|
|
1fc522ec9c | ||
|
|
3a8a6aa429 | ||
|
|
e15ee23925 | ||
|
|
85657c9200 | ||
|
|
3f20e5af1d | ||
|
|
18e2838772 | ||
|
|
f023db8638 | ||
|
|
cb0b94c064 | ||
|
|
7087665ccc | ||
|
|
1793dd7f0b | ||
|
|
a61fd6d280 | ||
|
|
034a048c8a | ||
|
|
608fc4df9f | ||
|
|
5961bcaf86 | ||
|
|
fd5489458a | ||
|
|
cf5dd5496a | ||
|
|
ccffa03de2 | ||
|
|
ce0ab62c78 | ||
|
|
0bc5cbca74 | ||
|
|
a2aa9fae6b | ||
|
|
9d79c3f33d | ||
|
|
a26055c932 | ||
|
|
b296875c05 | ||
|
|
a54658d37f | ||
|
|
0a0e097bdf | ||
|
|
2df51bfe07 | ||
|
|
cb065152ea | ||
|
|
07fa14f00b | ||
|
|
ff1b17d18b | ||
|
|
c3d5778ae0 | ||
|
|
373ad1a399 | ||
|
|
6deed2d4a4 | ||
|
|
6324d78c32 | ||
|
|
9e14338b77 | ||
|
|
6e4614b6a4 | ||
|
|
0b52819090 | ||
|
|
404a78943d | ||
|
|
7215a9fa12 | ||
|
|
463c6957fc | ||
|
|
8ec3a52ef7 | ||
|
|
ab1efdda73 | ||
|
|
4f3b6f922d | ||
|
|
869a1d93cb | ||
|
|
5d468ccbf0 | ||
|
|
887ed5b9e9 | ||
|
|
5c1a6d7623 | ||
|
|
1b49ebf855 | ||
|
|
98155b61f1 | ||
|
|
60cdea5292 | ||
|
|
49e836b1ab | ||
|
|
5b83108dd1 | ||
|
|
a50de26c5d | ||
|
|
7b50c87858 | ||
|
|
a816a78e98 | ||
|
|
39694e65c0 | ||
|
|
2a41fa6281 | ||
|
|
49b8cfe267 | ||
|
|
20dfdf5c5d | ||
|
|
1e543dc286 | ||
|
|
06884720e5 | ||
|
|
1ea181524e | ||
|
|
b16ee3119d | ||
|
|
4e66693065 | ||
|
|
5f96b8e4c6 | ||
|
|
748a2f6530 | ||
|
|
6360ddbc77 | ||
|
|
eccc6700dc | ||
|
|
52ececba37 | ||
|
|
ffc65bcbf4 | ||
|
|
0da6bf4455 | ||
|
|
7063e684ba | ||
|
|
07363c6ecd | ||
|
|
5070840da9 | ||
|
|
4a25bcc5ee | ||
|
|
d43d7100af | ||
|
|
791cbbfb62 | ||
|
|
9b05dfdef1 | ||
|
|
b072e88bb3 | ||
|
|
af33d2b1cc | ||
|
|
d438d7510e | ||
|
|
1673aef74f | ||
|
|
b3bce10699 | ||
|
|
3ff922e12b | ||
|
|
3b718335ec | ||
|
|
f25ad512a7 | ||
|
|
5a3c484ceb | ||
|
|
331a77c1c2 | ||
|
|
045053f6bf | ||
|
|
4809979898 | ||
|
|
a5b2636b2e | ||
|
|
c483c3b75f | ||
|
|
c10d449e43 | ||
|
|
54b110a367 | ||
|
|
ee8dc3d681 | ||
|
|
a38a627a1d | ||
|
|
8aaf54e8b1 | ||
|
|
423152202f | ||
|
|
f37e7460aa | ||
|
|
8f5d5a0bb5 | ||
|
|
883052a6bb | ||
|
|
9cf271f5fb | ||
|
|
5d8cad91b1 | ||
|
|
614c6989d8 | ||
|
|
03ad72afbb | ||
|
|
b924ede71a | ||
|
|
a81f857d8c | ||
|
|
6169d7acdf | ||
|
|
4af31da7ea | ||
|
|
0bd2c81757 | ||
|
|
5ed1281451 | ||
|
|
7b649314ec | ||
|
|
e37343fbe3 | ||
|
|
7b02907830 | ||
|
|
6eee760239 | ||
|
|
ae33d1a206 | ||
|
|
3430240c2a | ||
|
|
6f11260a5c | ||
|
|
df67b7dddd | ||
|
|
f3b7df2ab0 | ||
|
|
a000ae560a | ||
|
|
0d85caf21c | ||
|
|
e34a26422f | ||
|
|
fe70f10bcc | ||
|
|
d6be1cbf65 | ||
|
|
04cd079d4e | ||
|
|
b4b14674b4 | ||
|
|
5a1831c989 | ||
|
|
e9ec421b3b | ||
|
|
872f63eecc | ||
|
|
1eb8c0ced1 | ||
|
|
b5ae2485a7 | ||
|
|
fe6817f90d | ||
|
|
98a72007f8 | ||
|
|
87c1e09a7a | ||
|
|
e0fbe31f00 | ||
|
|
7d6c844b99 | ||
|
|
a03446a26d | ||
|
|
a7e77fec78 | ||
|
|
05c3fa1afd | ||
|
|
30c8435669 | ||
|
|
94497c968b | ||
|
|
281d4af464 | ||
|
|
cb2d0e798e | ||
|
|
da67cd4eb0 | ||
|
|
7d06ca6d35 | ||
|
|
6d45450130 | ||
|
|
27bbeaf79c | ||
|
|
3af5a537da | ||
|
|
6287e8dfbf | ||
|
|
1f49a5448e | ||
|
|
ece4a2b6fb | ||
|
|
e68bd0c57f | ||
|
|
eaeafb7299 | ||
|
|
44d736a537 | ||
|
|
122dbb3668 | ||
|
|
c2abbe2e2f | ||
|
|
3ccabd625c | ||
|
|
cfe6534c15 | ||
|
|
f5090eb158 | ||
|
|
d3db91536c | ||
|
|
9c42919e5a | ||
|
|
a6d33ec4af | ||
|
|
b03ae8bb17 | ||
|
|
59fd777198 | ||
|
|
d8286fa2a5 | ||
|
|
3345e70c63 | ||
|
|
12eea4a98d | ||
|
|
fdc20dc642 | ||
|
|
c5e3a1b2da | ||
|
|
35270fb0bf | ||
|
|
d782dad149 | ||
|
|
92aefb15f8 | ||
|
|
8342ac80d3 | ||
|
|
c71444e7a0 | ||
|
|
06e10bb69f | ||
|
|
fabd96552f | ||
|
|
2e99ae5ab9 | ||
|
|
8fea6ee8ab | ||
|
|
e6b10ee00c | ||
|
|
6aff6d66de | ||
|
|
8035991ac3 | ||
|
|
c0bd14cee7 | ||
|
|
3ba0beee96 | ||
|
|
8e6535f13c | ||
|
|
0d8f9520e9 | ||
|
|
3caaf0a3e8 | ||
|
|
a2206f1bcd | ||
|
|
7b5277d52c | ||
|
|
9f55a74d2e | ||
|
|
3eb8f68ded | ||
|
|
bd9bc118db | ||
|
|
95375a2591 | ||
|
|
b7115e9c70 | ||
|
|
078d9dc0b7 | ||
|
|
79c0c8e082 | ||
|
|
69b3263a00 | ||
|
|
cbd415881b | ||
|
|
6dbd81a6f9 | ||
|
|
e605a21fd3 | ||
|
|
d1349c9cfb | ||
|
|
c375aef9a3 | ||
|
|
3c1f692d49 | ||
|
|
29e20ee1be | ||
|
|
cf14787552 | ||
|
|
10241df23c | ||
|
|
8795ffc4f1 | ||
|
|
e25812a071 | ||
|
|
14a929588a | ||
|
|
02d1486af9 | ||
|
|
bab317282c | ||
|
|
a3a6319bcf | ||
|
|
17dfbb377e | ||
|
|
ff39f14dd1 | ||
|
|
af4309b286 | ||
|
|
176fb47c23 | ||
|
|
3a69c9f1fe | ||
|
|
944cc00d34 | ||
|
|
a751177a3e | ||
|
|
d291a40f69 | ||
|
|
cb006dd715 | ||
|
|
c7f09f2879 | ||
|
|
c0387221af | ||
|
|
0c7c5fbd7b | ||
|
|
fafcf3d0a9 | ||
|
|
b757f1447b | ||
|
|
bc3d9d761f | ||
|
|
10bc68fb39 | ||
|
|
de8aed9d96 | ||
|
|
1080303768 | ||
|
|
0503e15e31 | ||
|
|
ca2fabc9f9 | ||
|
|
0178a44b3c | ||
|
|
8f3cb9c6e9 | ||
|
|
c339278ff7 | ||
|
|
47316dac59 | ||
|
|
90d3f429aa | ||
|
|
239d249f01 | ||
|
|
7312c10b9e | ||
|
|
3c6e6f1965 | ||
|
|
28b9be64ee | ||
|
|
d2cae909e1 | ||
|
|
e194081e21 | ||
|
|
04cc34f12e | ||
|
|
f7143c1852 | ||
|
|
1781e97f02 | ||
|
|
c17cb7d0ca | ||
|
|
21343baa75 | ||
|
|
58c59361ea | ||
|
|
cb17cfff7d | ||
|
|
1634217fc4 | ||
|
|
bc9b0900a5 | ||
|
|
f43a7540c5 | ||
|
|
410a25f334 | ||
|
|
35c04cdc36 | ||
|
|
3e641ab82b | ||
|
|
7972397c65 | ||
|
|
9bf933ae31 | ||
|
|
a69ee59b82 | ||
|
|
961aa0ef61 | ||
|
|
48318c3ad4 | ||
|
|
a004cd3d03 | ||
|
|
768ce6092d | ||
|
|
e4e499edd2 | ||
|
|
f36e9fea48 | ||
|
|
d5eec296a0 | ||
|
|
e2e2ca1d7f | ||
|
|
6ab7198f2f | ||
|
|
2e1f7c95ce | ||
|
|
a2ef63f5b6 | ||
|
|
b7c9a4e2e9 | ||
|
|
28ffb864a3 | ||
|
|
18b4ce4e7d | ||
|
|
551ce34b9b | ||
|
|
de09a19a48 | ||
|
|
ba55946c9a | ||
|
|
33ab18033a | ||
|
|
96127d4ff3 | ||
|
|
43163fe2a0 | ||
|
|
a52b30c951 | ||
|
|
7d6a864d56 | ||
|
|
eeab73df4e | ||
|
|
db45abdfc7 | ||
|
|
cb32441533 | ||
|
|
643aa47e99 | ||
|
|
5e1bf75621 | ||
|
|
7c8e3dd4fd | ||
|
|
ad02fad167 | ||
|
|
261184b5c1 | ||
|
|
d07da2804e | ||
|
|
702b63ddb7 | ||
|
|
e35dbd29fb | ||
|
|
b52ab886d2 | ||
|
|
4b95d6be4c | ||
|
|
34b0b6f8f9 | ||
|
|
4fea202e6d | ||
|
|
8cfdb76869 | ||
|
|
858f8d2405 | ||
|
|
67aa18c1aa | ||
|
|
31b15c775e | ||
|
|
bf7e7e2397 | ||
|
|
eb8fb8871f | ||
|
|
85dc9c45ab | ||
|
|
076ef187cb | ||
|
|
c8d39251a9 | ||
|
|
f5e6b697b8 | ||
|
|
e8e88c1920 | ||
|
|
82a58e1c66 | ||
|
|
8e8d0436ad | ||
|
|
db99de9717 | ||
|
|
fd9fbe26a1 | ||
|
|
582453cb45 | ||
|
|
b73a44ec7d | ||
|
|
be98a01ed8 | ||
|
|
625a6d987d | ||
|
|
2597f6217e | ||
|
|
0470f3be8e | ||
|
|
1d25197bfd | ||
|
|
aae873c044 | ||
|
|
6471728ee5 | ||
|
|
29bae1fbd6 | ||
|
|
9c770f360e | ||
|
|
3b6d68ef21 | ||
|
|
24c03f9800 | ||
|
|
ed61b51489 | ||
|
|
0205ee8688 | ||
|
|
abd3585c44 | ||
|
|
aa910a1c44 | ||
|
|
00b88a8027 | ||
|
|
229fdd6193 | ||
|
|
5292e08cd6 | ||
|
|
90990ed2f3 | ||
|
|
c99284103d | ||
|
|
b463358add | ||
|
|
0e10b71cbf | ||
|
|
cb2d8133e0 | ||
|
|
f2d27229d2 | ||
|
|
604661b12c | ||
|
|
440df8415e | ||
|
|
c31c423386 | ||
|
|
8358af2240 | ||
|
|
4625b457fb | ||
|
|
151a28a92a | ||
|
|
9fe6c77d28 | ||
|
|
1c4f7a4b61 | ||
|
|
f23bbb342c | ||
|
|
91b866c967 | ||
|
|
483fe62750 | ||
|
|
2a47cc2ca9 | ||
|
|
e707190539 | ||
|
|
cb62554a26 | ||
|
|
9c58db99af | ||
|
|
90c339ebdb | ||
|
|
e3a8a3ec02 | ||
|
|
bdbe81fedd | ||
|
|
f0142e3b1a | ||
|
|
2c6ff00261 | ||
|
|
61a21203f4 | ||
|
|
d7affc5028 | ||
|
|
eb9549a818 | ||
|
|
6d9906db0a | ||
|
|
334ee975b9 | ||
|
|
d600d0898c | ||
|
|
44f4efa5aa | ||
|
|
8151305701 | ||
|
|
3ac9bfc387 | ||
|
|
51b9cb85bc | ||
|
|
d805ff9782 | ||
|
|
fa9ba3f607 | ||
|
|
9d2be2851b | ||
|
|
2639338426 | ||
|
|
d8daa4ac83 | ||
|
|
6641155892 | ||
|
|
86034353ec | ||
|
|
944ef0fc8d | ||
|
|
194b7c4772 | ||
|
|
6963e143a1 | ||
|
|
4977bd1d78 | ||
|
|
a21e641748 | ||
|
|
208b0f6d0e | ||
|
|
0bc546595d | ||
|
|
a673b4ad66 | ||
|
|
9a68a5c063 | ||
|
|
e790df539d | ||
|
|
fbeb779335 | ||
|
|
625bfa5713 | ||
|
|
943a294900 | ||
|
|
3400dd5e42 | ||
|
|
e8b3c44de3 | ||
|
|
9575698fa4 | ||
|
|
5f0a7dd63e | ||
|
|
38bc11b7b8 | ||
|
|
04528aee1f | ||
|
|
5c82747970 | ||
|
|
b45c337515 | ||
|
|
9dc6b0e660 | ||
|
|
428165590e | ||
|
|
53051e04a3 | ||
|
|
869bcf8b2b | ||
|
|
382a65abcd | ||
|
|
908d705669 | ||
|
|
d422fb699f | ||
|
|
506e63284b | ||
|
|
ed92476916 | ||
|
|
1218afd51f | ||
|
|
b88722b4a6 | ||
|
|
694d297eb8 | ||
|
|
2053f2767b | ||
|
|
448176b0b7 | ||
|
|
b1a22b5002 | ||
|
|
e9aee55714 | ||
|
|
2acf3c57c7 | ||
|
|
f2babb6063 | ||
|
|
b8d07474fe | ||
|
|
cf913b41c6 | ||
|
|
adb3df05c6 | ||
|
|
34bded30eb | ||
|
|
774a375ce7 | ||
|
|
ee35001732 | ||
|
|
da4105ffb1 | ||
|
|
5c5692ae98 | ||
|
|
379d16abe7 | ||
|
|
078ce38c57 | ||
|
|
f99b903d78 | ||
|
|
3650b81970 | ||
|
|
bb6fcdfa6f | ||
|
|
8df112e157 | ||
|
|
af91f35017 | ||
|
|
3cce90bba1 | ||
|
|
1a351bdb6d | ||
|
|
efaac43248 | ||
|
|
f082cac762 | ||
|
|
2bd289ebd6 | ||
|
|
aba9baf207 | ||
|
|
e755c36ea2 | ||
|
|
6c7dc2a28e | ||
|
|
cdd530d807 | ||
|
|
02c0db7b8b | ||
|
|
8a62c12089 | ||
|
|
988549599d | ||
|
|
70159c00cc | ||
|
|
2dfbdea889 | ||
|
|
299d1f530f | ||
|
|
bf0ff8abbc | ||
|
|
123b1c8f44 | ||
|
|
a314e05826 | ||
|
|
83fd24faa2 | ||
|
|
1d4ad5f1d5 | ||
|
|
26d5cc694a | ||
|
|
a2122e0153 | ||
|
|
10fc94aaa7 | ||
|
|
0835bada39 | ||
|
|
277af1a2b6 | ||
|
|
5b835bec3e | ||
|
|
dc23cef59a | ||
|
|
098079d317 | ||
|
|
1ab57ecf20 | ||
|
|
808ab56545 | ||
|
|
19acdc7a19 | ||
|
|
e15fdc709f | ||
|
|
457244e3de | ||
|
|
d5559ecafd | ||
|
|
802fbfcf1e | ||
|
|
a20e74c401 | ||
|
|
7cdb1ce9eb | ||
|
|
0d170a70b6 | ||
|
|
b19cd0b87d | ||
|
|
50efc95c83 | ||
|
|
fa50268b4e | ||
|
|
ae1d51d089 | ||
|
|
1b8355ff07 | ||
|
|
f32afb70b8 | ||
|
|
60d96ca7b7 | ||
|
|
014f734260 | ||
|
|
de4963ef95 | ||
|
|
e7d3e60ebd | ||
|
|
a46f73ad24 | ||
|
|
759bc1d909 | ||
|
|
c79c9dac8d | ||
|
|
635d4babc4 | ||
|
|
9b3b4ae8be | ||
|
|
b3e7f074e9 | ||
|
|
ee1ed73fc5 | ||
|
|
10e11fb742 | ||
|
|
8b47317ec7 | ||
|
|
04626b72cd | ||
|
|
2151cd0929 | ||
|
|
93ded9c1e0 | ||
|
|
20964b43ce | ||
|
|
af192354fd | ||
|
|
ad48637cbb | ||
|
|
89507bd335 | ||
|
|
21533ffee4 | ||
|
|
4502a9286c | ||
|
|
ba11511c69 | ||
|
|
965ef19a5b | ||
|
|
8f86b331c1 | ||
|
|
59a1590955 | ||
|
|
fad87b294b | ||
|
|
13bb2b6690 | ||
|
|
4a803ed0cf |
15
.clang-format
Normal file
15
.clang-format
Normal file
@@ -0,0 +1,15 @@
|
||||
IndentWidth: 4
|
||||
UseCRLF: false
|
||||
IndentCaseLabels: true
|
||||
UseTab: ForIndentation
|
||||
TabWidth: 4
|
||||
BreakBeforeBraces: Allman
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeParens: ControlStatementsExceptControlMacros
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
44
.clang-tidy
44
.clang-tidy
@@ -1,21 +1,7 @@
|
||||
---
|
||||
# Configure clang-tidy for this project.
|
||||
|
||||
IndentWidth: 4
|
||||
UseCRLF: false
|
||||
IndentCaseLabels: true
|
||||
UseTab: UT_ForIndentation
|
||||
TabWidth: 4
|
||||
BreakBeforeBraces: Allman
|
||||
AllowShortBlocksOnASingleLine: SBS_Empty
|
||||
AllowShortIfStatementsOnASingleLine: SIS_WithoutElse
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeParens: SBPO_ControlStatementsExceptControlMacros
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
|
||||
|
||||
# Disabled:
|
||||
# -google-readability-namespace-comments the *_CLIENT_NS is a macro, and
|
||||
@@ -40,17 +26,19 @@ Checks: >
|
||||
|
||||
# Turn all the warnings from the checks above into errors.
|
||||
WarningsAsErrors: "*"
|
||||
|
||||
CheckOptions:
|
||||
- { key: readability-function-cognitive-complexity.Threshold, value: 100 }
|
||||
- { key: readability-identifier-naming.StructCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.FunctionCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.ConstexprVariablePrefix, value: k }
|
||||
- { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.GlobalConstantPrefix, value: k }
|
||||
- { key: readability-identifier-naming.StaticConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.StaticConstantPrefix, value: k }
|
||||
- { key: readability-function-cognitive-complexity.Threshold, value: 100 }
|
||||
- { key: readability-identifier-naming.StructCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.FunctionCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.ConstexprVariablePrefix, value: k }
|
||||
- { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.GlobalConstantPrefix, value: k }
|
||||
- { key: readability-identifier-naming.StaticConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.StaticConstantPrefix, value: k }
|
||||
- { key: readability-identifier-naming.MinimumParameterNameLength, value: 0 }
|
||||
|
||||
MinimumParameterNameLength: 0
|
||||
|
||||
964
.github/workflows/main.yml
vendored
964
.github/workflows/main.yml
vendored
File diff suppressed because it is too large
Load Diff
21
.gitignore
vendored
21
.gitignore
vendored
@@ -1,5 +1,7 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
testrun
|
||||
benchmarkrun
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
@@ -7,6 +9,8 @@
|
||||
*.obj
|
||||
*.elf
|
||||
*.ll
|
||||
*.wasm
|
||||
*.s
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
@@ -69,15 +73,21 @@ out/
|
||||
/cmake-build-debug/
|
||||
/cmake-build-release/
|
||||
|
||||
# Emacs files
|
||||
# etags(Emacs), ctags, gtags
|
||||
TAGS
|
||||
GPATH
|
||||
GRTAGS
|
||||
GTAGS
|
||||
tags
|
||||
|
||||
# Clangd LSP files
|
||||
/.cache/
|
||||
/compile_commands.json
|
||||
|
||||
# 'nix build' resulting symlink
|
||||
# Nix
|
||||
result
|
||||
/.envrc
|
||||
/.direnv/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
@@ -85,4 +95,9 @@ result
|
||||
# tests
|
||||
/test/tmp/*
|
||||
/test/testrun
|
||||
/test/test_suite_runner
|
||||
/test/test_suite_runner
|
||||
|
||||
# patches, originals and rejects
|
||||
*.patch
|
||||
*.rej
|
||||
*.orig
|
||||
|
||||
118
CMakeLists.txt
118
CMakeLists.txt
@@ -1,8 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
set(C3_LLVM_MIN_VERSION 17)
|
||||
set(C3_LLVM_MAX_VERSION 21)
|
||||
set(C3_LLVM_DEFAULT_VERSION 19)
|
||||
set(C3_LLVM_MAX_VERSION 22)
|
||||
set(C3_LLVM_DEFAULT_VERSION 21)
|
||||
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
|
||||
message(FATAL_ERROR "In-tree build detected, please build in a separate directory")
|
||||
@@ -56,29 +56,49 @@ 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}")
|
||||
add_compile_options(/utf-8)
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
FetchContent_GetProperties(LLVM_Windows)
|
||||
if(NOT LLVM_Windows_URL MATCHES "msvcrt")
|
||||
set(MSVC_CRT_SUFFIX "")
|
||||
message(STATUS "Detected STATIC LLVM (libcmt)")
|
||||
else()
|
||||
set(MSVC_CRT_SUFFIX "DLL")
|
||||
message(STATUS "Detected DYNAMIC LLVM (msvcrt)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Force the Runtime to Release (/MT or /MD) even in Debug
|
||||
# This is required to match our RelWithDebInfo LLVM builds
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
set_property(GLOBAL PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
|
||||
add_compile_definitions(_ITERATOR_DEBUG_LEVEL=0)
|
||||
|
||||
# Suppresses the LNK4098 mismatch warning in Debug builds
|
||||
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:libcmtd /NODEFAULTLIB:msvcrtd")
|
||||
else()
|
||||
add_compile_options(-gdwarf-3 -fno-exceptions)
|
||||
|
||||
# add_compile_options(-fsanitize=address,undefined)
|
||||
# add_link_options(-fsanitize=address,undefined)
|
||||
#add_compile_options(-fsanitize=address,undefined)
|
||||
#add_link_options(-fsanitize=address,undefined)
|
||||
endif()
|
||||
|
||||
# Options
|
||||
set(C3_LINK_DYNAMIC OFF CACHE BOOL "Link dynamically with LLVM/LLD libs")
|
||||
set(C3_WITH_LLVM ON CACHE BOOL "Build with LLVM")
|
||||
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
|
||||
set(C3_USE_MIMALLOC OFF CACHE BOOL "Use built-in mimalloc")
|
||||
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
|
||||
set(C3_USE_TB OFF CACHE BOOL "Use TB")
|
||||
set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory")
|
||||
set(C3_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_LINK_DYNAMIC OFF CACHE BOOL "Link dynamically with LLVM/LLD libs")
|
||||
set(C3_WITH_LLVM ON CACHE BOOL "Build with LLVM")
|
||||
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
|
||||
set(C3_USE_MIMALLOC OFF CACHE BOOL "Use built-in mimalloc")
|
||||
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
|
||||
set(C3_USE_TB OFF CACHE BOOL "Use TB")
|
||||
set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory")
|
||||
set(C3_LLD_INCLUDE_DIR "" CACHE STRING "Use custom LLD include directory")
|
||||
set(C3_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation")
|
||||
set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
|
||||
set(TCC_LIB_PATH "/usr/lib/tcc/libtcc1.a" CACHE STRING "Use custom libtcc1.a path")
|
||||
|
||||
set(C3_OPTIONS
|
||||
C3_LINK_DYNAMIC
|
||||
@@ -141,11 +161,11 @@ if(C3_WITH_LLVM)
|
||||
endif()
|
||||
FetchContent_Declare(
|
||||
LLVM_Windows
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt.7z
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_21_1_8/llvm-21.1.8-windows-amd64-msvc17-libcmt.7z
|
||||
)
|
||||
FetchContent_Declare(
|
||||
LLVM_Windows_debug
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt-dbg.7z
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_21_1_8/llvm-21.1.8-windows-amd64-msvc17-libcmt-dbg.7z
|
||||
)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
message("Loading Windows LLVM debug libraries, this may take a while...")
|
||||
@@ -262,20 +282,33 @@ if(C3_WITH_LLVM)
|
||||
find_library(LLD_COFF NAMES liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.dll.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.dll.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.dll.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
||||
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
else()
|
||||
set(LLD_MACHO "")
|
||||
endif()
|
||||
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.dll.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.dll.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
else()
|
||||
message(STATUS "Looking for shared lld libraries in ${LLVM_LIBRARY_DIRS}")
|
||||
|
||||
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
#find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
if(UNIX AND NOT WIN32)
|
||||
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} REQUIRED)
|
||||
else()
|
||||
find_library(LLVM NAMES libLLVM.a LLVM.lib PATHS ${LLVM_LIBRARY_DIRS} REQUIRED)
|
||||
endif()
|
||||
set(llvm_libs ${LLVM})
|
||||
|
||||
# These don't seem to be reliable on windows.
|
||||
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_MACHO NAMES liblldMachO.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()
|
||||
@@ -382,6 +415,7 @@ add_executable(c3c
|
||||
src/utils/unzipper.c
|
||||
src/compiler/c_codegen.c
|
||||
src/compiler/decltable.c
|
||||
src/compiler/methodtable.c
|
||||
src/compiler/mac_support.c
|
||||
src/compiler/windows_support.c
|
||||
src/compiler/codegen_asm.c
|
||||
@@ -425,10 +459,18 @@ if(C3_WITH_LLVM)
|
||||
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=1)
|
||||
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
|
||||
if (MSVC)
|
||||
# Use the same detected CRT for the wrapper
|
||||
set_target_properties(c3c_wrappers PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
set_target_properties(miniz PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
|
||||
target_compile_options(c3c PRIVATE
|
||||
"$<$<CONFIG:Debug>:/EHa>"
|
||||
"$<$<CONFIG:Release>:/EHsc>")
|
||||
endif()
|
||||
|
||||
if(C3_LLD_INCLUDE_DIR)
|
||||
target_include_directories(c3c_wrappers PRIVATE ${C3_LLD_INCLUDE_DIR})
|
||||
endif()
|
||||
else()
|
||||
target_sources(c3c PRIVATE src/utils/hostinfo.c)
|
||||
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0)
|
||||
@@ -512,7 +554,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)
|
||||
@@ -542,13 +584,21 @@ if(MSVC)
|
||||
endif()
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
set(clang_lib_dir ${llvm_dir}/lib/clang/${C3_LLVM_VERSION}/lib/windows)
|
||||
set(sanitizer_runtime_libraries
|
||||
${clang_lib_dir}/clang_rt.asan-x86_64.lib
|
||||
${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.lib
|
||||
${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.dll
|
||||
${clang_lib_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
|
||||
# The the sanitizer libs are in the folder "lib/clang/21/lib/windows/" but use the find anyway
|
||||
file(GLOB_RECURSE FOUND_ASAN_LIB "${llvm_dir}/*clang_rt.asan_dynamic-x86_64.lib")
|
||||
if(FOUND_ASAN_LIB)
|
||||
list(GET FOUND_ASAN_LIB 0 _asan_path)
|
||||
get_filename_component(_asan_dir "${_asan_path}" DIRECTORY)
|
||||
set(sanitizer_runtime_libraries
|
||||
${_asan_dir}/clang_rt.asan_dynamic-x86_64.lib
|
||||
${_asan_dir}/clang_rt.asan_dynamic-x86_64.dll
|
||||
${_asan_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
|
||||
message(STATUS "Found Sanitizer binaries at: ${_asan_dir}")
|
||||
else()
|
||||
message(WARNING "Could not find sanitizer runtime libraries in ${llvm_dir}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
else()
|
||||
if (C3_WITH_LLVM AND NOT LLVM_ENABLE_RTTI)
|
||||
target_compile_options(c3c_wrappers PRIVATE -fno-rtti)
|
||||
@@ -562,10 +612,22 @@ else()
|
||||
-Wno-unused-function
|
||||
-Wno-unused-variable
|
||||
-Wno-unused-parameter
|
||||
-Wno-char-subscripts
|
||||
)
|
||||
target_link_options(c3c PRIVATE -pthread)
|
||||
endif()
|
||||
|
||||
if(CMAKE_C_COMPILER_ID STREQUAL "TinyCC")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-eh-frame-hdr -z noexecstack")
|
||||
|
||||
# Link the static tcc runtime archive if it exists
|
||||
if(EXISTS "${TCC_LIB_PATH}")
|
||||
target_link_libraries(c3c "${TCC_LIB_PATH}")
|
||||
else()
|
||||
message(FATAL_ERROR "TCC runtime not found at ${TCC_LIB_PATH}; Ensure the path is correct.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
install(TARGETS c3c DESTINATION bin)
|
||||
install(DIRECTORY lib/ DESTINATION lib/c3)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at . All
|
||||
reported by contacting the project team at info@c3-lang.org. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
179
LICENSE
179
LICENSE
@@ -1,165 +1,20 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
Copyright (c) 2022-2025 Christoffer Lernö and contributors
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
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.
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
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.
|
||||
165
LICENSE_SRC
Normal file
165
LICENSE_SRC
Normal file
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -1,20 +0,0 @@
|
||||
Copyright (c) 2022 Christoffer Lernö and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
220
README.md
220
README.md
@@ -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-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).
|
||||
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
|
||||
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
|
||||
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
|
||||
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
|
||||
- OpenBSD x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz), [install instructions](#installing-on-openbsd-with-precompiled-binaries).
|
||||
|
||||
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
|
||||
|
||||
@@ -35,10 +36,10 @@ whole new language.
|
||||
|
||||
### Example code
|
||||
|
||||
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/).
|
||||
The following code shows [generics](https://c3-lang.org/generic-programming/generics/) (more examples can be found at https://c3-lang.org/language-overview/examples/).
|
||||
|
||||
```cpp
|
||||
module stack {Type};
|
||||
```c3
|
||||
module stack <Type>;
|
||||
// Above: the parameterized type is applied to the entire module.
|
||||
|
||||
struct Stack
|
||||
@@ -77,7 +78,7 @@ fn bool Stack.empty(Stack* this)
|
||||
|
||||
Testing it out:
|
||||
|
||||
```cpp
|
||||
```c3
|
||||
import stack;
|
||||
|
||||
// Define our new types, the first will implicitly create
|
||||
@@ -141,7 +142,7 @@ fn void main()
|
||||
|
||||
### Current status
|
||||
|
||||
The current stable version of the compiler is **version 0.7.3**.
|
||||
The current stable version of the compiler is **version 0.7.9**.
|
||||
|
||||
The upcoming 0.7.x releases will focus on expanding the standard library,
|
||||
fixing bugs and improving compile time analysis.
|
||||
@@ -150,7 +151,7 @@ Follow the issues [here](https://github.com/c3lang/c3c/issues).
|
||||
If you have suggestions on how to improve the language, either [file an issue](https://github.com/c3lang/c3c/issues)
|
||||
or discuss C3 on its dedicated Discord: [https://discord.gg/qN76R87](https://discord.gg/qN76R87).
|
||||
|
||||
The compiler is currently verified to compile on Linux, Windows and MacOS.
|
||||
The compiler is currently verified to compile on Linux, OpenBSD, Windows and MacOS.
|
||||
|
||||
**Support matrix**
|
||||
|
||||
@@ -171,17 +172,21 @@ The compiler is currently verified to compile on Linux, Windows and MacOS.
|
||||
| ELF freestanding Aarch64 | No | Untested | No | No | No | Yes* |
|
||||
| ELF freestanding Riscv64 | No | Untested | No | No | No | Untested |
|
||||
| ELF freestanding Riscv32 | No | Untested | No | No | No | Untested |
|
||||
| ELF freestanding Xtensa* | No | Untested | No | No | No | Untested |
|
||||
| FreeBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| FreeBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| NetBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| NetBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| OpenBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| OpenBSD x64 | 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*<br>
|
||||
*\* Xtensa support is enabled by compiling with `-DXTENSA_ENABLE`. The [espressif llvm fork](https://github.com/espressif/llvm-project) is recommended for best compatibility*
|
||||
|
||||
More platforms will be supported in the future.
|
||||
|
||||
@@ -200,33 +205,72 @@ More platforms will be supported in the future.
|
||||
This installs the latest prerelease build, as opposed to the latest released version.
|
||||
|
||||
#### Installing on Windows with precompiled binaries
|
||||
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/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))
|
||||
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows-debug.zip))
|
||||
2. Unzip exe and standard lib.
|
||||
3. If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
|
||||
4. Run `c3c.exe`.
|
||||
|
||||
#### Installing on Windows with the install script
|
||||
|
||||
Open a PowerShell terminal (you may need to run it as an administrator) and run the following command:
|
||||
```bash
|
||||
iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex
|
||||
```
|
||||
The script will inform you once the installation is successful and add the `~/.c3` directory to your PATH, which will allow you to run the c3c command from any location.
|
||||
|
||||
You can choose another version with option `C3_VERSION`.
|
||||
For example, you can force the installation of the 0.7.4 version:
|
||||
```bash
|
||||
$env:C3_VERSION='0.7.4'; powershell -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex"
|
||||
```
|
||||
|
||||
If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
|
||||
|
||||
|
||||
#### Installing on Debian with precompiled binaries
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/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))
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux-debug.tar.gz))
|
||||
2. Unpack executable and standard lib.
|
||||
3. Run `./c3c`.
|
||||
|
||||
#### Installing on Debian with the install script
|
||||
|
||||
Open a terminal and run the following command:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | bash
|
||||
```
|
||||
The C3 compiler will be installed, and the script will also update your ~/.bashrc to include `~/.c3` in your PATH, allowing you to invoke the c3c command from anywhere. You might need to restart your terminal or source your shell for the changes to take effect.
|
||||
|
||||
You can choose another version with option `C3_VERSION`.
|
||||
For example, you can force the installation of the 0.7.4 version:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | C3_VERSION=0.7.4 bash
|
||||
```
|
||||
|
||||
#### Installing on Ubuntu with precompiled binaries
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/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))
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20-debug.tar.gz))
|
||||
2. Unpack executable and standard lib.
|
||||
3. Run `./c3c`.
|
||||
|
||||
#### Installing on MacOS with precompiled binaries
|
||||
1. Make sure you have XCode with command line tools installed.
|
||||
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/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))
|
||||
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos.zip)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos-debug.zip))
|
||||
3. Unzip executable and standard lib.
|
||||
4. Run `./c3c`.
|
||||
|
||||
(*Note that there is a known issue with debug symbol generation on MacOS 13, see [issue #1086](https://github.com/c3lang/c3c/issues/1086))
|
||||
|
||||
#### Installing on OpenBSD with precompiled binaries
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd-debug.tar.gz))
|
||||
2. Unpack executable and standard lib.
|
||||
3. Run `./c3c`.
|
||||
|
||||
(*Note that this is specifically for OpenBSD 7.7, running it on any other version is prone to ABI breaks)
|
||||
|
||||
#### Installing on Arch Linux
|
||||
Arch includes c3c in the official 'extra' repo. It can be easily installed the usual way:
|
||||
|
||||
@@ -253,6 +297,60 @@ cd c3c-git
|
||||
makepkg -si
|
||||
```
|
||||
|
||||
#### Installing via Nix
|
||||
|
||||
You can access `c3c` via [flake.nix](./flake.nix), which will contain the latest commit of the compiler. To add `c3c` to your `flake.nix`, do the following:
|
||||
```nix
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
c3c.url = "github:c3lang/c3c";
|
||||
# Those are desired if you don't want to copy extra nixpkgs
|
||||
c3c.inputs = {
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import inputs.nixpkgs { inherit system; };
|
||||
c3c = inputs.c3c.packages.${system}.c3c;
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.c3c
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Installing on Gentoo
|
||||
|
||||
`c3c` is available in the [Gentoo GURU overlay](https://wiki.gentoo.org/wiki/Project:GURU).
|
||||
|
||||
Enable and sync the GURU repository (if not already done):
|
||||
|
||||
```sh
|
||||
sudo eselect repository enable guru
|
||||
sudo emaint sync -r guru
|
||||
```
|
||||
|
||||
Install `c3c` with:
|
||||
|
||||
```sh
|
||||
sudo emerge -av dev-lang/c3c
|
||||
```
|
||||
|
||||
* The compiler binary is installed to `/usr/bin/c3c`.
|
||||
* The standard library is installed to `/usr/lib/c3`.
|
||||
|
||||
For Gentoo-specific issues, please use the [Gentoo Bugzilla](https://bugs.gentoo.org/) (Product: *GURU*).
|
||||
|
||||
#### Building via Docker
|
||||
|
||||
You can build `c3c` using an Ubuntu container. By default, the script will build through Ubuntu 22.04. You can specify the version by passing the `UBUNTU_VERSION` environment variable.
|
||||
@@ -265,14 +363,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
|
||||
|
||||
@@ -285,7 +384,7 @@ scoop install c3
|
||||
#### Getting started with a "hello world"
|
||||
|
||||
Create a `main.c3` file with:
|
||||
```c++
|
||||
```c3
|
||||
module hello_world;
|
||||
import std::io;
|
||||
|
||||
@@ -336,13 +435,12 @@ 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.
|
||||
|
||||
@@ -355,13 +453,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
|
||||
@@ -371,15 +468,15 @@ For a sytem-wide installation, run the following as root: `cmake --install .`
|
||||
3. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
|
||||
4. Enter the C3C directory: `cd c3c`
|
||||
5. Create a build directory and navigate into it: `mkdir build && cd build`
|
||||
6. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake .. -DC3_LINK_DYNAMIC=1`
|
||||
7. Build the project: `cmake --build .`
|
||||
5. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake -B build -S . -DC3_LINK_DYNAMIC=1`
|
||||
6. Build the project: `cmake --build build`
|
||||
7. Enter the build directory: `cd build`
|
||||
|
||||
The c3c binary should be created in the build directory. You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
|
||||
|
||||
#### Compiling on Arch Linux
|
||||
|
||||
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm`
|
||||
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm libxml2`
|
||||
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
|
||||
3. Enter the C3C directory: `cd c3c`
|
||||
@@ -389,32 +486,44 @@ cmake -B build \
|
||||
-D C3_LINK_DYNAMIC=ON \
|
||||
-D CMAKE_BUILD_TYPE=Release
|
||||
```
|
||||
5. Build the project: `make -C build`.
|
||||
5. Build the project: `cmake --build build`.
|
||||
|
||||
After compilation, the `c3c` binary will be located in the `build` directory. You can test it by compiling an example: `./build/c3c compile resources/examples/ls.c3`.
|
||||
|
||||
6. To install the compiler globally: `sudo cmake --install build`
|
||||
|
||||
#### Compiling on NixOS
|
||||
|
||||
1. Enter nix shell, by typing `nix develop` in root directory
|
||||
2. Configure cmake via `cmake . -Bbuild $=C3_CMAKE_FLAGS`. Note: passing `C3_CMAKE_FLAGS` is needed in due to generate `compile_commands.json` and find missing libs.
|
||||
4. Build it `cmake --build build`
|
||||
5. Test it out: `./build/c3c -V`
|
||||
6. If you use `clangd` lsp server for your editor, it is recommended to make a symbolic link to `compile_command.json` in the root: `ln -s ./build/compile_commands.json compile_commands.json`
|
||||
|
||||
#### Compiling on other Linux / Unix variants
|
||||
|
||||
1. Install CMake.
|
||||
2. Install or compile LLVM and LLD *libraries* (version 17+ or higher)
|
||||
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
|
||||
this functionality is non-essential and it is perfectly fine to user the compiler without it.*
|
||||
this functionality is non-essential and it is perfectly fine to use the compiler without it.*
|
||||
|
||||
#### Licensing
|
||||
|
||||
The C3 compiler is licensed under LGPL 3.0, the standard library itself is
|
||||
MIT licensed.
|
||||
Unless specified otherwise, the code in this repository is MIT licensed.
|
||||
The exception is the compiler source code (the source code under `src`),
|
||||
which is licensed under LGPL 3.0.
|
||||
|
||||
This means you are free to use all parts of standard library,
|
||||
tests, benchmarks, grammar, examples and so on under the MIT license, including
|
||||
using those libraries and tests if you build your own C3 compiler.
|
||||
|
||||
#### Editor plugins
|
||||
|
||||
@@ -433,7 +542,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
|
||||
|
||||
|
||||
219
benchmarks/stdlib/collections/hashmap.c3
Normal file
219
benchmarks/stdlib/collections/hashmap.c3
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
//
|
||||
// Some benchmark test ideas are sourced from this article on C++ hashmap benchmarking:
|
||||
// https://martin.ankerl.com/2022/08/27/hashmap-bench-01/
|
||||
//
|
||||
module hashmap_benchmarks;
|
||||
|
||||
import std::collections::map;
|
||||
import std::math::random;
|
||||
|
||||
|
||||
const DEFAULT_ITERATIONS = 16384;
|
||||
|
||||
Lcg64Random rand;
|
||||
|
||||
HashMap { int, int } modifying_numbers_random;
|
||||
|
||||
fn void bench_setup() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(DEFAULT_ITERATIONS);
|
||||
|
||||
// TODO: Cannot take the address of a @benchmark function. If we could, we could pass &insert_erase as a fn ptr and use the $qnameof CT eval internally.
|
||||
set_benchmark_func_iterations($qnameof(insert_erase), 32);
|
||||
set_benchmark_func_iterations($qnameof(random_access), 1024);
|
||||
|
||||
random::seed(&rand, 0x4528_21e6_38d0_1377);
|
||||
|
||||
for (usz i = 0; i < 1_000; ++i) modifying_numbers_random.set(rand.next_int(), rand.next_int());
|
||||
}
|
||||
|
||||
|
||||
// ==============================================================================================
|
||||
module hashmap_benchmarks @benchmark;
|
||||
|
||||
import std::collections::map;
|
||||
|
||||
import std::math::random;
|
||||
import std::encoding::base64;
|
||||
|
||||
|
||||
fn void generic_hash_speeds()
|
||||
{
|
||||
(char){}.hash();
|
||||
(char[<100>]){}.hash();
|
||||
(char[100]){}.hash();
|
||||
(ichar){}.hash();
|
||||
(ichar[<100>]){}.hash();
|
||||
(ichar[100]){}.hash();
|
||||
(short){}.hash();
|
||||
(short[<100>]){}.hash();
|
||||
(short[100]){}.hash();
|
||||
(ushort){}.hash();
|
||||
(ushort[<100>]){}.hash();
|
||||
(ushort[100]){}.hash();
|
||||
(int){}.hash();
|
||||
(int[<100>]){}.hash();
|
||||
(int[100]){}.hash();
|
||||
(uint){}.hash();
|
||||
(uint[<100>]){}.hash();
|
||||
(uint[100]){}.hash();
|
||||
(long){}.hash();
|
||||
(long[<20>]){}.hash();
|
||||
(long[100]){}.hash();
|
||||
(ulong){}.hash();
|
||||
(ulong[<20>]){}.hash();
|
||||
(ulong[100]){}.hash();
|
||||
(int128){}.hash();
|
||||
(int128[<20>]){}.hash();
|
||||
(int128[100]){}.hash();
|
||||
(uint128){}.hash();
|
||||
(uint128[<20>]){}.hash();
|
||||
(uint128[100]){}.hash();
|
||||
(bool){}.hash();
|
||||
(bool[<100>]){}.hash();
|
||||
(bool[100]){}.hash();
|
||||
String x = "abc";
|
||||
char[] y = "abc";
|
||||
assert(x.hash() == y.hash());
|
||||
String z1 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
|
||||
char[] z2 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
|
||||
assert(z1.hash() == z2.hash());
|
||||
assert(int.typeid.hash());
|
||||
}
|
||||
|
||||
|
||||
fn void hash_speeds_of_many_random_values() => @pool()
|
||||
{
|
||||
var $arrsz = 10_000;
|
||||
uint fake_checksum;
|
||||
|
||||
char[] chars = allocator::new_array(tmem, char, $arrsz)[:$arrsz];
|
||||
foreach (&v : chars) *v = (char)random::next(&rand, uint.max);
|
||||
|
||||
ushort[] shorts = allocator::new_array(tmem, ushort, $arrsz)[:$arrsz];
|
||||
foreach (&v : shorts) *v = (ushort)random::next(&rand, uint.max);
|
||||
|
||||
uint[] ints = allocator::new_array(tmem, uint, $arrsz)[:$arrsz];
|
||||
foreach (&v : ints) *v = random::next(&rand, uint.max);
|
||||
|
||||
ulong[] longs = allocator::new_array(tmem, ulong, $arrsz)[:$arrsz];
|
||||
foreach (&v : longs) *v = (ulong)random::next(&rand, uint.max);
|
||||
|
||||
uint128[] vwideints = allocator::new_array(tmem, uint128, $arrsz)[:$arrsz];
|
||||
foreach (&v : vwideints) *v = (uint128)random::next(&rand, uint.max);
|
||||
|
||||
char[48][] zstrs = allocator::new_array(tmem, char[48], $arrsz)[:$arrsz];
|
||||
|
||||
String[] strs = mem::temp_array(String, $arrsz);
|
||||
foreach (x, &v : zstrs)
|
||||
{
|
||||
foreach (&c : (*v)[:random::next(&rand, 48)]) *c = (char)random::next(&rand, char.max);
|
||||
strs[x] = ((ZString)&v[0]).str_view();
|
||||
}
|
||||
|
||||
runtime::@start_benchmark();
|
||||
foreach (v : chars) fake_checksum += v.hash();
|
||||
foreach (v : shorts) fake_checksum += v.hash();
|
||||
foreach (v : ints) fake_checksum += v.hash();
|
||||
foreach (v : longs) fake_checksum += v.hash();
|
||||
foreach (v : vwideints) fake_checksum += v.hash();
|
||||
foreach (v : strs) fake_checksum += v.hash();
|
||||
runtime::@end_benchmark();
|
||||
}
|
||||
|
||||
|
||||
fn void modifying_numbers_init_from_map() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit_from_map(&modifying_numbers_random);
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void insert_erase() => @pool()
|
||||
{
|
||||
uint iters = 1_000_000;
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (int i = 0; i < iters; ++i) v[i] = i;
|
||||
for (int i = 0; i < iters; ++i) v.remove(i);
|
||||
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
uint bound = 10_000;
|
||||
usz pseudo_checksum = 0;
|
||||
|
||||
for (uint i = 0; i < bound; ++i) v[i] = i;
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (uint i = 0; i < 1_000_000; ++i) pseudo_checksum += (v[i.hash() % bound] ?? 0);
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access_erase() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
uint bound = 10_000;
|
||||
|
||||
for (uint i = 0; i < bound; ++i) v[i] = i;
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (uint i = 0; i < bound; ++i)
|
||||
{
|
||||
v[i.hash() % bound] = i; // supplant an entry
|
||||
|
||||
v.remove(random::next(&rand, bound)); // remove a random entry
|
||||
}
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access_string_keys() => @pool()
|
||||
{
|
||||
HashMap { String, ulong } v;
|
||||
v.tinit();
|
||||
|
||||
usz pseudo_checksum = 0;
|
||||
String[] saved = mem::temp_array(String, 5_000);
|
||||
|
||||
for (usz i = 0; i < saved.len; ++i)
|
||||
{
|
||||
ulong hash = i.hash();
|
||||
String b64key = base64::tencode(@as_char_view(hash));
|
||||
|
||||
v[b64key] = hash;
|
||||
|
||||
if (i < saved.len) saved[i] = b64key;
|
||||
}
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (usz i = 0; i < saved.len; ++i)
|
||||
{
|
||||
pseudo_checksum += v[ saved[random::next(&rand, saved.len)] ]!! % 512;
|
||||
}
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
38
benchmarks/stdlib/collections/linkedlist.c3
Normal file
38
benchmarks/stdlib/collections/linkedlist.c3
Normal file
@@ -0,0 +1,38 @@
|
||||
module linkedlist_benchmarks;
|
||||
|
||||
import std::collections::linkedlist;
|
||||
|
||||
|
||||
LinkedList{int} long_list;
|
||||
const HAY = 2;
|
||||
const NEEDLE = 1000;
|
||||
|
||||
fn void bench_setup() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(4096);
|
||||
|
||||
int[*] haystack = { [0..999] = HAY };
|
||||
long_list = linkedlist::@new{int}(mem, haystack[..]);
|
||||
long_list.push(NEEDLE);
|
||||
long_list.push_all(haystack[..]);
|
||||
}
|
||||
|
||||
|
||||
// ==============================================================================================
|
||||
module linkedlist_benchmarks @benchmark;
|
||||
|
||||
String die_str = "Failed to find the value `1`. Is something broken?";
|
||||
|
||||
|
||||
fn void foreach_iterator()
|
||||
{
|
||||
foreach (v : long_list.array_view()) if (v == NEEDLE) return;
|
||||
runtime::@kill_benchmark(die_str);
|
||||
}
|
||||
|
||||
fn void foreach_r_iterator()
|
||||
{
|
||||
foreach_r (v : long_list.array_view()) if (v == NEEDLE) return;
|
||||
runtime::@kill_benchmark(die_str);
|
||||
}
|
||||
46
benchmarks/stdlib/core/string_trim.c3
Normal file
46
benchmarks/stdlib/core/string_trim.c3
Normal file
@@ -0,0 +1,46 @@
|
||||
module string_trim_wars;
|
||||
|
||||
const String WHITESPACE_TARGET = " \n\t\r\f\va \tbcde\v\f\r\t\n ";
|
||||
const String WHITESPACE_NUMERIC_TARGET = " 25290 0969 99a \tbcde12332 34 43 0000";
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(64);
|
||||
set_benchmark_max_iterations(1 << 24);
|
||||
}
|
||||
|
||||
macro void trim_bench($trim_str, String $target = WHITESPACE_TARGET) => @pool()
|
||||
{
|
||||
String s1;
|
||||
String s2 = $target.tcopy();
|
||||
|
||||
runtime::@start_benchmark();
|
||||
|
||||
$switch:
|
||||
$case $typeof($trim_str) == String:
|
||||
s1 = s2.trim($trim_str);
|
||||
$case $typeof($trim_str) == AsciiCharset:
|
||||
s1 = s2.trim_charset($trim_str);
|
||||
$default: $error "Unable to determine the right String `trim` operation to use.";
|
||||
$endswitch
|
||||
|
||||
@volatile_load(s1);
|
||||
|
||||
runtime::@end_benchmark();
|
||||
}
|
||||
|
||||
|
||||
module string_trim_wars @benchmark;
|
||||
|
||||
fn void trim_control() => trim_bench(" "); // only spaces
|
||||
|
||||
fn void trim_whitespace_default() => trim_bench("\t\n\r "); // default set
|
||||
fn void trim_whitespace_default_ordered() => trim_bench(" \n\t\r"); // default \w set, but ordered by expected freq
|
||||
|
||||
fn void trim_whitespace_bad() => trim_bench("\f\v\n\t\r "); // bad-perf ordering, all \w
|
||||
|
||||
fn void trim_whitespace_ordered_extended() => trim_bench(" \n\t\r\f\v"); // proposed ordering, all \w
|
||||
fn void trim_charset_whitespace() => trim_bench(ascii::WHITESPACE_SET); // use charset, all \w
|
||||
|
||||
fn void trim_many() => trim_bench(" \n\t\r\f\v0123456789", WHITESPACE_NUMERIC_TARGET); // ordered, all \w + num
|
||||
fn void trim_charset_many() => trim_bench(ascii::WHITESPACE_SET | ascii::NUMBER_SET, WHITESPACE_NUMERIC_TARGET); // set, all \w + num
|
||||
31
benchmarks/stdlib/crypto/aes_bench.c3
Normal file
31
benchmarks/stdlib/crypto/aes_bench.c3
Normal file
@@ -0,0 +1,31 @@
|
||||
module std::crypto::aes_bench;
|
||||
|
||||
import std::crypto::aes;
|
||||
|
||||
fn void init() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(5);
|
||||
set_benchmark_max_iterations(10_000);
|
||||
}
|
||||
|
||||
|
||||
AesType aes = AES256;
|
||||
char[] key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4";
|
||||
char[] text = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710";
|
||||
char[] cipher = x"601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6";
|
||||
char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
|
||||
|
||||
fn void bench_ctr_xcrypt() @benchmark
|
||||
{
|
||||
char[64] out;
|
||||
Aes ctx;
|
||||
|
||||
// encrypt
|
||||
ctx.init(aes, key, iv);
|
||||
ctx.encrypt_buffer(text, &out);
|
||||
|
||||
// decrypt
|
||||
ctx.init(aes, key, iv);
|
||||
ctx.decrypt_buffer(cipher, &out);
|
||||
}
|
||||
|
||||
39
benchmarks/stdlib/crypto/chacha20.c3
Normal file
39
benchmarks/stdlib/crypto/chacha20.c3
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module chacha20_benchmarks;
|
||||
|
||||
import std::crypto::chacha20;
|
||||
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(1024);
|
||||
}
|
||||
|
||||
const char[] KEY = x'98bef1469be7269837a45bfbc92a5a6ac762507cf96443bf33b96b1bd4c6f8f6';
|
||||
const char[] NONCE = x'44e792d63335abb1582e9253';
|
||||
const uint COUNTER = 42;
|
||||
|
||||
char[] one_mb @align(ulong.sizeof) = { [0..1024*1024] = 0xA5 };
|
||||
|
||||
// This doesn't test both encryption + decryption, because it's a symmetric operation that shares
|
||||
// a single common data transformation. Testing one limb is enough.
|
||||
fn void gogo_chacha20() @benchmark
|
||||
{
|
||||
chacha20::encrypt_mut(one_mb[..], KEY, NONCE, COUNTER);
|
||||
}
|
||||
|
||||
// Check what the speed of an unligned buffer looks like.
|
||||
fn void gogo_chacha20_unaligned() @benchmark => @pool()
|
||||
{
|
||||
char[] copy = mem::talloc_array(char, one_mb.len + 3);
|
||||
char[] im_off_slightly = copy[3..];
|
||||
copy[3..] = one_mb[..];
|
||||
assert((usz)im_off_slightly.ptr % usz.sizeof > 0);
|
||||
|
||||
runtime::@start_benchmark();
|
||||
chacha20::encrypt_mut(im_off_slightly, KEY, NONCE, COUNTER);
|
||||
runtime::@end_benchmark();
|
||||
}
|
||||
113
benchmarks/stdlib/crypto/crypto_shootout.c3
Normal file
113
benchmarks/stdlib/crypto/crypto_shootout.c3
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module crypto_hash_benchmarks;
|
||||
|
||||
import std::collections::pair;
|
||||
|
||||
|
||||
const usz COMMON_ITERATIONS = 1 << 17;
|
||||
|
||||
char* common_1mib_ptr;
|
||||
char[] common_16;
|
||||
char[] common_256;
|
||||
char[] common_4kib;
|
||||
char[] common_1mib;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(COMMON_ITERATIONS + 3);
|
||||
|
||||
common_1mib_ptr = mem::alloc_array(char, 1024*1024);
|
||||
common_1mib = common_1mib_ptr[:1024*1024];
|
||||
common_1mib[..] = 0xA5;
|
||||
|
||||
common_16 = common_1mib[:16];
|
||||
common_256 = common_1mib[:256];
|
||||
common_4kib = common_1mib[:4096];
|
||||
|
||||
static String[] function_prefixes = {
|
||||
$qnameof(md5_16)[..^4],
|
||||
$qnameof(sha1_16)[..^4],
|
||||
$qnameof(sha2_256_16)[..^4],
|
||||
$qnameof(sha2_512_16)[..^4],
|
||||
$qnameof(blake2s_256_16)[..^4],
|
||||
$qnameof(blake2b_256_16)[..^4],
|
||||
$qnameof(blake3_16)[..^4],
|
||||
$qnameof(ripemd_160_16)[..^4],
|
||||
$qnameof(whirlpool_16)[..^4],
|
||||
$qnameof(streebog_256_16)[..^4],
|
||||
$qnameof(streebog_512_16)[..^4],
|
||||
};
|
||||
|
||||
static Pair{ String, uint }[] to_iters = {
|
||||
{ "_4kib", 1 << 15 },
|
||||
{ "_1mib", 1024 },
|
||||
};
|
||||
|
||||
foreach (p : to_iters)
|
||||
{
|
||||
foreach (name : function_prefixes) set_benchmark_func_iterations(name.tconcat(p.first), p.second);
|
||||
}
|
||||
}
|
||||
|
||||
fn void teardown_bench() @finalizer
|
||||
{
|
||||
mem::free(common_1mib_ptr);
|
||||
}
|
||||
|
||||
|
||||
// =======================================================================================
|
||||
module crypto_hash_benchmarks @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
|
||||
fn void md5_16() => md5::hash(common_16);
|
||||
fn void sha1_16() => sha1::hash(common_16);
|
||||
fn void sha2_256_16() => sha256::hash(common_16);
|
||||
fn void sha2_512_16() => sha512::hash(common_16);
|
||||
fn void blake2s_256_16() => blake2::s(256, common_16);
|
||||
fn void blake2b_256_16() => blake2::b(256, common_16);
|
||||
fn void blake3_16() => blake3::hash(common_16);
|
||||
fn void ripemd_160_16() => ripemd::hash{160}(common_16);
|
||||
fn void whirlpool_16() => whirlpool::hash(common_16);
|
||||
fn void streebog_256_16() => streebog::hash_256(common_16);
|
||||
fn void streebog_512_16() => streebog::hash_512(common_16);
|
||||
|
||||
fn void md5_256() => md5::hash(common_256);
|
||||
fn void sha1_256() => sha1::hash(common_256);
|
||||
fn void sha2_256_256() => sha256::hash(common_256);
|
||||
fn void sha2_512_256() => sha512::hash(common_256);
|
||||
fn void blake2s_256_256() => blake2::s(256, common_256);
|
||||
fn void blake2b_256_256() => blake2::b(256, common_256);
|
||||
fn void blake3_256() => blake3::hash(common_256);
|
||||
fn void ripemd_160_256() => ripemd::hash{160}(common_256);
|
||||
fn void whirlpool_256() => whirlpool::hash(common_256);
|
||||
fn void streebog_256_256() => streebog::hash_256(common_256);
|
||||
fn void streebog_512_256() => streebog::hash_512(common_256);
|
||||
|
||||
fn void md5_4kib() => md5::hash(common_4kib);
|
||||
fn void sha1_4kib() => sha1::hash(common_4kib);
|
||||
fn void sha2_256_4kib() => sha256::hash(common_4kib);
|
||||
fn void sha2_512_4kib() => sha512::hash(common_4kib);
|
||||
fn void blake2s_256_4kib() => blake2::s(256, common_4kib);
|
||||
fn void blake2b_256_4kib() => blake2::b(256, common_4kib);
|
||||
fn void blake3_4kib() => blake3::hash(common_4kib);
|
||||
fn void ripemd_160_4kib() => ripemd::hash{160}(common_4kib);
|
||||
fn void whirlpool_4kib() => whirlpool::hash(common_4kib);
|
||||
fn void streebog_256_4kib() => streebog::hash_256(common_4kib);
|
||||
fn void streebog_512_4kib() => streebog::hash_512(common_4kib);
|
||||
|
||||
fn void md5_1mib() => md5::hash(common_1mib);
|
||||
fn void sha1_1mib() => sha1::hash(common_1mib);
|
||||
fn void sha2_256_1mib() => sha256::hash(common_1mib);
|
||||
fn void sha2_512_1mib() => sha512::hash(common_1mib);
|
||||
fn void blake2s_256_1mib() => blake2::s(256, common_1mib);
|
||||
fn void blake2b_256_1mib() => blake2::b(256, common_1mib);
|
||||
fn void blake3_1mib() => blake3::hash(common_1mib);
|
||||
fn void ripemd_160_1mib() => ripemd::hash{160}(common_1mib);
|
||||
fn void whirlpool_1mib() => whirlpool::hash(common_1mib);
|
||||
fn void streebog_256_1mib() => streebog::hash_256(common_1mib);
|
||||
fn void streebog_512_1mib() => streebog::hash_512(common_1mib);
|
||||
94
benchmarks/stdlib/hash/non_crypto_shootout.c3
Normal file
94
benchmarks/stdlib/hash/non_crypto_shootout.c3
Normal 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);
|
||||
76
benchmarks/stdlib/hash/ripemd.c3
Normal file
76
benchmarks/stdlib/hash/ripemd.c3
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module ripemd_bench;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(256);
|
||||
|
||||
input = mem::alloc_array(char, BUFSZ);
|
||||
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
|
||||
input_slice = input[:BUFSZ];
|
||||
}
|
||||
|
||||
fn void teardown_bench() @finalizer
|
||||
{
|
||||
mem::free(input);
|
||||
input = null;
|
||||
}
|
||||
|
||||
char* input;
|
||||
char[] input_slice;
|
||||
const usz BUFSZ = 1024 * 1024;
|
||||
|
||||
module ripemd_bench @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
fn void ripemd_128()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{128}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void ripemd_160()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{160}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void ripemd_256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{256}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void ripemd_320()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{320}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_sha256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = sha256::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_whirlpool()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = whirlpool::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
51
benchmarks/stdlib/hash/streebog.c3
Normal file
51
benchmarks/stdlib/hash/streebog.c3
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module streebog_bench;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(256);
|
||||
}
|
||||
|
||||
const char[] INPUT = { [0..1024*1024] = 0xA5 };
|
||||
const char[*] EXPECTED_256 = x'694676905b7cf099755db1cc186f741f0fd1877aaaa4badcbfb305537f986971';
|
||||
const char[*] EXPECTED_512 = x'fe21d08857ea97e79035d1e5c9ba5130786e8d1875bc74d628349560d94d6bdff0b0dcd2f6347eb8b3f0239b6cca76b5028c0ff45f631fcdf77b1d551dd079f3';
|
||||
|
||||
|
||||
module streebog_bench @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
fn void get_in_the_bog_256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = streebog::hash_256(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void get_in_the_bog_512()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = streebog::hash_512(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_sha256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = sha256::hash(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_whirlpool()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = whirlpool::hash(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
: ${DOCKER:=docker}
|
||||
: ${IMAGE:="c3c-builder"}
|
||||
@@ -41,4 +41,4 @@ exec $DOCKER run -i --rm \
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-$LLVM_VERSION \
|
||||
-DC3_LLVM_VERSION=auto && \
|
||||
cmake --build build && \
|
||||
cp -r build/c3c build/lib bin"
|
||||
cp -r build/c3c build/lib bin"
|
||||
|
||||
@@ -6,7 +6,7 @@ ENV LLVM_DEV_VERSION=20
|
||||
|
||||
ARG CMAKE_VERSION=3.20
|
||||
|
||||
RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ && \
|
||||
RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ libcurl4-openssl-dev && \
|
||||
wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-$CMAKE_VERSION-linux-x86_64.sh && \
|
||||
mkdir -p /opt/cmake && \
|
||||
sh cmake-${CMAKE_VERSION}-linux-x86_64.sh --prefix=/opt/cmake --skip-license && \
|
||||
@@ -46,4 +46,4 @@ RUN groupadd -g 1337 c3c && \
|
||||
USER c3c
|
||||
ENV PATH="/opt/cmake/bin:${PATH}"
|
||||
|
||||
WORKDIR /home/c3c
|
||||
WORKDIR /home/c3c
|
||||
|
||||
18
flake.nix
18
flake.nix
@@ -6,29 +6,27 @@
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem
|
||||
outputs = { self, ... }@inputs: inputs.flake-utils.lib.eachDefaultSystem
|
||||
(system:
|
||||
let pkgs = import inputs.nixpkgs { inherit system; };
|
||||
call = set: pkgs.callPackage ./nix/default.nix (
|
||||
set // {
|
||||
rev = self.rev or "unknown";
|
||||
}
|
||||
);
|
||||
c3cBuild = set: pkgs.callPackage ./nix/default.nix (set // {
|
||||
rev = self.rev or "unknown";
|
||||
});
|
||||
in {
|
||||
packages = {
|
||||
default = self.packages.${system}.c3c;
|
||||
|
||||
c3c = call {};
|
||||
c3c = c3cBuild {};
|
||||
|
||||
c3c-checks = pkgs.callPackage ./nix/default.nix {
|
||||
c3c-checks = c3cBuild {
|
||||
checks = true;
|
||||
};
|
||||
|
||||
c3c-debug = pkgs.callPackage ./nix/default.nix {
|
||||
c3c-debug = c3cBuild {
|
||||
debug = true;
|
||||
};
|
||||
|
||||
c3c-debug-checks = pkgs.callPackage ./nix/default.nix {
|
||||
c3c-debug-checks = c3cBuild {
|
||||
debug = true;
|
||||
checks = true;
|
||||
};
|
||||
|
||||
189
install/install.ps1
Normal file
189
install/install.ps1
Normal file
@@ -0,0 +1,189 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
C3 install script.
|
||||
.DESCRIPTION
|
||||
This script installs C3 on Windows from the command line.
|
||||
.PARAMETER C3Version
|
||||
Specifies the version of C3 to install.
|
||||
Default is 'latest'. Can also be set via environment variable 'C3_VERSION'.
|
||||
.PARAMETER C3Home
|
||||
Specifies C3's installation directory.
|
||||
Default is '$Env:USERPROFILE\.c3'. Can also be set via environment variable 'C3_HOME'.
|
||||
.PARAMETER NoPathUpdate
|
||||
If specified, the script will not modify the PATH environment variable.
|
||||
.PARAMETER C3Repourl
|
||||
Specifies the repository URL of C3.
|
||||
Default is 'https://github.com/c3lang/c3c'. Can also be set via environment variable 'C3_REPOURL'.
|
||||
.LINK
|
||||
https://c3-lang.org/
|
||||
.LINK
|
||||
https://github.com/c3lang/c3c
|
||||
#>
|
||||
|
||||
# Script parameters with defaults
|
||||
param (
|
||||
[string] $C3Version = 'latest',
|
||||
[string] $C3Home = "$Env:USERPROFILE\.c3",
|
||||
[switch] $NoPathUpdate,
|
||||
[string] $C3Repourl = 'https://github.com/c3lang/c3c'
|
||||
)
|
||||
|
||||
# Enable strict mode for better error handling
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
# Function to broadcast environment variable changes to Windows system
|
||||
function Publish-Env {
|
||||
# Add P/Invoke type if it does not exist
|
||||
if (-not ("Win32.NativeMethods" -as [Type])) {
|
||||
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr SendMessageTimeout(
|
||||
IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
|
||||
uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
|
||||
"@
|
||||
}
|
||||
|
||||
# Constants for broadcasting environment changes
|
||||
$HWND_BROADCAST = [IntPtr] 0xffff
|
||||
$WM_SETTINGCHANGE = 0x1a
|
||||
$result = [UIntPtr]::Zero
|
||||
|
||||
# Broadcast the message to all windows
|
||||
[Win32.Nativemethods]::SendMessageTimeout($HWND_BROADCAST,
|
||||
$WM_SETTINGCHANGE,
|
||||
[UIntPtr]::Zero,
|
||||
"Environment",
|
||||
2,
|
||||
5000,
|
||||
[ref] $result
|
||||
) | Out-Null
|
||||
}
|
||||
|
||||
# Function to write or update an environment variable in the registry
|
||||
function Write-Env {
|
||||
param(
|
||||
[String] $name,
|
||||
[String] $val,
|
||||
[Switch] $global
|
||||
)
|
||||
|
||||
# Determine the registry key based on scope (user or system)
|
||||
$RegisterKey = if ($global) {
|
||||
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
|
||||
} else {
|
||||
Get-Item -Path 'HKCU:'
|
||||
}
|
||||
|
||||
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment', $true)
|
||||
|
||||
# If value is null, delete the variable
|
||||
if ($null -eq $val) {
|
||||
$EnvRegisterKey.DeleteValue($name)
|
||||
} else {
|
||||
# Determine the correct registry value type
|
||||
$RegistryValueKind = if ($val.Contains('%')) {
|
||||
[Microsoft.Win32.RegistryValueKind]::ExpandString
|
||||
} elseif ($EnvRegisterKey.GetValue($name)) {
|
||||
$EnvRegisterKey.GetValueKind($name)
|
||||
} else {
|
||||
[Microsoft.Win32.RegistryValueKind]::String
|
||||
}
|
||||
$EnvRegisterKey.SetValue($name, $val, $RegistryValueKind)
|
||||
}
|
||||
|
||||
# Broadcast the change to the system
|
||||
Publish-Env
|
||||
}
|
||||
|
||||
# Function to get an environment variable from the registry
|
||||
function Get-Env {
|
||||
param(
|
||||
[String] $name,
|
||||
[Switch] $global
|
||||
)
|
||||
|
||||
# Determine registry key based on scope
|
||||
$RegisterKey = if ($global) {
|
||||
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
|
||||
} else {
|
||||
Get-Item -Path 'HKCU:'
|
||||
}
|
||||
|
||||
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment')
|
||||
$RegistryValueOption = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
|
||||
|
||||
# Retrieve the value without expanding environment variables
|
||||
$EnvRegisterKey.GetValue($name, $null, $RegistryValueOption)
|
||||
}
|
||||
|
||||
# Override defaults if environment variables exist
|
||||
if ($Env:C3_VERSION) { $C3Version = $Env:C3_VERSION }
|
||||
if ($Env:C3_HOME) { $C3Home = $Env:C3_HOME }
|
||||
if ($Env:C3_NO_PATH_UPDATE) { $NoPathUpdate = $true }
|
||||
if ($Env:C3_REPOURL) { $C3Repourl = $Env:C3_REPOURL -replace '/$', '' }
|
||||
|
||||
# Set binary name
|
||||
$BINARY = "c3-windows"
|
||||
|
||||
# Determine the download URL based on version
|
||||
if ($C3Version -eq 'latest') {
|
||||
$DOWNLOAD_URL = "$C3Repourl/releases/latest/download/$BINARY.zip"
|
||||
} else {
|
||||
# Ensure version starts with 'v'
|
||||
$C3Version = "v" + ($C3Version -replace '^v', '')
|
||||
$DOWNLOAD_URL = "$C3Repourl/releases/download/$C3Version/$BINARY.zip"
|
||||
}
|
||||
|
||||
$BinDir = $C3Home
|
||||
|
||||
Write-Host "This script will automatically download and install C3 ($C3Version) for you."
|
||||
Write-Host "Getting it from this url: $DOWNLOAD_URL"
|
||||
Write-Host "The binary will be installed into '$BinDir'"
|
||||
|
||||
# Create temporary file for download
|
||||
$TEMP_FILE = [System.IO.Path]::GetTempFileName()
|
||||
|
||||
try {
|
||||
# Download the binary
|
||||
Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $TEMP_FILE
|
||||
|
||||
# Remove previous installation if it exists
|
||||
if (Test-Path -Path $BinDir) {
|
||||
Remove-Item -Path $BinDir -Recurse -Force | Out-Null
|
||||
}
|
||||
|
||||
# Rename temp file to .zip
|
||||
$ZIP_FILE = $TEMP_FILE + ".zip"
|
||||
Rename-Item -Path $TEMP_FILE -NewName $ZIP_FILE
|
||||
|
||||
# Extract downloaded zip
|
||||
Expand-Archive -Path $ZIP_FILE -DestinationPath $Env:USERPROFILE -Force
|
||||
|
||||
# Rename extracted folder to target installation directory
|
||||
Rename-Item -Path "$Env:USERPROFILE/c3-windows-Release" -NewName $BinDir
|
||||
} catch {
|
||||
Write-Host "Error: '$DOWNLOAD_URL' is not available or failed to download"
|
||||
exit 1
|
||||
} finally {
|
||||
# Cleanup temporary zip file
|
||||
Remove-Item -Path $ZIP_FILE
|
||||
}
|
||||
|
||||
# Update PATH environment variable if requested
|
||||
if (!$NoPathUpdate) {
|
||||
$PATH = Get-Env 'PATH'
|
||||
if ($PATH -notlike "*$BinDir*") {
|
||||
Write-Output "Adding $BinDir to PATH"
|
||||
|
||||
# Persist PATH for future sessions
|
||||
Write-Env -name 'PATH' -val "$BinDir;$PATH"
|
||||
|
||||
# Update PATH for current session
|
||||
$Env:PATH = "$BinDir;$PATH"
|
||||
Write-Output "You may need to restart your shell"
|
||||
} else {
|
||||
Write-Output "$BinDir is already in PATH"
|
||||
}
|
||||
} else {
|
||||
Write-Output "You may need to update your PATH manually to use c3"
|
||||
}
|
||||
137
install/install.sh
Normal file
137
install/install.sh
Normal file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail # Exit on error, unset variables, and fail pipelines on any error
|
||||
|
||||
__wrap__() {
|
||||
# Version of C3 to install (default: latest)
|
||||
VERSION="${C3_VERSION:-latest}"
|
||||
# Installation directory (default: ~/.c3)
|
||||
C3_HOME="${C3_HOME:-$HOME/.c3}"
|
||||
# Expand '~' if present
|
||||
C3_HOME="${C3_HOME/#\~/$HOME}"
|
||||
BIN_DIR="$C3_HOME"
|
||||
# C3 compiler repository URL
|
||||
REPO="c3lang/c3c"
|
||||
REPOURL="${C3_REPOURL:-https://github.com/$REPO}"
|
||||
|
||||
detect_platform() {
|
||||
# Detects the operating system
|
||||
local os_type
|
||||
os_type="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
case "$os_type" in
|
||||
darwin) # macOS
|
||||
echo "macos"
|
||||
;;
|
||||
msys*|mingw*|cygwin*) # Windows (Git Bash / MSYS / Cygwin)
|
||||
IS_MSYS=true
|
||||
echo "windows"
|
||||
;;
|
||||
*)
|
||||
echo $os_type
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Determine platform string
|
||||
PLATFORM="$(detect_platform)"
|
||||
|
||||
# File extension for the archive (ZIP for Windows, TAR.GZ for others)
|
||||
EXT=".tar.gz"
|
||||
BINARY="c3-${PLATFORM}"
|
||||
if [[ "${IS_MSYS:-false}" == true ]]; then
|
||||
EXT=".zip"
|
||||
fi
|
||||
|
||||
# Determine the download URL (latest release or specific version)
|
||||
if [[ "$VERSION" == "latest" ]]; then
|
||||
URL="${REPOURL%/}/releases/latest/download/${BINARY}${EXT}"
|
||||
else
|
||||
URL="${REPOURL%/}/releases/download/v${VERSION#v}/${BINARY}${EXT}"
|
||||
fi
|
||||
|
||||
# Temporary file for the downloaded archive
|
||||
TEMP_FILE="$(mktemp "${TMPDIR:-/tmp}/.C3_install.XXXXXXXX")"
|
||||
trap 'rm -f "$TEMP_FILE"' EXIT # Ensure temp file is deleted on exit
|
||||
|
||||
download_file() {
|
||||
# Download the archive using curl or wget
|
||||
# Check that the curl version is not 8.8.0, which is broken for --write-out
|
||||
# https://github.com/curl/curl/issues/13845
|
||||
if command -v curl >/dev/null && [[ "$(curl --version | awk 'NR==1{print $2}')" != "8.8.0" ]]; then
|
||||
curl -SL "$URL" -o "$TEMP_FILE"
|
||||
elif command -v wget >/dev/null; then
|
||||
wget -O "$TEMP_FILE" "$URL"
|
||||
else
|
||||
echo "Error: curl or wget is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Downloading C3 ($VERSION) from $URL..."
|
||||
download_file
|
||||
|
||||
# Remove existing installation and extract the new one
|
||||
rm -rf "$BIN_DIR"
|
||||
if [[ "$EXT" == ".zip" ]]; then
|
||||
unzip "$TEMP_FILE" -d "$HOME"
|
||||
else
|
||||
tar -xzf "$TEMP_FILE" -C "$HOME"
|
||||
fi
|
||||
|
||||
# Move extracted folder to installation directory
|
||||
mv "$HOME/c3" "$BIN_DIR"
|
||||
chmod +x "$BIN_DIR/c3c" # Ensure compiler binary is executable
|
||||
echo "✅ Installation completed in $BIN_DIR"
|
||||
|
||||
# Update PATH unless suppressed by environment variable
|
||||
if [ -n "${C3_NO_PATH_UPDATE:-}" ]; then
|
||||
echo "No path update because C3_NO_PATH_UPDATE is set"
|
||||
else
|
||||
update_shell() {
|
||||
FILE="$1"
|
||||
LINE="$2"
|
||||
|
||||
# Create shell config file if missing
|
||||
if [ ! -f "$FILE" ]; then
|
||||
touch "$FILE"
|
||||
fi
|
||||
|
||||
# Add the PATH line if not already present
|
||||
if ! grep -Fxq "$LINE" "$FILE"; then
|
||||
echo "Updating '${FILE}'"
|
||||
echo "$LINE" >>"$FILE"
|
||||
echo "Please restart or source your shell."
|
||||
fi
|
||||
}
|
||||
|
||||
# Detect the current shell and add C3 to its PATH
|
||||
case "$(basename "${SHELL-}")" in
|
||||
bash)
|
||||
# Default to bashrc as that is used in non login shells instead of the profile.
|
||||
LINE="export PATH=\"${BIN_DIR}:\$PATH\""
|
||||
update_shell ~/.bashrc "$LINE"
|
||||
;;
|
||||
fish)
|
||||
LINE="fish_add_path ${BIN_DIR}"
|
||||
update_shell ~/.config/fish/config.fish "$LINE"
|
||||
;;
|
||||
zsh)
|
||||
LINE="export PATH=\"${BIN_DIR}:\$PATH\""
|
||||
update_shell ~/.zshrc "$LINE"
|
||||
;;
|
||||
tcsh)
|
||||
LINE="set path = ( ${BIN_DIR} \$path )"
|
||||
update_shell ~/.tcshrc "$LINE"
|
||||
;;
|
||||
'')
|
||||
echo "warn: Could not detect shell type." >&2
|
||||
echo " Please permanently add '${BIN_DIR}' to your \$PATH to enable the 'c3c' command." >&2
|
||||
;;
|
||||
*)
|
||||
echo "warn: Could not update shell $(basename "$SHELL")" >&2
|
||||
echo " Please permanently add '${BIN_DIR}' to your \$PATH to enable the 'c3c' command." >&2
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
__wrap__
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2023-2025 Eduardo José Gómez Hernández. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::atomic::types{Type};
|
||||
module std::atomic::types <Type>;
|
||||
|
||||
struct Atomic
|
||||
{
|
||||
@@ -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,10 +171,12 @@ 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:
|
||||
$case CONST_ENUM:
|
||||
return is_native_atomic_type($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
@@ -231,13 +233,13 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require $defined(*ptr * y) : "* must be defined between the values."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
@@ -271,13 +273,13 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require $defined(*ptr * y) : "/ must be defined between the values."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
@@ -363,13 +365,13 @@ macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) : "The value for shift right must be an integer"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
@@ -405,13 +407,13 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) : "The value for shift left must be an integer"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
@@ -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
|
||||
{
|
||||
@@ -473,7 +475,7 @@ macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -58,7 +58,7 @@ fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired,
|
||||
nextcase;
|
||||
$endif
|
||||
default:
|
||||
unreachable("Unsuported size (%d) for atomic_compare_exchange", size);
|
||||
unreachable("Unsupported size (%d) for atomic_compare_exchange", size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::anylist;
|
||||
import std::io,std::math;
|
||||
import std::collections::interfacelist;
|
||||
|
||||
alias AnyPredicate = fn bool(any value);
|
||||
alias AnyTest = fn bool(any type, any context);
|
||||
alias AnyPredicate = InterfacePredicate {any};
|
||||
alias AnyTest = InterfaceTest {any};
|
||||
|
||||
<*
|
||||
The AnyList contains a heterogenous set of types. Anything placed in the
|
||||
@@ -18,282 +18,7 @@ alias AnyTest = fn bool(any type, any context);
|
||||
If we're not doing pop, then things are easier, since we can just hand over
|
||||
the existing any.
|
||||
*>
|
||||
struct AnyList (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
any* entries;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Initialize the list. If not initialized then it will use the temp allocator
|
||||
when something is pushed to it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param initial_capacity : "The initial capacity to reserve, defaults to 16"
|
||||
*>
|
||||
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
if (initial_capacity > 0)
|
||||
{
|
||||
initial_capacity = math::next_power_of_2(initial_capacity);
|
||||
self.entries = allocator::alloc_array(allocator, any, initial_capacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.entries = null;
|
||||
}
|
||||
self.capacity = initial_capacity;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity : "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn bool AnyList.is_initialized(&self) @inline => self.allocator != null;
|
||||
|
||||
<*
|
||||
Push an element on the list by cloning it.
|
||||
*>
|
||||
macro void AnyList.push(&self, element)
|
||||
{
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
self._append(allocator::clone(self.allocator, element));
|
||||
}
|
||||
|
||||
<*
|
||||
Free a retained element removed using *_retained.
|
||||
*>
|
||||
fn void AnyList.free_element(&self, any element) @inline
|
||||
{
|
||||
allocator::free(self.allocator, element.ptr);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@param $Type : "The type we assume the value has"
|
||||
@return "The last value as the type given"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.pop(&self, $Type)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return *anycast(self.entries[--self.size], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the last value, pop it and return the copy of it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use for copying"
|
||||
@return "A copy of the last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.copy_pop(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return allocator::clone_any(allocator, self.entries[--self.size]);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Copy the last value, pop it and return the copy of it.
|
||||
|
||||
@return "A temp copy of the last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.tcopy_pop(&self) => self.copy_pop(tmem);
|
||||
|
||||
|
||||
<*
|
||||
Pop the last value. It must later be released using `list.free_element()`.
|
||||
|
||||
@return "The last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.pop_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Remove all elements in the list.
|
||||
*>
|
||||
fn void AnyList.clear(&self)
|
||||
{
|
||||
for (usz i = 0; i < self.size; i++)
|
||||
{
|
||||
self.free_element(self.entries[i]);
|
||||
}
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@param $Type : "The type we assume the value has"
|
||||
@return "The first value as the type given"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.pop_first(&self, $Type)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return *anycast(self.entries[0], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the first value. It must later be released using `list.free_element()`.
|
||||
|
||||
@return "The first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.pop_first_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Copy the first value, pop it and return the copy of it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use for copying"
|
||||
@return "A copy of the first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.copy_pop_first(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
defer self.remove_at(0);
|
||||
return allocator::clone_any(allocator, self.entries[0]);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the first value, pop it and return the temp copy of it.
|
||||
|
||||
@return "A temp copy of the first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.tcopy_pop_first(&self) => self.copy_pop_first(tmem);
|
||||
|
||||
<*
|
||||
Remove the element at the particular index.
|
||||
|
||||
@param index : "The index of the element to remove"
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void AnyList.remove_at(&self, usz index)
|
||||
{
|
||||
if (!--self.size || index == self.size) return;
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Add all the elements in another AnyList.
|
||||
|
||||
@param [&in] other_list : "The list to add"
|
||||
*>
|
||||
fn void AnyList.add_all(&self, AnyList* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
self.reserve(other_list.size);
|
||||
foreach (value : other_list)
|
||||
{
|
||||
self.entries[self.size++] = allocator::clone_any(self.allocator, value);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the order of the elements in the list.
|
||||
*>
|
||||
fn void AnyList.reverse(&self)
|
||||
{
|
||||
if (self.size < 2) return;
|
||||
usz half = self.size / 2U;
|
||||
usz end = self.size - 1;
|
||||
for (usz i = 0; i < half; i++)
|
||||
{
|
||||
self.swap(i, end - i);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Return a view of the data as a slice.
|
||||
|
||||
@return "The slice view"
|
||||
*>
|
||||
fn any[] AnyList.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Push an element to the front of the list.
|
||||
|
||||
@param value : "The value to push to the list"
|
||||
*>
|
||||
macro void AnyList.push_front(&self, value)
|
||||
{
|
||||
self.insert_at(0, value);
|
||||
}
|
||||
|
||||
<*
|
||||
Insert an element at a particular index.
|
||||
|
||||
@param index : "the index where the element should be inserted"
|
||||
@param type : "the value to insert"
|
||||
@require index <= self.size : "The index is out of bounds"
|
||||
*>
|
||||
macro void AnyList.insert_at(&self, usz index, type)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(type);
|
||||
return;
|
||||
}
|
||||
any value = allocator::copy(self.allocator, type);
|
||||
self._insert_at(self, index, value);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove the last element in the list. The list may not be empty.
|
||||
|
||||
@require self.size > 0 : "The list was already empty"
|
||||
*>
|
||||
fn void AnyList.remove_last(&self)
|
||||
{
|
||||
self.free_element(self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove the first element in the list, the list may not be empty.
|
||||
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn void AnyList.remove_first(&self)
|
||||
{
|
||||
self.remove_at(0);
|
||||
}
|
||||
typedef AnyList = inline InterfaceList {any};
|
||||
|
||||
<*
|
||||
Return the first element by value, assuming it is the given type.
|
||||
@@ -313,10 +38,7 @@ macro AnyList.first(&self, $Type)
|
||||
@return "The first element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.first_any(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
|
||||
}
|
||||
fn any? AnyList.first_any(&self) @inline => InterfaceList {any}.first(self);
|
||||
|
||||
<*
|
||||
Return the last element by value, assuming it is the given type.
|
||||
@@ -336,29 +58,36 @@ macro AnyList.last(&self, $Type)
|
||||
@return "The last element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.last_any(&self) @inline
|
||||
fn any? AnyList.last_any(&self) @inline => InterfaceList {any}.last(self);
|
||||
|
||||
<*
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@param $Type : "The type we assume the value has"
|
||||
@return "The last value as the type given"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.pop(&self, $Type)
|
||||
{
|
||||
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return *anycast(self.entries[--self.size], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Return whether the list is empty.
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@return "True if the list is empty"
|
||||
@param $Type : "The type we assume the value has"
|
||||
@return "The first value as the type given"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
fn bool AnyList.is_empty(&self) @inline
|
||||
macro AnyList.pop_first(&self, $Type)
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the length of the list.
|
||||
|
||||
@return "The number of elements in the list"
|
||||
*>
|
||||
fn usz AnyList.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.remove_at(0);
|
||||
return *anycast(self.entries[0], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -383,222 +112,11 @@ macro AnyList.get(&self, usz index, $Type)
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
@require index < self.size : "Index out of range"
|
||||
*>
|
||||
fn any AnyList.get_any(&self, usz index) @inline @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
fn any AnyList.get_any(&self, usz index) @inline @operator([]) => InterfaceList {any}.get(self, index);
|
||||
|
||||
<*
|
||||
Completely free and clear a list.
|
||||
Return the length of the list.
|
||||
|
||||
@return "The number of elements in the list"
|
||||
*>
|
||||
fn void AnyList.free(&self)
|
||||
{
|
||||
if (!self.allocator) return;
|
||||
self.clear();
|
||||
allocator::free(self.allocator, self.entries);
|
||||
self.capacity = 0;
|
||||
self.entries = null;
|
||||
}
|
||||
|
||||
<*
|
||||
Swap two elements in a list.
|
||||
|
||||
@param i : "Index of one of the elements"
|
||||
@param j : "Index of the other element"
|
||||
@require i < self.size : "The first index is out of range"
|
||||
@require j < self.size : "The second index is out of range"
|
||||
*>
|
||||
fn void AnyList.swap(&self, usz i, usz j)
|
||||
{
|
||||
any temp = self.entries[i];
|
||||
self.entries[i] = self.entries[j];
|
||||
self.entries[j] = temp;
|
||||
}
|
||||
|
||||
<*
|
||||
Print the list to a formatter.
|
||||
*>
|
||||
fn usz? AnyList.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
case 0:
|
||||
return formatter.print("[]")!;
|
||||
case 1:
|
||||
return formatter.printf("[%s]", self.entries[0])!;
|
||||
default:
|
||||
usz n = formatter.print("[")!;
|
||||
foreach (i, element : self.entries[:self.size])
|
||||
{
|
||||
if (i != 0) formatter.print(", ")!;
|
||||
n += formatter.printf("%s", element)!;
|
||||
}
|
||||
n += formatter.print("]")!;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Remove any elements matching the predicate.
|
||||
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.remove_if(&self, AnyPredicate filter)
|
||||
{
|
||||
return self._remove_if(filter, false);
|
||||
}
|
||||
|
||||
<*
|
||||
Retain the elements matching the predicate.
|
||||
|
||||
@param selection : "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.retain_if(&self, AnyPredicate selection)
|
||||
{
|
||||
return self._remove_if(selection, true);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove any elements matching the predicate.
|
||||
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@param context : "The context to the function"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
|
||||
{
|
||||
return self._remove_using_test(filter, false, context);
|
||||
}
|
||||
|
||||
<*
|
||||
Retain any elements matching the predicate.
|
||||
|
||||
@param selection : "The function to determine if it should be retained or not"
|
||||
@param context : "The context to the function"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.retain_using_test(&self, AnyTest selection, any context)
|
||||
{
|
||||
return self._remove_using_test(selection, true, context);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Reserve memory so that at least the `min_capacity` exists.
|
||||
|
||||
@param min_capacity : "The min capacity to hold"
|
||||
*>
|
||||
fn void AnyList.reserve(&self, usz min_capacity)
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
self.entries = allocator::realloc(self.allocator, self.entries, any.sizeof * min_capacity);
|
||||
self.capacity = min_capacity;
|
||||
}
|
||||
|
||||
<*
|
||||
Set the element at any index.
|
||||
|
||||
@param index : "The index where to set the value."
|
||||
@param value : "The value to set"
|
||||
@require index <= self.size : "Index out of range"
|
||||
*>
|
||||
macro void AnyList.set(&self, usz index, value)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(value);
|
||||
return;
|
||||
}
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index] = allocator::copy(self.allocator, value);
|
||||
}
|
||||
|
||||
// -- private
|
||||
|
||||
fn void AnyList.ensure_capacity(&self, usz added = 1) @inline @private
|
||||
{
|
||||
usz new_size = self.size + added;
|
||||
if (self.capacity >= new_size) return;
|
||||
|
||||
assert(new_size < usz.max / 2U);
|
||||
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
|
||||
while (new_capacity < new_size) new_capacity *= 2U;
|
||||
self.reserve(new_capacity);
|
||||
}
|
||||
|
||||
fn void AnyList._append(&self, any element) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void AnyList._insert_at(&self, usz index, any value) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
for (usz i = self.size; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.size++;
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @local
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
macro usz AnyList._remove_if(&self, AnyPredicate filter, bool $invert) @local
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(&self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && filter(&self.entries[i - 1])) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(&self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(&self.entries[i - 1])) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
fn usz AnyList.len(&self) @operator(len) @inline => InterfaceList {any}.len(self);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<*
|
||||
@require SIZE > 0 : "The size of the bitset in bits must be at least 1"
|
||||
*>
|
||||
module std::collections::bitset {SIZE};
|
||||
module std::collections::bitset <SIZE>;
|
||||
|
||||
const BITS = uint.sizeof * 8;
|
||||
const SZ = (SIZE + BITS - 1) / BITS;
|
||||
@@ -136,6 +136,7 @@ fn void BitSet.unset(&self, usz i)
|
||||
@param i : "The index of the bit"
|
||||
|
||||
@require i < SIZE : "Index was out of range"
|
||||
@pure
|
||||
*>
|
||||
fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
{
|
||||
@@ -144,6 +145,11 @@ fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
return self.data[q] & (1 << r) != 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the number of bits.
|
||||
|
||||
@pure
|
||||
*>
|
||||
fn usz BitSet.len(&self) @operator(len) @inline
|
||||
{
|
||||
return SZ * BITS;
|
||||
@@ -166,7 +172,7 @@ fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
|
||||
<*
|
||||
@require Type.kindof == UNSIGNED_INT
|
||||
*>
|
||||
module std::collections::growablebitset{Type};
|
||||
module std::collections::growablebitset <Type>;
|
||||
import std::collections::list;
|
||||
|
||||
const BITS = Type.sizeof * 8;
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
<*
|
||||
@require MAX_SIZE >= 1 : `The size must be at least 1 element big.`
|
||||
*>
|
||||
module std::collections::elastic_array {Type, MAX_SIZE};
|
||||
module std::collections::elastic_array <Type, MAX_SIZE>;
|
||||
import std::io, std::math, std::collections::list_common;
|
||||
|
||||
alias ElementPredicate = fn bool(Type *type);
|
||||
alias ElementTest = fn bool(Type *type, any context);
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
|
||||
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct ElasticArray (Printable)
|
||||
{
|
||||
@@ -46,7 +46,7 @@ fn String ElasticArray.to_tstring(&self)
|
||||
|
||||
fn void? ElasticArray.push_try(&self, Type element) @inline
|
||||
{
|
||||
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
|
||||
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY~;
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ fn void ElasticArray.push(&self, Type element) @inline
|
||||
|
||||
fn Type? ElasticArray.pop(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ fn void ElasticArray.clear(&self)
|
||||
*>
|
||||
fn Type? ElasticArray.pop_first(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
@@ -121,7 +121,24 @@ fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
|
||||
|
||||
@param [in] array
|
||||
*>
|
||||
fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
|
||||
fn usz ElasticArray.add_array_to_limit(&self, Type[] array) @deprecated("Use push_all_to_limit")
|
||||
{
|
||||
if (!array.len) return 0;
|
||||
foreach (i, &value : array)
|
||||
{
|
||||
if (self.size == MAX_SIZE) return array.len - i;
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Add as many values from this array as possible, returning the
|
||||
number of elements that didn't fit.
|
||||
|
||||
@param [in] array
|
||||
*>
|
||||
fn usz ElasticArray.push_all_to_limit(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return 0;
|
||||
foreach (i, &value : array)
|
||||
@@ -139,7 +156,7 @@ fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
|
||||
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void ElasticArray.add_array(&self, Type[] array)
|
||||
fn void ElasticArray.add_array(&self, Type[] array) @deprecated("Use push_all")
|
||||
{
|
||||
if (!array.len) return;
|
||||
foreach (&value : array)
|
||||
@@ -148,6 +165,21 @@ fn void ElasticArray.add_array(&self, Type[] array)
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Add the values of an array to this list.
|
||||
|
||||
@param [in] array
|
||||
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void ElasticArray.push_all(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return;
|
||||
foreach (&value : array)
|
||||
{
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@@ -209,7 +241,7 @@ fn void? ElasticArray.push_front_try(&self, Type type) @inline
|
||||
*>
|
||||
fn void? ElasticArray.insert_at_try(&self, usz index, Type value)
|
||||
{
|
||||
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
|
||||
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY~;
|
||||
self.insert_at(index, value);
|
||||
}
|
||||
|
||||
@@ -237,25 +269,25 @@ fn void ElasticArray.set_at(&self, usz index, Type type)
|
||||
|
||||
fn void? ElasticArray.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
self.size--;
|
||||
}
|
||||
|
||||
fn void? ElasticArray.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type? ElasticArray.first(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
fn Type? ElasticArray.last(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
@@ -336,7 +368,7 @@ fn usz? ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -345,7 +377,7 @@ fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -426,4 +458,4 @@ fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER)
|
||||
fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
return list_common::list_compact(self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<*
|
||||
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap"
|
||||
@require Enum.values.len > 0 : "Only non-empty enums may be used with enummap"
|
||||
*>
|
||||
module std::collections::enummap{Enum, ValueType};
|
||||
module std::collections::enummap <Enum, ValueType>;
|
||||
import std::io;
|
||||
|
||||
struct EnumMap (Printable)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<*
|
||||
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enumset"
|
||||
*>
|
||||
module std::collections::enumset{Enum};
|
||||
module std::collections::enumset <Enum>;
|
||||
import std::io;
|
||||
|
||||
const ENUM_COUNT @private = Enum.values.len;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<*
|
||||
@require $defined((Key){}.hash()) : `No .hash function found on the key`
|
||||
*>
|
||||
module std::collections::map{Key, Value};
|
||||
module std::collections::map <Key, Value>;
|
||||
import std::math;
|
||||
import std::io @norecurse;
|
||||
|
||||
@@ -30,8 +30,10 @@ struct HashMap (Printable)
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator allocator;
|
||||
uint count; // Number of elements
|
||||
uint threshold; // Resize limit
|
||||
<* Last inserted LinkedEntry *>
|
||||
uint count;
|
||||
<* Resize limit *>
|
||||
uint threshold;
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
@@ -90,7 +92,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);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -173,29 +175,48 @@ fn usz HashMap.len(&map) @inline
|
||||
|
||||
fn Value*? HashMap.get_ref(&map, Key key)
|
||||
{
|
||||
if (!map.count) return NOT_FOUND?;
|
||||
if (!map.count) return NOT_FOUND~;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return &e.value;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn Value* HashMap.get_or_create_ref(&map, Key key) @operator(&[])
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
if (map.count)
|
||||
{
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return &e.value;
|
||||
}
|
||||
}
|
||||
map.set(key, {});
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return &e.value;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
fn Entry*? HashMap.get_entry(&map, Key key)
|
||||
{
|
||||
if (!map.count) return NOT_FOUND?;
|
||||
if (!map.count) return NOT_FOUND~;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value or update and
|
||||
@require $assignable(#expr, Value)
|
||||
Get the value or set it to the value
|
||||
|
||||
@require $defined(Value val = #expr)
|
||||
*>
|
||||
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
{
|
||||
@@ -229,15 +250,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)
|
||||
@@ -254,7 +275,7 @@ fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
|
||||
|
||||
fn void? HashMap.remove(&map, Key key) @maydiscard
|
||||
{
|
||||
if (!map.remove_entry_for_key(key)) return NOT_FOUND?;
|
||||
if (!map.remove_entry_for_key(key)) return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn void HashMap.clear(&map)
|
||||
@@ -332,10 +353,7 @@ macro HashMap.@each_entry(map; @body(entry))
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] HashMap.tvalues(&map)
|
||||
{
|
||||
return map.values(tmem) @inline;
|
||||
}
|
||||
fn Value[] HashMap.tvalues(&self) => self.values(tmem) @inline;
|
||||
|
||||
fn Value[] HashMap.values(&self, Allocator allocator)
|
||||
{
|
||||
@@ -421,7 +439,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(" }");
|
||||
}
|
||||
|
||||
@@ -589,4 +607,4 @@ macro uint index_for(uint hash, uint capacity) @private
|
||||
return hash & (capacity - 1);
|
||||
}
|
||||
|
||||
int dummy @local;
|
||||
int dummy @local;
|
||||
|
||||
654
lib/std/collections/hashset.c3
Normal file
654
lib/std/collections/hashset.c3
Normal file
@@ -0,0 +1,654 @@
|
||||
<*
|
||||
@require $defined((Value){}.hash()) : `No .hash function found on the value`
|
||||
*>
|
||||
module std::collections::set <Value>;
|
||||
import std::math;
|
||||
import std::io @norecurse;
|
||||
|
||||
const uint DEFAULT_INITIAL_CAPACITY = 16;
|
||||
const uint MAXIMUM_CAPACITY = 1u << 31;
|
||||
const float DEFAULT_LOAD_FACTOR = 0.75;
|
||||
|
||||
const Allocator SET_HEAP_ALLOCATOR = (Allocator)&dummy;
|
||||
|
||||
<* Copy the ONHEAP allocator to initialize to a set that is heap allocated *>
|
||||
const HashSet ONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
|
||||
|
||||
struct Entry
|
||||
{
|
||||
uint hash;
|
||||
Value value;
|
||||
Entry* next;
|
||||
}
|
||||
|
||||
struct HashSet (Printable)
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator allocator;
|
||||
<* Number of elements *>
|
||||
usz count;
|
||||
<* Resize limit *>
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
fn usz HashSet.len(&self) @operator(len) => self.count;
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
self.allocator = allocator;
|
||||
self.threshold = (usz) (capacity * load_factor);
|
||||
self.load_factor = load_factor;
|
||||
self.table = allocator::new_array(allocator, Entry*, capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init(tmem, capacity, load_factor) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashSet* HashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
self.add($vaarg[$i]);
|
||||
$endfor
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashSet* HashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the HashSet"
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
foreach (v : values) self.add(v);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the HashSet entries"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_from_values(tmem, values, capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
Has this hash set been initialized yet?
|
||||
|
||||
@param [&in] set : "The hash set we are testing"
|
||||
@return "Returns true if it has been initialized, false otherwise"
|
||||
*>
|
||||
fn bool HashSet.is_initialized(&set)
|
||||
{
|
||||
return set.allocator && set.allocator.ptr != &dummy;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn HashSet* HashSet.init_from_set(&self, Allocator allocator, HashSet* other_set)
|
||||
{
|
||||
self.init(allocator, other_set.table.len, other_set.load_factor);
|
||||
self.put_all_for_create(other_set);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !set.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn HashSet* HashSet.tinit_from_set(&set, HashSet* other_set)
|
||||
{
|
||||
return set.init_from_set(tmem, other_set) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set is empty
|
||||
|
||||
@return "true if it is empty"
|
||||
@pure
|
||||
*>
|
||||
fn bool HashSet.is_empty(&set) @inline
|
||||
{
|
||||
return !set.count;
|
||||
}
|
||||
|
||||
<*
|
||||
Add all elements in the slice to the set.
|
||||
|
||||
@param [in] list
|
||||
@return "The number of new elements added"
|
||||
@ensure total <= list.len
|
||||
*>
|
||||
fn usz HashSet.add_all(&set, Value[] list)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : list)
|
||||
{
|
||||
if (set.add(v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other
|
||||
@return "The number of new elements added"
|
||||
@ensure return <= other.count
|
||||
*>
|
||||
fn usz HashSet.add_all_from(&set, HashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (set.add(value)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to add"
|
||||
@return "true if the value didn't exist in the set"
|
||||
*>
|
||||
fn bool HashSet.add(&set, Value value)
|
||||
{
|
||||
// If the set isn't initialized, use the defaults to initialize it.
|
||||
switch (set.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
set.init(mem);
|
||||
case null:
|
||||
set.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint hash = rehash(value.hash());
|
||||
uint index = index_for(hash, set.table.len);
|
||||
for (Entry *e = set.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return false;
|
||||
}
|
||||
set.add_entry(hash, value, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Iterate over all the values in the set
|
||||
*>
|
||||
macro HashSet.@each(set; @body(value))
|
||||
{
|
||||
if (!set.count) return;
|
||||
foreach (Entry* entry : set.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@body(entry.value);
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set contains the given value.
|
||||
|
||||
@param value : "The value to check"
|
||||
@return "true if it exists in the set"
|
||||
*>
|
||||
fn bool HashSet.contains(&set, Value value)
|
||||
{
|
||||
if (!set.count) return false;
|
||||
uint hash = rehash(value.hash());
|
||||
for (Entry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
Remove a single value from the set.
|
||||
|
||||
@param value : "The value to remove"
|
||||
@return? NOT_FOUND : "If the entry is not found"
|
||||
*>
|
||||
fn void? HashSet.remove(&set, Value value) @maydiscard
|
||||
{
|
||||
if (!set.remove_entry_for_value(value)) return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz HashSet.remove_all(&set, Value[] values)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : values)
|
||||
{
|
||||
if (set.remove_entry_for_value(v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other : "Other set"
|
||||
*>
|
||||
fn usz HashSet.remove_all_from(&set, HashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value val)
|
||||
{
|
||||
if (set.remove_entry_for_value(val)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
Free all memory allocated by the hash set.
|
||||
*>
|
||||
fn void HashSet.free(&set)
|
||||
{
|
||||
if (!set.is_initialized()) return;
|
||||
set.clear();
|
||||
set.free_internal(set.table.ptr);
|
||||
*set = {};
|
||||
}
|
||||
|
||||
<*
|
||||
Clear all elements from the set while keeping the underlying storage
|
||||
|
||||
@ensure set.count == 0
|
||||
*>
|
||||
fn void HashSet.clear(&set)
|
||||
{
|
||||
if (!set.count) return;
|
||||
|
||||
foreach (Entry** &entry_ref : set.table)
|
||||
{
|
||||
Entry* entry = *entry_ref;
|
||||
if (!entry) continue;
|
||||
|
||||
Entry *next = entry.next;
|
||||
while (next)
|
||||
{
|
||||
Entry *to_delete = next;
|
||||
next = next.next;
|
||||
set.free_entry(to_delete);
|
||||
}
|
||||
|
||||
set.free_entry(entry);
|
||||
*entry_ref = null;
|
||||
}
|
||||
set.count = 0;
|
||||
}
|
||||
|
||||
fn void HashSet.reserve(&set, usz capacity)
|
||||
{
|
||||
if (capacity > set.threshold)
|
||||
{
|
||||
set.resize(math::next_power_of_2(capacity));
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] HashSet.tvalues(&self) => self.values(tmem) @inline;
|
||||
|
||||
fn Value[] HashSet.values(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, self.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : self.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
list[index++] = entry.value;
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// --- Set Operations ---
|
||||
|
||||
<*
|
||||
Returns the union of two sets (A | B)
|
||||
|
||||
@param [&in] other : "The other set to union with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the union of both sets"
|
||||
*>
|
||||
fn HashSet HashSet.set_union(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
usz new_capacity = math::next_power_of_2(self.count + other.count);
|
||||
HashSet result;
|
||||
result.init(allocator, new_capacity, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
result.add_all_from(other);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tset_union(&self, HashSet* other) => self.set_union(tmem, other);
|
||||
|
||||
<*
|
||||
Returns the intersection of the two sets (A & B)
|
||||
|
||||
@param [&in] other : "The other set to intersect with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the intersection of both sets"
|
||||
*>
|
||||
fn HashSet HashSet.intersection(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
HashSet result;
|
||||
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
|
||||
|
||||
// Iterate through the smaller set for efficiency
|
||||
HashSet* smaller = self.count <= other.count ? self : other;
|
||||
HashSet* larger = self.count > other.count ? self : other;
|
||||
|
||||
smaller.@each(;Value value)
|
||||
{
|
||||
if (larger.contains(value)) result.add(value);
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tintersection(&self, HashSet* other) => self.intersection(tmem, other);
|
||||
|
||||
<*
|
||||
Return this set - other, so (A & ~B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set but not in the other"
|
||||
*>
|
||||
fn HashSet HashSet.difference(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
HashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
self.@each(;Value value)
|
||||
{
|
||||
if (!other.contains(value))
|
||||
{
|
||||
result.add(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tdifference(&self, HashSet* other) => self.difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Return (A ^ B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set or the other, but not both"
|
||||
*>
|
||||
fn HashSet HashSet.symmetric_difference(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
HashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (!result.add(value))
|
||||
{
|
||||
result.remove(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tsymmetric_difference(&self, HashSet* other) => self.symmetric_difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Check if this hash set is a subset of another set.
|
||||
|
||||
@param [&in] other : "The other set to check against"
|
||||
@return "True if all elements of this set are in the other set"
|
||||
*>
|
||||
fn bool HashSet.is_subset(&self, HashSet* other)
|
||||
{
|
||||
if (self.count == 0) return true;
|
||||
if (self.count > other.count) return false;
|
||||
|
||||
self.@each(;Value value)
|
||||
{
|
||||
if (!other.contains(value)) return false;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void HashSet.add_entry(&set, uint hash, Value value, uint bucket_index) @private
|
||||
{
|
||||
Entry* entry = allocator::new(set.allocator, Entry, { .hash = hash, .value = value, .next = set.table[bucket_index] });
|
||||
set.table[bucket_index] = entry;
|
||||
if (set.count++ >= set.threshold)
|
||||
{
|
||||
set.resize(set.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashSet.resize(&self, usz new_capacity) @private
|
||||
{
|
||||
Entry*[] old_table = self.table;
|
||||
usz old_capacity = old_table.len;
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
self.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
Entry*[] new_table = allocator::new_array(self.allocator, Entry*, new_capacity);
|
||||
self.transfer(new_table);
|
||||
self.table = new_table;
|
||||
self.free_internal(old_table.ptr);
|
||||
self.threshold = (uint)(new_capacity * self.load_factor);
|
||||
}
|
||||
|
||||
fn usz? HashSet.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len;
|
||||
len += f.print("{ ")!;
|
||||
self.@each(; Value value)
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s", value)!;
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
fn void HashSet.transfer(&self, Entry*[] new_table) @private
|
||||
{
|
||||
Entry*[] src = self.table;
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, Entry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
Entry* next = e.next;
|
||||
uint i = index_for(e.hash, new_capacity);
|
||||
e.next = new_table[i];
|
||||
new_table[i] = e;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashSet.put_all_for_create(&set, HashSet* other_set) @private
|
||||
{
|
||||
if (!other_set.count) return;
|
||||
foreach (Entry *e : other_set.table)
|
||||
{
|
||||
while (e)
|
||||
{
|
||||
set.put_for_create(e.value);
|
||||
e = e.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashSet.put_for_create(&set, Value value) @private
|
||||
{
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
for (Entry *e = set.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
// Value already exists, no need to do anything
|
||||
return;
|
||||
}
|
||||
}
|
||||
set.create_entry(hash, value, i);
|
||||
}
|
||||
|
||||
fn void HashSet.free_internal(&self, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(self.allocator, ptr);
|
||||
}
|
||||
|
||||
fn void HashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
|
||||
{
|
||||
Entry* entry = allocator::new(set.allocator, Entry, {
|
||||
.hash = hash,
|
||||
.value = value,
|
||||
.next = set.table[bucket_index]
|
||||
});
|
||||
set.table[bucket_index] = entry;
|
||||
set.count++;
|
||||
}
|
||||
|
||||
<*
|
||||
Removes the entry for the specified value if present
|
||||
@return "true if found and removed, false otherwise"
|
||||
*>
|
||||
fn bool HashSet.remove_entry_for_value(&set, Value value) @private
|
||||
{
|
||||
if (!set.count) return false;
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
Entry* prev = set.table[i];
|
||||
Entry* e = prev;
|
||||
while (e)
|
||||
{
|
||||
Entry *next = e.next;
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
set.count--;
|
||||
if (prev == e)
|
||||
{
|
||||
set.table[i] = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
set.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void HashSet.free_entry(&set, Entry *entry) @private
|
||||
{
|
||||
allocator::free(set.allocator, entry);
|
||||
}
|
||||
|
||||
struct HashSetIterator
|
||||
{
|
||||
HashSet* set;
|
||||
usz bucket_index;
|
||||
Entry* current;
|
||||
}
|
||||
|
||||
fn HashSetIterator HashSet.iter(&set) => { .set = set, .bucket_index = 0, .current = null };
|
||||
|
||||
fn Value? HashSetIterator.next(&self)
|
||||
{
|
||||
if (self.current)
|
||||
{
|
||||
Value value = self.current.value;
|
||||
self.current = self.current.next;
|
||||
return value;
|
||||
}
|
||||
|
||||
while (self.bucket_index < self.set.table.len)
|
||||
{
|
||||
self.current = self.set.table[self.bucket_index++];
|
||||
if (self.current)
|
||||
{
|
||||
Value value = self.current.value;
|
||||
self.current = self.current.next;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz HashSetIterator.len(&self) @operator(len)
|
||||
{
|
||||
return self.set.count;
|
||||
}
|
||||
|
||||
<* @pure *>
|
||||
fn uint rehash(uint hash) @inline @private
|
||||
{
|
||||
hash ^= (hash >> 20) ^ (hash >> 12);
|
||||
return hash ^ ((hash >> 7) ^ (hash >> 4));
|
||||
}
|
||||
|
||||
macro uint index_for(uint hash, uint capacity) @private => hash & (capacity - 1);
|
||||
|
||||
int dummy @local;
|
||||
539
lib/std/collections/interfacelist.c3
Normal file
539
lib/std/collections/interfacelist.c3
Normal file
@@ -0,0 +1,539 @@
|
||||
// Copyright (c) 2024-2025 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
<*
|
||||
@require Type.kindof == INTERFACE || Type.kindof == ANY : "The kind of an interfacelist must be an interface or `any`"
|
||||
*>
|
||||
module std::collections::interfacelist <Type>;
|
||||
import std::io,std::math;
|
||||
|
||||
alias InterfacePredicate = fn bool(Type value);
|
||||
alias InterfaceTest = fn bool(Type type, Type context);
|
||||
|
||||
<*
|
||||
The InterfaceList contains a heterogenous set of types implementing an interface. anything placed in the
|
||||
list will shallowly copied in order to be stored as the interface. This means
|
||||
that the list will copy and free its elements.
|
||||
|
||||
However, because we're getting interface values back when we pop, those operations
|
||||
need to take an allocator, as we can only copy then pop then return the copy.
|
||||
|
||||
If we're not doing pop, then things are easier, since we can just hand over
|
||||
the existing value.
|
||||
*>
|
||||
struct InterfaceList (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
Type* entries;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Initialize the list. If not initialized then it will use the temp allocator
|
||||
when something is pushed to it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param initial_capacity : "The initial capacity to reserve, defaults to 16"
|
||||
*>
|
||||
fn InterfaceList* InterfaceList.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
if (initial_capacity > 0)
|
||||
{
|
||||
initial_capacity = math::next_power_of_2(initial_capacity);
|
||||
self.entries = allocator::alloc_array(allocator, Type, initial_capacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.entries = null;
|
||||
}
|
||||
self.capacity = initial_capacity;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity : "The initial capacity to reserve"
|
||||
*>
|
||||
fn InterfaceList* InterfaceList.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn bool InterfaceList.is_initialized(&self) @inline => self.allocator != null;
|
||||
|
||||
<*
|
||||
Push an element on the list by cloning it.
|
||||
@require $defined(Type t = &element) : "Element must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.push(&self, element)
|
||||
{
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
self._append(allocator::clone(self.allocator, element));
|
||||
}
|
||||
|
||||
<*
|
||||
Free a retained element removed using *_retained.
|
||||
*>
|
||||
fn void InterfaceList.free_element(&self, Type element) @inline
|
||||
{
|
||||
allocator::free(self.allocator, element.ptr);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the last value, pop it and return the copy of it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use for copying"
|
||||
@return "A copy of the last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.copy_pop(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return (Type)allocator::clone_any(allocator, self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the last value, pop it and return the copy of it.
|
||||
|
||||
@return "A temp copy of the last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.tcopy_pop(&self) => self.copy_pop(tmem);
|
||||
|
||||
<*
|
||||
Pop the last value. It must later be released using `list.free_element()`.
|
||||
|
||||
@return "The last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.pop_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Remove all elements in the list.
|
||||
*>
|
||||
fn void InterfaceList.clear(&self)
|
||||
{
|
||||
for (usz i = 0; i < self.size; i++)
|
||||
{
|
||||
self.free_element(self.entries[i]);
|
||||
}
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the first value. It must later be released using `list.free_element()`.
|
||||
|
||||
@return "The first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.pop_first_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the first value, pop it and return the copy of it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use for copying"
|
||||
@return "A copy of the first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.copy_pop_first(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
defer self.remove_at(0);
|
||||
return (Type)allocator::clone_any(allocator, self.entries[0]);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the first value, pop it and return the temp copy of it.
|
||||
|
||||
@return "A temp copy of the first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.tcopy_pop_first(&self) => self.copy_pop_first(tmem);
|
||||
|
||||
<*
|
||||
Remove the element at the particular index.
|
||||
|
||||
@param index : "The index of the element to remove"
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void InterfaceList.remove_at(&self, usz index)
|
||||
{
|
||||
if (!--self.size || index == self.size) return;
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Add all the elements in another InterfaceList.
|
||||
|
||||
@param [&in] other_list : "The list to add"
|
||||
*>
|
||||
fn void InterfaceList.add_all(&self, InterfaceList* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
self.reserve(other_list.size);
|
||||
foreach (value : other_list)
|
||||
{
|
||||
self.entries[self.size++] = (Type)allocator::clone_any(self.allocator, value);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the order of the elements in the list.
|
||||
*>
|
||||
fn void InterfaceList.reverse(&self)
|
||||
{
|
||||
if (self.size < 2) return;
|
||||
usz half = self.size / 2U;
|
||||
usz end = self.size - 1;
|
||||
for (usz i = 0; i < half; i++)
|
||||
{
|
||||
self.swap(i, end - i);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Return a view of the data as a slice.
|
||||
|
||||
@return "The slice view"
|
||||
*>
|
||||
fn Type[] InterfaceList.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Push an element to the front of the list.
|
||||
|
||||
@param value : "The value to push to the list"
|
||||
@require $defined(Type t = &value) : "Value must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.push_front(&self, value)
|
||||
{
|
||||
self.insert_at(0, value);
|
||||
}
|
||||
|
||||
<*
|
||||
Insert an element at a particular index.
|
||||
|
||||
@param index : "the index where the element should be inserted"
|
||||
@param type : "the value to insert"
|
||||
@require index <= self.size : "The index is out of bounds"
|
||||
@require $defined(Type t = &type) : "Type must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.insert_at(&self, usz index, type)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(type);
|
||||
return;
|
||||
}
|
||||
Type value = allocator::clone(self.allocator, type);
|
||||
self._insert_at(self, index, value);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove the last element in the list. The list may not be empty.
|
||||
|
||||
@require self.size > 0 : "The list was already empty"
|
||||
*>
|
||||
fn void InterfaceList.remove_last(&self)
|
||||
{
|
||||
self.free_element(self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove the first element in the list, the list may not be empty.
|
||||
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn void InterfaceList.remove_first(&self)
|
||||
{
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
<*
|
||||
Return the first element
|
||||
|
||||
@return "The first element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.first(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[0] : NO_MORE_ELEMENT~;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the last element
|
||||
|
||||
@return "The last element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.last(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT~;
|
||||
}
|
||||
|
||||
<*
|
||||
Return whether the list is empty.
|
||||
|
||||
@return "True if the list is empty"
|
||||
*>
|
||||
fn bool InterfaceList.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the length of the list.
|
||||
|
||||
@return "The number of elements in the list"
|
||||
*>
|
||||
fn usz InterfaceList.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Return an element in the list.
|
||||
|
||||
@param index : "The index of the element to retrieve"
|
||||
@return "The element at the index"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
@require index < self.size : "Index out of range"
|
||||
*>
|
||||
fn Type InterfaceList.get(&self, usz index) @inline @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
<*
|
||||
Completely free and clear a list.
|
||||
*>
|
||||
fn void InterfaceList.free(&self)
|
||||
{
|
||||
if (!self.allocator) return;
|
||||
self.clear();
|
||||
allocator::free(self.allocator, self.entries);
|
||||
self.capacity = 0;
|
||||
self.entries = null;
|
||||
}
|
||||
|
||||
<*
|
||||
Swap two elements in a list.
|
||||
|
||||
@param i : "Index of one of the elements"
|
||||
@param j : "Index of the other element"
|
||||
@require i < self.size : "The first index is out of range"
|
||||
@require j < self.size : "The second index is out of range"
|
||||
*>
|
||||
fn void InterfaceList.swap(&self, usz i, usz j)
|
||||
{
|
||||
Type temp = self.entries[i];
|
||||
self.entries[i] = self.entries[j];
|
||||
self.entries[j] = temp;
|
||||
}
|
||||
|
||||
<*
|
||||
Print the list to a formatter.
|
||||
*>
|
||||
fn usz? InterfaceList.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
case 0:
|
||||
return formatter.print("[]")!;
|
||||
case 1:
|
||||
return formatter.printf("[%s]", self.entries[0])!;
|
||||
default:
|
||||
usz n = formatter.print("[")!;
|
||||
foreach (i, element : self.entries[:self.size])
|
||||
{
|
||||
if (i != 0) formatter.print(", ")!;
|
||||
n += formatter.printf("%s", element)!;
|
||||
}
|
||||
n += formatter.print("]")!;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Remove Type elements matching the predicate.
|
||||
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.remove_if(&self, InterfacePredicate filter)
|
||||
{
|
||||
return self._remove_if(filter, false);
|
||||
}
|
||||
|
||||
<*
|
||||
Retain the elements matching the predicate.
|
||||
|
||||
@param selection : "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.retain_if(&self, InterfacePredicate selection)
|
||||
{
|
||||
return self._remove_if(selection, true);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove Type elements matching the predicate.
|
||||
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@param context : "The context to the function"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.remove_using_test(&self, InterfaceTest filter, Type context)
|
||||
{
|
||||
return self._remove_using_test(filter, false, context);
|
||||
}
|
||||
|
||||
<*
|
||||
Retain Type elements matching the predicate.
|
||||
|
||||
@param selection : "The function to determine if it should be retained or not"
|
||||
@param context : "The context to the function"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.retain_using_test(&self, InterfaceTest selection, Type context)
|
||||
{
|
||||
return self._remove_using_test(selection, true, context);
|
||||
}
|
||||
|
||||
<*
|
||||
Reserve memory so that at least the `min_capacity` exists.
|
||||
|
||||
@param min_capacity : "The min capacity to hold"
|
||||
*>
|
||||
fn void InterfaceList.reserve(&self, usz min_capacity)
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity);
|
||||
self.capacity = min_capacity;
|
||||
}
|
||||
|
||||
<*
|
||||
Set the element at Type index.
|
||||
|
||||
@param index : "The index where to set the value."
|
||||
@param value : "The value to set"
|
||||
@require index <= self.size : "Index out of range"
|
||||
@require $defined(Type t = &value) : "Value must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.set(&self, usz index, value)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(value);
|
||||
return;
|
||||
}
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index] = allocator::clone(self.allocator, value);
|
||||
}
|
||||
|
||||
// -- private
|
||||
|
||||
fn void InterfaceList.ensure_capacity(&self, usz added = 1) @inline @private
|
||||
{
|
||||
usz new_size = self.size + added;
|
||||
if (self.capacity >= new_size) return;
|
||||
|
||||
assert(new_size < usz.max / 2U);
|
||||
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
|
||||
while (new_capacity < new_size) new_capacity *= 2U;
|
||||
self.reserve(new_capacity);
|
||||
}
|
||||
|
||||
fn void InterfaceList._append(&self, Type element) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void InterfaceList._insert_at(&self, usz index, Type value) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
for (usz i = self.size; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.size++;
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
macro usz InterfaceList._remove_using_test(&self, InterfaceTest filter, bool $invert, ctx) @local
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && filter(self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
macro usz InterfaceList._remove_if(&self, InterfacePredicate filter, bool $invert) @local
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && filter(self.entries[i - 1])) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(self.entries[i - 1])) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
333
lib/std/collections/linked_blockingqueue.c3
Normal file
333
lib/std/collections/linked_blockingqueue.c3
Normal file
@@ -0,0 +1,333 @@
|
||||
module std::collections::blockingqueue <Value>;
|
||||
import std::thread, std::time;
|
||||
|
||||
|
||||
const INITIAL_CAPACITY = 16;
|
||||
|
||||
struct QueueEntry
|
||||
{
|
||||
Value value;
|
||||
<* Next in queue order *>
|
||||
QueueEntry* next;
|
||||
<* Previous in queue order *>
|
||||
QueueEntry* prev;
|
||||
}
|
||||
|
||||
struct LinkedBlockingQueue
|
||||
{
|
||||
<* First element in queue *>
|
||||
QueueEntry* head;
|
||||
<* Last element in queue *>
|
||||
QueueEntry* tail;
|
||||
<* Current number of elements *>
|
||||
usz count;
|
||||
<* Maximum capacity (0 for unbounded) *>
|
||||
usz capacity;
|
||||
Mutex lock;
|
||||
ConditionVariable not_empty;
|
||||
ConditionVariable not_full;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param capacity : "Maximum capacity (0 for unbounded)"
|
||||
@require !self.is_initialized() : "Queue was already initialized"
|
||||
*>
|
||||
fn LinkedBlockingQueue* LinkedBlockingQueue.init(&self, Allocator allocator, usz capacity = 0)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.capacity = capacity;
|
||||
self.count = 0;
|
||||
self.head = null;
|
||||
self.tail = null;
|
||||
|
||||
self.lock.init()!!;
|
||||
self.not_empty.init()!!;
|
||||
self.not_full.init()!!;
|
||||
return self;
|
||||
}
|
||||
|
||||
fn LinkedBlockingQueue* LinkedBlockingQueue.tinit(&self, usz capacity = 0)
|
||||
{
|
||||
return self.init(tmem, capacity) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
*>
|
||||
fn void LinkedBlockingQueue.free(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
// Free all remaining entries
|
||||
QueueEntry* entry = self.head;
|
||||
while (entry != null)
|
||||
{
|
||||
QueueEntry* next = entry.next;
|
||||
allocator::free(self.allocator, entry);
|
||||
entry = next;
|
||||
}
|
||||
};
|
||||
|
||||
self.lock.destroy();
|
||||
self.not_empty.destroy();
|
||||
self.not_full.destroy();
|
||||
}
|
||||
|
||||
fn void LinkedBlockingQueue.link_entry(&self, QueueEntry* entry) @private
|
||||
{
|
||||
entry.next = null;
|
||||
entry.prev = self.tail;
|
||||
|
||||
if (self.tail == null)
|
||||
{
|
||||
// First element in queue
|
||||
self.head = entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append to tail
|
||||
self.tail.next = entry;
|
||||
}
|
||||
self.tail = entry;
|
||||
self.count++;
|
||||
}
|
||||
|
||||
|
||||
fn QueueEntry* LinkedBlockingQueue.unlink_head(&self) @private
|
||||
{
|
||||
if (self.head == null) return null;
|
||||
|
||||
QueueEntry* entry = self.head;
|
||||
self.head = entry.next;
|
||||
|
||||
if (self.head != null)
|
||||
{
|
||||
self.head.prev = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queue is now empty
|
||||
self.tail = null;
|
||||
}
|
||||
|
||||
self.count--;
|
||||
return entry;
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "Value to add to the queue"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
*>
|
||||
fn void LinkedBlockingQueue.push(&self, Value value)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
while (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
self.not_full.wait(&self.lock);
|
||||
}
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
self.link_entry(entry);
|
||||
|
||||
// Signal that queue is no longer empty
|
||||
self.not_empty.signal();
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Get a value from the queue, blocking if there is no element in the queue.
|
||||
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value"
|
||||
*>
|
||||
fn Value LinkedBlockingQueue.poll(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
while (self.count == 0)
|
||||
{
|
||||
self.not_empty.wait(&self.lock);
|
||||
}
|
||||
|
||||
QueueEntry* entry = self.unlink_head();
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Pop an element from the queue, fail is it is empty.
|
||||
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value"
|
||||
@return? NO_MORE_ELEMENT : "If the queue is empty"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.pop(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.count == 0) return NO_MORE_ELEMENT~;
|
||||
|
||||
QueueEntry* entry = self.unlink_head();
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Poll with a timeout.
|
||||
|
||||
@param timeout : "Timeout in microseconds"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value or null if timeout occurred"
|
||||
@return? NO_MORE_ELEMENT : "If we reached the timeout"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.poll_timeout(&self, Duration timeout)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
// Use while loop to handle spurious wakeups
|
||||
if (!self.count)
|
||||
{
|
||||
Time start = time::now();
|
||||
Time end = start + timeout;
|
||||
while (!self.count)
|
||||
{
|
||||
if (end <= time::now()) break;
|
||||
if (catch self.not_empty.wait_until(&self.lock, end)) break;
|
||||
}
|
||||
if (!self.count) return NO_MORE_ELEMENT~;
|
||||
}
|
||||
|
||||
QueueEntry* entry = self.unlink_head();
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
|
||||
// Must signal not_full after removing an item
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "Current size of the queue"
|
||||
*>
|
||||
fn usz LinkedBlockingQueue.size(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return self.count;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "True if queue is empty"
|
||||
*>
|
||||
fn bool LinkedBlockingQueue.is_empty(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return self.count == 0;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Try to push, return CAPACITY_EXCEEDED if the queue is full.
|
||||
|
||||
@param value : "Value to add to the queue"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return? CAPACITY_EXCEEDED : "If the queue is full"
|
||||
*>
|
||||
fn void? LinkedBlockingQueue.try_push(&self, Value value)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED~;
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
self.link_entry(entry);
|
||||
self.not_empty.signal();
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Try to push, return CAPACITY_EXCEEDED if the queue is still full after timeout is reached.
|
||||
|
||||
@param value : "Value to add to the queue"
|
||||
@param timeout : "Timeout in microseconds"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return? CAPACITY_EXCEEDED : "If the queue is full"
|
||||
*>
|
||||
fn void? LinkedBlockingQueue.push_timeout(&self, Value value, Duration timeout)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
Time start = time::now();
|
||||
Time end = start + timeout;
|
||||
while (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
if (end <= time::now()) break;
|
||||
if (catch self.not_empty.wait_until(&self.lock, end)) break;
|
||||
}
|
||||
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED~;
|
||||
}
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
self.link_entry(entry);
|
||||
self.not_empty.signal();
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The head value or NO_MORE_ELEMENT~ if queue is empty"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.peek(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return (self.head != null) ? self.head.value : NO_MORE_ELEMENT~;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@return "True if queue is initialized"
|
||||
*>
|
||||
fn bool LinkedBlockingQueue.is_initialized(&self)
|
||||
{
|
||||
return self.allocator && self.lock.initialized;
|
||||
}
|
||||
651
lib/std/collections/linked_hashmap.c3
Normal file
651
lib/std/collections/linked_hashmap.c3
Normal file
@@ -0,0 +1,651 @@
|
||||
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
<*
|
||||
@require $defined((Key){}.hash()) : `No .hash function found on the key`
|
||||
*>
|
||||
module std::collections::map <Key, Value>;
|
||||
import std::math;
|
||||
import std::io @norecurse;
|
||||
|
||||
const LinkedHashMap LINKEDONHEAP = { .allocator = MAP_HEAP_ALLOCATOR };
|
||||
|
||||
struct LinkedEntry
|
||||
{
|
||||
uint hash;
|
||||
Key key;
|
||||
Value value;
|
||||
<* For bucket chain *>
|
||||
LinkedEntry* next;
|
||||
<* Previous in insertion order *>
|
||||
LinkedEntry* before;
|
||||
<* Next in insertion order *>
|
||||
LinkedEntry* after;
|
||||
}
|
||||
|
||||
struct LinkedHashMap (Printable)
|
||||
{
|
||||
LinkedEntry*[] table;
|
||||
Allocator allocator;
|
||||
usz count;
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
<* First inserted LinkedEntry *>
|
||||
LinkedEntry* head;
|
||||
<* Last inserted LinkedEntry *>
|
||||
LinkedEntry* tail;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
self.allocator = allocator;
|
||||
self.load_factor = load_factor;
|
||||
self.threshold = (usz)(capacity * load_factor);
|
||||
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
|
||||
self.head = null;
|
||||
self.tail = null;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init(tmem, capacity, load_factor) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro LinkedHashMap* LinkedHashMap.init_with_key_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
$for var $i = 0; $i < $vacount; $i += 2:
|
||||
self.set($vaarg[$i], $vaarg[$i + 1]);
|
||||
$endfor
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro LinkedHashMap* LinkedHashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys : "The keys for the LinkedHashMap entries"
|
||||
@param [in] values : "The values for the LinkedHashMap entries"
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@require keys.len == values.len : "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
assert(keys.len == values.len);
|
||||
self.init(allocator, capacity, load_factor);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
self.set(keys[i], values[i]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys : "The keys for the LinkedHashMap entries"
|
||||
@param [in] values : "The values for the LinkedHashMap entries"
|
||||
@require keys.len == values.len : "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_from_keys_and_values(tmem, keys, values, capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
Has this hash map been initialized yet?
|
||||
|
||||
@param [&in] map : "The hash map we are testing"
|
||||
@return "Returns true if it has been initialized, false otherwise"
|
||||
*>
|
||||
fn bool LinkedHashMap.is_initialized(&map)
|
||||
{
|
||||
return map.allocator && map.allocator.ptr != &dummy;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param [&in] other_map : "The map to copy from."
|
||||
@require !self.is_initialized() : "Map was already initialized"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.init_from_map(&self, Allocator allocator, LinkedHashMap* other_map)
|
||||
{
|
||||
self.init(allocator, other_map.table.len, other_map.load_factor);
|
||||
self.put_all_for_create(other_map);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_map : "The map to copy from."
|
||||
@require !map.is_initialized() : "Map was already initialized"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.tinit_from_map(&map, LinkedHashMap* other_map)
|
||||
{
|
||||
return map.init_from_map(tmem, other_map) @inline;
|
||||
}
|
||||
|
||||
fn bool LinkedHashMap.is_empty(&map) @inline
|
||||
{
|
||||
return !map.count;
|
||||
}
|
||||
|
||||
fn usz LinkedHashMap.len(&map) @inline => map.count;
|
||||
|
||||
fn Value*? LinkedHashMap.get_ref(&map, Key key)
|
||||
{
|
||||
if (!map.count) return NOT_FOUND~;
|
||||
uint hash = rehash(key.hash());
|
||||
for (LinkedEntry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return &e.value;
|
||||
}
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn LinkedEntry*? LinkedHashMap.get_entry(&map, Key key)
|
||||
{
|
||||
if (!map.count) return NOT_FOUND~;
|
||||
uint hash = rehash(key.hash());
|
||||
for (LinkedEntry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e;
|
||||
}
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value or set it to the value
|
||||
|
||||
@require $defined(Value val = #expr)
|
||||
*>
|
||||
macro Value LinkedHashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
{
|
||||
if (!map.count)
|
||||
{
|
||||
Value val = #expr;
|
||||
map.set(key, val);
|
||||
return val;
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (LinkedEntry *e = map.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e.value;
|
||||
}
|
||||
Value val = #expr;
|
||||
map.add_entry(hash, key, val, index);
|
||||
return val;
|
||||
}
|
||||
|
||||
fn Value? LinkedHashMap.get(&map, Key key) @operator([]) => *map.get_ref(key) @inline;
|
||||
|
||||
fn bool LinkedHashMap.has_key(&map, Key key) => @ok(map.get_ref(key));
|
||||
|
||||
fn bool LinkedHashMap.set(&map, Key key, Value value) @operator([]=)
|
||||
{
|
||||
// If the map isn't initialized, use the defaults to initialize it.
|
||||
switch (map.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
map.init(mem);
|
||||
case null:
|
||||
map.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (LinkedEntry *e = map.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
e.value = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
map.add_entry(hash, key, value, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void? LinkedHashMap.remove(&map, Key key) @maydiscard
|
||||
{
|
||||
if (!map.remove_entry_for_key(key)) return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.clear(&map)
|
||||
{
|
||||
if (!map.count) return;
|
||||
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
LinkedEntry* next = entry.after;
|
||||
map.free_entry(entry);
|
||||
entry = next;
|
||||
}
|
||||
|
||||
foreach (LinkedEntry** &bucket : map.table)
|
||||
{
|
||||
*bucket = null;
|
||||
}
|
||||
|
||||
map.count = 0;
|
||||
map.head = null;
|
||||
map.tail = null;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.free(&map)
|
||||
{
|
||||
if (!map.is_initialized()) return;
|
||||
map.clear();
|
||||
map.free_internal(map.table.ptr);
|
||||
map.table = {};
|
||||
}
|
||||
|
||||
fn Key[] LinkedHashMap.tkeys(&self)
|
||||
{
|
||||
return self.keys(tmem) @inline;
|
||||
}
|
||||
|
||||
fn Key[] LinkedHashMap.keys(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
|
||||
Key[] list = allocator::alloc_array(allocator, Key, self.count);
|
||||
usz index = 0;
|
||||
|
||||
LinkedEntry* entry = self.head;
|
||||
while (entry)
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
list[index++] = entry.key.copy(allocator);
|
||||
$else
|
||||
list[index++] = entry.key;
|
||||
$endif
|
||||
entry = entry.after;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
macro LinkedHashMap.@each(map; @body(key, value))
|
||||
{
|
||||
map.@each_entry(; LinkedEntry* entry)
|
||||
{
|
||||
@body(entry.key, entry.value);
|
||||
};
|
||||
}
|
||||
|
||||
macro LinkedHashMap.@each_entry(map; @body(entry))
|
||||
{
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
@body(entry);
|
||||
entry = entry.after;
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] LinkedHashMap.tvalues(&map) => map.values(tmem) @inline;
|
||||
|
||||
fn Value[] LinkedHashMap.values(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, self.count);
|
||||
usz index = 0;
|
||||
LinkedEntry* entry = self.head;
|
||||
while (entry)
|
||||
{
|
||||
list[index++] = entry.value;
|
||||
entry = entry.after;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
fn bool LinkedHashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
|
||||
{
|
||||
if (!map.count) return false;
|
||||
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
if (equals(v, entry.value)) return true;
|
||||
entry = entry.after;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn LinkedHashMapIterator LinkedHashMap.iter(&self) => { .map = self, .current = self.head, .started = false };
|
||||
|
||||
fn LinkedHashMapValueIterator LinkedHashMap.value_iter(&self) => { .map = self, .current = self.head, .started = false };
|
||||
|
||||
fn LinkedHashMapKeyIterator LinkedHashMap.key_iter(&self) => { .map = self, .current = self.head, .started = false };
|
||||
|
||||
fn bool LinkedHashMapIterator.next(&self)
|
||||
{
|
||||
if (!self.started)
|
||||
{
|
||||
self.current = self.map.head;
|
||||
self.started = true;
|
||||
}
|
||||
else if (self.current)
|
||||
{
|
||||
self.current = self.current.after;
|
||||
}
|
||||
return self.current != null;
|
||||
}
|
||||
|
||||
fn LinkedEntry*? LinkedHashMapIterator.get(&self)
|
||||
{
|
||||
return self.current ? self.current : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn Value*? LinkedHashMapValueIterator.get(&self)
|
||||
{
|
||||
return self.current ? &self.current.value : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn Key*? LinkedHashMapKeyIterator.get(&self)
|
||||
{
|
||||
return self.current ? &self.current.key : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool LinkedHashMapIterator.has_next(&self)
|
||||
{
|
||||
if (!self.started) return self.map.head != null;
|
||||
return self.current && self.current.after != null;
|
||||
}
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
|
||||
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.key = key,
|
||||
.value = value,
|
||||
.next = map.table[bucket_index],
|
||||
.before = map.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
// Update bucket chain
|
||||
map.table[bucket_index] = entry;
|
||||
|
||||
// Update linked list
|
||||
if (map.tail)
|
||||
{
|
||||
map.tail.after = entry;
|
||||
entry.before = map.tail;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.head = entry;
|
||||
}
|
||||
map.tail = entry;
|
||||
|
||||
if (map.count++ >= map.threshold)
|
||||
{
|
||||
map.resize(map.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.resize(&map, uint new_capacity) @private
|
||||
{
|
||||
LinkedEntry*[] old_table = map.table;
|
||||
uint old_capacity = old_table.len;
|
||||
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
map.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedEntry*[] new_table = allocator::new_array(map.allocator, LinkedEntry*, new_capacity);
|
||||
map.table = new_table;
|
||||
map.threshold = (uint)(new_capacity * map.load_factor);
|
||||
|
||||
// Rehash all entries - linked list order remains unchanged
|
||||
foreach (uint i, LinkedEntry *e : old_table)
|
||||
{
|
||||
if (!e) continue;
|
||||
|
||||
// Split the bucket chain into two chains based on new bit
|
||||
LinkedEntry* lo_head = null;
|
||||
LinkedEntry* lo_tail = null;
|
||||
LinkedEntry* hi_head = null;
|
||||
LinkedEntry* hi_tail = null;
|
||||
|
||||
do
|
||||
{
|
||||
LinkedEntry* next = e.next;
|
||||
if ((e.hash & old_capacity) == 0)
|
||||
{
|
||||
if (!lo_tail)
|
||||
{
|
||||
lo_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
lo_tail.next = e;
|
||||
}
|
||||
lo_tail = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!hi_tail)
|
||||
{
|
||||
hi_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
hi_tail.next = e;
|
||||
}
|
||||
hi_tail = e;
|
||||
}
|
||||
e.next = null;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
|
||||
if (lo_tail)
|
||||
{
|
||||
lo_tail.next = null;
|
||||
new_table[i] = lo_head;
|
||||
}
|
||||
if (hi_tail)
|
||||
{
|
||||
hi_tail.next = null;
|
||||
new_table[i + old_capacity] = hi_head;
|
||||
}
|
||||
}
|
||||
|
||||
map.free_internal(old_table.ptr);
|
||||
}
|
||||
|
||||
fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len;
|
||||
len += f.print("{ ")!;
|
||||
self.@each_entry(; LinkedEntry* entry)
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s: %s", entry.key, entry.value)!;
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.transfer(&map, LinkedEntry*[] new_table) @private
|
||||
{
|
||||
LinkedEntry*[] src = map.table;
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, LinkedEntry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
LinkedEntry* next = e.next;
|
||||
uint i = index_for(e.hash, new_capacity);
|
||||
e.next = new_table[i];
|
||||
new_table[i] = e;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
}
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.put_all_for_create(&map, LinkedHashMap* other_map) @private
|
||||
{
|
||||
if (!other_map.count) return;
|
||||
other_map.@each(; Key key, Value value) {
|
||||
map.set(key, value);
|
||||
};
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.put_for_create(&map, Key key, Value value) @private
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
for (LinkedEntry *e = map.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
e.value = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
map.create_entry(hash, key, value, i);
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.free_internal(&map, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(map.allocator, ptr);
|
||||
}
|
||||
|
||||
fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
if (!map.count) return false;
|
||||
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
LinkedEntry* prev = null;
|
||||
LinkedEntry* e = map.table[i];
|
||||
|
||||
while (e)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
if (prev)
|
||||
{
|
||||
prev.next = e.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.table[i] = e.next;
|
||||
}
|
||||
|
||||
if (e.before)
|
||||
{
|
||||
e.before.after = e.after;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.head = e.after;
|
||||
}
|
||||
|
||||
if (e.after)
|
||||
{
|
||||
e.after.before = e.before;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.tail = e.before;
|
||||
}
|
||||
|
||||
map.count--;
|
||||
map.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = e.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
|
||||
{
|
||||
LinkedEntry *e = map.table[bucket_index];
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
|
||||
map.table[bucket_index] = entry;
|
||||
map.count++;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.free_entry(&self, LinkedEntry *entry) @local
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
allocator::free(self.allocator, entry.key);
|
||||
$endif
|
||||
self.free_internal(entry);
|
||||
}
|
||||
|
||||
|
||||
struct LinkedHashMapIterator
|
||||
{
|
||||
LinkedHashMap* map;
|
||||
LinkedEntry* current;
|
||||
bool started;
|
||||
}
|
||||
|
||||
typedef LinkedHashMapValueIterator = inline LinkedHashMapIterator;
|
||||
typedef LinkedHashMapKeyIterator = inline LinkedHashMapIterator;
|
||||
|
||||
fn usz LinkedHashMapValueIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz LinkedHashMapKeyIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz LinkedHashMapIterator.len(self) @operator(len) => self.map.count;
|
||||
|
||||
int dummy @local;
|
||||
730
lib/std/collections/linked_hashset.c3
Normal file
730
lib/std/collections/linked_hashset.c3
Normal file
@@ -0,0 +1,730 @@
|
||||
<*
|
||||
@require $defined((Value){}.hash()) : `No .hash function found on the value`
|
||||
*>
|
||||
module std::collections::set <Value>;
|
||||
import std::math;
|
||||
import std::io @norecurse;
|
||||
|
||||
const LinkedHashSet LINKEDONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
|
||||
|
||||
struct LinkedEntry
|
||||
{
|
||||
uint hash;
|
||||
Value value;
|
||||
<* For bucket chain *>
|
||||
LinkedEntry* next;
|
||||
<* Previous in insertion order *>
|
||||
LinkedEntry* before;
|
||||
<* Next in insertion order *>
|
||||
LinkedEntry* after;
|
||||
}
|
||||
|
||||
struct LinkedHashSet (Printable)
|
||||
{
|
||||
LinkedEntry*[] table;
|
||||
Allocator allocator;
|
||||
<* Number of elements *>
|
||||
usz count;
|
||||
<* Resize limit *>
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
<* Resize limit *>
|
||||
LinkedEntry* head;
|
||||
<* First inserted LinkedEntry *>
|
||||
LinkedEntry* tail;
|
||||
}
|
||||
|
||||
fn usz LinkedHashSet.len(&self) @operator(len) => self.count;
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
self.allocator = allocator;
|
||||
self.threshold = (usz)(capacity * load_factor);
|
||||
self.load_factor = load_factor;
|
||||
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
|
||||
|
||||
self.head = null;
|
||||
self.tail = null;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init(tmem, capacity, load_factor) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro LinkedHashSet* LinkedHashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
self.add($vaarg[$i]);
|
||||
$endfor
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro LinkedHashSet* LinkedHashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the LinkedHashSet"
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
foreach (v : values) self.add(v);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the LinkedHashSet entries"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_from_values(tmem, values, capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
Has this linked hash set been initialized yet?
|
||||
|
||||
@param [&in] set : "The linked hash set we are testing"
|
||||
@return "Returns true if it has been initialized, false otherwise"
|
||||
*>
|
||||
fn bool LinkedHashSet.is_initialized(&set)
|
||||
{
|
||||
return set.allocator && set.allocator.ptr != &dummy;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.init_from_set(&self, Allocator allocator, LinkedHashSet* other_set)
|
||||
{
|
||||
self.init(allocator, other_set.table.len, other_set.load_factor);
|
||||
LinkedEntry* entry = other_set.head;
|
||||
while (entry) // Save insertion order
|
||||
{
|
||||
self.put_for_create(entry.value);
|
||||
entry = entry.after;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !set.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.tinit_from_set(&set, LinkedHashSet* other_set)
|
||||
{
|
||||
return set.init_from_set(tmem, other_set) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set is empty
|
||||
|
||||
@return "true if it is empty"
|
||||
@pure
|
||||
*>
|
||||
fn bool LinkedHashSet.is_empty(&set) @inline
|
||||
{
|
||||
return !set.count;
|
||||
}
|
||||
|
||||
<*
|
||||
Add all elements in the slice to the set.
|
||||
|
||||
@param [in] list
|
||||
@return "The number of new elements added"
|
||||
@ensure total <= list.len
|
||||
*>
|
||||
fn usz LinkedHashSet.add_all(&set, Value[] list)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : list)
|
||||
{
|
||||
if (set.add(v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other
|
||||
@return "The number of new elements added"
|
||||
@ensure return <= other.count
|
||||
*>
|
||||
fn usz LinkedHashSet.add_all_from(&set, LinkedHashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (set.add(value)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to add"
|
||||
@return "true if the value didn't exist in the set"
|
||||
*>
|
||||
fn bool LinkedHashSet.add(&set, Value value)
|
||||
{
|
||||
// If the set isn't initialized, use the defaults to initialize it.
|
||||
switch (set.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
set.init(mem);
|
||||
case null:
|
||||
set.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint hash = rehash(value.hash());
|
||||
uint index = index_for(hash, set.table.len);
|
||||
for (LinkedEntry *e = set.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return false;
|
||||
}
|
||||
set.add_entry(hash, value, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Iterate over all the values in the set
|
||||
*>
|
||||
macro LinkedHashSet.@each(set; @body(value))
|
||||
{
|
||||
if (!set.count) return;
|
||||
LinkedEntry* entry = set.head;
|
||||
while (entry)
|
||||
{
|
||||
@body(entry.value);
|
||||
entry = entry.after;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set contains the given value.
|
||||
|
||||
@param value : "The value to check"
|
||||
@return "true if it exists in the set"
|
||||
*>
|
||||
fn bool LinkedHashSet.contains(&set, Value value)
|
||||
{
|
||||
if (!set.count) return false;
|
||||
uint hash = rehash(value.hash());
|
||||
for (LinkedEntry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
Remove a single value from the set.
|
||||
|
||||
@param value : "The value to remove"
|
||||
@return? NOT_FOUND : "If the entry is not found"
|
||||
*>
|
||||
fn void? LinkedHashSet.remove(&set, Value value) @maydiscard
|
||||
{
|
||||
if (!set.remove_entry_for_value(value)) return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz LinkedHashSet.remove_all(&set, Value[] values)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : values)
|
||||
{
|
||||
if (set.remove_entry_for_value(v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other : "Other set"
|
||||
*>
|
||||
fn usz LinkedHashSet.remove_all_from(&set, LinkedHashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value val)
|
||||
{
|
||||
if (set.remove_entry_for_value(val)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
Free all memory allocated by the hash set.
|
||||
*>
|
||||
fn void LinkedHashSet.free(&set)
|
||||
{
|
||||
if (!set.is_initialized()) return;
|
||||
set.clear();
|
||||
set.free_internal(set.table.ptr);
|
||||
set.table = {};
|
||||
}
|
||||
|
||||
<*
|
||||
Clear all elements from the set while keeping the underlying storage
|
||||
|
||||
@ensure set.count == 0
|
||||
*>
|
||||
fn void LinkedHashSet.clear(&set)
|
||||
{
|
||||
if (!set.count) return;
|
||||
|
||||
LinkedEntry* entry = set.head;
|
||||
while (entry)
|
||||
{
|
||||
LinkedEntry* next = entry.after;
|
||||
set.free_entry(entry);
|
||||
entry = next;
|
||||
}
|
||||
|
||||
foreach (LinkedEntry** &bucket : set.table)
|
||||
{
|
||||
*bucket = null;
|
||||
}
|
||||
|
||||
set.count = 0;
|
||||
set.head = null;
|
||||
set.tail = null;
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.reserve(&set, usz capacity)
|
||||
{
|
||||
if (capacity > set.threshold)
|
||||
{
|
||||
set.resize(math::next_power_of_2(capacity));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Set Operations ---
|
||||
|
||||
<*
|
||||
Returns the union of two sets (A | B)
|
||||
|
||||
@param [&in] other : "The other set to union with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the union of both sets"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.set_union(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
usz new_capacity = math::next_power_of_2(self.count + other.count);
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, new_capacity, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
result.add_all_from(other);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tset_union(&self, LinkedHashSet* other) => self.set_union(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Returns the intersection of the two sets (A & B)
|
||||
|
||||
@param [&in] other : "The other set to intersect with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the intersection of both sets"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.intersection(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
|
||||
|
||||
// Iterate through the smaller set for efficiency
|
||||
LinkedHashSet* smaller = self.count <= other.count ? self : other;
|
||||
LinkedHashSet* larger = self.count > other.count ? self : other;
|
||||
|
||||
smaller.@each(;Value value)
|
||||
{
|
||||
if (larger.contains(value)) result.add(value);
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tintersection(&self, LinkedHashSet* other) => self.intersection(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Return this set - other, so (A & ~B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set but not in the other"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.difference(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
self.@each(;Value value)
|
||||
{
|
||||
if (!other.contains(value))
|
||||
{
|
||||
result.add(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tdifference(&self, LinkedHashSet* other) => self.difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Return (A ^ B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set or the other, but not both"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.symmetric_difference(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (!result.add(value))
|
||||
{
|
||||
result.remove(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tsymmetric_difference(&self, LinkedHashSet* other) => self.symmetric_difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Check if this hash set is a subset of another set.
|
||||
|
||||
@param [&in] other : "The other set to check against"
|
||||
@return "True if all elements of this set are in the other set"
|
||||
*>
|
||||
fn bool LinkedHashSet.is_subset(&self, LinkedHashSet* other)
|
||||
{
|
||||
if (self.count == 0) return true;
|
||||
if (self.count > other.count) return false;
|
||||
|
||||
self.@each(; Value value) {
|
||||
if (!other.contains(value)) return false;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void LinkedHashSet.add_entry(&set, uint hash, Value value, uint bucket_index) @private
|
||||
{
|
||||
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.value = value,
|
||||
.next = set.table[bucket_index],
|
||||
.before = set.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
// Update bucket chain
|
||||
set.table[bucket_index] = entry;
|
||||
|
||||
// Update linked list
|
||||
if (set.tail)
|
||||
{
|
||||
set.tail.after = entry;
|
||||
entry.before = set.tail;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.head = entry;
|
||||
}
|
||||
set.tail = entry;
|
||||
|
||||
if (set.count++ >= set.threshold)
|
||||
{
|
||||
set.resize(set.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.resize(&set, usz new_capacity) @private
|
||||
{
|
||||
LinkedEntry*[] old_table = set.table;
|
||||
usz old_capacity = old_table.len;
|
||||
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
set.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedEntry*[] new_table = allocator::new_array(set.allocator, LinkedEntry*, new_capacity);
|
||||
set.table = new_table;
|
||||
set.threshold = (uint)(new_capacity * set.load_factor);
|
||||
|
||||
// Rehash all entries - linked list order remains unchanged
|
||||
foreach (uint i, LinkedEntry *e : old_table)
|
||||
{
|
||||
if (!e) continue;
|
||||
|
||||
// Split the bucket chain into two chains based on new bit
|
||||
LinkedEntry* lo_head = null;
|
||||
LinkedEntry* lo_tail = null;
|
||||
LinkedEntry* hi_head = null;
|
||||
LinkedEntry* hi_tail = null;
|
||||
|
||||
do
|
||||
{
|
||||
LinkedEntry* next = e.next;
|
||||
if ((e.hash & old_capacity) == 0)
|
||||
{
|
||||
if (!lo_tail)
|
||||
{
|
||||
lo_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
lo_tail.next = e;
|
||||
}
|
||||
lo_tail = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!hi_tail)
|
||||
{
|
||||
hi_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
hi_tail.next = e;
|
||||
}
|
||||
hi_tail = e;
|
||||
}
|
||||
e.next = null;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
|
||||
if (lo_tail)
|
||||
{
|
||||
lo_tail.next = null;
|
||||
new_table[i] = lo_head;
|
||||
}
|
||||
if (hi_tail)
|
||||
{
|
||||
hi_tail.next = null;
|
||||
new_table[i + old_capacity] = hi_head;
|
||||
}
|
||||
}
|
||||
|
||||
set.free_internal(old_table.ptr);
|
||||
}
|
||||
|
||||
fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len;
|
||||
len += f.print("{ ")!;
|
||||
self.@each(; Value value)
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s", value)!;
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.transfer(&set, LinkedEntry*[] new_table) @private
|
||||
{
|
||||
LinkedEntry*[] src = set.table;
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, LinkedEntry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
LinkedEntry* next = e.next;
|
||||
uint i = index_for(e.hash, new_capacity);
|
||||
e.next = new_table[i];
|
||||
new_table[i] = e;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
}
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.put_for_create(&set, Value value) @private
|
||||
{
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
for (LinkedEntry *e = set.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
// Value already exists, no need to do anything
|
||||
return;
|
||||
}
|
||||
}
|
||||
set.create_entry(hash, value, i);
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.free_internal(&set, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(set.allocator, ptr);
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
|
||||
{
|
||||
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.value = value,
|
||||
.next = set.table[bucket_index],
|
||||
.before = set.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
set.table[bucket_index] = entry;
|
||||
|
||||
// Update linked list
|
||||
if (set.tail)
|
||||
{
|
||||
set.tail.after = entry;
|
||||
entry.before = set.tail;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.head = entry;
|
||||
}
|
||||
set.tail = entry;
|
||||
|
||||
set.count++;
|
||||
}
|
||||
|
||||
fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
|
||||
{
|
||||
if (!set.count) return false;
|
||||
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
LinkedEntry* prev = null;
|
||||
LinkedEntry* e = set.table[i];
|
||||
|
||||
while (e)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
if (prev)
|
||||
{
|
||||
prev.next = e.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.table[i] = e.next;
|
||||
}
|
||||
|
||||
if (e.before)
|
||||
{
|
||||
e.before.after = e.after;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.head = e.after;
|
||||
}
|
||||
|
||||
if (e.after)
|
||||
{
|
||||
e.after.before = e.before;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.tail = e.before;
|
||||
}
|
||||
|
||||
set.count--;
|
||||
set.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = e.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.free_entry(&set, LinkedEntry *entry) @private
|
||||
{
|
||||
allocator::free(set.allocator, entry);
|
||||
}
|
||||
|
||||
struct LinkedHashSetIterator
|
||||
{
|
||||
LinkedHashSet* set;
|
||||
LinkedEntry* current;
|
||||
bool started;
|
||||
}
|
||||
|
||||
fn LinkedHashSetIterator LinkedHashSet.iter(&set) => { .set = set, .current = set.head, .started = false };
|
||||
|
||||
fn bool LinkedHashSetIterator.next(&self)
|
||||
{
|
||||
if (!self.started)
|
||||
{
|
||||
self.current = self.set.head;
|
||||
self.started = true;
|
||||
}
|
||||
else if (self.current)
|
||||
{
|
||||
self.current = self.current.after;
|
||||
}
|
||||
return self.current != null;
|
||||
}
|
||||
|
||||
fn Value*? LinkedHashSetIterator.get(&self)
|
||||
{
|
||||
return self.current ? &self.current.value : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool LinkedHashSetIterator.has_next(&self)
|
||||
{
|
||||
if (!self.started) return self.set.head != null;
|
||||
return self.current && self.current.after != null;
|
||||
}
|
||||
|
||||
fn usz LinkedHashSetIterator.len(&self) @operator(len)
|
||||
{
|
||||
return self.set.count;
|
||||
}
|
||||
|
||||
int dummy @local;
|
||||
@@ -1,14 +1,15 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::linkedlist{Type};
|
||||
module std::collections::linkedlist <Type>;
|
||||
import std::io;
|
||||
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
|
||||
struct Node @private
|
||||
struct Node
|
||||
{
|
||||
Node *next;
|
||||
Node *prev;
|
||||
Node* next;
|
||||
Node* prev;
|
||||
Type value;
|
||||
}
|
||||
|
||||
@@ -16,8 +17,31 @@ struct LinkedList
|
||||
{
|
||||
Allocator allocator;
|
||||
usz size;
|
||||
Node *_first;
|
||||
Node *_last;
|
||||
Node* _first;
|
||||
Node* _last;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len = f.print("{ ")!;
|
||||
for (Node* node = self._first; node != null; node = node.next)
|
||||
{
|
||||
len += f.printf(node.next ? "%s, " : "%s", node.value)!;
|
||||
}
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
macro LinkedList @new(Allocator allocator, Type[] #default_values = {})
|
||||
{
|
||||
LinkedList new_list;
|
||||
new_list.init(allocator);
|
||||
new_list.push_all(#default_values);
|
||||
return new_list;
|
||||
}
|
||||
|
||||
macro LinkedList @tnew(Type[] #default_values = {})
|
||||
{
|
||||
return @new(tmem, #default_values);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -68,6 +92,11 @@ fn void LinkedList.push_front(&self, Type value)
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn void LinkedList.push_front_all(&self, Type[] value)
|
||||
{
|
||||
foreach_r (v : value) self.push_front(v);
|
||||
}
|
||||
|
||||
fn void LinkedList.push(&self, Type value)
|
||||
{
|
||||
Node *last = self._last;
|
||||
@@ -85,18 +114,23 @@ fn void LinkedList.push(&self, Type value)
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn void LinkedList.push_all(&self, Type[] value)
|
||||
{
|
||||
foreach (v : value) self.push(v);
|
||||
}
|
||||
|
||||
fn Type? LinkedList.peek(&self) => self.first() @inline;
|
||||
fn Type? LinkedList.peek_last(&self) => self.last() @inline;
|
||||
|
||||
fn Type? LinkedList.first(&self)
|
||||
{
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
if (!self._first) return NO_MORE_ELEMENT~;
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn Type? LinkedList.last(&self)
|
||||
{
|
||||
if (!self._last) return NO_MORE_ELEMENT?;
|
||||
if (!self._last) return NO_MORE_ELEMENT~;
|
||||
return self._last.value;
|
||||
}
|
||||
|
||||
@@ -133,6 +167,7 @@ macro Node* LinkedList.node_at_index(&self, usz index)
|
||||
while (index--) node = node.next;
|
||||
return node;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
@@ -141,6 +176,14 @@ fn Type LinkedList.get(&self, usz index)
|
||||
return self.node_at_index(index).value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn Type* LinkedList.get_ref(&self, usz index)
|
||||
{
|
||||
return &self.node_at_index(index).value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
@@ -149,6 +192,26 @@ fn void LinkedList.set(&self, usz index, Type element)
|
||||
self.node_at_index(index).value = element;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.index_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = self._first, usz i = 0; node != null; node = node.next, ++i)
|
||||
{
|
||||
if (node.value == t) return i;
|
||||
}
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.rindex_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = self._last, usz i = self.size - 1; node != null; node = node.prev, --i)
|
||||
{
|
||||
if (node.value == t) return i;
|
||||
if (i == 0) break;
|
||||
}
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
@@ -233,7 +296,7 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
|
||||
fn Type? LinkedList.pop(&self)
|
||||
{
|
||||
if (!self._last) return NO_MORE_ELEMENT?;
|
||||
if (!self._last) return NO_MORE_ELEMENT~;
|
||||
defer self.unlink_last();
|
||||
return self._last.value;
|
||||
}
|
||||
@@ -245,20 +308,20 @@ fn bool LinkedList.is_empty(&self)
|
||||
|
||||
fn Type? LinkedList.pop_front(&self)
|
||||
{
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
if (!self._first) return NO_MORE_ELEMENT~;
|
||||
defer self.unlink_first();
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn void? LinkedList.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
if (!self._first) return NO_MORE_ELEMENT~;
|
||||
self.unlink_last();
|
||||
}
|
||||
|
||||
fn void? LinkedList.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
if (!self._first) return NO_MORE_ELEMENT~;
|
||||
self.unlink_first();
|
||||
}
|
||||
|
||||
@@ -334,3 +397,69 @@ fn void LinkedList.unlink(&self, Node* x) @private
|
||||
self.free_node(x);
|
||||
self.size--;
|
||||
}
|
||||
|
||||
|
||||
macro bool LinkedList.eq(&self, other) @operator(==) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
Node* node1 = self._first;
|
||||
Node* node2 = other._first;
|
||||
while (true)
|
||||
{
|
||||
if (!node1) return node2 == null;
|
||||
if (!node2) return false;
|
||||
if (node1.value != node2.value) return false;
|
||||
node1 = node1.next;
|
||||
node2 = node2.next;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn LinkedListArrayView LinkedList.array_view(&self)
|
||||
{
|
||||
return { .list = self, .current_node = self._first };
|
||||
}
|
||||
|
||||
|
||||
struct LinkedListArrayView
|
||||
{
|
||||
LinkedList* list;
|
||||
Node* current_node;
|
||||
usz current_index;
|
||||
}
|
||||
|
||||
fn usz LinkedListArrayView.len(&self) @operator(len) => self.list.size;
|
||||
|
||||
<*
|
||||
@require index < self.list.size
|
||||
*>
|
||||
fn Type LinkedListArrayView.get(&self, usz index) @operator([])
|
||||
{
|
||||
return *self.get_ref(index);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.list.size
|
||||
*>
|
||||
fn Type* LinkedListArrayView.get_ref(&self, usz index) @operator(&[])
|
||||
{
|
||||
if (index == self.list.size - 1)
|
||||
{
|
||||
self.current_node = self.list._last;
|
||||
self.current_index = index;
|
||||
}
|
||||
|
||||
while (self.current_index != index)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case index < self.current_index: // reverse iteration
|
||||
self.current_node = self.current_node.prev;
|
||||
self.current_index--;
|
||||
case index > self.current_index:
|
||||
self.current_node = self.current_node.next;
|
||||
self.current_index++;
|
||||
}
|
||||
}
|
||||
|
||||
return &self.current_node.value;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::list{Type};
|
||||
module std::collections::list <Type>;
|
||||
import std::io, std::math, std::collections::list_common;
|
||||
|
||||
alias ElementPredicate = fn bool(Type *type);
|
||||
@@ -13,7 +13,7 @@ const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
|
||||
|
||||
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR };
|
||||
|
||||
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct List (Printable)
|
||||
{
|
||||
@@ -57,7 +57,7 @@ fn List* List.tinit(&self, usz initial_capacity = 16)
|
||||
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
|
||||
{
|
||||
self.init(allocator, values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
self.push_all(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
|
||||
fn List* List.tinit_with_array(&self, Type[] values)
|
||||
{
|
||||
self.tinit(values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
self.push_all(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ fn void List.push(&self, Type element) @inline
|
||||
|
||||
fn Type? List.pop(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.set_size(self.size - 1);
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
@@ -127,7 +127,7 @@ fn void List.clear(&self)
|
||||
|
||||
fn Type? List.pop_first(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
@@ -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)
|
||||
@@ -198,7 +199,21 @@ fn Type[] List.array_view(&self)
|
||||
@param [in] array
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void List.add_array(&self, Type[] array)
|
||||
fn void List.add_array(&self, Type[] array) @deprecated("Use push_all")
|
||||
{
|
||||
if (!array.len) return;
|
||||
self.reserve(array.len);
|
||||
usz index = self.set_size(self.size + array.len);
|
||||
self.entries[index : array.len] = array[..];
|
||||
}
|
||||
|
||||
<*
|
||||
Add the values of an array to this list.
|
||||
|
||||
@param [in] array
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void List.push_all(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return;
|
||||
self.reserve(array.len);
|
||||
@@ -235,25 +250,25 @@ fn void List.set_at(&self, usz index, Type type)
|
||||
|
||||
fn void? List.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
self.set_size(self.size - 1);
|
||||
}
|
||||
|
||||
fn void? List.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type? List.first(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
fn Type? List.last(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
@@ -446,22 +461,22 @@ macro void List.post_alloc(&self) @private
|
||||
// Functions for equatable types
|
||||
|
||||
|
||||
fn usz? List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
fn usz? List.index_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz? List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
fn usz? List.rindex_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach_r (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -517,7 +532,8 @@ fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
usz old_size = self.size;
|
||||
defer {
|
||||
defer
|
||||
{
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
return list_common::list_remove_item(self, value);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module std::collections::maybe{Type};
|
||||
module std::collections::maybe <Type>;
|
||||
import std::io;
|
||||
|
||||
struct Maybe (Printable)
|
||||
@@ -32,7 +32,7 @@ const Maybe EMPTY = { };
|
||||
|
||||
macro Type? Maybe.get(self)
|
||||
{
|
||||
return self.has_value ? self.value : NOT_FOUND?;
|
||||
return self.has_value ? self.value : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool Maybe.equals(self, Maybe other) @operator(==) @if(types::is_equatable_type(Type))
|
||||
|
||||
@@ -178,17 +178,14 @@ 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"
|
||||
@require $typeof(value) != void* ||| value == null : "void pointers cannot be stored in an object"
|
||||
*>
|
||||
macro Object* Object.object_from_value(&self, value) @private
|
||||
{
|
||||
@@ -206,7 +203,7 @@ macro Object* Object.object_from_value(&self, value) @private
|
||||
return value;
|
||||
$case $Type.typeid == void*.typeid:
|
||||
return &NULL_OBJECT;
|
||||
$case $assignable(value, String):
|
||||
$case $defined(String x = value):
|
||||
return new_string(value, self.allocator);
|
||||
$default:
|
||||
$error "Unsupported object type.";
|
||||
@@ -245,8 +242,7 @@ macro Object* Object.push(&self, value)
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn Object*? Object.get(&self, String key) => self.is_empty() ? NOT_FOUND? : self.map.get(key);
|
||||
|
||||
fn Object*? Object.get(&self, String key) => self.is_empty() ? NOT_FOUND~ : self.map.get(key);
|
||||
|
||||
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
|
||||
|
||||
@@ -311,7 +307,7 @@ macro get_integer_value(Object* value, $Type)
|
||||
return ($Type)value.s.to_uint128();
|
||||
$endif
|
||||
}
|
||||
if (!value.is_int()) return string::MALFORMED_INTEGER?;
|
||||
if (!value.is_int()) return string::MALFORMED_INTEGER~;
|
||||
return ($Type)value.i;
|
||||
}
|
||||
|
||||
@@ -364,7 +360,7 @@ fn uint128? Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint1
|
||||
fn String? Object.get_string(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_string()) return TYPE_MISMATCH?;
|
||||
if (!value.is_string()) return TYPE_MISMATCH~;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
@@ -374,7 +370,7 @@ fn String? Object.get_string(&self, String key)
|
||||
fn String? Object.get_string_at(&self, usz index)
|
||||
{
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_string()) return TYPE_MISMATCH?;
|
||||
if (!value.is_string()) return TYPE_MISMATCH~;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
@@ -384,7 +380,7 @@ fn String? Object.get_string_at(&self, usz index)
|
||||
macro String? Object.get_enum(&self, $EnumType, String key)
|
||||
{
|
||||
Object value = self.get(key)!;
|
||||
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
|
||||
if ($EnumType.typeid != value.type) return TYPE_MISMATCH~;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
@@ -394,7 +390,7 @@ macro String? Object.get_enum(&self, $EnumType, String key)
|
||||
macro String? Object.get_enum_at(&self, $EnumType, usz index)
|
||||
{
|
||||
Object value = self.get_at(index);
|
||||
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
|
||||
if ($EnumType.typeid != value.type) return TYPE_MISMATCH~;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
@@ -404,7 +400,7 @@ macro String? Object.get_enum_at(&self, $EnumType, usz index)
|
||||
fn bool? Object.get_bool(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_bool()) return TYPE_MISMATCH?;
|
||||
if (!value.is_bool()) return TYPE_MISMATCH~;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
@@ -415,7 +411,7 @@ fn bool? Object.get_bool(&self, String key)
|
||||
fn bool? Object.get_bool_at(&self, usz index)
|
||||
{
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_bool()) return TYPE_MISMATCH?;
|
||||
if (!value.is_bool()) return TYPE_MISMATCH~;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
@@ -434,7 +430,7 @@ fn double? Object.get_float(&self, String key)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
return TYPE_MISMATCH?;
|
||||
return TYPE_MISMATCH~;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +449,7 @@ fn double? Object.get_float_at(&self, usz index)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
return TYPE_MISMATCH?;
|
||||
return TYPE_MISMATCH~;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,16 +20,13 @@
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
module std::collections::priorityqueue{Type};
|
||||
import std::collections::priorityqueue::private;
|
||||
|
||||
typedef PriorityQueue = inline PrivatePriorityQueue{Type, false};
|
||||
typedef PriorityQueueMax = inline PrivatePriorityQueue{Type, true};
|
||||
|
||||
module std::collections::priorityqueue::private{Type, MAX};
|
||||
module std::collections::priorityqueue;
|
||||
import std::collections::list, std::io;
|
||||
|
||||
struct PrivatePriorityQueue (Printable)
|
||||
typedef PriorityQueue <Type> = inline PrivatePriorityQueue{Type, false};
|
||||
typedef PriorityQueueMax <Type> = inline PrivatePriorityQueue{Type, true};
|
||||
|
||||
struct PrivatePriorityQueue (Printable) <Type, MAX>
|
||||
{
|
||||
List{Type} heap;
|
||||
}
|
||||
@@ -86,7 +83,7 @@ fn Type? PrivatePriorityQueue.pop(&self)
|
||||
{
|
||||
usz i = 0;
|
||||
usz len = self.heap.len();
|
||||
if (!len) return NO_MORE_ELEMENT?;
|
||||
if (!len) return NO_MORE_ELEMENT~;
|
||||
usz new_count = len - 1;
|
||||
self.heap.swap(0, new_count);
|
||||
while OUTER: ((2 * i + 1) < new_count)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<*
|
||||
@require Type.is_ordered : "The type must be ordered"
|
||||
*>
|
||||
module std::collections::range{Type};
|
||||
module std::collections::range <Type>;
|
||||
import std::io;
|
||||
|
||||
struct Range (Printable)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<*
|
||||
@require Type.kindof == ARRAY : "Required an array type"
|
||||
*>
|
||||
module std::collections::ringbuffer{Type};
|
||||
module std::collections::ringbuffer <Type>;
|
||||
import std::io;
|
||||
|
||||
alias Element = $typeof((Type){}[0]);
|
||||
@@ -48,7 +48,7 @@ fn Element? RingBuffer.pop(&self)
|
||||
switch
|
||||
{
|
||||
case self.written == 0:
|
||||
return NO_MORE_ELEMENT?;
|
||||
return NO_MORE_ELEMENT~;
|
||||
case self.written < self.buf.len:
|
||||
self.written--;
|
||||
return self.buf[self.written];
|
||||
|
||||
@@ -1,16 +1,72 @@
|
||||
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 $defined(*a = self.first) : "You cannot assign the first value to a"
|
||||
@require $defined(*b = self.second) : "You cannot assign the second value to b"
|
||||
*>
|
||||
macro void Pair.unpack(&self, a, b)
|
||||
{
|
||||
*a = self.first;
|
||||
*b = self.second;
|
||||
}
|
||||
|
||||
fn bool Pair.equal(self, Pair other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2))
|
||||
{
|
||||
return self.first == other.first && self.second == other.second;
|
||||
}
|
||||
|
||||
module std::collections::triple <Type1, Type2, Type3>;
|
||||
import std::io;
|
||||
|
||||
struct Triple (Printable)
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
Type3 third;
|
||||
}
|
||||
|
||||
fn usz? Triple.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
return f.printf("{ %s, %s, %s }", self.first, self.second, self.third);
|
||||
}
|
||||
<*
|
||||
@param [&out] a
|
||||
@param [&out] b
|
||||
@param [&out] c
|
||||
@require $defined(*a = self.first) : "You cannot assign the first value to a"
|
||||
@require $defined(*b = self.second) : "You cannot assign the second value to b"
|
||||
@require $defined(*c = self.third) : "You cannot assign the second value to c"
|
||||
*>
|
||||
macro void Triple.unpack(&self, a, b, c)
|
||||
{
|
||||
*a = self.first;
|
||||
*b = self.second;
|
||||
*c = self.third;
|
||||
}
|
||||
|
||||
fn bool Triple.equal(self, Triple other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2) &&& types::has_equals(Type3))
|
||||
{
|
||||
return self.first == other.first && self.second == other.second && self.third == other.third;
|
||||
}
|
||||
|
||||
module std::collections::tuple <Type1, Type2>;
|
||||
|
||||
struct Tuple @deprecated("Use 'Pair' instead")
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
}
|
||||
@@ -7,10 +7,12 @@ const uint PIXELS_MAX = 400000000;
|
||||
Purely informative. It will be saved to the file header,
|
||||
but does not affect how chunks are en-/decoded.
|
||||
*>
|
||||
enum QOIColorspace : char (char id)
|
||||
enum QOIColorspace : const char
|
||||
{
|
||||
SRGB = 0, // sRGB with linear alpha
|
||||
LINEAR = 1 // all channels linear
|
||||
<* sRGB with linear alpha *>
|
||||
SRGB = 0,
|
||||
<* all channels linear *>
|
||||
LINEAR = 1
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -19,7 +21,7 @@ enum QOIColorspace : char (char id)
|
||||
AUTO can be used when decoding to automatically determine
|
||||
the channels from the file's header.
|
||||
*>
|
||||
enum QOIChannels : char (char id)
|
||||
enum QOIChannels : const inline char
|
||||
{
|
||||
AUTO = 0,
|
||||
RGB = 3,
|
||||
@@ -98,7 +100,7 @@ fn usz? write(String filename, char[] input, QOIDesc* desc) => @pool()
|
||||
fn char[]? read(Allocator allocator, String filename, QOIDesc* desc, QOIChannels channels = AUTO) => @pool()
|
||||
{
|
||||
// read file
|
||||
char[] data = file::load_temp(filename) ?? FILE_OPEN_FAILED?!;
|
||||
char[] data = file::load_temp(filename) ?? FILE_OPEN_FAILED~!;
|
||||
// pass data to decode function
|
||||
return decode(allocator, data, desc, channels);
|
||||
}
|
||||
@@ -126,14 +128,14 @@ import std::bits;
|
||||
fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
{
|
||||
// check info in desc
|
||||
if (desc.width == 0 || desc.height == 0) return INVALID_PARAMETERS?;
|
||||
if (desc.channels == AUTO) return INVALID_PARAMETERS?;
|
||||
if (desc.width == 0 || desc.height == 0) return INVALID_PARAMETERS~;
|
||||
if (desc.channels == AUTO) return INVALID_PARAMETERS~;
|
||||
uint pixels = desc.width * desc.height;
|
||||
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
|
||||
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS~;
|
||||
|
||||
// check input data size
|
||||
uint image_size = pixels * desc.channels.id;
|
||||
if (image_size != input.len) return INVALID_DATA?;
|
||||
uint image_size = pixels * desc.channels;
|
||||
if (image_size != input.len) return INVALID_DATA~;
|
||||
|
||||
// allocate memory for encoded data (output)
|
||||
// header + chunk tag and RGB(A) data for each pixel + end of stream
|
||||
@@ -146,13 +148,13 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
.be_magic = bswap('qoif'),
|
||||
.be_width = bswap(desc.width),
|
||||
.be_height = bswap(desc.height),
|
||||
.channels = desc.channels.id,
|
||||
.colorspace = desc.colorspace.id
|
||||
.channels = desc.channels,
|
||||
.colorspace = desc.colorspace
|
||||
};
|
||||
|
||||
uint pos = Header.sizeof; // Current position in output
|
||||
uint loc; // Current position in image (top-left corner)
|
||||
uint loc_end = image_size - desc.channels.id; // End of image data
|
||||
uint loc_end = image_size - desc.channels; // End of image data
|
||||
char run_length = 0; // Length of the current run
|
||||
|
||||
Pixel[64] palette; // Zero-initialized by default
|
||||
@@ -163,7 +165,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
ichar[<3>] luma; // ...and luma
|
||||
|
||||
// write chunks
|
||||
for (loc = 0; loc < image_size; loc += desc.channels.id)
|
||||
for (loc = 0; loc < image_size; loc += desc.channels)
|
||||
{
|
||||
// set previous pixel
|
||||
prev = p;
|
||||
@@ -281,27 +283,27 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels channels = AUTO) @nodiscard
|
||||
{
|
||||
// check input data
|
||||
if (data.len < Header.sizeof + END_OF_STREAM.len) return INVALID_DATA?;
|
||||
if (data.len < Header.sizeof + END_OF_STREAM.len) return INVALID_DATA~;
|
||||
|
||||
// get header
|
||||
Header* header = (Header*)data.ptr;
|
||||
|
||||
// check magic bytes (FourCC)
|
||||
if (bswap(header.be_magic) != 'qoif') return INVALID_DATA?;
|
||||
if (bswap(header.be_magic) != 'qoif') return INVALID_DATA~;
|
||||
|
||||
// copy header data to desc
|
||||
desc.width = bswap(header.be_width);
|
||||
desc.height = bswap(header.be_height);
|
||||
desc.channels = @enumcast(QOIChannels, header.channels)!; // Rethrow if invalid
|
||||
desc.colorspace = @enumcast(QOIColorspace, header.colorspace)!; // Rethrow if invalid
|
||||
if (desc.channels == AUTO) return INVALID_DATA?; // Channels must be specified in the header
|
||||
uint width = desc.width = bswap(header.be_width);
|
||||
uint height = desc.height = bswap(header.be_height);
|
||||
QOIChannels desc_channels = desc.channels = header.channels;
|
||||
desc.colorspace = header.colorspace;
|
||||
if (desc_channels == AUTO) return INVALID_DATA~; // Channels must be specified in the header
|
||||
|
||||
// check width and height
|
||||
if (desc.width == 0 || desc.height == 0) return INVALID_DATA?;
|
||||
if (width == 0 || height == 0) return INVALID_DATA~;
|
||||
|
||||
// check pixel count
|
||||
ulong pixels = (ulong)desc.width * (ulong)desc.height;
|
||||
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
|
||||
ulong pixels = (ulong)width * (ulong)height;
|
||||
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS~;
|
||||
|
||||
uint pos = Header.sizeof; // Current position in data
|
||||
uint loc; // Current position in image (top-left corner)
|
||||
@@ -311,14 +313,14 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c
|
||||
Pixel[64] palette; // Zero-initialized by default
|
||||
Pixel p = { 0, 0, 0, 255 };
|
||||
|
||||
if (channels == AUTO) channels = desc.channels;
|
||||
if (channels == AUTO) channels = desc_channels;
|
||||
|
||||
// allocate memory for image data
|
||||
usz image_size = (usz)pixels * channels.id;
|
||||
usz image_size = (usz)pixels * channels;
|
||||
char[] image = allocator::alloc_array(allocator, char, image_size);
|
||||
defer catch allocator::free(allocator, image);
|
||||
|
||||
for (loc = 0; loc < image_size; loc += channels.id)
|
||||
for (loc = 0; loc < image_size; loc += channels)
|
||||
{
|
||||
// get chunk tag
|
||||
tag = data[pos];
|
||||
@@ -391,31 +393,22 @@ const OP_RUN = 0b11;
|
||||
|
||||
struct Header @packed
|
||||
{
|
||||
uint be_magic; // magic bytes "qoif"
|
||||
uint be_width; // image width in pixels (BE)
|
||||
uint be_height; // image height in pixels (BE)
|
||||
<* magic bytes "qoif" *>
|
||||
uint be_magic;
|
||||
<* image width in pixels (BE) *>
|
||||
uint be_width;
|
||||
<* image height in pixels (BE) *>
|
||||
uint be_height;
|
||||
|
||||
// informative fields
|
||||
char channels; // 3 = RGB, 4 = RGB
|
||||
char colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
|
||||
<* 3 = RGB, 4 = RGB *>
|
||||
QOIChannels channels;
|
||||
<* 0 = sRGB with linear alpha, 1 = all channels linear *>
|
||||
QOIColorspace colorspace;
|
||||
}
|
||||
|
||||
const char[*] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1};
|
||||
|
||||
// inefficient, but it's only run once at a time
|
||||
|
||||
<*
|
||||
@return? INVALID_DATA
|
||||
*>
|
||||
macro @enumcast($Type, raw)
|
||||
{
|
||||
foreach (value : $Type.values)
|
||||
{
|
||||
if (value.id == raw) return value;
|
||||
}
|
||||
return INVALID_DATA?;
|
||||
}
|
||||
|
||||
typedef Pixel = inline char[<4>];
|
||||
macro char Pixel.hash(Pixel p)
|
||||
{
|
||||
|
||||
@@ -90,12 +90,12 @@ fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz a
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
|
||||
if (size > total_len) return mem::INVALID_ALLOC_SIZE~;
|
||||
void* start_mem = self.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof;
|
||||
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(mem - self.data.ptr) + size;
|
||||
if (end > total_len) return mem::OUT_OF_MEMORY?;
|
||||
if (end > total_len) return mem::OUT_OF_MEMORY~;
|
||||
self.used = end;
|
||||
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
|
||||
header.size = size;
|
||||
@@ -117,7 +117,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
|
||||
if (size > total_len) return mem::INVALID_ALLOC_SIZE~;
|
||||
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
// Do last allocation and alignment match?
|
||||
@@ -130,7 +130,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
|
||||
else
|
||||
{
|
||||
usz new_used = self.used + size - old_size;
|
||||
if (new_used > total_len) return mem::OUT_OF_MEMORY?;
|
||||
if (new_used > total_len) return mem::OUT_OF_MEMORY~;
|
||||
self.used = new_used;
|
||||
}
|
||||
header.size = size;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ fn void*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignm
|
||||
}
|
||||
|
||||
AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
mem::copy(data, pointer, math::min(size, chunk.size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import std::math;
|
||||
|
||||
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.
|
||||
memory from that memory), whereas the BackedArenaAllocator will have heap allocator characteristics.
|
||||
*>
|
||||
struct DynamicArenaAllocator (Allocator)
|
||||
{
|
||||
@@ -117,7 +117,7 @@ fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a
|
||||
return old_pointer;
|
||||
}
|
||||
void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
mem::copy(new_mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return new_mem;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ fn void*? DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @loca
|
||||
if (catch err = page)
|
||||
{
|
||||
allocator::free(self.backing_allocator, mem);
|
||||
return err?;
|
||||
return err~;
|
||||
}
|
||||
page.memory = mem;
|
||||
void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment);
|
||||
|
||||
@@ -8,12 +8,9 @@ import libc;
|
||||
<*
|
||||
The LibcAllocator is a wrapper around malloc to conform to the Allocator interface.
|
||||
*>
|
||||
typedef LibcAllocator (Allocator, Printable) = uptr;
|
||||
typedef LibcAllocator (Allocator) = uptr;
|
||||
const LibcAllocator LIBC_ALLOCATOR = {};
|
||||
|
||||
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
|
||||
fn usz? LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
|
||||
|
||||
module std::core::mem::allocator @if(env::POSIX);
|
||||
import std::os;
|
||||
import libc;
|
||||
@@ -26,22 +23,22 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
|
||||
void* data @noinit;
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY~;
|
||||
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
}
|
||||
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
|
||||
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
else
|
||||
{
|
||||
void* data @noinit;
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(data = libc::malloc(bytes))) return mem::OUT_OF_MEMORY?;
|
||||
if (!(data = libc::malloc(bytes))) return mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
@@ -52,9 +49,9 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
|
||||
|
||||
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
|
||||
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~;
|
||||
void* new_ptr;
|
||||
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return mem::OUT_OF_MEMORY?;
|
||||
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return mem::OUT_OF_MEMORY~;
|
||||
|
||||
$switch:
|
||||
$case env::DARWIN:
|
||||
@@ -86,12 +83,12 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
|
||||
{
|
||||
if (alignment > 0)
|
||||
{
|
||||
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY?;
|
||||
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
|
||||
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
|
||||
if (!data) return mem::OUT_OF_MEMORY?;
|
||||
if (!data) return mem::OUT_OF_MEMORY~;
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
@@ -102,9 +99,9 @@ fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignmen
|
||||
{
|
||||
if (alignment)
|
||||
{
|
||||
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY?;
|
||||
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
|
||||
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
|
||||
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
@@ -125,12 +122,12 @@ 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_MEMORY?;
|
||||
return data ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
else
|
||||
{
|
||||
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
|
||||
if (!data) return mem::OUT_OF_MEMORY?;
|
||||
if (!data) return mem::OUT_OF_MEMORY~;
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
@@ -144,9 +141,9 @@ fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignmen
|
||||
if (alignment)
|
||||
{
|
||||
void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!;
|
||||
return data ?: mem::OUT_OF_MEMORY?;
|
||||
return data ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
|
||||
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module std::core::mem::allocator;
|
||||
|
||||
import std::math;
|
||||
<*
|
||||
The OnStackAllocator is similar to the ArenaAllocator: it allocates from a chunk of memory
|
||||
given to it.
|
||||
@@ -124,7 +124,7 @@ fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignm
|
||||
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
mem::copy(mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
module std::core::mem::allocator;
|
||||
module std::core::mem::allocator @if(!(env::POSIX || env::WIN32) || !$feature(VMEM_TEMP));
|
||||
import std::io, std::math;
|
||||
import std::core::sanitizer::asan;
|
||||
|
||||
// This implements the temp allocator.
|
||||
// The temp allocator is a specialized allocator only intended for use where
|
||||
@@ -327,3 +326,81 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
|
||||
return &page.data[0];
|
||||
}
|
||||
|
||||
module std::core::mem::allocator @if((env::POSIX || env::WIN32) && $feature(VMEM_TEMP));
|
||||
import std::math;
|
||||
|
||||
tlocal VmemOptions temp_allocator_default_options = {
|
||||
.shrink_on_reset = env::MEMORY_ENV != NORMAL,
|
||||
.protect_unused_pages = env::COMPILER_OPT_LEVEL <= O1 || env::COMPILER_SAFE_MODE,
|
||||
.scratch_released_data = env::COMPILER_SAFE_MODE
|
||||
};
|
||||
|
||||
|
||||
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve = temp_allocator_reserve_size, usz min_size = temp_allocator_min_size, usz realloc_size = temp_allocator_realloc_size)
|
||||
{
|
||||
Vmem mem;
|
||||
TempAllocator* t = allocator::new(allocator, TempAllocator);
|
||||
defer catch allocator::free(allocator, t);
|
||||
t.vmem.init(preferred_size: isz.sizeof > 4 ? 4 * mem::GB : 512 * mem::MB,
|
||||
reserve_page_size: isz.sizeof > 4 ? 256 * mem::KB : 0,
|
||||
options: temp_allocator_default_options)!;
|
||||
t.allocator = allocator;
|
||||
return t;
|
||||
}
|
||||
|
||||
struct TempAllocator (Allocator)
|
||||
{
|
||||
Vmem vmem;
|
||||
TempAllocator* derived;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
<*
|
||||
@require size > 0
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
*>
|
||||
fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
return self.vmem.acquire(size, init_type, alignment) @inline;
|
||||
}
|
||||
|
||||
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0)
|
||||
{
|
||||
if (self.derived) return self.derived;
|
||||
return self.derived = new_temp_allocator(self.allocator, 0)!;
|
||||
}
|
||||
|
||||
<*
|
||||
Reset the entire temp allocator, destroying all children
|
||||
*>
|
||||
fn void TempAllocator.reset(&self)
|
||||
{
|
||||
TempAllocator* child = self.derived;
|
||||
if (!child) return;
|
||||
child.reset();
|
||||
child.vmem.reset(0);
|
||||
}
|
||||
fn void TempAllocator.free(&self)
|
||||
{
|
||||
self.destroy();
|
||||
}
|
||||
|
||||
fn void TempAllocator.destroy(&self) @local
|
||||
{
|
||||
TempAllocator* child = self.derived;
|
||||
if (!child) return;
|
||||
child.destroy();
|
||||
self.vmem.free() @inline;
|
||||
allocator::free(self.allocator, self) @inline;
|
||||
}
|
||||
|
||||
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
return self.vmem.resize(pointer, size, alignment) @inline;
|
||||
}
|
||||
|
||||
fn void TempAllocator.release(&self, void* old_pointer, bool b) @dynamic
|
||||
{
|
||||
self.vmem.release(old_pointer, b) @inline;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ alias AllocMap = HashMap { uptr, Allocation };
|
||||
// It tracks allocations using a hash map but
|
||||
// is not compatible with allocators that uses mark()
|
||||
//
|
||||
// It is also embarassingly single-threaded, so
|
||||
// It is also embarrassingly single-threaded, so
|
||||
// do not use it to track allocations that cross threads.
|
||||
|
||||
struct TrackingAllocator (Allocator)
|
||||
@@ -28,6 +28,8 @@ struct TrackingAllocator (Allocator)
|
||||
AllocMap map;
|
||||
usz mem_total;
|
||||
usz allocs_total;
|
||||
usz usage;
|
||||
usz max_usage;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -70,6 +72,11 @@ fn usz TrackingAllocator.total_allocated(&self) => self.mem_total;
|
||||
*>
|
||||
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
|
||||
|
||||
<*
|
||||
@return "the maximum amount of memory allocated"
|
||||
*>
|
||||
fn usz TrackingAllocator.max_allocated(&self) => self.max_usage;
|
||||
|
||||
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
|
||||
{
|
||||
return self.map.tvalues();
|
||||
@@ -88,27 +95,34 @@ fn void*? TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, us
|
||||
backtrace::capture_current(&bt);
|
||||
self.map.set((uptr)data, { data, size, bt });
|
||||
self.mem_total += size;
|
||||
self.usage += size;
|
||||
if (self.usage > self.max_usage) self.max_usage = self.usage;
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*? TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
void* data = self.inner_allocator.resize(old_pointer, size, alignment)!;
|
||||
self.usage -= self.map[(uptr)old_pointer]!!.size;
|
||||
self.map.remove((uptr)old_pointer);
|
||||
void*[MAX_BACKTRACE] bt;
|
||||
backtrace::capture_current(&bt);
|
||||
self.map.set((uptr)data, { data, size, bt });
|
||||
self.mem_total += size;
|
||||
self.usage += size;
|
||||
if (self.usage > self.max_usage) self.max_usage = self.usage;
|
||||
self.allocs_total++;
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
|
||||
{
|
||||
usz? old_size = self.map[(uptr)old_pointer].size;
|
||||
if (catch self.map.remove((uptr)old_pointer))
|
||||
{
|
||||
unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
|
||||
}
|
||||
self.usage -= old_size!!;
|
||||
self.inner_allocator.release(old_pointer, is_aligned);
|
||||
}
|
||||
|
||||
@@ -177,6 +191,7 @@ fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
|
||||
io::fprintfn(out, "- Total current allocations: %d", entries)!;
|
||||
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
|
||||
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
|
||||
io::fprintfn(out, "- Maximum memory usage: %d", self.max_usage)!;
|
||||
if (leaks)
|
||||
{
|
||||
io::fprintn(out)!;
|
||||
@@ -216,4 +231,4 @@ fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
255
lib/std/core/allocators/vmem.c3
Normal file
255
lib/std/core/allocators/vmem.c3
Normal file
@@ -0,0 +1,255 @@
|
||||
module std::core::mem::allocator @if(env::POSIX || env::WIN32);
|
||||
import std::math, std::os::posix, libc, std::bits;
|
||||
import std::core::mem;
|
||||
import std::core::env;
|
||||
|
||||
|
||||
|
||||
// Virtual Memory allocator
|
||||
|
||||
faultdef VMEM_RESERVE_FAILED;
|
||||
|
||||
struct Vmem (Allocator)
|
||||
{
|
||||
VirtualMemory memory;
|
||||
usz allocated;
|
||||
usz pagesize;
|
||||
usz page_pot;
|
||||
usz last_page;
|
||||
usz high_water;
|
||||
VmemOptions options;
|
||||
}
|
||||
|
||||
bitstruct VmemOptions : int
|
||||
{
|
||||
<* Release memory on reset *>
|
||||
bool shrink_on_reset;
|
||||
<* Protect unused pages on reset *>
|
||||
bool protect_unused_pages;
|
||||
<* Overwrite released data with 0xAA *>
|
||||
bool scratch_released_data;
|
||||
}
|
||||
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require !reserve_page_size || math::is_power_of_2(reserve_page_size)
|
||||
@require reserve_page_size <= preferred_size : "The min reserve_page_size size must be less or equal to the preferred size"
|
||||
@require preferred_size >= 1 * mem::KB : "The preferred size must exceed 1 KB"
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY, VMEM_RESERVE_FAILED
|
||||
*>
|
||||
fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOptions options = { true, true, env::COMPILER_SAFE_MODE }, usz min_size = 0)
|
||||
{
|
||||
static usz page_size = 0;
|
||||
if (!page_size) page_size = mem::os_pagesize();
|
||||
if (page_size < reserve_page_size) page_size = reserve_page_size;
|
||||
preferred_size = mem::aligned_offset(preferred_size, page_size);
|
||||
if (!min_size) min_size = max(preferred_size / 1024, 1);
|
||||
VirtualMemory? memory = mem::OUT_OF_MEMORY~;
|
||||
while (preferred_size >= min_size)
|
||||
{
|
||||
memory = vm::virtual_alloc(preferred_size, PROTECTED);
|
||||
// It worked?
|
||||
if (try memory) break;
|
||||
switch (@catch(memory))
|
||||
{
|
||||
case mem::OUT_OF_MEMORY:
|
||||
case vm::RANGE_OVERFLOW:
|
||||
// Try a smaller size.
|
||||
preferred_size /= 2;
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (catch memory) return VMEM_RESERVE_FAILED~;
|
||||
if (page_size > preferred_size) page_size = preferred_size;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(memory.ptr, memory.size);
|
||||
$endif
|
||||
*self = { .memory = memory,
|
||||
.high_water = 0,
|
||||
.pagesize = page_size,
|
||||
.page_pot = page_size.ctz(),
|
||||
.options = options,
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require size > 0
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*? Vmem.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = self.memory.size;
|
||||
if (size > total_len) return mem::INVALID_ALLOC_SIZE~;
|
||||
void* start_mem = self.memory.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.allocated + VmemHeader.sizeof;
|
||||
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz after = (usz)(mem - start_mem) + size;
|
||||
if (after > total_len) return mem::OUT_OF_MEMORY~;
|
||||
if (init_type == ZERO && self.high_water <= self.allocated)
|
||||
{
|
||||
init_type = NO_ZERO;
|
||||
}
|
||||
protect(self, after)!;
|
||||
VmemHeader* header = mem - VmemHeader.sizeof;
|
||||
header.size = size;
|
||||
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
fn bool Vmem.owns_pointer(&self, void* ptr) @inline
|
||||
{
|
||||
return (uptr)ptr >= (uptr)self.memory.ptr && (uptr)ptr < (uptr)self.memory.ptr + self.memory.size;
|
||||
}
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require old_pointer != null
|
||||
@require size > 0
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
if (size > self.memory.size) return mem::INVALID_ALLOC_SIZE~;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
assert(self.owns_pointer(old_pointer), "Pointer originates from a different allocator: %p, not in %p - %p", old_pointer, self.memory.ptr, self.memory.ptr + self.allocated);
|
||||
VmemHeader* header = old_pointer - VmemHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
if (old_size == size) return old_pointer;
|
||||
// Do last allocation and alignment match?
|
||||
if (self.memory.ptr + self.allocated == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
if (old_size > size)
|
||||
{
|
||||
unprotect(self, self.allocated + size - old_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
usz allocated = self.allocated + size - old_size;
|
||||
if (allocated > self.memory.size) return mem::OUT_OF_MEMORY~;
|
||||
protect(self, allocated)!;
|
||||
}
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
}
|
||||
if (old_size > size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(old_pointer + size, old_size - size);
|
||||
$endif
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
}
|
||||
// Otherwise just allocate new memory.
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
assert(size > old_size);
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require ptr != null
|
||||
*>
|
||||
fn void Vmem.release(&self, void* ptr, bool) @dynamic
|
||||
{
|
||||
assert(self.owns_pointer(ptr), "Pointer originates from a different allocator %p.", ptr);
|
||||
VmemHeader* header = ptr - VmemHeader.sizeof;
|
||||
// Reclaim memory if it's the last element.
|
||||
if (ptr + header.size == self.memory.ptr + self.allocated)
|
||||
{
|
||||
unprotect(self, self.allocated - header.size - VmemHeader.sizeof);
|
||||
}
|
||||
}
|
||||
|
||||
fn usz Vmem.mark(&self)
|
||||
{
|
||||
return self.allocated;
|
||||
}
|
||||
|
||||
<*
|
||||
@require mark <= self.allocated : "Invalid mark"
|
||||
*>
|
||||
fn void Vmem.reset(&self, usz mark)
|
||||
{
|
||||
if (mark == self.allocated) return;
|
||||
unprotect(self, mark);
|
||||
}
|
||||
|
||||
fn void Vmem.free(&self)
|
||||
{
|
||||
if (!self.memory.ptr) return;
|
||||
$switch:
|
||||
$case env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(self.memory.ptr, self.memory.size);
|
||||
$case env::COMPILER_SAFE_MODE:
|
||||
((char*)self.memory.ptr)[0:self.allocated] = 0xAA;
|
||||
$endswitch
|
||||
(void)self.memory.destroy();
|
||||
*self = {};
|
||||
}
|
||||
|
||||
// Internal data
|
||||
|
||||
struct VmemHeader @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
macro void? protect(Vmem* mem, usz after) @local
|
||||
{
|
||||
usz shift = mem.page_pot;
|
||||
usz page_after = (after + mem.pagesize - 1) >> shift;
|
||||
usz last_page = mem.last_page;
|
||||
bool over_high_water = mem.high_water < after;
|
||||
if (page_after > last_page)
|
||||
{
|
||||
usz page_start = last_page << shift;
|
||||
usz page_len = (page_after - last_page) << shift;
|
||||
mem.memory.commit(page_start, page_len)!;
|
||||
if (mem.options.protect_unused_pages || over_high_water)
|
||||
{
|
||||
mem.memory.protect(page_start, page_len, READWRITE)!;
|
||||
}
|
||||
mem.last_page = page_after;
|
||||
}
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(mem.memory.ptr + mem.allocated, after - mem.allocated);
|
||||
$endif
|
||||
mem.allocated = after;
|
||||
if (over_high_water) mem.high_water = after;
|
||||
}
|
||||
|
||||
macro void unprotect(Vmem* mem, usz after) @local
|
||||
{
|
||||
usz shift = mem.page_pot;
|
||||
usz last_page = mem.last_page;
|
||||
usz page_after = mem.last_page = (after + mem.pagesize - 1) >> shift;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(mem.memory.ptr + after, mem.allocated - after);
|
||||
$else
|
||||
if (mem.options.scratch_released_data)
|
||||
{
|
||||
mem::set(mem.memory.ptr + after, 0xAA, mem.allocated - after);
|
||||
}
|
||||
$endif
|
||||
if ((mem.options.shrink_on_reset || mem.options.protect_unused_pages) && page_after < last_page)
|
||||
{
|
||||
usz start = page_after << shift;
|
||||
usz len = (last_page - page_after) << shift;
|
||||
if (mem.options.shrink_on_reset) (void)mem.memory.decommit(start, len, false);
|
||||
if (mem.options.protect_unused_pages) (void)mem.memory.protect(start, len, PROTECTED);
|
||||
}
|
||||
mem.allocated = after;
|
||||
}
|
||||
165
lib/std/core/ansi.c3
Normal file
165
lib/std/core/ansi.c3
Normal file
@@ -0,0 +1,165 @@
|
||||
module std::core::string::ansi;
|
||||
import std::io;
|
||||
|
||||
enum Ansi : const inline String
|
||||
{
|
||||
RESET = "\e[0m",
|
||||
BOLD = "\e[1m",
|
||||
DIM = "\e[2m",
|
||||
ITALIC = "\e[3m",
|
||||
UNDERLINE = "\e[4m",
|
||||
BLINK = "\e[5m",
|
||||
BLINK_FAST = "\e[6m",
|
||||
INVERT = "\e[7m",
|
||||
HIDDEN = "\e[8m",
|
||||
STRIKETHROUGH = "\e[9m",
|
||||
DOUBLE_UNDER = "\e[21m",
|
||||
NO_DIM = "\e[22m",
|
||||
NO_ITALIC = "\e[23m",
|
||||
NO_UNDERLINE = "\e[24m",
|
||||
NO_BLINK = "\e[25m",
|
||||
NO_INVERT = "\e[27m",
|
||||
NO_HIDDEN = "\e[28m",
|
||||
NO_STRIKETHROUGH = "\e[29m",
|
||||
BLACK = "\e[30m",
|
||||
RED = "\e[31m",
|
||||
GREEN = "\e[32m",
|
||||
YELLOW = "\e[33m",
|
||||
BLUE = "\e[34m",
|
||||
MAGENTA = "\e[35m",
|
||||
CYAN = "\e[36m",
|
||||
WHITE = "\e[37m",
|
||||
DEFAULT = "\e[39m",
|
||||
BRIGHT_BLACK = "\e[90m",
|
||||
BRIGHT_RED = "\e[91m",
|
||||
BRIGHT_GREEN = "\e[92m",
|
||||
BRIGHT_YELLOW = "\e[93m",
|
||||
BRIGHT_BLUE = "\e[94m",
|
||||
BRIGHT_MAGENTA = "\e[95m",
|
||||
BRIGHT_CYAN = "\e[96m",
|
||||
BRIGHT_WHITE = "\e[97m",
|
||||
BG_BLACK = "\e[40m",
|
||||
BG_RED = "\e[41m",
|
||||
BG_GREEN = "\e[42m",
|
||||
BG_YELLOW = "\e[43m",
|
||||
BG_BLUE = "\e[44m",
|
||||
BG_MAGENTA = "\e[45m",
|
||||
BG_CYAN = "\e[46m",
|
||||
BG_WHITE = "\e[47m",
|
||||
BG_DEFAULT = "\e[49m",
|
||||
BG_BRIGHT_BLACK = "\e[100m",
|
||||
BG_BRIGHT_RED = "\e[101m",
|
||||
BG_BRIGHT_GREEN = "\e[102m",
|
||||
BG_BRIGHT_YELLOW = "\e[103m",
|
||||
BG_BRIGHT_BLUE = "\e[104m",
|
||||
BG_BRIGHT_MAGENTA = "\e[105m",
|
||||
BG_BRIGHT_CYAN = "\e[106m",
|
||||
BG_BRIGHT_WHITE = "\e[107m",
|
||||
}
|
||||
|
||||
struct AnsiColor (Printable)
|
||||
{
|
||||
char r, g, b;
|
||||
bool bg;
|
||||
}
|
||||
|
||||
fn usz? AnsiColor.to_format(&self, Formatter* fmt) @dynamic
|
||||
{
|
||||
return fmt.printf("\e[%s8;2;%s;%s;%sm", self.bg ? 4 : 3, self.r, self.g, self.b);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code
|
||||
|
||||
@return `A struct that, when formatted with '%s', sets the foreground or background colour to the specified rgb value`
|
||||
*>
|
||||
fn AnsiColor get_color_rgb(char r, char g, char b, bool bg = false)
|
||||
{
|
||||
return {r, g, b, bg};
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code
|
||||
|
||||
@return `A struct that, when formatted with '%s', sets the foreground or background colour of printed text to the specified rgb value`
|
||||
*>
|
||||
fn AnsiColor get_color(uint rgb, bool bg = false)
|
||||
{
|
||||
return {(char)(rgb >> 16), (char)((rgb & 0x00FF00) >> 8), (char)rgb, bg};
|
||||
}
|
||||
|
||||
<*
|
||||
8-bit color code
|
||||
|
||||
@return `the formatting char for the given background color`
|
||||
*>
|
||||
macro String color_8bit(char $index, bool $bg = false) @const
|
||||
{
|
||||
int $mode = $bg ? 4 : 3;
|
||||
return @sprintf("\e[%s8;5;%sm", $mode, $index);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code
|
||||
|
||||
@return `the string for the given foreground color`
|
||||
*>
|
||||
macro String color_rgb(char $r, char $g, char $b, bool $bg = false) @const
|
||||
{
|
||||
int $mode = $bg ? 4 : 3;
|
||||
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $r, $g, $b);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@require $rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
macro String color(uint $rgb, bool $bg = false) @const
|
||||
{
|
||||
int $mode = $bg ? 4 : 3;
|
||||
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $rgb >> 16, ($rgb & 0xFF00) >> 8, $rgb & 0xFF);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_color(Allocator mem, uint rgb, bool bg = false) @deprecated("use get_color instead")
|
||||
{
|
||||
return make_color_rgb(mem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_tcolor(uint rgb, bool bg = false) @deprecated("use get_color instead")
|
||||
{
|
||||
return make_color_rgb(tmem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_color_rgb(Allocator mem, char r, char g, char b, bool bg = false) @deprecated("use get_color_rgb instead")
|
||||
{
|
||||
return string::format(mem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_tcolor_rgb(char r, char g, char b, bool bg = false) @deprecated("use get_color_rgb instead")
|
||||
{
|
||||
return string::format(tmem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
module std::core::array;
|
||||
import std::core::array::slice;
|
||||
import std::collections::pair, std::io;
|
||||
|
||||
<*
|
||||
Returns true if the array contains at least one element, else false
|
||||
|
||||
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@require @typekind(array) == SLICE || @typekind(array) == ARRAY
|
||||
@require @typeis(array[0], $typeof(element)) : "array and element must have the same type"
|
||||
@require $kindof(array) == SLICE || $kindof(array) == ARRAY
|
||||
@require @typematch(array[0], element) : "array and element must have the same type"
|
||||
*>
|
||||
macro bool contains(array, element)
|
||||
{
|
||||
@@ -15,26 +15,30 @@ macro bool contains(array, element)
|
||||
{
|
||||
if (*item == element) return true;
|
||||
}
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Return the first index of element found in the array, searching from the start.
|
||||
|
||||
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@require $kindof(array) == SLICE || $kindof(array) == ARRAY
|
||||
@require @typematch(array[0], element) : "array and element must have the same type"
|
||||
@return "the first index of the element"
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro index_of(array, element)
|
||||
macro usz? index_of(array, element)
|
||||
{
|
||||
foreach (i, &e : array)
|
||||
{
|
||||
if (*e == element) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Slice a 2d array and create a Slice2d from it.
|
||||
|
||||
@@ -44,9 +48,9 @@ macro index_of(array, element)
|
||||
@param xlen : "The length of the slice in x, defaults to the length of the array"
|
||||
@param ylen : "The length of the slice in y, defaults to the length of the array"
|
||||
@return "A Slice2d from the array"
|
||||
@require @typekind(array_ptr) == POINTER
|
||||
@require @typekind(*array_ptr) == VECTOR || @typekind(*array_ptr) == ARRAY
|
||||
@require @typekind((*array_ptr)[0]) == VECTOR || @typekind((*array_ptr)[0]) == ARRAY
|
||||
@require $kindof(array_ptr) == POINTER
|
||||
@require $kindof(*array_ptr) == VECTOR || $kindof(*array_ptr) == ARRAY
|
||||
@require $kindof((*array_ptr)[0]) == VECTOR || $kindof((*array_ptr)[0]) == ARRAY
|
||||
*>
|
||||
macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
|
||||
{
|
||||
@@ -59,30 +63,31 @@ macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
|
||||
|
||||
<*
|
||||
Return the first index of element found in the array, searching in reverse from the end.
|
||||
|
||||
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@return "the last index of the element"
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro rindex_of(array, element)
|
||||
macro usz? rindex_of(array, element)
|
||||
{
|
||||
foreach_r (i, &e : array)
|
||||
{
|
||||
if (*e == element) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
|
||||
|
||||
@param [in] arr1
|
||||
@param [in] arr2
|
||||
@param [&inout] allocator : "The allocator to use, default is the heap allocator"
|
||||
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
|
||||
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
|
||||
@require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
|
||||
@require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY
|
||||
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY
|
||||
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type"
|
||||
@ensure result.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro concat(Allocator allocator, arr1, arr2) @nodiscard
|
||||
@@ -99,15 +104,450 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice containing the concatenation of them,
|
||||
allocated using the temp allocator.
|
||||
|
||||
@param [in] arr1
|
||||
@param [in] arr2
|
||||
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
|
||||
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
|
||||
@require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
|
||||
@require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY
|
||||
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY
|
||||
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type"
|
||||
@ensure return.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
|
||||
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
|
||||
|
||||
|
||||
<*
|
||||
Apply a reduction/folding operation to an iterable type. This walks along the input array
|
||||
and applies an `#operation` to each value, returning it to the `identity` (or "accumulator")
|
||||
base value.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
int[] my_slice = { 1, 8, 12 };
|
||||
int folded = array::@reduce(my_slice, 2, fn (i, e) => i * e);
|
||||
assert(folded == (2 * 1 * 8 * 12));
|
||||
```
|
||||
|
||||
Notice how the given `identity` value started the multiplication chain at 2. When enumerating
|
||||
`my_slice`, each element is accumulated onto the `identity` value with each sequential iteration.
|
||||
```
|
||||
i = 2; // identity value
|
||||
i *= 1; // my_slice[0]
|
||||
i *= 8; // my_slice[1]
|
||||
i *= 12; // my_slice[2]
|
||||
```
|
||||
|
||||
@param [in] array
|
||||
@param identity
|
||||
@param #operation : "The reduction/folding lambda function or function pointer to apply."
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@reduce_fn(array, identity)) $func = #operation) : "Invalid lambda or function pointer type"
|
||||
*>
|
||||
macro @reduce(array, identity, #operation)
|
||||
{
|
||||
$typefrom(@reduce_fn(array, identity)) $func = #operation;
|
||||
foreach (index, element : array) identity = $func(identity, element, index);
|
||||
return identity;
|
||||
}
|
||||
|
||||
<*
|
||||
Apply a summation operator (+) to an identity value across a span of array elements
|
||||
and return the final accumulated result.
|
||||
|
||||
@pure
|
||||
|
||||
@param [in] array
|
||||
@param identity_value : "The base accumulator value to use for the sum"
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined(array[0] + array[0]) : "Array element type must implement the '+' operator"
|
||||
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
|
||||
*>
|
||||
macro @sum(array, identity_value = 0)
|
||||
{
|
||||
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc + e);
|
||||
}
|
||||
|
||||
<*
|
||||
Apply a product operator (*) to an identity value across a span of array elements
|
||||
and return the final accumulated result.
|
||||
|
||||
@pure
|
||||
|
||||
@param [in] array
|
||||
@param identity_value : "The base accumulator value to use for the product"
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined(array[0] * array[0]) : "Array element type must implement the '*' operator"
|
||||
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
|
||||
*>
|
||||
macro @product(array, identity_value = 1)
|
||||
{
|
||||
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc * e);
|
||||
}
|
||||
|
||||
<*
|
||||
Applies a given predicate function to each element of an array and returns a new
|
||||
array of `usz` values, each element representing an index within the original array
|
||||
where the predicate returned `true`.
|
||||
|
||||
The `.len` value of the returned array can also be used to quickly identify how many
|
||||
input array elements matched the predicate.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
int[] arr = { 0, 20, 4, 30 };
|
||||
int[] matched_indices = array::@indices_of(mem, arr, fn (u, a) => a > 10);
|
||||
```
|
||||
|
||||
The `matched_indices` variable should contain a dynamically-allocated array of `[1, 3]`,
|
||||
and thus its count indicates that 2 of the 4 elements matched the predicate condition.
|
||||
|
||||
@param [&inout] allocator
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro usz[] @indices_of(Allocator allocator, array, #predicate)
|
||||
{
|
||||
usz[] results = allocator::new_array(allocator, usz, lengthof(array));
|
||||
usz matches;
|
||||
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array)
|
||||
{
|
||||
if ($predicate(element, index)) results[matches++] = index;
|
||||
}
|
||||
|
||||
return results[:matches];
|
||||
}
|
||||
|
||||
<*
|
||||
Array `@indices_of` using the temp allocator.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro usz[] @tindices_of(array, #predicate)
|
||||
{
|
||||
return @indices_of(tmem, array, #predicate);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Applies a predicate function to each element of an input array and returns a new array
|
||||
containing shallow copies of _only_ the elements for which the predicate function returned
|
||||
a `true` value.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
int[] my_arr = { 1, 2, 4, 10, 11, 45 };
|
||||
int[] evens = array::@filter(mem, my_arr, fn (e, u) => !(e % 2));
|
||||
assert(evens == (int[]){2, 4, 10 });
|
||||
```
|
||||
|
||||
@param [&inout] allocator
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro @filter(Allocator allocator, array, #predicate) @nodiscard
|
||||
{
|
||||
var $InnerType = $typeof(array[0]);
|
||||
|
||||
usz[] matched_indices = @indices_of(allocator, array, #predicate);
|
||||
defer allocator::free(allocator, matched_indices.ptr); // can free this upon leaving this call
|
||||
|
||||
if (!matched_indices.len) return ($InnerType[]){};
|
||||
|
||||
$InnerType[] result = allocator::new_array(allocator, $InnerType, matched_indices.len);
|
||||
|
||||
foreach (i, index : matched_indices) result[i] = array[index];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Array `@filter` using the temp allocator.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro @tfilter(array, #predicate) @nodiscard
|
||||
{
|
||||
return @filter(tmem, array, #predicate);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns `true` if _any_ element of the input array returns `true` when
|
||||
the `#predicate` function is applied.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro bool @any(array, #predicate)
|
||||
{
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array) if ($predicate(element, index)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns `true` if _all_ elements of the input array return `true` when
|
||||
the `#predicate` function is applied.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro bool @all(array, #predicate)
|
||||
{
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array) if (!$predicate(element, index)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Zip together two separate arrays/slices into a single array of Pairs or return values. Values will
|
||||
be collected up to the length of the shorter array if `fill_with` is left undefined; otherwise, they
|
||||
will be collected up to the length of the LONGER array, with missing values in the shorter array being
|
||||
assigned to the value of `fill_with`. Return array elements do not have to be of the same type.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
uint[] chosen_session_ids = server::get_random_sessions(instance)[:128];
|
||||
String[200] refreshed_session_keys = prng::new_keys_batch();
|
||||
|
||||
Pair { uint, String }[] sessions_meta = array::zip(mem, chosen_session_ids, refreshed_session_keys);
|
||||
// The resulting Pair{}[] slice is then length of the shortest of the two arrays, so 128.
|
||||
|
||||
foreach (i, &sess : sessions:meta) {
|
||||
// distribute new session keys to associated instance IDs
|
||||
}
|
||||
```
|
||||
|
||||
Or:
|
||||
```c3
|
||||
String[] client_names = server::online_usernames(instance);
|
||||
uint128[] session_ids = server::user_keys();
|
||||
|
||||
// in this example, we 'know' ahead of time that 'session_ids' can only ever be SHORTER
|
||||
// than 'client_names', but never longer, because it's possible new users have logged
|
||||
// in without getting whatever this 'session ID' is delegated to them.
|
||||
Pair { String, uint128 }[] zipped = array::tzip(client_names, session_ids, fill_with: uint128.max);
|
||||
|
||||
server::refresh_session_keys_by_pair(zipped)!;
|
||||
```
|
||||
|
||||
### When an `operation` is supplied...
|
||||
Apply an operation to each element of two slices or arrays and return the results of
|
||||
each operation into a newly allocated array.
|
||||
|
||||
This essentially combines Iterable1 with Iterable2 using the `operation` functor.
|
||||
|
||||
See the functional `zipWith` construct, which has a more appropriate name than, e.g., `map`;
|
||||
a la: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#v:zipWith
|
||||
|
||||
Similar to "normal" `zip`, this macro pads the shorter input array with a given `fill_with`, or
|
||||
an empty value if one isn't supplied. This `fill_with` is supplied to the `operation` functor
|
||||
_BEFORE_ calculating its result while zipping.
|
||||
|
||||
For example: a functor of `fn char (char a, char b) => a + b` with a `fill_with` of 7,
|
||||
where the `left` array is the shorter iterable, will put 7 into that lambda in each place
|
||||
where `left` is being filled in during the zip operation.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use; default is the heap allocator."
|
||||
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
|
||||
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
|
||||
|
||||
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
|
||||
@require @is_valid_operation(left, right, ...#operation) : "The operator must take two parameters matching the elements of the left and right side"
|
||||
@require @is_valid_fill(left, right, ...fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
|
||||
*>
|
||||
macro @zip(Allocator allocator, left, right, #operation = ..., fill_with = ...) @nodiscard
|
||||
{
|
||||
var $LeftType = $typeof(left[0]);
|
||||
var $RightType = $typeof(right[0]);
|
||||
|
||||
var $Type = Pair { $LeftType, $RightType };
|
||||
bool $is_op = $defined(#operation);
|
||||
$if $is_op:
|
||||
$Type = $typeof(#operation).returns;
|
||||
$endif
|
||||
|
||||
usz left_len = lengthof(left);
|
||||
usz right_len = lengthof(right);
|
||||
|
||||
$LeftType left_fill;
|
||||
$RightType right_fill;
|
||||
usz result_len = min(left_len, right_len);
|
||||
$if $defined(fill_with):
|
||||
switch
|
||||
{
|
||||
case left_len > right_len:
|
||||
$if !$defined(($RightType)fill_with):
|
||||
unreachable();
|
||||
$else
|
||||
right_fill = ($RightType)fill_with;
|
||||
result_len = left_len;
|
||||
$endif
|
||||
case left_len < right_len:
|
||||
$if !$defined(($LeftType)fill_with):
|
||||
unreachable();
|
||||
$else
|
||||
left_fill = ($LeftType)fill_with;
|
||||
result_len = right_len;
|
||||
$endif
|
||||
}
|
||||
$endif
|
||||
|
||||
if (result_len == 0) return ($Type[]){};
|
||||
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, result_len);
|
||||
|
||||
foreach (idx, &item : result)
|
||||
{
|
||||
$if $is_op:
|
||||
var $LambdaType = $typeof(fn $Type ($LeftType a, $RightType b) => ($Type){});
|
||||
$LambdaType $operation = ($LambdaType)#operation;
|
||||
$LeftType lval = idx >= left_len ? left_fill : left[idx];
|
||||
$RightType rval = idx >= right_len ? right_fill : right[idx];
|
||||
*item = $operation(lval, rval);
|
||||
$else
|
||||
*item = {
|
||||
idx >= left_len ? left_fill : left[idx],
|
||||
idx >= right_len ? right_fill : right[idx]
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Array 'zip' using the temp allocator.
|
||||
|
||||
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
|
||||
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
|
||||
|
||||
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
|
||||
@require @is_valid_operation(left, right, ...#operation) : "The operator must take two parameters matching the elements of the left and right side"
|
||||
@require @is_valid_fill(left, right, ...fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
|
||||
*>
|
||||
macro @tzip(left, right, #operation = ..., fill_with = ...) @nodiscard
|
||||
{
|
||||
return @zip(tmem, left, right, #operation: ...#operation, fill_with: ...fill_with);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Apply an operation to each element of two slices or arrays and store the results of
|
||||
each operation into the 'left' value.
|
||||
|
||||
This is useful because no memory allocations are required in order to perform the operation.
|
||||
|
||||
A good example of using this might be using algorithmic transformations on data in-place:
|
||||
```
|
||||
char[] partial_cipher = get_next_plaintext_block();
|
||||
|
||||
array::@zip_into(
|
||||
partial_cipher[ENCRYPT_OFFSET:BASE_KEY.len],
|
||||
BASE_KEY,
|
||||
fn char (char a, char b) => a ^ (b * 5) % 37
|
||||
);
|
||||
```
|
||||
|
||||
This parameterizes the lambda function with left (`partial_cipher`) and right (`BASE_KEY`) slice
|
||||
elements and stores the end result in-place within the left slice. This is in contrast to a
|
||||
regular `zip_with` which will create a cloned final result and return it.
|
||||
|
||||
@param [inout] left : `Slice to store results of applied functor/operation.`
|
||||
@param [in] right : `Slice to apply in the functor/operation.`
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
|
||||
@require @is_valid_list(left) : "Expected a valid list"
|
||||
@require @is_valid_list(right) : "Expected a valid list"
|
||||
@require lengthof(right) >= lengthof(left) : `Right side length must be >= the destination (left) side length; consider using a sub-array of data for the assignment.`
|
||||
@require $defined($typefrom(@zip_into_fn(left, right)) x = #operation) : "The functor must use the same types as the `left` and `right` inputs, and return a value of the `left` type."
|
||||
*>
|
||||
macro @zip_into(left, right, #operation)
|
||||
{
|
||||
$typefrom(@zip_into_fn(left, right)) $operation = #operation;
|
||||
|
||||
foreach (i, &v : left) *v = $operation(left[i], right[i]);
|
||||
}
|
||||
|
||||
|
||||
// --- helper functions
|
||||
module std::core::array @private;
|
||||
|
||||
|
||||
macro typeid @predicate_fn(#array) @const
|
||||
{
|
||||
return $typeof(fn bool ($typeof(#array[0]) a, usz index = 0) => true).typeid;
|
||||
}
|
||||
|
||||
macro typeid @reduce_fn(#array, #identity) @const
|
||||
{
|
||||
return @typeid(fn $typeof(#identity) ($typeof(#identity) i, $typeof(#array[0]) a, usz index = 0) => i);
|
||||
}
|
||||
|
||||
macro typeid @zip_into_fn(#left, #right) @const
|
||||
{
|
||||
return @typeid(fn $typeof(#left[0]) ($typeof(#left[0]) l, $typeof(#right[0]) r) => l);
|
||||
}
|
||||
|
||||
macro bool @is_valid_operation(#left, #right, #operation = ...) @const
|
||||
{
|
||||
$switch:
|
||||
$case !$defined(#operation):
|
||||
return true;
|
||||
$case $kindof(#operation) != FUNC:
|
||||
return false;
|
||||
$default:
|
||||
return $defined(#operation(#left[0], #right[0]));
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool @is_valid_list(#expr) @const
|
||||
{
|
||||
return $defined(#expr[0], lengthof(#expr));
|
||||
}
|
||||
|
||||
macro bool @is_valid_fill(left, right, fill_with = ...)
|
||||
{
|
||||
$if !$defined(fill_with):
|
||||
return true;
|
||||
$else
|
||||
usz left_len = lengthof(left);
|
||||
usz right_len = lengthof(right);
|
||||
if (left_len == right_len) return true;
|
||||
return left_len > right_len ? $defined(($typeof(right[0]))fill_with) : $defined(($typeof(left[0]))fill_with);
|
||||
$endif
|
||||
}
|
||||
|
||||
@@ -112,3 +112,45 @@ const char[256] HEX_VALUE = {
|
||||
const char[256] TO_UPPER @private = { ['a'..'z'] = 'a' - 'A' };
|
||||
const char[256] TO_LOWER @private = { ['A'..'Z'] = 'a' - 'A' };
|
||||
|
||||
typedef AsciiCharset = uint128;
|
||||
|
||||
macro AsciiCharset @create_set(String $string) @const
|
||||
{
|
||||
AsciiCharset $set;
|
||||
$foreach $c : $string:
|
||||
$set |= 1ULL << $c;
|
||||
$endforeach
|
||||
return $set;
|
||||
}
|
||||
|
||||
fn AsciiCharset create_set(String string)
|
||||
{
|
||||
AsciiCharset set;
|
||||
foreach (c : string) set |= (AsciiCharset)1ULL << c;
|
||||
return set;
|
||||
}
|
||||
|
||||
macro bool AsciiCharset.@contains($set, char $c) @const => !!($c < 128) & !!($set & (AsciiCharset)(1ULL << $c));
|
||||
|
||||
macro AsciiCharset @combine_sets(AsciiCharset $first, AsciiCharset... $sets) @const
|
||||
{
|
||||
var $res = $first;
|
||||
$foreach $c : $sets:
|
||||
$res |= $c;
|
||||
$endforeach
|
||||
return $res;
|
||||
}
|
||||
fn AsciiCharset combine_sets(AsciiCharset first, AsciiCharset... sets)
|
||||
{
|
||||
foreach (c : sets) first |= c;
|
||||
return first;
|
||||
}
|
||||
|
||||
macro bool AsciiCharset.contains(set, char c) => !!(c < 128) & !!(set & (AsciiCharset)(1ULL << c));
|
||||
|
||||
const AsciiCharset WHITESPACE_SET = @create_set("\t\n\v\f\r ");
|
||||
const AsciiCharset NUMBER_SET = @create_set("0123456789");
|
||||
const AsciiCharset ALPHA_UPPER_SET = @create_set("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
const AsciiCharset ALPHA_LOWER_SET = @create_set("abcdefghijklmnopqrstuvwxyz");
|
||||
const AsciiCharset ALPHA_SET = @combine_sets(ALPHA_UPPER_SET, ALPHA_LOWER_SET);
|
||||
const AsciiCharset ALPHANUMERIC_SET = @combine_sets(ALPHA_SET, NUMBER_SET);
|
||||
|
||||
@@ -90,36 +90,38 @@ 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_bitorder($Type) : "type must be a bitorder integer"
|
||||
@require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is too short to contain value"
|
||||
*>
|
||||
macro read(bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch @typekind(bytes):
|
||||
char *ptr;
|
||||
$switch $kindof(bytes):
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
ptr = bytes;
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
ptr = bytes[..].ptr;
|
||||
$endswitch
|
||||
return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
|
||||
return bitcast(mem::load((char[$Type.sizeof]*)ptr, $align: 1), $Type).val;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_arrayptr_or_slice_of_char(bytes) : "argument must be a pointer to an array or a slice of char"
|
||||
@require is_bitorder($Type) : "type must be a bitorder integer"
|
||||
@require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is not sufficent to hold value"
|
||||
*>
|
||||
macro write(x, bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch @typekind(bytes):
|
||||
char *ptr;
|
||||
$switch $kindof(bytes):
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
ptr = bytes;
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
ptr = bytes[..].ptr;
|
||||
$endswitch
|
||||
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
|
||||
mem::store(($typeof(x)*)ptr, bitcast(x, $Type).val, 1);
|
||||
}
|
||||
|
||||
macro is_bitorder($Type)
|
||||
macro bool is_bitorder($Type)
|
||||
{
|
||||
$switch $Type:
|
||||
$case UShortLE:
|
||||
@@ -181,4 +183,4 @@ macro bool @is_arrayptr_or_slice_of_char(#bytes) @const
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import libc, std::hash, std::io, std::os::backtrace;
|
||||
|
||||
<*
|
||||
EMPTY_MACRO_SLOT is a value used for implementing optional arguments for macros in an efficient
|
||||
way. It relies on the fact that distinct types are not implicitly convertable.
|
||||
way. It relies on the fact that distinct types are not implicitly convertible.
|
||||
|
||||
You can use `@is_empty_macro_slot()` and `@is_valid_macro_slot()` to figure out whether
|
||||
the argument has been used or not.
|
||||
@@ -18,17 +18,21 @@ import libc, std::hash, std::io, std::os::backtrace;
|
||||
macro foo(a, #b = EMPTY_MACRO_SLOT)
|
||||
{
|
||||
$if @is_valid_macro_slot(#b):
|
||||
return invoke_foo2(a, #b);
|
||||
$else
|
||||
return invoke_foo1(a);
|
||||
$endif
|
||||
return invoke_foo2(a, #b);
|
||||
$else
|
||||
return invoke_foo1(a);
|
||||
$endif
|
||||
}
|
||||
*>
|
||||
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
|
||||
const EmptySlot EMPTY_MACRO_SLOT @builtin @deprecated("Use `#arg = ...` instead.") = null;
|
||||
|
||||
typedef EmptySlot = void*;
|
||||
macro @is_empty_macro_slot(#arg) @const @builtin => @typeis(#arg, EmptySlot);
|
||||
macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
|
||||
macro bool @is_empty_macro_slot(#arg) @const @builtin
|
||||
@deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.")
|
||||
=> $typeof(#arg) == EmptySlot;
|
||||
macro bool @is_valid_macro_slot(#arg) @const @builtin
|
||||
@deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.")
|
||||
=> $typeof(#arg) != EmptySlot;
|
||||
|
||||
<*
|
||||
Returns a random value at compile time.
|
||||
@@ -39,20 +43,28 @@ macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
|
||||
macro @rnd() @const @builtin => $$rnd();
|
||||
|
||||
/*
|
||||
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
|
||||
Use `NO_MORE_ELEMENT` when reading the end of an iterator, or accessing a result out of bounds.
|
||||
*/
|
||||
faultdef NO_MORE_ELEMENT @builtin;
|
||||
|
||||
/*
|
||||
Use `SearchResult` when trying to return a value from some collection but the element is missing.
|
||||
Use `NOT_FOUND` when trying to return a value from some collection but the element is missing.
|
||||
*/
|
||||
faultdef NOT_FOUND @builtin;
|
||||
|
||||
/*
|
||||
Use `CastResult` when an attempt at conversion fails.
|
||||
Use `TYPE_MISMATCH` when an attempt at conversion fails.
|
||||
*/
|
||||
faultdef TYPE_MISMATCH @builtin;
|
||||
|
||||
/*
|
||||
Use `CAPACITY_EXCEEDED` when trying to add to a bounded list or similar.
|
||||
*/
|
||||
faultdef CAPACITY_EXCEEDED @builtin;
|
||||
/*
|
||||
Use `NOT_IMPLEMENTED` when something is conditionally available.
|
||||
*/
|
||||
faultdef NOT_IMPLEMENTED @builtin;
|
||||
|
||||
alias VoidFn = fn void();
|
||||
|
||||
@@ -61,7 +73,7 @@ alias VoidFn = fn void();
|
||||
macro scope.
|
||||
|
||||
@param #variable : `the variable to store and restore`
|
||||
@require values::@is_lvalue(#variable)
|
||||
@require $defined(#variable = #variable) : `Expected an actual variable`
|
||||
*>
|
||||
macro void @scope(#variable; @body) @builtin
|
||||
{
|
||||
@@ -81,94 +93,160 @@ macro void @swap(#a, #b) @builtin
|
||||
#b = temp;
|
||||
}
|
||||
|
||||
macro usz bitsizeof($Type) @builtin @const => $Type.sizeof * 8u;
|
||||
|
||||
macro usz @bitsizeof(#expr) @builtin @const => $sizeof(#expr) * 8u;
|
||||
|
||||
<*
|
||||
Compile-time check for whether a set of constants contains a certain expression.
|
||||
|
||||
@param #needle : "The expression whose value should be located."
|
||||
*>
|
||||
macro bool @in(#needle, ...) @builtin @const
|
||||
{
|
||||
$for var $x = 0; $x < $vacount; $x++:
|
||||
$assert $defined(#needle == $vaconst[$x])
|
||||
: "Index %s: types '%s' (needle) and '%s' are not equatable", $x, $typeof(#needle), $typeof($vaconst[$x]);
|
||||
$if #needle == $vaconst[$x]: return true; $endif
|
||||
$endfor
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
Convert an `any` type to a type, returning an failure if there is a type mismatch.
|
||||
|
||||
@param v : `the any to convert to the given type.`
|
||||
@param $Type : `the type to convert to`
|
||||
@return `The any.ptr converted to its type.`
|
||||
@ensure @typeis(return, $Type*)
|
||||
@ensure $typeof(return) == $Type*
|
||||
@return? TYPE_MISMATCH
|
||||
*>
|
||||
macro anycast(any v, $Type) @builtin
|
||||
{
|
||||
if (v.type != $Type.typeid) return TYPE_MISMATCH?;
|
||||
if (v.type != $Type.typeid) return TYPE_MISMATCH~;
|
||||
return ($Type*)v.ptr;
|
||||
}
|
||||
|
||||
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
|
||||
<*
|
||||
@return "The value in the pointer"
|
||||
@return? TYPE_MISMATCH
|
||||
*>
|
||||
macro any.to(self, $Type)
|
||||
{
|
||||
if (self.type != $Type.typeid) return TYPE_MISMATCH~;
|
||||
return *($Type*)self.ptr;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.type == $Type : "The 'any' contained an unexpected type."
|
||||
@return "The value in the pointer"
|
||||
*>
|
||||
macro any.as(self, $Type)
|
||||
{
|
||||
return *($Type*)self.ptr;
|
||||
}
|
||||
|
||||
macro bool @assignable_to(#foo, $Type) @const @builtin @deprecated("use '$defined($Type x = #foo)'") => $defined(*&&($Type){} = #foo);
|
||||
|
||||
macro @addr(#val) @builtin
|
||||
{
|
||||
$if $defined(&#val):
|
||||
return &#val;
|
||||
$else
|
||||
return &&#val;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro typeid @typeid(#value) @const @builtin
|
||||
{
|
||||
return $typeof(#value).typeid;
|
||||
}
|
||||
|
||||
macro TypeKind @typekind(#value) @const @builtin @deprecated("Use `$kindof(#value)`.")
|
||||
{
|
||||
return $kindof(#value);
|
||||
}
|
||||
|
||||
macro bool @typeis(#value, $Type) @const @builtin @deprecated("Use `$typeof(#value) == $Type` instead.")
|
||||
{
|
||||
return $typeof(#value).typeid == $Type.typeid;
|
||||
}
|
||||
|
||||
|
||||
fn bool print_backtrace(String message, int backtraces_to_ignore, void *added_backtrace = null) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
|
||||
{
|
||||
Allocator t = allocator::current_temp;
|
||||
TempAllocator* new_t = allocator::new_temp_allocator(smem, 0x1000)!!;
|
||||
allocator::current_temp = new_t;
|
||||
defer
|
||||
{
|
||||
allocator::current_temp = t;
|
||||
new_t.free();
|
||||
}
|
||||
void*[256] buffer;
|
||||
void*[] backtraces = backtrace::capture_current(&buffer);
|
||||
backtraces_to_ignore++;
|
||||
BacktraceList? backtrace = backtrace::symbolize_backtrace(tmem, backtraces);
|
||||
if (catch backtrace) return false;
|
||||
if (backtrace.len() <= backtraces_to_ignore) return false;
|
||||
io::eprint("\nERROR: '");
|
||||
io::eprint(message);
|
||||
io::eprintn("'");
|
||||
foreach (i, &trace : backtrace)
|
||||
if (added_backtrace)
|
||||
{
|
||||
if (i < backtraces_to_ignore) continue;
|
||||
String inline_suffix = trace.is_inline ? " [inline]" : "";
|
||||
if (trace.is_unknown())
|
||||
{
|
||||
io::eprintfn(" in ???%s", inline_suffix);
|
||||
continue;
|
||||
}
|
||||
if (trace.has_file())
|
||||
{
|
||||
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
|
||||
continue;
|
||||
}
|
||||
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
|
||||
backtraces[++backtraces_to_ignore] = added_backtrace;
|
||||
}
|
||||
@stack_mem(4096; Allocator mem)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (i < backtraces_to_ignore) continue;
|
||||
String inline_suffix = trace.is_inline ? " [inline]" : "";
|
||||
if (trace.is_unknown())
|
||||
{
|
||||
io::eprintfn(" in ???%s", inline_suffix);
|
||||
continue;
|
||||
}
|
||||
if (trace.has_file())
|
||||
{
|
||||
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
|
||||
continue;
|
||||
}
|
||||
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
in_panic = true;
|
||||
$if $defined(io::stderr) && env::PANIC_MSG:
|
||||
if (!print_backtrace(message, 2))
|
||||
{
|
||||
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
|
||||
}
|
||||
$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();
|
||||
}
|
||||
|
||||
bool in_panic @local = false;
|
||||
bool in_panic @private = false;
|
||||
|
||||
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
|
||||
fn void default_panic(String message, String file, String function, uint line) @if (!env::NATIVE_STACKTRACE)
|
||||
{
|
||||
if (in_panic)
|
||||
{
|
||||
io::eprintn("Panic inside of panic.");
|
||||
return;
|
||||
}
|
||||
in_panic = true;
|
||||
$if $defined(io::stderr):
|
||||
io::eprint("\nERROR: '");
|
||||
io::eprint(message);
|
||||
io::eprintfn("', in %s (%s:%d)", function, file, line);
|
||||
$if $defined(io::stderr) && env::PANIC_MSG:
|
||||
if (in_panic)
|
||||
{
|
||||
io::eprintn("Panic inside of panic.");
|
||||
return;
|
||||
}
|
||||
in_panic = true;
|
||||
$if $defined(io::stderr):
|
||||
io::eprint("\nERROR: '");
|
||||
io::eprint(message);
|
||||
io::eprintfn("', in %s (%s:%d)", function, file, line);
|
||||
$endif
|
||||
in_panic = false;
|
||||
$endif
|
||||
in_panic = false;
|
||||
$$trap();
|
||||
}
|
||||
|
||||
@@ -178,21 +256,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
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -247,7 +327,7 @@ macro any.as_inner(&self)
|
||||
@param $Type : "the type to cast to"
|
||||
|
||||
@require $sizeof(expr) == $Type.sizeof : "Cannot bitcast between types of different size."
|
||||
@ensure @typeis(return, $Type)
|
||||
@ensure $typeof(return) == $Type*
|
||||
*>
|
||||
macro bitcast(expr, $Type) @builtin
|
||||
{
|
||||
@@ -264,7 +344,7 @@ macro bitcast(expr, $Type) @builtin
|
||||
@param $Type : `The type of the enum`
|
||||
@param [in] enum_name : `The name of the enum to search for`
|
||||
@require $Type.kindof == ENUM : `Only enums may be used`
|
||||
@ensure @typeis(return, $Type)
|
||||
@ensure $typeof(return) == $Type*
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro enum_by_name($Type, String enum_name) @builtin
|
||||
@@ -274,15 +354,15 @@ macro enum_by_name($Type, String enum_name) @builtin
|
||||
{
|
||||
if (name == enum_name) return $Type.from_ordinal(i);
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@param $Type : `The type of the enum`
|
||||
@require $Type.kindof == ENUM : `Only enums may be used`
|
||||
@require $defined($Type.#value) : `Expected '#value' to match an enum associated value`
|
||||
@require $assignable(value, $typeof(($Type){}.#value)) : `Expected the value to match the type of the associated value`
|
||||
@ensure @typeis(return, $Type)
|
||||
@require $defined($typeof(($Type){}.#value) v = value) : `Expected the value to match the type of the associated value`
|
||||
@ensure $typeof(return) == $Type*
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.lookup_field and Enum.lookup")
|
||||
@@ -291,7 +371,7 @@ macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.look
|
||||
{
|
||||
if (e.#value == value) return e;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -334,7 +414,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 $defined($typeof(#value) v = expected)
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro @expect(#value, expected, $probability = 1.0) @builtin
|
||||
@@ -375,20 +455,62 @@ macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write =
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Shuffle a vector by its index
|
||||
|
||||
int[<4>] a = { 1, 2, 3, 4 };
|
||||
assert(swizzle(a, 0, 1, 1, 3) == (int[<4>]) { 1, 2, 2, 4 });
|
||||
*>
|
||||
macro swizzle(v, ...) @builtin
|
||||
{
|
||||
return $$swizzle(v, $vasplat);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Shuffle two vectors by a common index from arranging the vectors sequentially in memory
|
||||
|
||||
int[<4>] a = { 1, 2, 3, 4 };
|
||||
int[<4>] b = { 100, 1000, 10000, 100000 };
|
||||
assert(swizzle2(a, b, 0, 1, 4, 6, 2) == (int[<5>]) { 1, 2, 100, 10000, 3 });
|
||||
*>
|
||||
macro swizzle2(v, v2, ...) @builtin
|
||||
{
|
||||
return $$swizzle2(v, v2, $vasplat);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns the count of leading zero bits from an integer at compile-time.
|
||||
|
||||
@require types::is_int($typeof($value)) : "Input value must be an integer"
|
||||
@require $sizeof($value) * 8 <= 128 : "Input value must be 128 bits wide or lower"
|
||||
*>
|
||||
macro uint @clz($value) @builtin @const
|
||||
{
|
||||
$if $value == 0:
|
||||
return $sizeof($value) * 8; // it's all leading zeroes
|
||||
$endif
|
||||
|
||||
usz $n = 0;
|
||||
uint128 $x = (uint128)$value;
|
||||
|
||||
$if $x <= 0x0000_0000_0000_0000_FFFF_FFFF_FFFF_FFFF: $n += 64; $x <<= 64; $endif
|
||||
$if $x <= 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 32; $x <<= 32; $endif
|
||||
$if $x <= 0x0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 16; $x <<= 16; $endif
|
||||
$if $x <= 0x00FF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 8; $x <<= 8; $endif
|
||||
$if $x <= 0x0FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 4; $x <<= 4; $endif
|
||||
$if $x <= 0x3FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 2; $x <<= 2; $endif
|
||||
$if $x <= 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 1; $endif
|
||||
|
||||
return $n % ($sizeof($value) * 8); // mod by the bitsize of the input value to go back from uint128 -> it's-type
|
||||
}
|
||||
|
||||
<*
|
||||
Return the excuse in the Optional if it is Empty, otherwise
|
||||
return a null fault.
|
||||
|
||||
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
|
||||
@require $kindof(#expr) == OPTIONAL : `@catch expects an Optional value`
|
||||
*>
|
||||
macro fault @catch(#expr) @builtin
|
||||
{
|
||||
@@ -400,7 +522,7 @@ macro fault @catch(#expr) @builtin
|
||||
Check if an Optional expression holds a value or is empty, returning true
|
||||
if it has a value.
|
||||
|
||||
@require @typekind(#expr) == OPTIONAL : `@ok expects an Optional value`
|
||||
@require $kindof(#expr) == OPTIONAL : `@ok expects an Optional value`
|
||||
*>
|
||||
macro bool @ok(#expr) @builtin
|
||||
{
|
||||
@@ -408,6 +530,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 $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
|
||||
*>
|
||||
macro void? @try(#v, #expr) @builtin @maydiscard
|
||||
{
|
||||
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 $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
|
||||
@return "True if it was the expected fault, false if the variable was assigned, otherwise returns an optional."
|
||||
*>
|
||||
macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin
|
||||
{
|
||||
var res = #expr;
|
||||
if (catch err = res)
|
||||
{
|
||||
return err == expected_fault ? true : err~;
|
||||
}
|
||||
#v = res;
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(&#value, (char*)&#value) : "This must be a value that can be viewed as a char array"
|
||||
*>
|
||||
@@ -420,6 +603,27 @@ macro isz @str_find(String $string, String $needle) @builtin => $$str_find($stri
|
||||
macro String @str_upper(String $str) @builtin => $$str_upper($str);
|
||||
macro String @str_lower(String $str) @builtin => $$str_lower($str);
|
||||
macro uint @str_hash(String $str) @builtin => $$str_hash($str);
|
||||
macro String @str_pascalcase(String $str) @builtin => $$str_pascalcase($str);
|
||||
macro String @str_snakecase(String $str) @builtin => $$str_snakecase($str);
|
||||
macro String @str_camelcase(String $str) @builtin => @str_capitalize($$str_pascalcase($str));
|
||||
macro String @str_constantcase(String $str) @builtin => @str_upper($$str_snakecase($str));
|
||||
macro String @str_replace(String $str, String $pattern, String $replace, uint $limit = 0) @builtin => $$str_replace($str, $pattern, $replace, $limit);
|
||||
macro String @str_capitalize(String $str) @builtin
|
||||
{
|
||||
$switch $str.len:
|
||||
$case 0: return $str;
|
||||
$case 1: return $$str_upper($str);
|
||||
$default: return $$str_upper($str[0:1]) +++ $str[1..];
|
||||
$endswitch
|
||||
}
|
||||
macro String @str_uncapitalize(String $str) @builtin
|
||||
{
|
||||
$switch $str.len:
|
||||
$case 0: return $str;
|
||||
$case 1: return $$str_lower($str);
|
||||
$default: return $$str_lower($str[0:1]) +++ $str[1..];
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro @generic_hash_core(h, value)
|
||||
{
|
||||
@@ -429,7 +633,7 @@ macro @generic_hash_core(h, value)
|
||||
return h;
|
||||
}
|
||||
|
||||
macro @generic_hash(value)
|
||||
macro uint @generic_hash(value)
|
||||
{
|
||||
uint h = @generic_hash_core((uint)0x3efd4391, value);
|
||||
$for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4:
|
||||
@@ -477,24 +681,36 @@ 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));
|
||||
|
||||
<*
|
||||
@require @typekind(array_ptr) == POINTER &&& @typekind(*array_ptr) == ARRAY
|
||||
@require $kindof(array_ptr) == POINTER &&& $kindof(*array_ptr) == ARRAY
|
||||
*>
|
||||
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
|
||||
}
|
||||
|
||||
<*
|
||||
@require @typekind(vec) == VECTOR
|
||||
@require $kindof(vec) == VECTOR
|
||||
*>
|
||||
macro uint hash_vec(vec) @local
|
||||
{
|
||||
return (uint)fnv32a::hash(((char*)&&vec)[:$sizeof(vec.len * $typeof(vec).inner.sizeof)]);
|
||||
var $len = $sizeof(vec);
|
||||
|
||||
$if $len > 16:
|
||||
return (uint)komi::hash(((char*)&&vec)[:$len]);
|
||||
$else
|
||||
return (uint)wyhash2::hash(((char*)&&vec)[:$len]);
|
||||
$endif
|
||||
}
|
||||
|
||||
const MAX_FRAMEADDRESS = 128;
|
||||
@@ -781,57 +997,73 @@ macro void* get_returnaddress(int n)
|
||||
}
|
||||
|
||||
module std::core::builtin @if((env::LINUX || env::ANDROID || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
|
||||
import libc, std::io;
|
||||
import libc, std::io, std::os::posix;
|
||||
|
||||
fn void sig_panic(String message)
|
||||
{
|
||||
default_panic(message, "???", "???", 0);
|
||||
}
|
||||
|
||||
SignalFunction old_bus_error;
|
||||
SignalFunction old_segmentation_fault;
|
||||
|
||||
fn void sig_bus_error(CInt i)
|
||||
fn void sig_bus_error(CInt i, void* info, void* context)
|
||||
{
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Illegal memory access.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Illegal memory access.", 1))
|
||||
if (!print_backtrace("Illegal memory access.", 2, posix::stack_instruction(context)))
|
||||
{
|
||||
io::eprintn("\nERROR: 'Illegal memory access'.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
$$trap();
|
||||
os::fastexit(128 + i);
|
||||
}
|
||||
|
||||
fn void sig_segmentation_fault(CInt i)
|
||||
fn void sig_segmentation_fault(CInt i, void* p1, void* context)
|
||||
{
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Out of bounds memory access.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Out of bounds memory access.", 1))
|
||||
if (!print_backtrace("Out of bounds memory access.", 2, posix::stack_instruction(context)))
|
||||
{
|
||||
io::eprintn("\nERROR: Memory error without backtrace, possible stack overflow.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
$$trap();
|
||||
os::fastexit(128 + i);
|
||||
}
|
||||
|
||||
fn void install_signal_handler(CInt signal, SignalFunction func) @local
|
||||
fn void sig_illegal_instruction(CInt i, void* p1, void* context)
|
||||
{
|
||||
SignalFunction old = libc::signal(signal, func);
|
||||
// Restore
|
||||
if ((iptr)old > 1024) libc::signal(signal, old);
|
||||
if (in_panic) os::fastexit(128 + i);
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Illegal instruction.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Illegal instruction.", 2, posix::stack_instruction(context)))
|
||||
{
|
||||
io::eprintn("\nERROR: Illegal instruction.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
os::fastexit(128 + i);
|
||||
}
|
||||
|
||||
char[64 * 1024] sig_stack @local @if(env::BACKTRACE && env::LINUX);
|
||||
|
||||
// Clean this up
|
||||
fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE)
|
||||
{
|
||||
install_signal_handler(libc::SIGBUS, &sig_bus_error);
|
||||
install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
|
||||
}
|
||||
$if env::LINUX:
|
||||
Stack_t ss = {
|
||||
.ss_sp = &sig_stack,
|
||||
.ss_size = sig_stack.len
|
||||
};
|
||||
libc::sigaltstack(&ss, null);
|
||||
$endif
|
||||
|
||||
posix::install_signal_handler(libc::SIGBUS, &sig_bus_error);
|
||||
posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
|
||||
posix::install_signal_handler(libc::SIGILL, &sig_illegal_instruction);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ module std::core::builtin;
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro less(a, b) @builtin
|
||||
macro bool less(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -21,7 +21,7 @@ macro less(a, b) @builtin
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro less_eq(a, b) @builtin
|
||||
macro bool less_eq(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -36,7 +36,7 @@ macro less_eq(a, b) @builtin
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro greater(a, b) @builtin
|
||||
macro bool greater(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -65,7 +65,7 @@ macro int compare_to(a, b) @builtin
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro greater_eq(a, b) @builtin
|
||||
macro bool greater_eq(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -126,3 +126,36 @@ macro max(x, ...) @builtin
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require types::is_numerical($typeof($a))
|
||||
*>
|
||||
macro @max($a, ...) @builtin @const
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return $a > $vaconst[0] ? $a : $vaconst[0];
|
||||
$else
|
||||
var $result = $a;
|
||||
$for var $x = 0; $x < $vacount; ++$x:
|
||||
$if $vaconst[$x] > $result: $result = $vaconst[$x]; $endif
|
||||
$endfor
|
||||
return $result;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::is_numerical($typeof($a))
|
||||
*>
|
||||
macro @min($a, ...) @builtin @const
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return $a < $vaconst[0] ? $a : $vaconst[0];
|
||||
$else
|
||||
var $result = $a;
|
||||
$for var $x = 0; $x < $vacount; ++$x:
|
||||
$if $vaconst[$x] < $result: $result = $vaconst[$x]; $endif
|
||||
$endfor
|
||||
return $result;
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::cinterop;
|
||||
import std::core::env;
|
||||
|
||||
|
||||
const C_INT_SIZE = $$C_INT_SIZE;
|
||||
const C_LONG_SIZE = $$C_LONG_SIZE;
|
||||
@@ -59,3 +61,57 @@ macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
|
||||
$default: $error("Invalid bitsize");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
const USE_STACK_VALIST = env::ARCH_32_BIT || env::WIN32 || (env::DARWIN && env::AARCH64);
|
||||
module std::core::cinterop @if(USE_STACK_VALIST);
|
||||
|
||||
typedef CVaList = void*;
|
||||
macro CVaList.next(&self, $Type)
|
||||
{
|
||||
void *ptr = mem::aligned_pointer((void*)*self, max($Type.alignof, 8));
|
||||
defer *self = (CVaList)(ptr + 1);
|
||||
return *($Type*)ptr;
|
||||
}
|
||||
|
||||
module std::core::cinterop @if(env::X86_64 && !env::WIN32);
|
||||
|
||||
struct CVaListData
|
||||
{
|
||||
uint gp_offset;
|
||||
uint fp_offset;
|
||||
void *overflow_arg_area;
|
||||
void *reg_save_area;
|
||||
}
|
||||
|
||||
typedef CVaList = CVaListData*;
|
||||
|
||||
macro CVaList.next(self, $Type)
|
||||
{
|
||||
CVaListData* data = (CVaListData*)self;
|
||||
$switch:
|
||||
$case $Type.kindof == FLOAT ||| ($Type.kindof == VECTOR && $Type.sizeof <= 16):
|
||||
var $LoadType = $Type.sizeof < 8 ? double : $Type;
|
||||
if (data.fp_offset < 6 * 8 + 8 * 16 )
|
||||
{
|
||||
defer data.fp_offset += (uint)mem::aligned_offset($Type.sizeof, 16);
|
||||
return ($Type)*($LoadType*)(data.reg_save_area + data.fp_offset);
|
||||
}
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return ($Type)*($LoadType*)ptr;
|
||||
$case $Type.kindof == SIGNED_INT || $Type.kindof == UNSIGNED_INT:
|
||||
var $LoadType = $Type.sizeof < 4 ? int : $Type;
|
||||
if (data.gp_offset < 6 * 8 && $Type.sizeof <= 8)
|
||||
{
|
||||
defer data.gp_offset += (uint)mem::aligned_offset($Type.sizeof, 8);
|
||||
return ($Type)*($LoadType*)(data.reg_save_area + data.gp_offset);
|
||||
}
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return ($Type)*($LoadType*)ptr;
|
||||
$default:
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return *($Type*)ptr;
|
||||
$endswitch
|
||||
}
|
||||
@@ -16,25 +16,25 @@ const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
|
||||
*>
|
||||
fn usz? char32_to_utf8(Char32 c, char[] output)
|
||||
{
|
||||
if (!output.len) return string::CONVERSION_FAILED?;
|
||||
if (!output.len) return string::CONVERSION_FAILED~;
|
||||
switch (true)
|
||||
{
|
||||
case c <= 0x7f:
|
||||
output[0] = (char)c;
|
||||
return 1;
|
||||
case c <= 0x7ff:
|
||||
if (output.len < 2) return string::CONVERSION_FAILED?;
|
||||
if (output.len < 2) return string::CONVERSION_FAILED~;
|
||||
output[0] = (char)(0xC0 | c >> 6);
|
||||
output[1] = (char)(0x80 | (c & 0x3F));
|
||||
return 2;
|
||||
case c <= 0xffff:
|
||||
if (output.len < 3) return string::CONVERSION_FAILED?;
|
||||
if (output.len < 3) return string::CONVERSION_FAILED~;
|
||||
output[0] = (char)(0xE0 | c >> 12);
|
||||
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c & 0x3F));
|
||||
return 3;
|
||||
case c <= 0x10ffff:
|
||||
if (output.len < 4) return string::CONVERSION_FAILED?;
|
||||
if (output.len < 4) return string::CONVERSION_FAILED~;
|
||||
output[0] = (char)(0xF0 | c >> 18);
|
||||
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
@@ -42,7 +42,7 @@ fn usz? char32_to_utf8(Char32 c, char[] output)
|
||||
return 4;
|
||||
default:
|
||||
// 0x10FFFF and above is not defined.
|
||||
return string::CONVERSION_FAILED?;
|
||||
return string::CONVERSION_FAILED~;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,15 +84,15 @@ fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
return;
|
||||
}
|
||||
// Low surrogate first is an error
|
||||
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return string::INVALID_UTF16?;
|
||||
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return string::INVALID_UTF16~;
|
||||
|
||||
// Unmatched high surrogate is an error
|
||||
if (*available == 1) return string::INVALID_UTF16?;
|
||||
if (*available == 1) return string::INVALID_UTF16~;
|
||||
|
||||
Char16 low = ptr[1];
|
||||
|
||||
// Unmatched high surrogate, invalid
|
||||
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return string::INVALID_UTF16?;
|
||||
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return string::INVALID_UTF16~;
|
||||
|
||||
// The high bits of the codepoint are the value bits of the high surrogate
|
||||
// The low bits of the codepoint are the value bits of the low surrogate
|
||||
@@ -138,7 +138,7 @@ fn usz char32_to_utf8_unsafe(Char32 c, char** output)
|
||||
fn Char32? utf8_to_char32(char* ptr, usz* size)
|
||||
{
|
||||
usz max_size = *size;
|
||||
if (max_size < 1) return string::INVALID_UTF8?;
|
||||
if (max_size < 1) return string::INVALID_UTF8~;
|
||||
char c = (ptr++)[0];
|
||||
|
||||
if ((c & 0x80) == 0)
|
||||
@@ -148,40 +148,40 @@ fn Char32? utf8_to_char32(char* ptr, usz* size)
|
||||
}
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
if (max_size < 2) return string::INVALID_UTF8?;
|
||||
if (max_size < 2) return string::INVALID_UTF8~;
|
||||
*size = 2;
|
||||
Char32 uc = (c & 0x1F) << 6;
|
||||
c = *ptr;
|
||||
// Overlong sequence or invalid second.
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
if (max_size < 3) return string::INVALID_UTF8?;
|
||||
if (max_size < 3) return string::INVALID_UTF8~;
|
||||
*size = 3;
|
||||
Char32 uc = (c & 0x0F) << 12;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
uc += (c & 0x3F) << 6;
|
||||
c = ptr++[0];
|
||||
// Overlong sequence or invalid last
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if (max_size < 4) return string::INVALID_UTF8?;
|
||||
if ((c & 0xF8) != 0xF0) return string::INVALID_UTF8?;
|
||||
if (max_size < 4) return string::INVALID_UTF8~;
|
||||
if ((c & 0xF8) != 0xF0) return string::INVALID_UTF8~;
|
||||
*size = 4;
|
||||
Char32 uc = (c & 0x07) << 18;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
uc += (c & 0x3F) << 12;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
uc += (c & 0x3F) << 6;
|
||||
c = ptr++[0];
|
||||
// Overlong sequence or invalid last
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
usz buf_len = utf32_buffer.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
if (len32 == buf_len) return string::CONVERSION_FAILED?;
|
||||
if (len32 == buf_len) return string::CONVERSION_FAILED~;
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
|
||||
@@ -95,7 +95,7 @@ fn void DString.replace(&self, String needle, String replacement)
|
||||
match++;
|
||||
if (match == needle_len)
|
||||
{
|
||||
self.append_chars(replacement);
|
||||
self.append_string(replacement);
|
||||
match = 0;
|
||||
continue;
|
||||
}
|
||||
@@ -103,12 +103,12 @@ fn void DString.replace(&self, String needle, String replacement)
|
||||
}
|
||||
if (match > 0)
|
||||
{
|
||||
self.append_chars(str[i - match:match]);
|
||||
self.append_string(str[i - match:match]);
|
||||
match = 0;
|
||||
}
|
||||
self.append_char(c);
|
||||
}
|
||||
if (match > 0) self.append_chars(str[^match:match]);
|
||||
if (match > 0) self.append_string(str[^match:match]);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -305,18 +305,23 @@ fn bool DString.less(self, DString other_string)
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void DString.append_chars(&self, String str)
|
||||
fn void DString.append_chars(&self, String str) @deprecated("Use append_string")
|
||||
{
|
||||
usz other_len = str.len;
|
||||
self.append_bytes(str);
|
||||
}
|
||||
|
||||
fn void DString.append_bytes(&self, char[] bytes)
|
||||
{
|
||||
usz other_len = bytes.len;
|
||||
if (!other_len) return;
|
||||
if (!*self)
|
||||
{
|
||||
*self = temp(str);
|
||||
*self = temp((String)bytes);
|
||||
return;
|
||||
}
|
||||
self.reserve(other_len);
|
||||
StringData* data = self.data();
|
||||
mem::copy(&data.chars[data.len], str.ptr, other_len);
|
||||
mem::copy(&data.chars[data.len], bytes.ptr, other_len);
|
||||
data.len += other_len;
|
||||
}
|
||||
|
||||
@@ -325,7 +330,24 @@ fn Char32[] DString.copy_utf32(&self, Allocator allocator)
|
||||
return self.str_view().to_utf32(allocator) @inline!!;
|
||||
}
|
||||
|
||||
fn void DString.append_string(&self, DString str)
|
||||
<*
|
||||
@require $typeof(str) == String || $typeof(str) == DString : "Expected string or DString"
|
||||
*>
|
||||
macro void DString.append_string(&self, str)
|
||||
{
|
||||
$if $typeof(str) == String:
|
||||
self.append_bytes(str);
|
||||
$else
|
||||
self.append_string_deprecated(str);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void DString.append_string_deprecated(&self, DString str) @deprecated("Use .append_dstring()")
|
||||
{
|
||||
self.append_dstring(str);
|
||||
}
|
||||
|
||||
fn void DString.append_dstring(&self, DString str)
|
||||
{
|
||||
StringData* other = str.data();
|
||||
if (!other) return;
|
||||
@@ -340,7 +362,7 @@ fn void DString.clear(self)
|
||||
|
||||
fn usz? DString.write(&self, char[] buffer) @dynamic
|
||||
{
|
||||
self.append_chars((String)buffer);
|
||||
self.append_bytes(buffer);
|
||||
return buffer.len;
|
||||
}
|
||||
|
||||
@@ -400,9 +422,9 @@ macro void DString.append(&self, value)
|
||||
$case ichar:
|
||||
self.append_char(value);
|
||||
$case DString:
|
||||
self.append_string(value);
|
||||
self.append_dstring(value);
|
||||
$case String:
|
||||
self.append_chars(value);
|
||||
self.append_string(value);
|
||||
$case Char32:
|
||||
self.append_char32(value);
|
||||
$default:
|
||||
@@ -410,7 +432,7 @@ macro void DString.append(&self, value)
|
||||
$case $defined((Char32)value):
|
||||
self.append_char32((Char32)value);
|
||||
$case $defined((String)value):
|
||||
self.append_chars((String)value);
|
||||
self.append_string((String)value);
|
||||
$default:
|
||||
$error "Unsupported type for append – use appendf instead.";
|
||||
$endswitch
|
||||
|
||||
@@ -124,7 +124,8 @@ 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;
|
||||
const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
|
||||
const bool NO_LIBC = !LIBC && !CUSTOM_LIBC;
|
||||
const bool CUSTOM_LIBC = $$CUSTOM_LIBC;
|
||||
const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel.from_ordinal($$COMPILER_OPT_LEVEL);
|
||||
const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
|
||||
const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED;
|
||||
@@ -137,12 +138,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;
|
||||
@@ -152,14 +154,20 @@ const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
|
||||
const bool NETBSD = LIBC && OS_TYPE == NETBSD;
|
||||
const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD;
|
||||
const bool WASI = LIBC && OS_TYPE == WASI;
|
||||
const bool WASM = ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool ANDROID = LIBC && OS_TYPE == ANDROID;
|
||||
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool WASM_NOLIBC @builtin @deprecated("Use 'FREESTANDING_WASM' instead") = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool FREESTANDING_PE32 = NO_LIBC && OS_TYPE == WIN32;
|
||||
const bool FREESTANDING_MACHO = NO_LIBC && OS_TYPE == MACOS;
|
||||
const bool FREESTANDING_ELF = NO_LIBC && !env::FREESTANDING_PE32 && !env::FREESTANDING_MACHO && !env::FREESTANDING_WASM;
|
||||
const bool FREESTANDING_WASM = NO_LIBC && WASM;
|
||||
const bool FREESTANDING = env::FREESTANDING_PE32 || env::FREESTANDING_MACHO || env::FREESTANDING_ELF || env::FREESTANDING_WASM;
|
||||
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
|
||||
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
|
||||
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
|
||||
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
|
||||
const int LANGUAGE_DEV_VERSION = $$LANGUAGE_DEV_VERSION;
|
||||
const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::DARWIN || env::WIN32;
|
||||
const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::OPENBSD || env::DARWIN || env::WIN32 || env::NETBSD;
|
||||
|
||||
macro bool os_is_darwin() @const
|
||||
{
|
||||
@@ -198,6 +206,8 @@ macro bool os_is_posix() @const
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
const String[] AUTHORS = $$AUTHORS;
|
||||
const String[] AUTHOR_EMAILS = $$AUTHOR_EMAILS;
|
||||
const String PROJECT_VERSION = $$PROJECT_VERSION;
|
||||
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
|
||||
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);
|
||||
|
||||
240
lib/std/core/logging.c3
Normal file
240
lib/std/core/logging.c3
Normal file
@@ -0,0 +1,240 @@
|
||||
module std::core::log;
|
||||
import std::io, std::thread, std::time, std::math::random;
|
||||
|
||||
const FULL_LOG = env::COMPILER_SAFE_MODE || $feature(FULL_LOG);
|
||||
|
||||
typedef LogCategory = inline char;
|
||||
typedef LogTag = char[12];
|
||||
|
||||
const LogCategory CATEGORY_APPLICATION = 0;
|
||||
const LogCategory CATEGORY_SYSTEM = 1;
|
||||
const LogCategory CATEGORY_KERNEL = 2;
|
||||
const LogCategory CATEGORY_AUDIO = 3;
|
||||
const LogCategory CATEGORY_VIDEO = 4;
|
||||
const LogCategory CATEGORY_RENDER = 5;
|
||||
const LogCategory CATEGORY_INPUT = 6;
|
||||
const LogCategory CATEGORY_NETWORK = 7;
|
||||
const LogCategory CATEGORY_SOCKET = 8;
|
||||
const LogCategory CATEGORY_SECURITY = 9;
|
||||
const LogCategory CATEGORY_TEST = 10;
|
||||
const LogCategory CATEGORY_ERROR = 11;
|
||||
const LogCategory CATEGORY_ASSERT = 12;
|
||||
const LogCategory CATEGORY_CRASH = 13;
|
||||
const LogCategory CATEGORY_STATS = 14;
|
||||
const LogCategory CATEGORY_CUSTOM_START = 100;
|
||||
|
||||
tlocal LogCategory default_category = CATEGORY_APPLICATION;
|
||||
tlocal LogTag current_tag;
|
||||
|
||||
|
||||
enum LogPriority : int
|
||||
{
|
||||
VERBOSE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR,
|
||||
CRITICAL,
|
||||
}
|
||||
|
||||
interface Logger
|
||||
{
|
||||
fn void log(LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
|
||||
}
|
||||
|
||||
macro void verbose(String fmt, ..., LogCategory category = default_category) => call_log(VERBOSE, category, fmt, $vasplat);
|
||||
macro void debug(String fmt, ..., LogCategory category = default_category) => call_log(DEBUG, category, fmt, $vasplat);
|
||||
macro void info(String fmt, ..., LogCategory category = default_category) => call_log(INFO, category, fmt, $vasplat);
|
||||
macro void warn(String fmt, ..., LogCategory category = default_category) => call_log(WARN, category, fmt, $vasplat);
|
||||
macro void error(String fmt, ..., LogCategory category = default_category) => call_log(ERROR, category, fmt, $vasplat);
|
||||
macro void critical(String fmt, ..., LogCategory category = default_category) => call_log(CRITICAL, category, fmt, $vasplat);
|
||||
|
||||
macro void @category_scope(LogCategory new_category; @body)
|
||||
{
|
||||
LogCategory old = default_category;
|
||||
default_category = new_category;
|
||||
defer default_category = old;
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
macro void @tag_scope(String tag_prefix = ""; @body)
|
||||
{
|
||||
LogTag old = current_tag;
|
||||
push_tag(tag_prefix);
|
||||
defer current_tag = old;
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
macro void push_tag(String tag_prefix = "")
|
||||
{
|
||||
current_tag = create_tag(tag_prefix);
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
fn LogTag create_tag(String tag_prefix)
|
||||
{
|
||||
LogTag tag @noinit;
|
||||
int start = 0;
|
||||
foreach (int i, c : tag_prefix)
|
||||
{
|
||||
if (c == 0) break;
|
||||
tag[start++] = c;
|
||||
}
|
||||
if (start > 0) tag[start++] = '_';
|
||||
for (int i = start; i < tag.len; i++)
|
||||
{
|
||||
tag[i] = (char)rand_in_range('a', 'z');
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
fn void set_priority_for_category(LogCategory category, LogPriority new_priority)
|
||||
{
|
||||
@atomic_store(config_priorities[category], new_priority, UNORDERED);
|
||||
}
|
||||
|
||||
fn LogPriority get_priority_for_category(LogCategory category)
|
||||
{
|
||||
return @atomic_load(config_priorities[category], UNORDERED);
|
||||
}
|
||||
|
||||
fn void set_priority_all(LogPriority new_priority)
|
||||
{
|
||||
for (int i = 0; i < config_priorities.len; i++)
|
||||
{
|
||||
@atomic_store(config_priorities[i], new_priority, UNORDERED);
|
||||
}
|
||||
}
|
||||
fn void set_logger(Logger logger)
|
||||
{
|
||||
init();
|
||||
if (!logger_mutex.is_initialized())
|
||||
{
|
||||
current_logger = logger;
|
||||
current_logfn = &logger.log;
|
||||
return;
|
||||
}
|
||||
logger_mutex.@in_lock()
|
||||
{
|
||||
current_logger = logger;
|
||||
current_logfn = &logger.log;
|
||||
};
|
||||
}
|
||||
|
||||
macro void init()
|
||||
{
|
||||
log_init.call(fn () => (void)logger_mutex.init());
|
||||
}
|
||||
|
||||
macro void call_log(LogPriority prio, LogCategory category, String fmt, args...)
|
||||
{
|
||||
$if FULL_LOG:
|
||||
call_log_internal(prio, category, $$FILE, $$FUNC, $$LINE, fmt, args);
|
||||
$else
|
||||
call_log_internal(prio, category, "", "", 0, fmt, args);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void call_log_internal(LogPriority prio, LogCategory category, String file, String func, int line, String fmt, any[] args)
|
||||
{
|
||||
LogPriority priority = mem::@atomic_load(config_priorities[category], UNORDERED);
|
||||
if (priority > prio) return;
|
||||
init();
|
||||
bool locked = logger_mutex.is_initialized();
|
||||
if (locked) logger_mutex.lock();
|
||||
Logger logger = current_logger;
|
||||
LogFn logfn = current_logfn;
|
||||
defer if (locked) logger_mutex.unlock();
|
||||
logfn(logger.ptr, prio, category, current_tag, file, func, line, fmt, args);
|
||||
}
|
||||
|
||||
fn String? get_category_name(LogCategory category)
|
||||
{
|
||||
String val = category_names[category];
|
||||
return val ?: NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn void set_category_name(LogCategory category, String name)
|
||||
{
|
||||
category_names[category] = name;
|
||||
}
|
||||
|
||||
struct NullLogger (Logger)
|
||||
{
|
||||
void* dummy;
|
||||
}
|
||||
|
||||
fn void NullLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
|
||||
{}
|
||||
|
||||
struct MultiLogger (Logger)
|
||||
{
|
||||
Logger[] loggers;
|
||||
}
|
||||
|
||||
fn void MultiLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
|
||||
{
|
||||
foreach (logger : self.loggers)
|
||||
{
|
||||
logger.log(priority, category, tag, file, function, line, fmt, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module std::core::log @private;
|
||||
import std::io, std::thread, std::time;
|
||||
|
||||
struct StderrLogger (Logger) @if(env::LIBC)
|
||||
{
|
||||
void* dummy;
|
||||
}
|
||||
|
||||
fn void StderrLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic @if(env::LIBC)
|
||||
{
|
||||
@stack_mem(256 + 64; Allocator mem)
|
||||
{
|
||||
DString str;
|
||||
str.init(mem, 256);
|
||||
str.appendf(fmt, ...args);
|
||||
TzDateTime time = datetime::now().to_local();
|
||||
$if FULL_LOG:
|
||||
io::eprintfn("[%02d:%02d:%02d:%04d] %s:%d [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), file, line, priority, str);
|
||||
$else
|
||||
io::eprintfn("[%02d:%02d:%02d:%04d] [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), priority, str);
|
||||
$endif
|
||||
};
|
||||
}
|
||||
|
||||
alias LogFn = fn void(void*, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
|
||||
LogFn current_logfn = env::LIBC ??? (LogFn)&StderrLogger.log : (LogFn)&NullLogger.log;
|
||||
OnceFlag log_init;
|
||||
Mutex logger_mutex;
|
||||
Logger current_logger = env::LIBC ??? &stderr_logger : &null_logger;
|
||||
StderrLogger stderr_logger @if (env::LIBC);
|
||||
NullLogger null_logger;
|
||||
LogPriority[256] config_priorities = { [0..255] = ERROR, [CATEGORY_APPLICATION] = INFO, [CATEGORY_TEST] = VERBOSE, [CATEGORY_ASSERT] = WARN};
|
||||
String[256] category_names = {
|
||||
[CATEGORY_APPLICATION] = "APP",
|
||||
[CATEGORY_SYSTEM] = "SYSTEM",
|
||||
[CATEGORY_KERNEL] = "KERNEL",
|
||||
[CATEGORY_AUDIO] = "AUDIO",
|
||||
[CATEGORY_VIDEO] = "VIDEO",
|
||||
[CATEGORY_RENDER] = "RENDER",
|
||||
[CATEGORY_INPUT] = "INPUT",
|
||||
[CATEGORY_NETWORK] = "NETWORD",
|
||||
[CATEGORY_SOCKET] = "SOCKET",
|
||||
[CATEGORY_SECURITY] = "SECURITY",
|
||||
[CATEGORY_TEST] = "TEST",
|
||||
[CATEGORY_ERROR] = "ERROR",
|
||||
[CATEGORY_ASSERT] = "ASSERT",
|
||||
[CATEGORY_CRASH] = "CRASH",
|
||||
[CATEGORY_STATS] = "STATS"
|
||||
};
|
||||
@@ -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 DEFAULT_MEM_ALIGNMENT = env::WASM ? 16 : (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,14 +20,35 @@ macro bool @constant_is_power_of_2($x) @const @private
|
||||
return $x != 0 && ($x & ($x - 1)) == 0;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "The os page size."
|
||||
*>
|
||||
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 @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptr = passthru) : "Pointer and passthru must match"
|
||||
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
|
||||
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
@@ -40,12 +66,13 @@ 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 @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptr = passthru) : "Pointer and passthru must match"
|
||||
@require $kindof(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"
|
||||
|
||||
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
@ensure $typeof(return) == $typeof(*ptr)
|
||||
*>
|
||||
macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
|
||||
{
|
||||
@@ -59,9 +86,9 @@ macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
|
||||
@param mask : "The mask for the load"
|
||||
@param passthru : "The value to use for non masked values"
|
||||
|
||||
@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 $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptrvec[0] = passthru[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"
|
||||
|
||||
@@ -81,9 +108,9 @@ macro gather(ptrvec, bool[<*>] mask, passthru)
|
||||
@param passthru : "The value to use for non masked values"
|
||||
@param $alignment : "The alignment to assume for the pointers"
|
||||
|
||||
@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 $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptrvec[0] = passthru[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,8 +130,8 @@ 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 @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptr = value) : "Pointer and value must match"
|
||||
@require $kindof(value) == VECTOR : "Expected value to be a vector"
|
||||
@require value.len == mask.len : "Mask and value must have the same length"
|
||||
*>
|
||||
macro masked_store(ptr, value, bool[<*>] mask)
|
||||
@@ -118,8 +145,8 @@ 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 @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptr = value) : "Pointer and value must match"
|
||||
@require $kindof(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"
|
||||
|
||||
@@ -133,9 +160,9 @@ macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
|
||||
@param ptrvec : "The vector pointer containing the addresses to store to."
|
||||
@param value : "The value to store masked"
|
||||
@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 $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptrvec[0] = value[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"
|
||||
|
||||
@@ -151,9 +178,9 @@ macro scatter(ptrvec, value, bool[<*>] mask)
|
||||
@param mask : "The mask for the store"
|
||||
@param $alignment : "The alignment of the load"
|
||||
|
||||
@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 $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptrvec[0] = value[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"
|
||||
@@ -173,7 +200,7 @@ macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
|
||||
*>
|
||||
macro @unaligned_load(#x, usz $alignment) @builtin
|
||||
{
|
||||
return $$unaligned_load(&#x, $alignment);
|
||||
return $$unaligned_load(&#x, $alignment, false);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -188,9 +215,10 @@ macro @unaligned_load(#x, usz $alignment) @builtin
|
||||
*>
|
||||
macro @unaligned_store(#x, value, usz $alignment) @builtin
|
||||
{
|
||||
return $$unaligned_store(&#x, ($typeof(#x))value, $alignment);
|
||||
return $$unaligned_store(&#x, ($typeof(#x))value, $alignment, false);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param #x : "The variable or dereferenced pointer to load."
|
||||
@return "The value of the variable"
|
||||
@@ -215,6 +243,39 @@ macro @volatile_store(#x, value) @builtin
|
||||
return $$volatile_store(&#x, ($typeof(#x))value);
|
||||
}
|
||||
|
||||
<*
|
||||
@param ptr : "The pointer to load from"
|
||||
@param $align : "The alignment to assume for the load"
|
||||
@param $volatile : "Whether the load is volatile or not, defaults to false"
|
||||
@return "The value of the variable"
|
||||
|
||||
@require $defined(*ptr) : "This must be a typed pointer"
|
||||
@require @constant_is_power_of_2($align) : "The alignment must be a power of two"
|
||||
*>
|
||||
macro load(ptr, usz $align, bool $volatile = false)
|
||||
{
|
||||
return $$unaligned_load(ptr, $align, $volatile);
|
||||
}
|
||||
|
||||
<*
|
||||
@param ptr : "The pointer to store to."
|
||||
@param value : "The value to store."
|
||||
@param $align : "The alignment to assume for the store"
|
||||
@param $volatile : "Whether the store is volatile, defaults to false"
|
||||
@return "The value stored"
|
||||
|
||||
@require $defined(*ptr) : "This must be a typed pointer"
|
||||
@require $defined(*ptr = value) : "The value doesn't match the variable"
|
||||
@require @constant_is_power_of_2($align) : "The alignment must be a power of two"
|
||||
*>
|
||||
macro store(ptr, value, usz $align, bool $volatile = false)
|
||||
{
|
||||
return $$unaligned_store(ptr, ($typeof(*ptr))value, $align, $volatile);
|
||||
}
|
||||
|
||||
<*
|
||||
All possible atomic orderings
|
||||
*>
|
||||
enum AtomicOrdering : int
|
||||
{
|
||||
NOT_ATOMIC, // Not atomic
|
||||
@@ -282,7 +343,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 +359,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);
|
||||
@@ -405,7 +471,7 @@ macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $i
|
||||
@require values::@inner_kind(b) == TypeKind.SLICE || values::@inner_kind(b) == TypeKind.POINTER
|
||||
@require values::@inner_kind(a) != TypeKind.SLICE || len == -1
|
||||
@require values::@inner_kind(a) != TypeKind.POINTER || len > -1
|
||||
@require values::@assign_to(a, b) && values::@assign_to(b, a)
|
||||
@require $defined(a = b, b = a)
|
||||
*>
|
||||
macro bool equals(a, b, isz len = -1, usz $align = 0)
|
||||
{
|
||||
@@ -618,7 +684,7 @@ macro void @pool(usz reserve = 0; @body) @builtin
|
||||
@body();
|
||||
}
|
||||
|
||||
module std::core::mem @if(WASM_NOLIBC);
|
||||
module std::core::mem @if(env::FREESTANDING_WASM);
|
||||
import std::core::mem::allocator @public;
|
||||
SimpleHeapAllocator wasm_allocator @private;
|
||||
extern int __heap_base;
|
||||
@@ -656,6 +722,12 @@ macro @clone(value) @builtin @nodiscard
|
||||
return allocator::clone(mem, value);
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value"
|
||||
*>
|
||||
macro @clone_slice(value) @builtin @nodiscard => allocator::clone_slice(mem, value);
|
||||
|
||||
<*
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value, which must be released using free_aligned"
|
||||
@@ -678,6 +750,12 @@ macro @tclone(value) @builtin @nodiscard
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value"
|
||||
*>
|
||||
macro @tclone_slice(value) @builtin @nodiscard => allocator::clone_slice(tmem, value);
|
||||
|
||||
fn void* malloc(usz size) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::malloc(mem, size);
|
||||
@@ -699,56 +777,70 @@ 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"
|
||||
@param $Type : "The type to allocate"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@return "A pointer to data of type $Type."
|
||||
*>
|
||||
macro new($Type, ...) @nodiscard
|
||||
macro new($Type, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc($Type.sizeof);
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = malloc($Type.sizeof);
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)calloc($Type.sizeof);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($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"
|
||||
@param $Type : "The type to allocate"
|
||||
@param padding : "The padding to add after the allocation"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to data of type $Type."
|
||||
*>
|
||||
macro new_with_padding($Type, usz padding, ...) @nodiscard
|
||||
macro new_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc($Type.sizeof + padding);
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = malloc($Type.sizeof + padding);
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)calloc($Type.sizeof + padding);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
|
||||
@param $Type : "The type to allocate"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to data of type $Type with the proper alignment"
|
||||
*>
|
||||
macro new_aligned($Type, ...) @nodiscard
|
||||
macro new_aligned($Type, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param $Type : "The type to allocate"
|
||||
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@return "A pointer to uninitialized data for the type $Type"
|
||||
*>
|
||||
macro alloc($Type) @nodiscard
|
||||
{
|
||||
@@ -756,7 +848,12 @@ macro alloc($Type) @nodiscard
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@param $Type : "The type to allocate"
|
||||
@param padding : "The padding to add after the allocation"
|
||||
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@return "A pointer to uninitialized data for the type $Type"
|
||||
*>
|
||||
macro alloc_with_padding($Type, usz padding) @nodiscard
|
||||
{
|
||||
@@ -764,8 +861,13 @@ macro alloc_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.
|
||||
|
||||
@param $Type : "The type to allocate"
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@return "A pointer to uninitialized data for the type $Type with the proper alignment"
|
||||
*>
|
||||
macro alloc_aligned($Type) @nodiscard
|
||||
{
|
||||
@@ -773,46 +875,62 @@ 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"
|
||||
@param $Type : "The type to allocate"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to temporary data of type $Type."
|
||||
*>
|
||||
macro tnew($Type, ...) @nodiscard
|
||||
macro tnew($Type, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@param $Type : "The type to allocate"
|
||||
@param padding : "The padding to add after the allocation"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to temporary data of type $Type with added padding at the end."
|
||||
*>
|
||||
macro temp_with_padding($Type, usz padding, ...) @nodiscard
|
||||
macro temp_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline;
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
*>
|
||||
macro talloc($Type) @nodiscard
|
||||
{
|
||||
return tmalloc($Type.sizeof, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
*>
|
||||
macro talloc_with_padding($Type, usz padding) @nodiscard
|
||||
{
|
||||
return tmalloc($Type.sizeof + padding, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
|
||||
*>
|
||||
macro new_array($Type, usz elements) @nodiscard
|
||||
@@ -823,6 +941,8 @@ macro new_array($Type, usz elements) @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 $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
*>
|
||||
macro new_array_aligned($Type, usz elements) @nodiscard
|
||||
{
|
||||
@@ -830,6 +950,7 @@ macro new_array_aligned($Type, usz elements) @nodiscard
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
|
||||
*>
|
||||
macro alloc_array($Type, usz elements) @nodiscard
|
||||
@@ -840,6 +961,8 @@ macro alloc_array($Type, usz elements) @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 $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
*>
|
||||
macro alloc_array_aligned($Type, usz elements) @nodiscard
|
||||
{
|
||||
@@ -903,6 +1026,18 @@ fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMEN
|
||||
return tmem.resize(ptr, size, alignment)!!;
|
||||
}
|
||||
|
||||
<*
|
||||
Takes the address of a possibly unaligned variable or member,
|
||||
and offers safe access to that member, by constructing an UnalignedRef.
|
||||
|
||||
@require $defined(&#arg) : "It must be possible to take the address of the argument."
|
||||
@return "An 'UnalignedRef' with the proper type and alignment, with a pointer to argument"
|
||||
*>
|
||||
macro @unaligned_addr(#arg) @builtin
|
||||
{
|
||||
return (UnalignedRef{$typeof(#arg), $alignof(#arg)})&#arg;
|
||||
}
|
||||
|
||||
module std::core::mem @if(env::NO_LIBC);
|
||||
|
||||
fn CInt __memcmp(void* s1, void* s2, usz n) @weak @export("memcmp")
|
||||
@@ -940,3 +1075,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, false);
|
||||
}
|
||||
|
||||
macro Type UnalignedRef.set(&self, Type val)
|
||||
{
|
||||
return @unaligned_store(*(Type*)self, val, ALIGNMENT, false);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
// C3 has multiple different allocators available:
|
||||
// C3 has several different allocators available:
|
||||
//
|
||||
// Name Arena Uses buffer OOM Fallback? Mark? Reset?
|
||||
// ArenaAllocator Yes Yes No Yes Yes
|
||||
@@ -12,6 +12,7 @@ import std::math;
|
||||
// OnStackAllocator Yes Yes Yes No No *Note: Used by @stack_mem
|
||||
// TempAllocator Yes No Yes No* No* *Note: Mark/reset using @pool
|
||||
// TrackingAllocator No No N/A No No *Note: Wraps other heap allocator
|
||||
// Vmem Yes No No Yes Yes *Note: Can be set to huge sizes
|
||||
|
||||
const DEFAULT_SIZE_PREFIX = usz.sizeof;
|
||||
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
|
||||
@@ -64,7 +65,7 @@ alias MemoryAllocFn = fn char[]?(usz);
|
||||
|
||||
|
||||
|
||||
fn usz alignment_for_allocation(usz alignment) @inline @private
|
||||
fn usz alignment_for_allocation(usz alignment) @inline
|
||||
{
|
||||
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
|
||||
}
|
||||
@@ -166,7 +167,7 @@ macro void free_aligned(Allocator allocator, void* ptr)
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
@@ -182,7 +183,7 @@ macro new(Allocator allocator, $Type, ...) @nodiscard
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_try(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
@@ -199,7 +200,7 @@ macro new_try(Allocator allocator, $Type, ...) @nodiscard
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
@@ -303,6 +304,31 @@ macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
|
||||
*>
|
||||
macro realloc_array(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return realloc_array_try(allocator, ptr, $Type, elements)!!;
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
macro realloc_array_aligned(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)realloc_aligned(allocator, ptr, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
|
||||
*>
|
||||
macro realloc_array_try(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)realloc_try(allocator, ptr, $Type.sizeof * elements))[:elements];
|
||||
}
|
||||
|
||||
<*
|
||||
Clone a value.
|
||||
|
||||
@@ -316,6 +342,25 @@ macro clone(Allocator allocator, value) @nodiscard
|
||||
return new(allocator, $typeof(value), value);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator used to clone"
|
||||
@param slice : "The slice to clone"
|
||||
@return "A pointer to the cloned slice"
|
||||
|
||||
@require $kindof(slice) == SLICE || $kindof(slice) == ARRAY
|
||||
*>
|
||||
macro clone_slice(Allocator allocator, slice) @nodiscard
|
||||
{
|
||||
if (!lengthof(slice)) return {};
|
||||
|
||||
var $Type = $typeof(slice[0]);
|
||||
|
||||
$Type[] new_arr = new_array(allocator, $Type, slice.len);
|
||||
mem::copy(new_arr.ptr, &slice[0], slice.len * $Type.sizeof);
|
||||
|
||||
return new_arr;
|
||||
}
|
||||
|
||||
<*
|
||||
Clone overaligned values. Must be released using free_aligned.
|
||||
|
||||
@@ -347,7 +392,7 @@ macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
|
||||
if (alignment < void*.alignof) alignment = void*.alignof;
|
||||
usz header = AlignedBlock.sizeof + alignment;
|
||||
usz alignsize = bytes + header;
|
||||
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
|
||||
$if $kindof(#alloc_fn(bytes)) == OPTIONAL:
|
||||
void* data = #alloc_fn(alignsize)!;
|
||||
$else
|
||||
void* data = #alloc_fn(alignsize);
|
||||
@@ -368,7 +413,7 @@ struct AlignedBlock
|
||||
macro void? @aligned_free(#free_fn, void* old_pointer)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
|
||||
$if $kindof(#free_fn(desc.start)) == OPTIONAL:
|
||||
#free_fn(desc.start)!;
|
||||
$else
|
||||
#free_fn(desc.start);
|
||||
@@ -385,7 +430,7 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
|
||||
void* data_start = desc.start;
|
||||
void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!;
|
||||
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
|
||||
$if @typekind(#free_fn(data_start)) == OPTIONAL:
|
||||
$if $kindof(#free_fn(data_start)) == OPTIONAL:
|
||||
#free_fn(data_start)!;
|
||||
$else
|
||||
#free_fn(data_start);
|
||||
@@ -463,7 +508,7 @@ macro usz temp_allocator_default_reserve_size() @local
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro Allocator heap() => thread_allocator;
|
||||
macro Allocator heap() @deprecated("Use 'mem' instead.") => thread_allocator;
|
||||
|
||||
<*
|
||||
@require !top_temp : "This should never be called when temp already exists"
|
||||
@@ -486,14 +531,14 @@ fn Allocator create_temp_allocator(Allocator allocator, usz size, usz reserve, u
|
||||
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size, reserve, min_size, realloc_size)!!;
|
||||
}
|
||||
|
||||
macro Allocator temp()
|
||||
macro Allocator temp() @deprecated("Use 'tmem' instead")
|
||||
{
|
||||
return current_temp;
|
||||
}
|
||||
|
||||
alias tmem @builtin = current_temp;
|
||||
|
||||
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::WASM_NOLIBC)
|
||||
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::FREESTANDING_WASM)
|
||||
{
|
||||
auto_create_temp = true;
|
||||
}
|
||||
@@ -538,12 +583,12 @@ typedef NullAllocator (Allocator) = uptr;
|
||||
|
||||
fn void*? NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
return mem::OUT_OF_MEMORY?;
|
||||
return mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
|
||||
fn void*? NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
return mem::OUT_OF_MEMORY?;
|
||||
return mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
|
||||
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
|
||||
252
lib/std/core/mem_mempool.c3
Normal file
252
lib/std/core/mem_mempool.c3
Normal file
@@ -0,0 +1,252 @@
|
||||
module std::core::mem::mempool;
|
||||
import std::core::mem, std::core::mem::allocator, std::math;
|
||||
import std::core::sanitizer::asan;
|
||||
|
||||
const INITIAL_CAPACITY = 0;
|
||||
|
||||
struct FixedBlockPoolNode
|
||||
{
|
||||
void* buffer;
|
||||
FixedBlockPoolNode *next;
|
||||
usz capacity;
|
||||
}
|
||||
|
||||
struct FixedBlockPoolEntry
|
||||
{
|
||||
void *previous;
|
||||
}
|
||||
|
||||
<*
|
||||
Fixed blocks pool pre-allocating blocks backed by an Allocator which are then reserved for the user,
|
||||
blocks deallocated by the user are later re-used by future blocks allocations
|
||||
|
||||
`grow_capacity` can be changed in order to affect how many blocks will be allocated by next pool allocation,
|
||||
it has to be greater than 0
|
||||
`allocated` number of allocated blocks
|
||||
`used` number of used blocks by the user
|
||||
*>
|
||||
struct FixedBlockPool
|
||||
{
|
||||
Allocator allocator;
|
||||
FixedBlockPoolNode head;
|
||||
FixedBlockPoolNode *tail;
|
||||
void *next_free;
|
||||
void *freelist;
|
||||
usz block_size;
|
||||
usz grow_capacity;
|
||||
usz allocated;
|
||||
usz page_size;
|
||||
usz alignment;
|
||||
usz used;
|
||||
bool initialized;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool
|
||||
|
||||
@param [in] allocator : "The allocator to use"
|
||||
@param block_size : "The block size to use"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@param alignment : "The alignment of the buffer"
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
@require block_size > 0 : "Block size must be non zero"
|
||||
@require calculate_actual_capacity(capacity, block_size) * block_size >= block_size
|
||||
: "Total memory would overflow"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_size, usz capacity = INITIAL_CAPACITY, usz alignment = 0)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.tail = &self.head;
|
||||
self.head.next = null;
|
||||
self.block_size = math::max(block_size, FixedBlockPoolEntry.sizeof);
|
||||
capacity = calculate_actual_capacity(capacity, self.block_size);
|
||||
self.alignment = allocator::alignment_for_allocation(alignment);
|
||||
self.page_size = capacity * self.block_size;
|
||||
assert(self.page_size >= self.block_size, "Total memory would overflow %d %d", block_size, capacity);
|
||||
self.head.buffer = self.allocate_page();
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(self.head.buffer, self.page_size);
|
||||
$endif
|
||||
self.head.capacity = capacity;
|
||||
self.next_free = self.head.buffer;
|
||||
self.freelist = null;
|
||||
self.grow_capacity = capacity;
|
||||
self.initialized = true;
|
||||
self.allocated = capacity;
|
||||
self.used = 0;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool
|
||||
|
||||
@param [in] allocator : "The allocator to use"
|
||||
@param $Type : "The type used for setting the block size"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.init_for_type(&self, Allocator allocator, $Type, usz capacity = INITIAL_CAPACITY)
|
||||
{
|
||||
return self.init(allocator, $Type.sizeof, capacity, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool using Temporary allocator
|
||||
|
||||
@param $Type : "The type used for setting the block size"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.tinit_for_type(&self, $Type, usz capacity = INITIAL_CAPACITY) => self.init_for_type(tmem, $Type, capacity);
|
||||
|
||||
<*
|
||||
Initialize an block pool using Temporary allocator
|
||||
|
||||
@param block_size : "The block size to use"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.tinit(&self, usz block_size, usz capacity = INITIAL_CAPACITY) => self.init(tmem, block_size, capacity);
|
||||
|
||||
<*
|
||||
Free up the entire block pool
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn void FixedBlockPool.free(&self)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(self.head.buffer, self.page_size);
|
||||
$endif
|
||||
self.free_page(self.head.buffer);
|
||||
FixedBlockPoolNode* iter = self.head.next;
|
||||
|
||||
while (iter)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(iter.buffer, self.page_size);
|
||||
$endif
|
||||
self.free_page(iter.buffer);
|
||||
FixedBlockPoolNode* current = iter;
|
||||
iter = iter.next;
|
||||
allocator::free(self.allocator, current);
|
||||
}
|
||||
self.initialized = false;
|
||||
self.allocated = 0;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate an block on the block pool, re-uses previously deallocated blocks
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn void* FixedBlockPool.alloc(&self)
|
||||
{
|
||||
defer self.used++;
|
||||
|
||||
if (self.freelist)
|
||||
{
|
||||
FixedBlockPoolEntry* entry = self.freelist;
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(entry, self.block_size);
|
||||
$endif
|
||||
self.freelist = entry.previous;
|
||||
mem::clear(entry, self.block_size);
|
||||
return entry;
|
||||
}
|
||||
|
||||
void* end = self.tail.buffer + (self.tail.capacity * self.block_size);
|
||||
if (self.next_free >= end) self.new_node();
|
||||
void* ptr = self.next_free;
|
||||
self.next_free += self.block_size;
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(ptr, self.block_size);
|
||||
$endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
<*
|
||||
Deallocate a block from the block pool
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
@require self.check_ptr(ptr) : "The pointer should be part of the pool"
|
||||
*>
|
||||
fn void FixedBlockPool.dealloc(&self, void* ptr)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
|
||||
mem::set(ptr, 0xAA, self.block_size);
|
||||
$endif
|
||||
|
||||
FixedBlockPoolEntry* entry = ptr;
|
||||
entry.previous = self.freelist;
|
||||
self.freelist = entry;
|
||||
self.used--;
|
||||
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(ptr, self.block_size);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn bool FixedBlockPool.check_ptr(&self, void *ptr) @local
|
||||
{
|
||||
FixedBlockPoolNode* iter = &self.head;
|
||||
|
||||
while (iter)
|
||||
{
|
||||
void* end = iter.buffer + (iter.capacity * self.block_size);
|
||||
if (ptr >= iter.buffer && ptr < end) return true;
|
||||
iter = iter.next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.grow_capacity > 0 : "How many blocks will it store"
|
||||
*>
|
||||
fn void FixedBlockPool.new_node(&self) @local
|
||||
{
|
||||
FixedBlockPoolNode* node = allocator::new(self.allocator, FixedBlockPoolNode);
|
||||
node.buffer = self.allocate_page();
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(node.buffer, self.page_size);
|
||||
$endif
|
||||
node.capacity = self.grow_capacity;
|
||||
self.tail.next = node;
|
||||
self.tail = node;
|
||||
self.next_free = node.buffer;
|
||||
self.allocated += node.capacity;
|
||||
}
|
||||
|
||||
macro void* FixedBlockPool.allocate_page(&self) @private
|
||||
{
|
||||
return self.alignment > mem::DEFAULT_MEM_ALIGNMENT
|
||||
? allocator::calloc_aligned(self.allocator, self.page_size, self.alignment)!!
|
||||
: allocator::calloc(self.allocator, self.page_size);
|
||||
}
|
||||
|
||||
macro void FixedBlockPool.free_page(&self, void* page) @private
|
||||
{
|
||||
if (self.alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
allocator::free_aligned(self.allocator, page);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator::free(self.allocator, page);
|
||||
}
|
||||
}
|
||||
|
||||
macro usz calculate_actual_capacity(usz capacity, usz block_size) @private
|
||||
{
|
||||
// Assume some overhead
|
||||
if (capacity) return capacity;
|
||||
capacity = (mem::os_pagesize() - 128) / block_size;
|
||||
return capacity ?: 1;
|
||||
}
|
||||
353
lib/std/core/os/mem_vm.c3
Normal file
353
lib/std/core/os/mem_vm.c3
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ fn char[]? WasmMemory.allocate_block(&self, usz bytes)
|
||||
}
|
||||
|
||||
usz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE;
|
||||
if ($$wasm_memory_grow(0, blocks_required) == -1) return mem::OUT_OF_MEMORY?;
|
||||
if ($$wasm_memory_grow(0, blocks_required) == -1) return mem::OUT_OF_MEMORY~;
|
||||
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
defer self.use += bytes;
|
||||
return ((char*)self.use)[:bytes];
|
||||
|
||||
@@ -79,7 +79,7 @@ fn SegmentCommand64*? find_segment(MachHeader* header, char* segname)
|
||||
}
|
||||
command = (void*)command + command.cmdsize;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
fn Section64*? find_section(SegmentCommand64* command, char* sectname)
|
||||
{
|
||||
@@ -89,7 +89,7 @@ fn Section64*? find_section(SegmentCommand64* command, char* sectname)
|
||||
if (name_cmp(sectname, §ion.sectname)) return section;
|
||||
section++;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type)
|
||||
|
||||
@@ -12,7 +12,10 @@ macro int @main_to_err_main(#m, int, char**)
|
||||
if (catch #m()) return 1;
|
||||
return 0;
|
||||
}
|
||||
macro int @main_to_int_main(#m, int, char**) => #m();
|
||||
macro int @main_to_int_main(#m, int, char**)
|
||||
{
|
||||
return #m();
|
||||
}
|
||||
macro int @main_to_void_main(#m, int, char**)
|
||||
{
|
||||
#m();
|
||||
@@ -63,8 +66,19 @@ macro int @main_to_void_main_args(#m, int argc, char** argv)
|
||||
}
|
||||
|
||||
module std::core::main_stub @if(env::WIN32);
|
||||
import std::os::win32;
|
||||
|
||||
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @extern("CommandLineToArgvW");
|
||||
macro win32_set_utf8_codepage() @local
|
||||
{
|
||||
// By default windows uses an OEM codepage that differs based on locale
|
||||
// and does not support printing utf-8 characters. This allows both
|
||||
// printing utf-8 characters from strings and reading them from stdin.
|
||||
win32::setConsoleCP(UTF8);
|
||||
win32::setConsoleOutputCP(UTF8);
|
||||
}
|
||||
|
||||
|
||||
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @cname("CommandLineToArgvW");
|
||||
|
||||
macro String[] win_command_line_to_strings(ushort* cmd_line) @private
|
||||
{
|
||||
@@ -93,18 +107,26 @@ macro void release_wargs(String[] list) @private
|
||||
|
||||
macro int @win_to_err_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
if (catch #m()) return 1;
|
||||
return 0;
|
||||
}
|
||||
macro int @win_to_int_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd) => #m();
|
||||
macro int @win_to_int_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
return #m();
|
||||
}
|
||||
|
||||
macro int @win_to_void_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
#m();
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(args)) return 1;
|
||||
@@ -113,6 +135,7 @@ macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd
|
||||
|
||||
macro int @win_to_int_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
return #m(args);
|
||||
@@ -120,6 +143,7 @@ macro int @win_to_int_main_args(#m, void* handle, void* prev_handle, Char16* cmd
|
||||
|
||||
macro int @win_to_void_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
#m(args);
|
||||
@@ -128,6 +152,7 @@ macro int @win_to_void_main_args(#m, void* handle, void* prev_handle, Char16* cm
|
||||
|
||||
macro int @win_to_err_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(handle, prev_handle, args, show_cmd)) return 1;
|
||||
@@ -136,6 +161,7 @@ macro int @win_to_err_main(#m, void* handle, void* prev_handle, Char16* cmd_line
|
||||
|
||||
macro int @win_to_int_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
return #m(handle, prev_handle, args, show_cmd);
|
||||
@@ -143,6 +169,7 @@ macro int @win_to_int_main(#m, void* handle, void* prev_handle, Char16* cmd_line
|
||||
|
||||
macro int @win_to_void_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
#m(handle, prev_handle, args, show_cmd);
|
||||
@@ -151,6 +178,7 @@ macro int @win_to_void_main(#m, void* handle, void* prev_handle, Char16* cmd_lin
|
||||
|
||||
macro int @wmain_to_err_main_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(args)) return 1;
|
||||
@@ -159,6 +187,7 @@ macro int @wmain_to_err_main_args(#m, int argc, Char16** argv)
|
||||
|
||||
macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
return #m(args);
|
||||
@@ -166,6 +195,7 @@ macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
|
||||
|
||||
macro int @_wmain_runner(#m, int argc, Char16** argv)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
return #m(args) ? 0 : 1;
|
||||
@@ -173,6 +203,7 @@ macro int @_wmain_runner(#m, int argc, Char16** argv)
|
||||
|
||||
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
#m(args);
|
||||
|
||||
132
lib/std/core/refcount.c3
Normal file
132
lib/std/core/refcount.c3
Normal 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) ||| $typeof((Type){}.dealloc()) == void : "'dealloc' must return 'void'"
|
||||
*>
|
||||
module std::core::mem::ref <Type>;
|
||||
import std::thread, std::atomic;
|
||||
|
||||
const OVERALIGNED @private = Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
alias DeallocFn = fn void(void*);
|
||||
|
||||
fn Ref wrap(Type* ptr, Allocator allocator = mem)
|
||||
{
|
||||
return { .refcount = allocator::new(allocator, Atomic{int}), .ptr = ptr, .allocator = allocator };
|
||||
}
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $defined(Type a = $vaexpr[0]) : "The first argument must be an initializer for the type"
|
||||
*>
|
||||
macro Ref new(..., Allocator allocator = mem)
|
||||
{
|
||||
|
||||
$switch:
|
||||
$case OVERALIGNED && !$vacount:
|
||||
Type* ptr = allocator::calloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
|
||||
$case OVERALIGNED:
|
||||
Type* ptr = allocator::malloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
|
||||
*ptr = $vaexpr[0];
|
||||
$case !$vacount:
|
||||
Type* ptr = allocator::calloc(allocator, Type.sizeof);
|
||||
$default:
|
||||
Type* ptr = allocator::malloc(allocator, Type.sizeof);
|
||||
*ptr = $vaexpr[0];
|
||||
$endswitch
|
||||
return { .refcount = allocator::new(allocator, Atomic{int}),
|
||||
.ptr = ptr,
|
||||
.allocator = allocator };
|
||||
}
|
||||
|
||||
struct Ref
|
||||
{
|
||||
Atomic{int}* refcount;
|
||||
Type* ptr;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
fn Ref* Ref.retain(&self)
|
||||
{
|
||||
assert(self.refcount != null, "Reference already released");
|
||||
assert(self.refcount.load(RELAXED) >= 0, "Retaining zombie");
|
||||
self.refcount.add(1, RELAXED);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn void Ref.release(&self)
|
||||
{
|
||||
assert(self.refcount != null, "Reference already released");
|
||||
assert(self.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
|
||||
if (self.refcount.sub(1, RELAXED) == 0)
|
||||
{
|
||||
thread::fence(ACQUIRE);
|
||||
$if $defined(Type.dealloc):
|
||||
self.ptr.dealloc();
|
||||
$endif
|
||||
$if OVERALIGNED:
|
||||
allocator::free_aligned(self.allocator, self.ptr);
|
||||
$else
|
||||
allocator::free(self.allocator, self.ptr);
|
||||
$endif
|
||||
allocator::free(self.allocator, self.refcount);
|
||||
*self = {};
|
||||
}
|
||||
}
|
||||
|
||||
module std::core::mem::rc;
|
||||
import std::thread, std::atomic;
|
||||
|
||||
<*
|
||||
A RefCounted struct should be an inline base of a struct.
|
||||
If a `dealloc` is defined, then it will be called rather than `free`
|
||||
|
||||
For convenience, a ref count of 0 is still valid, and the struct is
|
||||
only freed when when ref count drops to -1.
|
||||
|
||||
The macros rc::retain and rc::release must be used on the full pointer,
|
||||
not on the RefCounted substruct.
|
||||
|
||||
So `Foo* f = ...; RefCounted* rc = f; rc::release(rc);` will not do the right thing.
|
||||
*>
|
||||
struct RefCounted
|
||||
{
|
||||
Atomic{int} refcount;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
|
||||
*>
|
||||
macro retain(refcounted)
|
||||
{
|
||||
if (refcounted)
|
||||
{
|
||||
assert(refcounted.refcount.load(RELAXED) >= 0, "Retaining zombie");
|
||||
refcounted.refcount.add(1, RELAXED);
|
||||
}
|
||||
return refcounted;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
|
||||
@require !$defined(refcounted.dealloc()) ||| $typeof(refcounted.dealloc()) == void
|
||||
: "Expected refcounted type to have a valid dealloc"
|
||||
*>
|
||||
macro void release(refcounted)
|
||||
{
|
||||
if (!refcounted) return;
|
||||
assert(refcounted.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
|
||||
if (refcounted.refcount.sub(1, RELAXED) == 0)
|
||||
{
|
||||
thread::fence(ACQUIRE);
|
||||
$if $defined(refcounted.dealloc):
|
||||
refcounted.dealloc();
|
||||
$else
|
||||
free(refcounted);
|
||||
$endif
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ macro @enum_lookup($Type, #value, value)
|
||||
$foreach $val : $Type.values:
|
||||
if ($val.#value == value) return $val;
|
||||
$endforeach
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
macro @enum_lookup_new($Type, $name, value)
|
||||
@@ -35,14 +35,14 @@ macro @enum_lookup_new($Type, $name, value)
|
||||
$foreach $val : $Type.values:
|
||||
if ($val.$eval($name) == value) return $val;
|
||||
$endforeach
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
|
||||
module std::core::runtime @if(WASM_NOLIBC);
|
||||
module std::core::runtime @if(env::FREESTANDING_WASM);
|
||||
|
||||
extern fn void __wasm_call_ctors();
|
||||
fn void wasm_initialize() @extern("_initialize") @wasm
|
||||
fn void wasm_initialize() @cname("_initialize") @wasm
|
||||
{
|
||||
// The linker synthesizes this to call constructors.
|
||||
__wasm_call_ctors();
|
||||
|
||||
@@ -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,49 @@ fn void set_benchmark_max_iterations(uint value) @builtin
|
||||
{
|
||||
assert(value > 0);
|
||||
benchmark_max_iterations = value;
|
||||
foreach (k : bench_fn_iters.key_iter()) bench_fn_iters[k] = value;
|
||||
}
|
||||
|
||||
fn void set_benchmark_func_iterations(String func, uint value) @builtin
|
||||
{
|
||||
assert(value > 0);
|
||||
bench_fn_iters[func] = value;
|
||||
}
|
||||
|
||||
|
||||
Clock benchmark_clock @local;
|
||||
NanoDuration benchmark_nano_seconds @local;
|
||||
long cycle_start @local;
|
||||
long cycle_stop @local;
|
||||
DString benchmark_log @local;
|
||||
bool benchmark_warming @local;
|
||||
uint this_iteration @local;
|
||||
bool benchmark_stop @local;
|
||||
|
||||
macro void @start_benchmark()
|
||||
{
|
||||
benchmark_clock = clock::now();
|
||||
cycle_start = $$sysclock();
|
||||
}
|
||||
|
||||
macro void @end_benchmark()
|
||||
{
|
||||
benchmark_nano_seconds = benchmark_clock.mark();
|
||||
cycle_stop = $$sysclock();
|
||||
}
|
||||
|
||||
macro void @kill_benchmark(String format, ...)
|
||||
{
|
||||
@log_benchmark(format, $vasplat);
|
||||
benchmark_stop = true;
|
||||
}
|
||||
|
||||
macro void @log_benchmark(msg, args...) => @pool()
|
||||
{
|
||||
if (benchmark_warming) return;
|
||||
|
||||
benchmark_log.appendf("%s [%d]: ", $$FUNC, this_iteration);
|
||||
benchmark_log.appendfn(msg, ...args);
|
||||
}
|
||||
|
||||
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
|
||||
@@ -58,36 +104,69 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
|
||||
|
||||
name.clear();
|
||||
|
||||
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();
|
||||
sys_clock_started = $$sysclock();
|
||||
NanoDuration running_timer;
|
||||
long total_clocks;
|
||||
|
||||
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;
|
||||
uint print_step = current_benchmark_iterations / 100;
|
||||
|
||||
for (this_iteration = 0; this_iteration < current_benchmark_iterations; ++this_iteration, benchmark_nano_seconds = {})
|
||||
{
|
||||
if (0 == this_iteration % print_step) // only print right about when the % will update
|
||||
{
|
||||
perc_str[0..(uint)math::floor((this_iteration / (float)current_benchmark_iterations) * 20)] = '#';
|
||||
perc = (uint)math::ceil(100 * (this_iteration / (float)current_benchmark_iterations));
|
||||
|
||||
io::printf("\r%s [%s] %d / %d (%d%%)", name.str_view(), (ZString)perc_str, this_iteration, current_benchmark_iterations, perc);
|
||||
io::stdout().flush()!!;
|
||||
}
|
||||
|
||||
@start_benchmark(); // can be overridden by calls inside the unit's func
|
||||
|
||||
unit.func() @inline;
|
||||
if (benchmark_stop) return false;
|
||||
|
||||
if (benchmark_nano_seconds == (NanoDuration){}) @end_benchmark(); // only mark when it wasn't already by the unit.func
|
||||
|
||||
total_clocks += cycle_stop - cycle_start;
|
||||
running_timer += benchmark_nano_seconds;
|
||||
}
|
||||
|
||||
sys_clock_finished = $$sysclock();
|
||||
NanoDuration nano_seconds = clock.mark();
|
||||
sys_clocks = sys_clock_finished - sys_clock_started;
|
||||
float clock_cycles = (float)total_clocks / current_benchmark_iterations;
|
||||
float measurement = (float)running_timer / current_benchmark_iterations;
|
||||
String[] units = { "nanoseconds", "microseconds", "milliseconds", "seconds" };
|
||||
|
||||
io::printfn("[COMPLETE] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
|
||||
float adjusted_measurement = measurement;
|
||||
while (adjusted_measurement > 1_000) adjusted_measurement /= 1_000;
|
||||
float adjusted_runtime_total = (float)running_timer;
|
||||
while (adjusted_runtime_total > 1_000) adjusted_runtime_total /= 1_000;
|
||||
|
||||
io::printf("\r%s ", name.str_view());
|
||||
io::printfn(
|
||||
"[COMPLETE] %.2f %s, %.2f CPU clocks, %d iterations (runtime %.2f %s)",
|
||||
adjusted_measurement,
|
||||
units[math::min(3, (int)math::floor(math::log(measurement, 1_000)))],
|
||||
clock_cycles,
|
||||
current_benchmark_iterations,
|
||||
adjusted_runtime_total,
|
||||
units[math::min(3, (int)math::floor(math::log((float)running_timer, 1_000)))],
|
||||
);
|
||||
}
|
||||
|
||||
io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : "");
|
||||
@@ -96,5 +175,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));
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
module std::core::runtime;
|
||||
import std::core::test @public;
|
||||
import std::core::mem::allocator @public;
|
||||
import libc, std::time, std::io, std::sort;
|
||||
import std::os::env;
|
||||
import libc, std::time, std::io, std::sort, std::os;
|
||||
|
||||
|
||||
alias TestFn = fn void();
|
||||
|
||||
@@ -14,10 +14,12 @@ TestContext* test_context @private;
|
||||
struct TestContext
|
||||
{
|
||||
JmpBuf buf;
|
||||
// Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add'
|
||||
<* Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add' *>
|
||||
String test_filter;
|
||||
// Triggers debugger breakpoint when assert or test:: checks failed
|
||||
<* Triggers debugger breakpoint when assert or test:: checks failed *>
|
||||
bool breakpoint_on_assert;
|
||||
<* Controls level of printed logs *>
|
||||
LogPriority log_level;
|
||||
|
||||
// internal state
|
||||
bool assert_print_backtrace;
|
||||
@@ -25,6 +27,8 @@ struct TestContext
|
||||
bool is_in_panic;
|
||||
bool is_quiet_mode;
|
||||
bool is_no_capture;
|
||||
bool sort;
|
||||
bool check_leaks;
|
||||
String current_test_name;
|
||||
TestFn setup_fn;
|
||||
TestFn teardown_fn;
|
||||
@@ -86,7 +90,21 @@ fn bool terminal_has_ansi_codes() @local => @pool()
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void sig_bus_error(CInt i, void*, void* context) @local @if(env::POSIX)
|
||||
{
|
||||
panic_test("Bus error", "Unknown", "Unknown", 1, posix::stack_instruction(context));
|
||||
}
|
||||
|
||||
fn void sig_segmentation_fault(CInt i, void*, void* context) @local @if(env::POSIX)
|
||||
{
|
||||
panic_test("Segmentation fault", "Unknown", "Unknown", 1, posix::stack_instruction(context));
|
||||
}
|
||||
|
||||
fn void test_panic(String message, String file, String function, uint line) @local
|
||||
{
|
||||
panic_test(message, file, function, line);
|
||||
}
|
||||
fn void panic_test(String message, String file, String function, uint line, void* extra_trace = null) @local
|
||||
{
|
||||
if (test_context.is_in_panic) return;
|
||||
test_context.is_in_panic = true;
|
||||
@@ -96,7 +114,7 @@ fn void test_panic(String message, String file, String function, uint line) @loc
|
||||
if (test_context.assert_print_backtrace)
|
||||
{
|
||||
$if env::NATIVE_STACKTRACE:
|
||||
builtin::print_backtrace(message, 0);
|
||||
builtin::print_backtrace(message, extra_trace ? 3 : 0, extra_trace);
|
||||
$endif
|
||||
}
|
||||
io::printf("\nTest failed ^^^ ( %s:%s ) %s\n", file, line, message);
|
||||
@@ -140,7 +158,6 @@ fn void unmute_output(bool has_error) @local
|
||||
usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
|
||||
if (has_error)
|
||||
{
|
||||
io::printf("\nTesting %s ", test_context.current_test_name);
|
||||
io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
|
||||
}
|
||||
|
||||
@@ -166,11 +183,19 @@ fn void unmute_output(bool has_error) @local
|
||||
(void)stdout.flush();
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
$if !env::NO_LIBC && env::POSIX:
|
||||
posix::install_signal_handler(libc::SIGBUS, &sig_bus_error);
|
||||
posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
|
||||
$endif
|
||||
foreach (&unit : tests)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
@@ -179,6 +204,9 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
{
|
||||
.assert_print_backtrace = true,
|
||||
.breakpoint_on_assert = false,
|
||||
.sort = true,
|
||||
.check_leaks = true,
|
||||
.log_level = LogPriority.ERROR,
|
||||
.test_filter = "",
|
||||
.has_ansi_codes = terminal_has_ansi_codes(),
|
||||
.stored.allocator = mem,
|
||||
@@ -192,10 +220,11 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
case "--test-breakpoint":
|
||||
context.breakpoint_on_assert = true;
|
||||
case "--test-nosort":
|
||||
sort_tests = false;
|
||||
context.sort = false;
|
||||
case "--test-noleak":
|
||||
check_leaks = false;
|
||||
context.check_leaks = false;
|
||||
case "--test-nocapture":
|
||||
case "--test-show-output":
|
||||
context.is_no_capture = true;
|
||||
case "--noansi":
|
||||
context.has_ansi_codes = false;
|
||||
@@ -211,13 +240,30 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
}
|
||||
context.test_filter = args[i + 1];
|
||||
i++;
|
||||
case "--test-log-level":
|
||||
if (i == args.len - 1)
|
||||
{
|
||||
io::printn("Missing log level for argument `--test-log-level`.");
|
||||
return false;
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
String upper = args[i + 1].to_upper_copy(tmem);
|
||||
if (catch @try(context.log_level, enum_by_name(LogPriority, upper)))
|
||||
{
|
||||
io::printn("Log level given to `--test-log-level` is not one of verbose, debug, info, warn, error or critical.");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
i++;
|
||||
default:
|
||||
io::printfn("Unknown argument: %s", args[i]);
|
||||
}
|
||||
}
|
||||
test_context = &context;
|
||||
log::set_priority_all(test_context.log_level);
|
||||
|
||||
if (sort_tests)
|
||||
if (context.sort)
|
||||
{
|
||||
quicksort(tests, &cmp_test_unit);
|
||||
}
|
||||
@@ -277,14 +323,17 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
{
|
||||
mute_output();
|
||||
mem.clear();
|
||||
if (check_leaks) allocator::thread_allocator = &mem;
|
||||
unit.func();
|
||||
if (context.check_leaks) allocator::thread_allocator = &mem;
|
||||
@pool()
|
||||
{
|
||||
unit.func();
|
||||
};
|
||||
// track cleanup that may take place in teardown_fn
|
||||
if (context.teardown_fn)
|
||||
{
|
||||
context.teardown_fn();
|
||||
}
|
||||
if (check_leaks) allocator::thread_allocator = context.stored.allocator;
|
||||
if (context.check_leaks) allocator::thread_allocator = context.stored.allocator;
|
||||
|
||||
unmute_output(false); // all good, discard output
|
||||
if (mem.has_leaks())
|
||||
@@ -305,7 +354,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: ",
|
||||
|
||||
@@ -29,11 +29,11 @@ alias ErrorCallback = fn void (ZString);
|
||||
@param addr : "Start of memory region."
|
||||
@param size : "Size of memory region."
|
||||
*>
|
||||
macro poison_memory_region(void* addr, usz size)
|
||||
macro void poison_memory_region(void* addr, usz size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__asan_poison_memory_region(addr, size);
|
||||
$endif
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -50,11 +50,11 @@ macro poison_memory_region(void* addr, usz size)
|
||||
@param addr : "Start of memory region."
|
||||
@param size : "Size of memory region."
|
||||
*>
|
||||
macro unpoison_memory_region(void* addr, usz size)
|
||||
macro void unpoison_memory_region(void* addr, usz size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__asan_unpoison_memory_region(addr, size);
|
||||
$endif
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
module std::core::array::slice {Type};
|
||||
module std::core::array;
|
||||
|
||||
<*
|
||||
A slice2d allows slicing an array like int[10][10] into an arbitrary "int[][]"-like counterpart
|
||||
Typically you'd use array::slice2d(...) to create one.
|
||||
*>
|
||||
struct Slice2d
|
||||
struct Slice2d <Type>
|
||||
{
|
||||
Type* ptr;
|
||||
usz inner_len;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module std::core::string;
|
||||
import std::io;
|
||||
import std::io, std::ascii;
|
||||
|
||||
|
||||
typedef String @if(!$defined(String)) = inline char[];
|
||||
<*
|
||||
@@ -69,7 +70,7 @@ macro WString @wstring(String $string) @builtin
|
||||
}
|
||||
|
||||
<*
|
||||
Create a slice of an UTF32 encoded string at compile time.
|
||||
Create a slice of an UTF16 encoded string at compile time.
|
||||
|
||||
@param $string : "The string to encode"
|
||||
*>
|
||||
@@ -107,6 +108,28 @@ 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, the resulting string must fit the buffer.
|
||||
|
||||
@param [inout] buffer : `The buffer to use`
|
||||
@param [in] fmt : `The formatting string`
|
||||
*>
|
||||
fn String bformat(char[] buffer, String fmt, args...) @format(1)
|
||||
{
|
||||
Formatter f;
|
||||
OutputFn format_fn = fn void?(void* buf, char c) {
|
||||
char[]* buffer_ref = buf;
|
||||
char[] buffer = *buffer_ref;
|
||||
if (buffer.len == 0) return io::BUFFER_EXCEEDED~;
|
||||
buffer[0] = c;
|
||||
*buffer_ref = buffer[1..];
|
||||
};
|
||||
char[] buffer_copy = buffer;
|
||||
f.init(format_fn, &buffer_copy);
|
||||
usz len = f.vprintf(fmt, args)!!;
|
||||
return (String)buffer[:len];
|
||||
}
|
||||
|
||||
<*
|
||||
Return a temporary String created using the formatting function.
|
||||
|
||||
@@ -123,7 +146,7 @@ fn String tformat(String fmt, args...) @format(0)
|
||||
Check if a character is in a set.
|
||||
|
||||
@param c : `the character to check`
|
||||
@param [in] set : `The formatting string`
|
||||
@param [in] set : `String containing the characters`
|
||||
@pure
|
||||
@return `True if a character is in the set`
|
||||
*>
|
||||
@@ -167,13 +190,18 @@ fn String join(Allocator allocator, String[] s, String joiner)
|
||||
@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
|
||||
fn String String.replace(self, Allocator allocator, String needle, String new_str) @nodiscard => @pool()
|
||||
{
|
||||
@pool()
|
||||
Splitter s = self.tokenize_all(needle);
|
||||
DString d;
|
||||
d.init(tmem, new_str.len * 2 + self.len + 16);
|
||||
(void)d.append(s.next());
|
||||
while (try element = s.next())
|
||||
{
|
||||
String[] split = self.tsplit(needle);
|
||||
return dstring::join(tmem, split, new_str).copy_str(mem);
|
||||
};
|
||||
d.append(new_str);
|
||||
d.append(element);
|
||||
}
|
||||
return d.copy_str(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -186,8 +214,16 @@ fn String String.replace(self, Allocator allocator, String needle, String new_st
|
||||
*>
|
||||
fn String String.treplace(self, String needle, String new_str)
|
||||
{
|
||||
String[] split = self.tsplit(needle);
|
||||
return dstring::join(tmem, split, new_str).str_view();
|
||||
Splitter s = self.tokenize_all(needle);
|
||||
DString d;
|
||||
d.init(tmem, new_str.len * 2 + self.len + 16);
|
||||
(void)d.append(s.next());
|
||||
while (try element = s.next())
|
||||
{
|
||||
d.append(new_str);
|
||||
d.append(element);
|
||||
}
|
||||
return d.str_view();
|
||||
}
|
||||
|
||||
|
||||
@@ -199,11 +235,28 @@ fn String String.treplace(self, String needle, String new_str)
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim(self, String to_trim = "\t\n\r ")
|
||||
fn String String.trim(self, String to_trim = " \n\t\r\f\v")
|
||||
{
|
||||
return self.trim_left(to_trim).trim_right(to_trim);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove characters from the front and end of a string.
|
||||
|
||||
@param [in] self : `The string to trim`
|
||||
@param to_trim : `The set of characters to trim, defaults to whitespace`
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim_charset(self, AsciiCharset to_trim = ascii::WHITESPACE_SET)
|
||||
{
|
||||
usz start = 0;
|
||||
usz len = self.len;
|
||||
while (start < len && to_trim.contains(self[start])) start++;
|
||||
while (len > start && to_trim.contains(self[len - 1])) len--;
|
||||
return self[start..len - 1];
|
||||
}
|
||||
|
||||
<*
|
||||
Remove characters from the front of a string.
|
||||
|
||||
@@ -212,7 +265,7 @@ fn String String.trim(self, String to_trim = "\t\n\r ")
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim_left(self, String to_trim = "\t\n\r ")
|
||||
fn String String.trim_left(self, String to_trim = " \n\t\r\f\v")
|
||||
{
|
||||
usz start = 0;
|
||||
usz len = self.len;
|
||||
@@ -229,7 +282,7 @@ fn String String.trim_left(self, String to_trim = "\t\n\r ")
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim_right(self, String to_trim = "\t\n\r ")
|
||||
fn String String.trim_right(self, String to_trim = " \n\t\r\f\v")
|
||||
{
|
||||
usz len = self.len;
|
||||
while (len > 0 && char_in_set(self[len - 1], to_trim)) len--;
|
||||
@@ -315,7 +368,7 @@ fn String[] String.split(self, Allocator allocator, String delimiter, usz max =
|
||||
bool no_more = false;
|
||||
while (!no_more)
|
||||
{
|
||||
usz? index = i == max - 1 ? NOT_FOUND? : self.index_of(delimiter);
|
||||
usz? index = i == max - 1 ? NOT_FOUND~ : self.index_of(delimiter);
|
||||
String res @noinit;
|
||||
if (try index)
|
||||
{
|
||||
@@ -374,7 +427,7 @@ fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz ma
|
||||
bool no_more = false;
|
||||
while (!no_more)
|
||||
{
|
||||
usz? index = i == max - 1 ? NOT_FOUND? : s.index_of(delimiter);
|
||||
usz? index = i == max - 1 ? NOT_FOUND~ : s.index_of(delimiter);
|
||||
String res @noinit;
|
||||
if (try index)
|
||||
{
|
||||
@@ -392,7 +445,7 @@ fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz ma
|
||||
}
|
||||
if (i == max_capacity)
|
||||
{
|
||||
return BUFFER_EXCEEDED?;
|
||||
return BUFFER_EXCEEDED~;
|
||||
}
|
||||
buffer[i++] = res;
|
||||
}
|
||||
@@ -412,6 +465,19 @@ fn bool String.contains(s, String substr)
|
||||
return @ok(s.index_of(substr));
|
||||
}
|
||||
|
||||
<*
|
||||
Check if a character is found in the string.
|
||||
|
||||
@param [in] s
|
||||
@param character : "The character to look for."
|
||||
@pure
|
||||
@return "true if the string contains the character, false otherwise"
|
||||
*>
|
||||
fn bool String.contains_char(s, char character)
|
||||
{
|
||||
return @ok(s.index_of_char(character));
|
||||
}
|
||||
|
||||
<*
|
||||
Check how many non-overlapping instances of a substring there is.
|
||||
|
||||
@@ -460,7 +526,7 @@ fn usz? String.index_of_char(self, char character)
|
||||
{
|
||||
if (c == character) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -483,7 +549,7 @@ fn usz? String.index_of_chars(String self, char[] characters)
|
||||
}
|
||||
}
|
||||
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -500,12 +566,12 @@ fn usz? String.index_of_chars(String self, char[] characters)
|
||||
fn usz? String.index_of_char_from(self, char character, usz start_index)
|
||||
{
|
||||
usz len = self.len;
|
||||
if (len <= start_index) return NOT_FOUND?;
|
||||
if (len <= start_index) return NOT_FOUND~;
|
||||
for (usz i = start_index; i < len; i++)
|
||||
{
|
||||
if (self[i] == character) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -524,7 +590,7 @@ fn usz? String.rindex_of_char(self, char character)
|
||||
{
|
||||
if (c == character) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -549,7 +615,7 @@ fn usz? String.index_of(self, String substr)
|
||||
if (c == first && self[i : needed] == substr) return i;
|
||||
}
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -574,7 +640,7 @@ fn usz? String.rindex_of(self, String substr)
|
||||
if (c == first && self[i : needed] == substr) return i;
|
||||
}
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool ZString.eq(self, ZString other) @operator(==)
|
||||
@@ -582,6 +648,7 @@ fn bool ZString.eq(self, ZString other) @operator(==)
|
||||
char* a = self;
|
||||
char* b = other;
|
||||
if (a == b) return true;
|
||||
if (!a || !b) return false;
|
||||
for (;; a++, b++)
|
||||
{
|
||||
char c = *a;
|
||||
@@ -608,12 +675,17 @@ fn usz ZString.char_len(str)
|
||||
|
||||
fn usz ZString.len(self)
|
||||
{
|
||||
usz len = 0;
|
||||
char* ptr = (char*)self;
|
||||
while (char c = ptr++[0]) len++;
|
||||
usz len;
|
||||
for (char* ptr = (char*)self; *ptr; ptr++) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
fn usz WString.len(self)
|
||||
{
|
||||
usz len;
|
||||
for (Char16* ptr = (Char16*)self; *ptr; ptr++) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
fn ZString String.zstr_copy(self, Allocator allocator)
|
||||
{
|
||||
@@ -756,6 +828,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 };
|
||||
@@ -868,12 +1026,12 @@ macro String.to_integer(self, $Type, int base = 10)
|
||||
usz index = 0;
|
||||
char* ptr = self.ptr;
|
||||
while (index < len && ptr[index].is_blank()) index++;
|
||||
if (len == index) return EMPTY_STRING?;
|
||||
if (len == index) return EMPTY_STRING~;
|
||||
bool is_negative;
|
||||
switch (self[index])
|
||||
{
|
||||
case '-':
|
||||
if ($Type.min == 0) return NEGATIVE_VALUE?;
|
||||
if ($Type.min == 0) return NEGATIVE_VALUE~;
|
||||
is_negative = true;
|
||||
index++;
|
||||
case '+':
|
||||
@@ -881,7 +1039,7 @@ macro String.to_integer(self, $Type, int base = 10)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (len == index) return MALFORMED_INTEGER?;
|
||||
if (len == index) return MALFORMED_INTEGER~;
|
||||
$Type base_used = ($Type)base;
|
||||
if (self[index] == '0' && base == 10)
|
||||
{
|
||||
@@ -904,7 +1062,7 @@ macro String.to_integer(self, $Type, int base = 10)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (len == index) return MALFORMED_INTEGER?;
|
||||
if (len == index) return MALFORMED_INTEGER~;
|
||||
}
|
||||
$Type value = 0;
|
||||
while (index != len)
|
||||
@@ -914,22 +1072,18 @@ macro String.to_integer(self, $Type, int base = 10)
|
||||
{
|
||||
case base_used < 10 || c < 'A': c -= '0';
|
||||
case c <= 'F': c -= 'A' - 10;
|
||||
case c < 'a' || c > 'f': return MALFORMED_INTEGER?;
|
||||
case c < 'a' || c > 'f': return MALFORMED_INTEGER~;
|
||||
default: c -= 'a' - 10;
|
||||
}
|
||||
if (c >= base_used) return MALFORMED_INTEGER?;
|
||||
if (c >= base_used) return MALFORMED_INTEGER~;
|
||||
do
|
||||
{
|
||||
if (is_negative)
|
||||
{
|
||||
$Type new_value = value * base_used - c;
|
||||
if (new_value > value) return INTEGER_OVERFLOW?;
|
||||
value = new_value;
|
||||
value = value.overflow_mul(base_used).overflow_sub(c) ?? INTEGER_OVERFLOW~!;
|
||||
break;
|
||||
}
|
||||
$Type new_value = value * base_used + c;
|
||||
if (new_value < value) return INTEGER_OVERFLOW?;
|
||||
value = new_value;
|
||||
value = value.overflow_mul(base_used).overflow_add(c) ?? INTEGER_OVERFLOW~!;
|
||||
};
|
||||
}
|
||||
return value;
|
||||
@@ -1043,16 +1197,21 @@ fn void Splitter.reset(&self)
|
||||
self.current = 0;
|
||||
}
|
||||
|
||||
fn bool Splitter.at_end(&self)
|
||||
{
|
||||
return self.current > self.string.len;
|
||||
}
|
||||
|
||||
fn String? Splitter.next(&self)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
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?;
|
||||
if (self.type != TOKENIZE_ALL) return NO_MORE_ELEMENT~;
|
||||
self.current++;
|
||||
return self.string[current - 1:0];
|
||||
}
|
||||
@@ -1064,7 +1223,7 @@ fn String? Splitter.next(&self)
|
||||
if (!next && self.type == TOKENIZE) continue;
|
||||
return remaining[:next];
|
||||
}
|
||||
self.current = len;
|
||||
self.current = len + 1;
|
||||
return remaining;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2024 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2024-2025 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
@@ -22,43 +22,57 @@ faultdef INVALID_ESCAPE_SEQUENCE, UNTERMINATED_STRING, INVALID_HEX_ESCAPE, INVAL
|
||||
*>
|
||||
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);
|
||||
// Conservative allocation: most strings need minimal escaping
|
||||
usz initial_capacity = s.len + s.len / 5 + 2; // ~1.2x + quotes
|
||||
|
||||
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);
|
||||
if (allocator == tmem)
|
||||
{
|
||||
DString result = dstring::new_with_capacity(tmem, initial_capacity);
|
||||
escape_dstring(s, result, strip_quotes);
|
||||
return result.str_view();
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
DString result = dstring::temp_with_capacity(initial_capacity);
|
||||
escape_dstring(s, result, strip_quotes);
|
||||
return result.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
fn void escape_dstring(String s, DString result, bool strip_quotes) @private
|
||||
{
|
||||
if (!strip_quotes) result.append_char('"');
|
||||
|
||||
foreach (char c : s)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"': result.append(`\"`);
|
||||
case '\\': result.append(`\\`);
|
||||
case '\b': result.append(`\b`);
|
||||
case '\f': result.append(`\f`);
|
||||
case '\n': result.append(`\n`);
|
||||
case '\r': result.append(`\r`);
|
||||
case '\t': result.append(`\t`);
|
||||
case '\v': result.append(`\v`);
|
||||
case '\0': result.append(`\0`);
|
||||
default:
|
||||
if (c >= 32 && c <= 126)
|
||||
{
|
||||
// Printable ASCII
|
||||
result.append_char(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-printable, use hex escape
|
||||
result.appendf("\\x%02x", (uint)c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!strip_quotes) result.append_char('"');
|
||||
|
||||
}
|
||||
<*
|
||||
Escape a string using the temp allocator.
|
||||
|
||||
@@ -76,33 +90,33 @@ fn String String.tescape(String s, bool strip_quotes = false) => s.escape(tmem,
|
||||
*>
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -111,90 +125,103 @@ fn usz escape_len(String s)
|
||||
@param allocator : "The allocator to use for the result"
|
||||
@param s : "The quoted string to unescape"
|
||||
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
|
||||
@param lenient : "Be lenient with escapes, resolving unknown sequences to the escape character, defaults to false"
|
||||
@return "The unescaped string without quotes, safe to convert to ZString"
|
||||
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
|
||||
*>
|
||||
fn String? String.unescape(String s, Allocator allocator, bool allow_unquoted = false)
|
||||
fn String? String.unescape(String s, Allocator allocator, bool allow_unquoted = false, bool lenient = false)
|
||||
{
|
||||
if (s.len >= 2 && s[0] == '"' && s[^1] == '"')
|
||||
{
|
||||
// Remove quotes.
|
||||
s = s[1:^2];
|
||||
}
|
||||
else if (!allow_unquoted) return UNTERMINATED_STRING?;
|
||||
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);
|
||||
// Handle empty string case
|
||||
if (!s.len)
|
||||
{
|
||||
return "".copy(allocator);
|
||||
}
|
||||
if (allocator == tmem)
|
||||
{
|
||||
DString result = dstring::new_with_capacity(tmem, s.len);
|
||||
unescape_dstring(s, result, allow_unquoted, lenient)!;
|
||||
return result.str_view();
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
DString result = dstring::temp_with_capacity(s.len);
|
||||
unescape_dstring(s, result, allow_unquoted, lenient)!;
|
||||
return result.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
fn void? unescape_dstring(String s, DString result, bool allow_unquoted = false, bool lenient = false) @private
|
||||
{
|
||||
usz len = s.len;
|
||||
for (usz i = 0; i < len; i++)
|
||||
{
|
||||
char c = s[i];
|
||||
if (c != '\\')
|
||||
{
|
||||
result.append_char(c);
|
||||
continue;
|
||||
}
|
||||
if (c != '\\')
|
||||
{
|
||||
result.append_char(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle escape sequence
|
||||
if (i + 1 >= len) return INVALID_ESCAPE_SEQUENCE?;
|
||||
// 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);
|
||||
char escape_char = s[++i];
|
||||
switch (escape_char)
|
||||
{
|
||||
case '"': result.append_char('"');
|
||||
case '\\': result.append_char('\\');
|
||||
case '/': result.append_char('/');
|
||||
case 'b': result.append_char('\b');
|
||||
case 'f': result.append_char('\f');
|
||||
case 'n': result.append_char('\n');
|
||||
case 'r': result.append_char('\r');
|
||||
case 't': result.append_char('\t');
|
||||
case 'v': result.append_char('\v');
|
||||
case '0': result.append_char('\0');
|
||||
case 'x':
|
||||
// Hex escape \xHH
|
||||
if (i + 2 >= len) return INVALID_HEX_ESCAPE~;
|
||||
char h1 = s[++i];
|
||||
char h2 = s[++i];
|
||||
if (!h1.is_xdigit() || !h2.is_xdigit()) return INVALID_HEX_ESCAPE~;
|
||||
uint val = h1 > '9' ? (h1 | 32) - 'a' + 10 : h1 - '0';
|
||||
val = val << 4;
|
||||
val += h2 > '9' ? (h2 | 32) - 'a' + 10 : h2 - '0';
|
||||
result.append_char((char)val);
|
||||
case 'u':
|
||||
// Unicode escape \uHHHH
|
||||
if (i + 4 >= len) return INVALID_UNICODE_ESCAPE~;
|
||||
uint val;
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
char hex_char = s[++i];
|
||||
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~;
|
||||
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
|
||||
}
|
||||
result.append_char32(val);
|
||||
case 'U':
|
||||
// Unicode escape \UHHHHHHHH
|
||||
if (i + 8 >= len) return INVALID_UNICODE_ESCAPE~;
|
||||
uint val;
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
char hex_char = s[++i];
|
||||
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~;
|
||||
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
|
||||
}
|
||||
result.append_char32(val);
|
||||
default:
|
||||
if (!lenient) return INVALID_ESCAPE_SEQUENCE~;
|
||||
result.append_char(escape_char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -202,10 +229,11 @@ fn String? String.unescape(String s, Allocator allocator, bool allow_unquoted =
|
||||
|
||||
@param s : "The quoted string to unescape"
|
||||
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
|
||||
@param lenient : "Be lenient with escapes, resolving unknown sequences to the escape character, defaults to false"
|
||||
@return "The unescaped string without quotes"
|
||||
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
|
||||
*>
|
||||
fn String? String.tunescape(String s, bool allow_unquoted = false) => s.unescape(tmem, allow_unquoted);
|
||||
fn String? String.tunescape(String s, bool allow_unquoted = false, bool lenient = false) => s.unescape(tmem, allow_unquoted, lenient);
|
||||
|
||||
<*
|
||||
Check if a character needs to be escaped in a string literal.
|
||||
@@ -215,19 +243,19 @@ fn String? String.tunescape(String s, bool allow_unquoted = false) => s.unescape
|
||||
*>
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ fn Char32? StringIterator.next(&self)
|
||||
{
|
||||
usz len = self.utf8.len;
|
||||
usz current = self.current;
|
||||
if (current >= len) return NO_MORE_ELEMENT?;
|
||||
if (current >= len) return NO_MORE_ELEMENT~;
|
||||
usz read = (len - current < 4 ? len - current : 4);
|
||||
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
|
||||
self.current += read;
|
||||
@@ -26,7 +26,7 @@ fn Char32? StringIterator.peek(&self)
|
||||
{
|
||||
usz len = self.utf8.len;
|
||||
usz current = self.current;
|
||||
if (current >= len) return NO_MORE_ELEMENT?;
|
||||
if (current >= len) return NO_MORE_ELEMENT~;
|
||||
usz read = (len - current < 4 ? len - current : 4);
|
||||
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
|
||||
return res;
|
||||
@@ -43,7 +43,7 @@ fn Char32? StringIterator.get(&self)
|
||||
usz current = self.current;
|
||||
usz read = (len - current < 4 ? len - current : 4);
|
||||
usz index = current > read ? current - read : 0;
|
||||
if (index >= len) return NO_MORE_ELEMENT?;
|
||||
if (index >= len) return NO_MORE_ELEMENT~;
|
||||
Char32 res = conv::utf8_to_char32(&self.utf8[index], &read)!;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
got_rad = true;
|
||||
if (index == last_char)
|
||||
{
|
||||
if (!got_digit) return MALFORMED_FLOAT?;
|
||||
if (!got_digit) return MALFORMED_FLOAT~;
|
||||
return sign * 0.0;
|
||||
}
|
||||
if (index != last_char && (c = chars[++index]) == '0')
|
||||
@@ -83,7 +83,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
switch
|
||||
{
|
||||
case c == '.':
|
||||
if (got_rad) return MALFORMED_FLOAT?;
|
||||
if (got_rad) return MALFORMED_FLOAT~;
|
||||
got_rad = true;
|
||||
lrp = dc;
|
||||
case k < KMAX - 3:
|
||||
@@ -113,24 +113,24 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
c = chars[++index];
|
||||
}
|
||||
if (!got_rad) lrp = dc;
|
||||
if (!got_digit) return MALFORMED_FLOAT?;
|
||||
if (!got_digit) return MALFORMED_FLOAT~;
|
||||
if ((c | 32) == 'e')
|
||||
{
|
||||
if (last_char == index) return MALFORMED_FLOAT?;
|
||||
long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT?!;
|
||||
if (last_char == index) return MALFORMED_FLOAT~;
|
||||
long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT~!;
|
||||
lrp += e10;
|
||||
}
|
||||
else if (index != last_char)
|
||||
{
|
||||
return MALFORMED_FLOAT?;
|
||||
return MALFORMED_FLOAT~;
|
||||
}
|
||||
// Handle zero specially to avoid nasty special cases later
|
||||
if (!x[0]) return sign * 0.0;
|
||||
|
||||
// Optimize small integers (w/no exponent) and over/under-flow
|
||||
if (lrp == dc && dc < 10 && ($bits > 30 || (ulong)x[0] >> $bits == 0)) return sign * (double)x[0];
|
||||
if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE?;
|
||||
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
|
||||
if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE~;
|
||||
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE~;
|
||||
|
||||
// Align incomplete final B1B digit
|
||||
if (j)
|
||||
@@ -320,7 +320,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
y *= 0.5;
|
||||
e2++;
|
||||
}
|
||||
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT?;
|
||||
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT~;
|
||||
}
|
||||
return math::scalbn(y, e2);
|
||||
}
|
||||
@@ -351,7 +351,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
got_rad = true;
|
||||
if (index == last_char)
|
||||
{
|
||||
if (!got_digit) return MALFORMED_FLOAT?;
|
||||
if (!got_digit) return MALFORMED_FLOAT~;
|
||||
return sign * 0.0;
|
||||
}
|
||||
if (index != last_char && (c = chars[++index]) == '0')
|
||||
@@ -369,7 +369,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
{
|
||||
if (c == '.')
|
||||
{
|
||||
if (got_rad) return MALFORMED_FLOAT?;
|
||||
if (got_rad) return MALFORMED_FLOAT~;
|
||||
got_rad = true;
|
||||
rp = dc;
|
||||
}
|
||||
@@ -393,20 +393,20 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
if (index == last_char) break;
|
||||
c = chars[++index];
|
||||
}
|
||||
if (!got_digit) return MALFORMED_FLOAT?;
|
||||
if (!got_digit) return MALFORMED_FLOAT~;
|
||||
if (!got_rad) rp = dc;
|
||||
for (; dc < 8; dc++) x *= 16;
|
||||
|
||||
long e2;
|
||||
if ((c | 32) == 'p')
|
||||
{
|
||||
long e2val = String.to_long((String)chars[index + 1..]) ?? (MALFORMED_FLOAT?)!;
|
||||
long e2val = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT~!;
|
||||
e2 = e2val;
|
||||
}
|
||||
e2 += 4 * rp - 32;
|
||||
if (!x) return sign * 0.0;
|
||||
if (e2 > -$emin) return FLOAT_OUT_OF_RANGE?;
|
||||
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
|
||||
if (e2 > -$emin) return FLOAT_OUT_OF_RANGE~;
|
||||
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE~;
|
||||
|
||||
while (x < 0x80000000)
|
||||
{
|
||||
@@ -441,7 +441,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
}
|
||||
y = bias + sign * (double)x + sign * y;
|
||||
y -= bias;
|
||||
if (!y) return FLOAT_OUT_OF_RANGE?;
|
||||
if (!y) return FLOAT_OUT_OF_RANGE~;
|
||||
|
||||
return math::scalbn(y, (int)e2);
|
||||
}
|
||||
@@ -462,8 +462,8 @@ macro String.to_real(chars, $Type) @private
|
||||
$error "Unexpected type";
|
||||
$endswitch
|
||||
|
||||
while (chars.len && chars[0] == ' ') chars = chars[1..];
|
||||
if (!chars.len) return MALFORMED_FLOAT?;
|
||||
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;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ faultdef DIVISION_BY_ZERO;
|
||||
|
||||
fn double? divide(int a, int b)
|
||||
{
|
||||
if (b == 0) return MathError.DIVISION_BY_ZERO?;
|
||||
if (b == 0) return MathError.DIVISION_BY_ZERO~;
|
||||
return (double)(a) / (double)(b);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ fn void? test_div() @test
|
||||
test::le(2, 3);
|
||||
test::eq_approx(m::divide(1, 3)!, 0.333, places: 3);
|
||||
test::@check(2 == 2, "divide: %d", divide(6, 3)!);
|
||||
test::@error(m::divide(3, 0));
|
||||
test::@error(m::divide(3, 0), MathError.DIVISION_BY_ZERO);
|
||||
}
|
||||
|
||||
@@ -78,14 +79,15 @@ macro @check(#condition, String format = "", args...)
|
||||
}
|
||||
|
||||
<*
|
||||
Check if function returns specific error
|
||||
Check if function returns (specific) error
|
||||
|
||||
@param #funcresult : `result of function execution`
|
||||
@param error_expected : `expected error of function execution`
|
||||
@require runtime::test_context != null : "Only allowed in @test functions"
|
||||
*>
|
||||
macro @error(#funcresult, fault error_expected)
|
||||
macro @error(#funcresult, fault error_expected = ...)
|
||||
{
|
||||
$if $defined(error_expected):
|
||||
if (catch err = #funcresult)
|
||||
{
|
||||
if (err != error_expected)
|
||||
@@ -96,6 +98,10 @@ macro @error(#funcresult, fault error_expected)
|
||||
return;
|
||||
}
|
||||
print_panicf("`%s` error [%s] was not returned.", $stringify(#funcresult), error_expected);
|
||||
$else
|
||||
if (catch err = #funcresult) return;
|
||||
print_panicf("`%s` unexpectedly did not return error.", $stringify(#funcresult));
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
|
||||
@@ -29,47 +29,47 @@ macro any_to_int(any v, $Type)
|
||||
{
|
||||
case ichar:
|
||||
ichar c = *(char*)v.ptr;
|
||||
if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
|
||||
return ($Type)c;
|
||||
case short:
|
||||
short s = *(short*)v.ptr;
|
||||
if (is_mixed_signed && s < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (is_mixed_signed && s < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
|
||||
if (s > max || s < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)s;
|
||||
case int:
|
||||
int i = *(int*)v.ptr;
|
||||
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)i;
|
||||
case long:
|
||||
long l = *(long*)v.ptr;
|
||||
if (is_mixed_signed && l < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (is_mixed_signed && l < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
|
||||
if (l > max || l < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)l;
|
||||
case int128:
|
||||
int128 i = *(int128*)v.ptr;
|
||||
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)i;
|
||||
case char:
|
||||
char c = *(char*)v.ptr;
|
||||
if (c > max) return VALUE_OUT_OF_RANGE?;
|
||||
if (c > max) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)c;
|
||||
case ushort:
|
||||
ushort s = *(ushort*)v.ptr;
|
||||
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (s > max || s < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)s;
|
||||
case uint:
|
||||
uint i = *(uint*)v.ptr;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)i;
|
||||
case ulong:
|
||||
ulong l = *(ulong*)v.ptr;
|
||||
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (l > max || l < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)l;
|
||||
case uint128:
|
||||
uint128 i = *(uint128*)v.ptr;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)i;
|
||||
default:
|
||||
unreachable();
|
||||
@@ -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($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
|
||||
@@ -110,7 +115,9 @@ fn bool TypeKind.is_int(kind) @inline
|
||||
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
|
||||
}
|
||||
|
||||
macro bool is_slice_convertable($Type)
|
||||
macro bool is_slice_convertable($Type) @deprecated("Use is_slice_convertible") => is_slice_convertible($Type);
|
||||
|
||||
macro bool is_slice_convertible($Type)
|
||||
{
|
||||
$switch $Type.kindof:
|
||||
$case SLICE:
|
||||
@@ -158,7 +165,7 @@ macro bool is_unsigned($Type) @const
|
||||
|
||||
macro typeid flat_type($Type) @const
|
||||
{
|
||||
$if $Type.kindof == DISTINCT:
|
||||
$if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
|
||||
return flat_type($Type.inner);
|
||||
$else
|
||||
return $Type.typeid;
|
||||
@@ -167,7 +174,7 @@ macro typeid flat_type($Type) @const
|
||||
|
||||
macro TypeKind flat_kind($Type) @const
|
||||
{
|
||||
$if $Type.kindof == DISTINCT:
|
||||
$if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
|
||||
return flat_type($Type.inner);
|
||||
$else
|
||||
return $Type.kindof;
|
||||
@@ -191,8 +198,8 @@ macro bool is_flat_intlike($Type) @const
|
||||
$case UNSIGNED_INT:
|
||||
return true;
|
||||
$case VECTOR:
|
||||
return is_flat_intlike($Type.inner);
|
||||
$case DISTINCT:
|
||||
$case CONST_ENUM:
|
||||
return is_flat_intlike($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
@@ -246,7 +253,7 @@ macro bool is_vector($Type) @const
|
||||
|
||||
macro typeid inner_type($Type) @const
|
||||
{
|
||||
$if $Type.kindof == TypeKind.DISTINCT:
|
||||
$if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
|
||||
return inner_type($Type.inner);
|
||||
$else
|
||||
return $Type.typeid;
|
||||
@@ -302,6 +309,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:
|
||||
@@ -321,8 +329,8 @@ macro lower_to_atomic_compatible_type($Type) @const
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_promotable_to_floatlike($Type) @const => types::is_floatlike($Type) || types::is_int($Type);
|
||||
macro bool is_promotable_to_float($Type) @const => types::is_float($Type) || types::is_int($Type);
|
||||
macro bool is_promotable_to_floatlike($Type) @const => types::is_floatlike($Type) ||| types::is_int($Type);
|
||||
macro bool is_promotable_to_float($Type) @const => types::is_float($Type) ||| types::is_int($Type);
|
||||
|
||||
macro bool is_same_vector_type($Type1, $Type2) @const
|
||||
{
|
||||
@@ -333,9 +341,11 @@ 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):
|
||||
$if $defined($Type.less) ||| $defined($Type.compare_to) ||| $defined($Type.equals):
|
||||
return true;
|
||||
$else
|
||||
return $Type.is_eq;
|
||||
@@ -347,7 +357,7 @@ macro bool is_equatable_type($Type) @const
|
||||
*>
|
||||
macro bool implements_copy($Type) @const
|
||||
{
|
||||
return $defined($Type.copy) && $defined($Type.free);
|
||||
return $defined($Type.copy) &&& $defined($Type.free);
|
||||
}
|
||||
|
||||
macro bool @equatable_value(#value) @const
|
||||
@@ -357,7 +367,7 @@ macro bool @equatable_value(#value) @const
|
||||
|
||||
macro bool @comparable_value(#value) @const
|
||||
{
|
||||
$if $defined(#value.less) || $defined(#value.compare_to):
|
||||
$if $defined(#value.less) ||| $defined(#value.compare_to):
|
||||
return true;
|
||||
$else
|
||||
return $typeof(#value).is_ordered;
|
||||
@@ -375,6 +385,7 @@ enum TypeKind : char
|
||||
FAULT,
|
||||
ANY,
|
||||
ENUM,
|
||||
CONST_ENUM,
|
||||
STRUCT,
|
||||
UNION,
|
||||
BITSTRUCT,
|
||||
|
||||
@@ -2,13 +2,11 @@ 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;
|
||||
macro bool @typematch(#value1, #value2) @builtin @const => $typeof(#value1) == $typeof(#value2);
|
||||
<*
|
||||
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_same_type(#value1, #value2) @const @deprecated("Use @typematch") => $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));
|
||||
@@ -18,8 +16,12 @@ macro bool @is_promotable_to_floatlike(#value) @const => types::is_promotable_to
|
||||
macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_float($typeof(#value));
|
||||
macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
|
||||
macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
|
||||
macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2));
|
||||
macro bool @is_lvalue(#value) => $defined(#value = #value);
|
||||
macro bool @assign_to(#value1, #value2) @const @deprecated("use '$defined(#value1 = #value2)'") => @assignable_to(#value1, $typeof(#value2));
|
||||
macro bool @is_lvalue(#value) @deprecated("use '$defined(#value = #value)'")=> $defined(#value = #value);
|
||||
macro bool @is_const(#foo) @const @builtin @deprecated("use '$defined(var $v = expr)'")
|
||||
{
|
||||
return $defined(var $v = #foo);
|
||||
}
|
||||
|
||||
macro promote_int(x)
|
||||
{
|
||||
@@ -41,7 +43,7 @@ macro promote_int(x)
|
||||
@param #value_2
|
||||
@returns `The selected value.`
|
||||
*>
|
||||
macro @select(bool $bool, #value_1, #value_2) @builtin
|
||||
macro @select(bool $bool, #value_1, #value_2) @builtin @deprecated("Use '$bool ? #value_1 : #value_2' instead.")
|
||||
{
|
||||
$if $bool:
|
||||
return #value_1;
|
||||
@@ -49,6 +51,7 @@ macro @select(bool $bool, #value_1, #value_2) @builtin
|
||||
return #value_2;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro promote_int_same(x, y)
|
||||
{
|
||||
$if @is_int(x):
|
||||
|
||||
650
lib/std/crypto/aes.c3
Normal file
650
lib/std/crypto/aes.c3
Normal file
@@ -0,0 +1,650 @@
|
||||
<*
|
||||
This is an implementation of the AES algorithm with the ECB, CTR and CBC
|
||||
modes. The key size can be chosen among AES128, AES192, AES256.
|
||||
|
||||
Ported from github.com/kokke/tiny-aes-c by Koni Marti.
|
||||
|
||||
The implementation is verified against the test vectors from the National
|
||||
Institute of Standards and Technology Special Publication 800-38A 2001 ED.
|
||||
|
||||
Data length must be evenly divisible by 16 bytes (len % 16 == 0) unless CTR is
|
||||
used. You should pad the end of the string with zeros or use PKCS7 if this is not the case.
|
||||
For AES192/256 the key size is proportionally larger.
|
||||
|
||||
The following example demonstrates the AES encryption of a plaintext string
|
||||
with an AES 128-bit key:
|
||||
|
||||
```
|
||||
module app;
|
||||
import std::crypto::aes, std::io;
|
||||
fn void main()
|
||||
{
|
||||
char[] key = x"2b7e151628aed2a6abf7158809cf4f3c";
|
||||
char[] text = x"6bc1bee22e409f96e93d7e117393172a";
|
||||
char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
|
||||
Aes aes;
|
||||
aes.init(AES128, key, iv);
|
||||
defer aes.destroy();
|
||||
char[] cipher = aes.encrypt(mem, text);
|
||||
defer free(cipher);
|
||||
assert(cipher == x"874d6191b620e3261bef6864990db6ce");
|
||||
}
|
||||
```
|
||||
|
||||
*>
|
||||
module std::crypto::aes;
|
||||
|
||||
<* Block length in bytes. AES is 128-bit blocks only. *>
|
||||
const BLOCKLEN = 16;
|
||||
|
||||
<* Number of columns of a AES state. *>
|
||||
const COLNUM = 4;
|
||||
|
||||
<*
|
||||
Block modes:
|
||||
ECB - Electronic Code Book (Not recommended, indata be 16 byte multiple)
|
||||
CBC - Cipher Block Chaining (Indata be 16 byte multiple)
|
||||
CTR - Counter Mode (Recommended, data may be any size)
|
||||
*>
|
||||
enum BlockMode
|
||||
{
|
||||
ECB,
|
||||
CBC,
|
||||
CTR,
|
||||
}
|
||||
|
||||
<* AES type: 128, 192 or 256 bits *>
|
||||
enum AesType : (AesKey key)
|
||||
{
|
||||
AES128 = { 128, 16, 176, 4, 10 },
|
||||
AES192 = { 192, 24, 208, 6, 12 },
|
||||
AES256 = { 256, 32, 240, 8, 14 }
|
||||
}
|
||||
|
||||
struct AesKey
|
||||
{
|
||||
<* Size of key in bits *>
|
||||
usz key_size;
|
||||
<* Size of key in bytes *>
|
||||
int key_len;
|
||||
<* Size of the expanded round_key *>
|
||||
int key_exp_size; // expected size of round_key
|
||||
<* Number of 32 bit words in key *>
|
||||
usz nk;
|
||||
<* Number of rounds in the cipher *>
|
||||
usz nr;
|
||||
}
|
||||
|
||||
struct Aes
|
||||
{
|
||||
<* The type, AES128, AES192 or AES256 *>
|
||||
AesKey type;
|
||||
<* Block mode: ECB, CBC or CTR *>
|
||||
BlockMode mode;
|
||||
<* Initialization Vector *>
|
||||
char[BLOCKLEN] iv;
|
||||
<* Internal key state *>
|
||||
char[256] round_key;
|
||||
<* Internal state *>
|
||||
AesState state;
|
||||
}
|
||||
alias AesState = char[COLNUM][COLNUM];
|
||||
|
||||
<*
|
||||
Initializes the AES crypto. The initialization vector should be securely random for each encryption
|
||||
to mitigate things like replay attacks.
|
||||
|
||||
@param type : "The type or AES: 128, 192 or 256 bits"
|
||||
@param [in] key : "The key to use, should be the same bit size as the type, so 16, 24 or 32 bytes"
|
||||
@param iv : "The initialization vector"
|
||||
@param mode : "The block mode: EBC, CBC, CTR. Defaults to CTR"
|
||||
|
||||
@require key.len == type.key.key_len : "Key does not match expected length."
|
||||
*>
|
||||
fn Aes* Aes.init(&self, AesType type, char[] key, char[BLOCKLEN] iv, BlockMode mode = CTR)
|
||||
{
|
||||
*self = { .type = type.key, .mode = mode, .iv = iv };
|
||||
key_expansion(type, key, &self.round_key);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Completely erases data stored in the context.
|
||||
*>
|
||||
fn void Aes.destroy(&self)
|
||||
{
|
||||
*self = {};
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the length is valid using the given block mode. It has to be a multiple of 16 bytes unless CTR is used.
|
||||
*>
|
||||
macro bool is_valid_encryption_len(BlockMode mode, usz len)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case CTR:
|
||||
return true;
|
||||
case ECB:
|
||||
case CBC:
|
||||
return len % BLOCKLEN == 0;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The input must be a multiple of 16 unless CTR is used"
|
||||
@require out.len >= in.len : "Out buffer must be sufficiently large to hold the data"
|
||||
*>
|
||||
fn void Aes.encrypt_buffer(&self, char[] in, char[] out)
|
||||
{
|
||||
switch (self.mode)
|
||||
{
|
||||
case CTR: ctr_xcrypt_buffer(self, in, out);
|
||||
case ECB: ecb_encrypt_buffer(self, in, out);
|
||||
case CBC: cbc_encrypt_buffer(self, in, out);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The encrypted data must be a multiple of 16 unless CTR is used"
|
||||
@require out.len >= in.len : "Out buffer must be sufficiently large to hold the data"
|
||||
*>
|
||||
fn void Aes.decrypt_buffer(&self, char[] in, char[] out)
|
||||
{
|
||||
switch (self.mode)
|
||||
{
|
||||
case ECB: ecb_decrypt_buffer(self, in, out);
|
||||
case CBC: cbc_decrypt_buffer(self, in, out);
|
||||
case CTR: ctr_xcrypt_buffer(self, in, out);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Encrypt the data, allocating memory for the encrypted data.
|
||||
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [&inout] allocator : "The allocator to use for the output"
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
*>
|
||||
fn char[] Aes.encrypt(&self, Allocator allocator, char[] in)
|
||||
{
|
||||
char[] out = allocator::alloc_array(allocator, char, in.len);
|
||||
self.encrypt_buffer(in, out) @inline;
|
||||
return out;
|
||||
}
|
||||
|
||||
<*
|
||||
Encrypt the data, allocating temp memory for the encrypted data.
|
||||
|
||||
@param [in] in : "Plaintext input."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
*>
|
||||
fn char[] Aes.tencrypt(&self, char[] in)
|
||||
{
|
||||
return self.encrypt(tmem, in);
|
||||
}
|
||||
|
||||
<*
|
||||
Decrypt the data, allocating memory for the decrypted data.
|
||||
|
||||
@param [in] in : "Encrypted input."
|
||||
@param [&inout] allocator : "The allocator to use for the output"
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
*>
|
||||
fn char[] Aes.decrypt(&self, Allocator allocator, char[] in)
|
||||
{
|
||||
char[] out = allocator::alloc_array(allocator, char, in.len);
|
||||
self.decrypt_buffer(in, out) @inline;
|
||||
return out;
|
||||
}
|
||||
|
||||
<*
|
||||
Decrypt the data, allocating temp memory for the decrypted data.
|
||||
|
||||
@param [in] in : "Encrypted input."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
|
||||
*>
|
||||
fn char[] Aes.tdecrypt(&self, char[] in)
|
||||
{
|
||||
return self.decrypt(tmem, in);
|
||||
}
|
||||
|
||||
module std::crypto::aes @private;
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
*>
|
||||
fn void ecb_encrypt_block(Aes *aes, char[BLOCKLEN]* in, char[BLOCKLEN]* out)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[i][j] = (*in)[i * 4 + j];
|
||||
}
|
||||
}
|
||||
aes_cipher(aes, &aes.round_key);
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
(*out)[i * 4 + j] = aes.state[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
*>
|
||||
fn void ecb_decrypt_block(Aes *aes, char[BLOCKLEN]* in, char[BLOCKLEN]* out)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[i][j] = (*in)[i * 4 + j];
|
||||
}
|
||||
}
|
||||
inv_cipher(aes, &aes.round_key);
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
(*out)[i * 4 + j] = aes.state[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
@require out.len >= in.len : "out must be at least as large as buf"
|
||||
*>
|
||||
fn void ecb_decrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
usz len = in.len;
|
||||
for (usz i = 0; i < len; i += 4)
|
||||
{
|
||||
ecb_decrypt_block(aes, in[:BLOCKLEN], out[:BLOCKLEN]) @inline;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
*>
|
||||
fn void ecb_encrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
usz len = in.len;
|
||||
for (usz i = 0; i < len; i += BLOCKLEN)
|
||||
{
|
||||
ecb_encrypt_block(aes, in[i:BLOCKLEN], out[i:BLOCKLEN]) @inline;
|
||||
}
|
||||
}
|
||||
|
||||
fn void xor_with_iv(char[] buf, char[BLOCKLEN]* iv) @local
|
||||
{
|
||||
foreach (i, b : *iv)
|
||||
{
|
||||
buf[i] ^= b;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
*>
|
||||
fn void cbc_encrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
char[] iv = aes.iv[..];
|
||||
usz len = in.len;
|
||||
char[BLOCKLEN] tmp;
|
||||
char[BLOCKLEN] tmp2;
|
||||
for (usz i = 0; i < len; i += BLOCKLEN)
|
||||
{
|
||||
tmp[:BLOCKLEN] = in[i:BLOCKLEN];
|
||||
xor_with_iv(&tmp, iv);
|
||||
ecb_encrypt_block(aes, &tmp, &tmp2);
|
||||
out[i:BLOCKLEN] = tmp2[..];
|
||||
iv = tmp2[..];
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
*>
|
||||
fn void cbc_decrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
char[BLOCKLEN] tmp;
|
||||
usz len = in.len;
|
||||
for (usz i = 0; i < len; i += BLOCKLEN)
|
||||
{
|
||||
ecb_decrypt_block(aes, in[i:BLOCKLEN], &tmp);
|
||||
xor_with_iv(&tmp, aes.iv[..]);
|
||||
aes.iv[:BLOCKLEN] = in[i:BLOCKLEN];
|
||||
out[i:BLOCKLEN] = tmp[..];
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext/cipher input."
|
||||
@param [out] out : "Cipher/plaintext output."
|
||||
*>
|
||||
fn void ctr_xcrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
char[BLOCKLEN] buffer @noinit;
|
||||
usz len = in.len;
|
||||
for (int bi = BLOCKLEN, usz i = 0; i < len; i++)
|
||||
{
|
||||
if (bi == BLOCKLEN)
|
||||
{
|
||||
buffer = aes.iv;
|
||||
ecb_encrypt_block(aes, &buffer, &buffer);
|
||||
|
||||
for LOOP: (bi = (BLOCKLEN - 1); bi >= 0; bi--)
|
||||
{
|
||||
if (aes.iv[bi] == 255)
|
||||
{
|
||||
aes.iv[bi] = 0;
|
||||
continue;
|
||||
}
|
||||
aes.iv[bi]++;
|
||||
break LOOP;
|
||||
}
|
||||
bi = 0;
|
||||
}
|
||||
out[i] = in[i] ^ buffer[bi];
|
||||
bi++;
|
||||
}
|
||||
}
|
||||
|
||||
macro char get_sbox_value(num) => SBOX[num];
|
||||
macro char get_sbox_invert(num) => RSBOX[num];
|
||||
|
||||
const char[256] SBOX =
|
||||
x`637c777bf26b6fc53001672bfed7ab76
|
||||
ca82c97dfa5947f0add4a2af9ca472c0
|
||||
b7fd9326363ff7cc34a5e5f171d83115
|
||||
04c723c31896059a071280e2eb27b275
|
||||
09832c1a1b6e5aa0523bd6b329e32f84
|
||||
53d100ed20fcb15b6acbbe394a4c58cf
|
||||
d0efaafb434d338545f9027f503c9fa8
|
||||
51a3408f929d38f5bcb6da2110fff3d2
|
||||
cd0c13ec5f974417c4a77e3d645d1973
|
||||
60814fdc222a908846eeb814de5e0bdb
|
||||
e0323a0a4906245cc2d3ac629195e479
|
||||
e7c8376d8dd54ea96c56f4ea657aae08
|
||||
ba78252e1ca6b4c6e8dd741f4bbd8b8a
|
||||
703eb5664803f60e613557b986c11d9e
|
||||
e1f8981169d98e949b1e87e9ce5528df
|
||||
8ca1890dbfe6426841992d0fb054bb16`;
|
||||
|
||||
const char[256] RSBOX =
|
||||
x`52096ad53036a538bf40a39e81f3d7fb
|
||||
7ce339829b2fff87348e4344c4dee9cb
|
||||
547b9432a6c2233dee4c950b42fac34e
|
||||
082ea16628d924b2765ba2496d8bd125
|
||||
72f8f66486689816d4a45ccc5d65b692
|
||||
6c704850fdedb9da5e154657a78d9d84
|
||||
90d8ab008cbcd30af7e45805b8b34506
|
||||
d02c1e8fca3f0f02c1afbd0301138a6b
|
||||
3a9111414f67dcea97f2cfcef0b4e673
|
||||
96ac7422e7ad3585e2f937e81c75df6e
|
||||
47f11a711d29c5896fb7620eaa18be1b
|
||||
fc563e4bc6d279209adbc0fe78cd5af4
|
||||
1fdda8338807c731b11210592780ec5f
|
||||
60517fa919b54a0d2de57a9f93c99cef
|
||||
a0e03b4dae2af5b0c8ebbb3c83539961
|
||||
172b047eba77d626e169146355210c7d`;
|
||||
|
||||
const char[11] RCON = x`8d01020408102040801b36`;
|
||||
|
||||
fn void add_round_key(Aes* aes, usz round, char[] round_key)
|
||||
{
|
||||
usz i, j;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
for (j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[i][j] ^= round_key[(round * COLNUM * 4) + (i * COLNUM) + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void sub_bytes(Aes* aes)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[j][i] = get_sbox_value(aes.state[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void shift_rows(Aes* aes)
|
||||
{
|
||||
char temp;
|
||||
|
||||
temp = aes.state[0][1];
|
||||
aes.state[0][1] = aes.state[1][1];
|
||||
aes.state[1][1] = aes.state[2][1];
|
||||
aes.state[2][1] = aes.state[3][1];
|
||||
aes.state[3][1] = temp;
|
||||
|
||||
temp = aes.state[0][2];
|
||||
aes.state[0][2] = aes.state[2][2];
|
||||
aes.state[2][2] = temp;
|
||||
|
||||
temp = aes.state[1][2];
|
||||
aes.state[1][2] = aes.state[3][2];
|
||||
aes.state[3][2] = temp;
|
||||
|
||||
temp = aes.state[0][3];
|
||||
aes.state[0][3] = aes.state[3][3];
|
||||
aes.state[3][3] = aes.state[2][3];
|
||||
aes.state[2][3] = aes.state[1][3];
|
||||
aes.state[1][3] = temp;
|
||||
}
|
||||
|
||||
fn char xtime(char x) @local
|
||||
{
|
||||
return ((x << 1) ^ (((x >> 7) & 1) * 0x1b));
|
||||
}
|
||||
|
||||
fn void mix_columns(Aes* aes)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
char t = aes.state[i][0];
|
||||
char tmp = aes.state[i][0] ^ aes.state[i][1] ^ aes.state[i][2] ^ aes.state[i][3];
|
||||
|
||||
char tm = aes.state[i][0] ^ aes.state[i][1];
|
||||
tm = xtime(tm);
|
||||
aes.state[i][0] ^= tm ^ tmp;
|
||||
|
||||
tm = aes.state[i][1] ^ aes.state[i][2];
|
||||
tm = xtime(tm);
|
||||
aes.state[i][1] ^= tm ^ tmp;
|
||||
|
||||
tm = aes.state[i][2] ^ aes.state[i][3];
|
||||
tm = xtime(tm);
|
||||
aes.state[i][2] ^= tm ^ tmp;
|
||||
|
||||
tm = aes.state[i][3] ^ t;
|
||||
tm = xtime(tm);
|
||||
aes.state[i][3] ^= tm ^ tmp;
|
||||
}
|
||||
}
|
||||
|
||||
fn char multiply(char x, char y) @local
|
||||
{
|
||||
return (((y & 1) * x) ^
|
||||
(((y>>1) & 1) * xtime(x)) ^
|
||||
(((y>>2) & 1) * xtime(xtime(x))) ^
|
||||
(((y>>3) & 1) * xtime(xtime(xtime(x)))) ^
|
||||
(((y>>4) & 1) * xtime(xtime(xtime(xtime(x))))));
|
||||
}
|
||||
|
||||
fn void inv_mix_columns(Aes* aes)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
char a = aes.state[i][0];
|
||||
char b = aes.state[i][1];
|
||||
char c = aes.state[i][2];
|
||||
char d = aes.state[i][3];
|
||||
|
||||
aes.state[i][0] = multiply(a, 0x0e) ^ multiply(b, 0x0b) ^ multiply(c, 0x0d) ^ multiply(d, 0x09);
|
||||
aes.state[i][1] = multiply(a, 0x09) ^ multiply(b, 0x0e) ^ multiply(c, 0x0b) ^ multiply(d, 0x0d);
|
||||
aes.state[i][2] = multiply(a, 0x0d) ^ multiply(b, 0x09) ^ multiply(c, 0x0e) ^ multiply(d, 0x0b);
|
||||
aes.state[i][3] = multiply(a, 0x0b) ^ multiply(b, 0x0d) ^ multiply(c, 0x09) ^ multiply(d, 0x0e);
|
||||
}
|
||||
}
|
||||
|
||||
fn void inv_sub_bytes(Aes* aes)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[j][i] = get_sbox_invert(aes.state[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void inv_shift_rows(Aes* aes)
|
||||
{
|
||||
char temp;
|
||||
|
||||
temp = aes.state[3][1];
|
||||
aes.state[3][1] = aes.state[2][1];
|
||||
aes.state[2][1] = aes.state[1][1];
|
||||
aes.state[1][1] = aes.state[0][1];
|
||||
aes.state[0][1] = temp;
|
||||
|
||||
temp = aes.state[0][2];
|
||||
aes.state[0][2] = aes.state[2][2];
|
||||
aes.state[2][2] = temp;
|
||||
|
||||
temp = aes.state[1][2];
|
||||
aes.state[1][2] = aes.state[3][2];
|
||||
aes.state[3][2] = temp;
|
||||
|
||||
temp = aes.state[0][3];
|
||||
aes.state[0][3] = aes.state[1][3];
|
||||
aes.state[1][3] = aes.state[2][3];
|
||||
aes.state[2][3] = aes.state[3][3];
|
||||
aes.state[3][3] = temp;
|
||||
}
|
||||
|
||||
fn void aes_cipher(Aes* aes, char[] round_key)
|
||||
{
|
||||
usz round = 0;
|
||||
add_round_key(aes, 0, round_key);
|
||||
|
||||
for LOOP: (round = 1;; round++)
|
||||
{
|
||||
sub_bytes(aes);
|
||||
shift_rows(aes);
|
||||
if (round == aes.type.nr) break LOOP;
|
||||
mix_columns(aes);
|
||||
add_round_key(aes, round, round_key);
|
||||
}
|
||||
add_round_key(aes, aes.type.nr, round_key);
|
||||
}
|
||||
|
||||
fn void inv_cipher(Aes* aes, char[] round_key)
|
||||
{
|
||||
add_round_key(aes, aes.type.nr, round_key);
|
||||
for (usz round = aes.type.nr - 1; ; round--)
|
||||
{
|
||||
inv_shift_rows(aes);
|
||||
inv_sub_bytes(aes);
|
||||
add_round_key(aes, round, round_key);
|
||||
if (!round) return;
|
||||
inv_mix_columns(aes);
|
||||
}
|
||||
}
|
||||
|
||||
<*¨
|
||||
@param type : "The AES variant to expant the key for"
|
||||
@param [inout] round_key : "Key to expand into"
|
||||
@param [in] key : "The key to expand"
|
||||
@require key.len == type.key.key_len : "Key does not match expected length."
|
||||
*>
|
||||
fn void key_expansion(AesType type, char[] key, char[] round_key) @private
|
||||
{
|
||||
usz nk = type.key.nk;
|
||||
for (usz i = 0; i < nk; i++)
|
||||
{
|
||||
round_key[(i * 4) + 0] = key[(i * 4) + 0];
|
||||
round_key[(i * 4) + 1] = key[(i * 4) + 1];
|
||||
round_key[(i * 4) + 2] = key[(i * 4) + 2];
|
||||
round_key[(i * 4) + 3] = key[(i * 4) + 3];
|
||||
}
|
||||
|
||||
for (usz i = nk; i < COLNUM * (type.key.nr + 1); i++)
|
||||
{
|
||||
usz k = (i - 1) * 4;
|
||||
|
||||
char[4] tempa @noinit;
|
||||
|
||||
tempa[0] = round_key[k + 0];
|
||||
tempa[1] = round_key[k + 1];
|
||||
tempa[2] = round_key[k + 2];
|
||||
tempa[3] = round_key[k + 3];
|
||||
|
||||
if (i % nk == 0)
|
||||
{
|
||||
// rotword
|
||||
char tmp = tempa[0];
|
||||
tempa[0] = tempa[1];
|
||||
tempa[1] = tempa[2];
|
||||
tempa[2] = tempa[3];
|
||||
tempa[3] = tmp;
|
||||
|
||||
// subword
|
||||
tempa[0] = get_sbox_value(tempa[0]);
|
||||
tempa[1] = get_sbox_value(tempa[1]);
|
||||
tempa[2] = get_sbox_value(tempa[2]);
|
||||
tempa[3] = get_sbox_value(tempa[3]);
|
||||
|
||||
tempa[0] = tempa[0] ^ RCON[i / nk];
|
||||
}
|
||||
|
||||
if (type.key.key_size == 256)
|
||||
{
|
||||
if (i % nk == 4)
|
||||
{
|
||||
// subword
|
||||
tempa[0] = get_sbox_value(tempa[0]);
|
||||
tempa[1] = get_sbox_value(tempa[1]);
|
||||
tempa[2] = get_sbox_value(tempa[2]);
|
||||
tempa[3] = get_sbox_value(tempa[3]);
|
||||
}
|
||||
}
|
||||
usz j = i * 4;
|
||||
k = (i - nk) * 4;
|
||||
round_key[j + 0] = round_key[k + 0] ^ tempa[0];
|
||||
round_key[j + 1] = round_key[k + 1] ^ tempa[1];
|
||||
round_key[j + 2] = round_key[k + 2] ^ tempa[2];
|
||||
round_key[j + 3] = round_key[k + 3] ^ tempa[3];
|
||||
}
|
||||
}
|
||||
|
||||
87
lib/std/crypto/aes_128_192_256.c3
Normal file
87
lib/std/crypto/aes_128_192_256.c3
Normal file
@@ -0,0 +1,87 @@
|
||||
// Experimental implementation
|
||||
module std::crypto::aes128;
|
||||
import std::crypto::aes;
|
||||
|
||||
fn char[] encrypt(Allocator allocator, char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES128, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.encrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tencrypt(char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return encrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
fn char[] decrypt(Allocator allocator, char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES128, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.decrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tdecrypt(char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return decrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
module std::crypto::aes192;
|
||||
import std::crypto::aes;
|
||||
|
||||
fn char[] encrypt(Allocator allocator, char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES192, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.encrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tencrypt(char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return encrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
fn char[] decrypt(Allocator allocator, char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES192, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.decrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tdecrypt(char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return decrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
module std::crypto::aes256;
|
||||
import std::crypto::aes;
|
||||
|
||||
fn char[] encrypt(Allocator allocator, char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES256, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.encrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tencrypt(char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return encrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
fn char[] decrypt(Allocator allocator, char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES256, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.decrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tdecrypt(char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return decrypt(tmem, key, iv, data);
|
||||
}
|
||||
233
lib/std/crypto/chacha20.c3
Normal file
233
lib/std/crypto/chacha20.c3
Normal file
@@ -0,0 +1,233 @@
|
||||
// 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.
|
||||
//
|
||||
// ChaCha20 code dedicated from repo: https://github.com/NotsoanoNimus/chacha20_aead.c3l (but massively cleaned)
|
||||
module std::crypto::chacha20;
|
||||
|
||||
|
||||
<* The typical cipher block size in bytes. *>
|
||||
const BLOCK_SIZE = 64;
|
||||
|
||||
<* Required key size in bytes. *>
|
||||
const KEY_SIZE = 32;
|
||||
|
||||
<* ChaCha20 "nonce" (initialization vector) size. *>
|
||||
const NONCE_SIZE = 12;
|
||||
|
||||
<* A required ChaCha20 "magic" value used for state initialization. *>
|
||||
const char[] MAGIC = "expand 32-byte k";
|
||||
|
||||
<*
|
||||
Once a single ChaCha20 context has processed this many bytes, a new nonce MUST be used,
|
||||
unless the static `permit_overflow` runtime module variable is set to true.
|
||||
*>
|
||||
const CHACHA20_NONCE_REUSE_LIMIT = 64 * (1ull << 32);
|
||||
|
||||
<*
|
||||
SECURITY WARNING:
|
||||
This boolean should always remain 'false'. If set to 'true', you accept the security
|
||||
implications of nonce re-use caused by an overflow in the cipher's 'counter' field.
|
||||
|
||||
This security warning is only applicable when a single ChaCha20 context is being used
|
||||
to process more than about 256 GiB of data.
|
||||
*>
|
||||
bool permit_overflow = false;
|
||||
|
||||
|
||||
<* A context structure used to track an ongoing ChaCha20 transformation. *>
|
||||
struct ChaCha20
|
||||
{
|
||||
<* The position within a block before permuting the rounds. *>
|
||||
usz position;
|
||||
<* Count of bytes processed. Useful to track an approach to the 256GiB limit of a single context. *>
|
||||
ulong bytes_processed;
|
||||
<* The key stream or state used during cipher block operations. *>
|
||||
uint[16] key_stream @align(ulong.sizeof);
|
||||
<* The secret key for the context. *>
|
||||
char[32] key;
|
||||
<* The one-time nonce (or IV - initialization vector) used for the context. *>
|
||||
char[12] nonce;
|
||||
<* Internal state of the cipher. *>
|
||||
uint[16] state;
|
||||
}
|
||||
|
||||
|
||||
<* The meat and potatoes of the ChaCha20 stream cipher. *>
|
||||
macro quarter_round(uint* x, int a, int b, int c, int d) @local
|
||||
{
|
||||
x[a] += x[b]; x[d] = (x[d] ^ x[a]).rotl(16);
|
||||
x[c] += x[d]; x[b] = (x[b] ^ x[c]).rotl(12);
|
||||
x[a] += x[b]; x[d] = (x[d] ^ x[a]).rotl(8);
|
||||
x[c] += x[d]; x[b] = (x[b] ^ x[c]).rotl(7);
|
||||
}
|
||||
|
||||
<* Process the next (or final) chunk of ingested data. *>
|
||||
fn void ChaCha20.mutate_keystream(&self) @local @inline
|
||||
{
|
||||
self.key_stream[..] = self.state[..];
|
||||
|
||||
for (usz i = 0; i < 10; i++) // unrolling this does not improve performance measurably
|
||||
{
|
||||
quarter_round(&self.key_stream[0], 0, 4, 8, 12);
|
||||
quarter_round(&self.key_stream[0], 1, 5, 9, 13);
|
||||
quarter_round(&self.key_stream[0], 2, 6, 10, 14);
|
||||
quarter_round(&self.key_stream[0], 3, 7, 11, 15);
|
||||
quarter_round(&self.key_stream[0], 0, 5, 10, 15);
|
||||
quarter_round(&self.key_stream[0], 1, 6, 11, 12);
|
||||
quarter_round(&self.key_stream[0], 2, 7, 8, 13);
|
||||
quarter_round(&self.key_stream[0], 3, 4, 9, 14);
|
||||
}
|
||||
|
||||
// NOTE: This would 'feel' like a performance hit, but testing the benchmark doesn't show any noticeable
|
||||
// difference on -O5 between this and a for-loop, or even an unrolled loop with compile-time '$for'.
|
||||
array::@zip_into(self.key_stream[..], self.state[..], fn (a, b) => a + b);
|
||||
|
||||
self.state[12]++; // increment the block counter (rollovers are ok)
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a ChaCha20 transformation context.
|
||||
|
||||
@param key : `The secret key used for the transformation operation.`
|
||||
@param nonce : `The one-time nonce to use for the transformation operation.`
|
||||
@param counter : `An optional counter value to adjust the stream's position.`
|
||||
|
||||
@require key.len == KEY_SIZE : `Input key slice is not the correct length (32 bytes).`
|
||||
@require nonce.len == NONCE_SIZE : `Input nonce slice is not the correct length (12 bytes).`
|
||||
*>
|
||||
fn void ChaCha20.init(&self, char[KEY_SIZE] key, char[NONCE_SIZE] nonce, uint counter = 0)
|
||||
{
|
||||
// Init block.
|
||||
self.position = BLOCK_SIZE; // start at the "end" of a block on init
|
||||
self.bytes_processed = 0;
|
||||
self.key[..] = key[..];
|
||||
self.nonce[..] = nonce[..];
|
||||
((char*)&self.state[0])[:MAGIC.len] = MAGIC[..];
|
||||
((char*)&self.state[4])[:KEY_SIZE] = key[..];
|
||||
self.state[12] = counter;
|
||||
((char*)&self.state[13])[:NONCE_SIZE] = nonce[..];
|
||||
}
|
||||
|
||||
<*
|
||||
Transform some input data using the current context structure.
|
||||
|
||||
@param[inout] data : `The data to transform (encrypt or decrypt).`
|
||||
*>
|
||||
fn void ChaCha20.transform(&self, char[] data)
|
||||
{
|
||||
if (!data.len) return;
|
||||
|
||||
usz original_length = data.len;
|
||||
char[] key_stream = @as_char_view(self.key_stream);
|
||||
|
||||
// 1. Process remaining bytes in the current keystream block.
|
||||
if (self.position < BLOCK_SIZE)
|
||||
{
|
||||
usz len = data.len < (BLOCK_SIZE - self.position) ? data.len : (BLOCK_SIZE - self.position);
|
||||
for (usz i = 0; i < len; i++) data[i] ^= key_stream[self.position + i];
|
||||
self.position += len;
|
||||
data = data[len..];
|
||||
}
|
||||
|
||||
// 2. Get the amount of bytes offset from the nearest alignment boundary.
|
||||
// Process full blocks at a time, word by word according to the system's architecture.
|
||||
// Any extra bytes on each side are dynamically processed byte-by-byte.
|
||||
usz offset = usz.sizeof - (((usz)data.ptr % usz.sizeof) ?: usz.sizeof);
|
||||
|
||||
for (usz x = offset; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..], x = offset)
|
||||
{
|
||||
self.mutate_keystream();
|
||||
if (offset) foreach (i, &b : data[:offset]) *b ^= key_stream[i];
|
||||
char[] aligned_data = data[offset..];
|
||||
for (; x <= (BLOCK_SIZE - usz.sizeof); x += usz.sizeof)
|
||||
{
|
||||
((usz*)aligned_data.ptr)[x / usz.sizeof] ^= mem::load((usz*)(&key_stream[x]), $align: 1);
|
||||
}
|
||||
for (; x < BLOCK_SIZE; x++) data[x] ^= key_stream[x];
|
||||
}
|
||||
|
||||
// 3. Process any remaining bytes.
|
||||
if (data.len > 0)
|
||||
{
|
||||
self.mutate_keystream();
|
||||
for (usz i = 0; i < data.len; i++) data[i] ^= key_stream[i];
|
||||
self.position = data.len;
|
||||
}
|
||||
|
||||
// All done. Capture the transformed length of data and check limits.
|
||||
self.bytes_processed += original_length;
|
||||
if (@unlikely(self.bytes_processed >= CHACHA20_NONCE_REUSE_LIMIT && !permit_overflow))
|
||||
{
|
||||
abort(
|
||||
"ChaCha20 transform limit (~256 GiB) exceeded. You can set 'chacha20::permit_overflow = true;' at"
|
||||
" runtime to disable this panic, but you accept the terrible SECURITY IMPLICATIONS of doing so."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
<* Destroy the current context structure by zeroing all fields. *>
|
||||
fn void ChaCha20.destroy(&self) => mem::zero_volatile(@as_char_view(*self));
|
||||
|
||||
|
||||
<*
|
||||
Perform an in-place transformation of some data in a buffer, without cloning the data to a new buffer.
|
||||
|
||||
@param[inout] data : `The data to transform (encrypt or decrypt).`
|
||||
@param key : `The secret key used for the transformation operation.`
|
||||
@param nonce : `The one-time nonce to use for the transformation operation.`
|
||||
@param counter : `An optional counter value to adjust the stream's position.`
|
||||
|
||||
@require key.len == KEY_SIZE : `Input key slice is not the correct length (32 bytes).`
|
||||
@require nonce.len == NONCE_SIZE : `Input nonce slice is not the correct length (12 bytes).`
|
||||
*>
|
||||
fn void crypt(char[] data, char[KEY_SIZE] key, char[NONCE_SIZE] nonce, uint counter = 0) @private
|
||||
{
|
||||
if (@unlikely(!data.len)) return;
|
||||
ChaCha20 c @noinit;
|
||||
defer c.destroy();
|
||||
c.init(key, nonce, counter);
|
||||
c.transform(data);
|
||||
}
|
||||
alias encrypt_mut = crypt;
|
||||
alias decrypt_mut = crypt;
|
||||
|
||||
<*
|
||||
Perform a transformation of some data cloned from a source buffer.
|
||||
|
||||
@param[&inout] allocator : `The memory allocator which controls allocation of the cloned input data.`
|
||||
@param[inout] data : `The data to transform (encrypt or decrypt).`
|
||||
@param key : `The secret key used for the transformation operation.`
|
||||
@param nonce : `The one-time nonce to use for the transformation operation.`
|
||||
@param counter : `An optional counter value to adjust the stream's position.`
|
||||
|
||||
@require key.len == KEY_SIZE : `Input key slice is not the correct length (32 bytes).`
|
||||
@require nonce.len == NONCE_SIZE : `Input nonce slice is not the correct length (12 bytes).`
|
||||
*>
|
||||
fn char[] crypt_clone(Allocator allocator, char[] data, char[KEY_SIZE] key, char[NONCE_SIZE] nonce, uint counter = 0) @private
|
||||
{
|
||||
if (@unlikely(!data.len)) return {};
|
||||
char[] buff = allocator::clone_slice(allocator, data);
|
||||
crypt(buff, key, nonce, counter);
|
||||
return buff;
|
||||
}
|
||||
alias encrypt = crypt_clone;
|
||||
alias decrypt = crypt_clone;
|
||||
|
||||
<*
|
||||
Perform a transformation of some data cloned from a source buffer by the temp allocator.
|
||||
|
||||
@param[inout] data : `The data to transform (encrypt or decrypt).`
|
||||
@param key : `The secret key used for the transformation operation.`
|
||||
@param nonce : `The one-time nonce to use for the transformation operation.`
|
||||
@param counter : `An optional counter value to adjust the stream's position.`
|
||||
|
||||
@require key.len == KEY_SIZE : `Input key slice is not the correct length (32 bytes).`
|
||||
@require nonce.len == NONCE_SIZE : `Input nonce slice is not the correct length (12 bytes).`
|
||||
*>
|
||||
fn char[] tcrypt_clone(char[] data, char[KEY_SIZE] key, char[NONCE_SIZE] nonce, uint counter = 0) @private
|
||||
{
|
||||
return crypt_clone(tmem, data, key, nonce, counter);
|
||||
}
|
||||
alias tencrypt = tcrypt_clone;
|
||||
alias tdecrypt = tcrypt_clone;
|
||||
@@ -9,4 +9,3 @@ fn bool safe_compare(void* data1, void* data2, usz len)
|
||||
}
|
||||
return match == 0;
|
||||
}
|
||||
|
||||
|
||||
737
lib/std/crypto/ed25519.c3
Normal file
737
lib/std/crypto/ed25519.c3
Normal file
@@ -0,0 +1,737 @@
|
||||
/*
|
||||
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;
|
||||
<* Non-zero if true. *>
|
||||
char on_curve;
|
||||
}
|
||||
|
||||
<*
|
||||
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);
|
||||
|
||||
// Subtract 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 conditional 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));
|
||||
|
||||
<*
|
||||
Subtraction.
|
||||
|
||||
@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 conditional 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);
|
||||
}
|
||||
|
||||
<*
|
||||
Subtraction 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;
|
||||
}
|
||||
@@ -101,12 +101,12 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
{
|
||||
if (src.len == 0)
|
||||
{
|
||||
if (padding > 0) return encoding::INVALID_PADDING?;
|
||||
if (padding > 0) return encoding::INVALID_PADDING~;
|
||||
break;
|
||||
}
|
||||
if (src[0] == padding) break;
|
||||
buf[i] = alphabet.reverse[src[0]];
|
||||
if (buf[i] == INVALID) return encoding::INVALID_CHARACTER?;
|
||||
if (buf[i] == INVALID) return encoding::INVALID_CHARACTER~;
|
||||
src = src[1..];
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
dst[0] = buf[1] >> 2 | buf[0] << 3;
|
||||
n++;
|
||||
default:
|
||||
return encoding::INVALID_CHARACTER?;
|
||||
return encoding::INVALID_CHARACTER~;
|
||||
}
|
||||
if (dst.len < 5) break;
|
||||
dst = dst[5..];
|
||||
|
||||
@@ -87,11 +87,11 @@ fn usz? decode_len(usz n, char padding)
|
||||
usz trailing = n % 4;
|
||||
if (padding)
|
||||
{
|
||||
if (trailing != 0) return encoding::INVALID_PADDING?;
|
||||
if (trailing != 0) return encoding::INVALID_PADDING~;
|
||||
// source size is multiple of 4
|
||||
return dn;
|
||||
}
|
||||
if (trailing == 1) return encoding::INVALID_PADDING?;
|
||||
if (trailing == 1) return encoding::INVALID_PADDING~;
|
||||
return dn + trailing * 3 / 4;
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
case c1:
|
||||
case c2:
|
||||
case c3:
|
||||
return encoding::INVALID_CHARACTER?;
|
||||
return encoding::INVALID_CHARACTER~;
|
||||
}
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6 | (uint)c3;
|
||||
dst[0] = (char)(group >> 16);
|
||||
@@ -211,7 +211,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
src = src[^trailing..];
|
||||
char c0 = alphabet.reverse[src[0]];
|
||||
char c1 = alphabet.reverse[src[1]];
|
||||
if (c0 == 0xFF || c1 == 0xFF) return encoding::INVALID_PADDING?;
|
||||
if (c0 == 0xFF || c1 == 0xFF) return encoding::INVALID_PADDING~;
|
||||
if (!padding)
|
||||
{
|
||||
switch (src.len)
|
||||
@@ -221,7 +221,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
dst[0] = (char)(group >> 16);
|
||||
case 3:
|
||||
char c2 = alphabet.reverse[src[2]];
|
||||
if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
|
||||
if (c2 == 0xFF) return encoding::INVALID_CHARACTER~;
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
|
||||
dst[0] = (char)(group >> 16);
|
||||
dst[1] = (char)(group >> 8);
|
||||
@@ -235,13 +235,13 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
switch (padding)
|
||||
{
|
||||
case src[2]:
|
||||
if (src[3] != padding) return encoding::INVALID_PADDING?;
|
||||
if (src[3] != padding) return encoding::INVALID_PADDING~;
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12;
|
||||
dst[0] = (char)(group >> 16);
|
||||
dn -= 2;
|
||||
case src[3]:
|
||||
char c2 = alphabet.reverse[src[2]];
|
||||
if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
|
||||
if (c2 == 0xFF) return encoding::INVALID_CHARACTER~;
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
|
||||
dst[0] = (char)(group >> 16);
|
||||
dst[1] = (char)(group >> 8);
|
||||
|
||||
@@ -79,7 +79,7 @@ macro void? @each_row(InStream stream, String separator = ",", int max_rows = in
|
||||
if (catch err = s)
|
||||
{
|
||||
if (err == io::EOF) return;
|
||||
return err?;
|
||||
return err~;
|
||||
}
|
||||
@body(s.split(mem, separator));
|
||||
};
|
||||
|
||||
@@ -81,7 +81,7 @@ fn usz? decode_bytes(char[] src, char[] dst)
|
||||
{
|
||||
char a = HEXREVERSE[src[j - 1]];
|
||||
char b = HEXREVERSE[src[j]];
|
||||
if (a > 0x0f || b > 0x0f) return encoding::INVALID_CHARACTER?;
|
||||
if (a > 0x0f || b > 0x0f) return encoding::INVALID_CHARACTER~;
|
||||
dst[i] = (a << 4) | b;
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ module std::encoding::json;
|
||||
import std::io;
|
||||
import std::collections::object;
|
||||
|
||||
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, DUPLICATE_MEMBERS, INVALID_NUMBER;
|
||||
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, INVALID_NUMBER, MAX_DEPTH_REACHED;
|
||||
|
||||
int max_depth = 128;
|
||||
|
||||
fn Object*? parse_string(Allocator allocator, String s)
|
||||
{
|
||||
@@ -24,7 +26,7 @@ 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);
|
||||
return parse_any(&context)!;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -62,11 +64,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -80,13 +85,13 @@ fn Object*? parse_from_token(JsonContext* context, JsonTokenType token) @local
|
||||
case COMMA:
|
||||
case RBRACE:
|
||||
case RBRACKET:
|
||||
case COLON: return UNEXPECTED_CHARACTER?;
|
||||
case COLON: return UNEXPECTED_CHARACTER~;
|
||||
case STRING: return object::new_string(context.last_string.str_view(), context.allocator);
|
||||
case NUMBER: return object::new_float(context.last_number, context.allocator);
|
||||
case TRUE: return object::new_bool(true);
|
||||
case FALSE: return object::new_bool(false);
|
||||
case NULL: return object::new_null();
|
||||
case EOF: return io::EOF?;
|
||||
case EOF: return io::EOF~;
|
||||
}
|
||||
}
|
||||
fn Object*? parse_any(JsonContext* context) @local
|
||||
@@ -105,10 +110,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 == '.')
|
||||
{
|
||||
@@ -129,7 +140,7 @@ fn JsonTokenType? lex_number(JsonContext *context, char c) @local
|
||||
t.append(c);
|
||||
c = read_next(context)!;
|
||||
}
|
||||
if (!c.is_digit()) return INVALID_NUMBER?;
|
||||
if (!c.is_digit()) return INVALID_NUMBER~;
|
||||
while (c.is_digit())
|
||||
{
|
||||
t.append(c);
|
||||
@@ -137,7 +148,7 @@ fn JsonTokenType? lex_number(JsonContext *context, char c) @local
|
||||
}
|
||||
}
|
||||
pushback(context, c);
|
||||
double? d = t.str_view().to_double() ?? INVALID_NUMBER?;
|
||||
double? d = t.str_view().to_double() ?? INVALID_NUMBER~;
|
||||
context.last_number = d!;
|
||||
return NUMBER;
|
||||
};
|
||||
@@ -148,15 +159,16 @@ 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)
|
||||
{
|
||||
DString temp_key = dstring::new_with_capacity(mem, 32);
|
||||
while (token != JsonTokenType.RBRACE)
|
||||
{
|
||||
if (token != JsonTokenType.STRING) return UNEXPECTED_CHARACTER?;
|
||||
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();
|
||||
@@ -170,7 +182,7 @@ fn Object*? parse_map(JsonContext* context) @local
|
||||
token = advance(context)!;
|
||||
continue;
|
||||
}
|
||||
if (token != JsonTokenType.RBRACE) return UNEXPECTED_CHARACTER?;
|
||||
if (token != JsonTokenType.RBRACE) return UNEXPECTED_CHARACTER~;
|
||||
}
|
||||
return map;
|
||||
};
|
||||
@@ -180,6 +192,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)
|
||||
{
|
||||
@@ -191,7 +205,7 @@ fn Object*? parse_array(JsonContext* context) @local
|
||||
token = advance(context)!;
|
||||
continue;
|
||||
}
|
||||
if (token != JsonTokenType.RBRACKET) return UNEXPECTED_CHARACTER?;
|
||||
if (token != JsonTokenType.RBRACKET) return UNEXPECTED_CHARACTER~;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@@ -222,7 +236,7 @@ fn char? read_next(JsonContext* context) @local
|
||||
context.reached_end = true;
|
||||
return '\0';
|
||||
}
|
||||
return err?;
|
||||
return err~;
|
||||
}
|
||||
if (c == 0)
|
||||
{
|
||||
@@ -235,7 +249,7 @@ fn JsonTokenType? advance(JsonContext* context) @local
|
||||
{
|
||||
char c;
|
||||
// Skip whitespace
|
||||
while WS: (c = read_next(context)!)
|
||||
while WS: ((c = read_next(context)!))
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
@@ -248,7 +262,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 != '*')
|
||||
{
|
||||
@@ -258,12 +272,12 @@ fn JsonTokenType? advance(JsonContext* context) @local
|
||||
while COMMENT: (true)
|
||||
{
|
||||
// Skip to */
|
||||
while (c = read_next(context)!)
|
||||
while ((c = read_next(context)!))
|
||||
{
|
||||
if (c == '\n') context.line++;
|
||||
if (c != '*') continue;
|
||||
// Skip through all the '*'
|
||||
while (c = read_next(context)!)
|
||||
while ((c = read_next(context)!))
|
||||
{
|
||||
if (c == '\n') context.line++;
|
||||
if (c != '*') break;
|
||||
@@ -279,7 +293,7 @@ fn JsonTokenType? advance(JsonContext* context) @local
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
return io::EOF?;
|
||||
return io::EOF~;
|
||||
case '{':
|
||||
return LBRACE;
|
||||
case '}':
|
||||
@@ -307,7 +321,7 @@ fn JsonTokenType? advance(JsonContext* context) @local
|
||||
match(context, "ull")!;
|
||||
return NULL;
|
||||
default:
|
||||
return UNEXPECTED_CHARACTER?;
|
||||
return UNEXPECTED_CHARACTER~;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,13 +330,13 @@ fn void? match(JsonContext* context, String str) @local
|
||||
foreach (c : str)
|
||||
{
|
||||
char l = read_next(context)!;
|
||||
if (l != c) return UNEXPECTED_CHARACTER?;
|
||||
if (l != c) return UNEXPECTED_CHARACTER~;
|
||||
}
|
||||
}
|
||||
|
||||
fn void? parse_expected(JsonContext* context, JsonTokenType token) @local
|
||||
{
|
||||
if (advance(context)! != token) return UNEXPECTED_CHARACTER?;
|
||||
if (advance(context)! != token) return UNEXPECTED_CHARACTER~;
|
||||
}
|
||||
|
||||
fn JsonTokenType? lex_string(JsonContext* context)
|
||||
@@ -334,9 +348,9 @@ fn JsonTokenType? lex_string(JsonContext* context)
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
return io::EOF?;
|
||||
return io::EOF~;
|
||||
case 1..31:
|
||||
return UNEXPECTED_CHARACTER?;
|
||||
return UNEXPECTED_CHARACTER~;
|
||||
case '"':
|
||||
break LOOP;
|
||||
case '\\':
|
||||
@@ -349,9 +363,9 @@ fn JsonTokenType? lex_string(JsonContext* context)
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
return io::EOF?;
|
||||
return io::EOF~;
|
||||
case 1..31:
|
||||
return UNEXPECTED_CHARACTER?;
|
||||
return UNEXPECTED_CHARACTER~;
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
@@ -371,13 +385,13 @@ fn JsonTokenType? lex_string(JsonContext* context)
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
c = read_next(context)!;
|
||||
if (!c.is_xdigit()) return INVALID_ESCAPE_SEQUENCE?;
|
||||
if (!c.is_xdigit()) return INVALID_ESCAPE_SEQUENCE~;
|
||||
val = val << 4 + (c > '9' ? (c | 32) - 'a' + 10 : c - '0');
|
||||
}
|
||||
context.last_string.append_char32(val);
|
||||
continue;
|
||||
default:
|
||||
return INVALID_ESCAPE_SEQUENCE?;
|
||||
return INVALID_ESCAPE_SEQUENCE~;
|
||||
}
|
||||
context.last_string.append(c);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module std::experimental::scheduler{Event};
|
||||
module std::experimental::scheduler <Event>;
|
||||
import std::collections, std::thread, std::time;
|
||||
|
||||
struct DelayedSchedulerEvent @local
|
||||
@@ -43,7 +43,7 @@ macro void FrameScheduler.@destroy(&self; @destruct(Event e))
|
||||
self.events.free();
|
||||
self.pending_events.free();
|
||||
self.delayed_events.free();
|
||||
(void)self.mtx.destroy();
|
||||
self.mtx.destroy();
|
||||
}
|
||||
|
||||
fn void FrameScheduler.queue_delayed_event(&self, Event event, Duration delay)
|
||||
@@ -76,7 +76,7 @@ fn Event? FrameScheduler.pop_event(&self)
|
||||
while (true)
|
||||
{
|
||||
if (try event = self.events.pop()) return event;
|
||||
if (!@atomic_load(self.pending)) return NO_MORE_ELEMENT?;
|
||||
if (!@atomic_load(self.pending)) return NO_MORE_ELEMENT~;
|
||||
self.mtx.@in_lock()
|
||||
{
|
||||
self.events.add_all(&self.pending_events);
|
||||
@@ -88,7 +88,7 @@ fn Event? FrameScheduler.pop_event(&self)
|
||||
self.events.push(self.delayed_events.pop()!!);
|
||||
}
|
||||
@atomic_store(self.pending, self.delayed_events.len() > 0);
|
||||
if (!self.events.len()) return NO_MORE_ELEMENT?;
|
||||
if (!self.events.len()) return NO_MORE_ELEMENT~;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
96
lib/std/hash/a5hash.c3
Normal file
96
lib/std/hash/a5hash.c3
Normal 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 void @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(
|
||||
mem::load((ulong*)data.ptr, 1) ^ seed1,
|
||||
mem::load((ulong*)data.ptr + 1, 1) ^ seed2,
|
||||
seed1, seed2
|
||||
);
|
||||
|
||||
seed1 += val01;
|
||||
seed2 += val10;
|
||||
}
|
||||
|
||||
a = mem::load((ulong*)(data.ptr + (uptr)data.len - 16), 1);
|
||||
b = mem::load((ulong*)(data.ptr + (uptr)data.len - 8), 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
a = ((ulong)mem::load((uint*)&data[0], 1) << 32)
|
||||
| mem::load((uint*)&data[^4], 1);
|
||||
|
||||
b = ((ulong)mem::load((uint*)&data[(data.len >> 3) * 4], 1) << 32)
|
||||
| mem::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;
|
||||
}
|
||||
383
lib/std/hash/blake2.c3
Normal file
383
lib/std/hash/blake2.c3
Normal file
@@ -0,0 +1,383 @@
|
||||
// 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.
|
||||
//
|
||||
// This is based on the original BLAKE2 reference implementation, but it's been
|
||||
// significantly changed. You can really see how C3's features shine over the
|
||||
// original C source.
|
||||
//
|
||||
// You'll note that this may have been better done with generic modules. I'm not
|
||||
// an expert, but I'd argue that it's unnecessary for this particular algorithm.
|
||||
// There are only two important versions of the algorithm to implement in a "common"
|
||||
// (generic) way, and the output size of each type is controlled rather naturally.
|
||||
//
|
||||
module std::hash::blake2;
|
||||
|
||||
|
||||
// Common hash output sizes. The library is NOT limited to these six output sizes, but
|
||||
// these instead stand in for what would have been "magic numbers" throughout the code.
|
||||
const SIZE_128 = 16;
|
||||
const SIZE_160 = 20;
|
||||
const SIZE_224 = 28;
|
||||
const SIZE_256 = 32;
|
||||
const SIZE_384 = 48;
|
||||
const SIZE_512 = 64;
|
||||
|
||||
|
||||
macro @g(r, m, $s, i, #a, #b, #c, #d) @local
|
||||
{
|
||||
// I really dislike using these conditional one-liners, but... it's the only difference between 2s and 2b besides buffer sizes (mainly).
|
||||
#a += #b + m[$s[r][2*i+0]];
|
||||
#d = (#d ^ #a).rotr($sizeof(#a) == ulong.sizeof ??? 32 : 16);
|
||||
#c += #d;
|
||||
#b = (#b ^ #c).rotr($sizeof(#a) == ulong.sizeof ??? 24 : 12);
|
||||
#a += #b + m[$s[r][2*i+1]];
|
||||
#d = (#d ^ #a).rotr($sizeof(#a) == ulong.sizeof ??? 16 : 8);
|
||||
#c += #d;
|
||||
#b = (#b ^ #c).rotr($sizeof(#a) == ulong.sizeof ??? 63 : 7);
|
||||
}
|
||||
|
||||
macro @round(r, m, $s, #v) @local
|
||||
{
|
||||
@g(r, m, $s, 0, #v[ 0], #v[ 4], #v[ 8], #v[12]);
|
||||
@g(r, m, $s, 1, #v[ 1], #v[ 5], #v[ 9], #v[13]);
|
||||
@g(r, m, $s, 2, #v[ 2], #v[ 6], #v[10], #v[14]);
|
||||
@g(r, m, $s, 3, #v[ 3], #v[ 7], #v[11], #v[15]);
|
||||
@g(r, m, $s, 4, #v[ 0], #v[ 5], #v[10], #v[15]);
|
||||
@g(r, m, $s, 5, #v[ 1], #v[ 6], #v[11], #v[12]);
|
||||
@g(r, m, $s, 6, #v[ 2], #v[ 7], #v[ 8], #v[13]);
|
||||
@g(r, m, $s, 7, #v[ 3], #v[ 4], #v[ 9], #v[14]);
|
||||
}
|
||||
|
||||
macro @common_compress(#instance, $rounds, $iv, $sigma, block) @local
|
||||
{
|
||||
$typeof(#instance.h[0])[16] m, v;
|
||||
|
||||
((char*)&m)[:$sizeof(block)] = block[..];
|
||||
|
||||
v[:8] = #instance.h[..];
|
||||
v[ 8] = $iv[0];
|
||||
v[ 9] = $iv[1];
|
||||
v[10] = $iv[2];
|
||||
v[11] = $iv[3];
|
||||
v[12] = $iv[4] ^ #instance.t[0];
|
||||
v[13] = $iv[5] ^ #instance.t[1];
|
||||
v[14] = $iv[6] ^ #instance.f[0];
|
||||
v[15] = $iv[7] ^ #instance.f[1];
|
||||
|
||||
$for usz $i = 0; $i < $rounds; $i++:
|
||||
@round($i, m, $sigma, v);
|
||||
$endfor
|
||||
|
||||
$for usz $i = 0; $i < 8; $i++:
|
||||
#instance.h[$i] ^= v[$i] ^ v[$i + 8];
|
||||
$endfor
|
||||
}
|
||||
|
||||
macro @add_ctr(#instance, usz amount) @local
|
||||
{
|
||||
#instance.t[0] += ($typeof(#instance.t[0]))amount;
|
||||
#instance.t[1] += ($typeof(#instance.t[0]))(#instance.t[0] < amount); // adds 1 on overflow of [0]
|
||||
}
|
||||
|
||||
macro @common_init(#instance, $ParamType, $iv, usz out_len, char[] key = {}, char[] salt = {}, char[] personal = {}) @local
|
||||
{
|
||||
mem::zero_volatile(@as_char_view(*#instance)); // explicitly because habits around hash init usually involve @noinit
|
||||
|
||||
#instance.h[..] = $iv[..];
|
||||
#instance.outlen = out_len;
|
||||
|
||||
$ParamType p = {
|
||||
.digest_length = (char)out_len,
|
||||
.key_length = (char)key.len,
|
||||
.fanout = 1,
|
||||
.depth = 1,
|
||||
};
|
||||
if (salt.len) p.salt[:salt.len] = salt[..];
|
||||
if (personal.len) p.personal[:personal.len] = personal[..];
|
||||
|
||||
array::@zip_into(((char*)&#instance.h)[:$sizeof(p)], ((char*)&p)[:$sizeof(p)], fn (a, b) => a ^ b); // bytes(self.h) ^= bytes(p)
|
||||
|
||||
if (key.len)
|
||||
{
|
||||
char[$sizeof($iv[0])*16] dummy = {};
|
||||
dummy[:key.len] = key[..];
|
||||
#instance.update(dummy[..]); // consume a FULL block
|
||||
mem::zero_volatile(dummy[..]); // do not optimize clearing this from the stack
|
||||
}
|
||||
}
|
||||
|
||||
macro @common_update(#instance, $block_size, char[] data) @local
|
||||
{
|
||||
if (@unlikely(!data.len)) return;
|
||||
|
||||
usz fill = $block_size - #instance.buflen;
|
||||
if (data.len > fill)
|
||||
{
|
||||
#instance.buf[#instance.buflen:fill] = data[:fill];
|
||||
#instance.buflen = 0;
|
||||
|
||||
@add_ctr(#instance, $block_size);
|
||||
#instance.compress(#instance.buf);
|
||||
|
||||
data = data[fill..];
|
||||
|
||||
for (; data.len > $block_size; data = data[$block_size..])
|
||||
{
|
||||
@add_ctr(#instance, $block_size);
|
||||
#instance.compress(data[:$block_size]);
|
||||
}
|
||||
}
|
||||
#instance.buf[#instance.buflen:data.len] = data[..];
|
||||
#instance.buflen += data.len;
|
||||
}
|
||||
|
||||
macro @common_final(#instance, $output_length) @local
|
||||
{
|
||||
char[$output_length] result = {};
|
||||
if ($output_length != #instance.outlen) return result;
|
||||
|
||||
@add_ctr(#instance, #instance.buflen);
|
||||
if (#instance.f[0]) return result; // technically an error return
|
||||
|
||||
if (#instance.last_node) #instance.f[1] = $typeof(#instance.h[0]).max;
|
||||
#instance.f[0] = $typeof(#instance.h[0]).max;
|
||||
|
||||
mem::zero_volatile(#instance.buf[#instance.buflen..]); // pad buffer with zeroes
|
||||
#instance.compress(#instance.buf);
|
||||
|
||||
defer mem::zero_volatile(@as_char_view(*#instance)); // destroy the current context implicitly
|
||||
|
||||
result[:#instance.outlen] = @as_char_view(#instance.h)[:#instance.outlen];
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// ======================================================================================
|
||||
// BEGIN Blake2b contents. Do not separate this from Blake2s: there's not really a point.
|
||||
//
|
||||
const BLAKE2B_BLOCKBYTES @local = 128;
|
||||
const BLAKE2B_OUTBYTES @local = 64;
|
||||
const BLAKE2B_KEYBYTES @local = 64;
|
||||
const BLAKE2B_SALTBYTES @local = 16;
|
||||
const BLAKE2B_PERSONALBYTES @local = 16;
|
||||
const ulong[8] BLAKE2B_IV @local = {
|
||||
0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
|
||||
0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179
|
||||
};
|
||||
const char[16][12] BLAKE2B_SIGMA @local = {
|
||||
x'000102030405060708090a0b0c0d0e0f',
|
||||
x'0e0a0408090f0d06010c00020b070503',
|
||||
x'0b080c0005020f0d0a0e030607010904',
|
||||
x'070903010d0c0b0e0206050a04000f08',
|
||||
x'0900050702040a0f0e010b0c0608030d',
|
||||
x'020c060a000b0803040d07050f0e0109',
|
||||
x'0c05010f0e0d040a000706030902080b',
|
||||
x'0d0b070e0c01030905000f040806020a',
|
||||
x'060f0e090b0300080c020d0701040a05',
|
||||
x'0a020804070601050f0b090e030c0d00',
|
||||
x'000102030405060708090a0b0c0d0e0f',
|
||||
x'0e0a0408090f0d06010c00020b070503',
|
||||
};
|
||||
|
||||
struct Blake2b
|
||||
{
|
||||
ulong[8] h;
|
||||
ulong[2] t;
|
||||
ulong[2] f;
|
||||
char[BLAKE2B_BLOCKBYTES] buf;
|
||||
usz buflen;
|
||||
usz outlen;
|
||||
char last_node;
|
||||
}
|
||||
|
||||
struct Blake2bParam @packed @local
|
||||
{
|
||||
char digest_length; /* 1 */
|
||||
char key_length; /* 2 */
|
||||
char fanout; /* 3 */
|
||||
char depth; /* 4 */
|
||||
uint leaf_length; /* 8 */
|
||||
uint node_offset; /* 12 */
|
||||
uint xof_length; /* 16 */
|
||||
char node_depth; /* 17 */
|
||||
char inner_length; /* 18 */
|
||||
char[14] reserved; /* 32 */
|
||||
char[BLAKE2B_SALTBYTES] salt; /* 48 */
|
||||
char[BLAKE2B_PERSONALBYTES] personal; /* 64 */
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(data[0]) &&& $typeof(data[0]) == char : "Input data must be a char slice, char array, or string value."
|
||||
*>
|
||||
macro blake2b_hash($out_len, data, char[] key = {}, char[] salt = {})
|
||||
{
|
||||
Blake2b b @noinit;
|
||||
b.init($out_len, key, salt);
|
||||
b.update(data[..]);
|
||||
return b.final($out_len);
|
||||
}
|
||||
alias b = blake2b_hash;
|
||||
|
||||
// See RFC 7693, Section 4 for common parameter sets.
|
||||
macro blake2b_224(data, char[] key = {}, char[] salt = {}) => blake2b_hash(SIZE_224, data, key, salt);
|
||||
macro blake2b_256(data, char[] key = {}, char[] salt = {}) => blake2b_hash(SIZE_256, data, key, salt);
|
||||
macro blake2b_384(data, char[] key = {}, char[] salt = {}) => blake2b_hash(SIZE_384, data, key, salt);
|
||||
macro blake2b_512(data, char[] key = {}, char[] salt = {}) => blake2b_hash(SIZE_512, data, key, salt);
|
||||
|
||||
alias b_224 = blake2b_224;
|
||||
alias b_256 = blake2b_256;
|
||||
alias b_384 = blake2b_384;
|
||||
alias b_512 = blake2b_512;
|
||||
|
||||
<*
|
||||
Blake2b initialization method. Presents various options
|
||||
|
||||
@param out_len : "The desired output length from the hash function."
|
||||
@param[in] key : "An optional key value to use (keys the entire hash value to give HMAC-like functionality)."
|
||||
@param[in] salt : "An optional salt value to use when generating the hash."
|
||||
@param[in] personal : "An optional personalization value to use when generating the hash."
|
||||
|
||||
@require out_len > 0 && out_len <= BLAKE2B_OUTBYTES : "Blake2 output length must be within the proper range."
|
||||
@require !key.ptr || (key.len > 0 && key.len <= BLAKE2B_KEYBYTES) : "A specified key's length must be within the proper range."
|
||||
@require !salt.ptr || (salt.len > 0 && salt.len <= BLAKE2B_SALTBYTES) : "A specified salt's length must be within the proper range."
|
||||
@require !personal.ptr || (personal.len > 0 && personal.len <= BLAKE2B_PERSONALBYTES) : "A specified personalization's length must be within the proper range."
|
||||
*>
|
||||
fn void Blake2b.init(&self, usz out_len, char[] key = {}, char[] salt = {}, char[] personal = {})
|
||||
=> @common_init(self, Blake2bParam, BLAKE2B_IV, out_len, key, salt, personal);
|
||||
|
||||
<*
|
||||
Core compression inline function for Blake2b.
|
||||
*>
|
||||
fn void Blake2b.compress(&self, char[BLAKE2B_BLOCKBYTES] block) @local @inline
|
||||
=> @common_compress(self, 12, BLAKE2B_IV, BLAKE2B_SIGMA, block);
|
||||
|
||||
<*
|
||||
Add more data to the hash context or stream.
|
||||
|
||||
@param[in] data : "The data to ingest into the hash context."
|
||||
*>
|
||||
fn void Blake2b.update(&self, char[] data)
|
||||
=> @common_update(self, BLAKE2B_BLOCKBYTES, data);
|
||||
|
||||
<*
|
||||
Finalize the hash context and return the hash result at the given size.
|
||||
|
||||
@param $output_length : "The length of the output array which is returned by value, instead of as a slice."
|
||||
|
||||
@require $output_length == self.outlen : "The specified compile-time output size MUST be equal to the initialized output size."
|
||||
*>
|
||||
macro char[*] Blake2b.final(&self, $output_length)
|
||||
=> @common_final(self, $output_length);
|
||||
|
||||
|
||||
// ======================================================================================
|
||||
// BEGIN Blake2s contents. Do not separate this from Blake2b: there's not really a point.
|
||||
//
|
||||
const BLAKE2S_BLOCKBYTES @local = BLAKE2B_BLOCKBYTES / 2;
|
||||
const BLAKE2S_OUTBYTES @local = BLAKE2B_OUTBYTES / 2;
|
||||
const BLAKE2S_KEYBYTES @local = BLAKE2B_KEYBYTES / 2;
|
||||
const BLAKE2S_SALTBYTES @local = BLAKE2B_SALTBYTES / 2;
|
||||
const BLAKE2S_PERSONALBYTES @local = BLAKE2B_PERSONALBYTES / 2;
|
||||
const uint[8] BLAKE2S_IV @local = { 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 };
|
||||
const char[16][10] BLAKE2S_SIGMA @local = {
|
||||
x'000102030405060708090a0b0c0d0e0f',
|
||||
x'0e0a0408090f0d06010c00020b070503',
|
||||
x'0b080c0005020f0d0a0e030607010904',
|
||||
x'070903010d0c0b0e0206050a04000f08',
|
||||
x'0900050702040a0f0e010b0c0608030d',
|
||||
x'020c060a000b0803040d07050f0e0109',
|
||||
x'0c05010f0e0d040a000706030902080b',
|
||||
x'0d0b070e0c01030905000f040806020a',
|
||||
x'060f0e090b0300080c020d0701040a05',
|
||||
x'0a020804070601050f0b090e030c0d00',
|
||||
};
|
||||
|
||||
struct Blake2s
|
||||
{
|
||||
uint[8] h;
|
||||
uint[2] t;
|
||||
uint[2] f;
|
||||
char[BLAKE2S_BLOCKBYTES] buf;
|
||||
usz buflen;
|
||||
usz outlen;
|
||||
char last_node;
|
||||
}
|
||||
|
||||
struct Blake2sParam @packed @local
|
||||
{
|
||||
char digest_length; /* 1 */
|
||||
char key_length; /* 2 */
|
||||
char fanout; /* 3 */
|
||||
char depth; /* 4 */
|
||||
uint leaf_length; /* 8 */
|
||||
uint node_offset; /* 12 */
|
||||
ushort xof_length; /* 14 */
|
||||
char node_depth; /* 15 */
|
||||
char inner_length; /* 16 */
|
||||
char[BLAKE2S_SALTBYTES] salt; /* 24 */
|
||||
char[BLAKE2S_PERSONALBYTES] personal; /* 32 */
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(data[0]) &&& $typeof(data[0]) == char : "Input data must be a char slice, char array, or string value."
|
||||
*>
|
||||
macro blake2s_hash($out_len, data, char[] key = {}, char[] salt = {})
|
||||
{
|
||||
Blake2s b @noinit;
|
||||
b.init($out_len, key, salt);
|
||||
b.update(data[..]);
|
||||
return b.final($out_len);
|
||||
}
|
||||
alias s = blake2s_hash;
|
||||
|
||||
// See RFC 7693, Section 4 for common parameter sets.
|
||||
macro blake2s_128(data, char[] key = {}, char[] salt = {}) => blake2s_hash(SIZE_128, data, key, salt);
|
||||
macro blake2s_160(data, char[] key = {}, char[] salt = {}) => blake2s_hash(SIZE_160, data, key, salt);
|
||||
macro blake2s_224(data, char[] key = {}, char[] salt = {}) => blake2s_hash(SIZE_224, data, key, salt);
|
||||
macro blake2s_256(data, char[] key = {}, char[] salt = {}) => blake2s_hash(SIZE_256, data, key, salt);
|
||||
|
||||
alias s_128 = blake2s_128;
|
||||
alias s_160 = blake2s_160;
|
||||
alias s_224 = blake2s_224;
|
||||
alias s_256 = blake2s_256;
|
||||
|
||||
<*
|
||||
Blake2s initialization method. Presents various options
|
||||
|
||||
@param out_len : "The desired output length from the hash function."
|
||||
@param[in] key : "An optional key value to use (keys the entire hash value to give HMAC-like functionality)."
|
||||
@param[in] salt : "An optional salt value to use when generating the hash."
|
||||
@param[in] personal : "An optional personalization value to use when generating the hash."
|
||||
|
||||
@require out_len > 0 && out_len <= BLAKE2S_OUTBYTES : "Blake2 output length must be within the proper range."
|
||||
@require !key.ptr || (key.len > 0 && key.len <= BLAKE2S_KEYBYTES) : "A specified key's length must be within the proper range."
|
||||
@require !salt.ptr || (salt.len > 0 && salt.len <= BLAKE2S_SALTBYTES) : "A specified salt's length must be within the proper range."
|
||||
@require !personal.ptr || (personal.len > 0 && personal.len <= BLAKE2B_PERSONALBYTES) : "A specified personalization's length must be within the proper range."
|
||||
*>
|
||||
fn void Blake2s.init(&self, usz out_len, char[] key = {}, char[] salt = {}, char[] personal = {})
|
||||
=> @common_init(self, Blake2sParam, BLAKE2S_IV, out_len, key, salt, personal);
|
||||
|
||||
<*
|
||||
Core compression inline function for Blake2s.
|
||||
*>
|
||||
fn void Blake2s.compress(&self, char[BLAKE2S_BLOCKBYTES] block) @local @inline
|
||||
=> @common_compress(self, 10, BLAKE2S_IV, BLAKE2S_SIGMA, block);
|
||||
|
||||
<*
|
||||
Add more data to the hash context or stream.
|
||||
|
||||
@param[in] data : "The data to ingest into the hash context."
|
||||
*>
|
||||
fn void Blake2s.update(&self, char[] data)
|
||||
=> @common_update(self, BLAKE2S_BLOCKBYTES, data);
|
||||
|
||||
<*
|
||||
Finalize the hash context and return the hash result at the given size.
|
||||
|
||||
@param $output_length : "The length of the output array which is returned by value, instead of as a slice."
|
||||
|
||||
@require $output_length == self.outlen : "The specified compile-time output size MUST be equal to the initialized output size."
|
||||
*>
|
||||
macro char[*] Blake2s.final(&self, $output_length)
|
||||
=> @common_final(self, $output_length);
|
||||
747
lib/std/hash/blake3.c3
Normal file
747
lib/std/hash/blake3.c3
Normal file
@@ -0,0 +1,747 @@
|
||||
// Copyright (c) 2025-2026 Zack Puhl <github@xmit.xyz>. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
//
|
||||
// This is based on the original BLAKE3 reference implementation:
|
||||
// https://github.com/BLAKE3-team/BLAKE3/blob/master
|
||||
//
|
||||
module std::hash::blake3;
|
||||
|
||||
|
||||
const BLOCK_SIZE = 64;
|
||||
const CHUNK_SIZE = 1024;
|
||||
const KEY_SIZE = 32;
|
||||
const KEY_SIZE_WORDS = KEY_SIZE / uint.sizeof;
|
||||
const OUT_SIZE = 32;
|
||||
const MAX_DEPTH = 54;
|
||||
|
||||
const uint[8] IV = {
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
||||
};
|
||||
|
||||
const char[16][7] MESSAGE_SCHEDULE = {
|
||||
x'000102030405060708090a0b0c0d0e0f',
|
||||
x'0206030a0700040d010b0c05090e0f08',
|
||||
x'03040a0c0d02070e060509000b0f0801',
|
||||
x'0a070c090e030d0f04000b0205080106',
|
||||
x'0c0d090b0f0a0e080702050300010604',
|
||||
x'090e0b05080c0f010d03000a02060407',
|
||||
x'0b0f0500010908060e0a020c0304070d',
|
||||
};
|
||||
|
||||
|
||||
// Get feature-based optimization options.
|
||||
// For now, none of these are used until there's a chance to explore BLAKE3's (necessary) vectorization optimizations.
|
||||
//
|
||||
<* When true, force the use of slow-but-portable BLAKE3 functions. Do not vectorize the hash function. *>
|
||||
const FORCE_PORTABLE = true; //$feature(BLAKE3_FORCE_PORTABLE); // this is statically set to TRUE for now
|
||||
<* AARCH64: When not big-endian, use Neon. *>
|
||||
const USE_NEON = !FORCE_PORTABLE &&& (env::AARCH64 &&& !env::BIG_ENDIAN);
|
||||
<* Bundling some architecture booleans into one. *>
|
||||
const IS_X86 = !FORCE_PORTABLE &&& (env::X86_64 ||| env::X86);
|
||||
<*
|
||||
The maximum possible degree of parallelization based on the current architecture.
|
||||
This doesn't represent the ACTUAL degree available.
|
||||
*>
|
||||
const MAX_SIMD_DEGREE = IS_X86 ??? 16 : (USE_NEON ??? 4 : 1);
|
||||
<* There are cases in BLAKE3 where, at compile-time, it's necessary to easily get the max degree, or a minimum of 2. *>
|
||||
const MAX_SIMD_DEGREE_OR_2 = @max(MAX_SIMD_DEGREE, 2);
|
||||
|
||||
|
||||
<* Always set to true once BLAKE3 caches some initial CPU details. *>
|
||||
bool cpuinfo_initd @local = false;
|
||||
|
||||
<*
|
||||
Cache some information at runtime about the current processor and platform, as needed for optimizations.
|
||||
*>
|
||||
fn void init_blake3() @local @init
|
||||
{
|
||||
$if IS_X86:
|
||||
cpudetect::x86_initialize_cpu_features(); // query all x86 feature flags, one time
|
||||
$endif
|
||||
cpuinfo_initd = true;
|
||||
}
|
||||
|
||||
<* Check whether a given CPU flag is set (x86/x86_64 only). *>
|
||||
macro bool @check_cpu_flag(X86Feature f) @local @if(IS_X86)
|
||||
=> !!(cpudetect::x86_features & f.ordinal);
|
||||
|
||||
<*
|
||||
Return the actual SIMD degree of the processor at runtime.
|
||||
*>
|
||||
macro @simd_degree() @local
|
||||
{
|
||||
if (!cpuinfo_initd) init_blake3();
|
||||
assert(cpuinfo_initd == true, "Failed to run required BLAKE3 initializations.");
|
||||
|
||||
$switch:
|
||||
$case IS_X86:
|
||||
if (@check_cpu_flag(AVX512F) && @check_cpu_flag(AVX512VL)) return 16;
|
||||
if (@check_cpu_flag(AVX2)) return 8;
|
||||
if (@check_cpu_flag(SSE4_1) || @check_cpu_flag(SSE2)) return 4;
|
||||
$case USE_NEON:
|
||||
return 4;
|
||||
$endswitch
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
<* Flags used during hash computation based on its state. *>
|
||||
enum Blake3Flags : const inline char
|
||||
{
|
||||
CHUNK_START = 1 << 0,
|
||||
CHUNK_END = 1 << 1,
|
||||
PARENT = 1 << 2,
|
||||
ROOT = 1 << 3,
|
||||
KEYED_HASH = 1 << 4,
|
||||
DERIVE_KEY_CONTEXT = 1 << 5,
|
||||
DERIVE_KEY_MATERIAL = 1 << 6,
|
||||
}
|
||||
|
||||
struct Blake3ChunkState @local
|
||||
{
|
||||
uint[8] cv;
|
||||
ulong chunk_counter;
|
||||
char[BLOCK_SIZE] buf;
|
||||
char buf_len;
|
||||
char blocks_compressed;
|
||||
char flags;
|
||||
}
|
||||
|
||||
struct Blake3Output @local
|
||||
{
|
||||
uint[KEY_SIZE_WORDS] input_cv;
|
||||
ulong counter;
|
||||
char[BLOCK_SIZE] block;
|
||||
char block_len;
|
||||
char flags;
|
||||
}
|
||||
|
||||
struct Blake3
|
||||
{
|
||||
uint[KEY_SIZE_WORDS] key;
|
||||
Blake3ChunkState chunk;
|
||||
char cv_stack_len;
|
||||
char[(MAX_DEPTH + 1) * OUT_SIZE] cv_stack;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Generate an XOF hash based on the given inputs.
|
||||
|
||||
Consider the output hash w/ `seek = 0` and `$out_size = 41`:
|
||||
```
|
||||
2cc39783c223154fea8dfb7c1b1660f2ac2dcbd1c1de8277b0b0dd39b7e50d7d905630c8be290dfcf3
|
||||
```
|
||||
|
||||
Computing with the same input `key` and input `data`, but with a `seek = 3` and `$out_size = 8` yields:
|
||||
```
|
||||
83c223154fea8dfb
|
||||
|
||||
which is a slice cut out from the above hash:
|
||||
2cc397 [83c223154fea8dfb] 7c1b1660f2ac2dcbd1c1de8277b0b0dd39b7e50d7d905630c8be290dfcf3
|
||||
```
|
||||
|
||||
In this way, the XOF primitive that BLAKE3 is built from allows the hash output to be a potentially
|
||||
limitless result that one may slice to their liking using the right parameters.
|
||||
|
||||
@param [in] data : "The data to hash."
|
||||
@param [in] key : "An optional 32-byte key to turn the result into a keyed hash."
|
||||
@param seek : "An optional value specifying the offset into the XOF's yield where the resultant hash should begin."
|
||||
@param $out_size : "An optional value specifying the desired length to slice from the XOF's yield."
|
||||
|
||||
@return "The hash as a character array of `$out_size` bytes."
|
||||
|
||||
@require !key.len || key.len == KEY_SIZE : "Key value must be empty or exactly 32 bytes."
|
||||
@require $out_size > 0 : "You cannot use a zero $out_size."
|
||||
*>
|
||||
macro char[*] hash(char[] data, char[] key = {}, usz seek = 0, usz $out_size = 32)
|
||||
{
|
||||
char[$out_size] result;
|
||||
Blake3 b @noinit;
|
||||
defer b.destroy();
|
||||
b.init(key);
|
||||
b.update(data);
|
||||
b.final(result[..], $out_size, seek);
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Generate a hash from a context string. This call allows one to use the "context" to
|
||||
auto-generate keying material for the resultant hash value. Effectively, this allows for
|
||||
hashes made from data with completely variable-length keys, rather than having a key fixed
|
||||
to 32 bytes. The 'context' nomenclature is from BLAKE3 itself, not my naming.
|
||||
|
||||
@param [in] data : "The data to hash."
|
||||
@param [in] context : "An optional key to turn the result into a keyed hash."
|
||||
@param seek : "An optional value specifying the offset into the XOF's yield where the resultant hash should begin."
|
||||
@param $out_size : "An optional value specifying the desired length to slice from the XOF's yield."
|
||||
|
||||
@return "The context-based hash as a character array of `$out_size` bytes."
|
||||
|
||||
@require $out_size > 0 : "You cannot use a zero $out_size."
|
||||
*>
|
||||
macro char[*] ctx_hash(char[] data, char[] context, usz seek = 0, usz $out_size = 32)
|
||||
{
|
||||
char[$out_size] result;
|
||||
Blake3 b = new_from_context(context);
|
||||
defer b.destroy();
|
||||
b.update(data);
|
||||
b.final(result[..], $out_size, seek);
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Generate a new Blake3 hashing structure from the given context string. The context string
|
||||
acts as a variable-length key to seed the new hash structure, and makes it ready to ingest
|
||||
incoming data with `update`.
|
||||
|
||||
@param [in] context : "The context byte array used to seed the returned Blake3 context."
|
||||
*>
|
||||
macro Blake3 new_from_context(char[] context)
|
||||
{
|
||||
char[KEY_SIZE] context_based_key;
|
||||
defer mem::zero_volatile(context_based_key[..]);
|
||||
|
||||
Blake3 key_from_ctx @noinit;
|
||||
defer key_from_ctx.destroy();
|
||||
key_from_ctx.init(explicit_flags: Blake3Flags.DERIVE_KEY_CONTEXT);
|
||||
key_from_ctx.update(context);
|
||||
key_from_ctx.final(context_based_key[..], KEY_SIZE);
|
||||
|
||||
Blake3 b @noinit;
|
||||
b.init(key: context_based_key[..], explicit_flags: Blake3Flags.DERIVE_KEY_MATERIAL);
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Initialize a BLAKE3 context.
|
||||
|
||||
@param [in] key : "An optional key initializer to use."
|
||||
|
||||
@require !key.len || key.len == KEY_SIZE : "An explicit initialization key must be of KEY_SIZE (32 bytes)."
|
||||
*>
|
||||
fn void Blake3.init(&self, char[] key = {}, char explicit_flags = 0)
|
||||
{
|
||||
mem::zero_volatile(@as_char_view(*self));
|
||||
|
||||
if (key.len)
|
||||
{
|
||||
foreach (i, &w : self.key) *w = mem::load((uint*)&key[i * $sizeof(self.key[0])], 1);
|
||||
if (!explicit_flags) explicit_flags = Blake3Flags.KEYED_HASH;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.key[..] = IV[..];
|
||||
}
|
||||
|
||||
self.chunk.init(self.key[..], explicit_flags);
|
||||
}
|
||||
|
||||
<*
|
||||
Reset the state of the hashing context, in case it should be reused without reloading the key value.
|
||||
*>
|
||||
fn void Blake3.reset(&self) @local @inline
|
||||
{
|
||||
self.chunk.reset(self.key[..], 0);
|
||||
self.cv_stack_len = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Private function to merge tree results.
|
||||
*>
|
||||
fn void Blake3.merge_cv_stack(&self, ulong total_len) @local @inline
|
||||
{
|
||||
usz post_merge_stack_len = (usz)@popcnt(total_len);
|
||||
for (; self.cv_stack_len > post_merge_stack_len; self.cv_stack_len--)
|
||||
{
|
||||
char* parent_node = &self.cv_stack[(self.cv_stack_len - 2) * OUT_SIZE];
|
||||
Blake3Output o = parent_output(parent_node, self.key[..], self.chunk.flags);
|
||||
o.chaining_value(parent_node);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Private function to add a new tree onto the stack.
|
||||
*>
|
||||
fn void Blake3.push_cv(&self, char* new_cv, ulong chunk_counter) @local @inline
|
||||
{
|
||||
self.merge_cv_stack(chunk_counter);
|
||||
self.cv_stack[self.cv_stack_len * OUT_SIZE : OUT_SIZE] = new_cv[:OUT_SIZE];
|
||||
self.cv_stack_len++;
|
||||
}
|
||||
|
||||
<*
|
||||
Update the hash context by consuming incoming data.
|
||||
|
||||
@param [in] input : "The slice of new data to digest."
|
||||
@param use_tbb : "Should remain `false` until other BLAKE3 optimizations are set up."
|
||||
*>
|
||||
fn void Blake3.update(&self, char[] input, bool use_tbb = false)
|
||||
{
|
||||
if (!input.len) return;
|
||||
|
||||
if (self.chunk.len() > 0)
|
||||
{
|
||||
usz take = min(CHUNK_SIZE - self.chunk.len(), input.len);
|
||||
self.chunk.update(input[:take]);
|
||||
input = input[take..];
|
||||
|
||||
if (!input.len) return;
|
||||
|
||||
char[KEY_SIZE] chunk_cv;
|
||||
Blake3Output o = self.chunk.output();
|
||||
o.chaining_value(&chunk_cv);
|
||||
self.push_cv(&chunk_cv, self.chunk.chunk_counter);
|
||||
self.chunk.reset(self.key[..], self.chunk.chunk_counter + 1);
|
||||
}
|
||||
|
||||
while (input.len > CHUNK_SIZE)
|
||||
{
|
||||
usz subtree_len = @round_down_to_power_of_2(input.len);
|
||||
ulong count_so_far = self.chunk.chunk_counter * CHUNK_SIZE;
|
||||
|
||||
while ((((ulong)(subtree_len - 1)) & count_so_far) != 0) subtree_len /= 2;
|
||||
|
||||
ulong subtree_chunks = subtree_len / CHUNK_SIZE;
|
||||
if (subtree_len <= CHUNK_SIZE)
|
||||
{
|
||||
Blake3ChunkState chunk_state;
|
||||
chunk_state.init(self.key[..], self.chunk.flags);
|
||||
chunk_state.chunk_counter = self.chunk.chunk_counter;
|
||||
chunk_state.update(input[:subtree_len]);
|
||||
char[OUT_SIZE] cv;
|
||||
Blake3Output o = chunk_state.output();
|
||||
o.chaining_value(&cv);
|
||||
self.push_cv(&cv, chunk_state.chunk_counter);
|
||||
}
|
||||
else
|
||||
{
|
||||
char[2 * OUT_SIZE] cv_pair;
|
||||
compress_subtree_to_parent_node(input[:subtree_len], self.key[..], self.chunk.chunk_counter, self.chunk.flags, cv_pair[..], use_tbb);
|
||||
self.push_cv(&cv_pair[0], self.chunk.chunk_counter);
|
||||
self.push_cv(&cv_pair[OUT_SIZE], self.chunk.chunk_counter + (subtree_chunks / 2));
|
||||
}
|
||||
self.chunk.chunk_counter += subtree_chunks;
|
||||
input = input[subtree_len..];
|
||||
}
|
||||
|
||||
if (input.len > 0)
|
||||
{
|
||||
self.chunk.update(input);
|
||||
self.merge_cv_stack(self.chunk.chunk_counter);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Yield the results of the hash into a specified output buffer, at the specified length.
|
||||
Note that the `into` slice does not need to be properly cut to receive hash results; it
|
||||
just needs to be wide enough to accommodate `into_len` yielded bytes from the XOF.
|
||||
|
||||
@param [in] into : "The storage buffer for the output hash value. Must be >= `into_len` bytes."
|
||||
@param into_len : "How many bytes to receive from the XOF/hash output."
|
||||
@param seek : "How far into the XOF's yield to begin the stored byte sequence."
|
||||
|
||||
@require into.len >= into_len : "The requested output size must be equal to or less than the size of the output slice."
|
||||
*>
|
||||
fn void Blake3.final(&self, char[] into, usz into_len, usz seek = 0)
|
||||
{
|
||||
if (!into_len) return;
|
||||
|
||||
if (!self.cv_stack_len)
|
||||
{
|
||||
Blake3Output o = self.chunk.output();
|
||||
o.root_bytes(seek, into[:into_len]);
|
||||
return;
|
||||
}
|
||||
|
||||
Blake3Output o @noinit;
|
||||
usz cvs_remaining;
|
||||
if (self.chunk.len() > 0)
|
||||
{
|
||||
cvs_remaining = self.cv_stack_len;
|
||||
o = self.chunk.output();
|
||||
}
|
||||
else
|
||||
{
|
||||
cvs_remaining = (usz)self.cv_stack_len - 2;
|
||||
o = parent_output(&self.cv_stack[cvs_remaining * KEY_SIZE], self.key[..], self.chunk.flags);
|
||||
}
|
||||
|
||||
while (cvs_remaining > 0)
|
||||
{
|
||||
char[BLOCK_SIZE] parent_block;
|
||||
cvs_remaining--;
|
||||
parent_block[:32] = self.cv_stack[cvs_remaining * 32 : 32];
|
||||
o.chaining_value(&parent_block[32]);
|
||||
o = parent_output(&parent_block, self.key[..], self.chunk.flags);
|
||||
}
|
||||
|
||||
o.root_bytes(seek, into[:into_len]);
|
||||
}
|
||||
|
||||
<*
|
||||
Destroy a BLAKE3 hashing context.
|
||||
*>
|
||||
fn void Blake3.destroy(&self) @inline
|
||||
{
|
||||
mem::zero_volatile(@as_char_view(*self));
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Initialize a BLAKE3 chunk state.
|
||||
|
||||
@param [in] key
|
||||
@param flags
|
||||
*>
|
||||
fn void Blake3ChunkState.init(&self, uint[] key, char flags) @local @inline
|
||||
{
|
||||
mem::zero_volatile(@as_char_view(*self));
|
||||
self.cv[..] = key[..];
|
||||
self.flags = flags;
|
||||
}
|
||||
|
||||
<*
|
||||
Reset a BLAKE3 chunk state.
|
||||
|
||||
@param [in] key
|
||||
@param chunk_counter
|
||||
*>
|
||||
fn void Blake3ChunkState.reset(&self, uint[] key, ulong chunk_counter) @local @inline
|
||||
{
|
||||
self.init(key, self.flags); // maintain its own flags
|
||||
self.chunk_counter = chunk_counter; // update chunk counter
|
||||
}
|
||||
|
||||
<*
|
||||
Get bytes length of consumed data.
|
||||
*>
|
||||
fn usz Blake3ChunkState.len(&self) @operator(len) @local @inline
|
||||
=> (BLOCK_SIZE * (usz)self.blocks_compressed) + (usz)self.buf_len;
|
||||
|
||||
<*
|
||||
Ingest an amount of bytes into the chunk's buffer. NOTE: Doesn't check for underflow.
|
||||
|
||||
@param [in] data : "Data to ingest."
|
||||
*>
|
||||
fn usz Blake3ChunkState.fill_buf(&self, char[] data) @local @inline
|
||||
{
|
||||
usz take = min(BLOCK_SIZE - (usz)self.buf_len, data.len);
|
||||
self.buf[self.buf_len:take] = data[:take];
|
||||
self.buf_len += (char)take;
|
||||
return take;
|
||||
}
|
||||
|
||||
<*
|
||||
Determine whether to set the CHUNK_START flag.
|
||||
*>
|
||||
fn char Blake3ChunkState.maybe_start_flag(&self) @local @inline
|
||||
=> !self.blocks_compressed ? Blake3Flags.CHUNK_START : 0;
|
||||
|
||||
<*
|
||||
Update the chunk with the provided input bytes.
|
||||
|
||||
@param [in] input : "Incoming bytes to update with."
|
||||
*>
|
||||
fn void Blake3ChunkState.update(&self, char[] input) @local
|
||||
{
|
||||
if (self.buf_len)
|
||||
{
|
||||
usz take = self.fill_buf(input);
|
||||
input = input[take..];
|
||||
if (input.len)
|
||||
{
|
||||
compress_in_place(self.cv[..], self.buf[..], BLOCK_SIZE, self.chunk_counter, self.flags | self.maybe_start_flag());
|
||||
self.blocks_compressed++;
|
||||
self.buf_len = 0;
|
||||
self.buf[..] = {};
|
||||
}
|
||||
}
|
||||
for (; input.len > BLOCK_SIZE; self.blocks_compressed++, input = input[BLOCK_SIZE..])
|
||||
{
|
||||
compress_in_place(self.cv[..], input[:BLOCK_SIZE], BLOCK_SIZE, self.chunk_counter, self.flags | self.maybe_start_flag());
|
||||
}
|
||||
self.fill_buf(input);
|
||||
}
|
||||
|
||||
<*
|
||||
Convert the chunk state to an "output" type with the right flags.
|
||||
*>
|
||||
fn Blake3Output Blake3ChunkState.output(&self) @local @inline
|
||||
=> make_output(self.cv[..], &self.buf, self.buf_len, self.chunk_counter, self.flags | self.maybe_start_flag() | Blake3Flags.CHUNK_END);
|
||||
|
||||
<*
|
||||
Generate and initialize an output structure with the provided parameters.
|
||||
|
||||
@param [in] key
|
||||
@param [&in] in_block
|
||||
@param block_len
|
||||
@param counter
|
||||
@param flags
|
||||
*>
|
||||
fn Blake3Output make_output(uint[] key, char* in_block, usz block_len, ulong counter, char flags) @local @noinline
|
||||
{
|
||||
Blake3Output o;
|
||||
o.input_cv[..] = key[..];
|
||||
o.block[..] = in_block[:BLOCK_SIZE];
|
||||
o.block_len = (char)block_len;
|
||||
o.counter = counter;
|
||||
o.flags = flags;
|
||||
return o;
|
||||
}
|
||||
|
||||
<*
|
||||
Auto-generate a parent output structure, pre-initialized with some constant identifiers.
|
||||
|
||||
@param [&in] block
|
||||
@param [in] key
|
||||
@param flags
|
||||
*>
|
||||
macro Blake3Output parent_output(char* block, uint[] key, char flags) @local
|
||||
=> make_output(key, block, BLOCK_SIZE, 0, flags | Blake3Flags.PARENT);
|
||||
|
||||
<*
|
||||
Compress then store the chaining value of the output structure.
|
||||
|
||||
@param [&inout] cv
|
||||
*>
|
||||
macro void Blake3Output.chaining_value(&self, char* cv) @local
|
||||
{
|
||||
uint[KEY_SIZE_WORDS] cv_words;
|
||||
cv_words[..] = self.input_cv[..];
|
||||
compress_in_place(cv_words[..], self.block, self.block_len, self.counter, self.flags);
|
||||
cv[:KEY_SIZE] = @as_char_view(cv_words)[:KEY_SIZE];
|
||||
}
|
||||
|
||||
<*
|
||||
Store the result of the output into the designated slice.
|
||||
|
||||
@param seek
|
||||
@param [inout] into
|
||||
*>
|
||||
fn void Blake3Output.root_bytes(&self, usz seek, char[] into) @local
|
||||
{
|
||||
if (!into.len) return;
|
||||
|
||||
ulong output_block_counter = seek / BLOCK_SIZE;
|
||||
usz offset_within_block = seek % BLOCK_SIZE;
|
||||
char[BLOCK_SIZE] wide_buf;
|
||||
|
||||
if (offset_within_block)
|
||||
{
|
||||
compress_xof(self.input_cv[..], self.block, self.block_len, output_block_counter, self.flags | Blake3Flags.ROOT, wide_buf[..]);
|
||||
usz avail = BLOCK_SIZE - offset_within_block;
|
||||
usz bytes = min(into.len, avail);
|
||||
into[:bytes] = wide_buf[offset_within_block:bytes];
|
||||
into = into[bytes..];
|
||||
output_block_counter++;
|
||||
}
|
||||
if (into.len / BLOCK_SIZE)
|
||||
{
|
||||
@xof_many(self.input_cv[..], self.block, self.block_len, output_block_counter, self.flags | Blake3Flags.ROOT, into, into.len / BLOCK_SIZE);
|
||||
}
|
||||
output_block_counter += into.len / 64;
|
||||
into = into[(usz)(into.len & -64ll) ..];
|
||||
if (into.len)
|
||||
{
|
||||
compress_xof(self.input_cv[..], self.block, self.block_len, output_block_counter, self.flags | Blake3Flags.ROOT, wide_buf[..]);
|
||||
into[..] = wide_buf[:into.len];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =================================================================================================
|
||||
// =================================================================================================
|
||||
// =================================================================================================
|
||||
// WELCOME TO THE COMPUTATION GARDEN...
|
||||
//
|
||||
// You wanna understand BLAKE3? You gotta get through us.
|
||||
// ______________________________
|
||||
// ༼ ºل͟º ༼ ºل͟º ༼ ºل͟º ༽ ºل͟º ༽ ºل͟º ༽
|
||||
//
|
||||
//
|
||||
macro uint @popcnt(#x) @local => (uint)#x.popcount();
|
||||
macro uint @highest_one(#x) @local => 63 ^ (uint)#x.clz();
|
||||
macro usz @round_down_to_power_of_2(#x) @local => (usz)1 << @highest_one(#x | 1);
|
||||
|
||||
macro left_subtree_len(usz input_len) @local
|
||||
=> @round_down_to_power_of_2((input_len - 1) / CHUNK_SIZE) * CHUNK_SIZE;
|
||||
|
||||
|
||||
macro @g(#state, a, b, c, d, x, y) @local
|
||||
{
|
||||
#state[a] += #state[b] + x;
|
||||
#state[d] = (#state[d] ^ #state[a]).rotr(16);
|
||||
#state[c] += #state[d];
|
||||
#state[b] = (#state[b] ^ #state[c]).rotr(12);
|
||||
#state[a] += #state[b] + y;
|
||||
#state[d] = (#state[d] ^ #state[a]).rotr(8);
|
||||
#state[c] += #state[d];
|
||||
#state[b] = (#state[b] ^ #state[c]).rotr(7);
|
||||
}
|
||||
|
||||
macro @round(uint[] state, uint* msg, usz round) @local
|
||||
{
|
||||
char* schedule = &MESSAGE_SCHEDULE[round];
|
||||
@g(state, 0, 4, 8, 12, msg[schedule[0] ], msg[schedule[1] ]);
|
||||
@g(state, 1, 5, 9, 13, msg[schedule[2] ], msg[schedule[3] ]);
|
||||
@g(state, 2, 6, 10, 14, msg[schedule[4] ], msg[schedule[5] ]);
|
||||
@g(state, 3, 7, 11, 15, msg[schedule[6] ], msg[schedule[7] ]);
|
||||
@g(state, 0, 5, 10, 15, msg[schedule[8] ], msg[schedule[9] ]);
|
||||
@g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]);
|
||||
@g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]);
|
||||
@g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]);
|
||||
}
|
||||
|
||||
fn void compress_pre(uint[] state, uint[] cv, char[BLOCK_SIZE] block, usz block_len, ulong counter, char flags) @local @noinline
|
||||
{
|
||||
uint[16] block_words @noinit;
|
||||
foreach (i, &b : block_words) *b = mem::load((uint*)&block[i * 4], 1);
|
||||
state[0:8] = cv[0:8];
|
||||
state[8:4] = IV[0:4];
|
||||
state[12] = (uint)counter;
|
||||
state[13] = (uint)(counter >> 32);
|
||||
state[14] = (uint)block_len;
|
||||
state[15] = (uint)flags;
|
||||
@round(state, &block_words[0], 0);
|
||||
@round(state, &block_words[0], 1);
|
||||
@round(state, &block_words[0], 2);
|
||||
@round(state, &block_words[0], 3);
|
||||
@round(state, &block_words[0], 4);
|
||||
@round(state, &block_words[0], 5);
|
||||
@round(state, &block_words[0], 6);
|
||||
}
|
||||
|
||||
macro compress_in_place(uint[] cv, char[BLOCK_SIZE] block, usz block_len, ulong counter, char flags) @local
|
||||
{
|
||||
uint[16] state @noinit;
|
||||
compress_pre(state[..], cv, block, block_len, counter, flags);
|
||||
for (usz i = 0; i < 8; i++) cv[i] = state[i] ^ state[i + 8];
|
||||
}
|
||||
|
||||
macro compress_xof(uint[] cv, char[BLOCK_SIZE] block, usz block_len, ulong counter, char flags, char[] out) @local
|
||||
{
|
||||
uint[16] state @noinit;
|
||||
compress_pre(state[..], cv, block, block_len, counter, flags);
|
||||
$for usz $i = 0; $i < 8; $i++: mem::store((uint*)&out[4 * $i], state[$i] ^ state[$i + 8], 1); $endfor
|
||||
$for usz $i = 0; $i < 8; $i++: mem::store((uint*)&out[4 * (8 + $i)], state[$i + 8] ^ cv[$i], 1); $endfor
|
||||
}
|
||||
|
||||
macro @xof_many(uint[] cv, char[BLOCK_SIZE] block, usz block_len, ulong counter, char flags, char[] out, usz out_blocks) @local
|
||||
{
|
||||
for (usz i = 0; i < out_blocks; i++, out = out[BLOCK_SIZE..]) compress_xof(cv, block, block_len, counter + i, flags, out);
|
||||
}
|
||||
|
||||
macro hash_one(char* input, usz blocks, uint[] key, ulong counter, char flags, char flags_start, char flags_end, char[] out) @local
|
||||
{
|
||||
uint[8] cv;
|
||||
cv[..] = key[..];
|
||||
char block_flags = flags | flags_start;
|
||||
for (; blocks > 0; input += BLOCK_SIZE, blocks--, block_flags = flags)
|
||||
{
|
||||
if (blocks == 1) block_flags |= flags_end;
|
||||
compress_in_place(cv[..], input[:BLOCK_SIZE], BLOCK_SIZE, counter, block_flags);
|
||||
}
|
||||
foreach (i, c : cv) mem::store((uint*)&out[i * 4], c, 1);
|
||||
}
|
||||
|
||||
macro hash_many(char*[] inputs, usz num_inputs, usz blocks, uint[] key, ulong counter, bool $increment_counter, char flags, char flags_start, char flags_end, char* out) @local
|
||||
{
|
||||
for (; num_inputs > 0; num_inputs--, inputs = inputs[1..], out += OUT_SIZE)
|
||||
{
|
||||
hash_one(inputs[0], blocks, key, counter, flags, flags_start, flags_end, out[:OUT_SIZE]);
|
||||
$if $increment_counter: counter++; $endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn void compress_subtree_to_parent_node(char[] input, uint[] key, ulong chunk_counter, char flags, char[] out, bool use_tbb) @local @noinline
|
||||
{
|
||||
char[MAX_SIMD_DEGREE_OR_2 * OUT_SIZE] cv_array;
|
||||
|
||||
usz num_cvs = compress_subtree_wide(input, key, chunk_counter, flags, cv_array[..], use_tbb);
|
||||
assert(num_cvs <= 2);
|
||||
|
||||
$if MAX_SIMD_DEGREE_OR_2 > 2:
|
||||
char[MAX_SIMD_DEGREE_OR_2 * OUT_SIZE / 2] out_array;
|
||||
while (num_cvs > 2) num_cvs = compress_parents_parallel(cv_array[..], num_cvs, key, flags, &out_array);
|
||||
$endif
|
||||
|
||||
out[..] = cv_array[:2 * OUT_SIZE];
|
||||
}
|
||||
|
||||
fn usz compress_subtree_wide(char[] input, uint[] key, ulong chunk_counter, char flags, char* out, bool use_tbb) @local @noinline
|
||||
{
|
||||
if (input.len <= @simd_degree() * CHUNK_SIZE) return compress_chunks_parallel(input, key, chunk_counter, flags, out);
|
||||
|
||||
usz left_input_len = left_subtree_len(input.len);
|
||||
usz right_input_len = input.len - left_input_len;
|
||||
char* right_input = &input[left_input_len];
|
||||
ulong right_chunk_counter = chunk_counter + (ulong)(left_input_len / CHUNK_SIZE);
|
||||
|
||||
char[2 * MAX_SIMD_DEGREE_OR_2 * OUT_SIZE] cv_array;
|
||||
usz degree = @simd_degree();
|
||||
if (left_input_len > CHUNK_SIZE && degree == 1) degree = 2;
|
||||
char* right_cvs = &cv_array[degree * OUT_SIZE];
|
||||
|
||||
usz left_n = compress_subtree_wide(input[:left_input_len], key, chunk_counter, flags, &cv_array, use_tbb);
|
||||
usz right_n = compress_subtree_wide(right_input[:right_input_len], key, right_chunk_counter, flags, right_cvs, use_tbb);
|
||||
|
||||
if (left_n == 1)
|
||||
{
|
||||
out[:2 * OUT_SIZE] = cv_array[:2 * OUT_SIZE];
|
||||
return 2;
|
||||
}
|
||||
|
||||
return compress_parents_parallel(cv_array[..], left_n + right_n, key, flags, out);
|
||||
}
|
||||
|
||||
fn usz compress_parents_parallel(char[] child_chaining_values, usz num_chaining_values, uint[] key, char flags, char* out) @local @noinline
|
||||
{
|
||||
char*[MAX_SIMD_DEGREE_OR_2] parents_array;
|
||||
usz parents_array_len = 0;
|
||||
|
||||
while (num_chaining_values - (2 * parents_array_len) >= 2)
|
||||
{
|
||||
parents_array[parents_array_len++] = &child_chaining_values[2 * parents_array_len * OUT_SIZE];
|
||||
}
|
||||
|
||||
hash_many(parents_array[:parents_array_len], parents_array_len, 1, key, 0, false, flags | Blake3Flags.PARENT, 0, 0, out);
|
||||
|
||||
if (num_chaining_values > 2 * parents_array_len)
|
||||
{
|
||||
out[parents_array_len * OUT_SIZE : OUT_SIZE] = child_chaining_values[2 * parents_array_len * OUT_SIZE : OUT_SIZE];
|
||||
return parents_array_len + 1;
|
||||
}
|
||||
|
||||
return parents_array_len;
|
||||
}
|
||||
|
||||
fn usz compress_chunks_parallel(char[] input, uint[] key, ulong chunk_counter, char flags, char* out) @local @noinline
|
||||
{
|
||||
char*[MAX_SIMD_DEGREE] chunks_array;
|
||||
usz input_position = 0;
|
||||
usz chunks_array_len = 0;
|
||||
|
||||
for (; input.len - input_position >= CHUNK_SIZE; input_position += CHUNK_SIZE)
|
||||
{
|
||||
chunks_array[chunks_array_len++] = &input[input_position];
|
||||
}
|
||||
|
||||
hash_many(chunks_array[:chunks_array_len], chunks_array_len, CHUNK_SIZE / BLOCK_SIZE, key, chunk_counter, true, flags, Blake3Flags.CHUNK_START, Blake3Flags.CHUNK_END, out);
|
||||
|
||||
if (input.len <= input_position) return chunks_array_len;
|
||||
|
||||
ulong counter = chunk_counter + (ulong)chunks_array_len;
|
||||
Blake3ChunkState chunk_state;
|
||||
chunk_state.init(key, flags);
|
||||
chunk_state.chunk_counter = counter;
|
||||
chunk_state.update(input[input_position : input.len - input_position]);
|
||||
Blake3Output o = chunk_state.output();
|
||||
o.chaining_value(&out[chunks_array_len * OUT_SIZE]);
|
||||
|
||||
return chunks_array_len + 1;
|
||||
}
|
||||
169
lib/std/hash/gost/streebog.c3
Normal file
169
lib/std/hash/gost/streebog.c3
Normal file
@@ -0,0 +1,169 @@
|
||||
// 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 Streebog-256 and Streebog-512, defined in the
|
||||
// Russian standard GOST R 34.11-2012. Also known as "GOST-12".
|
||||
//
|
||||
module std::hash::streebog;
|
||||
|
||||
|
||||
enum StreebogLength : const inline uint
|
||||
{
|
||||
SIZE_256 = 32,
|
||||
SIZE_512 = 64,
|
||||
}
|
||||
|
||||
struct Streebog
|
||||
{
|
||||
ulong[8] h;
|
||||
ulong[8] n;
|
||||
ulong[8] s;
|
||||
ulong[8] message;
|
||||
usz index;
|
||||
usz hash_size;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require $defined(data[0]) &&& $typeof(data[0]) == char : "Input data must be a char slice, char array, or string value."
|
||||
*>
|
||||
macro char[*] hash(StreebogLength $hash_size, data)
|
||||
{
|
||||
// TODO: The speed of this hash hinges on use of >>> SIMD instructions <<<. It would be nice to have some added.
|
||||
Streebog s @noinit;
|
||||
s.init($hash_size);
|
||||
s.update(data);
|
||||
return s.final($hash_size);
|
||||
}
|
||||
|
||||
macro char[*] hash_256(char[] data) => hash(SIZE_256, data);
|
||||
macro char[*] hash_512(char[] data) => hash(SIZE_512, data);
|
||||
|
||||
|
||||
macro @xor_512(#x, #y, #z) @local
|
||||
{
|
||||
$for var $i = 0; $i < 8; $i++:
|
||||
#z[$i] = #x[$i] ^ #y[$i];
|
||||
$endfor
|
||||
}
|
||||
|
||||
macro @add_512(#sum, #x) @local
|
||||
{
|
||||
ulong carry = 0;
|
||||
$for usz $i = 0; $i < 8; $i++:
|
||||
#sum[$i] += #x[$i] + carry;
|
||||
carry = #sum[$i] < #x[$i] ? 1 : (#sum[$i] == #x[$i] ? carry : 0);
|
||||
$endfor
|
||||
}
|
||||
|
||||
macro @lpsx(#a, #b, #result) @local
|
||||
{
|
||||
ulong[8] z @noinit;
|
||||
@xor_512(#a, #b, z);
|
||||
// Do not unroll these loops - produces far too much code at compile-time.
|
||||
for (usz i = 0; i < 8; i++)
|
||||
{
|
||||
#result[i] = TR[0][(z[0] >> (i << 3)) & 0xff];
|
||||
for (usz j = 1; j < 8; j++) #result[i] ^= TR[j][(z[j] >> (i << 3)) & 0xff];
|
||||
}
|
||||
}
|
||||
|
||||
macro @g_n(#n, #h, #m) @local
|
||||
{
|
||||
ulong[8] k_i;
|
||||
ulong[8] state;
|
||||
|
||||
@lpsx(#h, #n, k_i);
|
||||
@lpsx(k_i, #m, state);
|
||||
// Do not unroll this loop - produces far too much code at compile-time.
|
||||
for (usz i = 0; i < 11; i++)
|
||||
{
|
||||
@lpsx(k_i, ITERATION_CONSTANTS[i], k_i);
|
||||
@lpsx(k_i, state, state);
|
||||
}
|
||||
@lpsx(k_i, ITERATION_CONSTANTS[11], k_i);
|
||||
@xor_512(k_i, state, state);
|
||||
@xor_512(state, #h, state);
|
||||
@xor_512(state, #m, #h);
|
||||
}
|
||||
|
||||
|
||||
macro Streebog.@stage2(&self, #m) @local
|
||||
{
|
||||
@g_n(self.n, self.h, #m);
|
||||
@add_512(self.n, STAGE2_512);
|
||||
@add_512(self.s, #m);
|
||||
}
|
||||
|
||||
macro void Streebog.init(&self, StreebogLength $hash_size)
|
||||
{
|
||||
mem::zero_volatile(@as_char_view(*self)); // explicitly initialize the entire state to 0
|
||||
self.hash_size = $hash_size;
|
||||
$if $hash_size != SIZE_512:
|
||||
@as_char_view(self.h)[..] = (char[*]){ [0..63] = 0x01 }[..];
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void Streebog.update(&self, char[] data)
|
||||
{
|
||||
if (self.index)
|
||||
{
|
||||
usz rest = BLOCK_SIZE - self.index;
|
||||
usz size = data.len;
|
||||
usz len = size < rest ? size : rest;
|
||||
|
||||
@as_char_view(self.message)[self.index:len] = data[:len];
|
||||
self.index += size;
|
||||
if (size < rest) return;
|
||||
|
||||
self.@stage2(self.message);
|
||||
data = data[rest..];
|
||||
self.index = 0;
|
||||
}
|
||||
bool aligned = 0 == (usz)data.ptr % ulong.sizeof;
|
||||
for (; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..])
|
||||
{
|
||||
if (aligned)
|
||||
{
|
||||
self.@stage2((ulong*)data.ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
@as_char_view(self.message)[:BLOCK_SIZE] = data[:BLOCK_SIZE];
|
||||
self.@stage2(self.message);
|
||||
}
|
||||
}
|
||||
if (data.len)
|
||||
{
|
||||
self.index = data.len;
|
||||
@as_char_view(self.message)[:data.len] = data[..];
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require $hash_size == self.hash_size : "You must use the same output hash size as was initialized with the context."
|
||||
*>
|
||||
macro char[*] Streebog.final(&self, StreebogLength $hash_size)
|
||||
{
|
||||
char[$hash_size] result;
|
||||
ulong[8] unprocessed_bits_count;
|
||||
usz index = self.index >> 3;
|
||||
usz shift = (self.index & 0b111) * 8;
|
||||
|
||||
unprocessed_bits_count[0] = self.index * 8;
|
||||
self.message[index] &= ~(ulong.max << shift);
|
||||
self.message[index++] ^= 1ul << shift;
|
||||
if (index < 8) self.message[index..] = {};
|
||||
|
||||
@g_n(self.n, self.h, self.message);
|
||||
@add_512(self.n, unprocessed_bits_count);
|
||||
@add_512(self.s, self.message);
|
||||
@g_n(ZERO_512, self.h, self.n);
|
||||
@g_n(ZERO_512, self.h, self.s);
|
||||
|
||||
defer mem::zero_volatile(@as_char_view(*self)); // implicitly clear the structure when finalized
|
||||
|
||||
result[..] = @as_char_view(self.h)[(BLOCK_SIZE - $hash_size)..];
|
||||
return result;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user