beck_core/fields.rs
1//! Where a record literal's fields go, decided once instead of once per record.
2//!
3//! A record's fields are held in one order — by name — because that order is the `Map`'s
4//! iteration, the state digest and the patch stream a replay has to reproduce bit for bit
5//! ([`54`](../../../../../docs/54-ordering.md)). A record *literal* is written in some other
6//! order, usually the declaration's, so building one meant sorting three to eight names on every
7//! construction.
8//!
9//! The names are in the source, so the answer is the same every time the literal runs. This pass
10//! computes it once and writes it on the node: [`crate::core::Core::order`] holds, for each
11//! position of the finished record, which written field belongs there — four bits each, so a
12//! literal of up to [`MAX_ORDERED`] fields carries its whole layout in a `u32` that fits in
13//! padding `Core` already had.
14//!
15//! Evaluation order is **not** what changes. A field expression can `raise`, so the values are
16//! still computed in the order they are written; what the permutation removes is the comparing
17//! and moving afterwards.
18//!
19//! Like [`crate::frames`], being unable to answer is safe: [`UNORDERED`] means "sort at run
20//! time", and it is what a literal with a repeated field name, one with more fields than fit, and
21//! every program built by something that never runs this pass all carry.
22
23use crate::core::{Core, CoreKind};
24
25/// No layout on this node: whoever builds the record sorts it, as it did before this pass existed.
26///
27/// It cannot collide with a real layout. Every nibble of one is a written field's index, which is
28/// below [`MAX_ORDERED`], so no layout has `0xf` anywhere and this one is `0xf` everywhere.
29pub const UNORDERED: u32 = u32::MAX;
30
31/// How many fields fit in the packed layout: eight, at four bits each.
32///
33/// Nothing in this tree declares a record wider than that, and one that did would sort at run time
34/// rather than fail — which is why this is a constant here and not a limit in the language.
35pub const MAX_ORDERED: usize = 8;
36
37/// Put `items` — a record literal's fields, in the order they are written — into the order the
38/// record holds them in.
39///
40/// **One allocation, and it is the caller's.** That is the whole design constraint: the vector of
41/// evaluated fields already exists, so this permutes it where it lies rather than selecting out of
42/// it into a second one. Building the answer into a fresh vector was tried and measured, and it
43/// cost more than the sort it replaced — an allocation is dearer than an insertion sort over four
44/// names ([`78`](../../../../../docs/78-a-record-is-a-permutation-report.md) §78.3).
45///
46/// The permutation is followed in its cycles, which needs somewhere to record what has already
47/// been placed; here that is the packed layout itself, copied into a local and rewritten as it
48/// goes. So the bookkeeping is a `u32` in a register and the whole of this touches no memory but
49/// the elements it moves.
50///
51/// The caller has established that the node carries a layout ([`UNORDERED`] does not) and that the
52/// record is no wider than [`MAX_ORDERED`].
53pub fn place<T>(items: &mut [T], order: u32) {
54 let mut dest = order;
55 for i in 0..items.len() {
56 loop {
57 let j = nibble(dest, i);
58 if j == i {
59 break;
60 }
61 items.swap(i, j);
62 dest = swap_nibbles(dest, i, j);
63 }
64 }
65}
66
67/// Where the field written at position `at` belongs in the finished record.
68#[inline]
69fn nibble(order: u32, at: usize) -> usize {
70 ((order >> (4 * at)) & 0xf) as usize
71}
72
73#[inline]
74fn swap_nibbles(order: u32, i: usize, j: usize) -> u32 {
75 let (a, b) = (nibble(order, i) as u32, nibble(order, j) as u32);
76 let cleared = order & !(0xf << (4 * i)) & !(0xf << (4 * j));
77 cleared | (b << (4 * i)) | (a << (4 * j))
78}
79
80/// Give every record literal in the program its layout.
81pub fn order_program(program: &mut crate::check::Program) {
82 for def in program.defs.values_mut() {
83 order(&mut def.body);
84 }
85 for test in program.tests.iter_mut() {
86 for c in test.cores_mut() {
87 order(c);
88 }
89 }
90}
91
92/// The same for one expression, and everything under it.
93pub fn order(c: &mut Core) {
94 order_here(c);
95 for child in crate::core::children_mut(c) {
96 order(child);
97 }
98}
99
100/// The layout of this node alone, for a record synthesised after the pass has run — the splitter's
101/// fused state is the one that is.
102pub fn order_here(c: &mut Core) {
103 if let CoreKind::Make { fields, .. } = &c.kind {
104 c.order = layout(fields.iter().map(|(name, _)| name.as_ref()));
105 }
106}
107
108/// The packed layout for these field names, or [`UNORDERED`] when there is not one to give.
109fn layout<'a>(names: impl Iterator<Item = &'a str>) -> u32 {
110 let names: Vec<&str> = names.collect();
111 if names.is_empty() || names.len() > MAX_ORDERED {
112 return UNORDERED;
113 }
114 let mut by_name: Vec<usize> = (0..names.len()).collect();
115 by_name.sort_unstable_by(|&i, &j| crate::core::cmp_name(names[i], names[j]));
116 // A repeated name has no layout: two written fields would want one position, and which of them
117 // wins is a question the run-time sort answers today and this pass must not answer differently.
118 if by_name
119 .windows(2)
120 .any(|w| crate::core::cmp_name(names[w[0]], names[w[1]]) != std::cmp::Ordering::Less)
121 {
122 return UNORDERED;
123 }
124 let mut order = 0u32;
125 for (at, &written) in by_name.iter().enumerate() {
126 order |= (at as u32) << (4 * written);
127 }
128 order
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::core::{Const, VarId};
135 use crate::ty::Ty;
136 use beck_diag::Span;
137 use std::sync::Arc;
138
139 fn make(names: &[&str]) -> Core {
140 Core::new(
141 CoreKind::Make {
142 ty: "R".into(),
143 variant: None,
144 fields: names
145 .iter()
146 .map(|n| {
147 (
148 Arc::from(*n),
149 Core::new(CoreKind::Const(Const::Int(0)), Ty::int(), Span::NONE),
150 )
151 })
152 .collect(),
153 },
154 Ty::int(),
155 Span::NONE,
156 )
157 }
158
159 /// Apply a node's layout to its own field names, which is what the evaluator does to their
160 /// values.
161 fn placed(c: &Core, names: &[&str]) -> Vec<String> {
162 let mut items: Vec<String> = names.iter().map(|s| s.to_string()).collect();
163 place(&mut items, c.order);
164 items
165 }
166
167 #[test]
168 fn a_literal_written_in_order_is_the_identity() {
169 let mut c = make(&["a", "b", "c"]);
170 order(&mut c);
171 assert_eq!(placed(&c, &["a", "b", "c"]), ["a", "b", "c"]);
172 }
173
174 /// `Ball(x=…, y=…, x_vel=…, y_vel=…)` — declaration order, which is not name order.
175 #[test]
176 fn a_literal_written_in_declaration_order_permutes() {
177 let mut c = make(&["x", "y", "x_vel", "y_vel"]);
178 order(&mut c);
179 assert_eq!(
180 placed(&c, &["x", "y", "x_vel", "y_vel"]),
181 ["x", "x_vel", "y", "y_vel"]
182 );
183 }
184
185 /// Every permutation of eight names, placed — because a cycle-following permutation is exactly
186 /// the kind of code that is right for the shapes somebody thought to write down and wrong for
187 /// one of the 40,320 they did not.
188 #[test]
189 fn every_permutation_of_a_full_record_places_correctly() {
190 fn perms(items: &mut Vec<usize>, k: usize, out: &mut Vec<Vec<usize>>) {
191 if k == items.len() {
192 out.push(items.clone());
193 return;
194 }
195 for i in k..items.len() {
196 items.swap(k, i);
197 perms(items, k + 1, out);
198 items.swap(k, i);
199 }
200 }
201 let names = ["a", "b", "c", "d", "e", "f", "g", "h"];
202 let mut all = Vec::new();
203 perms(&mut (0..names.len()).collect(), 0, &mut all);
204 assert_eq!(all.len(), 40_320);
205 for p in all {
206 let written: Vec<&str> = p.iter().map(|&i| names[i]).collect();
207 let mut c = make(&written);
208 order(&mut c);
209 assert_eq!(placed(&c, &written), names, "written as {written:?}");
210 }
211 }
212
213 #[test]
214 fn a_repeated_name_has_no_layout() {
215 let mut c = make(&["a", "b", "a"]);
216 order(&mut c);
217 assert_eq!(c.order, UNORDERED);
218 }
219
220 #[test]
221 fn a_record_wider_than_the_packing_has_no_layout() {
222 let wide: Vec<String> = (0..MAX_ORDERED + 1).map(|i| format!("f{i}")).collect();
223 let refs: Vec<&str> = wide.iter().map(|s| s.as_str()).collect();
224 let mut c = make(&refs);
225 order(&mut c);
226 assert_eq!(c.order, UNORDERED);
227 }
228
229 /// The point of the sentinel: it is not reachable as an answer, so "no layout" and "this
230 /// layout" cannot be confused.
231 #[test]
232 fn a_layout_is_never_the_sentinel() {
233 for n in 1..=MAX_ORDERED {
234 let names: Vec<String> = (0..n).map(|i| format!("f{}", MAX_ORDERED - i)).collect();
235 let refs: Vec<&str> = names.iter().map(|s| s.as_str()).collect();
236 let mut c = make(&refs);
237 order(&mut c);
238 assert_ne!(c.order, UNORDERED);
239 }
240 }
241
242 /// A literal nested inside a lambda gets its layout too — the walk is what this checks, since
243 /// a lambda's body is behind an `Arc` and is the one child that needs unsharing.
244 #[test]
245 fn a_literal_inside_a_lambda_is_reached() {
246 let inner = make(&["b", "a"]);
247 let mut lam = Core::new(
248 CoreKind::Lam {
249 params: Arc::from(vec![0 as VarId]),
250 body: Arc::new(inner),
251 },
252 Ty::int(),
253 Span::NONE,
254 );
255 order(&mut lam);
256 let CoreKind::Lam { body, .. } = &lam.kind else {
257 unreachable!()
258 };
259 assert_eq!(placed(body, &["b", "a"]), ["a", "b"]);
260 }
261}