1use crate::vm::binisa::opcode;
27use crate::vm::{CompiledProgram, Constant, Op, Reg};
28
29fn reg(r: Reg) -> Result<u32, String> {
31 if r > 15 {
32 return Err(format!(
33 "register r{r} exceeds the scalar core's 16-register file"
34 ));
35 }
36 Ok(r as u32)
37}
38
39fn encode_op(op: &Op, constants: &[Constant]) -> Result<u32, String> {
43 let oc = opcode(op) as u32;
44 let w = |dst: u32, a: u32, b: u32| (oc << 24) | (dst << 16) | (a << 8) | b;
46 Ok(match op {
47 Op::RevAdd { place, delta } | Op::RevSub { place, delta } | Op::RevXor { place, delta } => {
49 w(reg(*place)?, reg(*delta)?, 0)
50 }
51 Op::RevNeg { place } => w(reg(*place)?, 0, 0),
52 Op::RevSwap { a, b } => w(reg(*a)?, reg(*b)?, 0),
53 Op::Move { dst, src } => w(reg(*dst)?, reg(*src)?, 0),
55 Op::LoadConst { dst, idx } => {
56 let val = match constants.get(*idx as usize) {
57 Some(Constant::Int(v)) => *v,
58 Some(Constant::Bool(b)) => *b as i64,
59 Some(other) => {
60 return Err(format!("LoadConst of a non-integer constant ({other:?}) is not chip-runnable"))
61 }
62 None => return Err(format!("LoadConst references missing constant #{idx}")),
63 };
64 if !(-32768..=32767).contains(&val) {
68 return Err(format!("constant {val} exceeds the signed 16-bit LoadConst immediate range [-32768, 32767]"));
69 }
70 (oc << 24) | (reg(*dst)? << 16) | (((val as i32) as u32) & 0xffff)
71 }
72 Op::Div { .. }
76 | Op::Mod { .. }
77 | Op::ExactDiv { .. }
78 | Op::FloorDiv { .. }
79 | Op::DivPow2 { .. }
80 | Op::MagicDivU { .. }
81 | Op::Pow { .. } => {
82 return Err(format!(
83 "`{}` needs the sequential divider the scalar core omits — only the combinational alu.v ops \
84 (add/sub/mul, bitwise, shifts, comparisons) run on the chip",
85 op.name()
86 ))
87 }
88 Op::Add { dst, lhs, rhs }
90 | Op::Sub { dst, lhs, rhs }
91 | Op::Mul { dst, lhs, rhs }
92 | Op::Lt { dst, lhs, rhs }
93 | Op::Gt { dst, lhs, rhs }
94 | Op::LtEq { dst, lhs, rhs }
95 | Op::GtEq { dst, lhs, rhs }
96 | Op::Eq { dst, lhs, rhs }
97 | Op::NotEq { dst, lhs, rhs }
98 | Op::BitXor { dst, lhs, rhs }
99 | Op::BitAnd { dst, lhs, rhs }
100 | Op::BitOr { dst, lhs, rhs }
101 | Op::Shl { dst, lhs, rhs }
102 | Op::Shr { dst, lhs, rhs } => w(reg(*dst)?, reg(*lhs)?, reg(*rhs)?),
103 Op::Not { dst, src } => w(reg(*dst)?, reg(*src)?, 0),
104 Op::AddAssign { dst, src } => {
108 let add = crate::vm::binisa::opcode_of_name("Add").expect("Add is a registered opcode") as u32;
109 (add << 24) | (reg(*dst)? << 16) | (reg(*dst)? << 8) | reg(*src)?
110 }
111 Op::Jump { target } => {
114 if *target > 255 {
115 return Err(format!("jump target {target} exceeds the 256-word imem"));
116 }
117 (oc << 24) | ((*target as u32) << 16)
118 }
119 Op::JumpIfFalse { cond, target } | Op::JumpIfTrue { cond, target } => {
120 if *target > 255 {
121 return Err(format!("jump target {target} exceeds the 256-word imem"));
122 }
123 (oc << 24) | ((*target as u32) << 16) | (reg(*cond)? << 8)
124 }
125 Op::Show { src } => w(0, reg(*src)?, 0),
127 Op::Halt | Op::FailWith { .. } => oc << 24,
128 other => return Err(format!("`{}` is outside the function-free scalar chip subset", other.name())),
131 })
132}
133
134fn scalar_io(op: &Op) -> Option<(Vec<Reg>, Vec<Reg>)> {
137 Some(match *op {
138 Op::RevAdd { place, delta } | Op::RevSub { place, delta } | Op::RevXor { place, delta } => {
139 (vec![place, delta], vec![place])
140 }
141 Op::RevNeg { place } => (vec![place], vec![place]),
142 Op::RevSwap { a, b } => (vec![a, b], vec![a, b]),
143 Op::Move { dst, src } | Op::Not { dst, src } => (vec![src], vec![dst]),
144 Op::LoadConst { dst, .. } => (vec![], vec![dst]),
145 Op::Add { dst, lhs, rhs }
146 | Op::Sub { dst, lhs, rhs }
147 | Op::Mul { dst, lhs, rhs }
148 | Op::Lt { dst, lhs, rhs }
149 | Op::Gt { dst, lhs, rhs }
150 | Op::LtEq { dst, lhs, rhs }
151 | Op::GtEq { dst, lhs, rhs }
152 | Op::Eq { dst, lhs, rhs }
153 | Op::NotEq { dst, lhs, rhs }
154 | Op::BitXor { dst, lhs, rhs }
155 | Op::BitAnd { dst, lhs, rhs }
156 | Op::BitOr { dst, lhs, rhs }
157 | Op::Shl { dst, lhs, rhs }
158 | Op::Shr { dst, lhs, rhs } => (vec![lhs, rhs], vec![dst]),
159 Op::AddAssign { dst, src } => (vec![dst, src], vec![dst]),
160 Op::JumpIfFalse { cond, .. } | Op::JumpIfTrue { cond, .. } => (vec![cond], vec![]),
161 Op::Show { src } => (vec![src], vec![]),
162 Op::Jump { .. } | Op::Halt | Op::FailWith { .. } => (vec![], vec![]),
163 _ => return None,
164 })
165}
166
167fn rename(op: &Op, to: &dyn Fn(Reg) -> Reg) -> Op {
169 match *op {
170 Op::RevAdd { place, delta } => Op::RevAdd {
171 place: to(place),
172 delta: to(delta),
173 },
174 Op::RevSub { place, delta } => Op::RevSub {
175 place: to(place),
176 delta: to(delta),
177 },
178 Op::RevXor { place, delta } => Op::RevXor {
179 place: to(place),
180 delta: to(delta),
181 },
182 Op::RevNeg { place } => Op::RevNeg { place: to(place) },
183 Op::RevSwap { a, b } => Op::RevSwap { a: to(a), b: to(b) },
184 Op::Move { dst, src } => Op::Move {
185 dst: to(dst),
186 src: to(src),
187 },
188 Op::Not { dst, src } => Op::Not {
189 dst: to(dst),
190 src: to(src),
191 },
192 Op::LoadConst { dst, idx } => Op::LoadConst { dst: to(dst), idx },
193 Op::Add { dst, lhs, rhs } => Op::Add {
194 dst: to(dst),
195 lhs: to(lhs),
196 rhs: to(rhs),
197 },
198 Op::Sub { dst, lhs, rhs } => Op::Sub {
199 dst: to(dst),
200 lhs: to(lhs),
201 rhs: to(rhs),
202 },
203 Op::Mul { dst, lhs, rhs } => Op::Mul {
204 dst: to(dst),
205 lhs: to(lhs),
206 rhs: to(rhs),
207 },
208 Op::Lt { dst, lhs, rhs } => Op::Lt {
209 dst: to(dst),
210 lhs: to(lhs),
211 rhs: to(rhs),
212 },
213 Op::Gt { dst, lhs, rhs } => Op::Gt {
214 dst: to(dst),
215 lhs: to(lhs),
216 rhs: to(rhs),
217 },
218 Op::LtEq { dst, lhs, rhs } => Op::LtEq {
219 dst: to(dst),
220 lhs: to(lhs),
221 rhs: to(rhs),
222 },
223 Op::GtEq { dst, lhs, rhs } => Op::GtEq {
224 dst: to(dst),
225 lhs: to(lhs),
226 rhs: to(rhs),
227 },
228 Op::Eq { dst, lhs, rhs } => Op::Eq {
229 dst: to(dst),
230 lhs: to(lhs),
231 rhs: to(rhs),
232 },
233 Op::NotEq { dst, lhs, rhs } => Op::NotEq {
234 dst: to(dst),
235 lhs: to(lhs),
236 rhs: to(rhs),
237 },
238 Op::BitXor { dst, lhs, rhs } => Op::BitXor {
239 dst: to(dst),
240 lhs: to(lhs),
241 rhs: to(rhs),
242 },
243 Op::BitAnd { dst, lhs, rhs } => Op::BitAnd {
244 dst: to(dst),
245 lhs: to(lhs),
246 rhs: to(rhs),
247 },
248 Op::BitOr { dst, lhs, rhs } => Op::BitOr {
249 dst: to(dst),
250 lhs: to(lhs),
251 rhs: to(rhs),
252 },
253 Op::Shl { dst, lhs, rhs } => Op::Shl {
254 dst: to(dst),
255 lhs: to(lhs),
256 rhs: to(rhs),
257 },
258 Op::Shr { dst, lhs, rhs } => Op::Shr {
259 dst: to(dst),
260 lhs: to(lhs),
261 rhs: to(rhs),
262 },
263 Op::AddAssign { dst, src } => Op::AddAssign {
264 dst: to(dst),
265 src: to(src),
266 },
267 Op::JumpIfFalse { cond, target } => Op::JumpIfFalse {
268 cond: to(cond),
269 target,
270 },
271 Op::JumpIfTrue { cond, target } => Op::JumpIfTrue {
272 cond: to(cond),
273 target,
274 },
275 Op::Show { src } => Op::Show { src: to(src) },
276 other => other,
277 }
278}
279
280const SCALAR_REGISTERS: usize = 16;
282
283pub fn allocate_scalar_registers(code: &[Op]) -> Result<Vec<Op>, String> {
295 let io: Option<Vec<(Vec<Reg>, Vec<Reg>)>> = code.iter().map(scalar_io).collect();
296 let Some(io) = io else {
297 return Ok(code.to_vec());
298 };
299 let used: std::collections::BTreeSet<Reg> = io
300 .iter()
301 .flat_map(|(r, w)| r.iter().chain(w))
302 .copied()
303 .collect();
304 if used.iter().all(|&r| (r as usize) < SCALAR_REGISTERS) {
305 return Ok(code.to_vec());
306 }
307 let n = code.len();
308 let successors = |i: usize| -> Vec<usize> {
309 match code[i] {
310 Op::Jump { target } => vec![target],
311 Op::JumpIfFalse { target, .. } | Op::JumpIfTrue { target, .. } => vec![target, i + 1],
312 Op::Halt | Op::FailWith { .. } => vec![],
313 _ => vec![i + 1],
314 }
315 .into_iter()
316 .filter(|&s| s < n)
317 .collect()
318 };
319 use std::collections::{BTreeMap, BTreeSet};
320 let mut live_in: Vec<BTreeSet<Reg>> = vec![BTreeSet::new(); n];
321 let mut live_out: Vec<BTreeSet<Reg>> = vec![BTreeSet::new(); n];
322 loop {
323 let mut changed = false;
324 for i in (0..n).rev() {
325 let out: BTreeSet<Reg> = successors(i)
326 .into_iter()
327 .flat_map(|s| live_in[s].iter().copied())
328 .collect();
329 let (reads, writes) = &io[i];
330 let mut inn: BTreeSet<Reg> = out
331 .iter()
332 .copied()
333 .filter(|r| !writes.contains(r))
334 .collect();
335 inn.extend(reads.iter().copied());
336 if out != live_out[i] || inn != live_in[i] {
337 live_out[i] = out;
338 live_in[i] = inn;
339 changed = true;
340 }
341 }
342 if !changed {
343 break;
344 }
345 }
346 let mut adj: BTreeMap<Reg, BTreeSet<Reg>> =
347 used.iter().map(|&r| (r, BTreeSet::new())).collect();
348 let mut edge = |a: Reg, b: Reg| {
349 if a != b {
350 adj.get_mut(&a).unwrap().insert(b);
351 adj.get_mut(&b).unwrap().insert(a);
352 }
353 };
354 for i in 0..n {
355 let (_, writes) = &io[i];
356 let moved_from = match code[i] {
357 Op::Move { src, .. } => Some(src),
358 _ => None,
359 };
360 for &w in writes {
361 for &l in &live_out[i] {
362 if Some(l) != moved_from {
363 edge(w, l);
364 }
365 }
366 for &w2 in writes {
367 edge(w, w2);
368 }
369 }
370 }
371 for a in live_in[0].iter().copied().collect::<Vec<_>>() {
372 for b in live_in[0].iter().copied() {
373 edge(a, b);
374 }
375 }
376 let crowded = || {
377 (0..n)
378 .map(|i| {
379 (
380 live_out[i]
381 .iter()
382 .chain(&io[i].1)
383 .collect::<BTreeSet<_>>()
384 .len(),
385 i,
386 )
387 })
388 .max()
389 .unwrap_or((0, 0))
390 };
391 let mut remaining: BTreeSet<Reg> = used.clone();
392 let mut stack = Vec::with_capacity(used.len());
393 while !remaining.is_empty() {
394 let degree = |r: Reg| adj[&r].iter().filter(|x| remaining.contains(x)).count();
395 let pick = remaining
396 .iter()
397 .copied()
398 .find(|&r| degree(r) < SCALAR_REGISTERS)
399 .unwrap_or_else(|| {
400 remaining
401 .iter()
402 .copied()
403 .max_by_key(|&r| (degree(r), std::cmp::Reverse(r)))
404 .unwrap()
405 });
406 remaining.remove(&pick);
407 stack.push(pick);
408 }
409 let mut color: BTreeMap<Reg, Reg> = BTreeMap::new();
410 while let Some(r) = stack.pop() {
411 let taken: BTreeSet<Reg> = adj[&r]
412 .iter()
413 .filter_map(|x| color.get(x).copied())
414 .collect();
415 let Some(c) = (0..SCALAR_REGISTERS as Reg).find(|c| !taken.contains(c)) else {
416 let (live, at) = crowded();
417 return Err(format!(
418 "{live} values are live at once at instruction {at}, more than the scalar core's \
419 {SCALAR_REGISTERS} registers"
420 ));
421 };
422 color.insert(r, c);
423 }
424 Ok(code.iter().map(|op| rename(op, &|r| color[&r])).collect())
425}
426
427pub fn encode_soft_core_scalar(program: &CompiledProgram) -> Result<Vec<u32>, String> {
432 if !program.functions.is_empty() {
433 return Err("program defines user functions — the scalar encoder is function-free (calling-convention translation not yet implemented)".into());
434 }
435 if program.code.len() > 256 {
436 return Err(format!(
437 "{} instructions exceed the 256-word scalar imem",
438 program.code.len()
439 ));
440 }
441 allocate_scalar_registers(&program.code)?
442 .iter()
443 .enumerate()
444 .map(|(i, op)| {
445 encode_op(op, &program.constants).map_err(|e| format!("instruction {i}: {e}"))
446 })
447 .collect()
448}
449
450pub fn to_hex(words: &[u32]) -> String {
452 let mut s = String::with_capacity(words.len() * 9);
453 for w in words {
454 s.push_str(&format!("{w:08x}\n"));
455 }
456 s
457}
458
459pub fn compile_to_scalar_words(source: &str) -> Result<Vec<u32>, String> {
463 crate::ui_bridge::with_parsed_program(source, |parsed, interner| {
464 let (stmts, types, _policies) = parsed.map_err(|advice| format!("{advice}"))?;
465 let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
466 let program =
467 crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))?;
468 encode_soft_core_scalar(&program)
469 })
470}