1use std::panic::{catch_unwind, AssertUnwindSafe};
8
9use super::fabric_rtl::program_cfg_fabric;
10use std::collections::{HashMap, HashSet};
11
12use super::chip_protocol::{TAG_FLOAT, TAG_TEXT};
13use super::wprog_engine_rtl::{
14 eval_wprog_config, eval_wprog_config_run, func_code_needs_fpu, program_calls_fabric, program_main_fabric,
15 program_output, serialize_program, wprog_config_fits_engine, wprog_config_fits_hw_engine,
16 wprog_config_fits_stream_engine, wprog_config_image_unchecked, wprog_nregs, WBlock, WCell, WProgConfig, WSrc,
17 WTerm,
18};
19use crate::compile::vm_outcome;
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum Lifter {
26 Cfg,
28 Calls,
30 Main,
32}
33
34impl Lifter {
35 pub const FUNCTION: [Lifter; 2] = [Lifter::Cfg, Lifter::Calls];
37
38 pub fn name(self) -> &'static str {
40 match self {
41 Lifter::Cfg => "cfg",
42 Lifter::Calls => "calls",
43 Lifter::Main => "main",
44 }
45 }
46
47 pub fn lift(self, src: &str, func: &str) -> WProgConfig {
49 match self {
50 Lifter::Cfg => serialize_program(&program_cfg_fabric(src, func)),
51 Lifter::Calls => program_calls_fabric(src, func),
52 Lifter::Main => program_main_fabric(src),
53 }
54 }
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub enum Profile {
61 Hw,
63 Stream,
65 Generic,
67}
68
69impl Profile {
70 pub const ALL: [Profile; 3] = [Profile::Hw, Profile::Stream, Profile::Generic];
71
72 pub fn name(self) -> &'static str {
73 match self {
74 Profile::Hw => "hw",
75 Profile::Stream => "stream",
76 Profile::Generic => "generic",
77 }
78 }
79
80 pub fn from_name(name: &str) -> Option<Profile> {
81 Profile::ALL.into_iter().find(|p| p.name() == name)
82 }
83
84 pub fn fits(self, cfg: &WProgConfig) -> Result<(), String> {
86 match self {
87 Profile::Hw => wprog_config_fits_hw_engine(cfg),
88 Profile::Stream => wprog_config_fits_stream_engine(cfg),
89 Profile::Generic => wprog_config_fits_engine(cfg),
90 }
91 }
92}
93
94fn attempt<T>(f: impl FnOnce() -> T) -> Option<T> {
97 let hook = std::panic::take_hook();
98 std::panic::set_hook(Box::new(|_| {}));
99 let r = catch_unwind(AssertUnwindSafe(f)).ok();
100 std::panic::set_hook(hook);
101 r
102}
103
104pub fn vm_golden(src: &str, func: &str, params: &[i64]) -> Result<i64, String> {
107 let al = params
108 .iter()
109 .map(|a| if *a < 0 { format!("(0 - {})", -a) } else { a.to_string() })
110 .collect::<Vec<_>>()
111 .join(", ");
112 let prog = format!("{src}## Main\n Show {func}({al}).\n");
113 let out = vm_outcome(&prog);
114 if let Some(e) = out.error {
115 return Err(format!("the VM cannot run {func}({al}): {e}"));
116 }
117 out.output
118 .split_whitespace()
119 .next()
120 .and_then(|w| w.parse::<i64>().ok())
121 .ok_or_else(|| format!("{func}({al}) did not show an Int on the VM: {:?}", out.output))
122}
123
124pub fn vm_output_lines(src: &str) -> Result<Vec<String>, String> {
127 let out = vm_outcome(src);
128 if let Some(e) = out.error {
129 return Err(format!("the VM cannot run the program: {e}"));
130 }
131 Ok(out.output.trim_end().lines().map(str::to_string).collect())
132}
133
134pub fn lower_main(src: &str) -> Result<(WProgConfig, Vec<String>), String> {
137 let vm = vm_output_lines(src)?;
138 let cfg = attempt(|| Lifter::Main.lift(src, "")).ok_or_else(|| "the Main lifter refused the program".to_string())?;
139 let model = attempt(|| program_output(&cfg, &[])).ok_or_else(|| "the engine model faulted".to_string())?;
140 if model != vm {
141 return Err(format!("the engine model's output {model:?} is not the VM's {vm:?}"));
142 }
143 Ok((cfg, vm))
144}
145
146pub fn lower(src: &str, func: &str, params: &[i64]) -> Result<(WProgConfig, Lifter, i64), String> {
150 let vm = vm_golden(src, func, params)?;
151 let mut tried = Vec::new();
152 for lifter in Lifter::FUNCTION {
153 let name = lifter.name();
154 match attempt(|| lifter.lift(src, func)) {
155 Some(cfg) => match attempt(|| eval_wprog_config(&cfg, params)) {
156 Some(got) if got == vm => return Ok((cfg, lifter, vm)),
157 Some(got) => tried.push(format!("{name} lifter gives {got}")),
158 None => tried.push(format!("{name} lifter's model faulted")),
159 },
160 None => tried.push(format!("{name} lifter refused the program")),
161 }
162 }
163 Err(format!("no lowering runs {func}({params:?}) ≡ VM (VM says {vm}; {})", tried.join("; ")))
164}
165
166pub fn fits_caps(cfg: &WProgConfig, params: &[i64], caps: &str) -> Result<(), String> {
171 let run = eval_wprog_config_run(cfg, params, &[]);
172 let im = wprog_config_image_unchecked(cfg);
173 let measured: Vec<(&str, usize)> = vec![
174 ("NREG", wprog_nregs(cfg)),
175 ("MAXBLK", cfg.blocks.len()),
176 ("MAXCELL", cfg.blocks.iter().map(|b| b.cells.len()).sum()),
177 ("MAXV", cfg.blocks.iter().map(|b| b.cells.len()).max().unwrap_or(0)),
178 ("MAXWR", cfg.blocks.iter().map(|b| b.writes.len()).sum()),
179 ("MAXLIV", cfg.blocks.iter().map(|b| b.livein.len()).sum()),
180 ("MAXARG", im.arg_op.len()),
181 ("MAXK", im.konst.len()),
182 ("HEAP", run.heap.len()),
183 ("DEPTH", run.peak_depth),
184 ];
185 for entry in caps.split(',').map(str::trim).filter(|e| !e.is_empty()) {
186 let (name, cap) = entry.split_once('=').ok_or_else(|| format!("bad --caps entry {entry:?} (want NAME=N)"))?;
187 let cap: usize = cap.trim().parse().map_err(|_| format!("bad --caps value in {entry:?}"))?;
188 if name.trim() == "FPU" {
191 if cap == 0 {
192 if let Some(c) =
193 cfg.blocks.iter().flat_map(|b| b.cells.iter()).find(|c| c.kind == 2 && func_code_needs_fpu(c.code))
194 {
195 return Err(format!(
196 "the program uses a float unit (func code {}) but the flashed image was built with FPU=0",
197 c.code
198 ));
199 }
200 }
201 continue;
202 }
203 let need = measured.iter().find(|(n, _)| *n == name.trim()).map(|(_, v)| *v).ok_or_else(|| {
204 format!("unknown cap {name:?} (known: NREG MAXBLK MAXCELL MAXV MAXWR MAXLIV MAXARG MAXK HEAP DEPTH)")
205 })?;
206 if need > cap {
207 return Err(format!("{name} needs {need} but the flashed image has {name}={cap}"));
208 }
209 }
210 Ok(())
211}
212
213#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
219pub struct FabricMap {
220 pub blocks: Vec<MapBlock>,
221}
222
223#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
225pub struct MapBlock {
226 pub role: &'static str,
230 pub first_cell: usize,
232 pub cells: Vec<String>,
234 pub says: Vec<String>,
237 pub next: MapNext,
239}
240
241#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
243#[serde(tag = "kind", rename_all = "lowercase")]
244pub enum MapNext {
245 Jump { to: usize },
246 Branch { cond: String, then: usize, otherwise: usize },
247 Return { value: String },
248 Call { callee: usize, args: Vec<String>, result: String, back: usize },
249}
250
251pub fn main_register_names(src: &str) -> HashMap<u16, String> {
254 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
255 let Ok((stmts, _types, _policies)) = parsed else {
256 return HashMap::new();
257 };
258 crate::vm::Compiler::compile_for_debug(stmts, interner, None)
259 .map(|p| p.reg_names.into_iter().collect())
260 .unwrap_or_default()
261 })
262}
263
264const ARITH: [&str; 9] = ["+", "-", "*", "&", "|", "^", "<<", ">>", ">>>"];
265const COMPARE: [&str; 6] = ["<", ">", "<=", ">=", "==", "!="];
266const UNIT: [&str; 21] = [
267 "/", "%", "//", "**", "*hi", "f+", "f-", "f*", "f/", "sqrt", "floor", "~=", "*", "float", "f<", "f>", "f<=", "f>=",
268 "f==", "f!=", "popcount",
269];
270
271fn float_unit(code: u8) -> bool {
273 matches!(code, 5..=11 | 14..=19)
274}
275
276fn cell_op(c: &WCell) -> String {
278 let pick = |table: &[&str]| table.get(c.code as usize).copied().unwrap_or("?").to_string();
279 match c.kind {
280 0 => pick(&ARITH),
281 1 => pick(&COMPARE),
282 2 => pick(&UNIT),
283 3 => "ld".into(),
284 4 => "st".into(),
285 5 => "new".into(),
286 6 => "copy".into(),
287 7 => "++".into(),
288 8 => "at".into(),
289 9 => (if c.code == 0 { "==" } else { "!=" }).into(),
290 10 => "show".into(),
291 _ => "?".into(),
292 }
293}
294
295pub fn fabric_map(cfg: &WProgConfig, names: &HashMap<u16, String>) -> FabricMap {
298 let reach = |through_calls: bool| {
299 let mut seen = HashSet::new();
300 let mut work = vec![0usize];
301 while let Some(i) = work.pop() {
302 if i >= cfg.blocks.len() || !seen.insert(i) {
303 continue;
304 }
305 match &cfg.blocks[i].term {
306 WTerm::Jump(t) => work.push(*t),
307 WTerm::Branch { then_blk, else_blk, .. } => work.extend([*then_blk, *else_blk]),
308 WTerm::Call { callee_block, ret_block, .. } => {
309 work.push(*ret_block);
310 if through_calls {
311 work.push(*callee_block);
312 }
313 }
314 WTerm::Return(_) => {}
315 }
316 }
317 seen
318 };
319 let (main, runs) = (reach(false), reach(true));
320 let callees: HashSet<usize> = cfg
321 .blocks
322 .iter()
323 .filter_map(|b| match &b.term {
324 WTerm::Call { callee_block, .. } => Some(*callee_block),
325 _ => None,
326 })
327 .collect();
328 let read: HashSet<u16> = cfg.blocks.iter().flat_map(|b| b.livein.iter().copied()).collect();
330 let float_regs: HashSet<u16> = cfg
332 .blocks
333 .iter()
334 .flat_map(|b| {
335 b.cells.iter().flat_map(move |c| {
336 let float = (c.kind == 2 && float_unit(c.code)) || (c.kind == 10 && c.code == TAG_FLOAT);
337 c.ops.iter().filter(move |s| float && s.mode == 0).filter_map(move |s| b.livein.get(s.reference as usize).copied())
338 })
339 })
340 .collect();
341 let none = HashMap::new();
342 let mut first = 0;
343 let blocks = cfg
344 .blocks
345 .iter()
346 .enumerate()
347 .map(|(i, b)| {
348 let role = if !runs.contains(&i) {
349 "idle"
350 } else if i == 0 {
351 "init"
352 } else if callees.contains(&i) {
353 "fn"
354 } else {
355 match &b.term {
356 WTerm::Return(_) => "end",
357 WTerm::Call { .. } => "call",
358 WTerm::Branch { .. } => "test",
359 WTerm::Jump(t) if *t <= i => "loop",
360 WTerm::Jump(_) => "step",
361 }
362 };
363 let text = BlockText::new(b, if main.contains(&i) { names } else { &none }, &float_regs);
364 let block = MapBlock {
365 role,
366 first_cell: first,
367 cells: b.cells.iter().map(cell_op).collect(),
368 says: text.says(&read),
369 next: text.next(),
370 };
371 first += b.cells.len();
372 block
373 })
374 .collect();
375 FabricMap { blocks }
376}
377
378struct Built {
380 items: Vec<WSrc>,
381 text: Option<String>,
382 kept_in: Option<u16>,
383}
384
385struct BlockText<'a> {
387 b: &'a WBlock,
388 names: &'a HashMap<u16, String>,
389 float_regs: &'a HashSet<u16>,
390 built: HashMap<usize, Built>,
391 consumed: HashSet<usize>,
392}
393
394impl<'a> BlockText<'a> {
395 fn new(b: &'a WBlock, names: &'a HashMap<u16, String>, float_regs: &'a HashSet<u16>) -> Self {
396 let mut t = BlockText { b, names, float_regs, built: HashMap::new(), consumed: HashSet::new() };
397 let is_val = |s: &WSrc, i: usize| s.mode == 2 && s.reference as usize == i;
398 let konst = |s: &WSrc| (s.mode == 1).then_some(s.konst);
399 for (a, c) in b.cells.iter().enumerate() {
400 let Some(size) = (c.kind == 5).then(|| konst(&c.ops[0])).flatten() else { continue };
401 let n = size - 1;
402 let Some(len_write) =
403 b.cells.iter().position(|w| w.kind == 4 && is_val(&w.ops[0], a) && konst(&w.ops[1]) == Some(n))
404 else {
405 continue;
406 };
407 let mut used = vec![len_write];
408 let mut items = Vec::new();
409 for j in 1..=n {
410 let add = b.cells.iter().position(|x| {
411 x.kind == 0 && x.code == 0 && is_val(&x.ops[0], a) && konst(&x.ops[1]) == Some(j)
412 });
413 let write = add.and_then(|k| b.cells.iter().position(|w| w.kind == 4 && is_val(&w.ops[0], k)));
414 match (add, write) {
415 (Some(k), Some(w)) => {
416 used.extend([k, w]);
417 items.push(b.cells[w].ops[1]);
418 }
419 _ => break,
420 }
421 }
422 if items.len() as i64 != n {
423 continue;
424 }
425 let read_as_text = b.cells.iter().any(|u| {
426 let reads = |slot: usize| is_val(&u.ops[slot], a);
427 (u.kind == 10 && u.code == TAG_TEXT && reads(0))
428 || (u.kind == 7 && ((u.code & 1 != 0 && reads(0)) || (u.code & 2 != 0 && reads(1))))
429 || (matches!(u.kind, 8 | 9) && (reads(0) || (u.kind == 9 && reads(1))))
430 });
431 let bytes: Option<Vec<i64>> = items.iter().map(konst).collect();
432 let text = bytes.filter(|v| read_as_text && v.iter().all(|&x| (0x20..0x7F).contains(&x))).map(|v| {
433 format!("\"{}\"", v.iter().map(|&x| x as u8 as char).collect::<String>())
434 });
435 let kept_in = b.writes.iter().find(|(_, s)| is_val(s, a)).map(|(r, _)| *r);
436 t.built.insert(a, Built { items, text, kept_in });
437 t.consumed.extend(used);
438 }
439 t
440 }
441
442 fn reg(&self, r: u16) -> String {
443 self.names.get(&r).cloned().unwrap_or_else(|| format!("r{r}"))
444 }
445
446 fn konst(&self, k: i64, float: bool) -> String {
447 if float {
448 let f = f64::from_bits(k as u64);
449 if f.fract() == 0.0 && f.abs() < 1e15 {
450 format!("{f:.1}")
451 } else {
452 format!("{f}")
453 }
454 } else {
455 k.to_string()
456 }
457 }
458
459 fn src_as(&self, s: &WSrc, float: bool) -> String {
460 match s.mode {
461 0 => self.b.livein.get(s.reference as usize).map_or_else(|| "?".into(), |&r| self.reg(r)),
462 1 => self.konst(s.konst, float),
463 _ => self.val(s.reference as usize),
464 }
465 }
466
467 fn src(&self, s: &WSrc) -> String {
468 self.src_as(s, false)
469 }
470
471 fn literal(&self, built: &Built) -> String {
473 built.text.clone().unwrap_or_else(|| format!("[{}]", built.items.iter().map(|s| self.src(s)).collect::<Vec<_>>().join(", ")))
474 }
475
476 fn element(&self, at: &WSrc) -> (String, Option<String>) {
478 if at.mode == 2 {
479 if let Some(c) = self.b.cells.get(at.reference as usize) {
480 if c.kind == 0 && c.code == 0 {
481 return (self.src(&c.ops[0]), Some(self.src(&c.ops[1])));
482 }
483 }
484 }
485 (self.src(at), None)
486 }
487
488 fn val(&self, i: usize) -> String {
489 if let Some(built) = self.built.get(&i) {
490 return built.kept_in.map(|r| self.reg(r)).unwrap_or_else(|| self.literal(built));
491 }
492 let Some(c) = self.b.cells.get(i) else { return format!("v{i}") };
493 let float = c.kind == 2 && float_unit(c.code);
494 let (a, b) = (self.src_as(&c.ops[0], float), self.src_as(&c.ops[1], float));
495 let op = cell_op(c);
496 let e = match c.kind {
497 0 | 7 | 9 => bin(&a, &op, &b),
498 1 => {
499 let cond = bin(&a, &op, &b);
500 if c.ops[2].mode == 1 && c.ops[2].konst == 1 && c.ops[3].mode == 1 && c.ops[3].konst == 0 {
501 cond
502 } else {
503 format!("({cond} ? {} : {})", self.src(&c.ops[2]), self.src(&c.ops[3]))
504 }
505 }
506 2 => match c.code {
507 9 | 10 | 13 | 20 => format!("{op}({a})"),
508 _ => bin(&a, &op, &b),
509 },
510 3 => match self.element(&c.ops[0]) {
511 (base, Some(j)) => format!("{base}[{j}]"),
512 (base, None) => format!("length({base})"),
513 },
514 4 => b,
515 5 => format!("new[{a}]"),
516 6 => format!("copy({a})"),
517 8 => format!("{a}[{b}]"),
518 10 => a,
519 _ => format!("v{i}"),
520 };
521 if e.len() > 48 {
522 format!("v{i}")
523 } else {
524 e
525 }
526 }
527
528 fn says(&self, read: &HashSet<u16>) -> Vec<String> {
531 let mut out = Vec::new();
532 let mut defined = HashSet::new();
533 for (i, c) in self.b.cells.iter().enumerate() {
534 if let Some(built) = self.built.get(&i) {
535 if let Some(r) = built.kept_in {
536 out.push(format!("{} = {}", self.reg(r), self.literal(built)));
537 defined.insert(r);
538 }
539 continue;
540 }
541 if self.consumed.contains(&i) {
542 continue;
543 }
544 match c.kind {
545 10 => out.push(format!("show {}", self.src_as(&c.ops[0], c.code == TAG_FLOAT))),
546 4 => out.push(match self.element(&c.ops[0]) {
547 (base, Some(j)) => format!("{base}[{j}] = {}", self.src(&c.ops[1])),
548 (base, None) => format!("length({base}) = {}", self.src(&c.ops[1])),
549 }),
550 _ => {}
551 }
552 }
553 for (r, s) in &self.b.writes {
554 if defined.contains(r) || !read.contains(r) {
555 continue;
556 }
557 let (name, value) = (self.reg(*r), self.src_as(s, self.float_regs.contains(r)));
558 if name != value {
559 out.push(format!("{name} = {value}"));
560 }
561 }
562 out
563 }
564
565 fn next(&self) -> MapNext {
566 match &self.b.term {
567 WTerm::Jump(t) => MapNext::Jump { to: *t },
568 WTerm::Branch { cond, then_blk, else_blk } => {
569 MapNext::Branch { cond: self.src(cond), then: *then_blk, otherwise: *else_blk }
570 }
571 WTerm::Return(s) => MapNext::Return { value: self.src(s) },
572 WTerm::Call { callee_block, args, dst_reg, ret_block } => MapNext::Call {
573 callee: *callee_block,
574 args: args.iter().map(|a| self.src(a)).collect(),
575 result: self.reg(*dst_reg),
576 back: *ret_block,
577 },
578 }
579 }
580}
581
582fn bin(a: &str, op: &str, b: &str) -> String {
583 let wrap = |x: &str| {
584 if x.contains(' ') && !x.starts_with('"') && !x.starts_with('(') && !x.starts_with('[') {
585 format!("({x})")
586 } else {
587 x.to_string()
588 }
589 };
590 format!("{} {op} {}", wrap(a), wrap(b))
591}