1use core::fmt;
59use std::borrow::Cow;
60use std::collections::HashMap;
61use std::sync::Arc;
62
63use crate::error::Error;
64use crate::expr::{Context, Expr};
65use crate::facts::KnownBits;
66use crate::hash::combine;
67use crate::{BitVec, Width};
68
69pub const MAX_ARGS: usize = 3;
71pub const MAX_OUTPUTS: usize = 8;
73pub const MAX_OPS: usize = 256;
75
76#[derive(Clone, Debug, PartialEq, Eq)]
78pub struct ExtSig {
79 outputs: Vec<(Cow<'static, str>, Width)>,
80}
81
82impl ExtSig {
83 pub fn new(outputs: &[(&'static str, Width)]) -> ExtSig {
85 ExtSig {
86 outputs: outputs
87 .iter()
88 .map(|&(r, w)| (Cow::Borrowed(r), w))
89 .collect(),
90 }
91 }
92
93 pub fn outputs(&self) -> impl Iterator<Item = (&str, Width)> {
95 self.outputs.iter().map(|(r, w)| (&**r, *w))
96 }
97
98 pub fn len(&self) -> usize {
100 self.outputs.len()
101 }
102
103 pub fn is_empty(&self) -> bool {
105 self.outputs.is_empty()
106 }
107
108 pub fn width(&self, k: usize) -> Option<Width> {
110 self.outputs.get(k).map(|(_, w)| *w)
111 }
112}
113
114#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
116#[non_exhaustive]
117pub struct ExtTraits {
118 pub commutative: bool,
120 pub opaque: bool,
123}
124
125setters!(ExtTraits {
126 with_commutative: commutative: bool,
127 with_opaque: opaque: bool,
128});
129
130pub trait ExtOp: Send + Sync + 'static {
132 fn name(&self) -> &str;
134
135 fn revision(&self) -> u32 {
137 1
138 }
139
140 fn signature(&self, args: &[Width]) -> Result<ExtSig, String>;
142
143 fn eval(&self, args: &[BitVec], out: &mut [BitVec]);
146
147 fn known_bits(&self, _args: &[KnownBits], _out: &mut [KnownBits]) {}
151
152 fn expand(&self, _cx: &mut Context, _args: &[Expr]) -> Option<Result<Vec<Expr>, Error>> {
155 None
156 }
157
158 fn smtlib(&self, _output: u8, _args: &[&str], _widths: &[Width]) -> Option<String> {
161 None
162 }
163
164 fn traits(&self) -> ExtTraits {
166 ExtTraits::default()
167 }
168}
169
170#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
172pub struct ExtArgs {
173 args: [Expr; MAX_ARGS],
174 len: u8,
175}
176
177impl ExtArgs {
178 pub(crate) fn new(args: &[Expr]) -> ExtArgs {
180 let mut a = [args[0]; MAX_ARGS];
181 a[..args.len()].copy_from_slice(args);
182 ExtArgs {
183 args: a,
184 len: args.len() as u8,
185 }
186 }
187
188 pub fn as_slice(&self) -> &[Expr] {
190 &self.args[..usize::from(self.len)]
191 }
192}
193
194#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
197pub struct ExtId {
198 index: u8,
199 tag: u32,
200}
201
202impl ExtId {
203 pub fn index(self) -> usize {
205 usize::from(self.index)
206 }
207
208 pub(crate) fn raw(self) -> u8 {
209 self.index
210 }
211
212 pub(crate) fn dangling() -> ExtId {
214 ExtId {
215 index: u8::MAX,
216 tag: 0,
217 }
218 }
219}
220
221#[derive(Clone, Debug, PartialEq, Eq)]
223#[non_exhaustive]
224pub enum ExtError {
225 BadName(String),
227 Duplicate(String),
229 TooMany,
231 Contract(String, String),
233}
234
235impl fmt::Display for ExtError {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 match self {
238 ExtError::BadName(n) => write!(f, "`{n}` is not a valid extension operation name"),
239 ExtError::Duplicate(n) => write!(f, "`{n}` is already registered"),
240 ExtError::TooMany => write!(f, "a registry holds at most {MAX_OPS} operations"),
241 ExtError::Contract(n, why) => write!(f, "`{n}` breaks its contract: {why}"),
242 }
243 }
244}
245
246impl std::error::Error for ExtError {}
247
248#[derive(Clone, Default)]
250pub struct Registry {
251 ops: Vec<Arc<dyn ExtOp>>,
252 by_name: HashMap<String, ExtId>,
253 hashes: Vec<u64>,
255}
256
257impl fmt::Debug for Registry {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 f.debug_list()
260 .entries(
261 self.ops
262 .iter()
263 .map(|o| format!("{}#{}", o.name(), o.revision())),
264 )
265 .finish()
266 }
267}
268
269impl Registry {
270 pub fn builder() -> RegistryBuilder {
272 RegistryBuilder::default()
273 }
274
275 pub fn id(&self, name: &str) -> Option<ExtId> {
277 self.by_name.get(name).copied()
278 }
279
280 pub fn op(&self, id: ExtId) -> Option<&dyn ExtOp> {
282 (self.id_at(id.index)? == id).then(|| self.ops.get(id.index()).map(|o| &**o))?
283 }
284
285 pub(crate) fn id_at(&self, index: u8) -> Option<ExtId> {
287 let h = *self.hashes.get(usize::from(index))?;
288 Some(ExtId {
289 index,
290 tag: h as u32,
291 })
292 }
293
294 pub(crate) fn op_at(&self, index: u8) -> Option<&dyn ExtOp> {
296 self.ops.get(usize::from(index)).map(|o| &**o)
297 }
298
299 pub fn len(&self) -> usize {
301 self.ops.len()
302 }
303
304 pub fn is_empty(&self) -> bool {
306 self.ops.is_empty()
307 }
308
309 pub fn ids(&self) -> impl Iterator<Item = ExtId> + '_ {
311 (0..self.ops.len()).filter_map(|i| self.id_at(i as u8))
312 }
313
314 pub(crate) fn hash_at(&self, index: u8) -> u64 {
316 self.hashes.get(usize::from(index)).copied().unwrap_or(0)
317 }
318}
319
320#[derive(Default, Debug)]
322pub struct RegistryBuilder {
323 reg: Registry,
324}
325
326impl RegistryBuilder {
327 pub fn register(self, op: impl ExtOp) -> Result<Self, ExtError> {
329 check(&op)?;
330 self.register_unchecked(op)
331 }
332
333 pub fn register_unchecked(mut self, op: impl ExtOp) -> Result<Self, ExtError> {
338 let name = op.name().to_string();
339 let good = name.chars().next().is_some_and(|c| c.is_ascii_alphabetic())
340 && name
341 .chars()
342 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
343 && !name.ends_with('.')
344 && !name.contains("..");
345 if !good {
346 return Err(ExtError::BadName(name));
347 }
348 if self.reg.by_name.contains_key(&name) {
349 return Err(ExtError::Duplicate(name));
350 }
351 if self.reg.ops.len() >= MAX_OPS {
352 return Err(ExtError::TooMany);
353 }
354 let index = self.reg.ops.len() as u8;
355 let h = name
356 .bytes()
357 .fold(combine(0x6578_7400, u64::from(op.revision())), |h, b| {
358 combine(h, u64::from(b))
359 });
360 self.reg.hashes.push(h);
361 let id = ExtId {
362 index,
363 tag: h as u32,
364 };
365 self.reg.by_name.insert(name, id);
366 self.reg.ops.push(Arc::new(op));
367 Ok(self)
368 }
369
370 pub fn build(self) -> Registry {
372 self.reg
373 }
374}
375
376pub fn check(op: &dyn ExtOp) -> Result<(), ExtError> {
380 self_test(op).map_err(|why| ExtError::Contract(op.name().to_string(), why))
381}
382
383pub(crate) fn run_eval(op: &dyn ExtOp, args: &[BitVec]) -> Result<Vec<BitVec>, String> {
385 let widths: Vec<Width> = args.iter().map(|a| a.width()).collect();
386 let sig = op.signature(&widths)?;
387 if sig.is_empty() || sig.len() > MAX_OUTPUTS {
388 return Err(format!(
389 "{} outputs declared (1 to {MAX_OUTPUTS})",
390 sig.len()
391 ));
392 }
393 let mut out: Vec<BitVec> = sig.outputs().map(|(_, w)| BitVec::zero(w)).collect();
394 op.eval(args, &mut out);
395 for (k, (v, (_, w))) in out.iter().zip(sig.outputs()).enumerate() {
396 if v.width() != w {
397 return Err(format!(
398 "output {k} has {} bits, declared {}",
399 v.width().bits(),
400 w.bits()
401 ));
402 }
403 }
404 Ok(out)
405}
406
407pub(crate) fn run_known_bits(
409 op: &dyn ExtOp,
410 sig: &ExtSig,
411 args: &[KnownBits],
412) -> Result<Vec<KnownBits>, String> {
413 let mut out: Vec<KnownBits> = sig.outputs().map(|(_, w)| KnownBits::unknown(w)).collect();
414 op.known_bits(args, &mut out);
415 for (k, (v, (_, w))) in out.iter().zip(sig.outputs()).enumerate() {
416 if v.width() != w {
417 return Err(format!(
418 "known bits of output {k} have {} bits, declared {}",
419 v.width().bits(),
420 w.bits()
421 ));
422 }
423 }
424 Ok(out)
425}
426
427struct Rng(u64);
429
430impl Rng {
431 fn next(&mut self) -> u64 {
432 self.0 = self.0.wrapping_add(0x9e37_79b9_7f4a_7c15);
433 let mut z = self.0;
434 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
435 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
436 z ^ (z >> 31)
437 }
438
439 fn value(&mut self, w: Width) -> BitVec {
440 let limbs: Vec<u64> = (0..8)
441 .map(|_| match self.next() % 5 {
442 0 => 0,
443 1 => u64::MAX,
444 _ => self.next(),
445 })
446 .collect();
447 BitVec::wrapping_from_limbs(w, &limbs)
448 }
449}
450
451fn width_tuples() -> Vec<Vec<u16>> {
454 const W: [u16; 11] = [1, 3, 8, 13, 16, 32, 64, 65, 128, 256, 512];
455 let mut out: Vec<Vec<u16>> = W.iter().map(|&a| vec![a]).collect();
456 for &a in &W {
457 for &b in &W {
458 out.push(vec![a, b]);
459 }
460 }
461 for &a in &W {
462 for &b in &W {
463 for t in [vec![a, a, b], vec![a, b, a], vec![b, a, a]] {
464 if !out.contains(&t) {
465 out.push(t);
466 }
467 }
468 }
469 }
470 out
471}
472
473const TESTED_TUPLES: usize = 64;
476
477fn self_test(op: &dyn ExtOp) -> Result<(), String> {
481 use crate::facts::known::{bv_and, bv_not};
482 let mut rng = Rng(0x5e1f_7e57 ^ u64::from(op.revision()));
483 let commutative = op.traits().commutative;
484 let mut accepted = 0;
485 for tuple in width_tuples() {
486 if accepted >= TESTED_TUPLES {
487 break;
488 }
489 let widths: Vec<Width> = tuple
490 .iter()
491 .map(|&w| Width::new(w).map_err(|e| e.to_string()))
492 .collect::<Result<_, _>>()?;
493 let Ok(sig) = op.signature(&widths) else {
494 continue;
495 };
496 if sig.is_empty() || sig.len() > MAX_OUTPUTS {
497 return Err(format!(
498 "{} outputs declared (1 to {MAX_OUTPUTS})",
499 sig.len()
500 ));
501 }
502 accepted += 1;
503 let at = || format!(" at argument widths {tuple:?}");
504 for _ in 0..16 {
505 let args: Vec<BitVec> = widths.iter().map(|&w| rng.value(w)).collect();
506 let a = run_eval(op, &args).map_err(|e| e + &at())?;
507 if run_eval(op, &args)? != a {
508 return Err(format!("eval is not deterministic{}", at()));
509 }
510 if commutative && widths.len() >= 2 && widths[0] == widths[1] {
511 let mut swapped = args.clone();
512 swapped.swap(0, 1);
513 if run_eval(op, &swapped)? != a {
514 return Err(format!(
515 "declared commutative, but swapping the first two arguments changes \
516 the result{}",
517 at()
518 ));
519 }
520 }
521 let masks: Vec<BitVec> = widths.iter().map(|&w| rng.value(w)).collect();
524 let kb: Vec<KnownBits> = args
525 .iter()
526 .zip(&masks)
527 .map(|(v, m)| {
528 KnownBits::new(bv_and(&bv_not(v), m), bv_and(v, m))
529 .unwrap_or(KnownBits::unknown(v.width()))
530 })
531 .collect();
532 let out = run_known_bits(op, &sig, &kb).map_err(|e| e + &at())?;
533 for _ in 0..4 {
534 let inst: Vec<BitVec> = args
535 .iter()
536 .zip(&masks)
537 .map(|(v, m)| {
538 let r = rng.value(v.width());
539 crate::facts::known::bv_or(&bv_and(v, m), &bv_and(&r, &bv_not(m)))
540 })
541 .collect();
542 for (k, (v, o)) in run_eval(op, &inst)?.iter().zip(&out).enumerate() {
543 if !o.contains(v) {
544 return Err(format!("known bits of output {k} exclude {v}{}", at()));
545 }
546 }
547 }
548 }
549 let mut cx = Context::new();
551 let syms: Vec<Expr> = widths
552 .iter()
553 .enumerate()
554 .map(|(i, &w)| cx.symbol(format!("a{i}").as_str(), w))
555 .collect::<Result<_, _>>()
556 .map_err(|e| e.to_string())?;
557 if let Some(ex) = op.expand(&mut cx, &syms) {
558 let ex = ex.map_err(|e| format!("expand failed: {e}{}", at()))?;
559 if ex.len() != sig.len() {
560 return Err(format!(
561 "expand gave {} outputs, not {}{}",
562 ex.len(),
563 sig.len(),
564 at()
565 ));
566 }
567 for _ in 0..8 {
568 let args: Vec<BitVec> = widths.iter().map(|&w| rng.value(w)).collect();
569 let env: Vec<(crate::SymbolKey, BitVec)> = args
570 .iter()
571 .enumerate()
572 .map(|(i, v)| (crate::SymbolKey::from(format!("a{i}").as_str()), *v))
573 .collect();
574 let got = cx.eval(&ex, &env[..]).map_err(|e| e.to_string())?;
575 if got != run_eval(op, &args)? {
576 return Err(format!("expand disagrees with eval{}", at()));
577 }
578 }
579 }
580 }
581 if accepted == 0 {
582 return Err("no signature accepted at any tested argument widths".into());
583 }
584 Ok(())
585}