use crate::HashMap;
pub use celox_design::PortTypeKind;
pub(crate) use celox_design::{
AbsoluteAddrBase, BitAccess, InstanceId, ModuleId, RegionedAbsoluteAddrBase,
RegionedVarAddrBase, RuntimeSchema, SPARSE_WORKING_REGION, STABLE_REGION, VarAtomBase,
WORKING_REGION,
};
#[cfg(test)]
pub(crate) use celox_design::{BinaryOp, UnaryOp};
#[cfg(feature = "host-runtime")]
pub(crate) use celox_design::{
InitialStateData, InitialStateWriteRun, RuntimeEventKind, RuntimeEventSite,
};
pub use celox_frontend_core::shared::{
FrontendLookup, InstancePath, SourceAddr, SourceVarId, VariableInfo, VariableKind,
};
#[cfg(all(
feature = "host-runtime",
any(
target_arch = "x86_64",
feature = "arm64-codegen",
all(target_arch = "aarch64", feature = "experimental-arm64-backend")
)
))]
use celox_runtime::{
DesignReflection, ReflectionScope, ReflectionScopeId, ReflectionSignal, ReflectionSignalId,
SignalDirection,
};
#[cfg(test)]
pub(crate) use celox_sir::{BasicBlock, SIRValue, inline_single_predecessor_jumps};
pub(crate) use celox_sir::{
BlockId, ExecutionUnit, RegisterId, RegisterType, SIRInstruction, SIROffset, SIRTerminator,
collect_exact_zero_registers,
};
use celox_testbench::TestbenchProgram;
use std::{fmt, ops::Deref};
pub type AbsoluteAddr = celox_design::StateAddr;
pub type RegionedAbsoluteAddr = celox_design::RegionedStateAddr;
pub type SirProgram = celox_sir::SirProgram<AbsoluteAddr, RegionedAbsoluteAddr>;
#[derive(Debug, Clone, thiserror::Error)]
pub enum AddrLookupError {
#[error("Instance not found: {path}")]
InstanceNotFound { path: String },
#[error("Variable not found: {path}")]
VariableNotFound { path: String },
#[error("Ambiguous variable path: {path} — multiple variables share this path")]
AmbiguousPath { path: String },
}
#[derive(Debug, Clone, thiserror::Error)]
pub(crate) enum DesignProjectionError {
#[error("state object count differs: design={design} frontend={frontend}")]
StateObjectCount { design: usize, frontend: usize },
#[error("missing state projection for {source_address}")]
MissingStateProjection { source_address: SourceAddr },
#[error("missing flattened state object {address}")]
MissingStateObject { address: AbsoluteAddr },
#[error("metadata differs for flattened state object {address}")]
MetadataMismatch { address: AbsoluteAddr },
}
#[cfg(feature = "host-runtime")]
pub type InitialMemoryWriteRun = InitialStateWriteRun;
#[cfg(feature = "host-runtime")]
pub type InitialMemoryData = InitialStateData;
pub type RuntimeErrorInfo<Addr = AbsoluteAddr> = celox_design::RuntimeErrorInfo<Addr>;
#[derive(Clone)]
pub struct RuntimeProgram {
pub design: celox_design::ElaboratedDesign<AbsoluteAddr>,
pub frontend: FrontendLookup,
pub runtime_schema: RuntimeSchema<AbsoluteAddr>,
pub testbench: Option<TestbenchProgram<AbsoluteAddr>>,
}
#[derive(Clone, Debug)]
pub struct UnoptimizedSir {
pub sir: SirProgram,
pub layout_requirements: celox_state_layout::LayoutRequirements<AbsoluteAddr>,
pub runtime: RuntimeProgram,
}
impl UnoptimizedSir {
pub(crate) fn new(sir: SirProgram, runtime: RuntimeProgram) -> Self {
Self {
sir,
layout_requirements: Default::default(),
runtime,
}
}
pub(crate) fn into_optimized(self) -> OptimizedSir {
OptimizedSir::new(self.sir, self.runtime, self.layout_requirements)
}
}
impl Deref for UnoptimizedSir {
type Target = RuntimeProgram;
fn deref(&self) -> &Self::Target {
&self.runtime
}
}
#[derive(Clone, Debug)]
pub struct OptimizedSir {
pub sir: SirProgram,
pub layout_requirements: celox_state_layout::LayoutRequirements<AbsoluteAddr>,
pub(crate) runtime: RuntimeProgram,
}
impl OptimizedSir {
pub(crate) fn new(
sir: SirProgram,
runtime: RuntimeProgram,
layout_requirements: celox_state_layout::LayoutRequirements<AbsoluteAddr>,
) -> Self {
Self {
sir,
layout_requirements,
runtime,
}
}
#[cfg(all(
feature = "host-runtime",
any(
target_arch = "x86_64",
feature = "arm64-codegen",
all(target_arch = "aarch64", feature = "experimental-arm64-backend")
)
))]
pub(crate) fn into_runtime(self) -> RuntimeProgram {
self.runtime
}
}
impl Deref for OptimizedSir {
type Target = RuntimeProgram;
fn deref(&self) -> &Self::Target {
&self.runtime
}
}
#[derive(Clone, Debug)]
pub struct LaidOutProgram {
pub sir: SirProgram,
pub(crate) runtime: RuntimeProgram,
layout: crate::backend::MemoryLayout,
}
impl LaidOutProgram {
pub fn layout(&self) -> &crate::backend::MemoryLayout {
&self.layout
}
pub fn runtime(&self) -> &RuntimeProgram {
&self.runtime
}
pub fn into_runtime(self) -> RuntimeProgram {
self.runtime
}
}
impl Deref for LaidOutProgram {
type Target = RuntimeProgram;
fn deref(&self) -> &Self::Target {
&self.runtime
}
}
impl fmt::Debug for RuntimeProgram {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RuntimeProgram")
.field("num_events", &self.design.events.len())
.finish_non_exhaustive()
}
}
impl OptimizedSir {
pub fn into_laid_out(self, four_state: bool) -> LaidOutProgram {
self.into_laid_out_with_mode(
four_state,
crate::backend::memory_layout::MemoryLayoutMode::Packed,
)
}
pub fn into_laid_out_with_mode(
self,
four_state: bool,
mode: crate::backend::memory_layout::MemoryLayoutMode,
) -> LaidOutProgram {
let mut program = self;
if !program.runtime_schema.comb_observers.is_empty()
&& !program.layout_requirements.is_empty()
{
let observed_written: crate::HashSet<AbsoluteAddr> = program
.runtime_schema
.comb_observers
.iter()
.flat_map(|observer| observer.written_inputs.iter().copied())
.collect();
program
.layout_requirements
.state_aliases_mut()
.retain(|alias_addr, _| !observed_written.contains(alias_addr));
program
.layout_requirements
.state_aliases_mut()
.retain(|alias_addr, _| {
!comb_capture_enable_needs_unaliased_old_value(
&program.sir.eval_comb,
*alias_addr,
)
});
}
crate::optimizer::sir::retain_final_identity_aliases(&mut program, four_state);
let layout = crate::backend::MemoryLayout::build(&program, four_state, mode);
if !program.layout_requirements.is_empty() {
let aliased: crate::HashMap<AbsoluteAddr, AbsoluteAddr> = program
.layout_requirements
.state_aliases()
.iter()
.filter(|(alias_addr, canonical_addr)| {
layout
.offsets
.get(alias_addr)
.zip(layout.offsets.get(canonical_addr))
.is_some_and(|(a, c)| a == c)
})
.map(|(&alias, &canonical)| (alias, canonical))
.collect();
if !aliased.is_empty() {
crate::optimizer::sir::remove_final_identity_alias_stores(
&mut program,
&aliased,
four_state,
);
}
}
rebuild_rtl_writes(&mut program);
program.layout_requirements.clear();
let OptimizedSir {
sir,
runtime,
layout_requirements,
} = program;
debug_assert!(layout_requirements.is_empty());
LaidOutProgram {
sir,
runtime,
layout,
}
}
}
fn rebuild_rtl_writes(program: &mut OptimizedSir) {
let mut rtl_writes = crate::HashSet::default();
for unit in program
.sir
.eval_comb
.iter()
.chain(program.sir.eval_apply_ffs.values().flatten())
.chain(program.sir.eval_comb_apply_ffs.values().flatten())
.chain(program.sir.eval_only_ffs.values().flatten())
.chain(program.sir.apply_ffs.values().flatten())
{
for block in unit.blocks.values() {
for instruction in &block.instructions {
let (address, offset, width) = match instruction {
SIRInstruction::Store(address, offset, width, ..)
| SIRInstruction::Commit(_, address, offset, width, _) => {
(address.absolute_addr(), offset, *width)
}
_ => continue,
};
let access = offset
.constant_bit_offset()
.and_then(|lsb| {
width
.checked_sub(1)
.and_then(|tail| lsb.checked_add(tail))
.map(|msb| BitAccess::new(lsb, msb))
})
.or_else(|| {
program
.runtime
.design
.state_objects
.get(&address)
.and_then(|object| object.width.checked_sub(1))
.map(|msb| BitAccess::new(0, msb))
});
if let Some(access) = access {
rtl_writes.insert(VarAtomBase {
id: address,
access,
});
}
}
}
}
program.runtime.runtime_schema.rtl_writes = rtl_writes;
}
impl RuntimeProgram {
#[cfg(all(
feature = "host-runtime",
any(
target_arch = "x86_64",
feature = "arm64-codegen",
all(target_arch = "aarch64", feature = "experimental-arm64-backend")
)
))]
pub(crate) fn build_design_reflection(
&self,
layout: &crate::backend::MemoryLayout,
) -> DesignReflection {
struct ScopeSource {
instance_id: InstanceId,
name: String,
full_name: String,
parent_name: Option<String>,
module_name: String,
}
let root_instance = self
.frontend
.instance_ids
.get(&InstancePath(Vec::new()))
.expect("top-level instance exists");
let root_module = self.frontend.instance_module[root_instance];
let root_name = self
.frontend
.module_names
.get(&root_module)
.cloned()
.unwrap_or_else(|| root_module.to_string());
let mut scope_sources = self
.frontend
.instance_ids
.iter()
.map(|(path, &instance_id)| {
let module_id = self.frontend.instance_module[&instance_id];
let module_name = self
.frontend
.module_names
.get(&module_id)
.cloned()
.unwrap_or_else(|| module_id.to_string());
let segments = self.frontend.instance_path_segments(path);
let name = segments
.last()
.cloned()
.unwrap_or_else(|| root_name.clone());
let full_name = if segments.is_empty() {
root_name.clone()
} else {
format!("{root_name}.{}", segments.join("."))
};
let parent_name = (!segments.is_empty()).then(|| {
if segments.len() == 1 {
root_name.clone()
} else {
format!("{root_name}.{}", segments[..segments.len() - 1].join("."))
}
});
ScopeSource {
instance_id,
name,
full_name,
parent_name,
module_name,
}
})
.collect::<Vec<_>>();
scope_sources.sort_by(|left, right| left.full_name.cmp(&right.full_name));
let scope_ids = scope_sources
.iter()
.enumerate()
.map(|(index, scope)| {
(
scope.full_name.clone(),
ReflectionScopeId(u32::try_from(index).expect("scope count exceeds u32")),
)
})
.collect::<HashMap<_, _>>();
let instance_scopes = scope_sources
.iter()
.enumerate()
.map(|(index, scope)| {
(
scope.instance_id,
ReflectionScopeId(u32::try_from(index).expect("scope count exceeds u32")),
)
})
.collect::<HashMap<_, _>>();
let mut scopes = scope_sources
.iter()
.map(|scope| ReflectionScope {
name: scope.name.clone(),
full_name: scope.full_name.clone(),
module_name: scope.module_name.clone(),
parent: scope.parent_name.as_ref().map(|parent| scope_ids[parent]),
children: Vec::new(),
signals: Vec::new(),
})
.collect::<Vec<_>>();
let child_parents = scopes
.iter()
.enumerate()
.filter_map(|(index, scope)| {
scope.parent.map(|parent| {
(
parent,
ReflectionScopeId(u32::try_from(index).expect("scope count exceeds u32")),
)
})
})
.collect::<Vec<_>>();
for (parent, child) in child_parents {
scopes[parent.0 as usize].children.push(child);
}
let mut signals = Vec::new();
for scope in &scope_sources {
let module_id = self.frontend.instance_module[&scope.instance_id];
let variables = &self.frontend.module_variables[&module_id];
let path_index = &self.frontend.module_var_path_index[&module_id];
for info in variables.values() {
if matches!(
info.var_kind,
VariableKind::Parameter | VariableKind::Constant
) {
continue;
}
if path_index.get(&info.path) != Some(&Some(info.id)) {
continue;
}
let name = info.path.join(".");
let state_address = self
.state_address_for_source(scope.instance_id, info.id)
.expect("frontend state projection is complete");
let array_layout =
layout
.unpacked_arrays
.get(&state_address)
.map(|array| SignalArrayLayout {
element_width: array.element_width,
element_count: array.element_count,
element_stride: array.element_stride,
plane_size: array.plane_size,
});
let direction = match info.var_kind {
VariableKind::Input => SignalDirection::Input,
VariableKind::Output => SignalDirection::Output,
VariableKind::Inout => SignalDirection::Inout,
_ => SignalDirection::Internal,
};
signals.push(ReflectionSignal {
full_name: format!("{}.{}", scope.full_name, name),
name,
parent: instance_scopes[&scope.instance_id],
state_address,
signal: SignalRef {
offset: layout.offsets[&state_address],
width: layout.widths[&state_address],
is_4state: layout.is_4states[&state_address],
array_layout,
},
direction,
domain_kind: info.kind,
signed: info.signed,
packed_dims: info.packed_dims.clone(),
unpacked_dims: info.array_dims.clone(),
type_kind: info.type_kind,
});
}
}
signals.sort_by(|left, right| left.full_name.cmp(&right.full_name));
for (index, signal) in signals.iter().enumerate() {
scopes[signal.parent.0 as usize]
.signals
.push(ReflectionSignalId(
u32::try_from(index).expect("signal count exceeds u32"),
));
}
let reflection = DesignReflection::new(scopes, signals);
debug_assert!(reflection.validate().is_ok());
reflection
}
pub(crate) fn state_address_for_source(
&self,
instance_id: InstanceId,
var_id: SourceVarId,
) -> Option<AbsoluteAddr> {
self.frontend.state_address(&SourceAddr {
instance_id,
var_id,
})
}
pub(crate) fn from_scheduled(
scheduled: celox_frontend_core::ScheduledRtl,
) -> (SirProgram, Self) {
(
scheduled.sir,
Self {
design: scheduled.design,
frontend: scheduled.frontend_lookup,
runtime_schema: scheduled.runtime_schema,
testbench: None,
},
)
}
pub fn get_addr(
&self,
instance_path: &[(&str, usize)],
var_path: &[&str],
) -> Result<AbsoluteAddr, AddrLookupError> {
let instance_path: Vec<(String, usize)> = instance_path
.iter()
.map(|(name, index)| ((*name).to_string(), *index))
.collect();
let instance_id = *self
.frontend
.instance_ids
.get(&InstancePath(instance_path.clone()))
.ok_or_else(|| AddrLookupError::InstanceNotFound {
path: instance_path
.iter()
.map(|(s, i)| format!("{}[{}]", s, i))
.collect::<Vec<_>>()
.join("."),
})?;
let module_id = self.frontend.instance_module[&instance_id];
let target_path = var_path
.iter()
.map(|segment| (*segment).to_string())
.collect::<Vec<_>>();
let path_str = var_path.join(".");
let entry = self.frontend.module_var_path_index[&module_id]
.get(&target_path)
.ok_or_else(|| AddrLookupError::VariableNotFound {
path: path_str.clone(),
})?;
let var_id = entry.ok_or_else(|| AddrLookupError::AmbiguousPath { path: path_str })?;
let source_addr = SourceAddr {
instance_id,
var_id,
};
self.frontend
.state_address(&source_addr)
.ok_or_else(|| AddrLookupError::VariableNotFound {
path: var_path.join("."),
})
}
pub fn get_path(&self, addr: &AbsoluteAddr) -> String {
self.frontend.get_state_path(addr)
}
pub fn get_variable_info(&self, addr: &AbsoluteAddr) -> Option<&VariableInfo> {
let source = self.frontend.source_address(addr)?;
let module_id = self.frontend.instance_module.get(&source.instance_id)?;
let module_vars = self.frontend.module_variables.get(module_id)?;
module_vars.get(&source.var_id)
}
pub fn num_events(&self) -> usize {
self.design.events.len()
}
pub(crate) fn verify_design_projection(&self) -> Result<(), DesignProjectionError> {
let expected_count = self
.frontend
.instance_module
.values()
.map(|module_id| self.frontend.module_variables[module_id].len())
.sum::<usize>();
if self.design.state_objects.len() != expected_count {
return Err(DesignProjectionError::StateObjectCount {
design: self.design.state_objects.len(),
frontend: expected_count,
});
}
for (&instance_id, module_id) in &self.frontend.instance_module {
for info in self.frontend.module_variables[module_id].values() {
let source_address = SourceAddr {
instance_id,
var_id: info.id,
};
let Some(address) = self.frontend.state_address(&source_address) else {
return Err(DesignProjectionError::MissingStateProjection { source_address });
};
let Some(metadata) = self.design.state_objects.get(&address) else {
return Err(DesignProjectionError::MissingStateObject { address });
};
if metadata != &info.metadata {
return Err(DesignProjectionError::MetadataMismatch { address });
}
}
}
Ok(())
}
}
impl OptimizedSir {
pub fn collect_working_region_addrs(&self) -> crate::HashSet<AbsoluteAddr> {
let mut addrs = crate::HashSet::default();
let scan_units =
|units: &HashMap<AbsoluteAddr, Vec<ExecutionUnit<RegionedAbsoluteAddr>>>,
addrs: &mut crate::HashSet<AbsoluteAddr>| {
for eu_list in units.values() {
for eu in eu_list {
for block in eu.blocks.values() {
for inst in &block.instructions {
match inst {
SIRInstruction::Store(addr, _, _, _, _, _)
if addr.region == WORKING_REGION =>
{
addrs.insert(addr.absolute_addr());
}
SIRInstruction::Commit(src, dst, _, _, _) => {
if src.region == WORKING_REGION {
addrs.insert(src.absolute_addr());
}
if dst.region == WORKING_REGION {
addrs.insert(dst.absolute_addr());
}
}
_ => {}
}
}
}
}
}
};
scan_units(&self.sir.eval_apply_ffs, &mut addrs);
scan_units(&self.sir.eval_comb_apply_ffs, &mut addrs);
scan_units(&self.sir.eval_only_ffs, &mut addrs);
scan_units(&self.sir.apply_ffs, &mut addrs);
addrs
}
pub fn collect_sparse_working_region_addrs(&self) -> crate::HashSet<AbsoluteAddr> {
let mut addrs = crate::HashSet::default();
for units in self
.sir
.eval_apply_ffs
.values()
.chain(self.sir.eval_comb_apply_ffs.values())
.chain(self.sir.eval_only_ffs.values())
{
for eu in units {
for block in eu.blocks.values() {
for inst in &block.instructions {
if let SIRInstruction::Store(addr, _, _, _, _, _) = inst
&& addr.region == SPARSE_WORKING_REGION
{
addrs.insert(addr.absolute_addr());
}
}
}
}
}
addrs
}
}
fn comb_capture_enable_needs_unaliased_old_value(
units: &[ExecutionUnit<RegionedAbsoluteAddr>],
alias_addr: AbsoluteAddr,
) -> bool {
for eu in units {
for block in eu.blocks.values() {
let mut last_store = None;
for inst in &block.instructions {
match inst {
SIRInstruction::Store(addr, _, _, _, _, comb_capture_sites) => {
let abs = addr.absolute_addr();
if abs == alias_addr && !comb_capture_sites.is_empty() {
return true;
}
last_store = Some(abs);
}
SIRInstruction::CombCaptureEnableIfChanged { sites, .. } => {
if !sites.is_empty() && last_store == Some(alias_addr) {
return true;
}
last_store = None;
}
_ => {
last_store = None;
}
}
}
}
}
false
}
pub(crate) mod verify {
pub(crate) use celox_sir::verify::*;
}
pub use celox_slt::{GlueAddrBase, GlueBlockBase};
pub use celox_frontend_core::TraceSimModule as SimModule;
#[cfg(all(
feature = "host-runtime",
any(
target_arch = "x86_64",
feature = "arm64-codegen",
all(target_arch = "aarch64", feature = "experimental-arm64-backend")
)
))]
pub(crate) use celox_runtime::SignalArrayLayout;
pub use celox_runtime::SignalRef;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exact_zero_analysis_collapses_repeated_concat_dependencies() {
let zero = RegisterId(0);
let wide_zero = RegisterId(1);
let sliced_zero = RegisterId(2);
let nonzero = RegisterId(3);
let mixed = RegisterId(4);
let eu: ExecutionUnit<()> = ExecutionUnit {
entry_block_id: BlockId(0),
blocks: [(
BlockId(0),
BasicBlock {
id: BlockId(0),
params: vec![],
instructions: vec![
SIRInstruction::Imm(zero, SIRValue::new(0u8)),
SIRInstruction::Concat(wide_zero, vec![zero; 4096]),
SIRInstruction::Slice(sliced_zero, wide_zero, 0, 64),
SIRInstruction::Imm(nonzero, SIRValue::new(1u8)),
SIRInstruction::Concat(mixed, vec![zero, nonzero]),
],
terminator: SIRTerminator::Return,
},
)]
.into_iter()
.collect(),
register_map: HashMap::default(),
};
let zeros = collect_exact_zero_registers(&eu, [sliced_zero, mixed]);
assert!(zeros.contains(&zero));
assert!(zeros.contains(&wide_zero));
assert!(zeros.contains(&sliced_zero));
assert!(!zeros.contains(&nonzero));
assert!(!zeros.contains(&mixed));
}
#[test]
fn test_sirvalue_display() {
let val = SIRValue::new(42u64);
let display = format!("{}", val);
assert!(display.contains("SIRValue"));
assert!(display.contains("0x2a")); }
#[test]
fn test_absoluteaddr_display() {
let addr = AbsoluteAddr {
instance_id: InstanceId(0),
var_id: celox_design::StateObjectId(0),
};
let display = format!("{}", addr);
assert!(display.contains("AbsoluteAddr"));
assert!(display.contains("inst0"));
assert!(display.contains("state0"));
}
#[test]
fn test_glueaddr_display() {
let parent_addr =
celox_frontend_veryl::GlueAddr::Parent(veryl_analyzer::ir::VarId::default());
let parent_display = format!("{}", parent_addr);
assert!(parent_display.contains("GlueAddr::Parent"));
assert!(parent_display.contains("var0"));
let child_addr =
celox_frontend_veryl::GlueAddr::Child(veryl_analyzer::ir::VarId::default());
let child_display = format!("{}", child_addr);
assert!(child_display.contains("GlueAddr::Child"));
assert!(child_display.contains("var0"));
}
#[test]
fn test_instanceid_display() {
let id = InstanceId(42);
let display = format!("{}", id);
assert_eq!(display, "inst42");
}
#[test]
fn test_binaryop_display() {
assert_eq!(format!("{}", BinaryOp::Add), "Add");
assert_eq!(format!("{}", BinaryOp::Sub), "Sub");
assert_eq!(format!("{}", BinaryOp::Mul), "Mul");
assert_eq!(format!("{}", BinaryOp::Xor), "Xor");
}
#[test]
fn test_unaryop_display() {
assert_eq!(format!("{}", UnaryOp::Minus), "Minus");
assert_eq!(format!("{}", UnaryOp::LogicNot), "LogicNot");
assert_eq!(format!("{}", UnaryOp::BitNot), "BitNot");
assert_eq!(format!("{}", UnaryOp::PopCount), "PopCount");
assert_eq!(
format!("{}", UnaryOp::CountLeadingZeros),
"CountLeadingZeros"
);
assert_eq!(
format!("{}", UnaryOp::CountTrailingZeros),
"CountTrailingZeros"
);
}
#[test]
fn bit_count_result_width_represents_operand_width() {
for (operand_width, expected) in [
(0, 0),
(1, 1),
(2, 2),
(3, 2),
(8, 4),
(usize::MAX, usize::BITS as usize),
] {
for op in [
UnaryOp::PopCount,
UnaryOp::CountLeadingZeros,
UnaryOp::CountTrailingZeros,
] {
assert_eq!(op.result_width(operand_width), expected, "{op}");
}
}
}
#[test]
fn bit_count_unary_ops_roundtrip_through_serde() {
for op in [
UnaryOp::PopCount,
UnaryOp::CountLeadingZeros,
UnaryOp::CountTrailingZeros,
] {
let encoded = serde_json::to_string(&op).unwrap();
let decoded: UnaryOp = serde_json::from_str(&encoded).unwrap();
assert_eq!(decoded, op);
}
}
#[test]
fn test_sirinstruction_display() {
let imm: SIRInstruction<i32> = SIRInstruction::Imm(RegisterId(0), SIRValue::new(42u64));
let imm_display = format!("{}", imm);
assert!(imm_display.contains("r0"));
assert!(imm_display.contains("SIRValue"));
let binary: SIRInstruction<i32> =
SIRInstruction::Binary(RegisterId(0), RegisterId(1), BinaryOp::Add, RegisterId(2));
let binary_display = format!("{}", binary);
assert!(binary_display.contains("r0"));
assert!(binary_display.contains("r1"));
assert!(binary_display.contains("r2"));
assert!(binary_display.contains("Add"));
let unary: SIRInstruction<i32> =
SIRInstruction::Unary(RegisterId(0), UnaryOp::Minus, RegisterId(1));
let unary_display = format!("{}", unary);
assert!(unary_display.contains("r0"));
assert!(unary_display.contains("r1"));
assert!(unary_display.contains("Minus"));
}
#[test]
fn test_sirterminator_display() {
let jump = SIRTerminator::Jump(BlockId(1), vec![RegisterId(0), RegisterId(1)]);
let jump_display = format!("{}", jump);
assert!(jump_display.contains("Jump"));
assert!(jump_display.contains("b1"));
let ret = SIRTerminator::Return;
let ret_display = format!("{}", ret);
assert_eq!(ret_display, "Return");
let branch = SIRTerminator::Branch {
cond: RegisterId(0),
true_block: (BlockId(1), vec![]),
false_block: (BlockId(2), vec![]),
};
let branch_display = format!("{}", branch);
assert!(branch_display.contains("Branch"));
assert!(branch_display.contains("b1"));
assert!(branch_display.contains("b2"));
}
#[test]
fn test_basicblock_display() {
let _block: BasicBlock<i32> = BasicBlock {
id: BlockId(0),
params: vec![RegisterId(0), RegisterId(1)],
instructions: vec![
SIRInstruction::Imm(RegisterId(2), SIRValue::new(42u64)),
SIRInstruction::Binary(RegisterId(3), RegisterId(0), BinaryOp::Add, RegisterId(2)),
],
terminator: SIRTerminator::Return,
};
let block_display = format!("{}", _block);
assert!(block_display.contains("b0:"));
assert!(block_display.contains("params:"));
assert!(block_display.contains("r0"));
assert!(block_display.contains("r1"));
assert!(block_display.contains("Add"));
assert!(block_display.contains("Return"));
}
#[test]
fn single_predecessor_inlining_rewrites_dominated_parameter_uses() {
let mut eu: ExecutionUnit<()> = ExecutionUnit {
entry_block_id: BlockId(0),
blocks: [
BasicBlock {
id: BlockId(0),
params: vec![RegisterId(0)],
instructions: Vec::new(),
terminator: SIRTerminator::Jump(BlockId(1), vec![RegisterId(0)]),
},
BasicBlock {
id: BlockId(1),
params: vec![RegisterId(1)],
instructions: Vec::new(),
terminator: SIRTerminator::Jump(BlockId(2), Vec::new()),
},
BasicBlock {
id: BlockId(2),
params: Vec::new(),
instructions: vec![SIRInstruction::Unary(
RegisterId(2),
UnaryOp::Ident,
RegisterId(1),
)],
terminator: SIRTerminator::Return,
},
]
.into_iter()
.map(|block| (block.id, block))
.collect(),
register_map: (0..3)
.map(|register| {
(
RegisterId(register),
RegisterType::Bit {
width: 8,
signed: false,
},
)
})
.collect(),
};
eu.verify_result().unwrap();
assert!(inline_single_predecessor_jumps(&mut eu).unwrap());
eu.verify_result().unwrap();
assert_eq!(eu.blocks.len(), 1);
assert!(matches!(
eu.blocks[&BlockId(0)].instructions.as_slice(),
[SIRInstruction::Unary(
RegisterId(2),
UnaryOp::Ident,
RegisterId(0)
)]
));
}
#[test]
fn single_predecessor_inlining_handles_deep_linear_cfg() {
const BLOCK_COUNT: usize = 20_000;
let mut eu: ExecutionUnit<()> = ExecutionUnit {
entry_block_id: BlockId(0),
blocks: (0..BLOCK_COUNT)
.map(|index| {
let id = BlockId(index);
let terminator = if index + 1 == BLOCK_COUNT {
SIRTerminator::Return
} else {
SIRTerminator::Jump(BlockId(index + 1), Vec::new())
};
(
id,
BasicBlock {
id,
params: Vec::new(),
instructions: Vec::new(),
terminator,
},
)
})
.collect(),
register_map: crate::HashMap::default(),
};
eu.verify_result().unwrap();
assert!(inline_single_predecessor_jumps(&mut eu).unwrap());
assert_eq!(eu.blocks.len(), 1);
assert_eq!(eu.blocks[&BlockId(0)].terminator, SIRTerminator::Return);
eu.verify_result().unwrap();
}
}