1use std::collections::{BTreeMap, HashMap};
34
35use rucc_rules::{Error, Term, TermKind, parse_terms};
36
37const BUILTIN: [(&str, &str); 32] = [
45 ("=", "="),
46 ("and", "and"),
47 ("or", "or"),
48 ("not", "not"),
49 ("<", "bvslt"),
50 ("<=", "bvsle"),
51 (">", "bvsgt"),
52 (">=", "bvsge"),
53 ("bvslt", "bvslt"),
54 ("bvsle", "bvsle"),
55 ("bvsgt", "bvsgt"),
56 ("bvsge", "bvsge"),
57 ("bvult", "bvult"),
58 ("bvule", "bvule"),
59 ("bvugt", "bvugt"),
60 ("bvuge", "bvuge"),
61 ("bvadd", "bvadd"),
62 ("bvsub", "bvsub"),
63 ("bvmul", "bvmul"),
64 ("bvneg", "bvneg"),
65 ("bvnot", "bvnot"),
66 ("bvand", "bvand"),
67 ("bvor", "bvor"),
68 ("bvxor", "bvxor"),
69 ("bvshl", "bvshl"),
70 ("bvlshr", "bvlshr"),
71 ("bvashr", "bvashr"),
72 ("bvsdiv", "bvsdiv"),
73 ("bvudiv", "bvudiv"),
74 ("bvsrem", "bvsrem"),
75 ("bvurem", "bvurem"),
76 ("ite", "ite"),
77];
78
79const LOGICAL: [&str; 4] = ["and", "or", "not", "ite"];
82
83const CONVERSION: [&str; 3] = ["sign_extend", "zero_extend", "extract"];
87
88pub const DEFAULT_WIDTH: u32 = 64;
91
92#[derive(Debug, Clone, Default)]
105pub struct Widths {
106 natural: u32,
108 asked: u32,
110 at: BTreeMap<String, u32>,
112}
113
114impl Widths {
115 #[must_use]
117 pub fn of(pattern: &Term) -> Widths {
118 Widths::at(pattern, rule_width(pattern))
119 }
120
121 #[must_use]
123 pub fn at(pattern: &Term, asked: u32) -> Widths {
124 let natural = rule_width(pattern);
125 let mut widths = Widths { natural, asked, at: BTreeMap::new() };
126 widths.bind(pattern, asked);
127 widths
128 }
129
130 #[must_use]
132 pub fn width(&self) -> u32 {
133 self.asked
134 }
135
136 #[must_use]
138 pub fn natural(&self) -> u32 {
139 self.natural
140 }
141
142 pub fn names(&self) -> impl Iterator<Item = (&str, u32)> {
147 self.at.iter().map(|(name, width)| (name.as_str(), *width))
148 }
149
150 #[must_use]
153 pub fn with(&self, name: &str, width: u32) -> Widths {
154 let mut out = self.clone();
155 out.at.insert(name.to_owned(), width);
156 out
157 }
158
159 fn of_name(&self, name: &str) -> Option<u32> {
161 self.at.get(name).copied()
162 }
163
164 fn suffix(&self, head: &str) -> Option<u32> {
166 declared(head).map(|width| self.scale(width))
167 }
168
169 fn scale(&self, width: u32) -> u32 {
172 if self.asked == self.natural || self.natural == 0 {
173 return width;
174 }
175 self.index(width).max(1)
176 }
177
178 fn index(&self, position: u32) -> u32 {
181 if self.asked == self.natural || self.natural == 0 {
182 return position;
183 }
184 let scaled = u64::from(position) * u64::from(self.asked) / u64::from(self.natural);
185 u32::try_from(scaled).unwrap_or(position)
186 }
187
188 fn bind(&mut self, term: &Term, context: u32) {
190 match &term.kind {
191 TermKind::Var(name) => {
192 self.at.insert(name.clone(), context);
193 }
194 TermKind::Int(_) => {}
195 TermKind::App { head, args } => {
196 let inner = self.suffix(head).unwrap_or(context);
197 for arg in args {
198 self.bind(arg, inner);
199 }
200 }
201 }
202 }
203}
204
205#[must_use]
207pub fn rule_width(pattern: &Term) -> u32 {
208 match &pattern.kind {
209 TermKind::App { head, .. } => declared(head).unwrap_or(DEFAULT_WIDTH),
210 _ => DEFAULT_WIDTH,
211 }
212}
213
214fn declared(head: &str) -> Option<u32> {
216 head.rsplit_once('.')
217 .and_then(|(_, suffix)| suffix.strip_prefix('i'))
218 .and_then(|bits| bits.parse::<u32>().ok())
219}
220
221#[derive(Debug, Clone)]
223struct Meaning {
224 params: Vec<String>,
226 body: Term,
228}
229
230#[derive(Debug, Default)]
232pub struct Model {
233 heads: HashMap<String, Meaning>,
234}
235
236impl Model {
237 pub fn read(path: &str, text: &str) -> Result<Model, Vec<Error>> {
244 let terms = parse_terms(path, text)?;
245 let mut model = Model::default();
246 let mut errors = Vec::new();
247
248 for term in terms {
249 let TermKind::App { head, args } = &term.kind else {
250 errors.push(fail(path, &term, "expected a `(semantics ...)` form".to_owned()));
251 continue;
252 };
253 if head != "semantics" || args.len() != 2 {
254 errors.push(fail(path, &term, "expected a `(semantics ...)` form".to_owned()));
255 continue;
256 }
257 let TermKind::App { head: name, args: params } = &args[0].kind else {
258 errors.push(fail(path, &args[0], "expected a head and its parameters".to_owned()));
259 continue;
260 };
261 let mut names = Vec::new();
262 for param in params {
263 match ¶m.kind {
264 TermKind::Var(name) => names.push(name.clone()),
265 _ => errors.push(fail(path, param, "a parameter has to be a name".to_owned())),
266 }
267 }
268 if known(name) {
269 let said = format!("`{name}` is something the solver already knows");
270 errors.push(fail(path, &args[0], said));
271 continue;
272 }
273 let meaning = Meaning { params: names, body: args[1].clone() };
274 if model.heads.insert(name.clone(), meaning).is_some() {
275 let said = format!("`{name}` is given a meaning twice");
276 errors.push(fail(path, &args[0], said));
277 }
278 }
279
280 if errors.is_empty() { Ok(model) } else { Err(errors) }
281 }
282
283 pub fn write(&self, path: &str, term: &Term, widths: &Widths) -> Result<(String, u32), Error> {
292 self.write_at(path, term, widths.width(), widths, &HashMap::new())
293 }
294
295 fn write_at(
296 &self,
297 path: &str,
298 term: &Term,
299 context: u32,
300 widths: &Widths,
301 bound: &HashMap<&str, (String, u32)>,
302 ) -> Result<(String, u32), Error> {
303 match &term.kind {
304 TermKind::Var(name) => match bound.get(name.as_str()) {
305 Some((already, width)) => Ok((already.clone(), *width)),
306 None => Ok((name.clone(), widths.of_name(name).unwrap_or(context))),
307 },
308 TermKind::Int(value) => Ok((literal(*value, context), context)),
309 TermKind::App { head, args } => {
310 if CONVERSION.contains(&head.as_str()) {
311 return self.convert(path, term, head, args, context, widths, bound);
312 }
313 if let Some(name) = builtin(head) {
314 return self.combine(path, term, head, name, args, context, widths, bound);
315 }
316 let own = widths.suffix(head).unwrap_or(context);
317 let mut written = Vec::with_capacity(args.len());
318 for arg in args {
319 written.push(self.write_at(path, arg, own, widths, bound)?);
320 }
321 let Some(meaning) = self.heads.get(head) else {
322 let said = format!("nothing in the model says what `{head}` means");
323 return Err(fail(path, term, said));
324 };
325 if meaning.params.len() != written.len() {
326 let said = format!(
327 "`{head}` means something with {} arguments and this gives it {}",
328 meaning.params.len(),
329 written.len()
330 );
331 return Err(fail(path, term, said));
332 }
333 let inner: HashMap<&str, (String, u32)> =
334 meaning.params.iter().map(String::as_str).zip(written).collect();
335 let (text, width) = self.write_at(path, &meaning.body, own, widths, &inner)?;
336 if let Some(said) = widths.suffix(head) {
342 if said != width {
343 let told = format!(
344 "`{head}` is written for {said} bits and means something {width} \
345 bits wide"
346 );
347 return Err(fail(path, term, told));
348 }
349 }
350 Ok((text, width))
351 }
352 }
353 }
354
355 #[allow(clippy::too_many_arguments)]
358 fn combine(
359 &self,
360 path: &str,
361 term: &Term,
362 head: &str,
363 name: &str,
364 args: &[Term],
365 context: u32,
366 widths: &Widths,
367 bound: &HashMap<&str, (String, u32)>,
368 ) -> Result<(String, u32), Error> {
369 let mut written = Vec::with_capacity(args.len());
370 for arg in args {
371 written.push(self.write_at(path, arg, context, widths, bound)?);
372 }
373 let Some((_, first)) = written.first() else {
374 return Err(fail(path, term, format!("`{head}` needs arguments")));
375 };
376 let first = *first;
377 if !LOGICAL.contains(&head) {
378 if let Some((_, other)) = written.iter().find(|(_, width)| *width != first) {
379 let said = format!(
380 "`{head}` is given something {first} bits wide and something {other} bits \
381 wide, and those are not the same kind of thing"
382 );
383 return Err(fail(path, term, said));
384 }
385 }
386 let width = if head == "ite" && written.len() > 1 { written[1].1 } else { first };
389 let texts: Vec<&str> = written.iter().map(|(text, _)| text.as_str()).collect();
390 Ok((format!("({name} {})", texts.join(" ")), width))
391 }
392
393 #[allow(clippy::too_many_arguments)]
396 fn convert(
397 &self,
398 path: &str,
399 term: &Term,
400 head: &str,
401 args: &[Term],
402 context: u32,
403 widths: &Widths,
404 bound: &HashMap<&str, (String, u32)>,
405 ) -> Result<(String, u32), Error> {
406 if args.len() != 3 {
407 let said = format!("`{head}` takes two numbers and a value, and this gives it {}", {
408 args.len()
409 });
410 return Err(fail(path, term, said));
411 }
412 let (first, second) = (number(path, head, &args[0])?, number(path, head, &args[1])?);
415
416 if head == "extract" {
417 let (high, low) = (first, second);
418 if high < low {
419 let said = format!("`extract` takes bits {high} down to {low}, which is none");
420 return Err(fail(path, term, said));
421 }
422 let width = widths.scale(high - low + 1);
423 let bottom = widths.index(low);
424 let top = bottom + width - 1;
425 let (text, of) = self.write_at(path, &args[2], context, widths, bound)?;
426 if top >= of {
427 let said = format!(
428 "`extract` takes bits {top} down to {bottom} of something {of} bits wide"
429 );
430 return Err(fail(path, term, said));
431 }
432 return Ok((format!("((_ extract {top} {bottom}) {text})"), width));
433 }
434
435 let (from, to) = (widths.scale(first), widths.scale(second));
436 if to < from {
437 let said = format!("`{head}` goes from {from} bits to {to}, which is narrower");
438 return Err(fail(path, term, said));
439 }
440 let (text, of) = self.write_at(path, &args[2], from, widths, bound)?;
441 if of != from {
442 let said =
443 format!("`{head}` goes from {from} bits and is given something {of} bits wide");
444 return Err(fail(path, term, said));
445 }
446 if to == from {
449 return Ok((text, to));
450 }
451 Ok((format!("((_ {head} {}) {text})", to - from), to))
452 }
453}
454
455fn builtin(head: &str) -> Option<&'static str> {
457 BUILTIN.iter().find(|(name, _)| *name == head).map(|(_, smt)| *smt)
458}
459
460fn known(head: &str) -> bool {
462 builtin(head).is_some() || CONVERSION.contains(&head)
463}
464
465fn number(path: &str, head: &str, term: &Term) -> Result<u32, Error> {
467 match &term.kind {
468 TermKind::Int(value) => u32::try_from(*value).map_err(|_| {
469 let said = format!("`{head}` is given {value} where it needs a number of bits");
470 fail(path, term, said)
471 }),
472 _ => {
473 let said = format!("`{head}` says which widths it goes between, in numbers");
474 Err(fail(path, term, said))
475 }
476 }
477}
478
479fn literal(value: i128, width: u32) -> String {
482 let wrapped =
483 if width >= 128 { value as u128 } else { (value as u128) & ((1u128 << width) - 1) };
484 format!("(_ bv{wrapped} {width})")
485}
486
487fn fail(path: &str, term: &Term, message: String) -> Error {
488 Error { path: path.to_owned(), line: term.line, column: term.column, message }
489}