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
//! Names and ids the compile boundary agrees on with the island.
//!
//! Everything here is a *convention*, not a translation: how a user-defined
//! function's LLVM symbol is spelled, how an `rt_llvm_call` operation is
//! numbered, which builtin methods bypass the generic `assoc` path, and the
//! two trait-object id tables a translation is handed. `compiler.rs`'s own
//! Lisp source mangles and re-derives these same strings and numbers, so a
//! change on either side has to be matched on the other — which is exactly
//! why they live in one small module of their own rather than inside the
//! translator that happens to use them most.
//!
//! Extracted from `ast_bridge` when that translator was deleted (Phase 2
//! Stage C): none of this ever touched the typed AST, so none of it went with
//! it.
use HashMap;
use ;
use cratePath;
/// The two trait-object id tables a translation reads, interned by `Interp`
/// *before* translation starts: both are baked into the emitted IR as
/// constants (`(dyn-new id ...)`, `(dyn-upcast trait-id ...)`), so there is
/// nothing to resolve lazily mid-translation, exactly like [`Ctx::globals`].
///
/// One parameter rather than two because every translation entry point and
/// every nested-scope hand-off needs both, and they are interned together
/// (`Interp::register_dyn_box`).
/// A process-wide counter for synthesizing unique LLVM symbol names for
/// anonymous functions — every `lambda`/`fnref`-forwarding-wrapper/
/// immediately-invoked-lambda gets one (labels/closures Stage 4), since none
/// of those have a name from user source the way a `labels` def or `defun`
/// does. Process-wide (not per-translation-pass or per-outer-function) so
/// two lambdas compiled into the same shared AOT module — even from two
/// *different* `defun`s — can never collide, with no mangling scheme to get
/// right.
static LAMBDA_COUNTER: AtomicU64 = new;
/// A user-defined free function/`labels` sibling's own LLVM symbol name —
/// see [`crate::compile::USER_SYMBOL_PREFIX`]'s doc comment for why this is
/// never `logical_name` unprefixed. The single place [`translate_call`]/
/// [`translate_fnref`]'s embedded `(call ...)`-node name string is built,
/// so it always agrees with whatever `Interp::compile_scc`/
/// `compile::aot` declare/wire the *actual* LLVM function under.
/// The LLVM symbol a call to `path` must name: the `typelisp-rt` shim, when
/// `path` is a free builtin with no typelisp body
/// ([`crate::compile::externs::builtin_shim`]), and otherwise the mangled
/// name of the compiled function.
///
/// The one place that choice is made. `core_bridge` writes the answer into
/// the node it hands the island, which looks the name up and calls it —
/// there is no second derivation on the island side to disagree with this
/// one.
/// One definition with a compiled body, named the two ways the compile
/// boundary needs it: as a *node* (what `Interp::resolve_fn_def` and
/// `add_compiled_function` take) and as an *LLVM symbol* (what a call site
/// emits and the linker/JIT resolves).
///
/// Keeping both derivations on one type is the point: a `defun` and a
/// `defmethod` spell each of the two names differently, and every place that
/// builds a precompiled library — the generators in
/// [`crate::compile::bootstrap`] and [`crate::compile::prelude_bootstrap`],
/// and `Interp::install_compiled_library` on the loading side — needs the
/// same pair. Deriving them separately in each is how the island's own
/// `defun`-only, root-path-only shortcut got baked in.
/// A user-defined method's own LLVM symbol name — the `assoc`
/// counterpart of [`user_symbol_name`]. `compiler.rs`'s `compile-assoc-user`
/// mangles `type-name`/`method` back into this exact same
/// `tl_type::path::method` string (its own `(append "tl_" (append type-name
/// (append "::" method)))`, where `type-name` is whatever full `::`-joined
/// path [`ast_to_sexpr_scoped`]'s `assoc`/`methodref` arms
/// embedded — never just the type's local segment, or two same-named types
/// in different modules would mangle to the same symbol) before its
/// `get-function` lookup, so this must stay in lockstep with that — see
/// [`crate::compile::USER_SYMBOL_PREFIX`]'s doc comment.
/// `Vector<T>`'s builtin methods that have no compiled `defmethod` body and so
/// must be lowered to a dedicated `vector-op` node (`translate_vector_method`)
/// rather than routed through the generic `assoc` path. Deliberately *not*
/// `iter`: that one is a real prelude `defmethod` (`vector-iter::new`), a
/// normal compiled method, so it stays on the `assoc` path. `HashTable<K,V>`
/// shares the `new`/`get`/`set` names but a different `type_name`, so the
/// [`path_is_builtin`] guard at the call site keeps them apart.
pub const VECTOR_BUILTIN_METHODS: = ;
/// `HashTable<K,V>`'s builtin methods lowered to a `hashtable-op` node
/// (`translate_hashtable_method` -> the `rt_hashtable_*` family) rather than
/// the generic `assoc` path — every one except `iter`, `get`, `set`,
/// `remove`, `maphash` and `size`, which are genuine prelude `defmethod`s.
///
/// `get`/`set`/`remove` were here once, as three whole-lookup shims. They
/// could not stay: looking a key up means hashing it and comparing it, and
/// both of those are the key type's own `sxhash`/`equals` — typelisp
/// methods a Rust shim cannot call. The bucket primitives take the hash the
/// prelude computed and an index it found, which is the part this layer can
/// answer on its own.
pub const HASHTABLE_BUILTIN_METHODS: = ;
/// The stable operation id compiled code passes as `rt_llvm_call`'s first
/// argument: FNV-1a over `"type-key::method"`, folded into a tagged-`Sexpr`
/// integer's 61-bit signed payload. A *hash*, not a table index, so the id
/// embedded in a committed compiler-island bitcode artifact (interp-closure
/// removal Stage 3) survives methods being added to or removed from the
/// table in a later build — an index would silently shift. Collisions are
/// checked once at table construction (`interp`'s `llvm_op_table`), which
/// panics rather than dispatching two methods through one id.
///
/// The `<< 3 >> 3` fold is load-bearing, not cosmetic: this id is baked into
/// the `llvm-op` node as a `Sexpr` `Int`, and the *compiled* island reads
/// `Sexpr` ints back in the 3-bit-tagged representation (`raw >> 3`), which
/// would silently drop the top 3 bits of a full-width hash — so a
/// natively-compiled `rt_llvm_call` would pass a truncated id the table (keyed
/// on the full hash) has no entry for, aborting at runtime. Folding here means
/// the id already fits 61 bits, so it round-trips identically through the
/// tagged compiled path and the interpreter's full-width `Value::Int`, and the
/// table (built from this same function) keys on exactly what compiled code
/// passes. (A change to this fold requires regenerating `compiler_island.bc`,
/// whose own native-scope ops carry ids baked by it — the freshness test
/// catches a stale artifact.)