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
//! The interned small-`Int` range (§4.3).
//!
//! §4.3's uniform object model is normative "even if later optimizations intern
//! small integers, use tagged pointers, or eliminate allocations through escape
//! analysis" — provided such an optimization "preserves reference and aliasing
//! semantics". For `Int` there are none to preserve, and the language already
//! ships the existence proof: `Unit` and `Bool` are interned singletons, so
//! every `true` in every program is one object
//! ([`crate::immortal::Immortals`]).
//!
//! **Why sharing an `Int` is unobservable.** There is no identity operator in
//! the language — `praxis_hir`'s `BinOp` is arithmetic, comparison and the two
//! logical connectives, and nothing else. `==` on `Int` lowers to `Inst::IntCmp`
//! over extracted payloads; the structural fallback `praxis_struct_eq` has no
//! pointer fast path either. `Map`/`Set`/`Counter` keys go through
//! [`DynamicKey`](crate::dynamic_key::DynamicKey), whose `eq` *does* open with a
//! pointer comparison — but that is a fast path **for** structural equality, and
//! `int_equals` is reflexive, so sharing an object can only make it fire more
//! often, never change the answer. And an `Int` payload is never written after
//! its allocation: `Inst::StoreScalar` has no builder site and the backend's arm
//! for it is a documented no-op.
//!
//! **Why not `Float` or `Text`.** `Float` fails the reflexivity argument that
//! carries `DynamicKey`'s fast path — `float_equals` is IEEE, so NaN ≠ NaN — and
//! interning it would make two separately-written NaN literals compare equal as
//! map keys. `Text` fails a different test: `TextPayload::Owned(OwnedText)` is
//! not `Copy`, and [`Heap::alloc_immortal`](crate::Heap) requires `Copy`
//! *because* an immortal is invisible to `Heap`'s `Drop` — an immortal `Text`
//! would leak its `Box<str>` at teardown.
//!
//! `Char` passes every leg of the argument above, and [`crate::small_char`] is
//! the second interned scalar range (ADR-107): `char_equals` is a reflexive
//! `u32 ==`, a `CharPayload` is `Copy`, and ASCII is a bounded set. It is a
//! separate module rather than a second constant here because the two ranges
//! have different consumers — this one is read by three crates and by generated
//! code, and that one only by the runtime.
//!
//! **This module is the one statement of the range.** `praxis-mir` asks
//! [`index_of`] whether a literal is in range at compile time and `praxis-runtime`
//! asks it again at run time; the Cranelift backend derives the element offset
//! from the same [`SMALL_INT_MIN`]. A second spelling of the bounds anywhere
//! would let the compiler emit a table read for a value the table does not hold.
use crateGcRef;
use crateInlineInternSite;
/// The lowest `Int` the runtime interns.
///
/// Negative values are worth a bucket because a Praxis program's negative
/// integers are overwhelmingly small: `-1` as a "not found" sentinel, the four
/// neighbour offsets a grid walk steps by, an accumulator's initial `-1`.
pub const SMALL_INT_MIN: i64 = -256;
/// The highest `Int` the runtime interns.
///
/// Chosen against the benchmark suite (`benchmarks/praxis/`): it covers every
/// literal the suite contains, the digits and small constants AoC-shaped input
/// parsing produces, and the loop counters and collection lengths of a program
/// whose working set is a few hundred elements. It is deliberately *not* sized
/// to cover a program's whole data — a `Counter` over a million-line input will
/// leave the range immediately, and that is the case the allocator is for.
///
/// The cost of raising it is [`SMALL_INT_COUNT`] × 24 bytes of permanently
/// resident pages (a `GcHeader` is 16 bytes since ADR-109 and an `IntPayload`
/// is 8, and 24 is a rung of the size-class ladder exactly), so `-256..=1024`
/// is ~30 KiB. Anyone tuning this should re-run the suite rather than reason
/// about it: the table is free only while it stays in cache — and that 24 is
/// why ADR-109 paid here as well as on the allocation path, since the table is
/// resident for the whole process.
pub const SMALL_INT_MAX: i64 = 1024;
/// How many `Int`s the table holds — the length of
/// [`Immortals::small_ints`](crate::Immortals) and the bound every index derived
/// from [`index_of`] respects.
pub const SMALL_INT_COUNT: usize = as usize;
/// The size of one table element, for the backend's element-offset arithmetic.
///
/// The Cranelift `Inst::ConstGc` lowering indexes the table with a compile-time
/// constant byte offset, so it needs the stride. Reading it from here rather
/// than writing `8` there means the stride and the array it indexes are one
/// statement — the same reason [`index_of`] is the only in-range test.
pub const SMALL_INT_STRIDE: usize = ;
/// `v`'s index in the interned table, or `None` if `v` is outside the range.
///
/// A `const fn` so `praxis-mir` can ask it while lowering a literal and the
/// runtime can ask it on the allocation path, and both get the same answer by
/// construction. Returning an `Option<usize>` rather than a bool-plus-arithmetic
/// pair is what keeps "in range" and "which slot" from being two decisions: a
/// caller that has the index has already proved the value was in range.
pub const
/// Everything the Cranelift backend bakes in to answer an in-range `Int`
/// inline, as one value (ADR-113).
///
/// **This is the only [`InlineInternSite`] in the workspace, and that is the
/// mechanism.** `InlineInternSite::new` is `pub(crate)`, so the backend cannot
/// assemble a site of its own; it can only name one this crate minted, and there
/// is one, here, beside the bounds it describes. A future inline `Char` probe
/// mints its own next to [`crate::small_char`]'s range — which is what stops it
/// from being written as a copy of the `Int` arm reading `small_chars` with
/// `SMALL_INT_MIN`/`SMALL_INT_MAX`, a probe past the end of a table whose only
/// bound is its length.
///
/// The site also carries the pacing predicate's two offsets, which `new` fills
/// from `Heap` rather than taking as arguments: permission to read this table
/// inline and the obligation to test [`Heap::collection_is_due`] first are one
/// value, because they are one decision (ADR-113 decision 1).
pub const INLINE_INTERN_SITE: InlineInternSite = new;