-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathmodule.ae
More file actions
134 lines (117 loc) · 5.51 KB
/
Copy pathmodule.ae
File metadata and controls
134 lines (117 loc) · 5.51 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
// std.sync — a single atomic 64-bit integer cell.
// Import with: import std.sync
//
// Issue #2082.
//
// `.ae` already has an atomic *pointer* swap (std.snapshot) but no
// atomic *integer*, so a reference count or a small lock-free retire
// ring — exactly what a pool-owned copy-on-write structure needs to
// reclaim its displaced values safely — cannot be built in pure Aether.
// std.snapshot gives publication; it does not give reclamation. This
// module closes that gap with the smallest primitive that does the job:
// one atomic i64 with load / store / add / sub / compare-and-swap.
//
// c = sync.atomic_new(0) // heap atomic i64, initial value
// v = sync.atomic_load(c) // acquire load
// sync.atomic_store(c, 5) // release store
// n = sync.atomic_add(c, 1) // add, returns the NEW value
// n = sync.atomic_sub(c, 1) // subtract, returns the NEW value
// ok = sync.atomic_cas(c, exp, nw) // 1 if swapped exp->nw, else 0
// sync.atomic_free(c) // free the cell
//
// add / sub return the NEW (post-operation) value, NOT the previous one
// (unlike C's fetch_add). The dominant use is a refcount that must act
// exactly when it hits zero, so this reads correctly with no extra step:
//
// if sync.atomic_sub(rc, 1) == 0 {
// // last reference gone — safe to reclaim
// }
//
// MEMORY ORDERING. load is acquire, store is release, add/sub/cas are
// acquire+release. So an atomic that gates other data (a refcount
// protecting the value it counts, a flag publishing a freshly-built
// buffer) carries the publish/subscribe guarantee: a thread that
// acquires the counter sees everything the releasing thread wrote before
// it. Same discipline std.snapshot uses for its pointer.
//
// The value is a signed 64-bit integer (Aether `long`), so a refcount, a
// generation number, or a pointer punned through mem.long_to_ptr all
// fit. The cell is cap-accounted, consistent with the process memory
// cap.
//
// WHAT THIS IS FOR. Low-level plumbing for pool-owned, C-style shared
// state — the shape std/http/proxy/aether_proxy_lb.c already uses in C
// (atomics + mutex), lifted into `.ae` so a pool-owned COW structure can
// reclaim correctly without dropping to C. The motivating case: a
// snapshot-cell retire ring (guard the displaced value with a refcount,
// free it when the count reaches zero after a grace period).
//
// WHAT THIS IS NOT. It is not a mutex and not a general lock. It is
// deliberately minimal and is not an invitation to replace the actor
// model with lock-based sharing — actors remain the default for
// coordinating mutable state. Reach for this only for the pool-owned
// C-style seam actors do not cover.
exports(
// Raw externs (over std/sync/aether_sync.c).
aether_sync_atomic_new, aether_sync_atomic_load,
aether_sync_atomic_store, aether_sync_atomic_add,
aether_sync_atomic_sub, aether_sync_atomic_cas,
aether_sync_atomic_free,
// Ergonomic wrappers — call as sync.atomic_new / .atomic_load / etc.
atomic_new, atomic_load, atomic_store, atomic_add, atomic_sub,
atomic_cas, atomic_free
)
// ---- Raw externs ----
// Create an atomic i64 cell initialised to `initial`. Returns the cell
// handle, or null on allocation failure / memory-cap exceeded. CHECK the
// result: every op below is fatal on a null cell (a swallowed null would let
// atomic_sub return 0 = "reclaim" and free a live value), so an unchecked
// alloc failure crashes at first use, not silently.
extern aether_sync_atomic_new(initial: long) -> ptr
// Acquire load of the current value. Fatal on a null cell.
extern aether_sync_atomic_load(cell: ptr) -> long
// Release store of `value`. Fatal on a null cell.
extern aether_sync_atomic_store(cell: ptr, value: long)
// Atomically add `delta`; returns the NEW (post-add) value. Fatal on a
// null cell.
extern aether_sync_atomic_add(cell: ptr, delta: long) -> long
// Atomically subtract `delta`; returns the NEW (post-sub) value. Fatal on
// a null cell.
extern aether_sync_atomic_sub(cell: ptr, delta: long) -> long
// Compare-and-swap: if the cell holds `expected`, replace it with
// `desired` and return 1; else leave it and return 0. Strong (no
// spurious failures); success is acq_rel so a CAS winner that reads the
// data it guards acquires correctly. Fatal on a null cell.
extern aether_sync_atomic_cas(cell: ptr, expected: long, desired: long) -> int
// Free the cell. A null cell is a no-op (mirrors libc free).
extern aether_sync_atomic_free(cell: ptr)
// ---- Ergonomic wrappers ----
// Create an atomic i64 cell initialised to `initial`.
atomic_new(initial: long) -> ptr {
return aether_sync_atomic_new(initial)
}
// Lock-free acquire load of the current value.
atomic_load(cell: ptr) -> long {
return aether_sync_atomic_load(cell)
}
// Release store of `value`.
atomic_store(cell: ptr, value: long) {
aether_sync_atomic_store(cell, value)
}
// Add `delta`, return the NEW value. Negative `delta` decrements.
atomic_add(cell: ptr, delta: long) -> long {
return aether_sync_atomic_add(cell, delta)
}
// Subtract `delta`, return the NEW value. The refcount-release call:
// `if sync.atomic_sub(rc, 1) == 0 { reclaim }`.
atomic_sub(cell: ptr, delta: long) -> long {
return aether_sync_atomic_sub(cell, delta)
}
// Compare-and-swap exp -> desired. Returns 1 on swap, 0 otherwise.
atomic_cas(cell: ptr, expected: long, desired: long) -> int {
return aether_sync_atomic_cas(cell, expected, desired)
}
// Free the cell.
atomic_free(cell: ptr) {
aether_sync_atomic_free(cell)
}