use crate::conv::Context;
use crate::conv::checker::portability::allow_multiple_assign;
use crate::conv::utils::{
check_compatibility, check_implicit_clock_conversion, eval_array_literal,
};
use crate::ir::assign_table::{AssignContext, AssignTable};
use crate::ir::ff_table::AssignTarget;
use crate::ir::{
AssignDestination, Comptime, Expression, FfTable, IrResult, Shape, Signature, Statement,
ValueVariant, VarId, VarIndex, VarPath, VarPathSelect,
};
use crate::symbol::{Direction, Symbol, SymbolId, SymbolKind};
use crate::value::{Value, ValueBigUint};
use crate::{AnalyzerError, HashMap, ir_error};
use indent::indent_all_by;
use std::fmt;
use veryl_parser::resource_table::StrId;
use veryl_parser::token_range::TokenRange;
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct FuncPath {
pub path: VarPath,
pub sig: Signature,
}
impl FuncPath {
pub fn new(id: SymbolId) -> Self {
Self {
path: VarPath::default(),
sig: Signature::new(id),
}
}
pub fn add_prelude(&mut self, x: &[StrId]) {
self.path.add_prelude(x)
}
pub fn base(&self) -> FuncPath {
let mut ret = self.clone();
ret.sig.parameters.clear();
ret.sig.generic_parameters.clear();
ret
}
}
impl fmt::Display for FuncPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut ret = String::new();
if !self.path.0.is_empty() {
ret.push_str(&format!("{}.", self.path));
}
ret.push_str(&format!("{}", self.sig));
ret.fmt(f)
}
}
#[derive(Clone)]
pub struct FuncArg {
pub name: StrId,
pub comptime: Comptime,
pub members: Vec<(VarPath, Comptime, Direction)>,
}
#[derive(Clone)]
pub struct Function {
pub name: StrId,
pub id: VarId,
pub path: FuncPath,
pub r#type: Comptime,
pub array: Shape,
pub arity: usize,
pub args: Vec<FuncArg>,
pub is_const: bool,
pub functions: Vec<FunctionBody>,
pub token: TokenRange,
}
impl Function {
pub fn eval_assign(&self, context: &mut Context, assign_table: &mut AssignTable) {
for x in &self.functions {
x.eval_assign(context, assign_table);
}
}
pub fn set_index(&mut self, index: &VarIndex) {
for x in &mut self.functions {
x.set_index(index);
}
}
pub fn get_function(&self, index: &[usize]) -> Option<FunctionBody> {
let index = self.array.calc_index(index)?;
self.functions.get(index).cloned()
}
pub fn to_proto(&self) -> FuncProto {
FuncProto {
name: self.name,
id: self.id,
r#type: self.r#type.clone(),
arity: self.arity,
args: self.args.clone(),
token: self.token,
}
}
}
#[derive(Clone)]
pub struct FuncProto {
pub name: StrId,
pub id: VarId,
pub r#type: Comptime,
pub arity: usize,
pub args: Vec<FuncArg>,
pub token: TokenRange,
}
#[derive(Clone)]
pub struct FunctionBody {
pub ret: Option<VarId>,
pub arg_map: HashMap<VarPath, VarId>,
pub statements: Vec<Statement>,
}
impl FunctionBody {
pub fn eval_assign(&self, context: &mut Context, assign_table: &mut AssignTable) {
for x in &self.statements {
x.eval_assign(context, assign_table, AssignContext::Function, &[]);
}
}
pub fn set_index(&mut self, index: &VarIndex) {
for x in &mut self.statements {
x.set_index(index);
}
}
}
impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut ret = String::new();
for (i, f) in self.functions.iter().enumerate() {
if self.functions.len() == 1 {
ret.push_str(&format!("func {}({})", self.id, self.path));
} else {
ret.push_str(&format!("func {}[{}]({})", self.id, i, self.path));
}
if let Some(x) = f.ret {
ret.push_str(&format!(" -> {x}"));
}
ret.push_str(" {\n");
for s in &f.statements {
let text = format!("{}\n", s);
ret.push_str(&indent_all_by(2, text));
}
ret.push_str("}\n");
}
ret.trim_end().fmt(f)
}
}
#[derive(Clone, Debug)]
pub struct FunctionCall {
pub id: VarId,
pub index: Option<Vec<usize>>,
pub comptime: Comptime,
pub inputs: CallArgs<Expression>,
pub outputs: CallArgs<Vec<AssignDestination>>,
}
#[derive(Hash, PartialEq, Eq)]
struct FunctionValueKey {
id: VarId,
index: Option<Vec<usize>>,
inputs: Vec<(VarPath, Value)>,
}
#[derive(Default)]
pub(crate) struct FunctionValueCache {
depth: usize,
bits: usize,
values: HashMap<FunctionValueKey, Value>,
}
fn array_arg_values(context: &mut Context, expr: &Expression, len: usize) -> Option<Vec<Value>> {
let Expression::Term(factor) = expr else {
return None;
};
match factor.as_ref() {
crate::ir::Factor::Value(comptime) => match &comptime.value {
ValueVariant::NumericArray(values) => values.get(0..len).map(<[Value]>::to_vec),
_ => None,
},
crate::ir::Factor::Variable(id, index, select, _) => {
if !select.is_empty() || !index.eval_value(context)?.is_empty() {
return None;
}
let src = context.variables.get(id)?;
src.value.get(0..len).map(<[Value]>::to_vec)
}
_ => None,
}
}
impl FunctionCall {
pub fn eval_type(&mut self, context: &mut Context) {
self.comptime.is_const = self.eval_comptime_flag(context);
}
pub fn eval_value(&self, context: &mut Context) -> Option<Value> {
#[cfg(test)]
VALUE_EVALUATIONS.set(VALUE_EVALUATIONS.get() + 1);
context.function_value_cache.depth += 1;
let result = self.eval_value_inner(context);
context.function_value_cache.depth -= 1;
if context.function_value_cache.depth == 0 {
context.function_value_cache.values.clear();
context.function_value_cache.bits = 0;
}
result
}
fn eval_value_inner(&self, context: &mut Context) -> Option<Value> {
let func = context.functions.get(&self.id)?;
let mut cacheable = func.is_const && self.outputs.is_empty();
let func = if let Some(x) = &self.index {
func.get_function(x)
} else {
func.get_function(&[])
}?;
let mut inputs = Vec::new();
let mut input_bits = 0usize;
let mut evaluated_inputs = Vec::new();
let mut evaluated_arrays = Vec::new();
for (path, expr) in &self.inputs {
let id = *func.arg_map.get(path)?;
let total = context
.variables
.get(&id)?
.r#type
.total_array()
.unwrap_or(1);
if total > 1 {
let values = array_arg_values(context, expr, total)?;
cacheable = false;
evaluated_arrays.push((id, values));
continue;
}
let value = expr.eval_value(context)?;
if cacheable {
input_bits = input_bits.saturating_add(value.width().max(64));
inputs.push((path.clone(), value.clone()));
}
evaluated_inputs.push((id, value));
}
for (id, value) in evaluated_inputs {
let var = context.variable_mut(&id)?;
var.set_value(&[], value, None);
}
for (id, values) in evaluated_arrays {
context.variable_mut(&id)?.value = values;
}
let key = cacheable.then(|| FunctionValueKey {
id: self.id,
index: self.index.clone(),
inputs,
});
if let Some(value) = key
.as_ref()
.and_then(|key| context.function_value_cache.values.get(key))
{
return Some(value.clone());
}
let disable_const_opt = context.disalbe_const_opt;
context.disalbe_const_opt = true;
let prior_overflow = context.comptime_for_overflow.take();
let prior_ret_var = std::mem::replace(&mut context.function_ret_var, func.ret);
let prior_returned = std::mem::replace(&mut context.function_returned, false);
for x in &func.statements {
if x.eval_value(context) == crate::ir::ControlFlow::Break {
break;
}
}
context.function_ret_var = prior_ret_var;
context.function_returned = prior_returned;
let overflowed = context.comptime_for_overflow.is_some();
if let Some(x) = prior_overflow {
context.comptime_for_overflow.get_or_insert(x);
}
context.disalbe_const_opt = disable_const_opt;
if overflowed {
return None;
}
if let Some(x) = &func.ret {
let variable = context.variables.get(x)?;
let value = variable.get_value(&[]).cloned();
if let Some(key) = key
&& let Some(value) = &value
{
let bits = input_bits.saturating_add(value.width().max(64));
let total = context.function_value_cache.bits.saturating_add(bits);
if total <= context.config.evaluate_size_limit {
context.function_value_cache.bits = total;
context
.function_value_cache
.values
.insert(key, value.clone());
}
}
value
} else {
None
}
}
pub fn eval_comptime(&mut self, context: &mut Context) -> Comptime {
let value = self.eval_value(context);
let value = if let Some(x) = value {
ValueVariant::Numeric(x)
} else {
ValueVariant::Unknown
};
let mut ret = self.comptime.clone();
ret.value = value;
ret.is_const = self.eval_comptime_flag(context);
ret
}
pub fn eval_assign(
&self,
context: &mut Context,
assign_table: &mut AssignTable,
assign_context: AssignContext,
) {
for expr in self.inputs.values() {
expr.eval_assign(context, assign_table, assign_context);
}
for output in self.outputs.values() {
for dst in output {
if let Some(index) = dst.index.eval_value(context) {
let Some(variable) = context.get_variable_info(dst.id) else {
continue;
};
if let Some((beg, end)) = dst.select.conservative_packed_range(
context,
&variable.r#type,
dst.comptime.member_select_domain,
) {
let mask = ValueBigUint::gen_mask_range(beg, end);
let dynamic = !dst.index.is_const() || !dst.select.is_const_with_range();
let (success, tokens) = assign_table.insert_assign(
&variable,
index,
mask,
dynamic,
dynamic,
self.comptime.token,
);
if !success
&& assign_context.is_ff()
&& !allow_multiple_assign(context, dst.id)
{
context.insert_error(AnalyzerError::multiple_assignment(
&variable.path.to_string(),
&self.comptime.token,
&tokens,
));
}
}
}
}
}
}
pub fn gather_ff(
&self,
context: &mut Context,
table: &mut FfTable,
decl: usize,
assign_target: Option<&AssignTarget>,
from_ff: bool,
) {
for input in self.inputs.values() {
input.gather_ff(context, table, decl, assign_target, from_ff);
}
for dsts in self.outputs.values() {
for dst in dsts {
if from_ff {
dst.gather_ff(context, table, decl);
} else {
dst.gather_ff_comb_assign(context, table, decl);
}
}
}
}
pub fn gather_ff_comb_assign(&self, context: &mut Context, table: &mut FfTable, decl: usize) {
for dsts in self.outputs.values() {
for dst in dsts {
dst.gather_ff_comb_assign(context, table, decl);
}
}
}
pub fn set_index(&mut self, index: &VarIndex) {
for x in self.inputs.values_mut() {
x.set_index(index);
}
for x in self.outputs.values_mut() {
for x in x {
x.set_index(index);
}
}
}
fn eval_comptime_flag(&mut self, context: &mut Context) -> bool {
let mut is_const = context
.functions
.get(&self.id)
.map(|func| func.is_const)
.unwrap_or(true);
for expr in self.inputs.values_mut() {
is_const &= expr.eval_comptime(context, None).is_const;
}
if !self.outputs.is_empty() {
is_const = false;
}
is_const
}
}
#[cfg(test)]
thread_local! {
pub(crate) static VALUE_EVALUATIONS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
impl fmt::Display for FunctionCall {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut args = String::new();
let mut inputs: Vec<_> = self.inputs.iter().map(|(k, v)| (k, v)).collect();
let mut outputs: Vec<_> = self.outputs.iter().map(|(k, v)| (k, v)).collect();
inputs.sort_by_key(|x| x.0);
outputs.sort_by_key(|x| x.0);
for (id, val) in &inputs {
args.push_str(&format!("{id}: {val}, "));
}
for (id, val) in &outputs {
if val.len() == 1 {
args.push_str(&format!("{id}: {}, ", val[0]));
} else {
args.push_str(&format!("{id}: {{{}", val[0]));
for x in &val[1..] {
args.push_str(&format!(", {x}"));
}
args.push_str("}}, ");
}
}
let args = if args.is_empty() {
&args
} else {
&args[0..args.len() - 2]
};
let mut index = String::new();
if let Some(x) = &self.index {
for x in x {
index.push_str(&format!("[{}]", x));
}
}
format!("{}{}({})", self.id, index, args).fmt(f)
}
}
pub type PositionalArgs = Vec<(Expression, Vec<VarPathSelect>, TokenRange)>;
pub type NamedArgs = Vec<(StrId, (Expression, Vec<VarPathSelect>, TokenRange))>;
pub type FunctionArgs = (CallArgs<Expression>, CallArgs<Vec<AssignDestination>>);
#[derive(Clone, Debug, Default)]
pub struct CallArgs<T>(Vec<(VarPath, T)>);
impl<T> CallArgs<T> {
pub fn values(&self) -> impl Iterator<Item = &T> {
self.0.iter().map(|(_, v)| v)
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.0.iter_mut().map(|(_, v)| v)
}
}
impl<T> std::ops::Deref for CallArgs<T> {
type Target = [(VarPath, T)];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> std::ops::DerefMut for CallArgs<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<'a, T> IntoIterator for &'a CallArgs<T> {
type Item = &'a (VarPath, T);
type IntoIter = std::slice::Iter<'a, (VarPath, T)>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
#[derive(Clone)]
pub enum Arguments {
Positional(PositionalArgs),
Named(NamedArgs),
Mixed(PositionalArgs, NamedArgs),
Null,
}
impl Arguments {
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
match self {
Arguments::Positional(x) => x.len(),
Arguments::Named(x) => x.len(),
Arguments::Mixed(x, y) => x.len() + y.len(),
Arguments::Null => 0,
}
}
pub fn to_system_function_args(
self,
_context: &mut Context,
symbol: &Symbol,
) -> Vec<(Expression, Vec<VarPathSelect>, TokenRange)> {
let ret = match self {
Arguments::Positional(x) => x,
Arguments::Named(_) => {
return vec![];
}
Arguments::Mixed(_, _) => vec![],
Arguments::Null => vec![],
};
if let SymbolKind::SystemFunction(x) = &symbol.kind {
let arity = x.ports.len();
if arity != ret.len() {
}
}
ret
}
pub fn to_function_args(
self,
context: &mut Context,
func: &FuncProto,
token: TokenRange,
) -> IrResult<FunctionArgs> {
let mut inputs: Vec<(VarPath, Expression)> = Vec::new();
let mut outputs: Vec<(VarPath, Vec<AssignDestination>)> = Vec::new();
if func.arity != self.len() {
context.insert_error(AnalyzerError::mismatch_function_arity(
&func.name.to_string(),
func.arity,
self.len(),
&token,
));
return Err(ir_error!(func.token));
}
let mut arg_map_by_name = HashMap::default();
let mut arg_map_by_index = HashMap::default();
for (i, arg) in func.args.iter().enumerate() {
arg_map_by_name.insert(arg.name, arg.clone());
arg_map_by_index.insert(i, arg.clone());
}
let mut connections = vec![];
match self {
Arguments::Positional(x) => {
for (i, (expr, dst, _)) in x.into_iter().enumerate() {
if let Some(arg) = arg_map_by_index.get(&i) {
connections.push((arg, expr, dst));
}
}
}
Arguments::Named(x) => {
for (name, (expr, dst, _)) in x {
if let Some(arg) = arg_map_by_name.get(&name) {
connections.push((arg, expr, dst));
}
}
}
Arguments::Mixed(_, _) => (),
Arguments::Null => (),
};
for (arg, mut expr, dst) in connections {
if arg.members.len() == 1
&& !matches!(
&arg.comptime.r#type.kind,
crate::ir::TypeKind::Modport(_, _)
)
{
let (path, _, direction) = &arg.members[0];
match direction {
Direction::Input => {
let arg_type = &arg.comptime.r#type;
if matches!(expr, Expression::ArrayLiteral(..)) {
eval_array_literal(
context,
Some(&arg_type.array),
Some(arg_type.width()),
&mut expr,
)?;
}
expr.eval_comptime(context, arg_type.total_width());
if arg_type.is_clock() || arg_type.is_reset() {
let expr_comptime = expr.eval_comptime(context, None);
let expr_token = expr_comptime.token;
check_implicit_clock_conversion(
context,
arg_type,
expr_comptime,
&expr_token,
);
}
inputs.push((path.clone(), expr));
}
Direction::Output => {
let dst = dst
.into_iter()
.filter_map(|x| x.to_assign_destination(context, false))
.collect();
outputs.push((path.clone(), dst));
}
Direction::Inout => {
expr.eval_comptime(context, arg.comptime.r#type.total_width());
inputs.push((path.clone(), expr));
let dst = dst
.into_iter()
.filter_map(|x| x.to_assign_destination(context, false))
.collect();
outputs.push((path.clone(), dst));
}
_ => (),
}
} else {
let expr_comptime = expr.eval_comptime(context, None);
let expr_token = expr_comptime.token;
check_compatibility(context, &arg.comptime.r#type, expr_comptime, &expr_token);
let actual = &dst[0];
for x in &arg.members {
let arg_path = x.0.clone();
let direction = x.2;
let mut expr_path = actual.0.clone();
expr_path.0.extend_from_slice(&arg_path.0[1..]);
let actual_member = VarPathSelect(expr_path, actual.1.clone(), expr_token);
match direction {
Direction::Input => {
let expr = actual_member.to_expression(context);
if let Some(expr) = expr {
inputs.push((arg_path, expr));
}
}
Direction::Output => {
let dst = actual_member.to_assign_destination(context, false);
if let Some(dst) = dst {
outputs.push((arg_path, vec![dst]));
}
}
Direction::Inout => {
if let Some(expr) = actual_member.clone().to_expression(context) {
inputs.push((arg_path.clone(), expr));
}
if let Some(dst) = actual_member.to_assign_destination(context, false) {
outputs.push((arg_path, vec![dst]));
}
}
_ => (),
}
}
}
}
Ok((CallArgs(inputs), CallArgs(outputs)))
}
}