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}