use core::fmt::Write as _;
use crate::error::Error;
use crate::expr::{Context, Expr, OpCode};
use crate::ops::{BinOp, CmpOp};
use crate::{BitVec, SymbolKey, Width};
pub(crate) fn literal(v: &BitVec) -> String {
let w = v.width().bits() as usize;
let mut s = String::with_capacity(w + 2);
s.push_str("#b");
let limbs = v.limbs();
for i in (0..w).rev() {
let bit = (limbs[i / 64] >> (i % 64)) & 1;
s.push(if bit == 1 { '1' } else { '0' });
}
s
}
fn generated(s: &str) -> bool {
let digit_after = |p: &str| {
s.strip_prefix(p)
.is_some_and(|r| r.as_bytes().first().is_some_and(u8::is_ascii_digit))
};
digit_after("n") || digit_after("root") || s.starts_with("sym!") || s.starts_with("ext!")
}
const RESERVED: &[&str] = &[
"!",
"_",
"as",
"BINARY",
"DECIMAL",
"exists",
"forall",
"HEXADECIMAL",
"let",
"match",
"NUMERAL",
"par",
"STRING",
"assert",
"check-sat",
"declare-const",
"declare-fun",
"define-fun",
"define-sort",
"exit",
"get-model",
"get-value",
"pop",
"push",
"reset",
"set-info",
"set-logic",
"set-option",
"Bool",
"true",
"false",
"not",
"and",
"or",
"xor",
"=>",
"=",
"distinct",
"ite",
"BitVec",
"concat",
"extract",
"repeat",
"zero_extend",
"sign_extend",
"rotate_left",
"rotate_right",
"bvnot",
"bvand",
"bvor",
"bvxor",
"bvnand",
"bvnor",
"bvxnor",
"bvcomp",
"bvneg",
"bvadd",
"bvsub",
"bvmul",
"bvudiv",
"bvurem",
"bvsdiv",
"bvsrem",
"bvsmod",
"bvshl",
"bvlshr",
"bvashr",
"bvult",
"bvule",
"bvugt",
"bvuge",
"bvslt",
"bvsle",
"bvsgt",
"bvsge",
];
pub(crate) fn quotable(s: &str) -> bool {
!s.is_empty()
&& s.chars()
.all(|c| c != '|' && c != '\\' && (c == ' ' || c.is_ascii_graphic()))
&& !RESERVED.contains(&s)
&& !(s.starts_with("bv") && s[2..].bytes().all(|b| b.is_ascii_digit()) && s.len() > 2)
}
pub(crate) fn symbol_name(key: &SymbolKey, id: u32) -> String {
match key {
SymbolKey::U64(k) => format!("|#{k}|"),
SymbolKey::Fresh(k) => format!("|${k}|"),
SymbolKey::Str(s)
if quotable(s) && !s.starts_with(['#', '$', '@', '.']) && !generated(s) =>
{
format!("|{s}|")
}
_ => format!("|sym!{id}|"),
}
}
pub(crate) fn sort(w: u16) -> String {
format!("(_ BitVec {w})")
}
pub(crate) struct App<'a> {
pub(crate) tag: &'a str,
pub(crate) w: u16,
pub(crate) args: &'a [String],
pub(crate) arg_w: &'a [u16],
pub(crate) lo: u32,
pub(crate) fp: Option<crate::fp::node::Desc>,
}
pub(crate) fn term(out: &mut String, op: OpCode, app: &App<'_>) -> Result<String, Error> {
if let Some(d) = &app.fp {
return super::fp::term(out, d, app);
}
let (w, ww, tag) = (app.w, u32::from(app.w), app.tag);
let arg = |k: usize| -> Result<&str, Error> {
app.args
.get(k)
.map(String::as_str)
.ok_or_else(|| Error::Contract(format!("{op:?} is missing an operand")))
};
Width::new(w).map_err(|_| Error::Contract("bad width".into()))?;
let wconst = |v: u64| {
let v = if ww < 64 { v & ((1u64 << ww) - 1) } else { v };
format!("(_ bv{v} {ww})")
};
let def = |out: &mut String, name: &str, s: u16, body: &str| {
writeln!(out, "(define-fun {name} () {} {body})", sort(s)).ok();
};
let one_bit = |c: &str| format!("(ite {c} #b1 #b0)");
Ok(match op {
OpCode::Not => format!("(bvnot {})", arg(0)?),
OpCode::Neg => format!("(bvneg {})", arg(0)?),
OpCode::Popcnt => {
let a = arg(0)?;
let bit = |k: u32| {
if ww == 1 {
format!("((_ extract {k} {k}) {a})")
} else {
format!("((_ zero_extend {}) ((_ extract {k} {k}) {a}))", ww - 1)
}
};
let mut acc = bit(0);
for k in 1..ww {
let h = format!("{tag}_p{k}");
def(out, &h, w, &format!("(bvadd {acc} {})", bit(k)));
acc = h;
}
acc
}
OpCode::Clz | OpCode::Ctz => {
let a = arg(0)?;
let mut prev = wconst(u64::from(ww));
let bits: Vec<u32> = if op == OpCode::Clz {
(0..ww).collect()
} else {
(0..ww).rev().collect()
};
for k in bits {
let count = if op == OpCode::Clz { ww - 1 - k } else { k };
let h = format!("{tag}_c{k}");
def(
out,
&h,
w,
&format!(
"(ite (= ((_ extract {k} {k}) {a}) #b1) {} {prev})",
wconst(u64::from(count))
),
);
prev = h;
}
prev
}
OpCode::Bswap | OpCode::BitRev => {
let a = arg(0)?;
let size = if op == OpCode::Bswap { 8 } else { 1 };
if ww % size != 0 {
return Err(Error::Contract(
"bswap of a width not a multiple of 8".into(),
));
}
let piece = |k: u32| format!("((_ extract {} {}) {a})", k * size + size - 1, k * size);
let mut acc = piece(0);
for k in 1..ww / size {
let h = format!("{tag}_r{k}");
def(
out,
&h,
((k + 1) * size) as u16,
&format!("(concat {acc} {})", piece(k)),
);
acc = h;
}
acc
}
OpCode::Zext | OpCode::Sext => {
let from = u32::from(*app.arg_w.first().unwrap_or(&0));
if from == 0 || from > ww {
return Err(Error::Contract("bad extension".into()));
}
if from == ww {
arg(0)?.to_string()
} else {
let f = if op == OpCode::Zext {
"zero_extend"
} else {
"sign_extend"
};
format!("((_ {f} {}) {})", ww - from, arg(0)?)
}
}
OpCode::Extract => format!("((_ extract {} {}) {})", app.lo + ww - 1, app.lo, arg(0)?),
OpCode::Concat => format!("(concat {} {})", arg(0)?, arg(1)?),
OpCode::Select => format!("(ite (= {} #b1) {} {})", arg(0)?, arg(1)?, arg(2)?),
op => {
if let Some(c) = op.as_cmp() {
let (a, b) = (arg(0)?, arg(1)?);
let f = match c {
CmpOp::Eq => format!("(= {a} {b})"),
CmpOp::Ne => format!("(not (= {a} {b}))"),
CmpOp::Ult => format!("(bvult {a} {b})"),
CmpOp::Ule => format!("(bvule {a} {b})"),
CmpOp::Slt => format!("(bvslt {a} {b})"),
CmpOp::Sle => format!("(bvsle {a} {b})"),
};
one_bit(&f)
} else if let Some(bo) = op.as_bin() {
bin(out, bo, app, &wconst)?
} else {
return Err(Error::Contract(format!("no SMT-LIB form for {op:?}")));
}
}
})
}
fn bin(
out: &mut String,
bo: BinOp,
app: &App<'_>,
wconst: &dyn Fn(u64) -> String,
) -> Result<String, Error> {
let (w, ww, tag) = (app.w, u32::from(app.w), app.tag);
let [x, y] = app.args else {
return Err(Error::Contract(format!("{bo:?} takes two operands")));
};
let simple = |f: &str| format!("({f} {x} {y})");
Ok(match bo {
BinOp::Add => simple("bvadd"),
BinOp::Sub => simple("bvsub"),
BinOp::Mul => simple("bvmul"),
BinOp::UDiv => simple("bvudiv"),
BinOp::URem => simple("bvurem"),
BinOp::SDiv => simple("bvsdiv"),
BinOp::SRem => simple("bvsrem"),
BinOp::And => simple("bvand"),
BinOp::Or => simple("bvor"),
BinOp::Xor => simple("bvxor"),
BinOp::Shl => simple("bvshl"),
BinOp::LShr => simple("bvlshr"),
BinOp::AShr => simple("bvashr"),
BinOp::RotL | BinOp::RotR => {
let k = format!("(bvurem {y} {})", wconst(u64::from(ww)));
let back = format!("(bvsub {} {k})", wconst(u64::from(ww)));
let (s1, s2) = if bo == BinOp::RotL {
("bvshl", "bvlshr")
} else {
("bvlshr", "bvshl")
};
format!("(bvor ({s1} {x} {k}) ({s2} {x} {back}))")
}
BinOp::UMulHi | BinOp::SMulHi if 2 * ww <= u32::from(Width::MAX_BITS) => {
let ext = if bo == BinOp::UMulHi {
"zero_extend"
} else {
"sign_extend"
};
format!(
"((_ extract {} {ww}) (bvmul ((_ {ext} {ww}) {x}) ((_ {ext} {ww}) {y})))",
2 * ww - 1
)
}
BinOp::UMulHi | BinOp::SMulHi => {
let hi = wide_umulhi(out, tag, ww, x, y);
if bo == BinOp::UMulHi {
hi
} else {
let zero = wconst(0);
format!(
"(bvsub (bvsub {hi} (ite (bvslt {x} {zero}) {y} {zero})) \
(ite (bvslt {y} {zero}) {x} {zero}))"
)
}
}
BinOp::Pdep | BinOp::Pext => {
let (zero, one) = (wconst(0), wconst(1));
let (mut acc, mut cnt) = (zero.clone(), zero);
for k in 0..ww {
let set = format!("(= ((_ extract {k} {k}) {y}) #b1)");
let kc = wconst(u64::from(k));
let (an, cn) = (format!("{tag}_a{k}"), format!("{tag}_n{k}"));
let bit = if bo == BinOp::Pext {
format!("(bvshl (bvand (bvlshr {x} {kc}) {one}) {cnt})")
} else {
format!("(bvshl (bvand (bvlshr {x} {cnt}) {one}) {kc})")
};
writeln!(
out,
"(define-fun {an} () {s} (ite {set} (bvor {acc} {bit}) {acc}))",
s = sort(w)
)
.ok();
writeln!(
out,
"(define-fun {cn} () {s} (ite {set} (bvadd {cnt} {one}) {cnt}))",
s = sort(w)
)
.ok();
acc = an;
cnt = cn;
}
acc
}
})
}
fn wide_umulhi(out: &mut String, tag: &str, ww: u32, x: &str, y: &str) -> String {
let def = |out: &mut String, name: &str, s: u32, body: &str| {
writeln!(out, "(define-fun {name} () (_ BitVec {s}) {body})").ok();
};
let even = if ww.is_multiple_of(2) { ww } else { ww + 1 };
let (a, b) = if even == ww {
(x.to_string(), y.to_string())
} else {
(
format!("((_ zero_extend 1) {x})"),
format!("((_ zero_extend 1) {y})"),
)
};
let h = even / 2;
let shift = format!("(_ bv{h} {even})");
let mask = format!("((_ zero_extend {h}) (bvnot (_ bv0 {h})))");
let lo = |t: &str| format!("(bvand {t} {mask})");
let up = |t: &str| format!("(bvlshr {t} {shift})");
let n = |k: &str| format!("{tag}_mh{k}");
for (k, body) in [
("a", a.clone()),
("b", b.clone()),
("u0", lo(&n("a"))),
("u1", up(&n("a"))),
("v0", lo(&n("b"))),
("v1", up(&n("b"))),
("w0", format!("(bvmul {} {})", n("u0"), n("v0"))),
(
"t",
format!("(bvadd (bvmul {} {}) {})", n("u1"), n("v0"), up(&n("w0"))),
),
(
"w1",
format!("(bvadd (bvmul {} {}) {})", n("u0"), n("v1"), lo(&n("t"))),
),
(
"hi",
format!(
"(bvadd (bvadd (bvmul {} {}) {}) {})",
n("u1"),
n("v1"),
up(&n("t")),
up(&n("w1"))
),
),
] {
def(out, &n(k), even, &body);
}
if even == ww {
return n("hi");
}
format!(
"((_ extract {} 0) (bvor (bvshl {} (_ bv1 {even})) (bvlshr (bvmul {} {}) (_ bv{ww} {even}))))",
ww - 1,
n("hi"),
n("a"),
n("b")
)
}
pub(crate) fn emit(cx: &mut Context, roots: &[Expr]) -> Result<String, Error> {
let ids = cx.ids(roots)?;
let order = cx.post_order_ids(&ids);
let mut out = String::new();
let name = |i: u32| format!("n{i}");
let mut declared: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for &i in &order {
let n = cx.node(i);
let body = match n.op {
op if op.as_ext().is_some() => {
let (_, k) = op.as_ext().unwrap_or((1, 0));
let o = cx
.registry
.as_deref()
.and_then(|r| r.op_at(n.aux))
.ok_or_else(|| {
Error::Contract("an extension node without its operation".into())
})?;
let kids: Vec<u32> = n.children().collect();
let args: Vec<String> = kids.iter().map(|&c| name(c)).collect();
let widths: Vec<Width> = kids.iter().map(|&c| cx.width_of(c)).collect();
let arg_refs: Vec<&str> = args.iter().map(String::as_str).collect();
match o.smtlib(k as u8, &arg_refs, &widths) {
Some(t) => {
if !one_term(&t) {
return Err(Error::Contract(format!(
"`{}` gave an SMT-LIB term that is not one balanced expression",
o.name()
)));
}
t
}
None => {
let ws: Vec<String> = widths.iter().map(|w| w.bits().to_string()).collect();
let f =
format!("|ext!{}#{}[{k}]@{}|", o.name(), o.revision(), ws.join(","));
if declared.insert(f.clone()) {
let sorts: Vec<String> =
widths.iter().map(|w| sort(w.bits())).collect();
writeln!(
out,
"(declare-fun {f} ({}) {})",
sorts.join(" "),
sort(n.width)
)
.ok();
}
format!("({f} {})", args.join(" "))
}
}
}
OpCode::Const => {
let v = cx
.const_val(i)
.ok_or_else(|| Error::Contract("a constant without a value".into()))?;
literal(&v)
}
OpCode::Sym => {
let id = crate::expr::SymbolId(n.a);
let key = cx
.symbol_key(id)
.cloned()
.ok_or_else(|| Error::Contract("dangling symbol".into()))?;
let sname = symbol_name(&key, n.a);
writeln!(out, "(declare-const {sname} {})", sort(n.width)).ok();
if let Some(k) = cx.declared_at(i) {
writeln!(
out,
"(assert (= (bvand {sname} {}) {}))",
literal(&k.known()),
literal(&k.known_one())
)
.ok();
}
sname
}
op => {
let kids: Vec<u32> = n.children().collect();
let args: Vec<String> = kids.iter().map(|&k| name(k)).collect();
let arg_w: Vec<u16> = kids.iter().map(|&k| cx.wid(k)).collect();
let tag = name(i);
let lo = if op == OpCode::Extract { n.b } else { 0 };
term(
&mut out,
op,
&App {
tag: &tag,
w: n.width,
args: &args,
arg_w: &arg_w,
lo,
fp: cx.fp_desc(i),
},
)?
}
};
writeln!(out, "(define-fun {} () {} {body})", name(i), sort(n.width)).ok();
}
for (k, &r) in ids.iter().enumerate() {
writeln!(
out,
"(define-fun root{k} () {} {})",
sort(cx.wid(r)),
name(r)
)
.ok();
}
Ok(out)
}
fn one_term(t: &str) -> bool {
let t = t.trim();
if t.is_empty() {
return false;
}
let mut depth = 0i64;
let mut closed_at_top = false;
let mut chars = t.chars().peekable();
while let Some(c) = chars.next() {
if closed_at_top {
return false;
}
match c {
'(' => depth += 1,
')' => {
depth -= 1;
if depth < 0 {
return false;
}
if depth == 0 {
closed_at_top = true;
}
}
'|' => {
if !chars.by_ref().any(|d| d == '|') {
return false;
}
}
'"' => loop {
match chars.next() {
None => return false,
Some('"') if chars.peek() == Some(&'"') => {
chars.next();
}
Some('"') => break,
_ => {}
}
},
';' => return false,
c if c.is_whitespace() && depth == 0 => return false,
_ => {}
}
}
depth == 0
}