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
4 changes: 2 additions & 2 deletions conformance/sv-tests/expected.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -1261,8 +1261,8 @@ chapter-7/arrays/associative/arguments.sv 2state fail
chapter-7/arrays/associative/arguments.sv 4state fail
chapter-7/arrays/associative/assignment.sv 2state pass
chapter-7/arrays/associative/assignment.sv 4state pass
chapter-7/arrays/associative/class.sv 2state fail
chapter-7/arrays/associative/class.sv 4state fail
chapter-7/arrays/associative/class.sv 2state pass
chapter-7/arrays/associative/class.sv 4state pass
chapter-7/arrays/associative/integral.sv 2state pass
chapter-7/arrays/associative/integral.sv 4state pass
chapter-7/arrays/associative/literals.sv 2state fail
Expand Down
115 changes: 103 additions & 12 deletions crates/celox-frontend-sv/src/lowering/ff/containers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ impl Ff<'_, '_> {
.ok_or_else(|| unsupported("omitted container operand"))
})
.collect::<Result<Vec<_>, _>>()?;
// An update of an element of an outer container reads that element
// and writes it back: evaluate the indices on the way once.
if op.mutates()
&& let Some(receiver) = args.first_mut()
{
*receiver = self.freeze_receiver(receiver)?;
}
let original_key_width = args
.get(1)
.and_then(|key| self.m.comparison_width(key, key));
Expand All @@ -41,9 +48,11 @@ impl Ff<'_, '_> {
)?;
}
}
// An element of an outer container is written back through its
// read, whose indices `freeze_receiver` has evaluated.
if !value_receiver
&& self.m.calls(&args[0])
&& !matches!(&args[0], sv::ir::Expr::Call { callee: sv::ir::Callee::Object(op), .. } if matches!(**op, sv::ir::ObjectOp::Load(_)))
&& !matches!(&args[0], sv::ir::Expr::Call { callee: sv::ir::Callee::Object(op), .. } if matches!(**op, sv::ir::ObjectOp::Load(_) | sv::ir::ObjectOp::Container { op: Get, .. }))
Comment thread
tignear marked this conversation as resolved.
Resolved
{
args[0] = self.heap_operand(
&args[0],
Expand Down Expand Up @@ -85,10 +94,15 @@ impl Ff<'_, '_> {
*arg = self.heap_operand(arg, context, kind)?;
}
}
let (kind, capacity, signed_key, string_key) = match layout.kind {
ContainerKind::Dynamic => (0, 0, false, false),
ContainerKind::Queue { capacity } => (1, capacity, false, false),
ContainerKind::Associative { signed, string, .. } => (2, 0, signed, string),
let (kind, capacity, signed_key, string_key, object_key) = match layout.kind {
ContainerKind::Dynamic => (0, 0, false, false, false),
ContainerKind::Queue { capacity } => (1, capacity, false, false, false),
ContainerKind::Associative {
signed,
string,
object,
..
} => (2, 0, signed, string, object),
};
let parameter = heap::parameter(
layout.width,
Expand All @@ -102,6 +116,7 @@ impl Ff<'_, '_> {
capacity,
signed_key,
string_key,
object_key,
);
let code = |opcode| encode(opcode, parameter);
if op.reduction().is_some() {
Expand Down Expand Up @@ -381,13 +396,7 @@ impl Ff<'_, '_> {
let narrow = this.b.alloc_bit(32, false);
this.b.emit(SIRInstruction::Slice(narrow, updated, 0, 32));
this.store(id, SIROffset::Static(0), 32, narrow);
let sv::ir::Expr::Ident(receiver) = receiver else {
return Err(unsupported("container receiver must be a variable"));
};
this.assign(
&sv::ir::LValue::Ident(receiver),
&sv::ir::Expr::Ident(updated_name),
)
this.container_write_back(&receiver, sv::ir::Expr::Ident(updated_name))
})?;
}
zero
Expand All @@ -402,6 +411,88 @@ impl Ff<'_, '_> {
Ok(sv::ir::Expr::Ident(name))
}

/// `receiver` with the indices of the outer containers it reads, and the
/// objects of their properties, evaluated into temporaries.
fn freeze_receiver(
&mut self,
receiver: &sv::ir::Expr,
) -> Result<sv::ir::Expr, sv::AnalyzerError> {
let sv::ir::Expr::Call {
callee: sv::ir::Callee::Object(op),
args,
arg_order,
} = receiver
else {
return Ok(receiver.clone());
};
let frozen = match **op {
sv::ir::ObjectOp::Container {
op: ContainerOp::Get,
..
} => {
let [Some(outer), Some(index)] = args.as_slice() else {
return Ok(receiver.clone());
};
let outer = self.freeze_receiver(outer)?;
let index = self.hoist(index)?;
vec![Some(outer), Some(index)]
}
sv::ir::ObjectOp::Load(_) => {
let [Some(owner)] = args.as_slice() else {
return Ok(receiver.clone());
};
vec![Some(self.hoist(owner)?)]
}
_ => return Ok(receiver.clone()),
};
Ok(sv::ir::Expr::Call {
callee: sv::ir::Callee::Object(op.clone()),
args: frozen,
arg_order: arg_order.clone(),
})
}

/// Store the updated container `updated` where `receiver` read it: a
/// variable, or an element of an outer container (`m[a]` of `m[a][b]`),
/// which is set in turn (IEEE 1800-2023 7.4.5).
fn container_write_back(
&mut self,
receiver: &sv::ir::Expr,
updated: sv::ir::Expr,
) -> Result<(), sv::AnalyzerError> {
match receiver {
sv::ir::Expr::Ident(name) => {
self.assign(&sv::ir::LValue::Ident(name.clone()), &updated)
}
sv::ir::Expr::Call {
callee: sv::ir::Callee::Object(op),
args,
..
} if matches!(
**op,
sv::ir::ObjectOp::Container {
op: ContainerOp::Get,
..
}
) =>
{
let sv::ir::ObjectOp::Container { layout, .. } = **op else {
unreachable!("matched a container read")
};
let [Some(outer), Some(index)] = args.as_slice() else {
return Err(unsupported("container element receiver"));
};
self.container_call(
ContainerOp::Set,
layout,
&[Some(outer.clone()), Some(index.clone()), Some(updated)],
)?;
Comment thread
tignear marked this conversation as resolved.
Resolved
Ok(())
}
_ => Err(unsupported("container receiver must be a variable")),
}
}

fn container_reduction(
&mut self,
op: ContainerOp,
Expand Down
36 changes: 27 additions & 9 deletions crates/celox-heap/src/containers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ pub const RSORT: u64 = 99;
/// The operation parameter encodes the element width (bits 0..6), whether
/// it is two-state (7), its handle kind (8..9: none/string/object), container
/// kind (10..11: dynamic/queue/associative), queue capacity (12..43, 0 means
/// unbounded), and associative key ordering (44 signed, 45 string).
/// unbounded), associative key ordering (44 signed, 45 string), and whether
/// associative keys are object handles that the collector traces (46).
#[allow(clippy::too_many_arguments)]
pub const fn parameter(
width: u32,
two_state: bool,
Expand All @@ -44,6 +46,7 @@ pub const fn parameter(
capacity: u32,
signed_key: bool,
string_key: bool,
object_key: bool,
) -> u64 {
width as u64
| (two_state as u64) << 7
Expand All @@ -52,6 +55,7 @@ pub const fn parameter(
| (capacity as u64) << 12
| (signed_key as u64) << 44
| (string_key as u64) << 45
| (object_key as u64) << 46
}

#[derive(Clone, Copy)]
Expand Down Expand Up @@ -171,19 +175,33 @@ impl Heap {
let Some(container) = self.containers.get(&handle) else {
return Vec::new();
};
let mut roots = Vec::new();
// Object keys of an associative array keep their objects alive.
if container.layout & (1 << 46) != 0
&& let Contents::Associative(v) = &container.contents
{
roots.extend(v.keys().filter_map(|key| match key {
Key::Integer(handle) => Some(Root {
kind: crate::HandleKind::Object,
handle: *handle as Handle,
}),
Key::String(_) => None,
}));
}
let kind = match (container.layout >> 8) & 3 {
1 => crate::HandleKind::String,
2 => crate::HandleKind::Object,
_ => return Vec::new(),
_ => return roots,
};
let root = |element: &Element| Root {
kind,
handle: element.value as Handle,
};
match &container.contents {
Contents::Sequence(v) => v.iter().map(root).collect(),
Contents::Associative(v) => v.values().map(root).collect(),
Contents::Sequence(v) => roots.extend(v.iter().map(root)),
Contents::Associative(v) => roots.extend(v.values().map(root)),
}
roots
}
/// Execute a container helper. Indices have already undergone language
/// conversion; invalid four-state indices use an out-of-range sentinel.
Expand Down Expand Up @@ -435,7 +453,7 @@ mod tests {
#[test]
fn completed_updates_intern_values_and_collection_releases_lookup_entries() {
let mut heap = Heap::new();
let p = parameter(8, true, 0, 0, 0, false, false);
let p = parameter(8, true, 0, 0, 0, false, false, false);
let a = call(&mut heap, NEW, p, 2, 0, 0);
let b = call(&mut heap, SET, p, a, 0, 7);
let b = call(&mut heap, SET_MASK, p, b, 0, 0);
Expand All @@ -454,7 +472,7 @@ mod tests {
#[test]
fn resize_copy_delete_and_four_state_defaults() {
let mut heap = Heap::new();
let p = parameter(32, false, 0, 0, 0, false, false);
let p = parameter(32, false, 0, 0, 0, false, false, false);
let a = call(&mut heap, NEW, p, 2, 0, 0);
assert_eq!(call(&mut heap, GET_MASK, p, a, 0, 0), u32::MAX as u64);
let b = call(&mut heap, SET, p, a, 0, 42);
Expand All @@ -470,7 +488,7 @@ mod tests {
#[test]
fn bounded_queue_insert_and_value_copies() {
let mut heap = Heap::new();
let p = parameter(8, true, 0, 1, 2, false, false);
let p = parameter(8, true, 0, 1, 2, false, false, false);
let a = call(&mut heap, INSERT, p, 0, 0, 10);
let b = call(&mut heap, INSERT, p, a, 0, 20);
let c = call(&mut heap, INSERT, p, b, 0, 30);
Expand All @@ -484,7 +502,7 @@ mod tests {
#[test]
fn ordering_interns_unchanged_values_and_retains_nested_string_roots() {
let mut heap = Heap::new();
let p = parameter(32, true, 1, 1, 0, false, false);
let p = parameter(32, true, 1, 1, 0, false, false, false);
let z = heap.intern(b"z") as u64;
let a = heap.intern(b"a") as u64;
let first = call(&mut heap, INSERT, p, 0, 0, z);
Expand All @@ -509,7 +527,7 @@ mod tests {
#[test]
fn associative_order_and_gc_follow_element_handles() {
let mut heap = Heap::new();
let p = parameter(32, true, 1, 2, 0, true, false);
let p = parameter(32, true, 1, 2, 0, true, false, false);
let string = heap.intern(b"live");
let a = call(&mut heap, SET, p, 0, 3, string as u64);
let b = call(&mut heap, SET, p, a, (-2i64) as u64, string as u64);
Expand Down
Loading
Loading