Skip to main content

qcode/
types.rs

1//! First-class type system for qcode IR values.
2//!
3//! Every value (literal, instruction result, block param) carries a [`TypeId`]
4//! that encodes both its size and its semantic kind. Types are interned once in
5//! a [`TypeManager`] attached to the [`Context`](crate::context::Context); all passes use [`TypeId`] as
6//! a lightweight `Copy` handle.
7//!
8//! # Type taxonomy
9//!
10//! | Concrete type    | Meaning                                        |
11//! |------------------|------------------------------------------------|
12//! | `IntType`      | Plain integer of *n* bytes                     |
13//! | `BoolType`     | A byte-stored boolean, domain `{0, 1}`         |
14//! | `StackAddress` | Pointer-width address in the stack memory space |
15//!
16//! # Bool
17//!
18//! `bool` is its own type (byte-stored, `size() == 1`) minted only by
19//! comparisons and the `true`/`false` literals. The verifier pins its domain to
20//! `{0, 1}` and rejects mixing `bool` with `iN` in a binop, so bitwise
21//! `And`/`Or`/`Xor` over `bool` operands *is* logical and/or/xor.
22//!
23//! # TODO
24//!
25//! - Pointer types for RAM/register spaces.
26
27use std::sync::{
28    Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard,
29    atomic::{AtomicPtr, Ordering},
30};
31
32use rustc_hash::FxHashMap as HashMap;
33
34use crate::{
35    space::MemorySpaceId,
36    value::{
37        FunctionId,
38        insn::{Binop, IntBinop},
39    },
40};
41
42// ---------------------------------------------------------------------------
43// TypeId
44// ---------------------------------------------------------------------------
45
46/// A lightweight handle to a type registered in [`TypeManager`].
47///
48/// `TypeId` is `Copy + Hash + Eq` and carries no context borrow. Convert to a
49/// concrete [`Type`] via [`TypeManager::get`].
50#[derive(
51    Copy, Clone, Hash, Eq, PartialEq, Debug, Ord, PartialOrd, serde::Serialize, serde::Deserialize,
52)]
53pub struct TypeId(u32);
54
55/// A structural type a function pass needs published before it can rewrite its
56/// body. Requests are returned to the pipeline driver and created at the module
57/// barrier; the requesting pass is then rerun against the new publication.
58///
59/// This avoids temporary/sentinel [`TypeId`] values in IR and keeps assignment
60/// deterministic under parallel function passes.
61#[derive(Clone, Debug, PartialEq, Eq, Hash)]
62pub enum TypeRequest {
63    Aggregate { fields: Vec<AggregateField> },
64    StructPointer { size: usize, pointee: TypeId },
65    Array { elem: TypeId, count: usize },
66    List { elem: TypeId, bound: Option<usize> },
67}
68
69impl TypeRequest {
70    pub fn aggregate(fields: Vec<AggregateField>) -> Self {
71        Self::Aggregate { fields }
72    }
73
74    pub const fn struct_pointer(size: usize, pointee: TypeId) -> Self {
75        Self::StructPointer { size, pointee }
76    }
77
78    pub const fn array(elem: TypeId, count: usize) -> Self {
79        Self::Array { elem, count }
80    }
81
82    pub const fn list(elem: TypeId, bound: Option<usize>) -> Self {
83        Self::List { elem, bound }
84    }
85}
86
87// ---------------------------------------------------------------------------
88// Type trait
89// ---------------------------------------------------------------------------
90
91pub trait Type: Send + Sync {
92    /// Width of values of this type in bytes.
93    fn size(&self) -> usize;
94
95    /// The memory space this type lives in, if it is a pointer type.
96    fn space(&self) -> Option<MemorySpaceId> {
97        None
98    }
99
100    /// The ordered field types, if this is an `AggregateType` or nominal
101    /// `StructType`.
102    fn fields(&self) -> Option<&[AggregateField]> {
103        None
104    }
105
106    /// The name of this type, if it is a nominal `StructType`.
107    fn struct_name(&self) -> Option<&str> {
108        None
109    }
110
111    /// Owning function when this is its unique, editable return-record type.
112    fn function_return_owner(&self) -> Option<FunctionId> {
113        None
114    }
115
116    /// The pointee type, if this is a `StructPointer`.
117    fn pointee(&self) -> Option<TypeId> {
118        None
119    }
120
121    /// The `(elem, count)` pair, if this is an `ArrayType`. Returns `None` for
122    /// every other type — this is the *only* discriminator element-aware code
123    /// uses to tell an array from the width-N scalar it otherwise looks like.
124    fn array(&self) -> Option<(TypeId, usize)> {
125        None
126    }
127
128    /// The `(elem, bound)` pair, if this is a `ListType` — a variable-length
129    /// sequence whose `bound` is the static element upper bound (`Some(n)`) or
130    /// `None` when unbounded (a pointer-sourced string). Returns `None` (the outer
131    /// option) for every non-list type. This is the discriminator that tells a
132    /// *list* (`take_while`'s result) from a fixed-length [`array`](Type::array):
133    /// both look like width-N scalars structurally, but a list's length is not
134    /// statically known.
135    fn list(&self) -> Option<(TypeId, Option<usize>)> {
136        None
137    }
138
139    /// Clones this type into a fresh boxed trait object.
140    ///
141    /// This enables `Clone for Box<dyn Type>` (and hence `Clone` for
142    /// [`TypeManager`] and [`Context`](crate::context::Context)), which the GUI
143    /// relies on to fork a context before running an analysis pipeline.
144    fn clone_box(&self) -> Box<dyn Type>;
145
146    /// Describes this type in a flat, serializable form.
147    ///
148    /// Used to persist the [`TypeManager`] across a saved session: trait objects
149    /// cannot be serialized directly, so each type reports a [`TypeRepr`] from
150    /// which it can be reconstructed.
151    fn repr(&self) -> TypeRepr;
152}
153
154/// Serializable description of a concrete [`Type`].
155///
156/// There are only three concrete types, each fully described by a byte width and
157/// (for pointers) the memory space it points into. [`TypeManager`] serializes its
158/// type table as a `Vec<TypeRepr>` and replays the `get_or_make_*` constructors
159/// on load, which reproduces both the interned [`TypeId`] indices and the lookup
160/// maps exactly.
161#[derive(Clone, serde::Serialize, serde::Deserialize)]
162pub enum TypeRepr {
163    Int {
164        size: usize,
165    },
166    /// A byte-stored boolean whose value domain is `{0, 1}`. Minted only by
167    /// comparisons and the `true`/`false` literals; `size()` is always 1.
168    Bool,
169    SpaceAddress {
170        size: usize,
171        space: MemorySpaceId,
172    },
173    /// A fixed, ordered group of named field types — the functional-IR
174    /// representation of a tuple. Used by `argpromote` to return
175    /// `(real_return, write-set)`. Abstract: it has no physical return-register
176    /// ABI.
177    Aggregate {
178        fields: Vec<AggregateField>,
179    },
180    /// A named, nominal struct with explicit per-field byte offsets — the
181    /// pointee of a `StructPointer`. Identity is the `name`, not the field
182    /// list, so two structs with coincident layouts stay distinct. Sparse: only
183    /// the fields of interest are listed; `size` is the real struct size and
184    /// need not equal the fields' extent.
185    Struct {
186        name: String,
187        size: usize,
188        fields: Vec<AggregateField>,
189    },
190    /// A pointer to a nominal [`Struct`](TypeRepr::Struct) (or any other type),
191    /// of the given byte width. `pointee` is the [`TypeId`] it points at.
192    StructPointer {
193        size: usize,
194        pointee: TypeId,
195    },
196    /// A fixed-length homogeneous array of `count` elements of type `elem`,
197    /// laid out contiguously. Its byte width is `count * sizeof(elem)`.
198    ///
199    /// Deliberately **disguised as a width-N scalar**: it answers
200    /// [`Type::size`] like any integer and does *not* expose [`Type::fields`],
201    /// so structural passes (mem2reg, alias, DCE, GVN value-numbering) handle it
202    /// unchanged. Only element-aware sites (`argpromote`, the `Extract`/`Range`
203    /// over `Map` rewrite, emulation) consult [`Type::array`]. Lane projection is
204    /// defined as a contiguous bit-slice: `Extract(arr, k) ≡ Range(arr,
205    /// k*sizeof(elem), sizeof(elem))`.
206    Array {
207        elem: TypeId,
208        count: usize,
209    },
210    /// A variable-length homogeneous sequence of `elem` — the result of
211    /// [`take_while`](crate::intrinsics). `bound` is the static storage upper bound
212    /// in elements (`Some(n)` for a `take_while` over a fixed `[T; n]` array), or
213    /// `None` when the source is an unbounded pointer (a `char*` string of unknown
214    /// length). Like [`Array`](TypeRepr::Array) a *bounded* list is disguised as a
215    /// width-N scalar (its `size` is the `bound` footprint); an *unbounded* list has
216    /// no materialized footprint (`size` 0) — it is a handle consumed only by
217    /// `len`/`map`, never stored. Only [`Type::list`] tells either apart from a
218    /// fixed array; the runtime length is the position of the first failing element.
219    List {
220        elem: TypeId,
221        bound: Option<usize>,
222    },
223    /// A function-owned return record. Unlike structural [`Aggregate`](Self::Aggregate)
224    /// values, identity belongs to `owner`: two functions with identical fields
225    /// still have distinct types, and the owner may revise the fields later while
226    /// preserving the same [`TypeId`]. Kept last to preserve the existing bincode
227    /// discriminants of previously persisted type variants.
228    FunctionReturn {
229        owner: FunctionId,
230        fields: Vec<AggregateField>,
231    },
232    /// A pointer to *code* — a function/callable address, of the given byte
233    /// width. Minted by the `infer_code_pointers` pass for a value used as the
234    /// target of an indirect call. Deliberately structureless (no signature yet):
235    /// it marks "this scalar is a code address," enough to drive call-target
236    /// typing and (future) resolution/exploration. Kept last to preserve the
237    /// bincode discriminants of previously persisted variants.
238    CodePointer {
239        size: usize,
240    },
241}
242
243/// One field of an aggregate or struct type.
244///
245/// Field names are part of aggregate identity. For structural aggregates the
246/// slots are addressed by numeric index and `offset` is informational (the
247/// running byte sum); for nominal `StructType`s `offset` is the field's real
248/// byte offset and is the key a [`Gep`](crate::value::insn::Gep) resolves on.
249#[derive(Clone, Debug, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
250pub struct AggregateField {
251    pub name: String,
252    pub type_id: TypeId,
253    /// Byte offset of this field within its containing aggregate/struct.
254    pub offset: usize,
255}
256
257impl AggregateField {
258    /// Field with offset `0`. Used by structural aggregates, where the slot is
259    /// addressed by index and the offset is not consulted.
260    pub fn new(name: impl Into<String>, type_id: TypeId) -> Self {
261        Self::new_at(name, type_id, 0)
262    }
263
264    /// Field at an explicit byte `offset`. Used by nominal `StructType`s.
265    pub fn new_at(name: impl Into<String>, type_id: TypeId, offset: usize) -> Self {
266        Self {
267            name: name.into(),
268            type_id,
269            offset,
270        }
271    }
272}
273
274impl Clone for Box<dyn Type> {
275    fn clone(&self) -> Self {
276        self.clone_box()
277    }
278}
279
280// ---------------------------------------------------------------------------
281// Concrete types
282// ---------------------------------------------------------------------------
283
284#[derive(Clone)]
285struct IntType {
286    size: usize,
287}
288
289impl Type for IntType {
290    fn size(&self) -> usize {
291        self.size
292    }
293
294    fn clone_box(&self) -> Box<dyn Type> {
295        Box::new(self.clone())
296    }
297
298    fn repr(&self) -> TypeRepr {
299        TypeRepr::Int { size: self.size }
300    }
301}
302
303/// A byte-stored boolean, value domain `{0, 1}`. `size()` is always 1 (the type
304/// system is byte-granular). Distinct from `Int(1)` so the verifier can reject
305/// `bool`/`iN` mixing and so bitwise ops over `bool` read as logical and/or/xor.
306#[derive(Clone)]
307struct BoolType;
308
309impl Type for BoolType {
310    fn size(&self) -> usize {
311        1
312    }
313
314    fn clone_box(&self) -> Box<dyn Type> {
315        Box::new(self.clone())
316    }
317
318    fn repr(&self) -> TypeRepr {
319        TypeRepr::Bool
320    }
321}
322
323/// A pointer-typed value carrying the memory space it points into.
324///
325/// This represents arbitrary space provenance — e.g. the result of `&A + offset`,
326/// which points into the space
327/// of varnode `A`. It is the type-system encoding of the address-space tag that
328/// pointer-producing instructions used to carry as a separate field. Its byte
329/// width is the producing instruction's width (not necessarily the space's
330/// address size), matching the operand-derived size of pointer arithmetic.
331#[derive(Clone)]
332pub struct SpaceAddress {
333    size: usize,
334    space: MemorySpaceId,
335}
336
337impl Type for SpaceAddress {
338    fn size(&self) -> usize {
339        self.size
340    }
341
342    fn space(&self) -> Option<MemorySpaceId> {
343        Some(self.space)
344    }
345
346    fn clone_box(&self) -> Box<dyn Type> {
347        Box::new(self.clone())
348    }
349
350    fn repr(&self) -> TypeRepr {
351        TypeRepr::SpaceAddress {
352            size: self.size,
353            space: self.space,
354        }
355    }
356}
357
358/// A fixed, ordered group of field types — the functional-IR tuple. Its `size`
359/// is the sum of its fields' sizes (a nominal layout; aggregates are abstract and
360/// never lowered to a physical ABI, so the value is informational only).
361#[derive(Clone)]
362struct AggregateType {
363    fields: Vec<AggregateField>,
364    size: usize,
365}
366
367/// A unique, editable return-record declaration owned by one function.
368///
369/// Mutation replaces the declaration object at a module barrier while preserving
370/// its TypeId. Published generations keep the previous object alive for readers
371/// that began before the barrier.
372#[derive(Clone)]
373struct FunctionReturnType {
374    owner: FunctionId,
375    fields: Vec<AggregateField>,
376    size: usize,
377}
378
379impl Type for FunctionReturnType {
380    fn size(&self) -> usize {
381        self.size
382    }
383
384    fn fields(&self) -> Option<&[AggregateField]> {
385        Some(&self.fields)
386    }
387
388    fn function_return_owner(&self) -> Option<FunctionId> {
389        Some(self.owner)
390    }
391
392    fn clone_box(&self) -> Box<dyn Type> {
393        Box::new(self.clone())
394    }
395
396    fn repr(&self) -> TypeRepr {
397        TypeRepr::FunctionReturn {
398            owner: self.owner,
399            fields: self.fields.clone(),
400        }
401    }
402}
403
404impl Type for AggregateType {
405    fn size(&self) -> usize {
406        self.size
407    }
408
409    fn fields(&self) -> Option<&[AggregateField]> {
410        Some(&self.fields)
411    }
412
413    fn clone_box(&self) -> Box<dyn Type> {
414        Box::new(self.clone())
415    }
416
417    fn repr(&self) -> TypeRepr {
418        TypeRepr::Aggregate {
419            fields: self.fields.clone(),
420        }
421    }
422}
423
424/// A named, nominal struct with explicit per-field byte offsets.
425///
426/// Unlike [`AggregateType`], identity is the **name** (not the field list), so
427/// two structs that happen to share a layout stay distinct types. Field lists
428/// are **sparse** — only the fields of interest are recorded — and `size` is the
429/// real struct size, which need not equal the fields' extent. This is the
430/// pointee of a [`StructPointer`] and the type a
431/// [`Gep`](crate::value::insn::Gep) resolves field offsets against.
432#[derive(Clone)]
433struct StructType {
434    name: String,
435    fields: Vec<AggregateField>,
436    size: usize,
437}
438
439impl Type for StructType {
440    fn size(&self) -> usize {
441        self.size
442    }
443
444    fn fields(&self) -> Option<&[AggregateField]> {
445        Some(&self.fields)
446    }
447
448    fn struct_name(&self) -> Option<&str> {
449        Some(&self.name)
450    }
451
452    fn clone_box(&self) -> Box<dyn Type> {
453        Box::new(self.clone())
454    }
455
456    fn repr(&self) -> TypeRepr {
457        TypeRepr::Struct {
458            name: self.name.clone(),
459            size: self.size,
460            fields: self.fields.clone(),
461        }
462    }
463}
464
465/// A pointer of a given byte width pointing at `pointee` (typically a nominal
466/// [`StructType`]). Carries the pointee identity so a chain of
467/// [`Gep`](crate::value::insn::Gep) + `load` can resolve successive fields.
468#[derive(Clone)]
469struct StructPointer {
470    size: usize,
471    pointee: TypeId,
472}
473
474impl Type for StructPointer {
475    fn size(&self) -> usize {
476        self.size
477    }
478
479    fn pointee(&self) -> Option<TypeId> {
480        Some(self.pointee)
481    }
482
483    fn clone_box(&self) -> Box<dyn Type> {
484        Box::new(self.clone())
485    }
486
487    fn repr(&self) -> TypeRepr {
488        TypeRepr::StructPointer {
489            size: self.size,
490            pointee: self.pointee,
491        }
492    }
493}
494
495/// A code (function) pointer — see [`TypeRepr::CodePointer`].
496#[derive(Clone)]
497struct CodePointerType {
498    size: usize,
499}
500
501impl Type for CodePointerType {
502    fn size(&self) -> usize {
503        self.size
504    }
505
506    fn clone_box(&self) -> Box<dyn Type> {
507        Box::new(self.clone())
508    }
509
510    fn repr(&self) -> TypeRepr {
511        TypeRepr::CodePointer { size: self.size }
512    }
513}
514
515/// A fixed-length homogeneous array — see [`TypeRepr::Array`]. `size` is cached
516/// as `count * sizeof(elem)`; the array is opaque (no `fields()`) so it presents
517/// to structural passes exactly as a width-`size` integer would.
518#[derive(Clone)]
519struct ArrayType {
520    elem: TypeId,
521    count: usize,
522    size: usize,
523}
524
525impl Type for ArrayType {
526    fn size(&self) -> usize {
527        self.size
528    }
529
530    fn array(&self) -> Option<(TypeId, usize)> {
531        Some((self.elem, self.count))
532    }
533
534    fn clone_box(&self) -> Box<dyn Type> {
535        Box::new(self.clone())
536    }
537
538    fn repr(&self) -> TypeRepr {
539        TypeRepr::Array {
540            elem: self.elem,
541            count: self.count,
542        }
543    }
544}
545
546/// A variable-length homogeneous sequence — see [`TypeRepr::List`]. `bound` is the
547/// static element upper bound (`Some`) or `None` when unbounded; `size` is the
548/// `bound`-element footprint for a bounded list and 0 for an unbounded one (it has
549/// no materialized storage). Structurally a bounded list is indistinguishable from
550/// a width-`size` scalar; only [`Type::list`] recovers `(elem, bound)`.
551#[derive(Clone)]
552struct ListType {
553    elem: TypeId,
554    bound: Option<usize>,
555    size: usize,
556}
557
558impl Type for ListType {
559    fn size(&self) -> usize {
560        self.size
561    }
562
563    fn list(&self) -> Option<(TypeId, Option<usize>)> {
564        Some((self.elem, self.bound))
565    }
566
567    fn clone_box(&self) -> Box<dyn Type> {
568        Box::new(self.clone())
569    }
570
571    fn repr(&self) -> TypeRepr {
572        TypeRepr::List {
573            elem: self.elem,
574            bound: self.bound,
575        }
576    }
577}
578
579// ---------------------------------------------------------------------------
580// TypeManager
581// ---------------------------------------------------------------------------
582
583/// The default `field1`, `field2`, ... naming applied when an aggregate is
584/// built from bare types. Shared by the interner's hit-path probe and the
585/// mint path so both key the cache identically.
586fn default_named_fields(fields: Vec<TypeId>) -> Vec<AggregateField> {
587    fields
588        .into_iter()
589        .enumerate()
590        .map(|(i, type_id)| AggregateField::new(format!("field{}", i + 1), type_id))
591        .collect()
592}
593
594fn validate_unique_fields(fields: &[AggregateField]) -> Result<(), String> {
595    for (i, field) in fields.iter().enumerate() {
596        if fields[..i]
597            .iter()
598            .any(|previous| previous.name == field.name)
599        {
600            return Err(format!(
601                "aggregate field names must be unique; duplicate `{}`",
602                field.name
603            ));
604        }
605    }
606    Ok(())
607}
608
609/// Registry that owns all [`Type`] objects and hands out interned [`TypeId`]s.
610///
611/// Type identities are created once and never removed. Most definitions are
612/// immutable; function-owned return declarations may be replaced at an exclusive
613/// module barrier while preserving their TypeId. Superseded objects stay alive
614/// for older published readers.
615#[derive(Clone)]
616struct TypeManagerInner {
617    types: Vec<Box<dyn Type>>,
618    /// Superseded function-owned declarations retained because an older
619    /// lock-free publication generation may still point at them.
620    retired_types: Vec<Box<dyn Type>>,
621    /// Fast lookup: Int size → TypeId.
622    int_by_size: HashMap<usize, TypeId>,
623    /// The interned `bool` type, once created.
624    bool_id: Option<TypeId>,
625    /// Fast lookup: (size, space) → SpaceAddress TypeId.
626    space_address: HashMap<(usize, MemorySpaceId), TypeId>,
627    /// Fast lookup: named field-type list → Aggregate TypeId.
628    aggregate_by_fields: HashMap<Vec<AggregateField>, TypeId>,
629    /// One unique editable return-record declaration per function.
630    function_return: HashMap<FunctionId, TypeId>,
631    /// Nominal lookup: struct name → StructType TypeId.
632    struct_by_name: HashMap<String, TypeId>,
633    /// Fast lookup: (size, pointee) → StructPointer TypeId.
634    struct_pointer: HashMap<(usize, TypeId), TypeId>,
635    /// Fast lookup: size → CodePointer TypeId.
636    code_pointer: HashMap<usize, TypeId>,
637    /// Fast lookup: (elem, count) → Array TypeId.
638    array_by_elem_count: HashMap<(TypeId, usize), TypeId>,
639    /// Fast lookup: (elem, bound) → List TypeId (`bound` `None` = unbounded).
640    list_by_elem_bound: HashMap<(TypeId, Option<usize>), TypeId>,
641}
642
643impl Default for TypeManagerInner {
644    fn default() -> Self {
645        Self::new()
646    }
647}
648
649impl TypeManagerInner {
650    fn new() -> Self {
651        Self {
652            types: Vec::new(),
653            retired_types: Vec::new(),
654            int_by_size: HashMap::default(),
655            bool_id: None,
656            space_address: HashMap::default(),
657            aggregate_by_fields: HashMap::default(),
658            function_return: HashMap::default(),
659            struct_by_name: HashMap::default(),
660            struct_pointer: HashMap::default(),
661            array_by_elem_count: HashMap::default(),
662            list_by_elem_bound: HashMap::default(),
663            code_pointer: HashMap::default(),
664        }
665    }
666
667    fn register(&mut self, ty: Box<dyn Type>) -> TypeId {
668        let id = TypeId(self.types.len() as u32);
669        self.types.push(ty);
670        id
671    }
672
673    /// Returns the [`TypeId`] for `Int(size)`, creating the type if it does not
674    /// yet exist.
675    pub fn get_or_make_int(&mut self, size: usize) -> TypeId {
676        if let Some(&id) = self.int_by_size.get(&size) {
677            return id;
678        }
679        let id = self.register(Box::new(IntType { size }));
680        self.int_by_size.insert(size, id);
681        id
682    }
683
684    /// Returns the [`TypeId`] for a [`CodePointer`](TypeRepr::CodePointer) of the
685    /// given byte width, creating it if it does not yet exist. Keyed by size
686    /// alone (like `Int`), so a pass may mint it directly.
687    pub fn get_or_make_code_pointer(&mut self, size: usize) -> TypeId {
688        if let Some(&id) = self.code_pointer.get(&size) {
689            return id;
690        }
691        let id = self.register(Box::new(CodePointerType { size }));
692        self.code_pointer.insert(size, id);
693        id
694    }
695
696    /// Returns the [`TypeId`] for the byte-stored `bool` type, creating it if it
697    /// does not yet exist.
698    pub fn get_or_make_bool(&mut self) -> TypeId {
699        if let Some(id) = self.bool_id {
700            return id;
701        }
702        let id = self.register(Box::new(BoolType));
703        self.bool_id = Some(id);
704        id
705    }
706
707    /// The interned `bool` [`TypeId`], if it has been created.
708    pub fn bool_id(&self) -> Option<TypeId> {
709        self.bool_id
710    }
711
712    /// Whether `id` is the byte-stored `bool` type.
713    pub fn is_bool(&self, id: TypeId) -> bool {
714        matches!(self.get(id).repr(), TypeRepr::Bool)
715    }
716
717    /// Returns the [`TypeId`] for a [`SpaceAddress`] of the given byte width
718    /// pointing into `space`, creating it if it does not yet exist.
719    pub fn get_or_make_space_address(&mut self, size: usize, space: MemorySpaceId) -> TypeId {
720        if let Some(&id) = self.space_address.get(&(size, space)) {
721            return id;
722        }
723        let id = self.register(Box::new(SpaceAddress { size, space }));
724        self.space_address.insert((size, space), id);
725        id
726    }
727
728    /// Returns the [`TypeId`] for an [`AggregateType`] with the given ordered,
729    /// named fields, creating it if it does not yet exist. Each field type must
730    /// already be registered (it always is in practice: you build the field
731    /// types before grouping them).
732    pub fn get_or_make_named_aggregate(&mut self, fields: Vec<AggregateField>) -> TypeId {
733        validate_unique_fields(&fields).expect("aggregate field names must be unique");
734        if let Some(&id) = self.aggregate_by_fields.get(&fields) {
735            return id;
736        }
737        let size = fields.iter().map(|f| self.size_of(f.type_id)).sum();
738        let id = self.register(Box::new(AggregateType {
739            fields: fields.clone(),
740            size,
741        }));
742        self.aggregate_by_fields.insert(fields, id);
743        id
744    }
745
746    fn create_function_return(
747        &mut self,
748        owner: FunctionId,
749        fields: Vec<AggregateField>,
750    ) -> Result<TypeId, String> {
751        validate_unique_fields(&fields)?;
752        if let Some(&existing) = self.function_return.get(&owner) {
753            return Err(format!(
754                "function {owner:?} already owns return type {existing:?}"
755            ));
756        }
757        let size = fields.iter().map(|field| self.size_of(field.type_id)).sum();
758        let id = self.register(Box::new(FunctionReturnType {
759            owner,
760            fields,
761            size,
762        }));
763        self.function_return.insert(owner, id);
764        Ok(id)
765    }
766
767    fn edit_function_return(
768        &mut self,
769        owner: FunctionId,
770        fields: Vec<AggregateField>,
771    ) -> Result<TypeId, String> {
772        validate_unique_fields(&fields)?;
773        let id = self
774            .function_return
775            .get(&owner)
776            .copied()
777            .ok_or_else(|| format!("function {owner:?} has no owned return type"))?;
778        let size = fields.iter().map(|field| self.size_of(field.type_id)).sum();
779        let replacement: Box<dyn Type> = Box::new(FunctionReturnType {
780            owner,
781            fields,
782            size,
783        });
784        let old = std::mem::replace(&mut self.types[id.0 as usize], replacement);
785        self.retired_types.push(old);
786        Ok(id)
787    }
788
789    /// Returns the nominal [`StructType`] named `name`, creating it if it does
790    /// not yet exist. Identity is the name: a second call with the same `name`
791    /// returns the original `TypeId` and **ignores** `size`/`fields`.
792    pub fn get_or_make_struct(
793        &mut self,
794        name: impl Into<String>,
795        size: usize,
796        fields: Vec<AggregateField>,
797    ) -> TypeId {
798        let name = name.into();
799        if let Some(&id) = self.struct_by_name.get(&name) {
800            return id;
801        }
802        let id = self.register(Box::new(StructType {
803            name: name.clone(),
804            fields,
805            size,
806        }));
807        self.struct_by_name.insert(name, id);
808        id
809    }
810
811    /// The [`TypeId`] of the nominal struct named `name`, if registered.
812    pub fn struct_by_name(&self, name: &str) -> Option<TypeId> {
813        self.struct_by_name.get(name).copied()
814    }
815
816    /// Returns the [`TypeId`] for a [`StructPointer`] of the given byte width
817    /// pointing at `pointee`, creating it if it does not yet exist.
818    pub fn get_or_make_struct_pointer(&mut self, size: usize, pointee: TypeId) -> TypeId {
819        if let Some(&id) = self.struct_pointer.get(&(size, pointee)) {
820            return id;
821        }
822        let id = self.register(Box::new(StructPointer { size, pointee }));
823        self.struct_pointer.insert((size, pointee), id);
824        id
825    }
826
827    /// Returns the [`TypeId`] for an [`ArrayType`] of `count` elements of type
828    /// `elem`, creating it if it does not yet exist. `elem` must already be
829    /// registered (it always is: you build the element type first).
830    pub fn get_or_make_array(&mut self, elem: TypeId, count: usize) -> TypeId {
831        if let Some(&id) = self.array_by_elem_count.get(&(elem, count)) {
832            return id;
833        }
834        let size = self.size_of(elem) * count;
835        let id = self.register(Box::new(ArrayType { elem, count, size }));
836        self.array_by_elem_count.insert((elem, count), id);
837        id
838    }
839
840    /// Returns the [`TypeId`] for a *bounded* [`ListType`] — a variable-length
841    /// sequence of at most `bound` elements of type `elem` — creating it if it does
842    /// not yet exist. `elem` must already be registered. For a pointer-sourced
843    /// string of unknown length, see [`get_or_make_unbounded_list`].
844    ///
845    /// [`get_or_make_unbounded_list`]: Self::get_or_make_unbounded_list
846    pub fn get_or_make_list(&mut self, elem: TypeId, bound: usize) -> TypeId {
847        self.get_or_make_list_opt(elem, Some(bound))
848    }
849
850    /// Returns the [`TypeId`] for an *unbounded* [`ListType`] of `elem` — a string
851    /// of unknown length (a `char*`), with no static footprint (`size` 0).
852    pub fn get_or_make_unbounded_list(&mut self, elem: TypeId) -> TypeId {
853        self.get_or_make_list_opt(elem, None)
854    }
855
856    /// Shared constructor for bounded (`Some`) and unbounded (`None`) lists.
857    fn get_or_make_list_opt(&mut self, elem: TypeId, bound: Option<usize>) -> TypeId {
858        if let Some(&id) = self.list_by_elem_bound.get(&(elem, bound)) {
859            return id;
860        }
861        // A bounded list footprints its `bound` elements; an unbounded one is a
862        // handle with no materialized storage (size 0).
863        let size = bound.map_or(0, |b| self.size_of(elem) * b);
864        let id = self.register(Box::new(ListType { elem, bound, size }));
865        self.list_by_elem_bound.insert((elem, bound), id);
866        id
867    }
868
869    /// Returns a reference to the concrete [`Type`] for `id`.
870    pub fn get(&self, id: TypeId) -> &dyn Type {
871        &*self.types[id.0 as usize]
872    }
873
874    /// Returns the byte width of values with type `id`.
875    pub fn size_of(&self, id: TypeId) -> usize {
876        self.get(id).size()
877    }
878
879    /// Computes the result [`TypeId`] for a binary operation on `lhs op rhs`.
880    ///
881    /// Comparisons yield `bool`; `And`/`Or`/`Xor` over `bool` operands stay `bool`
882    /// (this *is* logical and/or/xor); every other integer/float op preserves the
883    /// left operand's type (so a pointer-typed operand keeps its space provenance
884    /// through `ptr + offset`).
885    pub fn binop_result(&mut self, lhs: TypeId, op: Binop, rhs: TypeId) -> TypeId {
886        // `None` only when the result is `bool` and `bool` isn't interned yet.
887        if let Some(id) = self.binop_result_probe(lhs, op, rhs) {
888            id
889        } else {
890            self.get_or_make_bool()
891        }
892    }
893
894    /// The read-only arm of [`binop_result`](Self::binop_result): resolves the
895    /// result type without minting, returning `None` exactly when the result is
896    /// `bool` and `bool` has not been interned yet (the caller then mints it).
897    fn binop_result_probe(&self, lhs: TypeId, op: Binop, _rhs: TypeId) -> Option<TypeId> {
898        match op {
899            Binop::Int(int_op) => match int_op {
900                IntBinop::Equal
901                | IntBinop::NotEqual
902                | IntBinop::Less
903                | IntBinop::LessEqual
904                | IntBinop::SLess
905                | IntBinop::SLessEqual => self.bool_id,
906                // Bitwise and/or/xor over bool operands is logical and/or/xor and
907                // preserves the bool type; over ints it preserves the int type.
908                IntBinop::And | IntBinop::Or | IntBinop::Xor if self.is_bool(lhs) => Some(lhs),
909                _ => Some(lhs),
910            },
911            Binop::Float(float_op) => {
912                if float_op.is_comparison() {
913                    self.bool_id
914                } else {
915                    Some(lhs)
916                }
917            }
918        }
919    }
920}
921
922/// The type registry: a global, append-only table of [`TypeId`] identities behind a
923/// [`RwLock`] so that types can be minted through a shared `&` reference (a
924/// prerequisite for running function passes in parallel against a shared
925/// `ContextView`). Reads — including the `get_or_make_*` hit path — take a read
926/// lock; only a cache miss takes the write lock (and re-checks under it).
927/// Interned [`TypeId`]s are globally stable and never remapped.
928pub struct TypeManager {
929    inner: RwLock<TypeManagerInner>,
930    /// Lock-free read index over the currently published type objects in `inner`.
931    ///
932    /// Publishing replaces this pointer after a successful mint. Old indexes
933    /// stay owned by `published_generations`, so a reader that raced with a
934    /// publication can safely finish through the generation it loaded. The
935    /// pointed-to `Type` objects live in `inner.types` or `inner.retired_types`;
936    /// those boxes never move or disappear.
937    published: AtomicPtr<PublishedTypes>,
938    // Each generation needs its own stable heap address after this Vec grows.
939    #[allow(clippy::vec_box)]
940    published_generations: Mutex<Vec<Box<PublishedTypes>>>,
941}
942
943/// One immutable generation of the lock-free TypeId -> Type pointer index.
944///
945/// The raw trait-object pointers target `Box<dyn Type>` pointees owned by the
946/// corresponding [`TypeManagerInner`]. They are immutable, `Send + Sync`, and
947/// remain allocated for the manager's entire lifetime. A generation is never
948/// modified after publication.
949struct PublishedTypes {
950    entries: Box<[*const dyn Type]>,
951}
952
953// SAFETY: every entry points to an immutable `dyn Type + Send + Sync` allocation
954// owned for the full lifetime of the enclosing TypeManager. PublishedTypes never
955// mutates an entry or the pointee after construction.
956unsafe impl Send for PublishedTypes {}
957// SAFETY: see the `Send` implementation above; concurrent access is read-only.
958unsafe impl Sync for PublishedTypes {}
959
960impl PublishedTypes {
961    fn from_inner(inner: &TypeManagerInner) -> Self {
962        Self {
963            entries: inner
964                .types
965                .iter()
966                .map(|ty| &**ty as *const dyn Type)
967                .collect(),
968        }
969    }
970}
971
972impl Default for TypeManager {
973    fn default() -> Self {
974        Self::new()
975    }
976}
977
978impl Clone for TypeManager {
979    fn clone(&self) -> Self {
980        Self::from_inner(self.read().clone())
981    }
982}
983
984impl TypeManager {
985    pub fn new() -> Self {
986        Self::from_inner(TypeManagerInner::new())
987    }
988
989    fn from_inner(inner: TypeManagerInner) -> Self {
990        let generation = Box::new(PublishedTypes::from_inner(&inner));
991        let published = AtomicPtr::new((&*generation as *const PublishedTypes).cast_mut());
992        Self {
993            inner: RwLock::new(inner),
994            published,
995            published_generations: Mutex::new(vec![generation]),
996        }
997    }
998
999    fn read(&self) -> RwLockReadGuard<'_, TypeManagerInner> {
1000        self.inner.read().expect("type manager RwLock poisoned")
1001    }
1002
1003    fn write(&self) -> RwLockWriteGuard<'_, TypeManagerInner> {
1004        self.inner.write().expect("type manager RwLock poisoned")
1005    }
1006
1007    /// Publish the current type table for lock-free readers.
1008    /// Caller holds the write lock, so only one generation can be constructed
1009    /// at a time and every registered type is fully initialized first.
1010    fn publish(&self, inner: &TypeManagerInner) {
1011        let generation = Box::new(PublishedTypes::from_inner(inner));
1012        let ptr = (&*generation as *const PublishedTypes).cast_mut();
1013        self.published_generations
1014            .lock()
1015            .expect("type publication generation lock poisoned")
1016            .push(generation);
1017        self.published.store(ptr, Ordering::Release);
1018    }
1019
1020    /// Publish after an exclusive module-barrier mutation. Taking `&mut self`
1021    /// makes creation/editing unavailable through a function pass's shared
1022    /// `ContextView` by construction.
1023    fn publish_exclusive(&mut self) {
1024        let generation = {
1025            let inner = self.inner.get_mut().expect("type manager RwLock poisoned");
1026            Box::new(PublishedTypes::from_inner(inner))
1027        };
1028        let ptr = (&*generation as *const PublishedTypes).cast_mut();
1029        self.published_generations
1030            .get_mut()
1031            .expect("type publication generation lock poisoned")
1032            .push(generation);
1033        self.published.store(ptr, Ordering::Release);
1034    }
1035
1036    fn published(&self) -> &PublishedTypes {
1037        let ptr = self.published.load(Ordering::Acquire);
1038        debug_assert!(!ptr.is_null(), "type publication pointer is null");
1039        // SAFETY: `from_inner` installs the initial generation before the manager
1040        // becomes observable. Every later generation is retained in
1041        // `published_generations` for the manager's lifetime and is immutable.
1042        unsafe { &*ptr }
1043    }
1044
1045    /// Run one double-checked mint operation and publish only when it appended a
1046    /// new type. Cache hits therefore retain the current generation unchanged.
1047    fn mint(&self, f: impl FnOnce(&mut TypeManagerInner) -> TypeId) -> TypeId {
1048        let mut inner = self.write();
1049        let old_len = inner.types.len();
1050        let id = f(&mut inner);
1051        if inner.types.len() != old_len {
1052            self.publish(&inner);
1053        }
1054        id
1055    }
1056
1057    // --- mint path (double-checked: read-lock hit, write-lock miss) -------
1058    //
1059    // Types are minted rarely and reused constantly, so each `get_or_make_*`
1060    // probes its cache under the read lock first and only takes the write lock
1061    // on a miss. The inner method re-checks its cache under the write lock, so
1062    // two racing minters agree on one id.
1063
1064    pub fn get_or_make_int(&self, size: usize) -> TypeId {
1065        if let Some(&id) = self.read().int_by_size.get(&size) {
1066            return id;
1067        }
1068        self.mint(|inner| inner.get_or_make_int(size))
1069    }
1070    pub fn get_or_make_bool(&self) -> TypeId {
1071        if let Some(id) = self.read().bool_id {
1072            return id;
1073        }
1074        self.mint(TypeManagerInner::get_or_make_bool)
1075    }
1076    pub fn get_or_make_space_address(
1077        &self,
1078        size: usize,
1079        space: impl Into<MemorySpaceId>,
1080    ) -> TypeId {
1081        let space = space.into();
1082        if let Some(&id) = self.read().space_address.get(&(size, space)) {
1083            return id;
1084        }
1085        self.mint(|inner| inner.get_or_make_space_address(size, space))
1086    }
1087    /// Returns the [`TypeId`] for an `AggregateType` with default field names
1088    /// (`field1`, `field2`, ...), creating it if it does not yet exist.
1089    pub fn get_or_make_aggregate(&self, fields: Vec<TypeId>) -> TypeId {
1090        self.get_or_make_named_aggregate(default_named_fields(fields))
1091    }
1092    pub fn get_or_make_named_aggregate(&self, fields: Vec<AggregateField>) -> TypeId {
1093        if let Some(&id) = self.read().aggregate_by_fields.get(&fields) {
1094            return id;
1095        }
1096        self.mint(|inner| inner.get_or_make_named_aggregate(fields))
1097    }
1098
1099    /// Create a fresh nominal return-record type owned by `owner`.
1100    ///
1101    /// This never structurally deduplicates: identical records owned by two
1102    /// functions receive distinct TypeIds. Exclusive access confines publication
1103    /// to a module barrier; function passes will eventually return this request as
1104    /// an effect for the driver to apply through the same API.
1105    pub fn create_function_return(
1106        &mut self,
1107        owner: FunctionId,
1108        fields: Vec<AggregateField>,
1109    ) -> Result<TypeId, String> {
1110        let id = self
1111            .inner
1112            .get_mut()
1113            .expect("type manager RwLock poisoned")
1114            .create_function_return(owner, fields)?;
1115        self.publish_exclusive();
1116        Ok(id)
1117    }
1118
1119    /// Replace an owned return record's fields while preserving its TypeId.
1120    /// Readers that began before this exclusive barrier retain the previous
1121    /// published declaration; subsequent reads observe the replacement.
1122    pub fn edit_function_return(
1123        &mut self,
1124        owner: FunctionId,
1125        fields: Vec<AggregateField>,
1126    ) -> Result<TypeId, String> {
1127        let id = self
1128            .inner
1129            .get_mut()
1130            .expect("type manager RwLock poisoned")
1131            .edit_function_return(owner, fields)?;
1132        self.publish_exclusive();
1133        Ok(id)
1134    }
1135
1136    /// Create a batch of types requested by function passes, in request order,
1137    /// then publish one new read generation. This is the module-barrier creation
1138    /// path; workers only use the corresponding `get_*` accessors.
1139    pub fn create_requested_types(&mut self, requests: &[TypeRequest]) -> Vec<TypeId> {
1140        if requests.is_empty() {
1141            return Vec::new();
1142        }
1143        let (ids, changed) = {
1144            let inner = self.inner.get_mut().expect("type manager RwLock poisoned");
1145            let before = inner.types.len();
1146            let ids = requests
1147                .iter()
1148                .map(|request| match *request {
1149                    TypeRequest::Aggregate { ref fields } => {
1150                        inner.get_or_make_named_aggregate(fields.clone())
1151                    }
1152                    TypeRequest::StructPointer { size, pointee } => {
1153                        inner.get_or_make_struct_pointer(size, pointee)
1154                    }
1155                    TypeRequest::Array { elem, count } => inner.get_or_make_array(elem, count),
1156                    TypeRequest::List { elem, bound } => inner.get_or_make_list_opt(elem, bound),
1157                })
1158                .collect();
1159            (ids, inner.types.len() != before)
1160        };
1161        if changed {
1162            self.publish_exclusive();
1163        }
1164        ids
1165    }
1166    pub fn get_or_make_struct(
1167        &self,
1168        name: impl Into<String>,
1169        size: usize,
1170        fields: Vec<AggregateField>,
1171    ) -> TypeId {
1172        let name = name.into();
1173        if let Some(&id) = self.read().struct_by_name.get(&name) {
1174            return id;
1175        }
1176        self.mint(|inner| inner.get_or_make_struct(name, size, fields))
1177    }
1178    pub fn get_or_make_struct_pointer(&self, size: usize, pointee: TypeId) -> TypeId {
1179        if let Some(&id) = self.read().struct_pointer.get(&(size, pointee)) {
1180            return id;
1181        }
1182        self.mint(|inner| inner.get_or_make_struct_pointer(size, pointee))
1183    }
1184    pub fn get_or_make_code_pointer(&self, size: usize) -> TypeId {
1185        if let Some(&id) = self.read().code_pointer.get(&size) {
1186            return id;
1187        }
1188        self.mint(|inner| inner.get_or_make_code_pointer(size))
1189    }
1190    /// Access an already-published struct-pointer type without creating state.
1191    pub fn get_struct_pointer(&self, size: usize, pointee: TypeId) -> Option<TypeId> {
1192        self.read().struct_pointer.get(&(size, pointee)).copied()
1193    }
1194
1195    /// Access an already-published structural aggregate without creating state.
1196    pub fn get_named_aggregate(&self, fields: &[AggregateField]) -> Option<TypeId> {
1197        self.read().aggregate_by_fields.get(fields).copied()
1198    }
1199    pub fn get_or_make_array(&self, elem: TypeId, count: usize) -> TypeId {
1200        if let Some(&id) = self.read().array_by_elem_count.get(&(elem, count)) {
1201            return id;
1202        }
1203        self.mint(|inner| inner.get_or_make_array(elem, count))
1204    }
1205
1206    /// Access an already-published array type without creating shared state.
1207    pub fn get_array(&self, elem: TypeId, count: usize) -> Option<TypeId> {
1208        self.read().array_by_elem_count.get(&(elem, count)).copied()
1209    }
1210    /// Access an already-published list type without creating shared state.
1211    pub fn get_list(&self, elem: TypeId, bound: Option<usize>) -> Option<TypeId> {
1212        self.read().list_by_elem_bound.get(&(elem, bound)).copied()
1213    }
1214
1215    /// Access an already-published sequence type of the requested kind.
1216    pub fn get_seq(&self, elem: TypeId, len: usize, is_list: bool) -> Option<TypeId> {
1217        if is_list {
1218            self.get_list(elem, Some(len))
1219        } else {
1220            self.get_array(elem, len)
1221        }
1222    }
1223    pub fn get_or_make_list(&self, elem: TypeId, bound: usize) -> TypeId {
1224        if let Some(&id) = self.read().list_by_elem_bound.get(&(elem, Some(bound))) {
1225            return id;
1226        }
1227        self.mint(|inner| inner.get_or_make_list(elem, bound))
1228    }
1229    pub fn get_or_make_unbounded_list(&self, elem: TypeId) -> TypeId {
1230        if let Some(&id) = self.read().list_by_elem_bound.get(&(elem, None)) {
1231            return id;
1232        }
1233        self.mint(|inner| inner.get_or_make_unbounded_list(elem))
1234    }
1235    /// Build the sequence type of the given kind: a [`List`](Self::get_or_make_list)
1236    /// when `is_list`, else a fixed [`Array`](Self::get_or_make_array). The inverse
1237    /// of [`seq_of`](Self::seq_of).
1238    pub fn get_or_make_seq(&self, elem: TypeId, len: usize, is_list: bool) -> TypeId {
1239        if is_list {
1240            self.get_or_make_list(elem, len)
1241        } else {
1242            self.get_or_make_array(elem, len)
1243        }
1244    }
1245    pub fn binop_result(&self, lhs: TypeId, op: Binop, rhs: TypeId) -> TypeId {
1246        if let Some(id) = self.read().binop_result_probe(lhs, op, rhs) {
1247            return id;
1248        }
1249        self.mint(|inner| inner.binop_result(lhs, op, rhs))
1250    }
1251
1252    // --- Registry-key reads (interner lock) ------------------------------
1253
1254    pub fn bool_id(&self) -> Option<TypeId> {
1255        self.read().bool_id()
1256    }
1257    /// Access an already-published canonical integer type.
1258    ///
1259    /// Function passes use this instead of silently creating shared state. A
1260    /// missing width means the pass failed to derive its type from published IR.
1261    pub fn get_int(&self, size: usize) -> TypeId {
1262        self.read()
1263            .int_by_size
1264            .get(&size)
1265            .copied()
1266            .unwrap_or_else(|| panic!("canonical integer type i{} is not published", size * 8))
1267    }
1268    /// Access the already-published canonical boolean type.
1269    pub fn get_bool(&self) -> TypeId {
1270        self.bool_id()
1271            .expect("canonical bool type is not published")
1272    }
1273    pub fn struct_by_name(&self, name: &str) -> Option<TypeId> {
1274        self.read().struct_by_name(name)
1275    }
1276    pub fn function_return(&self, owner: FunctionId) -> Option<TypeId> {
1277        self.read().function_return.get(&owner).copied()
1278    }
1279
1280    // --- Published TypeId reads (lock-free) ------------------------------
1281
1282    pub fn is_bool(&self, id: TypeId) -> bool {
1283        matches!(self.get(id).repr(), TypeRepr::Bool)
1284    }
1285    pub fn function_return_owner(&self, id: TypeId) -> Option<FunctionId> {
1286        self.get(id).function_return_owner()
1287    }
1288    pub fn size_of(&self, id: TypeId) -> usize {
1289        self.get(id).size()
1290    }
1291    pub fn space_of(&self, id: TypeId) -> Option<MemorySpaceId> {
1292        self.get(id).space()
1293    }
1294    pub fn pointee_of(&self, id: TypeId) -> Option<TypeId> {
1295        self.get(id).pointee()
1296    }
1297    pub fn array_of(&self, id: TypeId) -> Option<(TypeId, usize)> {
1298        self.get(id).array()
1299    }
1300    pub fn list_of(&self, id: TypeId) -> Option<(TypeId, Option<usize>)> {
1301        self.get(id).list()
1302    }
1303    pub fn seq_of(&self, id: TypeId) -> Option<(TypeId, usize, bool)> {
1304        if let Some((elem, count)) = self.array_of(id) {
1305            return Some((elem, count, false));
1306        }
1307        self.list_of(id)
1308            .and_then(|(elem, bound)| bound.map(|bound| (elem, bound, true)))
1309    }
1310    pub fn seq_elem_of(&self, id: TypeId) -> Option<TypeId> {
1311        self.array_of(id)
1312            .map(|(elem, _)| elem)
1313            .or_else(|| self.list_of(id).map(|(elem, _)| elem))
1314    }
1315    pub fn type_name(&self, id: TypeId) -> String {
1316        match self.get(id).repr() {
1317            TypeRepr::Bool => "bool".to_string(),
1318            TypeRepr::Struct { name, .. } => name,
1319            TypeRepr::StructPointer { pointee, .. } => {
1320                format!("{}*", self.type_name(pointee))
1321            }
1322            TypeRepr::Array { elem, count } => {
1323                format!("[{};{}]", self.type_name(elem), count)
1324            }
1325            TypeRepr::List { elem, bound } => match bound {
1326                Some(bound) => format!("[{};<={}]", self.type_name(elem), bound),
1327                None => format!("[{};*]", self.type_name(elem)),
1328            },
1329            TypeRepr::CodePointer { size } => format!("code{}*", size * 8),
1330            _ => format!("i{}", self.size_of(id) * 8),
1331        }
1332    }
1333    pub fn field_type(&self, id: TypeId, index: usize) -> Option<TypeId> {
1334        self.aggregate_fields(id)?
1335            .get(index)
1336            .map(|field| field.type_id)
1337    }
1338    pub fn field_index(&self, id: TypeId, name: &str) -> Option<usize> {
1339        self.aggregate_fields(id)?
1340            .iter()
1341            .position(|field| field.name == name)
1342    }
1343
1344    // --- reference reads (built on publication-stable `get`) --------------
1345
1346    /// The number of published types.
1347    ///
1348    /// Lock-free, and monotonic because type identities are never removed, so a
1349    /// consumer can use it as a cheap "has anything been added" probe before
1350    /// paying for a question that needs the lock.
1351    pub fn published_len(&self) -> usize {
1352        self.published().entries.len()
1353    }
1354
1355    /// Whether any array or list type has been created in this module.
1356    ///
1357    /// Answers "could any value here be sequence-typed" in one step, without
1358    /// inspecting a value. An interpreter uses it to skip a per-operand type
1359    /// query entirely on the overwhelmingly common modules that contain no
1360    /// sequences at all. Takes the lock, so pair it with
1361    /// [`published_len`](Self::published_len) rather than calling it per access.
1362    pub fn has_sequence_types(&self) -> bool {
1363        let inner = self.read();
1364        !inner.array_by_elem_count.is_empty() || !inner.list_by_elem_bound.is_empty()
1365    }
1366
1367    /// Returns a reference to the concrete [`Type`] for `id`.
1368    ///
1369    /// The lookup loads one immutable published index generation and takes no
1370    /// lock. The reference remains valid across later publications because the
1371    /// TypeIds are never removed, and each published `Box<dyn Type>` pointee is
1372    /// heap-allocated and never moved or freed, including superseded owned
1373    /// declarations retained for older generations.
1374    pub fn get(&self, id: TypeId) -> &dyn Type {
1375        let entries = &self.published().entries;
1376        let ptr = *entries.get(id.0 as usize).unwrap_or_else(|| {
1377            panic!(
1378                "missing published type {id:?}; published type count is {}",
1379                entries.len()
1380            )
1381        });
1382        // SAFETY: publication records pointers to immutable boxed type objects.
1383        // Every published box remains owned by `inner.types` or
1384        // `inner.retired_types` until this manager is dropped.
1385        unsafe { &*ptr }
1386    }
1387
1388    pub fn struct_name_of(&self, id: TypeId) -> Option<&str> {
1389        self.get(id).struct_name()
1390    }
1391    pub fn aggregate_fields(&self, id: TypeId) -> Option<&[AggregateField]> {
1392        self.get(id).fields()
1393    }
1394    pub fn field_by_offset(&self, id: TypeId, offset: usize) -> Option<(usize, &AggregateField)> {
1395        self.aggregate_fields(id)?
1396            .iter()
1397            .enumerate()
1398            .find(|(_, field)| field.offset == offset)
1399    }
1400    pub fn field_name(&self, id: TypeId, index: usize) -> Option<&str> {
1401        self.aggregate_fields(id)?
1402            .get(index)
1403            .map(|field| field.name.as_str())
1404    }
1405}
1406
1407// ---------------------------------------------------------------------------
1408// Serialization
1409// ---------------------------------------------------------------------------
1410//
1411// `TypeManager` owns `Box<dyn Type>` trait objects, which serde cannot derive
1412// over. Instead we serialize the type table as a `Vec<TypeRepr>` (the flat
1413// description each type reports via `Type::repr`) and replay the `get_or_make_*`
1414// constructors on load. Replaying in order reproduces the interned `TypeId`
1415// indices and rebuilds the lookup maps (`int_by_size`, `stack_address`,
1416// `space_address`) exactly, so no other field needs to be persisted.
1417
1418impl serde::Serialize for TypeManager {
1419    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1420        let reprs: Vec<TypeRepr> = self
1421            .published()
1422            .entries
1423            .iter()
1424            .map(|&ptr| {
1425                // SAFETY: the same publication invariant used by `get` applies
1426                // to every pointer in this immutable generation.
1427                unsafe { &*ptr }.repr()
1428            })
1429            .collect();
1430        reprs.serialize(serializer)
1431    }
1432}
1433
1434impl<'de> serde::Deserialize<'de> for TypeManager {
1435    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1436        let reprs = Vec::<TypeRepr>::deserialize(deserializer)?;
1437        let mut manager = TypeManager::new();
1438        // Function-return types are the one kind whose fields can be rewritten
1439        // after registration ([`edit_function_return`] replaces in place to keep
1440        // the id stable), so unlike every other aggregate below their fields may
1441        // name types built *later*. Register each with no fields first — reserving
1442        // its id in order — and install the real ones in a second pass, once every
1443        // type exists. Without this a snapshot whose return envelope was edited to
1444        // reference a later type cannot be loaded at all.
1445        let mut pending_returns: Vec<(FunctionId, Vec<AggregateField>)> = Vec::new();
1446        for repr in reprs {
1447            match repr {
1448                TypeRepr::Int { size } => {
1449                    manager.get_or_make_int(size);
1450                }
1451                TypeRepr::Bool => {
1452                    manager.get_or_make_bool();
1453                }
1454                TypeRepr::SpaceAddress { size, space } => {
1455                    manager.get_or_make_space_address(size, space);
1456                }
1457                // Field types have lower TypeIds (built before the aggregate),
1458                // so replaying in order guarantees they already exist here.
1459                TypeRepr::Aggregate { fields } => {
1460                    manager.get_or_make_named_aggregate(fields);
1461                }
1462                TypeRepr::FunctionReturn { owner, fields } => {
1463                    manager
1464                        .create_function_return(owner, Vec::new())
1465                        .map_err(serde::de::Error::custom)?;
1466                    pending_returns.push((owner, fields));
1467                }
1468                TypeRepr::Struct { name, size, fields } => {
1469                    manager.get_or_make_struct(name, size, fields);
1470                }
1471                // The pointee has a lower TypeId (built before the pointer),
1472                // so replaying in order guarantees it already exists here.
1473                TypeRepr::StructPointer { size, pointee } => {
1474                    manager.get_or_make_struct_pointer(size, pointee);
1475                }
1476                // The element type has a lower TypeId (built before the array),
1477                // so replaying in order guarantees it already exists here.
1478                TypeRepr::Array { elem, count } => {
1479                    manager.get_or_make_array(elem, count);
1480                }
1481                // The element type has a lower TypeId (built before the list),
1482                // so replaying in order guarantees it already exists here.
1483                TypeRepr::List { elem, bound } => match bound {
1484                    Some(b) => {
1485                        manager.get_or_make_list(elem, b);
1486                    }
1487                    None => {
1488                        manager.get_or_make_unbounded_list(elem);
1489                    }
1490                },
1491                TypeRepr::CodePointer { size } => {
1492                    manager.get_or_make_code_pointer(size);
1493                }
1494            }
1495        }
1496        // Second pass: every id is now reserved, so a return type's fields can
1497        // safely name any type in the table regardless of registration order.
1498        for (owner, fields) in pending_returns {
1499            manager
1500                .edit_function_return(owner, fields)
1501                .map_err(serde::de::Error::custom)?;
1502        }
1503        Ok(manager)
1504    }
1505}
1506
1507#[cfg(test)]
1508mod tests {
1509    use super::*;
1510
1511    #[test]
1512    fn binop_result_bool_rules() {
1513        use crate::value::insn::{Binop, FloatBinop, IntBinop};
1514        let tm = TypeManager::new();
1515        let i32 = tm.get_or_make_int(4);
1516        let boolt = tm.get_or_make_bool();
1517
1518        // Comparisons over ints yield bool.
1519        assert_eq!(tm.binop_result(i32, Binop::Int(IntBinop::Less), i32), boolt);
1520        assert_eq!(
1521            tm.binop_result(i32, Binop::Int(IntBinop::Equal), i32),
1522            boolt
1523        );
1524        // Float comparisons yield bool.
1525        assert_eq!(
1526            tm.binop_result(i32, Binop::Float(FloatBinop::Less), i32),
1527            boolt
1528        );
1529        // Bitwise over bool operands stays bool (logical and/or/xor).
1530        assert_eq!(
1531            tm.binop_result(boolt, Binop::Int(IntBinop::And), boolt),
1532            boolt
1533        );
1534        assert_eq!(
1535            tm.binop_result(boolt, Binop::Int(IntBinop::Or), boolt),
1536            boolt
1537        );
1538        // Bitwise over ints preserves the int type.
1539        assert_eq!(tm.binop_result(i32, Binop::Int(IntBinop::And), i32), i32);
1540        // Arithmetic preserves the left type.
1541        assert_eq!(tm.binop_result(i32, Binop::Int(IntBinop::Add), i32), i32);
1542    }
1543
1544    #[test]
1545    fn bool_is_byte_stored_and_interned() {
1546        let tm = TypeManager::new();
1547        let b = tm.get_or_make_bool();
1548        assert_eq!(tm.size_of(b), 1);
1549        assert!(tm.is_bool(b));
1550        assert_eq!(tm.get_or_make_bool(), b);
1551        assert_eq!(tm.type_name(b), "bool");
1552        let i8 = tm.get_or_make_int(1);
1553        assert!(!tm.is_bool(i8));
1554    }
1555
1556    #[test]
1557    fn bool_round_trips_through_serde() {
1558        let tm = TypeManager::new();
1559        let _i8 = tm.get_or_make_int(1);
1560        let b = tm.get_or_make_bool();
1561        let config = bincode::config::standard();
1562        let bytes = bincode::serde::encode_to_vec(&tm, config).unwrap();
1563        let (back, _): (TypeManager, _) =
1564            bincode::serde::decode_from_slice(&bytes, config).unwrap();
1565        assert!(back.is_bool(b));
1566        assert_eq!(back.size_of(b), 1);
1567    }
1568
1569    #[test]
1570    fn array_is_a_disguised_width_n_scalar() {
1571        let tm = TypeManager::new();
1572        let i8 = tm.get_or_make_int(1);
1573        let arr = tm.get_or_make_array(i8, 20);
1574
1575        // Width is count * sizeof(elem) — structural passes see a 20-byte scalar.
1576        assert_eq!(tm.size_of(arr), 20);
1577        // Disguise: no aggregate fields, so tuple/struct machinery skips it.
1578        assert!(tm.aggregate_fields(arr).is_none());
1579        // Element-aware sites recover (elem, count).
1580        assert_eq!(tm.array_of(arr), Some((i8, 20)));
1581        // Interned: same (elem, count) → same TypeId.
1582        assert_eq!(tm.get_or_make_array(i8, 20), arr);
1583        assert_ne!(tm.get_or_make_array(i8, 21), arr);
1584        // Pretty name for dumps.
1585        assert_eq!(tm.type_name(arr), "[i8;20]");
1586    }
1587
1588    #[test]
1589    fn array_round_trips_through_serde() {
1590        let tm = TypeManager::new();
1591        let i8 = tm.get_or_make_int(1);
1592        let arr = tm.get_or_make_array(i8, 20);
1593
1594        let config = bincode::config::standard();
1595        let bytes = bincode::serde::encode_to_vec(&tm, config).unwrap();
1596        let (back, _): (TypeManager, _) =
1597            bincode::serde::decode_from_slice(&bytes, config).unwrap();
1598        // Replaying constructors in TypeId order reproduces the same handles.
1599        assert_eq!(back.array_of(arr), Some((i8, 20)));
1600        assert_eq!(back.size_of(arr), 20);
1601    }
1602
1603    #[test]
1604    fn list_round_trips_through_serde() {
1605        let tm = TypeManager::new();
1606        let i8 = tm.get_or_make_int(1);
1607        let list = tm.get_or_make_list(i8, 20);
1608
1609        let config = bincode::config::standard();
1610        let bytes = bincode::serde::encode_to_vec(&tm, config).unwrap();
1611        let (back, _): (TypeManager, _) =
1612            bincode::serde::decode_from_slice(&bytes, config).unwrap();
1613        // The list survives as a list (not a fixed array) with its bound and
1614        // footprint intact.
1615        assert_eq!(back.list_of(list), Some((i8, Some(20))));
1616        assert_eq!(back.array_of(list), None);
1617        assert_eq!(back.size_of(list), 20);
1618    }
1619
1620    #[test]
1621    fn unbounded_list_round_trips_and_has_no_footprint() {
1622        let tm = TypeManager::new();
1623        let i8 = tm.get_or_make_int(1);
1624        let list = tm.get_or_make_unbounded_list(i8);
1625        // Unbounded: a list with no static bound and no materialized footprint.
1626        assert_eq!(tm.list_of(list), Some((i8, None)));
1627        assert_eq!(tm.size_of(list), 0);
1628        // Distinct from any bounded list of the same element.
1629        assert_ne!(list, tm.get_or_make_list(i8, 20));
1630
1631        let config = bincode::config::standard();
1632        let bytes = bincode::serde::encode_to_vec(&tm, config).unwrap();
1633        let (back, _): (TypeManager, _) =
1634            bincode::serde::decode_from_slice(&bytes, config).unwrap();
1635        assert_eq!(back.list_of(list), Some((i8, None)));
1636        assert_eq!(back.array_of(list), None);
1637    }
1638
1639    #[test]
1640    fn newly_minted_type_is_published_before_return() {
1641        let tm = TypeManager::new();
1642        let i16 = tm.get_or_make_int(2);
1643        let array = tm.get_or_make_array(i16, 7);
1644
1645        assert_eq!(tm.size_of(array), 14);
1646        assert_eq!(tm.array_of(array), Some((i16, 7)));
1647    }
1648
1649    #[test]
1650    fn concurrent_mint_and_published_reads_are_consistent() {
1651        let tm = TypeManager::new();
1652        let byte = tm.get_or_make_int(1);
1653
1654        std::thread::scope(|scope| {
1655            for _ in 0..8 {
1656                scope.spawn(|| {
1657                    for count in 1..=128 {
1658                        let array = tm.get_or_make_array(byte, count);
1659                        assert_eq!(tm.size_of(array), count);
1660                        assert_eq!(tm.array_of(array), Some((byte, count)));
1661                        assert_eq!(tm.size_of(byte), 1);
1662                    }
1663                });
1664            }
1665        });
1666    }
1667
1668    #[test]
1669    fn requested_types_are_created_and_published_as_one_barrier_batch() {
1670        let mut tm = TypeManager::new();
1671        let byte = tm.get_or_make_int(1);
1672        let requests = [
1673            TypeRequest::array(byte, 4),
1674            TypeRequest::array(byte, 8),
1675            TypeRequest::array(byte, 4),
1676        ];
1677        assert_eq!(tm.get_array(byte, 4), None);
1678
1679        let ids = tm.create_requested_types(&requests);
1680
1681        assert_eq!(ids[0], ids[2], "duplicate requests must intern once");
1682        assert_eq!(tm.get_array(byte, 4), Some(ids[0]));
1683        assert_eq!(tm.get_array(byte, 8), Some(ids[1]));
1684        assert_eq!(tm.array_of(ids[0]), Some((byte, 4)));
1685        assert_eq!(tm.array_of(ids[1]), Some((byte, 8)));
1686    }
1687
1688    #[test]
1689    fn function_return_types_are_unique_owned_and_editable() {
1690        let mut tm = TypeManager::new();
1691        let i32 = tm.get_or_make_int(4);
1692        let fields = vec![AggregateField::new("value", i32)];
1693        let first_owner = FunctionId::from(0usize);
1694        let second_owner = FunctionId::from(1usize);
1695
1696        let first = tm
1697            .create_function_return(first_owner, fields.clone())
1698            .unwrap();
1699        let second = tm
1700            .create_function_return(second_owner, fields.clone())
1701            .unwrap();
1702
1703        assert_ne!(first, second, "owned declarations must not deduplicate");
1704        assert_eq!(tm.function_return(first_owner), Some(first));
1705        assert_eq!(tm.function_return(second_owner), Some(second));
1706        assert_eq!(tm.function_return_owner(first), Some(first_owner));
1707        assert_eq!(tm.function_return_owner(second), Some(second_owner));
1708        assert!(
1709            tm.create_function_return(first_owner, fields.clone())
1710                .is_err(),
1711            "one function cannot acquire a second return identity"
1712        );
1713
1714        let edited = tm
1715            .edit_function_return(
1716                first_owner,
1717                vec![
1718                    AggregateField::new("value", i32),
1719                    AggregateField::new("status", i32),
1720                ],
1721            )
1722            .unwrap();
1723        assert_eq!(edited, first, "editing must preserve nominal identity");
1724        assert_eq!(tm.size_of(first), 8);
1725        assert_eq!(tm.aggregate_fields(first).unwrap().len(), 2);
1726        assert_eq!(tm.size_of(second), 4, "the other owner must not change");
1727        assert_eq!(tm.aggregate_fields(second).unwrap(), fields.as_slice());
1728    }
1729
1730    #[test]
1731    fn function_return_type_round_trips_with_owner_and_identity() {
1732        let mut tm = TypeManager::new();
1733        let i16 = tm.get_or_make_int(2);
1734        let owner = FunctionId::from(7usize);
1735        let return_type = tm
1736            .create_function_return(owner, vec![AggregateField::new("result", i16)])
1737            .unwrap();
1738
1739        let bytes = bincode::serde::encode_to_vec(&tm, bincode::config::standard()).unwrap();
1740        let (mut restored, _): (TypeManager, _) =
1741            bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
1742
1743        assert_eq!(restored.function_return(owner), Some(return_type));
1744        assert_eq!(restored.function_return_owner(return_type), Some(owner));
1745        assert_eq!(restored.size_of(return_type), 2);
1746        assert_eq!(
1747            restored
1748                .edit_function_return(
1749                    owner,
1750                    vec![
1751                        AggregateField::new("result", i16),
1752                        AggregateField::new("carry", i16),
1753                    ],
1754                )
1755                .unwrap(),
1756            return_type
1757        );
1758        assert_eq!(restored.size_of(return_type), 4);
1759    }
1760}