Repository navigation
Expand file tree
/
Copy pathmodule.ae
More file actions
306 lines (286 loc) · 12.3 KB
/
Copy pathmodule.ae
File metadata and controls
306 lines (286 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
// std.hash — fast NON-cryptographic hashes for checksums and hashmap
// seeding.
//
// fnv32 / fnv64 — FNV-1a (Fowler–Noll–Vo), simple and fast for short keys
// murmur3_32 — MurmurHash3 x86 32-bit, good distribution for hashmaps
// siphash24 — SipHash-2-4, a KEYED PRF: DoS-resistant hashmap seeding
// and short-message MACs. Not a general crypto hash, but
// unlike the others it is keyed and hash-flood resistant.
// crc32 — CRC-32 (IEEE 802.3, the reflected 0xEDB88320
// polynomial): the checksum PNG chunks, gzip members and
// ZIP entries carry. crc32_update continues one across
// several pieces of input.
//
// Input is a `byte[]` slice whose length travels with it (#2301): pass a
// string as `string.bytes(s)`, a prefix as `string.bytes(s)[0..n]`, a
// `std.bytes` buffer as `bytes.view(buf)`. Embedded NULs are honoured.
// Results are the exact 32-/64-bit unsigned values the reference algorithms
// produce, carried in Aether's signed `int`/`long` (mask with 0xFFFFFFFF /
// interpret bit-for-bit as unsigned). fnv/murmur are ported from C3's
// `std::hash::*` (MIT, Copyright (c) 2022-2026 Christoffer Lernö and
// contributors); siphash from C3's std::hash::siphash (MIT, Copyright (c)
// 2025 Zack Puhl), both re-expressed in Aether.
import std.bits
// A slice with no bound (`p as byte[]`, `.len` -1) has no length to hash.
fn unbounded(fname: string) {
panic("hash.${fname}: the slice has no bound (.len -1); give it one, as `(p as byte[])[0..n]`")
}
exports(
fnv32, fnv64, murmur3_32, siphash24, crc32, crc32_update
)
// FNV-1a 32-bit. offset basis 0x811c9dc5, prime 0x01000193. Returned as a
// non-negative 32-bit value in a `long` (masked each step so the multiply
// wraps like unsigned uint32 regardless of Aether's signed int width).
fnv32(data: byte[]) -> long {
if data.len < 0 {
unbounded("fnv32")
}
int length = data.len
let mask: long = 0xFFFFFFFF
let h: long = 0x811c9dc5
i = 0
while i < length {
b = (data[i] & 255) as long
h = (h ^ b) & mask
h = (h * 0x01000193) & mask
i = i + 1
}
return h
}
// FNV-1a 64-bit. offset basis 0xcbf29ce484222325, prime 0x100000001b3.
// Uses wrapping 64-bit multiply so the product wraps like unsigned uint64.
fnv64(data: byte[]) -> long {
if data.len < 0 {
unbounded("fnv64")
}
int length = data.len
h = 0xcbf29ce484222325
prime = 0x100000001b3
i = 0
while i < length {
b = (data[i] & 255) as long
h = h ^ b
h = bits.wrapping_mul64(h, prime)
i = i + 1
}
return h
}
// MurmurHash3 x86 32-bit. `seed` lets callers salt the hash (0 for a plain
// digest). Processes 4-byte little-endian blocks then the 1..3-byte tail,
// with the standard fmix32 finalizer.
// Implemented in 64-bit `long` throughout with an explicit 32-bit mask after
// every step, so the arithmetic matches unsigned uint32 exactly regardless
// of Aether's signed-int width, and the final value is a non-negative 32-bit
// number. Rotates use the 32-bit rotate on the masked value.
murmur3_32(data: byte[], seed: int) -> long {
if data.len < 0 {
unbounded("murmur3_32")
}
int length = data.len
let c1: long = 0xcc9e2d51
let c2: long = 0x1b873593
let mask: long = 0xFFFFFFFF
let h1: long = (seed as long) & mask
nblocks = length / 4
i = 0
while i < nblocks {
base = i * 4
b0 = (data[base] & 255) as long
b1 = (data[base + 1] & 255) as long
b2 = (data[base + 2] & 255) as long
b3 = (data[base + 3] & 255) as long
let k1: long = (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) & mask
k1 = (k1 * c1) & mask
k1 = rotl32m(k1, 15)
k1 = (k1 * c2) & mask
h1 = (h1 ^ k1) & mask
h1 = rotl32m(h1, 13)
h1 = ((h1 * 5) + 0xe6546b64) & mask
i = i + 1
}
// tail: remaining 1..3 bytes
tail = nblocks * 4
rem = length - tail
let k1t: long = 0
if rem == 3 { k1t = k1t ^ (((data[tail + 2] & 255) as long) << 16) }
if rem >= 2 { k1t = k1t ^ (((data[tail + 1] & 255) as long) << 8) }
if rem >= 1 {
k1t = k1t ^ ((data[tail] & 255) as long)
k1t = (k1t * c1) & mask
k1t = rotl32m(k1t, 15)
k1t = (k1t * c2) & mask
h1 = (h1 ^ k1t) & mask
}
// finalization
h1 = (h1 ^ (length as long)) & mask
h1 = fmix32(h1)
return h1
}
// 32-bit left rotate of a value already masked to 32 bits, in `long`.
fn rotl32m(x: long, r: int) -> long {
let mask: long = 0xFFFFFFFF
return ((x << r) | (x >> (32 - r))) & mask
}
// MurmurHash3 32-bit finalizer (in `long`, masked).
fn fmix32(h0: long) -> long {
let mask: long = 0xFFFFFFFF
h = h0 & mask
h = (h ^ (h >> 16)) & mask
h = (h * 0x85ebca6b) & mask
h = (h ^ (h >> 13)) & mask
h = (h * 0xc2b2ae35) & mask
h = (h ^ (h >> 16)) & mask
return h
}
// ---------------------------------------------------------------------------
// SipHash-2-4 (64-bit output). A KEYED pseudorandom function — the standard
// choice for hash-flood-resistant hashmaps and short-message MACs. Ported
// from C3's std::hash::siphash (MIT, © 2025 Zack Puhl), following the
// reference SipHash construction (Aumasson & Bernstein).
//
// All arithmetic is 64-bit with modular (wrapping) addition — bits.
// wrapping_add64 — and 64-bit left rotates (bits.rotl64). The 128-bit key is
// supplied as two `long` halves (k0 = low 64 bits, k1 = high 64 bits); the
// message is a `byte[]` slice. The returned `long` is the
// 64-bit digest interpreted bit-for-bit (may be negative when the top bit is
// set). c = 2 compression rounds per block, d = 4 finalization rounds.
// ---------------------------------------------------------------------------
// One SipRound over the 4-word state; returns the updated (v0,v1,v2,v3).
fn sipround(v0: long, v1: long, v2: long, v3: long) -> (long, long, long, long) {
a0 = bits.wrapping_add64(v0, v1)
b1 = bits.rotl64(v1, 13) ^ a0
a0b = bits.rotl64(a0, 32)
a2 = bits.wrapping_add64(v2, v3)
b3 = bits.rotl64(v3, 16) ^ a2
a0c = bits.wrapping_add64(a0b, b3)
b3b = bits.rotl64(b3, 21) ^ a0c
a2b = bits.wrapping_add64(a2, b1)
b1b = bits.rotl64(b1, 17) ^ a2b
a2c = bits.rotl64(a2b, 32)
return a0c, b1b, a2c, b3b
}
// Read up to 8 little-endian bytes starting at `off` into a 64-bit word
// (missing bytes are 0). `cnt` bytes are taken.
fn le64(data: byte[], off: int, cnt: int) -> long {
let w: long = 0
i = 0
while i < cnt {
b = (data[off + i] & 255) as long
w = w | (b << (i * 8))
i = i + 1
}
return w
}
// SipHash-2-4 of the bytes of `data` under the 128-bit key (k0, k1).
siphash24(data: byte[], k0: long, k1: long) -> long {
if data.len < 0 {
unbounded("siphash24")
}
int length = data.len
let v0: long = 0x736f6d6570736575 ^ k0
let v1: long = 0x646f72616e646f6d ^ k1
let v2: long = 0x6c7967656e657261 ^ k0
let v3: long = 0x7465646279746573 ^ k1
// full 8-byte blocks
nblocks = length / 8
b = 0
while b < nblocks {
m = le64(data, b * 8, 8)
v3 = v3 ^ m
v0, v1, v2, v3 = sipround(v0, v1, v2, v3) // c = 2
v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
v0 = v0 ^ m
b = b + 1
}
// final block: remaining bytes + the length in the top byte
tail = nblocks * 8
rem = length - tail
mlast = le64(data, tail, rem)
mlast = mlast | ((length as long) << 56)
v3 = v3 ^ mlast
v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
v0 = v0 ^ mlast
// finalization: d = 4 rounds
v2 = v2 ^ 0xff
v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
return v0 ^ v1 ^ v2 ^ v3
}
// ---- CRC-32 (IEEE 802.3) ----------------------------------------------------
// The reflected table: the CRC of each byte value under the polynomial
// 0xEDB88320, so the checksum costs one lookup per input byte. A constant,
// so it is read-only data shared by every thread, with nothing built at run
// time for two first callers to race on.
const CRC32_TABLE: uint32[256] = [
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
]
// Continues a CRC-32 over the bytes of `data`: pass the value a
// previous crc32 / crc32_update returned (0 to start), and the result is the
// CRC of everything so far, as crc32 of the whole input would be. Returned as
// the unsigned 32-bit value in a `long`.
crc32_update(crc: long, data: byte[]) -> long {
if data.len < 0 {
unbounded("crc32_update")
}
int length = data.len
let mask: long = 0xFFFFFFFF
let c: long = (crc ^ mask) & mask
i = 0
while i < length {
b = (data[i] & 255) as long
idx = ((c ^ b) & 255) as int
entry = (CRC32_TABLE[idx] as long) & mask
c = (entry ^ (c >> 8)) & mask
i = i + 1
}
return (c ^ mask) & mask
}
// CRC-32 of the bytes of `data`.
crc32(data: byte[]) -> long {
return crc32_update(0, data)
}