Skip to main content

logicaffeine_compile/codegen_dram/
mod.rs

1//! codegen_dram — the DRAM compute-in-memory target (ComputeDRAM bulk bitwise + RowClone).
2//!
3//! DRAM-via-ComputeDRAM is a CO-PROCESSOR target, like [`crate::codegen_ana`]: it realizes a
4//! restricted op subset (the charge-sharing sense amps do bulk bitwise AND/OR and RowClone), and
5//! everything else runs on the host/fabric control side. So it uses the same FOUR-way split as the
6//! analog tier:
7//!
8//! - **OnPlane** — bitwise/copy the DRAM sense amps compute in place, each with a witnessed
9//!   [`ComputeDramOp`](crate::codegen_sva::dram::ComputeDramOp) realization proven ≡VM by the
10//!   [`RowStore`](crate::codegen_sva::dram::RowStore) oracle and the
11//!   [`unified_placement`](crate::codegen_sva::unified_placement) equivalence locks;
12//! - **Boundary** — data staging: operand rows written into the DRAM, result rows read back out;
13//! - **ControlPlane** — digital-exact ops the host runs VM-identically (arithmetic, comparisons,
14//!   control flow — the DRAM has no ALU and no sequencer), refusing nothing;
15//! - **OffPlane(reason)** — refuses the whole DRAM offload (concurrency / networking — there is no
16//!   scheduler or host-IO story inside a memory device).
17//!
18//! [`fragment`] is an EXHAUSTIVE match on `Op` (no `_` arm): a new VM instruction fails to compile
19//! this crate until it is deliberately classified — the anti-drift pit of success. [`dram_prim`]
20//! ties every OnPlane op to the exact ComputeDRAM primitive that realizes it, and the op-coverage
21//! ratchet (`tests/dram_op_coverage_lock.rs`) proves that primitive is DRAM-capable and ≡VM. You
22//! cannot mark an op OnPlane without a proven in-DRAM realization.
23//!
24//! Beyond the single-op layer, [`dataflow`] is the SPATIAL-DATAFLOW layer: the hardware basis
25//! `{Copy, And, Or}` (ComputeDRAM) + `Not` (Ambit) is functionally complete, so chaining primitives
26//! through intermediate rows realizes any boolean function and, bit-serially, integer arithmetic —
27//! the memory computes a dataflow graph in place.
28
29pub mod dataflow;
30pub mod kernels;
31pub mod set_algebra;
32
33use crate::codegen_sva::unified_placement::PrimKind;
34use crate::vm::{CompiledProgram, Op};
35
36/// Why the DRAM tier declines a program — a SOUND refusal, never wrong memory.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum DramLowerError {
39    /// The program uses an op with no home in a memory device (concurrency / networking).
40    Unsupported(&'static str),
41    /// The program has no bulk-bitwise/copy work to offload into the DRAM plane.
42    NoBitwiseRegion,
43    /// An OnPlane op has no witnessed ComputeDRAM primitive — a named lowering gap, never a
44    /// silent drop.
45    UnloweredOp(String),
46}
47
48impl std::fmt::Display for DramLowerError {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            DramLowerError::Unsupported(what) => write!(f, "unsupported {what}"),
52            DramLowerError::NoBitwiseRegion => write!(
53                f,
54                "no DRAM region: the program has no bulk-bitwise/copy work to offload into the DRAM plane"
55            ),
56            DramLowerError::UnloweredOp(what) => write!(f, "unlowered op: {what}"),
57        }
58    }
59}
60
61impl std::error::Error for DramLowerError {}
62
63/// Where an op lives in the DRAM-co-processor model.
64#[derive(Clone, Copy, PartialEq, Eq, Debug)]
65pub enum DramFragment {
66    OnPlane,
67    Boundary,
68    ControlPlane,
69    OffPlane(&'static str),
70}
71
72/// ★ THE SINGLE SOURCE OF TRUTH ★ for how the DRAM tier classifies every VM op.
73/// EXHAUSTIVE on `Op` (no `_` arm) — a new instruction cannot escape classification.
74pub fn fragment(op: &Op) -> DramFragment {
75    use DramFragment::{Boundary as Bd, ControlPlane as Cp, OffPlane as Off, OnPlane as On};
76    match op {
77        // ── OnPlane: the sense amps compute this in place (bulk bitwise + RowClone). ──
78        // BitAnd/BitOr are the sense-amp majority MAJ(a,b,0)/MAJ(a,b,1); the set ops are the same
79        // reduction over a bitset representation (intersection = AND, union = OR); DeepClone is
80        // RowClone — all UNMODIFIED ComputeDRAM. BitXor needs the AMBIT tier (dual-contact NOT +
81        // MAJ) — a minor-DRAM-modification hardware assumption flagged by [`dram_tier`]. Each has a
82        // proven in-DRAM primitive — see [`dram_prim`].
83        Op::BitAnd { .. } | Op::BitOr { .. } | Op::BitXor { .. } => On,
84        Op::IntersectOp { .. } | Op::UnionOp { .. } => On,
85        Op::DeepClone { .. } => On,
86
87        // ── Boundary: staging operand rows into the plane and reading results back out. ──
88        Op::LoadConst { .. } | Op::Move { .. } | Op::Show { .. } => Bd,
89        Op::NewList { .. }
90        | Op::NewEmptyList { .. }
91        | Op::NewEmptyListWithCapacity { .. }
92        | Op::NewEmptyListFixed { .. }
93        | Op::NewEmptyListI32 { .. }
94        | Op::NewEmptySet { .. }
95        | Op::NewEmptyMap { .. }
96        | Op::NewEmptyMapWithCapacity { .. }
97        | Op::NewRange { .. } => Bd,
98        Op::ListPush { .. } | Op::SetAdd { .. } => Bd,
99        Op::Index { .. } | Op::IndexUnchecked { .. } | Op::Length { .. } => Bd,
100        Op::IterPrepare { .. } | Op::IterNext { .. } | Op::IterPop => Bd,
101
102        // ── ControlPlane: digital-exact — the host runs these VM-identically (no ALU/sequencer in
103        // DRAM). BitXor has no plain-ComputeDRAM primitive (multi-step De Morgan); Not needs
104        // Ambit-style dual-contact cells — honest gaps, kept on the host until a witnessed tile.
105        Op::Add { .. } | Op::AddAssign { .. } | Op::Sub { .. } | Op::Mul { .. } => Cp,
106        Op::Div { .. } | Op::ExactDiv { .. } | Op::FloorDiv { .. } | Op::Mod { .. } => Cp,
107        Op::DivPow2 { .. } | Op::MagicDivU { .. } | Op::Pow { .. } => Cp,
108        Op::Lt { .. } | Op::Gt { .. } | Op::LtEq { .. } | Op::GtEq { .. } => Cp,
109        Op::Eq { .. } | Op::NotEq { .. } | Op::ApproxEq { .. } | Op::Not { .. } => Cp,
110        Op::Shl { .. } | Op::Shr { .. } => Cp,
111        Op::Concat { .. } | Op::SeqConcat { .. } | Op::FormatValue { .. } => Cp,
112        Op::SliceOp { .. } | Op::Contains { .. } | Op::RemoveFrom { .. } => Cp,
113        Op::SetIndex { .. } | Op::SetIndexUnchecked { .. } | Op::RegionBoundsGuard { .. } => Cp,
114        Op::Jump { .. } | Op::JumpIfFalse { .. } | Op::JumpIfTrue { .. } => Cp,
115        Op::Call { .. }
116        | Op::CallBuiltin { .. }
117        | Op::CallValue { .. }
118        | Op::MakeClosure { .. } => Cp,
119        Op::Return { .. } | Op::ReturnNothing | Op::Halt => Cp,
120        Op::GlobalGet { .. } | Op::GlobalSet { .. } => Cp,
121        Op::EnsureOwned { .. } | Op::CheckPolicy { .. } | Op::ListPushField { .. } => Cp,
122        Op::LoadToday { .. } | Op::LoadNow { .. } => Cp,
123        Op::NewTuple { .. } | Op::DestructureTuple { .. } | Op::ListPop { .. } => Cp,
124        Op::NewStruct { .. } | Op::StructInsert { .. } | Op::GetField { .. } => Cp,
125        Op::NewInductive { .. } | Op::TestArm { .. } | Op::BindArm { .. } => Cp,
126        Op::NewCrdt { .. }
127        | Op::CrdtBump { .. }
128        | Op::CrdtMerge { .. }
129        | Op::CrdtAppend { .. }
130        | Op::CrdtResolve { .. }
131        | Op::CrdtJoin { .. } => Cp,
132        Op::BigIntWide { .. } | Op::Obs { .. } | Op::FailWith { .. } | Op::Args { .. } => Cp,
133        Op::RevSwap { .. }
134        | Op::RevXor { .. }
135        | Op::RevNeg { .. }
136        | Op::RevAdd { .. }
137        | Op::RevSub { .. } => Cp,
138
139        // ── OffPlane: refuses the whole offload — no scheduler / host IO inside a memory device. ──
140        Op::ChanNew { .. }
141        | Op::ChanSend { .. }
142        | Op::ChanRecv { .. }
143        | Op::ChanTrySend { .. }
144        | Op::ChanTryRecv { .. }
145        | Op::ChanClose { .. } => Off("channel"),
146        Op::Spawn { .. } | Op::SpawnHandle { .. } | Op::TaskAwait { .. } | Op::TaskAbort { .. } => {
147            Off("task")
148        }
149        Op::Sleep { .. }
150        | Op::SelectArmRecv { .. }
151        | Op::SelectArmTimeout { .. }
152        | Op::SelectWait { .. } => Off("virtual-time/select"),
153        Op::NetConnect { .. }
154        | Op::NetListen { .. }
155        | Op::NetSend { .. }
156        | Op::NetStream { .. }
157        | Op::NetAwait { .. }
158        | Op::NetMakePeer { .. }
159        | Op::NetSync { .. }
160        | Op::Tcp { .. }
161        | Op::Serve { .. } => Off("networking"),
162    }
163}
164
165/// For an OnPlane op, the ComputeDRAM primitive that realizes it (proven ≡VM in
166/// [`unified_placement`](crate::codegen_sva::unified_placement)). `None` for every non-OnPlane op —
167/// the op-coverage ratchet asserts this biconditional (OnPlane ⟺ `dram_prim` is `Some`).
168pub fn dram_prim(op: &Op) -> Option<PrimKind> {
169    match op {
170        Op::BitAnd { .. } | Op::IntersectOp { .. } => Some(PrimKind::And),
171        Op::BitOr { .. } | Op::UnionOp { .. } => Some(PrimKind::Or),
172        Op::BitXor { .. } => Some(PrimKind::Xor),
173        Op::DeepClone { .. } => Some(PrimKind::Copy),
174        _ => None,
175    }
176}
177
178/// The in-DRAM hardware tier an OnPlane op needs — the HONEST hardware assumption. Copy/AND/OR run on
179/// UNMODIFIED off-the-shelf DRAM (ComputeDRAM, Gao et al. MICRO 2019); XOR needs the AMBIT tier
180/// (dual-contact cells, a minor DRAM modification; Seshadri et al. MICRO 2017). `None` iff not OnPlane.
181#[derive(Clone, Copy, PartialEq, Eq, Debug)]
182pub enum DramTier {
183    ComputeDram,
184    Ambit,
185}
186
187pub fn dram_tier(op: &Op) -> Option<DramTier> {
188    match op {
189        Op::BitAnd { .. }
190        | Op::BitOr { .. }
191        | Op::IntersectOp { .. }
192        | Op::UnionOp { .. }
193        | Op::DeepClone { .. } => Some(DramTier::ComputeDram),
194        Op::BitXor { .. } => Some(DramTier::Ambit),
195        _ => None,
196    }
197}
198
199/// Returns `Some(reason)` if `program` uses an op with no home in a memory device — concurrency
200/// (a DRAM has no thread scheduler) or networking (host-only IO). Shares the offload classifier
201/// with the other single-device tiers so they cannot drift.
202pub fn unsupported_reason(program: &CompiledProgram) -> Option<&'static str> {
203    use crate::aot_unsupported::Offload;
204    crate::aot_unsupported::first(program).map(|o| match o {
205        Offload::Channel => "channel op (a DRAM device has no channels)",
206        Offload::Task => "task spawn/await (a DRAM device has no thread scheduler)",
207        Offload::VirtualTime => "virtual-time / select (needs a cooperative scheduler)",
208        Offload::Net => "networking op (host-only IO)",
209        Offload::Dom => "DOM / browser op (the DRAM fabric has no document to build)",
210    })
211}
212
213/// Whether the program carries any OnPlane bulk-bitwise/copy work — the DRAM-region existence probe.
214pub fn has_bitwise_region(program: &CompiledProgram) -> bool {
215    program
216        .code
217        .iter()
218        .any(|op| matches!(fragment(op), DramFragment::OnPlane))
219}
220
221/// The fail-closed refusal gate every DRAM offload runs FIRST. `None` means the program clears the
222/// gate (a bulk-bitwise/copy region exists, inside the memory-device model).
223pub fn dram_refusal(program: &CompiledProgram) -> Option<DramLowerError> {
224    if let Some(reason) = unsupported_reason(program) {
225        return Some(DramLowerError::Unsupported(reason));
226    }
227    if !has_bitwise_region(program) {
228        return Some(DramLowerError::NoBitwiseRegion);
229    }
230    None
231}
232
233#[cfg(test)]
234#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_dram/tests.rs"]
235mod tests;