Skip to main content

rustdv_methodology/
factory.rs

1//! The factory (design-doc §15, D69–D75).
2//!
3//! rustdv has a factory, and it works as the UVM factory works (D74). It is
4//! not a separate subsystem: a "maker" is an ordinary Rust value, so the
5//! override table is the [`ConfigDb`](crate::config) and registration is a
6//! link-time section like the test registry. From the user's chair there are
7//! two constructors — `Foo::new_comp()` (fixed) and `Foo::create_comp()`
8//! (overridable) — and `Factory::…` to install and inspect overrides.
9//!
10//! **How a `create_comp()` slot is overridden.** It is not resolved at the
11//! call — a `create_comp()` builds the default type immediately and flags the
12//! [`RustdvComp`] as factory-owned (D75). During the build walk, where the field
13//! name and so the path are finally known, the framework asks each flagged
14//! slot for its override (instance first, then type, by ConfigDb specificity,
15//! D13) and swaps it in before descending.
16
17use std::cell::RefCell;
18use std::collections::HashMap;
19use std::fmt;
20
21use crate::component::{Component, ComponentNode, RustdvCtx};
22use crate::config::ConfigDb;
23use crate::port::PortOwner;
24
25/// A maker: builds a component with no arguments (its name and parent come
26/// from the tree, D7). Non-capturing, so it is an ordinary `fn` pointer.
27pub type Maker = fn() -> Box<dyn ComponentNode>;
28
29// ===========================================================================
30// RustdvComp — the child slot (D75)
31// ===========================================================================
32
33/// A slot that holds any component. It says nothing about position in the
34/// tree and nothing about overridability; the *build line* decides that
35/// (`new_comp()` fixed, `create_comp()` overridable). Every child field a
36/// block may want to override is an `RustdvComp`.
37#[derive(Default)]
38pub struct RustdvComp {
39    inner: Option<Box<dyn ComponentNode>>,
40    /// Set by `create_comp()`; the walk checks flagged slots for an override.
41    overridable: bool,
42    /// The requested type's registered name, for the override lookup.
43    requested: Option<&'static str>,
44}
45
46impl RustdvComp {
47    /// A fixed slot: `new_comp()`. Never overridden.
48    pub fn fixed(node: Box<dyn ComponentNode>) -> RustdvComp {
49        RustdvComp { inner: Some(node), overridable: false, requested: None }
50    }
51
52    /// A factory slot: `create_comp()`. The default is built now and may be
53    /// swapped for an override during the walk.
54    pub fn overridable(node: Box<dyn ComponentNode>, requested: &'static str) -> RustdvComp {
55        RustdvComp { inner: Some(node), overridable: true, requested: Some(requested) }
56    }
57
58    /// The held component, shared, for asking it things — chiefly for one of
59    /// its ports during `connect`. `None` before the slot is built.
60    pub fn as_node(&self) -> Option<&(dyn ComponentNode + 'static)> {
61        self.inner.as_deref()
62    }
63
64    /// The held component, for the traversal. `None` before it is filled.
65    /// The object lifetime is `'static` (a boxed component always is), which
66    /// matches [`ComponentNode::children_mut`]'s element type.
67    pub fn as_node_mut(&mut self) -> Option<&mut (dyn ComponentNode + 'static)> {
68        self.inner.as_deref_mut()
69    }
70
71    /// Move the held component **out** of the slot, leaving it empty (D82b).
72    ///
73    /// This is what lets a parent's `run` be concurrent with its children's.
74    /// While the box sits in the slot it is part of the parent, so `&mut
75    /// parent` and `&mut child` overlap and cannot both exist. Once moved out
76    /// it is an independent value with no borrow relationship to the parent,
77    /// so both futures can be driven together.
78    ///
79    /// The slot is empty only for the duration of the run phase;
80    /// [`RustdvComp::put_node`] restores it before the post-run phases walk the tree.
81    pub fn take_node(&mut self) -> Option<Box<dyn ComponentNode>> {
82        self.inner.take()
83    }
84
85    /// Put a component taken by [`RustdvComp::take_node`] back into the slot.
86    pub fn put_node(&mut self, node: Box<dyn ComponentNode>) {
87        self.inner = Some(node);
88    }
89
90    /// Called by the derive-generated resolver during the build walk, with
91    /// this slot's field name. If flagged and an override applies at the
92    /// slot's path, swap it in. The discarded default's phases never ran —
93    /// resolution happens before the walk descends into the child.
94    pub fn resolve(&mut self, ctx: &RustdvCtx, name: &str) {
95        if !self.overridable {
96            return;
97        }
98        let Some(req) = self.requested else { return };
99        let path = if ctx.path().is_empty() {
100            name.to_string()
101        } else {
102            format!("{}.{}", ctx.path(), name)
103        };
104        if let Some(ov) = Factory::lookup_override(req, &path) {
105            self.inner = Some((ov.make)());
106        }
107        self.overridable = false;
108    }
109}
110
111/// A slot is a [`PortOwner`], so `connect(&self.producer, ..)` works on an
112/// erased child exactly as `connect(self, ..)` works on the connecting
113/// component. Both questions are answered by a `ComponentNode` method, which
114/// is reachable through `dyn` — no cast to the child's concrete type, which
115/// Rust would not allow anyway.
116impl PortOwner for RustdvComp {
117    fn owner_port_slot(&self, name: &str) -> Option<std::rc::Rc<dyn std::any::Any>> {
118        self.as_node()?.port_slot(name)
119    }
120    fn owner_label(&self) -> &'static str {
121        match self.as_node() {
122            Some(n) => n.node_name(),
123            // An empty slot: the build phase never created this child. Say so
124            // rather than reporting a missing port on a nameless component.
125            None => "an unbuilt child slot",
126        }
127    }
128}
129
130// ===========================================================================
131// Overrides
132// ===========================================================================
133
134/// An installed override: what to build, and the target's name for the dump.
135#[derive(Clone, Copy)]
136pub struct Override {
137    make: Maker,
138    to: &'static str,
139}
140
141impl fmt::Debug for Override {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        write!(f, "-> {}", self.to)
144    }
145}
146
147fn override_key(requested_name: &str) -> String {
148    format!("__factory_override__{requested_name}")
149}
150
151// ===========================================================================
152// Universal registration (D73) — a link-time section, like the test registry
153//
154// The section name `rustdv_comps` must be **≤ 16 bytes**: Mach-O caps section
155// names at 16 characters, and rustc rejects a longer one only on Apple
156// targets (ELF has no such limit, so Linux never complains). The original
157// `rustdv_components` was 17 and broke the macOS build while the Linux VM
158// stayed green. Keep any future section name short.
159// ===========================================================================
160
161/// One registered component: its name and its maker. Emitted by
162/// `#[derive(Component)]` for every component, universally (D73).
163pub struct ComponentReg {
164    /// Accessor rather than a const string, so the derive can compute it
165    /// from the type without `const` gymnastics.
166    pub name: fn() -> &'static str,
167    pub make: Maker,
168}
169
170fn sentinel_name() -> &'static str {
171    "__rustdv_component_sentinel"
172}
173fn sentinel_make() -> Box<dyn ComponentNode> {
174    panic!("the component-registry sentinel must never be built")
175}
176
177#[used]
178#[cfg_attr(not(target_vendor = "apple"), link_section = "rustdv_comps")]
179#[cfg_attr(target_vendor = "apple", link_section = "__DATA,rustdv_comps")]
180static SENTINEL: &ComponentReg = &ComponentReg { name: sentinel_name, make: sentinel_make };
181
182#[cfg(not(target_vendor = "apple"))]
183extern "C" {
184    static __start_rustdv_comps: u8;
185    static __stop_rustdv_comps: u8;
186}
187#[cfg(target_vendor = "apple")]
188extern "C" {
189    #[link_name = "\x01section$start$__DATA$rustdv_comps"]
190    static __start_rustdv_comps: u8;
191    #[link_name = "\x01section$end$__DATA$rustdv_comps"]
192    static __stop_rustdv_comps: u8;
193}
194
195fn collect_registry() -> HashMap<&'static str, Maker> {
196    std::hint::black_box(SENTINEL.name);
197    let mut map = HashMap::new();
198    unsafe {
199        let start = std::ptr::addr_of!(__start_rustdv_comps) as usize;
200        let stop = std::ptr::addr_of!(__stop_rustdv_comps) as usize;
201        let step = std::mem::size_of::<&ComponentReg>();
202        let base = start as *const &'static ComponentReg;
203        for i in 0..((stop - start) / step) {
204            let reg = *base.add(i);
205            let name = (reg.name)();
206            if name != sentinel_name() {
207                map.insert(name, reg.make);
208            }
209        }
210    }
211    map
212}
213
214thread_local! {
215    /// Built once from the link-time section (the set of types does not
216    /// change per test, unlike the override table).
217    static REGISTRY: RefCell<Option<HashMap<&'static str, Maker>>> = const { RefCell::new(None) };
218}
219
220fn with_registry<R>(f: impl FnOnce(&HashMap<&'static str, Maker>) -> R) -> R {
221    REGISTRY.with(|r| {
222        let mut slot = r.borrow_mut();
223        if slot.is_none() {
224            *slot = Some(collect_registry());
225        }
226        f(slot.as_ref().unwrap())
227    })
228}
229
230// ===========================================================================
231// The facade
232// ===========================================================================
233
234/// The factory. Ambient, like the ConfigDb it is built on; every method is
235/// an associated function.
236pub struct Factory;
237
238impl Factory {
239    /// Build a component from its registered string name (D71). Overridable,
240    /// like anything from the factory. A name that is not registered is a
241    /// testbench bug and panics; the file-driven form (ch39) will return a
242    /// `Result` instead.
243    pub fn create_by_name(name: &str) -> RustdvComp {
244        let make = with_registry(|reg| reg.get(name).copied());
245        match make {
246            Some(make) => {
247                // `requested` needs a 'static name; recover the registry's
248                // key so a by-name-created component can also be overridden.
249                let stored = with_registry(|reg| reg.keys().find(|k| **k == name).copied());
250                RustdvComp::overridable(make(), stored.expect("just found it"))
251            }
252            None => panic!("Factory::create_by_name: no component registered as \"{name}\""),
253        }
254    }
255
256    /// Override every `From::create_comp()` with a `To`, testbench-wide
257    /// (UVM `set_type_override_by_type`). Compile-checked: `To` must be a
258    /// component.
259    pub fn set_type_override<From, To>()
260    where
261        From: Component + ComponentNode + Default + 'static,
262        To: Component + ComponentNode + Default + 'static,
263    {
264        Self::store_override(None, "*", From::comp_name(), To::comp_name(), || {
265            Box::new(To::default())
266        });
267    }
268
269    /// The same, by string name (UVM `set_type_override_by_name`). Not
270    /// compile-checked; an unregistered `to` panics at this call.
271    pub fn set_type_override_by_name(from: &str, to: &str) {
272        let make = with_registry(|reg| reg.get(to).copied())
273            .unwrap_or_else(|| panic!("Factory::set_type_override_by_name: \"{to}\" is not registered"));
274        let to_static = with_registry(|reg| reg.keys().find(|k| **k == to).copied()).expect("just found it");
275        Self::store_override(None, "*", from, to_static, make);
276    }
277
278    /// Override a single instance, addressed by its path relative to `ctx`
279    /// (UVM `set_inst_override_by_type`). The path is a string because it
280    /// names a component elsewhere in the tree — not a duplicate of a field
281    /// name (D75).
282    pub fn set_inst_override<From, To>(ctx: &RustdvCtx, path: &str)
283    where
284        From: Component + ComponentNode + Default + 'static,
285        To: Component + ComponentNode + Default + 'static,
286    {
287        Self::store_override(Some(ctx), path, From::comp_name(), To::comp_name(), || {
288            Box::new(To::default())
289        });
290    }
291
292    fn store_override(
293        ctx: Option<&RustdvCtx>,
294        offset: &str,
295        from_name: &str,
296        to_name: &'static str,
297        make: Maker,
298    ) {
299        ConfigDb::set(ctx, offset, &override_key(from_name), Override { make, to: to_name });
300    }
301
302    /// The override in force for `requested_name` at `abs_path`, if any.
303    pub(crate) fn lookup_override(requested_name: &str, abs_path: &str) -> Option<Override> {
304        ConfigDb::get::<Override>(None, abs_path, &override_key(requested_name)).ok()
305    }
306
307    /// Print the overrides in force (UVM `uvm_factory().print()`). It is the
308    /// ConfigDb store, shown through the factory's window (D68).
309    pub fn print() {
310        rustdv_sim::log::info("Factory overrides:");
311        for (path, from, to) in ConfigDb::factory_overrides() {
312            rustdv_sim::log::info(&format!("  {path:<28}: {from} -> {to}"));
313        }
314    }
315}
316
317// ===========================================================================
318// Tests — no simulator.
319// ===========================================================================
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324    use crate::component::{Component, RustdvCtx};
325    use crate::config::ConfigDb;
326    use std::cell::RefCell;
327    use std::rc::Rc;
328
329    thread_local! {
330        static BUILT: RefCell<Vec<&'static str>> = const { RefCell::new(Vec::new()) };
331    }
332
333    fn record(name: &'static str) {
334        BUILT.with(|b| b.borrow_mut().push(name));
335    }
336
337    #[derive(Default)]
338    struct Base;
339    impl Component for Base {
340        fn build(&mut self, _ctx: &mut RustdvCtx) {
341            record("Base");
342        }
343    }
344    impl ComponentNode for Base {
345        fn node_name(&self) -> &'static str {
346            "Base"
347        }
348        fn children_mut(&mut self) -> Vec<(String, &mut (dyn ComponentNode + 'static))> {
349            Vec::new()
350        }
351    }
352
353    #[derive(Default)]
354    struct Derived;
355    impl Component for Derived {
356        fn build(&mut self, _ctx: &mut RustdvCtx) {
357            record("Derived");
358        }
359    }
360    impl ComponentNode for Derived {
361        fn node_name(&self) -> &'static str {
362            "Derived"
363        }
364        fn children_mut(&mut self) -> Vec<(String, &mut (dyn ComponentNode + 'static))> {
365            Vec::new()
366        }
367    }
368
369    fn fresh() {
370        ConfigDb::clear();
371        BUILT.with(|b| b.borrow_mut().clear());
372    }
373
374    #[test]
375    fn a_fixed_slot_holds_what_it_was_given() {
376        fresh();
377        let slot = RustdvComp::fixed(Box::new(Base));
378        assert_eq!(slot.as_node().unwrap().node_name(), "Base");
379    }
380
381    #[test]
382    fn an_empty_slot_reports_itself_by_name() {
383        fresh();
384        let slot = RustdvComp::default();
385        assert!(slot.as_node().is_none());
386        assert_eq!(slot.owner_label(), "an unbuilt child slot");
387    }
388
389    /// D75: `new_comp()` is fixed and `create_comp()` is overridable, and the
390    /// *build line* carries that choice — not the field type.
391    #[test]
392    fn a_type_override_swaps_a_create_slot_and_not_a_new_slot() {
393        fresh();
394        Factory::set_type_override::<Base, Derived>();
395        let ctx = RustdvCtx::for_test("env");
396
397        let mut overridable = RustdvComp::overridable(Box::new(Base), "Base");
398        overridable.resolve(&ctx, "tester");
399        assert_eq!(overridable.as_node().unwrap().node_name(), "Derived");
400
401        let mut fixed = RustdvComp::fixed(Box::new(Base));
402        fixed.resolve(&ctx, "scoreboard");
403        assert_eq!(fixed.as_node().unwrap().node_name(), "Base", "new_comp is never swapped");
404    }
405
406    #[test]
407    fn no_override_leaves_the_requested_type() {
408        fresh();
409        let ctx = RustdvCtx::for_test("env");
410        let mut slot = RustdvComp::overridable(Box::new(Base), "Base");
411        slot.resolve(&ctx, "tester");
412        assert_eq!(slot.as_node().unwrap().node_name(), "Base");
413    }
414
415    /// D13's precedence, applied to the factory: an instance override beats a
416    /// type override at the same path.
417    #[test]
418    fn an_instance_override_beats_a_type_override() {
419        fresh();
420        let ctx = RustdvCtx::for_test("env");
421        Factory::set_type_override::<Base, Base>();
422        Factory::set_inst_override::<Base, Derived>(&ctx, "tester");
423        let mut slot = RustdvComp::overridable(Box::new(Base), "Base");
424        slot.resolve(&ctx, "tester");
425        assert_eq!(slot.as_node().unwrap().node_name(), "Derived");
426    }
427
428    #[test]
429    fn an_instance_override_applies_only_at_its_path() {
430        fresh();
431        let ctx = RustdvCtx::for_test("env");
432        Factory::set_inst_override::<Base, Derived>(&ctx, "tester");
433
434        let mut here = RustdvComp::overridable(Box::new(Base), "Base");
435        here.resolve(&ctx, "tester");
436        assert_eq!(here.as_node().unwrap().node_name(), "Derived");
437
438        let mut elsewhere = RustdvComp::overridable(Box::new(Base), "Base");
439        elsewhere.resolve(&ctx, "other");
440        assert_eq!(elsewhere.as_node().unwrap().node_name(), "Base");
441    }
442
443    /// D75's guarantee: resolution happens before the walk descends, so the
444    /// discarded default's own phases never run.
445    #[test]
446    fn the_discarded_default_never_built() {
447        fresh();
448        Factory::set_type_override::<Base, Derived>();
449        let ctx = RustdvCtx::for_test("env");
450        let mut slot = RustdvComp::overridable(Box::new(Base), "Base");
451        slot.resolve(&ctx, "tester");
452        // Neither has been built yet — but the point is that the *Base* we
453        // threw away is gone before any walk could reach it.
454        assert_eq!(BUILT.with(|b| b.borrow().len()), 0);
455        assert_eq!(slot.as_node().unwrap().node_name(), "Derived");
456    }
457
458    /// Resolving twice must not re-apply: the slot is no longer overridable
459    /// once the walk has passed it.
460    #[test]
461    fn a_slot_resolves_once() {
462        fresh();
463        let ctx = RustdvCtx::for_test("env");
464        let mut slot = RustdvComp::overridable(Box::new(Base), "Base");
465        slot.resolve(&ctx, "tester");
466        Factory::set_type_override::<Base, Derived>(); // installed too late
467        slot.resolve(&ctx, "tester");
468        assert_eq!(
469            slot.as_node().unwrap().node_name(),
470            "Base",
471            "an override installed after the walk passed does not apply"
472        );
473    }
474
475    #[test]
476    fn take_and_put_move_the_box_out_and_back() {
477        fresh();
478        let mut slot = RustdvComp::fixed(Box::new(Base));
479        let node = slot.take_node().expect("something to take");
480        assert!(slot.as_node().is_none(), "the slot is empty during the run phase");
481        slot.put_node(node);
482        assert_eq!(slot.as_node().unwrap().node_name(), "Base", "and restored after");
483    }
484
485    // --- the sequence half of the factory (D80/D96) ----------------------
486
487    use crate::sequence::{clear_seq_overrides, create_seq, set_seq_override, SeqCtx, SeqError, Sequence};
488
489    #[derive(Default)]
490    struct BaseSeq;
491    #[derive(Default)]
492    struct RandomSeq;
493
494    impl Sequence for BaseSeq {
495        type Req = u8;
496        type Rsp = u8;
497        async fn body(&mut self, _c: &mut SeqCtx<u8, u8>) -> Result<(), SeqError> {
498            Ok(())
499        }
500        fn seq_name(&self) -> &'static str {
501            "BaseSeq"
502        }
503    }
504    impl Sequence for RandomSeq {
505        type Req = u8;
506        type Rsp = u8;
507        async fn body(&mut self, _c: &mut SeqCtx<u8, u8>) -> Result<(), SeqError> {
508            Ok(())
509        }
510        fn seq_name(&self) -> &'static str {
511            "RandomSeq"
512        }
513    }
514
515    #[test]
516    fn create_seq_builds_the_requested_type_by_default() {
517        clear_seq_overrides();
518        let seq = create_seq::<BaseSeq>();
519        assert_eq!(seq.name(), "BaseSeq");
520    }
521
522    #[test]
523    fn a_sequence_override_swaps_the_type() {
524        clear_seq_overrides();
525        set_seq_override::<BaseSeq, RandomSeq>();
526        let seq = create_seq::<BaseSeq>();
527        assert_eq!(seq.name(), "RandomSeq", "the test asked for Base and got Random");
528    }
529
530    #[test]
531    fn clearing_sequence_overrides_restores_the_default() {
532        clear_seq_overrides();
533        set_seq_override::<BaseSeq, RandomSeq>();
534        clear_seq_overrides();
535        assert_eq!(create_seq::<BaseSeq>().name(), "BaseSeq");
536    }
537
538    #[test]
539    fn an_override_on_one_sequence_leaves_others_alone() {
540        clear_seq_overrides();
541        set_seq_override::<BaseSeq, RandomSeq>();
542        assert_eq!(create_seq::<RandomSeq>().name(), "RandomSeq");
543    }
544
545    // Unused-import guard: `Rc` is here for future handle tests.
546    #[allow(dead_code)]
547    fn _rc_in_scope(_: Rc<u8>) {}
548}