-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathmodule.ae
More file actions
335 lines (307 loc) · 17.9 KB
/
Copy pathmodule.ae
File metadata and controls
335 lines (307 loc) · 17.9 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
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
// std.mem — byte-level access to caller-allocated raw pointers.
//
// Companion to std.bytes:
// std.bytes — Aether-allocated mutable buffer with length/capacity
// tracking. Use for "I want to build up some bytes."
// std.mem — raw caller-allocated pointer access. Use for "I have
// a void* / char* from elsewhere and need to read or
// write bytes at offsets."
//
// Module name: `std.mem` rather than `std.ptr` because `ptr` is a
// reserved keyword in Aether (the raw-pointer type). `mem` reads
// cleanly as "raw memory access."
//
// The split exists because Aether parses `p[i]` syntactically but
// lowers it to `*(void*)p` which the C compiler rejects.
// mem.get_byte / set_byte route the same intent through a same-
// effect extern call. No bounds checking — the caller owns the
// buffer and knows its size. NULL pointer is defended against;
// out-of-range index is the caller's problem (same as POSIX
// read/write).
//
// First consumer: the in-tree mquickjs port (ports a JS engine
// from C to Aether; every UTF-8 decoder, bytecode interpreter,
// packed-format parser fundamentally does `((uint8_t*)p)[i]`).
//
// Sized typed-array accessors (int8/uint8/int16/uint16/uint32/
// float32/float64) shipped after mquickjs's TypedArray load/store
// paths surfaced the need. Future additions (pointer arithmetic
// helpers, little-endian variants) ship when concrete uses surface.
exports(
aether_mem_get_byte, aether_mem_set_byte,
aether_mem_get_byte_sz, aether_mem_set_byte_sz,
aether_mem_get_ptr, aether_mem_set_ptr,
aether_mem_get_int, aether_mem_set_int,
aether_mem_get_long, aether_mem_set_long,
aether_mem_get_int8, aether_mem_set_int8,
aether_mem_get_uint8, aether_mem_set_uint8,
aether_mem_get_int16, aether_mem_set_int16,
aether_mem_get_uint16, aether_mem_set_uint16,
aether_mem_get_uint32, aether_mem_set_uint32,
aether_mem_get_float32, aether_mem_set_float32,
aether_mem_get_float64, aether_mem_set_float64,
aether_mem_bits_of_float, aether_mem_float_from_bits,
aether_mem_clz32, aether_mem_clz64,
aether_mem_udiv64_32,
aether_mem_call_fn3_int, aether_mem_call_fn3_void,
aether_mem_copy, aether_mem_move, aether_mem_compare, aether_mem_set_bulk,
aether_mem_get_u16_le, aether_mem_get_u16_be,
aether_mem_set_u16_le, aether_mem_set_u16_be,
aether_mem_get_u32_le, aether_mem_get_u32_be,
aether_mem_set_u32_le, aether_mem_set_u32_be,
aether_mem_get_u64_le, aether_mem_get_u64_be,
aether_mem_set_u64_le, aether_mem_set_u64_be,
get_byte, set_byte, get_byte_sz, set_byte_sz, get_ptr, set_ptr,
get_int, set_int,
get_long, set_long,
get_int8, set_int8, get_uint8, set_uint8,
get_int16, set_int16, get_uint16, set_uint16,
get_uint32, set_uint32,
get_float32, set_float32, get_float64, set_float64,
bits_of_float, float_from_bits,
clz32, clz64,
udiv64_32,
call_fn3_int, call_fn3_void,
copy, move, compare, set,
get_u16_le, get_u16_be, set_u16_le, set_u16_be,
get_u32_le, get_u32_be, set_u32_le, set_u32_be,
get_u64_le, get_u64_be, set_u64_le, set_u64_be
)
// Read unsigned byte at offset `i` from raw pointer `p`. Returns
// 0..255 on success, -1 if `p` is null. Negative or out-of-range
// `i` is undefined behaviour (the caller owns the buffer and
// knows its size; same contract as POSIX read into a char*).
extern aether_mem_get_byte(p: ptr, i: int) -> int
// Write the low 8 bits of `value` at offset `i` of `p`. Returns
// 1 on success, 0 if `p` is null. As with get_byte, out-of-range
// `i` is undefined behaviour.
extern aether_mem_set_byte(p: ptr, i: int, value: int) -> int
// size_t-indexed companions. The int variants top out at 2 GiB;
// codebases whose natural index width is size_t (Redis SDS, lzf,
// siphash, syncio, large mmap walkers) need the wider form. Aether
// has no primitive narrowing via `as` so callers cannot convert
// size_t to int at the call site — that's the gap these fill.
// Same null defence; same out-of-range UB contract.
extern aether_mem_get_byte_sz(p: ptr, i: size_t) -> int
extern aether_mem_set_byte_sz(p: ptr, i: size_t, value: int) -> int
// Read a pointer-sized value at byte offset `offset` from `p`.
// Returns null if `p` is null OR the loaded value is null —
// callers needing to distinguish should null-check `p` themselves.
// `offset` must be pointer-aligned and the slot must be at least
// `sizeof(void*)` bytes.
//
// Use for the C-side `*(void**)slot` read pattern — e.g. when a
// C library hands back an out-parameter through a `T**` argument
// the Aether caller filled with a malloc-d slot.
extern aether_mem_get_ptr(p: ptr, offset: int) -> ptr
// Write a pointer `value` into the slot at byte offset `offset`
// of `p`. Returns 1 on success, 0 if `p` is null. Drives the
// `*(void**)out_pp = value` pattern for out-parameter writes
// across the FFI boundary.
extern aether_mem_set_ptr(p: ptr, offset: int, value: ptr) -> int
// Read int32 at byte offset `offset` of `p`. Returns 0 if `p` is
// null (conflated with stored 0). Slot must be 4-byte aligned.
// Driver case: C functions with `int *out` out-parameters
// (mquickjs's get_pc2line et al, write line/col deltas).
extern aether_mem_get_int(p: ptr, offset: int) -> int
// Write int32 `value` at byte offset `offset` of `p`. Returns 1
// on success, 0 if `p` is null.
extern aether_mem_set_int(p: ptr, offset: int, value: int) -> int
// Read int64-sized value at byte offset `offset` of `p`. Returns 0
// if `p` is null (conflated with "stored value is 0" — null-check
// `p` first if you need to distinguish). Slot must be 8-byte aligned.
//
// Driver case: C functions taking `size_t *out` parameters —
// mquickjs's __unicode_from_utf8 writes the consumed-byte count
// to a size_t* slot. On 64-bit Linux/macOS (mquickjs targets)
// size_t is 8 bytes, matching int64_t.
extern aether_mem_get_long(p: ptr, offset: int) -> long
// Write int64-sized `value` at byte offset `offset` of `p`. Returns
// 1 on success, 0 if `p` is null. Slot must be 8-byte aligned and
// at least 8 bytes.
extern aether_mem_set_long(p: ptr, offset: int, value: long) -> int
// Sized typed-array accessors. Reads/writes at a byte offset with
// a known C type. Driver case: porting C codebases (mquickjs's
// TypedArray load/store paths) that walk packed numeric buffers —
// every port that didn't have these added bespoke C helpers.
//
// `get_byte` already exists for unsigned-byte reads; `get_uint8` is
// the parallel name for type-driven code (so the caller doesn't have
// to mentally translate "byte" to "uint8"). `get_int8` is the
// sign-extending variant returning -128..127.
//
// 16- and 32-bit accesses use memcpy internally so the offset doesn't
// need to be naturally aligned (matters on strict-alignment ARM /
// MIPS; a no-op on x86/x64). Modern compilers fold the memcpy into a
// single load/store.
//
// Float reads widen to Aether `float` (= C `double`); float writes
// narrow on store with IEEE-754 round-to-nearest.
//
// NULL `p` returns 0 (or 0.0); same conflation caveat as get_int /
// get_long ("p is null" vs. "stored value is 0"). Out-of-range
// offset is the caller's problem.
extern aether_mem_get_int8(p: ptr, offset: int) -> int
extern aether_mem_set_int8(p: ptr, offset: int, value: int) -> int
extern aether_mem_get_uint8(p: ptr, offset: int) -> int
extern aether_mem_set_uint8(p: ptr, offset: int, value: int) -> int
extern aether_mem_get_int16(p: ptr, offset: int) -> int
extern aether_mem_set_int16(p: ptr, offset: int, value: int) -> int
extern aether_mem_get_uint16(p: ptr, offset: int) -> int
extern aether_mem_set_uint16(p: ptr, offset: int, value: int) -> int
extern aether_mem_get_uint32(p: ptr, offset: int) -> int
extern aether_mem_set_uint32(p: ptr, offset: int, value: int) -> int
extern aether_mem_get_float32(p: ptr, offset: int) -> float
extern aether_mem_set_float32(p: ptr, offset: int, value: float) -> int
extern aether_mem_get_float64(p: ptr, offset: int) -> float
extern aether_mem_set_float64(p: ptr, offset: int, value: float) -> int
// Reinterpret an IEEE-754 double's bit pattern as a 64-bit integer.
// Bitwise type-pun (no value conversion) — preserves NaN payloads,
// signed-zero, infinities exactly. Aether `float` lowers to C
// `double` (binary64), so this is the standard 64-bit reinterpret.
//
// Driver case: porting C softfloat / IEEE-754 manipulation code
// (mquickjs libm.c) which uses `float64_as_uint64(d)` everywhere
// to access exponent/mantissa/sign bits.
extern aether_mem_bits_of_float(value: float) -> long
// Inverse of bits_of_float — reconstitute a double from its 64-bit
// bit pattern. Same byte-level reinterpret. Used for the
// `uint64_as_float64` direction in libm.c after softfloat ops.
extern aether_mem_float_from_bits(bits: long) -> float
// Count leading zeros in a 32-bit value. Wraps __builtin_clz.
// Undefined behavior if `value` is 0 — caller must guard.
// Driver case: bit-twiddling code in mquickjs's prop hash sizing,
// dtoa's pow_ui, regexp range count rounding-up to log2.
extern aether_mem_clz32(value: int) -> int
// Count leading zeros in a 64-bit value. Wraps __builtin_clzll.
// Same UB-on-zero contract as clz32.
extern aether_mem_clz64(value: long) -> int
// Unsigned 64-by-32-bit division. Returns the uint32 quotient
// and writes the uint32 remainder to *premainder.
//
// Aether's `long` is signed int64; `long / int` sign-extends
// the dividend, producing wrong results for values with bit 63
// set. This primitive does the division as true unsigned at the
// C boundary.
//
// Returns 0 if `premainder` is null or `divisor` is 0 (defensive
// — caller is expected to provide both). Quotient sign-extends
// to int return value but the bit pattern matches uint32 since
// the result fits in 32 bits.
//
// Driver: dtoa.c's mp_div1 needs uint64 / uint32 in its inner
// loop, where the dividend is `(remainder << 32) | next_limb`.
extern aether_mem_udiv64_32(dividend: long, divisor: int, premainder: ptr) -> int
// Invoke a C bare function pointer with a (size_t, size_t, void*)
// signature, int return. Aether's `fn` lowers to a closure struct
// (`{ fn, env }`) — incompatible with C's bare fnptr ABI. This
// shim bridges: Aether receives the C fnptr as `ptr` (raw address)
// and calls through this extern, which casts and invokes.
//
// Driver: porting C functions like rqsort_idx that take a
// comparator + swap callback from C.
extern aether_mem_call_fn3_int(fn: ptr, a: long, b: long, opaque: ptr) -> int
// Same shape, void return (e.g. swap callbacks).
extern aether_mem_call_fn3_void(fn: ptr, a: long, b: long, opaque: ptr)
// Two-arg fnptr shim, void return. Driver case: GC user-finalizer
// tables (`void (*)(JSContext*, void*)`) — both args cross the FFI
// as ptr.
extern aether_mem_call_fn2_void(fn: ptr, a: ptr, b: ptr)
// Reinterpret a raw pointer as a 64-bit integer address. Returns the
// machine address of `p`. Driver case: porting C tagged-pointer code
// that needs to do bit arithmetic on a pointer (e.g. test the low
// 3 bits to distinguish a JSValue's tag). Aether's `ptr` doesn't
// support `&` directly, so the tag test routes through `long`.
extern aether_mem_ptr_to_long(p: ptr) -> long
// Inverse of aether_mem_ptr_to_long. Construct a `ptr` from a 64-bit
// integer address. Used after stripping the tag bits from a tagged
// pointer that was loaded as a long; the un-tagged address gets
// converted back to ptr for indexed reads via mem.get_long /
// mem.get_ptr. Caller's responsibility to ensure the address is a
// valid pointer (same contract as POSIX read/write into a buffer).
extern aether_mem_long_to_ptr(addr: long) -> ptr
// Bulk operations — `mem.copy` / `mem.move` / `mem.compare` /
// `mem.set`. These map directly onto libc memcpy / memmove / memcmp
// / memset; length is a long (lowered to size_t on the C side).
// Added per docs/redis-porting-language-gaps.md §P2; the Redis port
// had to hand-roll byte loops or reach for libc shims for every
// bulk move/compare.
extern aether_mem_copy(dst: ptr, src: ptr, n: long) -> ptr
extern aether_mem_move(dst: ptr, src: ptr, n: long) -> ptr
extern aether_mem_compare(a: ptr, b: ptr, n: long) -> int
extern aether_mem_set_bulk(dst: ptr, value: int, n: long) -> ptr
// Endian-aware 16/32/64 load/store at a byte offset. Unaligned-safe
// (each access uses byte-by-byte composition). `_le` is little-
// endian (low byte first); `_be` is big-endian.
//
// 32-bit and 64-bit loads return `long` so the full unsigned range
// is representable — narrowing to Aether `int` truncates the same
// way C `(int32_t)u32` does.
extern aether_mem_get_u16_le(p: ptr, offset: int) -> int
extern aether_mem_get_u16_be(p: ptr, offset: int) -> int
extern aether_mem_set_u16_le(p: ptr, offset: int, value: int) -> int
extern aether_mem_set_u16_be(p: ptr, offset: int, value: int) -> int
extern aether_mem_get_u32_le(p: ptr, offset: int) -> long
extern aether_mem_get_u32_be(p: ptr, offset: int) -> long
extern aether_mem_set_u32_le(p: ptr, offset: int, value: long) -> int
extern aether_mem_set_u32_be(p: ptr, offset: int, value: long) -> int
extern aether_mem_get_u64_le(p: ptr, offset: int) -> long
extern aether_mem_get_u64_be(p: ptr, offset: int) -> long
extern aether_mem_set_u64_le(p: ptr, offset: int, value: long) -> int
extern aether_mem_set_u64_be(p: ptr, offset: int, value: long) -> int
// Aether-side wrappers — call shape consistent with the rest of
// std.* (`mem.get_byte(p, 0)`, `mem.set_byte(p, 0, 0xff)`).
get_byte(p: ptr, i: int) -> int { return aether_mem_get_byte(p, i) }
set_byte(p: ptr, i: int, value: int) -> int { return aether_mem_set_byte(p, i, value) }
get_byte_sz(p: ptr, i: size_t) -> int { return aether_mem_get_byte_sz(p, i) }
set_byte_sz(p: ptr, i: size_t, value: int) -> int { return aether_mem_set_byte_sz(p, i, value) }
get_ptr(p: ptr, offset: int) -> ptr { return aether_mem_get_ptr(p, offset) }
set_ptr(p: ptr, offset: int, value: ptr) -> int { return aether_mem_set_ptr(p, offset, value) }
get_int(p: ptr, offset: int) -> int { return aether_mem_get_int(p, offset) }
set_int(p: ptr, offset: int, value: int) -> int { return aether_mem_set_int(p, offset, value) }
get_long(p: ptr, offset: int) -> long { return aether_mem_get_long(p, offset) }
set_long(p: ptr, offset: int, value: long) -> int { return aether_mem_set_long(p, offset, value) }
get_int8(p: ptr, offset: int) -> int { return aether_mem_get_int8(p, offset) }
set_int8(p: ptr, offset: int, value: int) -> int { return aether_mem_set_int8(p, offset, value) }
get_uint8(p: ptr, offset: int) -> int { return aether_mem_get_uint8(p, offset) }
set_uint8(p: ptr, offset: int, value: int) -> int { return aether_mem_set_uint8(p, offset, value) }
get_int16(p: ptr, offset: int) -> int { return aether_mem_get_int16(p, offset) }
set_int16(p: ptr, offset: int, value: int) -> int { return aether_mem_set_int16(p, offset, value) }
get_uint16(p: ptr, offset: int) -> int { return aether_mem_get_uint16(p, offset) }
set_uint16(p: ptr, offset: int, value: int) -> int { return aether_mem_set_uint16(p, offset, value) }
get_uint32(p: ptr, offset: int) -> int { return aether_mem_get_uint32(p, offset) }
set_uint32(p: ptr, offset: int, value: int) -> int { return aether_mem_set_uint32(p, offset, value) }
get_float32(p: ptr, offset: int) -> float { return aether_mem_get_float32(p, offset) }
set_float32(p: ptr, offset: int, value: float) -> int { return aether_mem_set_float32(p, offset, value) }
get_float64(p: ptr, offset: int) -> float { return aether_mem_get_float64(p, offset) }
set_float64(p: ptr, offset: int, value: float) -> int { return aether_mem_set_float64(p, offset, value) }
bits_of_float(value: float) -> long { return aether_mem_bits_of_float(value) }
float_from_bits(bits: long) -> float { return aether_mem_float_from_bits(bits) }
clz32(value: int) -> int { return aether_mem_clz32(value) }
clz64(value: long) -> int { return aether_mem_clz64(value) }
udiv64_32(dividend: long, divisor: int, premainder: ptr) -> int { return aether_mem_udiv64_32(dividend, divisor, premainder) }
call_fn3_int(fn: ptr, a: long, b: long, opaque: ptr) -> int { return aether_mem_call_fn3_int(fn, a, b, opaque) }
call_fn3_void(fn: ptr, a: long, b: long, opaque: ptr) { aether_mem_call_fn3_void(fn, a, b, opaque) }
call_fn2_void(fn: ptr, a: ptr, b: ptr) { aether_mem_call_fn2_void(fn, a, b) }
ptr_to_long(p: ptr) -> long { return aether_mem_ptr_to_long(p) }
long_to_ptr(addr: long) -> ptr { return aether_mem_long_to_ptr(addr) }
// Bulk wrappers — call shape `mem.copy(dst, src, n)`, etc.
// `set` mirrors libc memset (NOT to be confused with `set_byte` /
// `set_int` etc, which write a single value at an offset).
copy(dst: ptr, src: ptr, n: long) -> ptr { return aether_mem_copy(dst, src, n) }
move(dst: ptr, src: ptr, n: long) -> ptr { return aether_mem_move(dst, src, n) }
compare(a: ptr, b: ptr, n: long) -> int { return aether_mem_compare(a, b, n) }
set(dst: ptr, value: int, n: long) -> ptr { return aether_mem_set_bulk(dst, value, n) }
// Endian-aware wrappers.
get_u16_le(p: ptr, offset: int) -> int { return aether_mem_get_u16_le(p, offset) }
get_u16_be(p: ptr, offset: int) -> int { return aether_mem_get_u16_be(p, offset) }
set_u16_le(p: ptr, offset: int, value: int) -> int { return aether_mem_set_u16_le(p, offset, value) }
set_u16_be(p: ptr, offset: int, value: int) -> int { return aether_mem_set_u16_be(p, offset, value) }
get_u32_le(p: ptr, offset: int) -> long { return aether_mem_get_u32_le(p, offset) }
get_u32_be(p: ptr, offset: int) -> long { return aether_mem_get_u32_be(p, offset) }
set_u32_le(p: ptr, offset: int, value: long) -> int { return aether_mem_set_u32_le(p, offset, value) }
set_u32_be(p: ptr, offset: int, value: long) -> int { return aether_mem_set_u32_be(p, offset, value) }
get_u64_le(p: ptr, offset: int) -> long { return aether_mem_get_u64_le(p, offset) }
get_u64_be(p: ptr, offset: int) -> long { return aether_mem_get_u64_be(p, offset) }
set_u64_le(p: ptr, offset: int, value: long) -> int { return aether_mem_set_u64_le(p, offset, value) }
set_u64_be(p: ptr, offset: int, value: long) -> int { return aether_mem_set_u64_be(p, offset, value) }