use super::*;
use crate::constraints::func::{Func, Stmt};
use crate::constraints::ir::{ArrayNode, FuncId};
use crate::process::cfg::{constant_case_live_arm, live_statements, proc_body, unwrap_invalid};
use std::collections::VecDeque;
use sv_lang::kinds::StatementKind;
use sv_lang::{
ArgumentDirection, CaseStatementCondition, KnownSystemName, MethodFlags, Statement,
StatementBlockKind, SubroutineKind, UniquePriorityCheck, VariableFlags, VariableLifetime,
};
#[derive(Clone)]
pub(super) struct FnBase {
root: SymbolId,
name: String,
hops: Vec<StateHop>,
key: Vec<SymbolId>,
}
pub(super) struct Frame {
name: String,
slots: Vec<Ty>,
ix: HashMap<SymbolId, u32>,
ret: u32,
lvalue: Option<Expr>,
}
impl Frame {
fn add(&mut self, uid: SymbolId, ty: Ty) -> u32 {
let slot = self.slots.len() as u32;
self.slots.push(ty);
self.ix.insert(uid, slot);
slot
}
}
fn resize(x: Expr, to: Ty) -> Expr {
if x.ty() == to {
x
} else {
Expr::Resize(Box::new(x), to)
}
}
fn zero(ty: Ty) -> Expr {
Expr::Const(ty, BigUint::zero())
}
impl Builder<'_> {
fn bad_fn(&self, what: impl Into<String>) -> BuildError {
BuildError::BadFunction {
function: self
.frame
.as_ref()
.map_or_else(String::new, |f| f.name.clone()),
what: what.into(),
}
}
pub(super) fn not_in_function(&self, what: &str) -> Result<(), BuildError> {
if self.frame.is_some() {
return Err(self.bad_fn(format!("{what} is not supported inside a function body")));
}
Ok(())
}
pub(super) fn reject_calls(x: &Expr) -> Result<(), BuildError> {
if x.any_node(&mut |n| matches!(n, Expr::Array(ArrayNode::Call { .. }))) {
return Err(unsupported(
"a function call in a real-valued constraint",
"C13",
));
}
Ok(())
}
pub(super) fn bit_count_call(
&mut self,
e: Expression<'_>,
ty: Ty,
) -> Option<Result<Expr, BuildError>> {
let kind = e.system_subroutine()?.known_name_id();
if !matches!(
kind,
KnownSystemName::CountOnes
| KnownSystemName::OneHot
| KnownSystemName::OneHot0
| KnownSystemName::IsUnknown
| KnownSystemName::Clog2
) {
return None;
}
let args = e.operands();
let [arg] = args.as_slice() else {
return Some(Err(BuildError::Missing {
what: "single argument of a bit-counting system function".into(),
}));
};
let x = match self.expr(*arg) {
Ok(x) => x,
Err(err) => return Some(Err(err)),
};
let w = x.ty().width;
let int = Ty::signed(32);
let x = resize(x, Ty::unsigned(w));
let konst = |v: u32| Expr::Const(int, BigUint::from(v));
let sum = |terms: Vec<Expr>| {
terms
.into_iter()
.reduce(|a, b| Expr::Binary(BinOp::Add, Box::new(a), Box::new(b), int))
.unwrap_or_else(|| konst(0))
};
let cmp = |op: BinOp, a: Expr, b: Expr| Expr::Binary(op, Box::new(a), Box::new(b), Ty::BIT);
let ones = || {
sum((0..w)
.map(|i| {
Expr::Resize(
Box::new(Expr::Slice(Box::new(x.clone()), i, Ty::unsigned(1))),
int,
)
})
.collect())
};
let r = match kind {
KnownSystemName::CountOnes => ones(),
KnownSystemName::OneHot => {
Expr::Resize(Box::new(cmp(BinOp::Eq, ones(), konst(1))), int)
}
KnownSystemName::OneHot0 => {
Expr::Resize(Box::new(cmp(BinOp::Le, ones(), konst(1))), int)
}
KnownSystemName::IsUnknown => konst(0),
_ => sum((0..w)
.map(|i| {
let pow = Expr::Const(Ty::unsigned(w), BigUint::one() << i);
Expr::Resize(Box::new(cmp(BinOp::Gt, x.clone(), pow)), int)
})
.collect()),
};
Some(Ok(resize(r, ty)))
}
pub(super) fn frame_var(&self, sym: Symbol<'_>) -> Option<Expr> {
let f = self.frame.as_ref()?;
let &slot = f.ix.get(&sym.uid())?;
Some(Expr::Var(slot, f.slots[slot as usize]))
}
pub(super) fn rand_read_in_function(&self, name: &str) -> BuildError {
self.bad_fn(format!(
"it reads the random variable `{name}` other than through an argument (a call \
precedes the solve, so such a read has no value; pass it as an argument)"
))
}
pub(super) fn lvalue_reference(&self) -> Result<Expr, BuildError> {
self.frame
.as_ref()
.and_then(|f| f.lvalue.clone())
.ok_or_else(|| BuildError::Missing {
what: "target of a compound assignment".into(),
})
}
fn fexpr(&mut self, e: Expression<'_>) -> Result<Expr, BuildError> {
let x = self.expr(e)?;
if x.any_node(&mut |n| matches!(n, Expr::Array(a) if !matches!(a, ArrayNode::Call { .. })))
{
return Err(self.bad_fn("it uses an array, which a function body cannot hold"));
}
Ok(x)
}
fn fbool(&mut self, e: Expression<'_>) -> Result<Expr, BuildError> {
Ok(to_bool(self.fexpr(e)?))
}
pub(super) fn user_call(&mut self, e: Expression<'_>, ty: Ty) -> Result<Expr, BuildError> {
let sub = e.call_subroutine().ok_or_else(|| BuildError::Missing {
what: "callee of a call".into(),
})?;
let name = sub.name().to_string();
self.arrays_allowed(&format!("a call to `{name}`"))?;
let through_handle = e.call_this_class().is_some_and(|t| {
!(t.kind() == ExpressionKind::NamedValue
&& t.referenced_symbol().is_some_and(|s| s.name() == "this"))
});
if sub.subroutine_is_virtual()
&& through_handle
&& self.frame.is_none()
&& self.virtual_targets.is_some()
{
return self.virtual_handle_call(e, sub, ty);
}
let sub = if sub.subroutine_is_virtual() && (self.fn_base.is_some() || through_handle) {
return Err(unsupported(
format!(
"a call to the virtual function `{name}` on an object other than the \
randomized one (that object's dynamic class is only known at run time)"
),
"C17 follow-up",
));
} else if sub.subroutine_is_virtual() {
match self.virtual_impl.as_ref().map(|r| r(sub)) {
Some(Some(imp)) => imp,
Some(None) => {
return Err(unsupported(
format!(
"a call to the virtual function `{name}`, which the object's class has no implementation of"
),
"C13",
));
}
None => sub,
}
} else {
sub
};
if sub.subroutine_flags().contains(MethodFlags::BUILT_IN) || sub.body().is_none() {
return Err(unsupported(
format!("a call to the built-in or bodiless subroutine `{name}`"),
"C13",
));
}
if sub.subroutine_kind() != Some(SubroutineKind::Function) {
return Err(unsupported(format!("a call to the task `{name}`"), "C13"));
}
let base = match e.call_this_class() {
Some(t) if through_handle => Some(self.handle_base(t, &name)?),
_ => self.fn_base.clone(),
};
let id = self.function_id(sub, base)?;
let ret_ty = self.func_ret_ty(id);
let formals = self.funcs[id as usize].n_params;
let mut actuals = e.operands();
if e.call_this_class().is_some() && actuals.len() == formals + 1 {
actuals.remove(0);
}
if actuals.len() != formals {
return Err(BuildError::Missing {
what: format!(
"{formals} argument(s) of the call to `{name}` (found {})",
actuals.len()
),
});
}
let mut args = Vec::with_capacity(actuals.len());
for (k, a) in actuals.into_iter().enumerate() {
let x = if self.frame.is_some() {
self.fexpr(a)?
} else {
self.expr(a)?
};
args.push(resize(x, self.funcs[id as usize].slots[k]));
}
let call = Expr::Array(ArrayNode::Call {
func: id,
args,
ty: ret_ty,
});
Ok(resize(call, ty))
}
fn virtual_handle_call(
&mut self,
e: Expression<'_>,
callee: Symbol<'_>,
ty: Ty,
) -> Result<Expr, BuildError> {
let name = callee.name().to_string();
let t = e.call_this_class().ok_or_else(|| BuildError::Missing {
what: "receiver of a call".into(),
})?;
let base = self.handle_base(t, &name)?;
let static_class = Self::handle_path(t)?
.last()
.and_then(|h| h.value_type())
.map(|t| t.canonical().as_symbol())
.ok_or_else(|| BuildError::Missing {
what: format!("static class of the handle a call to `{name}` goes through"),
})?;
let targets = (self
.virtual_targets
.as_ref()
.expect("checked by the caller"))(callee, static_class);
if targets.is_empty() {
return Err(unsupported(
format!(
"a call to the virtual function `{name}` through a handle none of whose possible classes implements it"
),
"C13",
));
}
let mut actuals = e.operands();
let formals = actuals.len().saturating_sub(1);
if !actuals.is_empty() {
actuals.remove(0);
}
let mut args = Vec::with_capacity(formals);
for a in actuals {
args.push(self.expr(a)?);
}
let low = targets.iter().map(|&(id, _)| id).min().unwrap_or(0);
let high = targets.iter().map(|&(id, _)| id).max().unwrap_or(0);
let mut elems: Vec<Option<Expr>> = vec![None; (high - low) as usize + 1];
for (id, imp) in targets {
if imp.subroutine_flags().contains(MethodFlags::BUILT_IN) || imp.body().is_none() {
return Err(unsupported(
format!("a call to the built-in or bodiless subroutine `{name}`"),
"C13",
));
}
if imp.subroutine_kind() != Some(SubroutineKind::Function) {
return Err(unsupported(format!("a call to the task `{name}`"), "C13"));
}
let fid = self.function_id(imp, Some(base.clone()))?;
if self.funcs[fid as usize].n_params != args.len() {
return Err(BuildError::Missing {
what: format!("arguments of the call to `{name}`"),
});
}
let ret_ty = self.func_ret_ty(fid);
let conv: Vec<Expr> = args
.iter()
.enumerate()
.map(|(k, a)| resize(a.clone(), self.funcs[fid as usize].slots[k]))
.collect();
let call = Expr::Array(ArrayNode::Call {
func: fid,
args: conv,
ty: ret_ty,
});
elems[(id - low) as usize] = Some(resize(call, ty));
}
let elems = elems
.into_iter()
.map(|x| x.unwrap_or_else(|| Expr::Const(ty, BigUint::default())))
.collect();
let sel = self.class_of_state(&base);
Ok(Expr::Array(ArrayNode::Pick {
index: Box::new(sel),
low: i64::from(low),
elems,
ty,
}))
}
fn class_of_state(&mut self, base: &FnBase) -> Expr {
let ty = Ty::unsigned(32);
let key = (base.key.clone(), u32::MAX);
if let Some(&id) = self.state_ix.get(&key) {
return Expr::State(id, ty);
}
let id = self.states.len() as u32;
self.states.push(StateInfo {
symbol: base.root.clone(),
name: format!("{} (dynamic class)", base.name),
ty,
via: base.hops.clone(),
window: None,
root_bit: 0,
class_of: true,
});
self.state_ix.insert(key, id);
Expr::State(id, ty)
}
fn handle_base(&mut self, t: Expression<'_>, callee: &str) -> Result<FnBase, BuildError> {
let refuse = |what: String| unsupported(what, "C17 follow-up");
let path = Self::handle_path(t)?;
if path.is_empty() || self.handle_root_element(t)?.is_some() {
return Err(refuse(format!(
"a call to `{callee}` through a handle that is not a plain property chain"
)));
}
for step in &path {
if !step.value_type().is_some_and(|t| t.canonical().is_class()) {
return Err(refuse(format!(
"a call to `{callee}` through `{}`, which is not a class handle",
step.name()
)));
}
}
let (mut base, rest) = match self.fn_base.clone() {
Some(b) => (b, &path[..]),
None => {
let root = path[0];
if self.var_ix.contains_key(&root.uid()) {
return Err(refuse(format!(
"a call to `{callee}` through the randomized handle `{}`",
root.name()
)));
}
let b = FnBase {
root: root.uid(),
name: root.name().to_string(),
hops: Vec::new(),
key: vec![root.uid()],
};
(b, &path[1..])
}
};
for h in rest {
let slot = self.hop_slot(&base.name, *h)?;
base.name = format!("{}.{}", base.name, h.name());
base.hops.push(StateHop {
symbol: h.uid(),
slot,
});
base.key.push(h.uid());
}
Ok(base)
}
fn hop_slot(&self, name: &str, member: Symbol<'_>) -> Result<FieldSlot, BuildError> {
let resolver = self
.slot_of
.as_ref()
.ok_or_else(|| BuildError::ForeignObjectAccess {
what: format!(
"read of `{name}.{}` (this builder was given no object layout to place \
another object's members)",
member.name()
),
})?;
resolver(member).map_err(|why| BuildError::Missing {
what: format!("field slot of `{name}.{}`: {why}", member.name()),
})
}
pub(super) fn based_state(
&mut self,
base: &FnBase,
path: &[Symbol<'_>],
ty: Ty,
) -> Result<Expr, BuildError> {
let mut key = base.key.clone();
key.extend(path.iter().map(|m| m.uid()));
let key = (key, 0);
if let Some(&id) = self.state_ix.get(&key) {
return Ok(Expr::State(id, self.states[id as usize].ty));
}
let mut name = base.name.clone();
let mut via = base.hops.clone();
for m in path {
let slot = self.hop_slot(&name, *m)?;
name = format!("{name}.{}", m.name());
via.push(StateHop {
symbol: m.uid(),
slot,
});
}
let id = self.states.len() as u32;
self.states.push(StateInfo {
symbol: base.root.clone(),
name,
ty,
via,
window: None,
root_bit: 0,
class_of: false,
});
self.state_ix.insert(key, id);
Ok(Expr::State(id, ty))
}
fn func_ret_ty(&self, id: FuncId) -> Ty {
let f = &self.funcs[id as usize];
f.slots[f.ret as usize]
}
fn function_id(&mut self, sub: Symbol<'_>, base: Option<FnBase>) -> Result<FuncId, BuildError> {
let uid = (
sub.uid(),
base.as_ref().map(|b| b.key.clone()).unwrap_or_default(),
);
if let Some(&id) = self.func_ix.get(&uid) {
return Ok(id);
}
let name = sub.name().to_string();
if sub.subroutine_is_virtual() && self.virtual_impl.is_none() {
return Err(unsupported(
format!(
"a call to the virtual function `{name}` (an override in the object's \
class would be the one called)"
),
"C13",
));
}
let id = self.funcs.len() as FuncId;
self.funcs.push(Func {
name: name.clone(),
slots: Vec::new(),
n_params: 0,
ret: 0,
body: Vec::new(),
});
self.func_ix.insert(uid, id);
let saved = self.frame.take();
let saved_base = std::mem::replace(&mut self.fn_base, base);
let r = self.translate_function(sub, id);
self.frame = saved;
self.fn_base = saved_base;
if let Err(e) = r {
self.funcs.truncate(id as usize);
self.func_ix.retain(|_, v| *v < id);
return Err(e);
}
Ok(id)
}
fn translate_function(&mut self, sub: Symbol<'_>, id: FuncId) -> Result<(), BuildError> {
let name = sub.name().to_string();
let ret_var = sub
.subroutine_return_val_var()
.ok_or_else(|| BuildError::Missing {
what: format!("return variable of function `{name}`"),
})?;
let ret_ty = ty_of(
sub.subroutine_return_type(),
&format!("the return value of function `{name}`"),
)?;
self.frame = Some(Frame {
name: name.clone(),
slots: Vec::new(),
ix: HashMap::new(),
ret: 0,
lvalue: None,
});
for formal in sub.subroutine_arguments() {
let fname = formal.name().to_string();
match formal.formal_argument_direction() {
ArgumentDirection::In => {}
ArgumentDirection::Ref
if formal.variable_flags().contains(VariableFlags::CONST) => {}
dir => {
return Err(self.bad_fn(format!(
"formal `{fname}` is {dir:?}: only `input` and `const ref` arguments are \
allowed (a function in a constraint has no side effects)"
)));
}
}
let ty = ty_of(
formal.value_type(),
&format!("formal `{fname}` of `{name}`"),
)?;
self.frame_mut().add(formal.uid(), ty);
}
let n_params = self.frame_mut().slots.len();
let ret = self.frame_mut().add(ret_var.uid(), ret_ty);
self.frame_mut().ret = ret;
{
let f = &mut self.funcs[id as usize];
f.slots = self
.frame
.as_ref()
.map(|fr| fr.slots.clone())
.unwrap_or_default();
f.n_params = n_params;
f.ret = ret;
}
let this_uid = sub.subroutine_this_var().map(|t| t.uid());
let mut decls: VecDeque<Symbol<'_>> = sub
.members()
.filter(|m| {
m.kind() == SymbolKind::Variable
&& m.uid() != ret_var.uid()
&& Some(m.uid()) != this_uid
})
.collect();
let body_stmt = proc_body(&sub).ok_or_else(|| BuildError::Missing {
what: format!("body of function `{name}`"),
})?;
let mut body = Vec::new();
self.stmt(body_stmt, &mut decls, &mut body)?;
if !decls.is_empty() {
return Err(BuildError::Missing {
what: format!(
"declaration statements for {} local variable(s) of function `{name}`",
decls.len()
),
});
}
let frame = self.frame.take().ok_or_else(|| BuildError::Missing {
what: "function frame".into(),
})?;
self.funcs[id as usize] = Func {
name,
slots: frame.slots,
n_params,
ret: frame.ret,
body,
};
Ok(())
}
fn frame_mut(&mut self) -> &mut Frame {
self.frame
.as_mut()
.expect("a function frame while a function body is built")
}
fn local_slot(&mut self, sym: Symbol<'_>) -> Result<(u32, Ty), BuildError> {
if let Some(Expr::Var(slot, ty)) = self.frame_var(sym) {
return Ok((slot, ty));
}
let name = sym.name().to_string();
let ty = ty_of(sym.value_type(), &format!("local variable `{name}`"))?;
let slot = self.frame_mut().add(sym.uid(), ty);
Ok((slot, ty))
}
fn declare_local(&mut self, sym: Symbol<'_>, out: &mut Vec<Stmt>) -> Result<(), BuildError> {
let name = sym.name().to_string();
let init = sym.initializer();
if init.is_some()
&& sym.variable_lifetime() == VariableLifetime::Static
&& !sym.variable_flags().contains(VariableFlags::CONST)
{
return Err(self.bad_fn(format!(
"the static local `{name}` has an initializer, so it keeps its value from one \
call to the next (declare it `automatic`)"
)));
}
let (slot, ty) = self.local_slot(sym)?;
let value = match init {
Some(i) => resize(self.fexpr(i)?, ty),
None => zero(ty),
};
out.push(Stmt::Assign(slot, value));
Ok(())
}
fn block<'d>(
&mut self,
stmts: Vec<Statement<'d>>,
decls: &mut VecDeque<Symbol<'d>>,
out: &mut Vec<Stmt>,
) -> Result<(), BuildError> {
for s in stmts {
self.stmt(s, decls, out)?;
}
Ok(())
}
fn sub_stmt<'d>(
&mut self,
s: Statement<'d>,
decls: &mut VecDeque<Symbol<'d>>,
) -> Result<Vec<Stmt>, BuildError> {
let mut v = Vec::new();
self.stmt(s, decls, &mut v)?;
Ok(v)
}
fn check_none(&self, what: &str, check: Option<UniquePriorityCheck>) -> Result<(), BuildError> {
match check {
None | Some(UniquePriorityCheck::None) => Ok(()),
Some(other) => Err(self.bad_fn(format!("a `{other:?}` {what}"))),
}
}
fn stmt<'d>(
&mut self,
s: Statement<'d>,
decls: &mut VecDeque<Symbol<'d>>,
out: &mut Vec<Stmt>,
) -> Result<(), BuildError> {
let s = unwrap_invalid(s);
let kind = s.kind();
match kind {
StatementKind::Empty => Ok(()),
StatementKind::List => self.block(live_statements(&s), decls, out),
StatementKind::Block => {
if s.block_kind() != Some(StatementBlockKind::Sequential) {
return Err(self.bad_fn("a `fork` block"));
}
match s.block_symbol() {
Some(bs) => {
let mut inner: VecDeque<Symbol<'d>> = bs
.members()
.filter(|m| m.kind() == SymbolKind::Variable)
.collect();
self.block(live_statements(&s), &mut inner, out)?;
if !inner.is_empty() {
return Err(BuildError::Missing {
what: format!(
"declaration statements for {} local variable(s) of block `{}`",
inner.len(),
bs.name()
),
});
}
Ok(())
}
None => self.block(live_statements(&s), decls, out),
}
}
StatementKind::VariableDeclaration => {
let sym = decls.pop_front().ok_or_else(|| BuildError::Missing {
what: "the variable a declaration statement declares".into(),
})?;
self.declare_local(sym, out)
}
StatementKind::ExpressionStatement => {
let e = s.expr().ok_or_else(|| BuildError::Missing {
what: "expression of an expression statement".into(),
})?;
self.effect(e, out)
}
StatementKind::Return => {
if let Some(e) = s.expr() {
let (ret, ty) = {
let f = self.frame_mut();
(f.ret, f.slots[f.ret as usize])
};
let v = resize(self.fexpr(e)?, ty);
out.push(Stmt::Assign(ret, v));
}
out.push(Stmt::Return);
Ok(())
}
StatementKind::Break => {
out.push(Stmt::Break);
Ok(())
}
StatementKind::Continue => {
out.push(Stmt::Continue);
Ok(())
}
StatementKind::Conditional => {
self.check_none("`if`", s.check())?;
let conds = s.conditions();
let [c] = conds.as_slice() else {
return Err(self.bad_fn("a `&&&` condition"));
};
if c.pattern().is_some() {
return Err(self.bad_fn("a pattern-matching `if`"));
}
if let Some(v) = c.expr().constant_value().and_then(|v| v.as_i64()) {
let live = if v != 0 {
s.then_branch()
} else {
s.else_branch()
};
if let Some(l) = live {
self.stmt(l, decls, out)?;
}
return Ok(());
}
let cond = self.fbool(c.expr())?;
let then = match s.then_branch() {
Some(t) => self.sub_stmt(t, decls)?,
None => Vec::new(),
};
let otherwise = match s.else_branch() {
Some(t) => self.sub_stmt(t, decls)?,
None => Vec::new(),
};
out.push(Stmt::If(cond, then, otherwise));
Ok(())
}
StatementKind::Case => self.case_stmt(s, decls, out),
StatementKind::ForLoop => {
for init in s.for_loop_initializers() {
self.effect(init, out)?;
}
for v in s.for_loop_vars() {
if self.frame_var(v).is_none() {
self.declare_local(v, out)?;
}
}
let cond = match s.condition() {
Some(c) => Some(self.fbool(c)?),
None => None,
};
let mut step = Vec::new();
for st in s.for_loop_steps() {
self.effect(st, &mut step)?;
}
let body = self.loop_body(s, decls)?;
out.push(Stmt::Loop {
cond,
body,
step,
test_first: true,
});
Ok(())
}
StatementKind::WhileLoop => {
let cond = match s.condition() {
Some(c) => Some(self.fbool(c)?),
None => None,
};
let body = self.loop_body(s, decls)?;
out.push(Stmt::Loop {
cond,
body,
step: Vec::new(),
test_first: true,
});
Ok(())
}
StatementKind::DoWhileLoop => {
let cond = match s.condition() {
Some(c) => Some(self.fbool(c)?),
None => None,
};
let body = self.loop_body(s, decls)?;
out.push(Stmt::Loop {
cond,
body,
step: Vec::new(),
test_first: false,
});
Ok(())
}
StatementKind::ForeverLoop => {
let body = self.loop_body(s, decls)?;
out.push(Stmt::Loop {
cond: None,
body,
step: Vec::new(),
test_first: true,
});
Ok(())
}
StatementKind::RepeatLoop => {
let count = s.condition().ok_or_else(|| BuildError::Missing {
what: "count of a `repeat`".into(),
})?;
let n = self.fexpr(count)?;
let ty = n.ty();
let slot = self.frame_mut().slots.len() as u32;
self.frame_mut().slots.push(ty);
out.push(Stmt::Assign(slot, n));
let t = Expr::Var(slot, ty);
let body = self.loop_body(s, decls)?;
out.push(Stmt::Loop {
cond: Some(bin(BinOp::Gt, t.clone(), zero(ty), Ty::BIT)),
body,
step: vec![Stmt::Assign(slot, bin(BinOp::Sub, t, iconst(ty, 1), ty))],
test_first: true,
});
Ok(())
}
other => Err(self.bad_fn(format!(
"a `{other:?}` statement (supported: blocks, declarations, assignments, `if`, \
`case`, loops, `return`, `break`, `continue`)"
))),
}
}
fn loop_body<'d>(
&mut self,
s: Statement<'d>,
decls: &mut VecDeque<Symbol<'d>>,
) -> Result<Vec<Stmt>, BuildError> {
match s.body() {
Some(b) => self.sub_stmt(b, decls),
None => Ok(Vec::new()),
}
}
fn case_stmt<'d>(
&mut self,
s: Statement<'d>,
decls: &mut VecDeque<Symbol<'d>>,
out: &mut Vec<Stmt>,
) -> Result<(), BuildError> {
self.check_none("`case`", s.check())?;
if let Some(live) = constant_case_live_arm(&s) {
return match live {
Some(l) => self.stmt(l, decls, out),
None => Ok(()),
};
}
match s.case_condition() {
Some(CaseStatementCondition::Normal) => {}
other => {
return Err(self.bad_fn(format!("a `case` of kind {other:?} (only plain `case`)")));
}
}
let sel = s.expr().ok_or_else(|| BuildError::Missing {
what: "selector of a `case`".into(),
})?;
let sel = self.fexpr(sel)?;
let mut arms: Vec<(Expr, Vec<Stmt>)> = Vec::new();
for item in s.case_items() {
let mut hit: Option<Expr> = None;
for x in item.expressions() {
let x = self.fexpr(x)?;
let t = common_ty(sel.ty(), x.ty());
let eq = bin(BinOp::Eq, coerce(sel.clone(), t), coerce(x, t), Ty::BIT);
hit = Some(match hit {
None => eq,
Some(prev) => bin(BinOp::LogOr, prev, eq, Ty::BIT),
});
}
let hit = hit.ok_or_else(|| BuildError::Missing {
what: "expressions of a `case` item".into(),
})?;
arms.push((hit, self.sub_stmt(item.stmt(), decls)?));
}
let mut acc = match s.default_case() {
Some(d) => self.sub_stmt(d, decls)?,
None => Vec::new(),
};
for (hit, body) in arms.into_iter().rev() {
acc = vec![Stmt::If(hit, body, acc)];
}
out.extend(acc);
Ok(())
}
fn effect(&mut self, e: Expression<'_>, out: &mut Vec<Stmt>) -> Result<(), BuildError> {
match e.kind() {
ExpressionKind::Assignment => {
if e.is_nonblocking() == Some(true) {
return Err(self.bad_fn("a nonblocking assignment"));
}
let (Some(lhs), Some(rhs)) = (e.left(), e.right()) else {
return Err(BuildError::Missing {
what: "operands of an assignment".into(),
});
};
let (slot, ty) = self.target(lhs)?;
let target = Expr::Var(slot, ty);
let saved = self.frame_mut().lvalue.replace(target);
let v = self.fexpr(rhs);
self.frame_mut().lvalue = saved;
out.push(Stmt::Assign(slot, resize(v?, ty)));
Ok(())
}
ExpressionKind::UnaryOp => {
let op = e.unary_op().ok_or_else(|| BuildError::Missing {
what: "operator of a unary expression".into(),
})?;
let bop = match op {
UnaryOp::Preincrement | UnaryOp::Postincrement => BinOp::Add,
UnaryOp::Predecrement | UnaryOp::Postdecrement => BinOp::Sub,
other => {
return Err(self.bad_fn(format!("the expression statement `{other:?}`")));
}
};
let (slot, ty) = self.target(Self::operand(e, "`++`/`--`")?)?;
out.push(Stmt::Assign(
slot,
bin(bop, Expr::Var(slot, ty), iconst(ty, 1), ty),
));
Ok(())
}
other => Err(self.bad_fn(format!(
"an expression statement of kind {other:?} (only assignments and `++`/`--`)"
))),
}
}
fn target(&mut self, lhs: Expression<'_>) -> Result<(u32, Ty), BuildError> {
let mut peeled = lhs;
while peeled.kind() == ExpressionKind::Conversion {
match peeled.conversion_operand() {
Some(inner) => peeled = inner,
None => break,
}
}
if peeled.kind() == ExpressionKind::NamedValue
&& let Some(sym) = peeled.referenced_symbol()
&& let Some(Expr::Var(slot, ty)) = self.frame_var(sym)
{
return Ok((slot, ty));
}
Err(self.bad_fn(format!(
"an assignment to a `{:?}` that is not a plain local variable of the function (a \
function called from a constraint cannot change state; selects on the left of an \
assignment are not supported)",
peeled.kind()
)))
}
}