Skip to main content

bb_ir/types/
mod.rs

1//! Hierarchical type system resolved at compile time by the
2//! compiler's TypeSolver. The tree is open via inventory: backends
3//! and DSL authors register new leaves via `inventory::submit!`. The
4//! [`Lattice`] is built once at startup; `is_subtype_of` walks the
5//! parent chain with caching. The runtime never sees abstract types.
6
7use std::collections::HashMap;
8use std::sync::OnceLock;
9
10pub mod builtins;
11pub mod common_relations;
12pub mod lattice;
13pub mod relations;
14pub mod storage;
15
16pub use builtins::*;
17pub use common_relations::{
18    BROADCAST_BINARY, ELEMENTWISE, MATMUL_BINARY, NO_RELATIONS, REDUCE_AXIS, UNARY_SAME_ELEMENT,
19};
20pub use lattice::Lattice;
21pub use relations::{CustomRelationCtx, PortRef, RelationResult, TypeRelation};
22pub use storage::{AnyTensor, Dtype, Storage};
23
24/// Concrete dispatchable leaf vs. abstract interior bound.
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub enum TypeKind {
27    /// Interior node — bound only. Never a value's runtime type.
28    Abstract,
29    /// Dispatchable leaf — always a value's runtime type.
30    Concrete,
31}
32
33/// Static type-identity carrier. Identity is pointer equality;
34/// subtype queries route through [`Lattice`].
35#[derive(Clone, Copy, Debug)]
36pub struct TypeNode {
37    /// Dotted-namespace identifier (`"any"`, `"tensor.f32"`).
38    /// Must be unique across submissions.
39    pub id: &'static str,
40    /// Parent's id, or `None` for the root.
41    pub parent: Option<&'static str>,
42    /// Dispatchable leaf vs. abstract bound.
43    pub kind: TypeKind,
44    /// C-FFI struct name (cbindgen). Empty for abstract nodes.
45    pub ffi_name: &'static str,
46    /// Wire-envelope discriminator. Concrete leaves non-zero;
47    /// abstract nodes 0.
48    pub wire_hash: u64,
49    /// ONNX denotation stamped on `ValueInfoProto.type.denotation`.
50    /// Empty when no canonical denotation exists.
51    pub denotation: &'static str,
52}
53
54impl PartialEq for TypeNode {
55    fn eq(&self, other: &Self) -> bool {
56        std::ptr::eq(self, other)
57    }
58}
59
60impl Eq for TypeNode {}
61
62impl std::hash::Hash for TypeNode {
63    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
64        std::ptr::hash(self, state);
65    }
66}
67
68impl TypeNode {
69    /// `true` if `self` is `other` or a descendant. Cached on
70    /// the global [`Lattice`].
71    pub fn is_subtype_of(&'static self, other: &'static TypeNode) -> bool {
72        Lattice::get().is_subtype(self, other)
73    }
74
75    /// `true` iff this node is a concrete (dispatchable) leaf.
76    pub fn is_concrete(&self) -> bool {
77        matches!(self.kind, TypeKind::Concrete)
78    }
79
80    /// `true` iff this node is an abstract interior bound.
81    pub fn is_abstract(&self) -> bool {
82        matches!(self.kind, TypeKind::Abstract)
83    }
84}
85
86/// Inventory submission carrier for `TypeNode`s.
87pub struct TypeNodeReg(pub &'static TypeNode);
88
89inventory::collect!(TypeNodeReg);
90
91/// `id → &TypeNode` map used by [`Lattice`] parent-chain resolution.
92pub(crate) fn id_to_node_map() -> &'static HashMap<&'static str, &'static TypeNode> {
93    static MAP: OnceLock<HashMap<&'static str, &'static TypeNode>> = OnceLock::new();
94    MAP.get_or_init(|| {
95        let mut m: HashMap<&'static str, &'static TypeNode> = HashMap::new();
96        for reg in inventory::iter::<TypeNodeReg> {
97            m.insert(reg.0.id, reg.0);
98        }
99        m
100    })
101}
102
103/// Resolve a `TypeNode` by its `id` string. `None` if no submission
104/// has registered that id at startup.
105pub fn lookup_by_id(id: &str) -> Option<&'static TypeNode> {
106    id_to_node_map().get(id).copied()
107}
108
109#[cfg(test)]
110#[path = "storage_tests.rs"]
111mod storage_tests;
112