Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions crates/celox-frontend-sv/src/lowering/comb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3446,6 +3446,31 @@ impl<'p, 'a> Comb<'p, 'a> {
.cloned()
.flatten()
.or_else(|| param.default.clone());
if param.direction.is_reference() && !param.leaves.is_empty() {
// Each member of a formal of unpacked structure type names
// the matching member variable of the actual.
let targets = match &actual {
Some(sv::ir::Expr::Concat(parts)) if parts.len() == param.leaves.len() => parts
.iter()
.map(|part| match part {
sv::ir::Expr::Ident(actual) => self.m.id(actual),
_ => None,
})
.collect::<Option<Vec<_>>>(),
_ => None,
}
.ok_or_else(|| {
unsupported(format!("ref argument `{}` of `{name}`", param.source_name))
})?;
for (leaf, target) in param.leaves.iter().zip(targets) {
let id = self
.m
.id(leaf)
.ok_or_else(|| unsupported(format!("argument `{}`", param.source_name)))?;
references.push((leaf.clone(), id, target));
}
continue;
}
if param.direction.is_reference() {
// A reference formal names the actual variable itself
// (IEEE 1800-2023 13.5.2); other actuals fall back to Ff.
Expand Down
167 changes: 149 additions & 18 deletions crates/celox-frontend-sv/src/lowering/ff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4628,12 +4628,12 @@ impl<'p, 'a> Ff<'p, 'a> {
let mut activation = Vec::new();
if self.kernel.is_some() && (subroutine.is_task || self.forked) {
for name in &subroutine.automatic_locals {
// A reference formal has no storage of its own.
if subroutine
.params
.iter()
.any(|param| &param.name == name && param.direction.is_reference())
{
// A reference formal, or a member of one, has no storage of
// its own.
if subroutine.params.iter().any(|param| {
param.direction.is_reference()
&& (&param.name == name || param.leaves.contains(name))
}) {
continue;
}
let original = self
Expand Down Expand Up @@ -4701,11 +4701,42 @@ impl<'p, 'a> Ff<'p, 'a> {
.cloned()
.flatten()
.or_else(|| param.default.clone());
match self.reference(name, param, actual)? {
Reference::Variable(target) => {
references.push((param.name.clone(), id, target));
if param.leaves.is_empty() {
match self.reference(name, param, actual)? {
Reference::Variable(target) => {
references.push((param.name.clone(), id, target));
}
Reference::Alias(alias) => aliases.push(*alias),
}
continue;
}
// Each member of a formal of unpacked structure type refers to
// the matching member of the actual, which the analyzer binds as
// the concatenation of its members.
let parts = match actual {
Some(sv::ir::Expr::Concat(parts)) if parts.len() == param.leaves.len() => parts,
_ => {
return Err(unsupported(format!(
"ref argument `{}` of `{name}`",
param.source_name
)));
}
};
for (leaf, part) in param.leaves.iter().zip(parts) {
let id = self
.m
.id(leaf)
.ok_or_else(|| unsupported(format!("argument `{}`", param.source_name)))?;
let member = sv::ir::SubroutineParam {
name: leaf.clone(),
leaves: Vec::new(),
default: None,
..param.clone()
};
match self.reference(name, &member, Some(part))? {
Reference::Variable(target) => references.push((leaf.clone(), id, target)),
Reference::Alias(alias) => aliases.push(*alias),
Comment thread
tignear marked this conversation as resolved.
Resolved
}
Reference::Alias(alias) => aliases.push(*alias),
}
}
// An input evaluates into a register, which a host request in a later
Expand Down Expand Up @@ -4748,8 +4779,12 @@ impl<'p, 'a> Ff<'p, 'a> {
.ok_or_else(|| unsupported("missing output argument"))?;
// A reference formal bound to a class property, forwarded:
// its object was evaluated when the reference was made.
if let Some(store) = forwarded_property(reference, &param.name) {
if let Some(store) =
forwarded_property(reference, sv::ir::Expr::Ident(param.name.clone()))
{
property_outputs.push(store);
} else if let Some(stores) = forwarded_members(reference, &param.name) {
property_outputs.extend(stores);
} else {
let lvalues = lvalue_from_expr(reference).ok_or_else(|| {
unsupported(format!(
Expand Down Expand Up @@ -4859,6 +4894,7 @@ impl<'p, 'a> Ff<'p, 'a> {
let constant = |position: &sv::ir::ConstExpr| {
sv::typecheck::eval_const_expr_with_types(position, constants, parameter_types)
};
self.split_property_concats(&mut body, &aliases);
for alias in &aliases {
body = alias.rewrite(body, &constant)?;
Comment thread
tignear marked this conversation as resolved.
Resolved
}
Expand Down Expand Up @@ -4902,6 +4938,67 @@ impl<'p, 'a> Ff<'p, 'a> {
Ok(result)
}

/// Rewrite each assignment in `body` to a concatenation that has members
/// of a structure reference formal bound to a class property, which
/// [`references::Alias::rewrite`] stores one property at a time: the
/// concatenation writes temporaries in their place, so every position
/// of its other parts is taken before any write, and each property is
/// then stored from its temporary.
fn split_property_concats(&mut self, body: &mut [sv::ir::Stmt], aliases: &[references::Alias]) {
let properties: HashSet<&str> = aliases
.iter()
.filter(|alias| matches!(alias.target, references::Target::Property { .. }))
.map(|alias| alias.formal.as_str())
.collect();
if properties.is_empty() {
return;
}
for stmt in body {
stmt.walk_mut(&mut |stmt| {
let sv::ir::Stmt::AssignConcat {
parts,
nonblocking,
..
} = stmt
else {
return;
};
if !parts.iter().any(|part| properties.contains(part.name())) {
return;
}
let nonblocking = *nonblocking;
let mut stores = Vec::new();
for part in parts.iter_mut() {
if !matches!(part, sv::ir::LValue::Ident(name) if properties.contains(name.as_str()))
{
continue;
}
// A select of a member is left to the rewrite to reject.
let Ok(width) = self.lvalue_width(part) else {
continue;
};
let (_, temp) = self.m.temp("ref_member", width, false, self.m.four_state);
let member = std::mem::replace(part, sv::ir::LValue::Ident(temp.clone()));
stores.push(sv::ir::Stmt::Assign {
lhs: member,
rhs: sv::ir::Expr::Ident(temp),
nonblocking,
});
}
if stores.is_empty() {
return;
}
let assignment = std::mem::replace(stmt, sv::ir::Stmt::Break);
stores.insert(0, assignment);
*stmt = sv::ir::Stmt::If {
condition: sv::ir::Expr::Literal("1'b1".into()),
then_body: stores,
else_body: Vec::new(),
};
});
}
}

/// What reference formal `param` of `name` refers to for a call with
/// `actual`. The object of a property is evaluated here, at the call.
fn reference(
Expand Down Expand Up @@ -8380,10 +8477,10 @@ enum Reference {
Alias(Box<references::Alias>),
}

/// The store that copies formal `formal` back into the class property that
/// `actual` reads through the object of a reference formal (see
/// The store that copies `value` back into the class property that `actual`
/// reads through the object of a reference formal (see
/// [`references::Alias`]), when it is such a read.
fn forwarded_property(actual: &sv::ir::Expr, formal: &str) -> Option<sv::ir::Stmt> {
fn forwarded_property(actual: &sv::ir::Expr, value: sv::ir::Expr) -> Option<sv::ir::Stmt> {
let sv::ir::Expr::Call {
callee: sv::ir::Callee::Object(op),
args,
Expand All @@ -8400,11 +8497,45 @@ fn forwarded_property(actual: &sv::ir::Expr, formal: &str) -> Option<sv::ir::Stm
handle.starts_with(references::OBJECT_TEMP).then(|| {
sv::ir::Stmt::Eval(sv::ir::Expr::Call {
callee: sv::ir::Callee::Object(Box::new(sv::ir::ObjectOp::Store(*field))),
args: vec![
Some(sv::ir::Expr::Ident(handle.clone())),
Some(sv::ir::Expr::Ident(formal.to_owned())),
],
args: vec![Some(sv::ir::Expr::Ident(handle.clone())), Some(value)],
arg_order: Vec::new(),
})
})
}

/// The stores that copy the members of formal `formal` of unpacked
/// structure type back into the class properties that `actual`, the members
/// of a structure reference formal bound to a property, read.
fn forwarded_members(actual: &sv::ir::Expr, formal: &str) -> Option<Vec<sv::ir::Stmt>> {
let sv::ir::Expr::Concat(parts) = actual else {
return None;
};
let widths = parts
.iter()
.map(|part| match part {
sv::ir::Expr::Call {
callee: sv::ir::Callee::Object(op),
..
} => match **op {
sv::ir::ObjectOp::Load(field) => Some(field.width as usize),
_ => None,
},
_ => None,
})
.collect::<Option<Vec<_>>>()?;
let mut lsb = widths.iter().sum::<usize>();
parts
.iter()
.zip(widths)
.map(|(part, width)| {
lsb -= width;
let bits = sv::ir::Expr::Select {
expr: Box::new(sv::ir::Expr::Ident(formal.to_owned())),
msb: sv::ir::ConstExpr::Literal((lsb + width - 1).to_string()),
lsb: sv::ir::ConstExpr::Literal(lsb.to_string()),
signed: false,
};
forwarded_property(part, bits)
})
.collect()
}
1 change: 1 addition & 0 deletions crates/celox-sv-analyzer/src/ast/const_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ impl ConstantFunctions {
direction: crate::procedural::ParamDirection::Input,
r#type: ty.clone(),
default: function.argument_defaults[position].clone().map(Into::into),
leaves: Vec::new(),
})
.collect(),
body: function
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1594,7 +1594,7 @@ fn read_index_value(
Ok(value)
}

fn path(
pub(super) fn path(
mut root: ExprId,
native: &Declarations,
tree: &AnalysisContext<'_>,
Expand Down
52 changes: 38 additions & 14 deletions crates/celox-sv-analyzer/src/ast/expression_binding/references.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,20 @@ pub(super) fn check_actual(
"a constant is not a variable (IEEE 1800-2023 13.5.2)",
));
}
None => objects::property(tree, actual, native, dimensions)?
.map(|(property, _)| procedural::dimensions_from_type(&property.r#type)),
None => property_shape(tree, actual, native, dimensions)?,
}
}
ExprKind::Member { .. } => match objects::property(tree, actual, native, dimensions)? {
Some((property, _)) => Some(procedural::dimensions_from_type(&property.r#type)),
ExprKind::Member { .. } => match property_shape(tree, actual, native, dimensions)? {
Some(shape) => Some(shape),
// A member of a variable of unpacked structure type is a
// variable of its own (see `unpacked_structs`).
None if let (parts, None) = direct::path(actual, native, tree, dimensions)?
&& let [variable] = parts.as_slice()
&& let Some(shape) = dimensions.get(variable)
&& !dimensions.const_env.contains_key(variable) =>
{
Some(shape.clone())
}
// A static property is a variable of its class's scope.
None if let Some(variable) =
objects::static_property(tree, actual, native, dimensions)? =>
Expand Down Expand Up @@ -134,15 +142,6 @@ pub(super) fn check_actual(
.subroutine_param_shapes
.get(name)
.and_then(|shapes| shapes.get(position));
// The members of a structure are separate variables, which one reference
// cannot alias.
if formal.is_some_and(|formal| formal.unpacked_struct.is_some())
|| shape.unpacked_struct.is_some()
{
return Err(unsupported(format!(
"ref argument of `{name}` of unpacked structure type"
)));
}
// Bounded and unbounded queues are equivalent (6.22.2), but a formal's
// operations would apply its own bound to the actual.
if let (Some(formal), Some(actual)) = (
Expand All @@ -167,9 +166,29 @@ pub(super) fn check_actual(
Ok(())
}

/// The shape of class property `actual`, also of unpacked structure type.
fn property_shape(
tree: &AnalysisContext<'_>,
actual: ExprId,
native: &Declarations,
dimensions: &PackedDimensions,
) -> Converted<Option<VariableDimensions>> {
if let Some((property, _)) = objects::property(tree, actual, native, dimensions)? {
return Ok(Some(procedural::dimensions_from_type(&property.r#type)));
}
// `objects::aggregate_load` rejects a receiver that calls a subroutine,
// which each member would evaluate again.
let Some((aggregate, _, _)) = objects::aggregate_property(tree, actual, native, dimensions)?
else {
return Ok(None);
};
Ok(Some(procedural::dimensions_from_type(&aggregate.r#type)))
}

/// Whether variables of shapes `a` and `b` have equivalent types (IEEE
/// 1800-2023 6.22.2). Integral types compare by total width, signedness and
/// state count; an enumeration only by its members.
/// state count; an enumeration only by its members; an unpacked structure
/// only with the same structure type.
fn equivalent(
state: &AnalysisState,
a: &VariableDimensions,
Expand Down Expand Up @@ -219,6 +238,11 @@ fn equivalent(
if a.real != b.real || a.string != b.string || a.class != b.class {
return false;
}
match (&a.unpacked_struct, &b.unpacked_struct) {
(None, None) => {}
(Some(a), Some(b)) if a.same_type(b) => {}
_ => return false,
}
if (a.enum_domain.is_some() || b.enum_domain.is_some()) && a.enum_domain != b.enum_domain {
return false;
}
Expand Down
Loading
Loading