logicaffeine_compile/codegen_sva/unified_placement.rs
1//! unified_placement — Phase 5: ONE LOGOS program placed across THREE execution planes that all run
2//! LOGOS (host-CPU tiers, the reconfigurable fabric-CPU, and the DRAM-compute plane), under a single
3//! shared primitive descriptor, certified ≡ the VM.
4//!
5//! The bet of the whole campaign, made mechanical: a bulk-bitwise/copy LOGOS op can run *inside the
6//! memory* (ComputeDRAM), a spatial op on the fabric, and everything else on the host CPU — and the
7//! compiler places each op where it wins, with the shared DRAM heap as the common data plane and no
8//! marshalling between planes. Because every plane's per-op semantics are individually ≡VM (host is
9//! the reference; the DRAM path is `super::dram::word_majority` — AND=MAJ(a,b,0), OR=MAJ(a,b,1),
10//! proven in Phase 0; the fabric computes the same bitwise result), the placed whole program is ≡VM.
11//!
12//! `PRIMS` is the SINGLE SOURCE OF TRUTH: both the placement pass and the per-plane executors read it,
13//! and a drift lock proves the DRAM executor supports exactly the ops the descriptor advertises.
14
15use super::dram::word_majority;
16
17/// The execution planes, all of which run LOGOS.
18#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
19pub enum Plane {
20 /// The i9 running the interpreter / VM / JIT / AOT / WASM tiers — universal (runs every op).
21 HostCpu,
22 /// The reconfigurable spatial fabric.
23 Fabric,
24 /// Compute-in-memory: bulk bitwise / copy inside the DDR2 (ComputeDRAM).
25 Dram,
26}
27
28/// The op kinds in the shared primitive vocabulary.
29#[derive(Clone, Copy, PartialEq, Eq, Debug)]
30pub enum PrimKind {
31 Copy,
32 And,
33 Or,
34 Not,
35 Xor,
36 Popcount,
37}
38
39/// A bulk primitive over whole bit-vector rows in the shared data plane (row indices into the heap).
40#[derive(Clone, Copy, PartialEq, Eq, Debug)]
41pub enum Prim {
42 Copy {
43 dst: usize,
44 src: usize,
45 },
46 And {
47 dst: usize,
48 a: usize,
49 b: usize,
50 },
51 Or {
52 dst: usize,
53 a: usize,
54 b: usize,
55 },
56 Not {
57 dst: usize,
58 src: usize,
59 },
60 Xor {
61 dst: usize,
62 a: usize,
63 b: usize,
64 },
65 /// Count the set bits of `src` into word 0 of `dst` (a reduction — host-CPU only).
66 Popcount {
67 dst: usize,
68 src: usize,
69 },
70}
71
72impl Prim {
73 pub fn kind(&self) -> PrimKind {
74 match self {
75 Prim::Copy { .. } => PrimKind::Copy,
76 Prim::And { .. } => PrimKind::And,
77 Prim::Or { .. } => PrimKind::Or,
78 Prim::Not { .. } => PrimKind::Not,
79 Prim::Xor { .. } => PrimKind::Xor,
80 Prim::Popcount { .. } => PrimKind::Popcount,
81 }
82 }
83}
84
85/// One entry of the shared primitive descriptor: which planes can realize this op. The DRAM plane has
86/// TWO tiers: `dram` = unmodified ComputeDRAM (Gao et al., MICRO 2019 — RowClone + MAJ-based AND/OR);
87/// `dram_ambit` = the Ambit tier (Seshadri et al., MICRO 2017), which adds bulk NOT (dual-contact
88/// cells) and thereby XOR, at the cost of MINOR DRAM modifications. `dram_ambit` is a superset of
89/// `dram`.
90#[derive(Clone, Copy, Debug)]
91pub struct PrimDesc {
92 pub kind: PrimKind,
93 pub host_cpu: bool,
94 pub fabric: bool,
95 pub dram: bool,
96 pub dram_ambit: bool,
97}
98
99/// THE SINGLE SOURCE OF TRUTH. host_cpu is always true (the host runs everything — the universal
100/// fallback). `dram` is true only for the ops UNMODIFIED ComputeDRAM realizes: RowClone (copy) + bulk
101/// bitwise AND/OR (the sense-amp majority) — the conservative tier the placement pass routes to.
102/// `dram_ambit` additionally covers NOT + XOR (the Ambit boolean basis) on minor-modification DRAM;
103/// the codegen_dram target uses this tier. Reductions (Popcount) are control-shaped → host only, on
104/// neither tier.
105pub const PRIMS: &[PrimDesc] = &[
106 PrimDesc {
107 kind: PrimKind::Copy,
108 host_cpu: true,
109 fabric: true,
110 dram: true,
111 dram_ambit: true,
112 },
113 PrimDesc {
114 kind: PrimKind::And,
115 host_cpu: true,
116 fabric: true,
117 dram: true,
118 dram_ambit: true,
119 },
120 PrimDesc {
121 kind: PrimKind::Or,
122 host_cpu: true,
123 fabric: true,
124 dram: true,
125 dram_ambit: true,
126 },
127 PrimDesc {
128 kind: PrimKind::Not,
129 host_cpu: true,
130 fabric: true,
131 dram: false,
132 dram_ambit: true,
133 },
134 PrimDesc {
135 kind: PrimKind::Xor,
136 host_cpu: true,
137 fabric: true,
138 dram: false,
139 dram_ambit: true,
140 },
141 PrimDesc {
142 kind: PrimKind::Popcount,
143 host_cpu: true,
144 fabric: false,
145 dram: false,
146 dram_ambit: false,
147 },
148];
149
150pub fn desc(kind: PrimKind) -> &'static PrimDesc {
151 PRIMS
152 .iter()
153 .find(|d| d.kind == kind)
154 .expect("every PrimKind has a descriptor")
155}
156
157/// Operands at least this many u64 words wide favor in-DRAM execution (the bytes-not-moved win).
158pub const WIDE_WORDS: usize = 8;
159
160/// The placement pass: choose the plane for `prim` given its operand width, from the shared descriptor.
161/// Bulk/wide bitwise + copy → DRAM (compute-in-memory); otherwise the fabric; else the host CPU (which
162/// runs everything). Never fails — the host is the universal fallback.
163pub fn place(prim: &Prim, operand_words: usize) -> Plane {
164 let d = desc(prim.kind());
165 if d.dram && operand_words >= WIDE_WORDS {
166 Plane::Dram // bulk/wide bitwise or copy: compute in place, move no bytes across the bus
167 } else if d.fabric {
168 Plane::Fabric // spatial/parallel bitwise the DRAM can't do (or too narrow to win in-memory)
169 } else {
170 debug_assert!(d.host_cpu, "every op is at least host-CPU realizable");
171 Plane::HostCpu // reductions / control — the universal fallback
172 }
173}
174
175/// A row of the shared data plane: a bit vector packed into u64 words.
176pub type Row = Vec<u64>;
177
178/// The reference (VM) semantics of a primitive over the heap — the oracle every plane is proven against.
179pub fn exec_reference(heap: &mut [Row], prim: &Prim) {
180 match *prim {
181 Prim::Copy { dst, src } => heap[dst] = heap[src].clone(),
182 Prim::And { dst, a, b } => {
183 heap[dst] = heap[a].iter().zip(&heap[b]).map(|(x, y)| x & y).collect()
184 }
185 Prim::Or { dst, a, b } => {
186 heap[dst] = heap[a].iter().zip(&heap[b]).map(|(x, y)| x | y).collect()
187 }
188 Prim::Not { dst, src } => heap[dst] = heap[src].iter().map(|x| !x).collect(),
189 Prim::Xor { dst, a, b } => {
190 heap[dst] = heap[a].iter().zip(&heap[b]).map(|(x, y)| x ^ y).collect()
191 }
192 Prim::Popcount { dst, src } => {
193 let n: u64 = heap[src].iter().map(|w| w.count_ones() as u64).sum();
194 let width = heap[dst].len();
195 heap[dst] = std::iter::once(n)
196 .chain(std::iter::repeat(0))
197 .take(width)
198 .collect();
199 }
200 }
201}
202
203/// Execute `prim` on the DRAM-compute plane (ComputeDRAM semantics): RowClone for copy, the sense-amp
204/// majority reduction for AND (MAJ(a,b,0)) and OR (MAJ(a,b,1)). Returns `None` for ops with no plain-
205/// ComputeDRAM realization (the descriptor's `dram: false` ops). MUST agree op-for-op with [`exec_reference`].
206pub fn exec_dram(heap: &mut [Row], prim: &Prim) -> Option<()> {
207 match *prim {
208 Prim::Copy { dst, src } => heap[dst] = heap[src].clone(), // RowClone
209 Prim::And { dst, a, b } => {
210 heap[dst] = heap[a]
211 .iter()
212 .zip(&heap[b])
213 .map(|(x, y)| word_majority(*x, *y, 0))
214 .collect()
215 }
216 Prim::Or { dst, a, b } => {
217 heap[dst] = heap[a]
218 .iter()
219 .zip(&heap[b])
220 .map(|(x, y)| word_majority(*x, *y, u64::MAX))
221 .collect()
222 }
223 // No plain-ComputeDRAM realization (matches the descriptor's dram: false): NOT needs
224 // Ambit-style dual-contact cells; XOR is multi-step; Popcount is a reduction → fabric/host.
225 Prim::Not { .. } | Prim::Xor { .. } | Prim::Popcount { .. } => return None,
226 }
227 Some(())
228}
229
230/// Execute `prim` on the DRAM plane at the AMBIT tier (Seshadri et al., MICRO 2017): the unmodified
231/// ComputeDRAM ops ([`exec_dram`]) PLUS bulk NOT (dual-contact cells) and XOR (NOT + triple-row MAJ,
232/// a multi-row in-DRAM sequence — the RESULT is exactly `a ^ b`, the ≡VM contract; the sequencing is
233/// the in-DRAM implementation detail). Ambit needs MINOR DRAM modifications, distinct from the
234/// unmodified [`exec_dram`]. Returns `None` only for the reduction (Popcount is control-shaped →
235/// host). Realizes EXACTLY the descriptor's `dram_ambit` ops, and agrees op-for-op with
236/// [`exec_reference`].
237pub fn exec_dram_ambit(heap: &mut [Row], prim: &Prim) -> Option<()> {
238 match *prim {
239 Prim::Copy { .. } | Prim::And { .. } | Prim::Or { .. } => return exec_dram(heap, prim),
240 Prim::Not { dst, src } => heap[dst] = heap[src].iter().map(|x| !x).collect(),
241 Prim::Xor { dst, a, b } => {
242 heap[dst] = heap[a].iter().zip(&heap[b]).map(|(x, y)| x ^ y).collect()
243 }
244 Prim::Popcount { .. } => return None,
245 }
246 Some(())
247}
248
249/// Execute `prim` on the fabric plane. The fabric computes the same bitwise result as the VM (its
250/// tile ALUs are proven ≡VM elsewhere); returns `false` for ops with no fabric realization.
251pub fn exec_fabric(heap: &mut [Row], prim: &Prim) -> bool {
252 if !desc(prim.kind()).fabric {
253 return false;
254 }
255 exec_reference(heap, prim);
256 true
257}
258
259/// Dispatch `prim` to `plane`, asserting the plane can realize it (placement must never assign an op to
260/// a plane that cannot run it). Each plane's result equals the VM reference (proven by the locks).
261pub fn exec_on(plane: Plane, heap: &mut [Row], prim: &Prim) {
262 match plane {
263 Plane::HostCpu => exec_reference(heap, prim),
264 Plane::Fabric => assert!(
265 exec_fabric(heap, prim),
266 "fabric cannot run {:?}",
267 prim.kind()
268 ),
269 Plane::Dram => {
270 exec_dram(heap, prim).unwrap_or_else(|| panic!("DRAM cannot run {:?}", prim.kind()))
271 }
272 }
273}
274
275/// Run a program with placement: each op executes on the plane the cost model assigns it. `operand_words`
276/// is the row width (the placement's size input). Returns the final heap.
277pub fn run_placed(heap0: &[Row], program: &[Prim], operand_words: usize) -> Vec<Row> {
278 let mut heap = heap0.to_vec();
279 for prim in program {
280 exec_on(place(prim, operand_words), &mut heap, prim);
281 }
282 heap
283}
284
285/// Run a program entirely on the host CPU — the VM reference.
286pub fn run_reference(heap0: &[Row], program: &[Prim]) -> Vec<Row> {
287 let mut heap = heap0.to_vec();
288 for prim in program {
289 exec_reference(&mut heap, prim);
290 }
291 heap
292}
293
294#[cfg(test)]
295#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/unified_placement/tests.rs"]
296mod tests;