Skip to main content

bitloom_builder/
closures.rs

1//! Elaborate-time closure / capture / inline surfaces (FR73–FR75 / Cap-R-*).
2//!
3//! Extracted from `lib.rs` as a size-hygiene first step (epic-27/28 builder split
4//! assessment). Behavior unchanged — re-exported at crate root.
5
6use bitloom_hir::{Diagnostic, Diagnostics, Span};
7
8/// Mask a mem init word to `width` bits (MVP: width ≤ 64).
9pub fn mask_mem_word(word: u64, width: u32) -> u64 {
10    if width == 0 {
11        0
12    } else if width >= 64 {
13        word
14    } else {
15        word & ((1u64 << width) - 1)
16    }
17}
18
19/// Elaborate-time LUT/ROM table builder (FR73). Runs `f(addr)` for each address;
20/// returns plain words — the closure never enters HIR.
21pub fn generate_mem_init_words<F>(depth: u32, width: u32, f: F) -> Vec<u64>
22where
23    F: Fn(usize) -> u64,
24{
25    (0..depth as usize)
26        .map(|i| mask_mem_word(f(i), width))
27        .collect()
28}
29
30/// Kind of hardware reference that must not be captured into elaborate-time
31/// generator / factory closures (FR73 / NFR35 / AD-18).
32///
33/// Session `Wire` / `Reg` / port names map to these kinds via [`HwCaptureRef`].
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum HwCaptureKind {
36    Wire,
37    Reg,
38    /// Directed port or other named hardware signal handle.
39    Signal,
40}
41
42/// Documented illegal-capture marker for Wire/Reg/signal refs (Story 27.3).
43///
44/// Elaborate-time generator APIs accept only **non-capturing** `Fn` that dissolve
45/// before freeze. Capturing a hardware ref is diagnosed via
46/// [`ElaborateSession::assert_no_hw_capture`] / [`ElaborateSession::reject_hw_capture`]
47/// (`rhdl::E0142`) — not silently treated as a legal generator closure.
48///
49/// Cycle-accurate “capturing closure” bans remain [`ElaborateSession::reject_unsynthesizable`]
50/// (`rhdl::E0141` / FR16).
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct HwCaptureRef {
53    pub kind: HwCaptureKind,
54    pub name: String,
55}
56
57impl HwCaptureRef {
58    /// Map a Wire net name to the illegal-capture marker.
59    pub fn wire(name: impl Into<String>) -> Self {
60        Self {
61            kind: HwCaptureKind::Wire,
62            name: name.into(),
63        }
64    }
65
66    /// Map a Reg name to the illegal-capture marker.
67    pub fn reg(name: impl Into<String>) -> Self {
68        Self {
69            kind: HwCaptureKind::Reg,
70            name: name.into(),
71        }
72    }
73
74    /// Map a port / signal handle name to the illegal-capture marker.
75    pub fn signal(name: impl Into<String>) -> Self {
76        Self {
77            kind: HwCaptureKind::Signal,
78            name: name.into(),
79        }
80    }
81
82    pub(crate) fn kind_label(&self) -> &'static str {
83        match self.kind {
84            HwCaptureKind::Wire => "Wire",
85            HwCaptureKind::Reg => "Reg",
86            HwCaptureKind::Signal => "Signal",
87        }
88    }
89}
90
91/// Documented constraints for closures that may enter the synthesizable hardware
92/// path (FR74 / Cap-R-48…50 / AD-18).
93///
94/// A legal synthesizable closure is:
95/// - **pure** (no side effects / I/O / threads) — Cap-R-50
96/// - **no heap** (`Box` / software `Vec` / `String` / …) — Cap-R-48
97/// - **no runtime capture state** (non-`const` captures; Wire/Reg still → E0142) — Cap-R-49
98/// - dissolved to ordinary HIR **before** freeze — never a Rust closure object in
99///   `tick` / FIRRTL / Chisel (NFR36 / Cap-R-58)
100///
101/// Comb inline (Story 28.2 / Cap-R-55): call
102/// [`ElaborateSession::inline_comb_fn`] after Cap-R-60 check.
103/// Seq inline (Story 28.3 / Cap-R-56): [`ElaborateSession::inline_seq_fn`]
104/// with Cap-R-70 ownership checks. Automatic rustc capture analysis is out of
105/// scope — macros / ATDD / typed surfaces pass violation tokens to
106/// [`ElaborateSession::check_synthesizable_closure`] /
107/// [`ElaborateSession::check_seq_ownership`].
108///
109/// Empty / simple stand-ins ([`LegalEmptyClosure`], [`LegalSimpleClosure`])
110/// implement this marker with no violations.
111pub trait SynthesizableClosure {
112    /// Documented violation tokens for Cap-R-60 checking. Empty = legal.
113    fn synthesizable_closure_violations(&self) -> Vec<SynthesizableClosureViolation> {
114        Vec::new()
115    }
116}
117
118/// Positive stand-in: empty non-capturing closure (FR74 ATDD).
119#[derive(Debug, Default, Clone, Copy)]
120pub struct LegalEmptyClosure;
121
122impl SynthesizableClosure for LegalEmptyClosure {}
123
124/// Positive stand-in: simple pure unary transform (FR74 / FR75 comb inline).
125#[derive(Debug, Default, Clone, Copy)]
126pub struct LegalSimpleClosure;
127
128impl SynthesizableClosure for LegalSimpleClosure {}
129
130/// Elaborate-time description of a combinational RHS (FR75 / Cap-R-55).
131///
132/// Produced by [`ElaborateSession::inline_comb_fn`] closures and immediately
133/// lowered to ordinary [`AssignExpr`] via existing `assign_*` APIs. Never
134/// stored as a Rust `Fn` in FrozenHir (NFR36). Also reusable as the RHS of
135/// [`SeqInline::Comb`] for sequential Reg.d inline (Cap-R-56).
136#[derive(Debug, Clone, PartialEq, Eq)]
137pub enum CombInline {
138    /// Copy from a named net / port / reg.
139    Ref(String),
140    /// Integer literal.
141    Lit(u64),
142    /// Same-width add.
143    Add(String, String),
144    /// Same-width subtract.
145    Sub(String, String),
146    /// Bitwise AND.
147    And(String, String),
148    /// Bitwise OR.
149    Or(String, String),
150    /// Bitwise XOR.
151    Xor(String, String),
152    /// Equality → 0/1 Bool.
153    Eq(String, String),
154    /// 2:1 mux (`sel != 0 ? t : f`).
155    Mux { sel: String, t: String, f: String },
156}
157
158/// Elaborate-time description of a sequential `Reg.d` next-state (FR75 / Cap-R-56).
159///
160/// Produced by [`ElaborateSession::inline_seq_fn`] and immediately lowered to
161/// ordinary sequential [`AssignExpr`] targeting [`AssignTarget::RegD`]. Never
162/// stored as a Rust `Fn` in FrozenHir (NFR36).
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub enum SeqInline {
165    /// Wrapping `dst + 1` (same as [`ElaborateSession::assign_reg_d_inc`]).
166    Inc,
167    /// Comb-shaped RHS lowered onto `Reg.d` (reuses [`CombInline`]).
168    Comb(CombInline),
169}
170
171impl From<CombInline> for SeqInline {
172    fn from(c: CombInline) -> Self {
173        Self::Comb(c)
174    }
175}
176
177/// Cap-R-70 ownership breach inside sequential synthesizable-closure inline.
178///
179/// Distinct from Cap-R-60 [`SynthesizableClosureViolation`] (E0143–E0145) and
180/// freeze multi-drive [`rhdl::E0140`] (AD-4, across processes).
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum SeqOwnershipViolationKind {
183    /// Illegal extra mutable borrow / second `Reg.d` write in the same
184    /// sequential process (or documented equivalent) → `rhdl::E0146`.
185    IllegalMutableBorrow,
186}
187
188impl SeqOwnershipViolationKind {
189    pub fn code(self) -> &'static str {
190        match self {
191            Self::IllegalMutableBorrow => "rhdl::E0146",
192        }
193    }
194
195    fn label(self) -> &'static str {
196        match self {
197            Self::IllegalMutableBorrow => "illegal mutable signal borrow",
198        }
199    }
200}
201
202/// Documented Cap-R-70 ownership violation token (Story 28.3).
203///
204/// Pass to [`ElaborateSession::check_seq_ownership`] /
205/// [`ElaborateSession::inline_seq_fn`]. Empty list = no tokenized breach;
206/// session still auto-detects a second `Reg.d` write on the inline destination.
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct SeqOwnershipViolation {
209    pub kind: SeqOwnershipViolationKind,
210    pub detail: String,
211}
212
213impl SeqOwnershipViolation {
214    pub fn illegal_mutable_borrow(detail: impl Into<String>) -> Self {
215        Self {
216            kind: SeqOwnershipViolationKind::IllegalMutableBorrow,
217            detail: detail.into(),
218        }
219    }
220
221    pub fn code(&self) -> &'static str {
222        self.kind.code()
223    }
224}
225
226/// Cap-R-70 check surface: diagnose seq-ownership violations without a session.
227pub fn diagnose_seq_ownership_violations(
228    violations: &[SeqOwnershipViolation],
229    span: Span,
230) -> Diagnostics {
231    let mut diags = Diagnostics::default();
232    for v in violations {
233        diags.push(Diagnostic {
234            span,
235            code: v.code().into(),
236            en: format!(
237                "sequential synthesizable-closure ownership violation: {} ({}); \
238                 Cap-R-70 forbids illegal extra mutable signal borrows / multi-drive \
239                 patterns inside seq inline (FR75)",
240                v.kind.label(),
241                v.detail
242            ),
243            zh: format!(
244                "时序可综合闭包所有权违规:{}({});Cap-R-70 禁止闭包体内额外非法可变信号借用/多驱动(FR75)",
245                v.kind.label(),
246                v.detail
247            ),
248        });
249    }
250    diags
251}
252
253/// Kind of SynthesizableClosure constraint breach (FR74 / Cap-R-48…50).
254///
255/// Distinct from FR16 [`ElaborateSession::reject_unsynthesizable`] (`rhdl::E0141`)
256/// and hardware-ref capture [`ElaborateSession::reject_hw_capture`] (`rhdl::E0142`).
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub enum SynthesizableClosureViolationKind {
259    /// Heap allocation in closure body / environment (Cap-R-48) → `rhdl::E0143`.
260    Heap,
261    /// Runtime (non-const) capture state (Cap-R-49) → `rhdl::E0144`.
262    RuntimeCaptureState,
263    /// Impure / side-effecting body (Cap-R-50) → `rhdl::E0145`.
264    Impure,
265}
266
267impl SynthesizableClosureViolationKind {
268    pub fn code(self) -> &'static str {
269        match self {
270            Self::Heap => "rhdl::E0143",
271            Self::RuntimeCaptureState => "rhdl::E0144",
272            Self::Impure => "rhdl::E0145",
273        }
274    }
275
276    fn label(self) -> &'static str {
277        match self {
278            Self::Heap => "heap allocation",
279            Self::RuntimeCaptureState => "runtime capture state",
280            Self::Impure => "impure / side-effecting body",
281        }
282    }
283}
284
285/// Documented SynthesizableClosure violation token (Story 28.1 / Cap-R-60).
286///
287/// Pass to [`ElaborateSession::reject_unsynthesizable_closure`] /
288/// [`ElaborateSession::check_synthesizable_closure`]. Empty violation list =
289/// legal empty/simple closure.
290#[derive(Debug, Clone, PartialEq, Eq)]
291pub struct SynthesizableClosureViolation {
292    pub kind: SynthesizableClosureViolationKind,
293    pub detail: String,
294}
295
296impl SynthesizableClosureViolation {
297    pub fn heap(detail: impl Into<String>) -> Self {
298        Self {
299            kind: SynthesizableClosureViolationKind::Heap,
300            detail: detail.into(),
301        }
302    }
303
304    pub fn runtime_capture_state(detail: impl Into<String>) -> Self {
305        Self {
306            kind: SynthesizableClosureViolationKind::RuntimeCaptureState,
307            detail: detail.into(),
308        }
309    }
310
311    pub fn impure(detail: impl Into<String>) -> Self {
312        Self {
313            kind: SynthesizableClosureViolationKind::Impure,
314            detail: detail.into(),
315        }
316    }
317
318    pub fn code(&self) -> &'static str {
319        self.kind.code()
320    }
321}
322
323/// Cap-R-60 check surface: diagnose SynthesizableClosure violations without a
324/// session (CLI / `cargo bitloom check` can call this; session methods wrap it).
325pub fn diagnose_synthesizable_closure_violations(
326    violations: &[SynthesizableClosureViolation],
327    span: Span,
328) -> Diagnostics {
329    let mut diags = Diagnostics::default();
330    for v in violations {
331        diags.push(Diagnostic {
332            span,
333            code: v.code().into(),
334            en: format!(
335                "synthesizable-closure violation: {} ({}); \
336                 SynthesizableClosure requires pure, no-heap, no runtime capture state \
337                 (FR74 / Cap-R-48…50)",
338                v.kind.label(),
339                v.detail
340            ),
341            zh: format!(
342                "可综合闭包违规:{}({});SynthesizableClosure 要求纯函数、无堆、无运行时捕获状态(FR74 / Cap-R-48…50)",
343                v.kind.label(),
344                v.detail
345            ),
346        });
347    }
348    diags
349}
350
351/// Plain instance spec produced by an elaborate-time factory `Fn` (FR73 / Cap-R-53).
352///
353/// Dissolves to ordinary [`bitloom_hir::Stmt::Instance`] via
354/// [`ElaborateSession::generate_instances_from`] / [`ElaborateSession::add_instance`].
355/// Never stored as a closure in FrozenHir (NFR36).
356#[derive(Debug, Clone)]
357pub struct GeneratedInstance {
358    pub name: String,
359    pub module: String,
360    /// `(child_port, parent_net)` pairs — same as [`ElaborateSession::add_instance`].
361    pub connects: Vec<(String, String)>,
362    pub params: Vec<(String, u32)>,
363}
364
365impl GeneratedInstance {
366    pub fn new(
367        name: impl Into<String>,
368        module: impl Into<String>,
369        connects: Vec<(String, String)>,
370        params: Vec<(String, u32)>,
371    ) -> Self {
372        Self {
373            name: name.into(),
374            module: module.into(),
375            connects,
376            params,
377        }
378    }
379}