1use super::chip_protocol::{
8 ChipValue, FrameError, Record, ResultFrame, FRAME_END, FRAME_END_CALL_STACK, FRAME_SYNC, REC_CALL, REC_SHOW,
9 TAG_BOOL, TAG_FLOAT, TAG_INT, TAG_TEXT, TAG_WORD32,
10};
11use super::engine_build::{fits_caps, lower, lower_main, Lifter, Profile};
12use super::wprog_engine_rtl::{
13 render_show, wprog_config_image_unchecked, WShow, WPE_HW_DEPTH, WPE_HW_HEAP, WPE_HW_MAXARG, WPE_HW_MAXBLK,
14 WPE_HW_MAXCELL, WPE_HW_MAXK, WPE_HW_MAXLIV, WPE_HW_MAXWR, WPE_HW_NREG,
15};
16use crate::vm::{binisa, Compiler};
17
18#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum Run {
21 Main,
22 Call { func: String, args: Vec<i64> },
23}
24
25#[derive(Clone, Debug, PartialEq, Eq)]
27pub enum Golden {
28 Lines(Vec<String>),
29 Answer(i64),
30}
31
32#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct Compiled {
35 pub bytecode: Vec<u8>,
37 pub wire: Vec<u8>,
39 pub lifter: Lifter,
41 pub golden: Golden,
43}
44
45pub fn register_bytecode(src: &str) -> Result<Vec<u8>, String> {
47 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
48 let (stmts, _types, _policies) = parsed?;
49 let program = Compiler::compile(stmts, interner)?;
50 Ok(binisa::encode_program(&program, interner))
51 })
52}
53
54pub fn compile(src: &str, run: &Run, profile: Profile, caps: &str) -> Result<Compiled, String> {
59 let (cfg, lifter, golden, params) = match run {
60 Run::Main => {
61 let (cfg, lines) = lower_main(src)?;
62 (cfg, Lifter::Main, Golden::Lines(lines), Vec::new())
63 }
64 Run::Call { func, args } => {
65 let (cfg, lifter, answer) = lower(src, func, args)?;
66 (cfg, lifter, Golden::Answer(answer), args.clone())
67 }
68 };
69 profile
70 .fits(&cfg)
71 .map_err(|e| format!("does not fit the {} engine: {e}", profile.name()))?;
72 fits_caps(&cfg, ¶ms, caps).map_err(|e| format!("does not fit the flashed image: {e}"))?;
73 let wire = wprog_config_image_unchecked(&cfg).wire_bytes(¶ms);
74 let bytecode = register_bytecode(src)?;
75 Ok(Compiled {
76 bytecode,
77 wire,
78 lifter,
79 golden,
80 })
81}
82
83#[derive(Clone, Debug, PartialEq, Eq)]
85pub struct Entry {
86 pub title: String,
87 pub program: String,
88 pub run: Run,
89}
90
91pub fn library(text: &str) -> Result<Vec<Entry>, String> {
95 let mut entries = Vec::new();
96 for (n, line) in text.lines().enumerate() {
97 if line.trim().is_empty() || line.trim_start().starts_with('#') {
98 continue;
99 }
100 let fields: Vec<&str> = line.split('|').map(str::trim).collect();
101 let [title, program, func, args] = fields[..] else {
102 return Err(format!("library line {}: want `title | program | function | arguments`", n + 1));
103 };
104 let run = if func.is_empty() {
105 if !args.is_empty() {
106 return Err(format!("library line {}: a Main program takes no arguments", n + 1));
107 }
108 Run::Main
109 } else {
110 let args = args
111 .split(',')
112 .map(str::trim)
113 .filter(|a| !a.is_empty())
114 .map(|a| a.parse::<i64>().map_err(|_| format!("library line {}: bad argument {a:?}", n + 1)))
115 .collect::<Result<Vec<_>, _>>()?;
116 Run::Call { func: func.to_string(), args }
117 };
118 entries.push(Entry { title: title.to_string(), program: program.to_string(), run });
119 }
120 Ok(entries)
121}
122
123pub fn hw_caps() -> String {
125 format!(
126 "NREG={WPE_HW_NREG},MAXBLK={WPE_HW_MAXBLK},MAXCELL={WPE_HW_MAXCELL},MAXWR={WPE_HW_MAXWR},\
127 MAXARG={WPE_HW_MAXARG},MAXLIV={WPE_HW_MAXLIV},MAXK={WPE_HW_MAXK},HEAP={WPE_HW_HEAP},DEPTH={WPE_HW_DEPTH}"
128 )
129}
130
131pub fn report(result: &Result<Compiled, String>) -> String {
135 match result {
136 Ok(c) => {
137 let golden = match &c.golden {
138 Golden::Answer(v) => format!("golden={v}"),
139 Golden::Lines(lines) => format!(
140 "golden_json={}",
141 serde_json::to_string(lines).expect("lines serialize")
142 ),
143 };
144 format!(
145 "lifter={}\n{golden}\nbytecode={}\nwire={}\n",
146 c.lifter.name(),
147 hex(&c.bytecode),
148 hex(&c.wire)
149 )
150 }
151 Err(e) => format!("error={}\n", e.replace('\n', " ")),
152 }
153}
154
155fn hex(bytes: &[u8]) -> String {
156 let mut s = String::with_capacity(bytes.len() * 2);
157 for b in bytes {
158 s.push_str(&format!("{b:02x}"));
159 }
160 s
161}
162
163#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165pub enum Ended {
166 Finished { result: i64, cycles: u32 },
168 CallStack { cycles: u32 },
170 Heap { cycles: u32 },
172}
173
174#[derive(Clone, Debug, PartialEq, Eq)]
177pub struct Reply {
178 pub shows: Vec<String>,
179 pub ended: Option<Ended>,
180}
181
182pub fn shown(v: &ChipValue) -> String {
185 let (tag, value, heap) = match v {
186 ChipValue::Int(i) => (TAG_INT, *i, Vec::new()),
187 ChipValue::Float(bits) => (TAG_FLOAT, *bits as i64, Vec::new()),
188 ChipValue::Word32(w) => (TAG_WORD32, i64::from(*w), Vec::new()),
189 ChipValue::Bool(b) => (TAG_BOOL, i64::from(*b), Vec::new()),
190 ChipValue::Text(t) => {
191 let mut heap = vec![t.len() as i64];
192 heap.extend(t.bytes().map(i64::from));
193 (TAG_TEXT, 0, heap)
194 }
195 };
196 render_show(WShow { tag, value }, &heap)
197}
198
199pub fn engine_reply(bytes: &[u8]) -> Result<Reply, String> {
203 let mut reply = Reply { shows: Vec::new(), ended: None };
204 let mut at = bytes.iter().position(|&b| b == REC_SHOW || b == REC_CALL || b == FRAME_SYNC).unwrap_or(bytes.len());
205 while at < bytes.len() && bytes[at] != FRAME_SYNC {
206 match Record::decode(&bytes[at..]) {
207 Ok((Record::Show(v), n)) => {
208 reply.shows.push(shown(&v));
209 at += n;
210 }
211 Ok((Record::Call { call, .. }, _)) => {
212 return Err(format!("the program calls the host ({}), which the board does not answer", call.name()))
213 }
214 Ok((Record::Reply(_), _)) => return Err("the engine sent a reply record".to_string()),
215 Err(FrameError::Short) => return Ok(reply),
216 Err(FrameError::Malformed(why)) => return Err(format!("the engine sent no record: {why}")),
217 }
218 }
219 if at < bytes.len() {
220 match ResultFrame::decode(&bytes[at..]) {
221 Ok((f, _)) => {
222 reply.ended = Some(match f.end {
223 FRAME_END => Ended::Finished { result: f.result, cycles: f.cycles },
224 FRAME_END_CALL_STACK => Ended::CallStack { cycles: f.cycles },
225 _ => Ended::Heap { cycles: f.cycles },
226 })
227 }
228 Err(FrameError::Short) => {}
229 Err(FrameError::Malformed(why)) => return Err(format!("the engine's result frame is damaged: {why}")),
230 }
231 }
232 Ok(reply)
233}
234
235pub fn agrees(golden: &Golden, reply: &Reply) -> bool {
238 match (golden, reply.ended) {
239 (Golden::Lines(lines), Some(Ended::Finished { .. })) => &reply.shows == lines,
240 (Golden::Answer(v), Some(Ended::Finished { result, .. })) => result == *v,
241 _ => false,
242 }
243}