beck_core/core.rs
1//! `Core` — the load-bearing IR, and the evaluator that runs it.
2//!
3//! [`docs/04-compiler-architecture.md`](../../../../../docs/04-compiler-architecture.md) §4.2:
4//! "Typed ANF/SSA hybrid; explicit closures, explicit effect operations, explicit tier annotation
5//! per node; `Query` sub-language kept *symbolic*. Typechecked semantics, placement, splitting,
6//! optimisation. The load-bearing IR."
7//!
8//! Two things stay symbolic here, exactly as §4.2 demands, and for the same stated reason —
9//! lowering them early would foreclose Phase 3:
10//!
11//! * **Signal and stream operations** are [`Prim`]s ([`Prim::Fold`], [`Prim::Durable`],
12//! [`Prim::SignalMap`], …), not loops. A fold that has already become a loop cannot be compiled
13//! to an incremental dataflow plan.
14//! * **UI trees** are `Html` *values* built by [`Prim::HtmlEl`] and friends, not DOM mutation
15//! calls, so the same value can be server-side rendered, diffed, or (Phase 3) compiled for the
16//! client.
17//!
18//! # On the backend
19//!
20//! The roadmap names Cranelift as Phase 1's server backend. What is here instead is a **`Core`
21//! evaluator**: the "engine-in-Rust with the language as its configuration" route that
22//! [`docs/00-original-idea.md`](../../../../../docs/00-original-idea.md) names as one of the three
23//! that work for a GC'd functional language on a Rust host (Materialize's shape). It is the
24//! deliberately-bad-but-complete option, and it keeps the `Core → Target` seam narrow, which §5.2
25//! says is what lets a backend slot in later. The Phase 1 report says plainly that native codegen
26//! is not done.
27
28use std::fmt;
29use std::sync::Arc;
30
31use beck_diag::Span;
32
33use crate::html::Html;
34use crate::pmap::PMap;
35use crate::ty::{Effect, Tier, Ty};
36
37pub type VarId = u32;
38
39/// A primitive operation. Everything the standard library provides in Phase 1.
40#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub enum Prim {
42 Add,
43 Sub,
44 Mul,
45 Div,
46 Rem,
47 Neg,
48 /// `abs`, `sqrt` and `Int` → `Float`, which are the three the numeric tower needs before any
49 /// of SICP §1.1.7 will run (`docs/32` §32.1). `Abs` is resolved from its operand the way the
50 /// arithmetic operators are; the other two are monomorphic.
51 Abs,
52 Sqrt,
53 Sin,
54 Cos,
55 Trunc,
56 ToFloat,
57 Eq,
58 Ne,
59 Lt,
60 Le,
61 Gt,
62 Ge,
63 And,
64 Or,
65 Not,
66 ToStr,
67 StrTrim,
68 StrToInt,
69 // ---- strings (docs/45 §? — Wave 2's string half). One primitive each, because a string is
70 // where a language's host has to be asked and there is nothing to express in Beck itself.
71 StrLen,
72 StrSlice,
73 StrSplit,
74 StrJoin,
75 StrContains,
76 StrStartsWith,
77 StrEndsWith,
78 StrUpper,
79 StrLower,
80 StrReplace,
81 StrIndexOf,
82 StrRepeat,
83 StrChars,
84 StrIsEmpty,
85 ListLen,
86 ListIsEmpty,
87 // ---- collections
88 ListGet,
89 ListSlice,
90 ListReverse,
91 ListTake,
92 ListDrop,
93 ListContains,
94 ListIndexOf,
95 ListFold,
96 ListAll,
97 ListAny,
98 ListFlatMap,
99 ListZip,
100 ListAppend,
101 MapKeys,
102 MapMerge,
103 // ---- JSON, and the standard library's first fallible function.
104 //
105 // `json_parse` raises rather than returning a `Result`, which is the shape
106 // [`docs/45`](../../../../../docs/45-error-rows-report.md) settled and the reason §8.5.3's trap 2
107 // said the library had to wait for it: a caller that wants a `Result` writes `try:`, and a
108 // caller inside something already fallible writes nothing at all.
109 JsonParse,
110 JsonRender,
111 // ---- time. `now()` gives milliseconds; these two are the civil calendar over them.
112 TimeFormat,
113 TimeParse,
114 // ---- digests, encodings and identifiers (`crate::digest`). A hash is a table and base64 is a
115 // grammar, so both are the host's half of `lib/README.md`'s division; a digest is also a *pure*
116 // function, which is what separates this from the other things a crypto library offers.
117 Digest,
118 /// A message authentication code: the one primitive whose input is a `secret[Str]` and whose
119 /// output is a `Str`. Charged `cap.sign` rather than left free, so a view cannot mint one —
120 /// `docs/adr/0014` is the record of the decision and §3.5 is what it is measured against.
121 DigestKeyed,
122 /// Constant-time equality, for the caller comparing a digest against one that arrived.
123 DigestEq,
124 HexEncode,
125 HexDecode,
126 Base64Encode,
127 Base64Decode,
128 /// Validates and *normalises*: two spellings of one identifier must not be two map keys.
129 UuidParse,
130 UuidVersion,
131 // ---- the outbound call. The *second* primitive whose row is a function of its argument
132 // (`Raise` is the first): the host it is given is the `net.out(host)` atom it performs, which
133 // is why that argument has to be written at the call site rather than computed.
134 HttpFetch,
135 MapList,
136 FilterList,
137 ConcatLists,
138 SortBy,
139 MapGet,
140 MapInsert,
141 MapRemove,
142 MapValues,
143 MapContains,
144 MapLen,
145 OptionIsSome,
146 OptionUnwrapOr,
147 HtmlEl,
148 HtmlText,
149 HtmlAttr,
150 HtmlOn,
151 HtmlKey,
152 /// Mints a fresh id. Nondeterministic, so §3.7 forbids it inside a fold; the client mints
153 /// entity ids instead, which is "the small tell that browsers here are replicas, not
154 /// terminals".
155 NewUuid,
156 /// Reads the wall clock. The other half of §3.7's rule: "time is data on the envelope".
157 Now,
158 /// Reads a secret from the process environment, yielding a `secret[Str]` (§3.5).
159 SecretEnv,
160 /// `raise e` — fail with a value.
161 ///
162 /// The atom it performs is `raises(T)`, which depends on the *type* of its argument, so the
163 /// checker attaches it where that type is known rather than [`Prim::effects`] declaring it.
164 /// This is the first primitive whose row is not a constant, and it is why that table's doc
165 /// says "the atoms this primitive performs *itself*".
166 Raise,
167 /// `try: block` — run a thunk, and turn a raise of the named type into an `Err`.
168 ///
169 /// Two arguments: the thunk, and the name of the error type this handler catches. The name is
170 /// what stops a handler from catching a failure it cannot type — a caller's function may raise
171 /// something this `try` never heard of, and that has to keep travelling.
172 Try,
173 /// `parallel: block` — run the scope's children, then its tail with their results bound.
174 ///
175 /// The arguments are the children's thunks followed by the continuation, so a child cannot
176 /// outlive the scope: there is no handle, and the only thing that can read a child's result is
177 /// the one lambda the scope built. That is [`docs/38`](../../../../../docs/38-literature-survey.md)
178 /// §38.4's "spawn/await as effect operations, the scope as their handler" with the handler as
179 /// the *only* form — the operations are not separately reachable.
180 ///
181 /// The children are independent by construction (none of them can name another) and no child
182 /// may perform an effect another child could observe, so the scope's answer does not depend on
183 /// the order they ran in. A backend may therefore run them together; running them in the order
184 /// they are written is a correct implementation of that, and is what the tree-walker does.
185 Parallel,
186 /// Wraps a value as `internal[T]`: storable, never Sendable.
187 InternalOf,
188 /// Unwraps one. Performs `cap.internal`, so only the authority chokepoint can do it.
189 Reveal,
190 // ---- the symbolic signal vocabulary (§3.7) ----
191 MergeClients,
192 /// `presence()` — who is connected now, as a signal that is not a function of the log.
193 ///
194 /// D6's last row. It performs `cap.presence` rather than an atom of its own, which is F16
195 /// ([`docs/14`](../../../../../docs/14-review-findings.md)) taken literally: "presence signals
196 /// leak who-is-online; gate behind a capability like any other view". The capability is also
197 /// what places it — no tier below the server discharges a `cap.*` — so a fold cannot read the
198 /// roster and a view reaches it across a declared edge.
199 Presence,
200 StreamFilterMap,
201 Fold,
202 Durable,
203 SignalMap,
204 SignalMap2,
205 /// §3.8's per-session view: `todos.map(filter_by(session.user))`. First-class because "the
206 /// fanout cost becomes a first-class engineering concern".
207 PerSession,
208 /// The authority chokepoint: the sole consumer of ingress, holding the accumulator so that
209 /// first-writer-wins and ownership can be decided (§3.7, F2).
210 Decide,
211}
212
213impl Prim {
214 pub fn name(self) -> &'static str {
215 use Prim::*;
216 match self {
217 Add => "+",
218 Sub => "-",
219 Mul => "*",
220 Div => "/",
221 Rem => "%",
222 Neg => "negate",
223 Raise => "raise",
224 Try => "try",
225 Parallel => "parallel",
226 Abs => "abs",
227 Sqrt => "sqrt",
228 Sin => "sin",
229 Cos => "cos",
230 Trunc => "trunc",
231 ToFloat => "float",
232 Eq => "==",
233 Ne => "!=",
234 Lt => "<",
235 Le => "<=",
236 Gt => ">",
237 Ge => ">=",
238 And => "and",
239 Or => "or",
240 Not => "not",
241 ToStr => "str",
242 StrTrim => "str_trim",
243 StrToInt => "str_to_int",
244 StrLen => "str_len",
245 StrSlice => "str_slice",
246 StrSplit => "str_split",
247 StrJoin => "str_join",
248 StrContains => "str_contains",
249 StrStartsWith => "str_starts_with",
250 StrEndsWith => "str_ends_with",
251 StrUpper => "str_upper",
252 StrLower => "str_lower",
253 StrReplace => "str_replace",
254 StrIndexOf => "str_index_of",
255 StrRepeat => "str_repeat",
256 StrChars => "str_chars",
257 ListGet => "list_get",
258 ListSlice => "list_slice",
259 ListReverse => "list_reverse",
260 ListTake => "list_take",
261 ListDrop => "list_drop",
262 ListContains => "list_contains",
263 ListIndexOf => "list_index_of",
264 ListFold => "list_fold",
265 ListAll => "list_all",
266 ListAny => "list_any",
267 ListFlatMap => "list_flat_map",
268 ListZip => "list_zip_with",
269 ListAppend => "list_append",
270 MapKeys => "map_keys",
271 MapMerge => "map_merge",
272 JsonParse => "json_parse",
273 JsonRender => "json_render",
274 TimeFormat => "time_format",
275 TimeParse => "time_parse",
276 Digest => "digest",
277 DigestKeyed => "digest_keyed",
278 DigestEq => "digest_eq",
279 HexEncode => "hex_encode",
280 HexDecode => "hex_decode",
281 Base64Encode => "base64_encode",
282 Base64Decode => "base64_decode",
283 UuidParse => "uuid_parse",
284 UuidVersion => "uuid_version",
285 HttpFetch => "http_fetch",
286 StrIsEmpty => "str_is_empty",
287 ListLen => "list_len",
288 ListIsEmpty => "list_is_empty",
289 MapList => "map_list",
290 FilterList => "filter_list",
291 ConcatLists => "concat_lists",
292 SortBy => "sort_by",
293 MapGet => "map_get",
294 MapInsert => "map_insert",
295 MapRemove => "map_remove",
296 MapValues => "map_values",
297 MapContains => "map_contains",
298 MapLen => "map_len",
299 OptionIsSome => "is_some",
300 OptionUnwrapOr => "unwrap_or",
301 HtmlEl => "html_el",
302 HtmlText => "html_text",
303 HtmlAttr => "html_attr",
304 HtmlOn => "html_on",
305 HtmlKey => "html_key",
306 NewUuid => "uuid",
307 Now => "now",
308 SecretEnv => "secret_env",
309 InternalOf => "internal_of",
310 Reveal => "reveal",
311 MergeClients => "merge_clients",
312 Presence => "presence",
313 StreamFilterMap => "filter_map",
314 Fold => "fold",
315 Durable => "durable",
316 SignalMap => "signal_map",
317 SignalMap2 => "map2",
318 PerSession => "per_session",
319 Decide => "decide",
320 }
321 }
322
323 /// The atoms this primitive performs *itself*.
324 ///
325 /// The polymorphic half of a primitive's row — `map_list`'s `e`, which is whatever its function
326 /// argument does — lives in the scheme in [`crate::prelude`], because a row variable is not a
327 /// constant. A test holds the two in agreement.
328 pub fn effects(self) -> Vec<Effect> {
329 match self {
330 // "Every connected client's send!s, interleaved. Arbitrary order — this is the
331 // nondeterminism; there is exactly one of these."
332 Prim::MergeClients => vec![Effect::Ingress],
333 // Who is connected is not a function of the log, so this is the second source of
334 // nondeterminism in a Beck program — and the only one a *view* may read. The atom is a
335 // capability rather than a new label: F16 asks for exactly that, and `cap.*` is what
336 // keeps it off the tiers that would make it unreplayable (§3.3).
337 Prim::Presence => vec![Effect::Cap(std::sync::Arc::from("presence"))],
338 Prim::Durable => vec![Effect::Durable],
339 Prim::NewUuid | Prim::Now => vec![Effect::Nondet],
340 // The scope performs `spawn` itself; what its children perform is charged by the
341 // checker, from each child's own row, because a thunk's effects belong to the thunk's
342 // type and this is the form that calls them.
343 Prim::Parallel => vec![Effect::Spawn],
344 Prim::SecretEnv => vec![Effect::Env],
345 // Wrapping is free; *reading* is the privileged half, and the capability is what stops
346 // a view unwrapping one to render it.
347 Prim::Reveal => vec![Effect::Cap(std::sync::Arc::from("internal"))],
348 // The standard library's fallible pair. Unlike `Prim::Raise`, whose atom depends on
349 // its argument's type, these two raise a type the prelude declares — so the row *is* a
350 // constant and belongs here, where a test holds it against the scheme.
351 Prim::JsonParse => vec![Effect::Raises(std::sync::Arc::from("JsonError"))],
352 Prim::TimeParse => vec![Effect::Raises(std::sync::Arc::from("TimeError"))],
353 Prim::HexDecode | Prim::Base64Decode => {
354 vec![Effect::Raises(std::sync::Arc::from("EncodingError"))]
355 }
356 Prim::UuidParse | Prim::UuidVersion => {
357 vec![Effect::Raises(std::sync::Arc::from("UuidError"))]
358 }
359 // The declassifier. `digest` and the encodings are pure; this one reads a
360 // `secret[Str]`, so it is held where `reveal` is held — behind a capability no client
361 // tier discharges, which is what stops a view minting a token (`docs/adr/0014`).
362 Prim::DigestKeyed => vec![Effect::Cap(std::sync::Arc::from("sign"))],
363 // Half of `http_fetch`'s row is a constant and half is its first argument. The
364 // constant half is here; the `net.out(host)` half is added by [`Core::effects`],
365 // which can see the argument, and by the checker, which is where it is charged.
366 Prim::HttpFetch => vec![Effect::Raises(std::sync::Arc::from("HttpError"))],
367 _ => Vec::new(),
368 }
369 }
370}
371
372#[derive(Clone, Debug, PartialEq)]
373pub enum Const {
374 Unit,
375 Bool(bool),
376 Int(i64),
377 Float(f64),
378 Str(Arc<str>),
379}
380
381/// One arm of a `match`. Patterns are shallow — a constructor and its named field binders — which
382/// is what §3.1's exhaustiveness check needs and no more.
383#[derive(Clone, Debug)]
384pub struct Arm {
385 pub pattern: Pattern,
386 /// `case Circle(r) if r > 0:` — a condition on the arm, in the scope of what the pattern bound.
387 ///
388 /// A guard that fails falls through to the next arm, which is what makes it a guard rather
389 /// than an `if` in the body.
390 pub guard: Option<Core>,
391 pub body: Core,
392 pub span: Span,
393}
394
395impl Arm {
396 /// Every expression this arm holds, in the order they run.
397 ///
398 /// One method rather than `&a.body` at each of fourteen call sites, for
399 /// [`Pattern::binders`]'s reason and with its history: a guard added as a field those sites
400 /// did not know about would be a `Core` that liveness never marks, that `frames` never counts
401 /// a slot for, and that the plan's free-variable analysis never sees — none of which is a
402 /// compile error, and all of which are wrong on a program that uses one.
403 pub fn exprs(&self) -> impl Iterator<Item = &Core> {
404 self.guard.iter().chain(std::iter::once(&self.body))
405 }
406
407 pub fn exprs_mut(&mut self) -> impl Iterator<Item = &mut Core> {
408 self.guard.iter_mut().chain(std::iter::once(&mut self.body))
409 }
410}
411
412#[derive(Clone, Debug)]
413pub enum Pattern {
414 Wildcard,
415 Bind(VarId),
416 Const(Const),
417 /// `Added(id, text)` — `variant` names the constructor, and each field carries the pattern
418 /// matched against it.
419 ///
420 /// A field's pattern is usually a [`Pattern::Bind`] or a [`Pattern::Wildcard`], which is what
421 /// `Added(id, text)` and `Added(_)` mean. It may be any pattern: `Some(Added(id, text))` is a
422 /// `Ctor` whose one field is a `Ctor`.
423 Ctor {
424 variant: Arc<str>,
425 binds: Vec<(Arc<str>, Pattern)>,
426 },
427 /// `whole @ Circle(r)` — a name for the value, and a pattern that takes it apart.
428 ///
429 /// The binder is irrefutable, so whether this matches is entirely `inner`'s question.
430 At {
431 var: VarId,
432 inner: Box<Pattern>,
433 },
434 /// `Circle(r) | Square(r)` — one of several, and every alternative binds the same names.
435 ///
436 /// The checker unifies the alternatives' binders onto one set of variables, so the body reads
437 /// `r` without knowing which alternative matched. That is the rule that makes an or-pattern a
438 /// pattern rather than two arms sharing a body.
439 Or(Vec<Pattern>),
440 /// `[]`, `[x]`, `[a, b]`, `[first, *rest]` — a list, taken apart.
441 ///
442 /// `items` is one pattern per fixed element and `rest` is the optional tail binder. A pattern
443 /// with no `rest` matches a list of exactly `items.len()` elements; one with a `rest` matches
444 /// any list at least that long.
445 ///
446 /// The tail is a binder rather than a pattern, and deliberately: `[a, *[b, c]]` is `[a, b, c]`
447 /// written twice over, so what it would add is a second spelling rather than a shape.
448 List {
449 items: Vec<Pattern>,
450 rest: Option<Option<VarId>>,
451 },
452}
453
454impl Pattern {
455 /// Every variable this pattern binds, at any depth.
456 ///
457 /// One method rather than a `match` at each of the three call sites, because those three were
458 /// `Bind`/`Ctor`/`_ => {}` — and a new pattern kind falling into the `_` would have been a
459 /// silent miscount in the splitter's variable supply and a false *free* variable in the plan's
460 /// analysis. Neither would have failed a test until a program used one (`docs/33` §33.5).
461 pub fn binders(&self) -> Vec<VarId> {
462 let mut out = Vec::new();
463 self.collect_binders(&mut out);
464 out
465 }
466
467 fn collect_binders(&self, out: &mut Vec<VarId>) {
468 match self {
469 Pattern::Wildcard | Pattern::Const(_) => {}
470 Pattern::Bind(v) => out.push(*v),
471 Pattern::At { var, inner } => {
472 out.push(*var);
473 inner.collect_binders(out);
474 }
475 Pattern::Ctor { binds, .. } => {
476 for (_, p) in binds {
477 p.collect_binders(out);
478 }
479 }
480 Pattern::List { items, rest } => {
481 for p in items {
482 p.collect_binders(out);
483 }
484 out.extend(rest.iter().filter_map(|b| *b));
485 }
486 // Every alternative binds the same variables, so one would do; all of them, deduped,
487 // is what stays right if the checker's unification is ever wrong about that.
488 Pattern::Or(alts) => {
489 for p in alts {
490 p.collect_binders(out);
491 }
492 out.sort_unstable();
493 out.dedup();
494 }
495 }
496 }
497
498 /// Whether this pattern matches every value of its type, so that no arm after it can run.
499 ///
500 /// Only a binder and a wildcard do — and a list pattern of nothing but a tail, `[*rest]`,
501 /// which is the one refutable-looking shape that refuses nothing.
502 pub fn irrefutable(&self) -> bool {
503 match self {
504 Pattern::Wildcard | Pattern::Bind(_) => true,
505 Pattern::List { items, rest } => items.is_empty() && rest.is_some(),
506 Pattern::Or(alts) => alts.iter().any(Pattern::irrefutable),
507 Pattern::At { inner, .. } => inner.irrefutable(),
508 Pattern::Const(_) | Pattern::Ctor { .. } => false,
509 }
510 }
511}
512
513#[derive(Clone, Debug)]
514pub enum CoreKind {
515 Const(Const),
516 Var(VarId),
517 /// A reference to a top-level definition.
518 Global(Arc<str>),
519 Lam {
520 /// Shared for the same reason `body` is: evaluating a `lam` hands the list to a closure,
521 /// and a refcount bump is cheaper than copying it once per call.
522 params: Arc<[VarId]>,
523 /// Shared, not owned: a closure is built every time a `lam` node is *evaluated*, and a
524 /// `Box` meant deep-copying the whole function body each time. `docs/73` §73.1 measured
525 /// 20,000 calls to a function whose executed path never changed costing 42 ms, 227 ms and
526 /// 606 ms as the *unexecuted* part of its body grew.
527 body: Arc<Core>,
528 },
529 App {
530 func: Box<Core>,
531 args: Vec<Core>,
532 },
533 Prim {
534 op: Prim,
535 args: Vec<Core>,
536 },
537 Let {
538 var: VarId,
539 value: Box<Core>,
540 body: Box<Core>,
541 },
542 If {
543 cond: Box<Core>,
544 then: Box<Core>,
545 alt: Box<Core>,
546 },
547 Match {
548 scrutinee: Box<Core>,
549 arms: Vec<Arm>,
550 },
551 /// Construct a union variant or a model record.
552 Make {
553 ty: Arc<str>,
554 variant: Option<Arc<str>>,
555 fields: Vec<(Arc<str>, Core)>,
556 },
557 Field {
558 base: Box<Core>,
559 name: Arc<str>,
560 },
561 /// `t.with(done=not t.done)` — a functional record update. The sketch's
562 /// `(set t :done (not t.done))`, and the reason `Todo` never needs a mutable binding.
563 With {
564 base: Box<Core>,
565 fields: Vec<(Arc<str>, Core)>,
566 },
567 ListLit(Vec<Core>),
568 MapLit(Vec<(Core, Core)>),
569}
570
571#[derive(Clone, Debug)]
572pub struct Core {
573 pub kind: CoreKind,
574 pub ty: Ty,
575 /// Which tier this node runs on. §4.2: "explicit tier annotation per node".
576 pub tier: Tier,
577 pub span: Span,
578 /// Set on a [`CoreKind::Var`] whose value this expression is the **last** reader of, so a
579 /// backend may move the binding rather than copy it. [`crate::liveness`] is what sets it and
580 /// what the guarantee means; `false` is always safe.
581 pub last_use: bool,
582 /// Set on a [`CoreKind::Make`]: which written field belongs at each position of the record it
583 /// builds, packed four bits per field. [`crate::fields`] is what sets it and what the packing
584 /// means; [`crate::fields::UNORDERED`] is always safe and means "sort at run time".
585 ///
586 /// It costs nothing: a `u32` here fits in the padding `last_use` already leaves, so `Core` is
587 /// 160 bytes either way.
588 pub order: u32,
589 /// Set on a [`CoreKind::Lam`]: how many bindings its body makes, so a call can reserve room
590 /// for them in one frame instead of allocating a scope per `let`. [`crate::frames`] is what
591 /// sets it and what the count means; `0` is always safe and means "chain a scope, as before".
592 pub locals: u32,
593}
594
595/// The string this expression *is*, when it is written as one.
596///
597/// Deliberately not an evaluator and deliberately not a constant folder: the one caller is the
598/// host of an outbound call, and "the host is written at the call site" is the rule that makes an
599/// egress policy derivable. A `let` that happens to bind a literal is not written at the call
600/// site, and reading through one would make the rule depend on how much folding the compiler
601/// currently does.
602pub fn literal_str(c: &Core) -> Option<Arc<str>> {
603 match &c.kind {
604 CoreKind::Const(Const::Str(s)) => Some(s.clone()),
605 _ => None,
606 }
607}
608
609impl Core {
610 pub fn new(kind: CoreKind, ty: Ty, span: Span) -> Core {
611 Core {
612 kind,
613 ty,
614 tier: Tier::Any,
615 span,
616 last_use: false,
617 order: crate::fields::UNORDERED,
618 locals: 0,
619 }
620 }
621
622 /// Every effect this expression can perform, by walking what it calls.
623 ///
624 /// Phase 1 used this as *the* effect analysis. Phase 2 does not: rows are inferred during
625 /// checking, where a call's latent row is known and a mere *reference* to a function performs
626 /// nothing. What survives is this syntactic over-approximation, used in one place where that is
627 /// the right answer — asking what a fold's function body could reach, including through a
628 /// function value it was handed.
629 pub fn effects(&self, globals: &dyn Fn(&str) -> Vec<Effect>, out: &mut Vec<Effect>) {
630 match &self.kind {
631 CoreKind::Prim { op, args } => {
632 for e in op.effects() {
633 if !out.contains(&e) {
634 out.push(e);
635 }
636 }
637 // `http_fetch`'s host is its first argument and a literal, so this walk can read
638 // it. It matters here rather than only in the checker because this is the oracle
639 // `testing::performs_itself` asks — a definition that makes the call is the one a
640 // `stub net.out(host)` replaces, and a definition that merely calls it is not.
641 if let (Prim::HttpFetch, Some(host)) = (op, args.first().and_then(literal_str)) {
642 let atom = Effect::NetOut(host);
643 if !out.contains(&atom) {
644 out.push(atom);
645 }
646 }
647 for a in args {
648 a.effects(globals, out);
649 }
650 }
651 CoreKind::Global(name) => {
652 for e in globals(name) {
653 if !out.contains(&e) {
654 out.push(e);
655 }
656 }
657 }
658 CoreKind::Const(_) | CoreKind::Var(_) => {}
659 CoreKind::Lam { body, .. } => body.effects(globals, out),
660 CoreKind::App { func, args } => {
661 func.effects(globals, out);
662 for a in args {
663 a.effects(globals, out);
664 }
665 }
666 CoreKind::Let { value, body, .. } => {
667 value.effects(globals, out);
668 body.effects(globals, out);
669 }
670 CoreKind::If { cond, then, alt } => {
671 cond.effects(globals, out);
672 then.effects(globals, out);
673 alt.effects(globals, out);
674 }
675 CoreKind::Match { scrutinee, arms } => {
676 scrutinee.effects(globals, out);
677 for e in arms.iter().flat_map(|a| a.exprs()) {
678 e.effects(globals, out);
679 }
680 }
681 CoreKind::Make { fields, .. } => {
682 for (_, f) in fields {
683 f.effects(globals, out);
684 }
685 }
686 CoreKind::Field { base, .. } => base.effects(globals, out),
687 CoreKind::With { base, fields } => {
688 base.effects(globals, out);
689 for (_, f) in fields {
690 f.effects(globals, out);
691 }
692 }
693 CoreKind::ListLit(xs) => {
694 for x in xs {
695 x.effects(globals, out);
696 }
697 }
698 CoreKind::MapLit(kvs) => {
699 for (k, v) in kvs {
700 k.effects(globals, out);
701 v.effects(globals, out);
702 }
703 }
704 }
705 }
706
707 /// Set the tier on this node and everything under it.
708 pub fn place(&mut self, tier: Tier) {
709 self.tier = tier;
710 match &mut self.kind {
711 CoreKind::Lam { body, .. } => Arc::make_mut(body).place(tier),
712 CoreKind::App { func, args } => {
713 func.place(tier);
714 for a in args {
715 a.place(tier);
716 }
717 }
718 CoreKind::Prim { args, .. } => {
719 for a in args {
720 a.place(tier);
721 }
722 }
723 CoreKind::Let { value, body, .. } => {
724 value.place(tier);
725 body.place(tier);
726 }
727 CoreKind::If { cond, then, alt } => {
728 cond.place(tier);
729 then.place(tier);
730 alt.place(tier);
731 }
732 CoreKind::Match { scrutinee, arms } => {
733 scrutinee.place(tier);
734 for e in arms.iter_mut().flat_map(|a| a.exprs_mut()) {
735 e.place(tier);
736 }
737 }
738 CoreKind::Make { fields, .. } => {
739 for (_, f) in fields {
740 f.place(tier);
741 }
742 }
743 CoreKind::Field { base, .. } => base.place(tier),
744 CoreKind::With { base, fields } => {
745 base.place(tier);
746 for (_, f) in fields {
747 f.place(tier);
748 }
749 }
750 CoreKind::ListLit(xs) => {
751 for x in xs {
752 x.place(tier);
753 }
754 }
755 CoreKind::MapLit(kvs) => {
756 for (k, v) in kvs {
757 k.place(tier);
758 v.place(tier);
759 }
760 }
761 CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => {}
762 }
763 }
764}
765
766// ---------------------------------------------------------------------------------------------
767// Values
768// ---------------------------------------------------------------------------------------------
769
770/// A runtime value.
771///
772/// `Map` and a record's fields are both *ordered* on purpose and for the same reason Phase 0 chose
773/// `BTreeMap`: iteration order is part of the rendered view, and replay must reproduce the *patch
774/// stream* bit for bit, not merely the set of values.
775///
776/// `Map` is a [`PMap`], not an `Arc<BTreeMap>`, because it is the fold's accumulator: an update
777/// must not copy it. See [`crate::pmap`] for why that structure and not another.
778#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
779pub enum Value {
780 Unit,
781 Bool(bool),
782 Int(i64),
783 /// A real, stored as an **order-preserving** key rather than as `f64::to_bits`, so that the
784 /// derived `Ord` is the numeric one.
785 ///
786 /// A total order is not optional here — a map key and a component of the state digest need one
787 /// — and `to_bits` supplies one that disagrees with arithmetic: `-1.0` has a larger bit pattern
788 /// than `1.0`, so `<` answered backwards for every negative number and `sort_by` sorted the
789 /// negatives in reverse. [`Value::float`] applies the standard monotone transform instead
790 /// (flip the sign bit for a positive, invert every bit for a negative), which makes the two
791 /// orders the same order. `docs/32` §32.2.
792 Float(u64),
793 /// Text, with the two facts a character-indexed language needs about it: how many characters
794 /// there are, and whether a character index is a byte index. [`Text`] is why.
795 Str(Arc<Text>),
796 List(Arc<Vec<Value>>),
797 Map(PMap<Value, Value>),
798 /// A model instance or a union variant — see [`Record`].
799 ///
800 /// Behind one pointer rather than inline, and that is a size decision rather than a style one:
801 /// the three fields inline made **every** `Value` 48 bytes, so a list of a million integers
802 /// carried 32 bytes of nothing per element and a call frame paid for the widest variant it did
803 /// not hold. One `Arc` makes a `Value` 16.
804 Data(Arc<Record>),
805 Html(Arc<Html>),
806 /// An attribute waiting to be attached to an element.
807 Attr(Arc<AttrValue>),
808 Closure(Arc<Closure>),
809}
810
811/// A model instance or a union variant. `variant` is `None` for a plain record.
812///
813/// Split out of [`Value::Data`] so that a `Value` is a discriminant and a pointer. Records are the
814/// widest thing the language has and the rarest thing in a hot loop, which is exactly the shape
815/// that should be behind an indirection.
816#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
817pub struct Record {
818 pub ty: Arc<str>,
819 pub variant: Option<Arc<str>>,
820 pub fields: Fields,
821}
822
823/// A record's fields, sorted by name.
824///
825/// This was a `BTreeMap`, and a record is the wrong size for one: three to eight entries, built
826/// once and read many times. A B-tree pays a node allocation and a pointer chase per level to buy
827/// an asymptotic advantage that never arrives at that size, and profiling `awfy/havlak.beck` put
828/// a fifth of the process inside its search, its insert and the `memcmp` underneath them.
829///
830/// Sorted by name and searched linearly: one allocation for the whole record, the names lie next
831/// to each other in cache, and `get` compares lengths before bytes because it wants equality
832/// rather than order. Iteration is in name order, so the value order, the state digest and the
833/// wire format ([`crate::repr`]) are all bit-for-bit what the `BTreeMap` gave — which is what
834/// makes this a representation change and not a semantic one.
835#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
836pub struct Fields(Vec<(Arc<str>, Value)>);
837
838impl Fields {
839 pub fn new() -> Fields {
840 Fields(Vec::new())
841 }
842
843 pub fn with_capacity(n: usize) -> Fields {
844 Fields(Vec::with_capacity(n))
845 }
846
847 pub fn get(&self, name: &str) -> Option<&Value> {
848 self.0
849 .iter()
850 .find(|(k, _)| same_name(k, name))
851 .map(|(_, v)| v)
852 }
853
854 /// Set `name`, keeping the order by name. Answers the value that was there.
855 ///
856 /// The search is by **equality** and not by order, which is the whole difference: `==` on two
857 /// `str`s compares their lengths first and reaches `memcmp` only for a pair that could match,
858 /// where a binary search has to order every probe it makes. A record has three to eight
859 /// fields, so a scan makes at most as many comparisons as a binary search and nearly all of
860 /// them are an integer test. Only a field that is genuinely new pays for the ordered insert,
861 /// and `with` — which is what calls this in a loop — never has one.
862 pub fn insert(&mut self, name: Arc<str>, value: Value) -> Option<Value> {
863 if let Some(slot) = self.0.iter_mut().find(|(k, _)| same_name(k, &name)) {
864 return Some(std::mem::replace(&mut slot.1, value));
865 }
866 let at = self
867 .0
868 .partition_point(|(k, _)| cmp_name(k, &name) == std::cmp::Ordering::Less);
869 self.0.insert(at, (name, value));
870 None
871 }
872
873 /// Build from fields in **any** order, sorting once.
874 ///
875 /// This is how a record literal is built, and it is a separate entry point from `insert` in a
876 /// loop because the two cost differently: `sort_unstable_by` on a handful of elements is an
877 /// insertion sort, which makes `n - 1` comparisons and moves nothing when the fields already
878 /// arrive in order — as a record literal's usually do.
879 pub fn from_pairs(mut pairs: Vec<(Arc<str>, Value)>) -> Fields {
880 pairs.sort_unstable_by(|(a, _), (b, _)| cmp_name(a, b));
881 pairs.dedup_by(|(a, _), (b, _)| same_name(a, b));
882 Fields(pairs)
883 }
884
885 /// Build from fields the caller has **already** put in order.
886 ///
887 /// The caller is [`crate::fields`], which decided the order once at compile time — a record
888 /// literal's field names are written in the source, so sorting them once per record built is
889 /// work with a known answer. Nothing else should use this: the order is the `Map` iteration,
890 /// the state digest and the patch stream, so getting it wrong is a wire-format bug rather than
891 /// a slow lookup.
892 pub fn from_sorted(pairs: Vec<(Arc<str>, Value)>) -> Fields {
893 debug_assert!(
894 pairs
895 .windows(2)
896 .all(|w| cmp_name(&w[0].0, &w[1].0) == std::cmp::Ordering::Less),
897 "from_sorted was given fields that are not sorted and distinct"
898 );
899 Fields(pairs)
900 }
901
902 pub fn len(&self) -> usize {
903 self.0.len()
904 }
905
906 pub fn is_empty(&self) -> bool {
907 self.0.is_empty()
908 }
909
910 pub fn iter(&self) -> std::slice::Iter<'_, (Arc<str>, Value)> {
911 self.0.iter()
912 }
913
914 pub fn values(&self) -> impl Iterator<Item = &Value> {
915 self.0.iter().map(|(_, v)| v)
916 }
917}
918
919impl FromIterator<(Arc<str>, Value)> for Fields {
920 fn from_iter<I: IntoIterator<Item = (Arc<str>, Value)>>(it: I) -> Fields {
921 Fields::from_pairs(it.into_iter().collect())
922 }
923}
924
925impl<'a> IntoIterator for &'a Fields {
926 type Item = &'a (Arc<str>, Value);
927 type IntoIter = std::slice::Iter<'a, (Arc<str>, Value)>;
928 fn into_iter(self) -> Self::IntoIter {
929 self.0.iter()
930 }
931}
932
933/// A string that knows its own length in **characters**, and whether it is ASCII.
934///
935/// Beck indexes text by character everywhere or nowhere (`docs/50` §50.5), and a `String` counts
936/// bytes — so `str_len` used to be `chars().count()` and `str_slice` used to `skip()` its way to
937/// the start. Both are `O(n)` in the *string* rather than in the answer, which makes the ordinary
938/// way to walk one — `while i < str_len(s)` reading `str_slice(s, i, 1)` — quadratic. Measured at
939/// ×2.7 per doubling in [`70`](../../../../../docs/70-last-use-moves-report.md) §70.6.
940///
941/// Both facts are computed once, when the string is built, which is work the construction was
942/// already doing: it had to copy the bytes, and `is_ascii` is a scan of the same bytes that answers
943/// `chars` for free when it is true. Everything downstream is then `O(1)` or `O(answer)`.
944///
945/// The `String` rather than a `Box<str>` is the other half: it has spare capacity, so `a + b` can
946/// push into `a` when the last-use analysis proves nobody else holds it ([`crate::liveness`]).
947#[derive(Clone, Debug)]
948pub struct Text {
949 bytes: String,
950 /// Characters, not bytes. `str_len`'s answer.
951 chars: usize,
952 /// Every character is one byte, so character index == byte index and a slice is a byte range.
953 ascii: bool,
954 /// For text that is *not* ASCII: the byte offset of every 32nd character.
955 ///
956 /// Chunked rather than one entry per character, because the point is to stop paying `O(n)` per
957 /// slice and a jump to the nearest 32 does that for a thirty-second of the memory — `n / 8`
958 /// bytes, and only for text that needs it, since an ASCII character index *is* a byte index.
959 ///
960 /// Built eagerly, in the pass that counts the characters, rather than cached on first use. A
961 /// lazy one would be interior mutability inside a `Value`, and a `Value` is a `Map` key: the
962 /// cache would be invisible to `Ord` and `Hash` and therefore harmless, but "harmless interior
963 /// mutability in a key" is a sentence every reader and `clippy::mutable_key_type` would have to
964 /// re-check. One pass and an eighth of the bytes is the cheaper answer.
965 index: Box<[u32]>,
966}
967
968/// How many characters one entry of [`Text`]'s index skips.
969const INDEX_STRIDE: usize = 32;
970
971impl Text {
972 pub fn new(bytes: String) -> Text {
973 let ascii = bytes.is_ascii();
974 // ASCII answers both questions from the scan `is_ascii` already did, and needs no index at
975 // all. Anything else pays one more pass — once, here — rather than paying it on every
976 // `str_len` and every `str_slice`.
977 if ascii {
978 let chars = bytes.len();
979 return Text {
980 bytes,
981 chars,
982 ascii,
983 index: Box::new([]),
984 };
985 }
986 let mut chars = 0usize;
987 let mut index = Vec::with_capacity(bytes.len() / INDEX_STRIDE + 1);
988 for (at, _) in bytes.char_indices() {
989 if chars.is_multiple_of(INDEX_STRIDE) {
990 index.push(at as u32);
991 }
992 chars += 1;
993 }
994 Text {
995 bytes,
996 chars,
997 ascii,
998 index: index.into_boxed_slice(),
999 }
1000 }
1001
1002 /// The byte offset of character `i`, in constant time for ASCII text and in at most
1003 /// one index stride for anything else. Past the end it answers the end, which is what a
1004 /// clamping slice wants.
1005 pub fn byte_offset(&self, i: usize) -> usize {
1006 if self.ascii {
1007 return i.min(self.bytes.len());
1008 }
1009 if i >= self.chars {
1010 return self.bytes.len();
1011 }
1012 let chunk = i / INDEX_STRIDE;
1013 let from = self.index.get(chunk).copied().unwrap_or(0) as usize;
1014 match self.bytes[from..]
1015 .char_indices()
1016 .nth(i - chunk * INDEX_STRIDE)
1017 {
1018 Some((at, _)) => from + at,
1019 None => self.bytes.len(),
1020 }
1021 }
1022
1023 /// The length in characters, in constant time.
1024 pub fn chars_len(&self) -> usize {
1025 self.chars
1026 }
1027
1028 pub fn is_ascii_text(&self) -> bool {
1029 self.ascii
1030 }
1031
1032 pub fn as_str(&self) -> &str {
1033 &self.bytes
1034 }
1035
1036 /// Append, consuming: the caller has established sole ownership, so this is a `push_str` and
1037 /// not a copy of both sides.
1038 pub fn appended(mut self, other: &str) -> Text {
1039 let before = self.chars;
1040 let start = self.bytes.len();
1041 let other_ascii = other.is_ascii();
1042 self.bytes.push_str(other);
1043
1044 // Everything here is `O(other)`, never `O(self)`, which is the property the whole change
1045 // exists for: appending in a loop has to stay linear in the total.
1046 if self.ascii && other_ascii {
1047 self.chars = self.bytes.len();
1048 return self;
1049 }
1050 if self.ascii {
1051 // The left half was ASCII, so its character numbers *are* its byte offsets and its
1052 // share of the index can be written down rather than walked for.
1053 let mut index = Vec::with_capacity(self.bytes.len() / INDEX_STRIDE + 1);
1054 let mut at = 0;
1055 while at < before {
1056 index.push(at as u32);
1057 at += INDEX_STRIDE;
1058 }
1059 self.index = index.into_boxed_slice();
1060 self.ascii = false;
1061 }
1062 let mut index = std::mem::take(&mut self.index).into_vec();
1063 let mut chars = before;
1064 for (at, _) in self.bytes[start..].char_indices() {
1065 if chars.is_multiple_of(INDEX_STRIDE) {
1066 index.push((start + at) as u32);
1067 }
1068 chars += 1;
1069 }
1070 self.chars = chars;
1071 self.index = index.into_boxed_slice();
1072 self
1073 }
1074}
1075
1076impl std::ops::Deref for Text {
1077 type Target = str;
1078 fn deref(&self) -> &str {
1079 &self.bytes
1080 }
1081}
1082
1083impl From<&str> for Text {
1084 fn from(s: &str) -> Text {
1085 Text::new(s.to_string())
1086 }
1087}
1088
1089/// Text compares, orders and hashes **as its characters**, so that adding the two cached facts
1090/// cannot change what a program means: a `Map` keyed by strings keeps its order, and so does the
1091/// state digest that a replay has to reproduce.
1092impl PartialEq for Text {
1093 fn eq(&self, other: &Self) -> bool {
1094 self.bytes == other.bytes
1095 }
1096}
1097impl Eq for Text {}
1098impl PartialOrd for Text {
1099 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1100 Some(self.cmp(other))
1101 }
1102}
1103impl Ord for Text {
1104 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1105 self.bytes.cmp(&other.bytes)
1106 }
1107}
1108impl std::hash::Hash for Text {
1109 fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
1110 self.bytes.hash(h)
1111 }
1112}
1113
1114#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1115pub enum AttrValue {
1116 Plain(Arc<str>, Arc<str>),
1117 On(Arc<str>, Value),
1118 Key(Arc<str>),
1119}
1120
1121#[derive(Debug)]
1122pub struct Closure {
1123 pub params: Arc<[VarId]>,
1124 /// The same `Arc` the [`CoreKind::Lam`] node holds, so building a closure is a refcount bump
1125 /// rather than a copy of the code.
1126 pub body: Arc<Core>,
1127 /// Behind an `Arc` so that *calling* the closure clones a pointer rather than the environment.
1128 pub env: Arc<Env>,
1129 /// How many bindings the body makes, copied off the [`CoreKind::Lam`] node so that a call can
1130 /// size one frame for the parameters and all of them. [`crate::frames`] is what counts it.
1131 pub locals: u32,
1132}
1133
1134impl PartialEq for Closure {
1135 /// Closures compare by identity of their code position. Two closures are never equal unless
1136 /// they came from the same lambda with the same captured frame, which is all any program
1137 /// should rely on.
1138 fn eq(&self, other: &Self) -> bool {
1139 self.params == other.params
1140 && self.body.span == other.body.span
1141 && self.env.frame == other.env.frame
1142 }
1143}
1144impl Eq for Closure {}
1145impl PartialOrd for Closure {
1146 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1147 Some(self.cmp(other))
1148 }
1149}
1150impl Ord for Closure {
1151 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1152 self.params
1153 .cmp(&other.params)
1154 .then_with(|| self.body.span.start.cmp(&other.body.span.start))
1155 }
1156}
1157
1158/// A lexical environment: a persistent chain of frames, so a closure can capture cheaply.
1159#[derive(Clone, Debug, Default, PartialEq, Eq)]
1160pub struct Env {
1161 /// `Arc<[T]>` rather than `Arc<Vec<T>>`: the second is two allocations and two hops to reach a
1162 /// binding.
1163 ///
1164 /// A call sizes this for the parameters **and** for every binding the body will make, so a
1165 /// `let` writes into a slot that is already there rather than allocating a scope of its own.
1166 /// The unwritten tail is filled with [`TOMBSTONE`], which no variable is named.
1167 frame: Arc<[(VarId, Value)]>,
1168 /// How much of `frame` holds a binding. Everything from here up is reserved and empty.
1169 used: u32,
1170 parent: Option<Arc<Env>>,
1171}
1172
1173impl Env {
1174 pub fn new() -> Env {
1175 Env::default()
1176 }
1177
1178 pub fn extend(&self, bindings: Vec<(VarId, Value)>) -> Env {
1179 Env {
1180 used: bindings.len() as u32,
1181 frame: bindings.into(),
1182 parent: Some(Arc::new(self.clone())),
1183 }
1184 }
1185
1186 /// The frame a call runs in: the parameters, then `locals` reserved slots for the bindings the
1187 /// body is going to make.
1188 ///
1189 /// One allocation. `Map<Range, _>` has a length the compiler can trust, so collecting it into
1190 /// an `Arc<[_]>` sizes the allocation once — which is why the parameters and the reserved tail
1191 /// are produced by one iterator rather than a vector that is then converted.
1192 pub fn call_frame(
1193 parent: &Arc<Env>,
1194 params: &[VarId],
1195 args: impl Iterator<Item = Value>,
1196 locals: u32,
1197 ) -> Env {
1198 let n = params.len();
1199 let mut given = args;
1200 let frame = (0..n + locals as usize)
1201 .map(|i| match given.next() {
1202 Some(v) => (params[i], v),
1203 None => (TOMBSTONE, Value::Unit),
1204 })
1205 .collect();
1206 Env {
1207 frame,
1208 used: n as u32,
1209 parent: Some(Arc::clone(parent)),
1210 }
1211 }
1212
1213 /// Bind `bindings` into this frame's reserved tail, if there is room for all of them and
1214 /// nobody else is holding the frame.
1215 ///
1216 /// Answers whether it did. `false` means the caller must fall back to [`Env::extend`] and
1217 /// chain a scope — which happens when a closure has captured this environment (its clone holds
1218 /// the frame, so `Arc::get_mut` refuses), when the reservation was too small, or when the
1219 /// program was built by something that never ran the reservation pass at all.
1220 ///
1221 /// The safety argument is the refusal: a closure that captured this environment can see the
1222 /// slots this would write, and `Arc::get_mut` is what proves that has not happened. Every
1223 /// binding gets a slot of its own, so nothing a closure captured is ever overwritten.
1224 pub fn put(&mut self, bindings: &mut Vec<(VarId, Value)>) -> bool {
1225 let at = self.used as usize;
1226 let n = bindings.len();
1227 if at + n > self.frame.len() {
1228 return false;
1229 }
1230 let Some(frame) = Arc::get_mut(&mut self.frame) else {
1231 return false;
1232 };
1233 for (i, b) in bindings.drain(..).enumerate() {
1234 frame[at + i] = b;
1235 }
1236 self.used = (at + n) as u32;
1237 true
1238 }
1239
1240 /// [`Env::put`] for a single binding, which is what a `let` is — and a `let` is much the most
1241 /// common of the two, so it does not build a vector to hand over. Answers the value back when
1242 /// there is no room for it.
1243 pub fn put_one(&mut self, var: VarId, value: Value) -> Result<(), Value> {
1244 let at = self.used as usize;
1245 if at >= self.frame.len() {
1246 return Err(value);
1247 }
1248 match Arc::get_mut(&mut self.frame) {
1249 Some(frame) => {
1250 frame[at] = (var, value);
1251 self.used += 1;
1252 Ok(())
1253 }
1254 None => Err(value),
1255 }
1256 }
1257
1258 /// The part of the frame that holds bindings, without the reserved tail.
1259 #[inline]
1260 fn bound(&self) -> &[(VarId, Value)] {
1261 &self.frame[..self.used as usize]
1262 }
1263
1264 /// Extend a parent that is **already** behind an `Arc` — which a closure's captured environment
1265 /// is, so that a call clones a pointer instead of boxing a copy of the environment.
1266 ///
1267 /// This is the per-call path. `extend` above is the per-`let` path, where the parent is owned by
1268 /// the evaluator's loop and has to be boxed; there are far fewer of those.
1269 pub fn extend_shared(parent: &Arc<Env>, frame: Arc<[(VarId, Value)]>) -> Env {
1270 Env {
1271 used: frame.len() as u32,
1272 frame,
1273 parent: Some(Arc::clone(parent)),
1274 }
1275 }
1276
1277 /// An environment with nothing in it, behind an `Arc`, shared by every top-level definition.
1278 pub fn empty_shared() -> Arc<Env> {
1279 Arc::new(Env::new())
1280 }
1281
1282 pub fn get(&self, v: VarId) -> Option<&Value> {
1283 let mut env = self;
1284 loop {
1285 if let Some((_, value)) = env.bound().iter().rev().find(|(id, _)| *id == v) {
1286 return Some(value);
1287 }
1288 match &env.parent {
1289 Some(p) => env = p,
1290 None => return None,
1291 }
1292 }
1293 }
1294
1295 /// Read `v`, and **move** it out of the frame when three things hold: the caller says no later
1296 /// evaluation reads it, this environment is the only holder of the frame it lives in, and the
1297 /// value is one whose copy costs something.
1298 ///
1299 /// The third condition is not an optimisation of an optimisation — it is what keeps the other
1300 /// two from costing more than they save. Moving is strictly more work than cloning at the point
1301 /// of the read: a clone of an `Int` is a copy of eight bytes and a clone of a container is one
1302 /// atomic increment, where a move has to find the slot, prove the frame is unshared and empty
1303 /// it. It pays only when somebody downstream can then *use* the sole ownership — which today is
1304 /// `list_append` pushing in place and `with` rebuilding a record's fields — and measuring it
1305 /// without this condition showed every benchmark in the tree 6–13% slower, because the reads
1306 /// that dominate a real program are of `Int`s and nothing was gained by moving one
1307 /// ([`69`](../../../../../docs/69-standard-library-imports-report.md) §69.7).
1308 ///
1309 /// The caller must have established that no later evaluation reads `v` — [`crate::liveness`]
1310 /// is what establishes it, and `last_use` is the flag. What this adds is the second half of the
1311 /// safety argument: a frame is emptied only when nothing else holds it, so an environment
1312 /// captured by a closure or shared with an inner scope is read from rather than emptied, and a
1313 /// caller that is wrong about liveness gets an unbound-variable error rather than somebody
1314 /// else's missing binding.
1315 pub fn read(&mut self, v: VarId, may_move: bool) -> Option<Value> {
1316 // The overwhelmingly common read is not a last use, and it needs none of the machinery
1317 // below: no scope on the way to the binding has to be proved unshared, because nothing is
1318 // going to be taken out of one. That proof costs **two atomic loads per scope level**
1319 // (`strong_count` and `weak_count`) plus an `Arc::get_mut`, and it was being paid on every
1320 // variable a program reads. `get` is a plain walk.
1321 if !may_move {
1322 return self.get(v).cloned();
1323 }
1324 let mut env = self;
1325 loop {
1326 if let Some(i) = env.bound().iter().rposition(|(id, _)| *id == v) {
1327 if may_move && worth_moving(&env.frame[i].1) {
1328 if let Some(frame) = Arc::get_mut(&mut env.frame) {
1329 // Tombstoned rather than removed: `Vec::remove` shifts every binding above
1330 // it, and this runs on the hottest path there is. The slot keeps its place
1331 // under a name no variable has, so a later read of `v` misses it and says
1332 // so instead of finding a neighbour.
1333 frame[i].0 = TOMBSTONE;
1334 return Some(std::mem::replace(&mut frame[i].1, Value::Unit));
1335 }
1336 }
1337 return Some(env.frame[i].1.clone());
1338 }
1339 let shared_parent = env
1340 .parent
1341 .as_ref()
1342 .is_some_and(|p| Arc::strong_count(p) > 1 || Arc::weak_count(p) > 0);
1343 if shared_parent {
1344 return env.parent.as_ref().and_then(|p| p.get(v)).cloned();
1345 }
1346 match env.parent.as_mut() {
1347 Some(p) => match Arc::get_mut(p) {
1348 Some(p) => env = p,
1349 None => return None,
1350 },
1351 None => return None,
1352 }
1353 }
1354 }
1355}
1356
1357/// Every subexpression of this one, to be rewritten in place.
1358///
1359/// The walk two passes over the finished program share — [`crate::frames`] and [`crate::fields`].
1360/// A lambda's body is behind an `Arc` because a closure shares it rather than copying it
1361/// (`docs/73`), so reaching into one is a `make_mut`: it runs once, on a program nothing else
1362/// holds yet, so nothing is actually cloned.
1363pub(crate) fn children_mut(c: &mut Core) -> Vec<&mut Core> {
1364 match &mut c.kind {
1365 CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => Vec::new(),
1366 CoreKind::Lam { body, .. } => vec![Arc::make_mut(body)],
1367 CoreKind::App { func, args } => std::iter::once(&mut **func).chain(args).collect(),
1368 CoreKind::Let { value, body, .. } => vec![&mut **value, &mut **body],
1369 CoreKind::If { cond, then, alt } => vec![&mut **cond, &mut **then, &mut **alt],
1370 CoreKind::Match { scrutinee, arms } => std::iter::once(&mut **scrutinee)
1371 .chain(arms.iter_mut().flat_map(|a| a.exprs_mut()))
1372 .collect(),
1373 CoreKind::Prim { args, .. } => args.iter_mut().collect(),
1374 CoreKind::Make { fields, .. } => fields.iter_mut().map(|(_, f)| f).collect(),
1375 CoreKind::Field { base, .. } => vec![&mut **base],
1376 CoreKind::With { base, fields } => std::iter::once(&mut **base)
1377 .chain(fields.iter_mut().map(|(_, f)| f))
1378 .collect(),
1379 CoreKind::ListLit(items) => items.iter_mut().collect(),
1380 CoreKind::MapLit(kvs) => kvs.iter_mut().flat_map(|(k, v)| [k, v]).collect(),
1381 }
1382}
1383
1384/// Are these the same field name?
1385///
1386/// Length, then first byte, then the rest. `str`'s own `==` checks the length and hands the bytes
1387/// to `memcmp`, and a `memcmp` call is dear next to what it decides here: field names are short,
1388/// there are three to eight of them in a record, and two that share a length almost never share a
1389/// first letter. Profiling `awfy/richards.beck` put 6% of the process inside `memcmp`, nearly all
1390/// of it deciding between `kind` and `link`.
1391#[inline]
1392fn same_name(a: &str, b: &str) -> bool {
1393 let (x, y) = (a.as_bytes(), b.as_bytes());
1394 x.len() == y.len() && (x.is_empty() || x[0] == y[0]) && x == y
1395}
1396
1397/// Order two field names, deciding on the first byte where it can.
1398///
1399/// `<[u8]>::cmp` calls `memcmp` over the common prefix before it looks at the lengths, so it pays
1400/// a call to distinguish `id` from `kind`. Sorting a record's fields is the other half of what
1401/// `same_name` documents.
1402#[inline]
1403pub(crate) fn cmp_name(a: &str, b: &str) -> std::cmp::Ordering {
1404 let (x, y) = (a.as_bytes(), b.as_bytes());
1405 match (x.first(), y.first()) {
1406 (Some(p), Some(q)) if p != q => p.cmp(q),
1407 _ => x.cmp(y),
1408 }
1409}
1410
1411/// The name a moved-out binding takes, which no variable has: `VarId`s are handed out from zero by
1412/// the checker, and a program with four billion of them has lost to `MAX_NESTING` long before.
1413const TOMBSTONE: VarId = VarId::MAX;
1414
1415/// Whether moving this value out of a frame can save anything downstream.
1416///
1417/// A `List` can be pushed into by `list_append`, a `Str` by `+`, and a record's fields can be
1418/// rebuilt in place by `with` — each only when nobody else holds them. Everything else is either a copy of a few bytes
1419/// or an atomic increment, and moving it costs more than it saves — `Env::read` is the measurement.
1420fn worth_moving(v: &Value) -> bool {
1421 matches!(v, Value::List(_) | Value::Str(_) | Value::Data(_))
1422}
1423
1424/// The monotone `f64` → `u64` transform: for a non-negative float flip the sign bit, for a
1425/// negative one invert every bit. `a < b` as reals iff `order_key(a) < order_key(b)` as integers,
1426/// with `-inf` at the bottom and NaN above `+inf`.
1427const SIGN: u64 = 1 << 63;
1428
1429fn order_key(f: f64) -> u64 {
1430 let bits = f.to_bits();
1431 if bits & SIGN != 0 {
1432 !bits
1433 } else {
1434 bits ^ SIGN
1435 }
1436}
1437
1438fn from_order_key(key: u64) -> f64 {
1439 f64::from_bits(if key & SIGN != 0 { key ^ SIGN } else { !key })
1440}
1441
1442impl Value {
1443 /// Make a real, canonicalising the two IEEE values that would otherwise break `Eq`.
1444 ///
1445 /// `-0.0` becomes `0.0` and every NaN becomes one NaN, because [`Value`] is `Eq` and `Ord` and
1446 /// a fold's accumulator is compared, hashed and used as a map key. IEEE 754 says `NaN != NaN`
1447 /// and `-0.0 == 0.0`; both are irreconcilable with a total order, and the total order is the
1448 /// one §3.7 needs. So Beck's `==` on reals is *structural*, and `docs/32` §32.2 says so where
1449 /// somebody porting numeric code will read it.
1450 pub fn float(f: f64) -> Value {
1451 let f = if f.is_nan() {
1452 f64::NAN
1453 } else if f == 0.0 {
1454 0.0
1455 } else {
1456 f
1457 };
1458 Value::Float(order_key(f))
1459 }
1460
1461 pub fn as_f64(&self) -> Option<f64> {
1462 match self {
1463 Value::Float(key) => Some(from_order_key(*key)),
1464 _ => None,
1465 }
1466 }
1467
1468 pub fn str_(s: impl AsRef<str>) -> Value {
1469 Value::Str(Arc::new(Text::from(s.as_ref())))
1470 }
1471
1472 /// The same from a `String` that is already owned, which is most of the string primitives:
1473 /// they build one and would otherwise copy it again on the way in.
1474 pub fn text(s: String) -> Value {
1475 Value::Str(Arc::new(Text::new(s)))
1476 }
1477
1478 pub fn as_str(&self) -> Option<&str> {
1479 match self {
1480 Value::Str(s) => Some(s),
1481 _ => None,
1482 }
1483 }
1484
1485 pub fn as_int(&self) -> Option<i64> {
1486 match self {
1487 Value::Int(i) => Some(*i),
1488 _ => None,
1489 }
1490 }
1491
1492 pub fn as_bool(&self) -> Option<bool> {
1493 match self {
1494 Value::Bool(b) => Some(*b),
1495 _ => None,
1496 }
1497 }
1498
1499 pub fn as_list(&self) -> Option<&Vec<Value>> {
1500 match self {
1501 Value::List(xs) => Some(xs),
1502 _ => None,
1503 }
1504 }
1505
1506 pub fn as_map(&self) -> Option<&PMap<Value, Value>> {
1507 match self {
1508 Value::Map(m) => Some(m),
1509 _ => None,
1510 }
1511 }
1512
1513 pub fn as_html(&self) -> Option<&Html> {
1514 match self {
1515 Value::Html(h) => Some(h),
1516 _ => None,
1517 }
1518 }
1519
1520 /// Build a record or a union variant. The one constructor, so that the `Arc` and the map are
1521 /// allocated in one place rather than at every call site.
1522 pub fn data(ty: impl Into<Arc<str>>, variant: Option<Arc<str>>, fields: Fields) -> Value {
1523 Value::Data(Arc::new(Record {
1524 ty: ty.into(),
1525 variant,
1526 fields,
1527 }))
1528 }
1529
1530 /// The same from a list of pairs, which is how most call sites have them.
1531 pub fn record<const N: usize>(
1532 ty: impl Into<Arc<str>>,
1533 variant: Option<&str>,
1534 fields: [(&str, Value); N],
1535 ) -> Value {
1536 Value::data(
1537 ty,
1538 variant.map(Arc::from),
1539 fields.into_iter().map(|(k, v)| (Arc::from(k), v)).collect(),
1540 )
1541 }
1542
1543 pub fn field(&self, name: &str) -> Option<&Value> {
1544 match self {
1545 Value::Data(d) => d.fields.get(name),
1546 _ => None,
1547 }
1548 }
1549
1550 pub fn variant(&self) -> Option<&str> {
1551 match self {
1552 Value::Data(d) => d.variant.as_deref(),
1553 _ => None,
1554 }
1555 }
1556
1557 pub fn some(v: Value) -> Value {
1558 Value::record(Ty::OPTION, Some("Some"), [("value", v)])
1559 }
1560
1561 pub fn none() -> Value {
1562 Value::record(Ty::OPTION, Some("None"), [])
1563 }
1564
1565 pub fn ok(v: Value) -> Value {
1566 Value::record(Ty::RESULT, Some("Ok"), [("value", v)])
1567 }
1568
1569 pub fn err(v: Value) -> Value {
1570 Value::record(Ty::RESULT, Some("Err"), [("error", v)])
1571 }
1572
1573 /// How `str(x)` renders a value, and how a value becomes a `Map` key's printed form.
1574 pub fn display(&self) -> String {
1575 match self {
1576 Value::Unit => "unit".into(),
1577 Value::Bool(b) => b.to_string(),
1578 Value::Int(i) => i.to_string(),
1579 Value::Float(_) => format!("{}", self.as_f64().unwrap_or(0.0)),
1580 Value::Str(s) => s.to_string(),
1581 Value::List(xs) => {
1582 let parts: Vec<String> = xs.iter().map(Value::display).collect();
1583 format!("[{}]", parts.join(", "))
1584 }
1585 Value::Map(m) => {
1586 let parts: Vec<String> = m
1587 .iter()
1588 .map(|(k, v)| format!("{}: {}", k.display(), v.display()))
1589 .collect();
1590 format!("{{{}}}", parts.join(", "))
1591 }
1592 Value::Data(d) => {
1593 // A newtype wrapping one field prints as that field — `Id(uuid)` reads as the uuid,
1594 // which is what a key attribute and a rendered list want.
1595 if d.variant.is_none() && d.fields.len() == 1 {
1596 if let Some(v) = d.fields.values().next() {
1597 return v.display();
1598 }
1599 }
1600 let name = d.variant.as_deref().unwrap_or(&d.ty);
1601 if d.fields.is_empty() {
1602 return name.to_string();
1603 }
1604 let parts: Vec<String> = d
1605 .fields
1606 .iter()
1607 .map(|(k, v)| format!("{k}: {}", v.display()))
1608 .collect();
1609 format!("{name}{{{}}}", parts.join(", "))
1610 }
1611 Value::Html(h) => h.render(),
1612 Value::Attr(_) => "<attr>".into(),
1613 Value::Closure(_) => "<fn>".into(),
1614 }
1615 }
1616
1617 /// The wire form, used for command payloads carried by handlers and for the log.
1618 pub fn to_json(&self) -> serde_json::Value {
1619 use serde_json::{Map as JMap, Value as J};
1620 match self {
1621 Value::Unit => J::Null,
1622 Value::Bool(b) => J::Bool(*b),
1623 Value::Int(i) => J::Number((*i).into()),
1624 Value::Float(_) => serde_json::Number::from_f64(self.as_f64().unwrap_or(0.0))
1625 .map(J::Number)
1626 .unwrap_or(J::Null),
1627 Value::Str(s) => J::String(s.to_string()),
1628 Value::List(xs) => J::Array(xs.iter().map(Value::to_json).collect()),
1629 Value::Map(m) => {
1630 let mut obj = JMap::new();
1631 for (k, v) in m.iter() {
1632 obj.insert(k.display(), v.to_json());
1633 }
1634 J::Object(obj)
1635 }
1636 Value::Data(d) => {
1637 if d.variant.is_none() && d.fields.len() == 1 {
1638 if let Some(v) = d.fields.values().next() {
1639 return v.to_json();
1640 }
1641 }
1642 let mut obj = JMap::new();
1643 if let Some(v) = &d.variant {
1644 obj.insert("c".into(), J::String(v.to_string()));
1645 }
1646 for (k, v) in d.fields.iter() {
1647 obj.insert(k.to_string(), v.to_json());
1648 }
1649 J::Object(obj)
1650 }
1651 Value::Html(h) => h.to_wire(),
1652 Value::Attr(_) | Value::Closure(_) => J::Null,
1653 }
1654 }
1655}
1656
1657impl fmt::Display for Value {
1658 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1659 f.write_str(&self.display())
1660 }
1661}
1662
1663/// A value that cannot be written to a log.
1664///
1665/// [`Value`] has four consumers with different requirements — the evaluator, the log, the wire, and
1666/// the digest — and three of its variants exist only for the first. Encoding one of those is not a
1667/// value to be lowered; it is a program that should not have compiled.
1668#[derive(Clone, Debug, PartialEq, Eq)]
1669pub struct NotStorable {
1670 /// Which variant, by name.
1671 pub kind: &'static str,
1672}
1673
1674impl fmt::Display for NotStorable {
1675 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1676 write!(
1677 f,
1678 "a {} cannot be written to the log: it is {}, not data",
1679 self.kind,
1680 match self.kind {
1681 "closure" => "code",
1682 "attribute" => "part of a view",
1683 _ => "a view",
1684 }
1685 )
1686 }
1687}
1688
1689impl std::error::Error for NotStorable {}
1690
1691/// A lossless encoding of a [`Value`], for the log and for snapshots.
1692///
1693/// [`Value::to_json`] is the *wire* form: it drops the type name of a record and unwraps a
1694/// newtype, because that is what a browser wants. The log needs the opposite — a record that can
1695/// be read back as exactly the value that was written, because replay compares digests. Hence two
1696/// encodings, and a test that says why.
1697///
1698/// # Why this returns a `Result`
1699///
1700/// It used to encode `Html`, `Attr` and `Closure` as `unit` on the grounds that "neither can appear
1701/// in a log". That grounds was an assumption, not a check: nothing stops a program declaring
1702/// `model State: cached: Html`, and the encoding would then write `unit` into the *durable* path,
1703/// silently, and replay would rebuild a different state. A system whose correctness argument is
1704/// "replay is exact" cannot have a lossy branch in the function that makes the log.
1705///
1706/// Until placement can prove such a type never reaches `durable` (Phase 2's effect rows), refusing
1707/// at the boundary is the honest position: the append fails, the process aborts by the same rule as
1708/// any other failed append (§18.5 item 6), and nothing unreadable is ever committed.
1709pub fn value_to_repr(v: &Value) -> Result<serde_json::Value, NotStorable> {
1710 use serde_json::{json, Map as JMap, Value as J};
1711 Ok(match v {
1712 Value::Unit => json!({"$": "unit"}),
1713 Value::Bool(b) => json!({"$": "bool", "v": b}),
1714 Value::Int(i) => json!({"$": "int", "v": i}),
1715 Value::Float(bits) => json!({"$": "float", "v": bits.to_string()}),
1716 Value::Str(s) => json!({"$": "str", "v": s.as_str()}),
1717 Value::List(xs) => {
1718 let items: Result<Vec<_>, _> = xs.iter().map(value_to_repr).collect();
1719 json!({"$": "list", "v": items?})
1720 }
1721 Value::Map(m) => {
1722 let mut pairs = Vec::with_capacity(m.len());
1723 for (k, val) in m.iter() {
1724 pairs.push(json!([value_to_repr(k)?, value_to_repr(val)?]));
1725 }
1726 json!({"$": "map", "v": pairs})
1727 }
1728 Value::Data(d) => {
1729 let mut f = JMap::new();
1730 for (k, val) in d.fields.iter() {
1731 f.insert(k.to_string(), value_to_repr(val)?);
1732 }
1733 json!({
1734 "$": "data",
1735 "t": d.ty.as_ref(),
1736 "c": d.variant.as_deref(),
1737 "f": J::Object(f)
1738 })
1739 }
1740 Value::Html(_) => return Err(NotStorable { kind: "view" }),
1741 Value::Attr(_) => return Err(NotStorable { kind: "attribute" }),
1742 Value::Closure(_) => return Err(NotStorable { kind: "closure" }),
1743 })
1744}
1745
1746pub fn value_from_repr(j: &serde_json::Value) -> Option<Value> {
1747 let tag = j.get("$")?.as_str()?;
1748 Some(match tag {
1749 "unit" => Value::Unit,
1750 "bool" => Value::Bool(j.get("v")?.as_bool()?),
1751 "int" => Value::Int(j.get("v")?.as_i64()?),
1752 "float" => Value::Float(j.get("v")?.as_str()?.parse().ok()?),
1753 "str" => Value::str_(j.get("v")?.as_str()?),
1754 "list" => Value::List(Arc::new(
1755 j.get("v")?
1756 .as_array()?
1757 .iter()
1758 .map(value_from_repr)
1759 .collect::<Option<Vec<_>>>()?,
1760 )),
1761 "map" => {
1762 let mut m = PMap::new();
1763 for pair in j.get("v")?.as_array()? {
1764 let pair = pair.as_array()?;
1765 m = m.insert(
1766 value_from_repr(pair.first()?)?,
1767 value_from_repr(pair.get(1)?)?,
1768 );
1769 }
1770 Value::Map(m)
1771 }
1772 "data" => {
1773 let mut fields = Fields::new();
1774 for (k, val) in j.get("f")?.as_object()? {
1775 fields.insert(Arc::from(k.as_str()), value_from_repr(val)?);
1776 }
1777 Value::data(
1778 Arc::from(j.get("t")?.as_str()?),
1779 j.get("c").and_then(|c| c.as_str()).map(Arc::from),
1780 fields,
1781 )
1782 }
1783 _ => return None,
1784 })
1785}
1786
1787/// Structural digest of a value — the replay-determinism oracle (§4.8).
1788///
1789/// A property of the *value*, not of whoever produced it: two backends that disagree are
1790/// detected by comparing digests, so the digest cannot live in either of them.
1791pub fn digest(v: &Value) -> [u8; 32] {
1792 let mut hasher = blake3::Hasher::new();
1793 hash_into(v, &mut hasher);
1794 *hasher.finalize().as_bytes()
1795}
1796
1797fn hash_into(v: &Value, h: &mut blake3::Hasher) {
1798 match v {
1799 Value::Unit => h.update(&[0]),
1800 Value::Bool(b) => h.update(&[1, *b as u8]),
1801 Value::Int(i) => {
1802 h.update(&[2]);
1803 h.update(&i.to_le_bytes())
1804 }
1805 Value::Float(bits) => {
1806 h.update(&[3]);
1807 h.update(&bits.to_le_bytes())
1808 }
1809 Value::Str(s) => {
1810 h.update(&[4]);
1811 h.update(&(s.len() as u64).to_le_bytes());
1812 h.update(s.as_bytes())
1813 }
1814 Value::List(xs) => {
1815 h.update(&[5]);
1816 h.update(&(xs.len() as u64).to_le_bytes());
1817 for x in xs.iter() {
1818 hash_into(x, h);
1819 }
1820 h
1821 }
1822 Value::Map(m) => {
1823 h.update(&[6]);
1824 h.update(&(m.len() as u64).to_le_bytes());
1825 for (k, val) in m.iter() {
1826 hash_into(k, h);
1827 hash_into(val, h);
1828 }
1829 h
1830 }
1831 Value::Data(d) => {
1832 h.update(&[7]);
1833 h.update(d.ty.as_bytes());
1834 h.update(d.variant.as_deref().unwrap_or("").as_bytes());
1835 h.update(&(d.fields.len() as u64).to_le_bytes());
1836 for (k, val) in d.fields.iter() {
1837 h.update(k.as_bytes());
1838 hash_into(val, h);
1839 }
1840 h
1841 }
1842 Value::Html(html) => {
1843 h.update(&[8]);
1844 h.update(html.render().as_bytes())
1845 }
1846 Value::Attr(_) => h.update(&[9]),
1847 Value::Closure(_) => h.update(&[10]),
1848 };
1849}
1850
1851#[cfg(test)]
1852mod tests {
1853 use super::*;
1854
1855 /// The property `Value::Float`'s representation exists for: the order the fold uses and the
1856 /// order arithmetic uses are one order.
1857 ///
1858 /// `f64::to_bits` does not have it — `(-1.0).to_bits()` is larger than `(1.0).to_bits()`
1859 /// because the sign bit is the top one — and that is the defect docs/32 §32.2 records. This is
1860 /// the test that would have caught it, checked across the sign, the zeroes and the infinities
1861 /// rather than on one example.
1862 #[test]
1863 fn reals_compare_as_reals_and_round_trip_through_their_key() {
1864 let ladder = [
1865 f64::NEG_INFINITY,
1866 -1e308,
1867 -1.5,
1868 -1.0,
1869 -f64::MIN_POSITIVE,
1870 0.0,
1871 f64::MIN_POSITIVE,
1872 1.0,
1873 1.5,
1874 1e308,
1875 f64::INFINITY,
1876 ];
1877 for w in ladder.windows(2) {
1878 let (a, b) = (Value::float(w[0]), Value::float(w[1]));
1879 assert!(a < b, "{} should order below {}", w[0], w[1]);
1880 assert_eq!(a.as_f64(), Some(w[0]), "and survive the round trip");
1881 }
1882
1883 // The two IEEE values that would otherwise break `Eq`, canonicalised.
1884 assert_eq!(
1885 Value::float(-0.0),
1886 Value::float(0.0),
1887 "`-0.0` and `0.0` are one value, because `Ord` cannot have two of them"
1888 );
1889 assert_eq!(
1890 Value::float(f64::NAN),
1891 Value::float(-f64::NAN),
1892 "and every NaN is one NaN — including a negative one — for the same reason"
1893 );
1894 assert!(
1895 Value::float(f64::NAN) > Value::float(f64::INFINITY),
1896 "NaN has to go somewhere, and above every number is somewhere"
1897 );
1898
1899 // The digest is a function of the value, and a different real is a different digest.
1900 assert_eq!(digest(&Value::float(1.5)), digest(&Value::float(1.5)));
1901 assert_ne!(digest(&Value::float(1.5)), digest(&Value::float(-1.5)));
1902 }
1903
1904 #[test]
1905 fn the_log_encoding_round_trips_exactly() {
1906 // The wire encoding deliberately loses information the browser does not need; the log
1907 // encoding cannot, because replay compares digests of what it reads back.
1908 let v = Value::data(
1909 Arc::from("State"),
1910 None,
1911 Fields::from_iter([(
1912 Arc::from("todos"),
1913 Value::Map(PMap::from_iter([(
1914 Value::str_("k"),
1915 Value::data(
1916 Arc::from("Todo"),
1917 None,
1918 Fields::from_iter([
1919 (Arc::from("done"), Value::Bool(true)),
1920 (Arc::from("n"), Value::Int(-3)),
1921 ]),
1922 ),
1923 )])),
1924 )]),
1925 );
1926 assert_eq!(
1927 value_from_repr(&value_to_repr(&v).unwrap()),
1928 Some(v.clone())
1929 );
1930 let evt = Value::data(
1931 Arc::from("Event"),
1932 Some(Arc::from("Toggled")),
1933 Fields::from_iter([(Arc::from("id"), Value::str_("x"))]),
1934 );
1935 assert_eq!(value_from_repr(&value_to_repr(&evt).unwrap()), Some(evt));
1936 }
1937
1938 #[test]
1939 fn a_value_the_log_cannot_hold_is_refused_rather_than_flattened() {
1940 // This used to encode as `unit`, on the assumption that a view never reaches the log.
1941 // Nothing checked the assumption: `model State: cached: Html` compiles today, and the
1942 // durable path would have written `unit` and replayed a different state — silently, in the
1943 // one place this system's correctness argument does not permit silence.
1944 let view = Value::Html(Arc::new(crate::html::Html::text("hello")));
1945 let err = value_to_repr(&view).expect_err("a view is not data");
1946 assert!(
1947 err.to_string().contains("cannot be written to the log"),
1948 "{err}"
1949 );
1950
1951 // …and nesting does not launder it: a record holding one is refused too.
1952 let state = Value::data(
1953 Arc::from("State"),
1954 None,
1955 Fields::from_iter([(Arc::from("cached"), view.clone())]),
1956 );
1957 assert!(
1958 value_to_repr(&state).is_err(),
1959 "a record holding a view is not data"
1960 );
1961 assert!(
1962 value_to_repr(&Value::List(Arc::new(vec![view.clone()]))).is_err(),
1963 "a list holding a view is not data"
1964 );
1965 assert!(
1966 value_to_repr(&Value::Map(PMap::new().insert(Value::str_("k"), view))).is_err(),
1967 "a map holding a view is not data"
1968 );
1969 }
1970
1971 #[test]
1972 fn maps_order_by_key_so_rendering_is_deterministic() {
1973 let m = PMap::new()
1974 .insert(Value::str_("b"), Value::Int(2))
1975 .insert(Value::str_("a"), Value::Int(1));
1976 let v = Value::Map(m);
1977 assert_eq!(v.display(), "{a: 1, b: 2}");
1978 }
1979
1980 #[test]
1981 fn a_newtype_renders_as_its_payload() {
1982 let id = Value::data(
1983 Arc::from("Id"),
1984 None,
1985 Fields::from_iter([(Arc::from("value"), Value::str_("u-1"))]),
1986 );
1987 assert_eq!(id.display(), "u-1");
1988 assert_eq!(id.to_json(), serde_json::json!("u-1"));
1989 }
1990
1991 #[test]
1992 fn a_command_serialises_with_its_variant_tag() {
1993 let cmd = Value::data(
1994 Arc::from("Command"),
1995 Some(Arc::from("Toggle")),
1996 Fields::from_iter([(Arc::from("id"), Value::str_("x"))]),
1997 );
1998 assert_eq!(cmd.to_json(), serde_json::json!({"c": "Toggle", "id": "x"}));
1999 }
2000
2001 #[test]
2002 fn environments_shadow_innermost_first() {
2003 let base = Env::new().extend(vec![(1, Value::Int(1))]);
2004 let inner = base.extend(vec![(1, Value::Int(2))]);
2005 assert_eq!(inner.get(1), Some(&Value::Int(2)));
2006 assert_eq!(base.get(1), Some(&Value::Int(1)));
2007 assert_eq!(inner.get(9), None);
2008 }
2009
2010 #[test]
2011 fn only_the_impure_primitives_carry_atoms_of_their_own() {
2012 assert_eq!(Prim::MergeClients.effects(), vec![Effect::Ingress]);
2013 assert_eq!(Prim::Durable.effects(), vec![Effect::Durable]);
2014 assert_eq!(Prim::NewUuid.effects(), vec![Effect::Nondet]);
2015 assert_eq!(Prim::Now.effects(), vec![Effect::Nondet]);
2016 assert_eq!(Prim::SecretEnv.effects(), vec![Effect::Env]);
2017 assert!(Prim::Add.effects().is_empty());
2018 assert!(
2019 Prim::Fold.effects().is_empty(),
2020 "a fold is pure; `durable` is the effect"
2021 );
2022 assert!(
2023 Prim::MapList.effects().is_empty(),
2024 "`map_list` performs nothing of its own — it performs its argument's row, which is a \
2025 variable and lives in the scheme"
2026 );
2027 }
2028}