use crate::FuncPtr;
use crate::HashSet;
#[cfg(not(target_family = "wasm"))]
use crate::cranelift::Context as CraneliftContext;
use crate::ir::context::{Context as ConvContext, Conv};
use crate::ir::expression::{
DynamicBitSelect, ExpressionContext, ProtoDynamicBitSelect, build_dynamic_bit_select,
build_linear_index_expr,
};
#[cfg(not(target_family = "wasm"))]
use crate::ir::expression::{
band_const, build_dynamic_select_shift, gen_mask_for_width, gen_mask_range_128, iconst_128,
};
use crate::ir::variable::{
VarOffset, native_bytes_for as calc_native_bytes_for, read_native_value, write_native_value,
};
use crate::ir::{Expression, ProtoExpression, Value};
use crate::output_buffer;
use crate::simulator_error::SimulatorError;
#[cfg(not(target_family = "wasm"))]
use cranelift::prelude::types::{I32, I64, I128};
#[cfg(not(target_family = "wasm"))]
use cranelift::prelude::{FunctionBuilder, InstBuilder, IntCC, MemFlags};
use veryl_analyzer::conv::utils::eval_array_literal;
use veryl_analyzer::ir as air;
use veryl_analyzer::ir::{
AssertKind, SystemFunctionInput, SystemFunctionKind, TypeKind, ValueVariant,
};
use veryl_analyzer::ir::{ControlFlow, FunctionCall};
#[cfg(not(target_family = "wasm"))]
use veryl_analyzer::value::ValueU64;
use veryl_analyzer::value::{MaskCache, Value as AnalyzerValue};
use veryl_parser::resource_table::StrId;
use veryl_parser::token_range::TokenRange;
pub type StmtDep = (Vec<VarOffset>, Vec<VarOffset>);
pub enum ProtoStatementBlock {
Interpreted(Vec<ProtoStatement>),
Compiled(FuncPtr),
}
pub struct ProtoStatements(pub Vec<ProtoStatementBlock>);
impl ProtoStatements {
pub(crate) fn to_statements(
&self,
ff_ptr: *mut u8,
ff_len: usize,
comb_ptr: *mut u8,
comb_len: usize,
use_4state: bool,
) -> Vec<Statement> {
let mut result = Vec::new();
for block in &self.0 {
match block {
ProtoStatementBlock::Interpreted(proto) => {
for s in proto {
result.push(unsafe {
s.apply_values_ptr(ff_ptr, ff_len, comb_ptr, comb_len, use_4state)
});
}
}
ProtoStatementBlock::Compiled(func) => {
result.push(Statement::Binary(
*func,
ff_ptr as *const u8,
comb_ptr as *const u8,
));
}
}
}
result
}
}
#[derive(Clone, Debug)]
pub struct ReadmemhElement {
pub current: VarOffset,
pub next_offset: Option<isize>,
}
#[derive(Clone)]
pub enum SystemFunctionCall {
Display {
format_str: String,
args: Vec<Expression>,
},
Write {
format_str: String,
args: Vec<Expression>,
},
Readmemh {
filename: String,
elements: Vec<(*mut u8, Option<*mut u8>, usize, bool)>,
width: usize,
},
Assert {
kind: AssertKind,
condition: Expression,
format_str: String,
args: Vec<Expression>,
},
Finish,
}
#[derive(Clone)]
pub struct AssignDynamicStatement {
pub dst_base_ptr: *mut u8,
pub dst_stride: isize,
pub dst_num_elements: usize,
pub dst_index_expr: Expression,
pub dst_width: usize,
pub dst_native_bytes: usize,
pub dst_use_4state: bool,
pub select: Option<(usize, usize)>,
pub dynamic_select: Option<DynamicBitSelect>,
pub rhs_select: Option<(usize, usize)>,
pub expr: Expression,
}
#[derive(Clone, Debug)]
pub enum ProtoTbMethodKind {
ClockNext {
count: Option<ProtoExpression>,
period: Option<ProtoExpression>,
},
ResetAssert {
clock: StrId,
duration: Option<ProtoExpression>,
},
}
#[derive(Clone)]
pub enum TbMethodKind {
ClockNext {
count: Option<Expression>,
period: Option<Expression>,
},
ResetAssert {
clock: StrId,
duration: Option<Expression>,
},
}
#[derive(Clone)]
pub enum RuntimeForBound {
Const(u64),
Dynamic(Box<Expression>),
}
unsafe impl Send for RuntimeForBound {}
impl RuntimeForBound {
pub fn eval(&self, mask_cache: &mut MaskCache) -> u64 {
match self {
RuntimeForBound::Const(v) => *v,
RuntimeForBound::Dynamic(expr) => {
let val = expr.eval(mask_cache);
val.to_usize().unwrap_or(0) as u64
}
}
}
}
#[derive(Clone)]
pub struct RuntimeForRange {
pub start: RuntimeForBound,
pub end: RuntimeForBound,
pub inclusive: bool,
pub step: u64,
pub op: Option<air::Op>,
pub reverse: bool,
}
#[derive(Clone)]
pub struct ForStatement {
pub var_ptr: *mut u8,
pub var_native_bytes: usize,
pub var_use_4state: bool,
pub var_width: usize,
pub var_signed: bool,
pub range: RuntimeForRange,
pub body: Vec<Statement>,
}
#[derive(Clone)]
pub enum Statement {
Assign(AssignStatement),
AssignDynamic(AssignDynamicStatement),
If(IfStatement),
For(ForStatement),
Break,
Binary(FuncPtr, *const u8, *const u8),
BinaryBatch(FuncPtr, Vec<(*const u8, *const u8)>),
SequentialBlock(Vec<Statement>),
SystemFunctionCall(SystemFunctionCall),
TbMethodCall { inst: StrId, method: TbMethodKind },
}
unsafe impl Send for Statement {}
unsafe impl Send for AssignStatement {}
unsafe impl Send for AssignDynamicStatement {}
unsafe impl Send for IfStatement {}
unsafe impl Send for ForStatement {}
unsafe impl Send for SystemFunctionCall {}
impl Statement {
pub fn is_binary(&self) -> bool {
matches!(
self,
Statement::Binary(_, _, _) | Statement::BinaryBatch(_, _)
)
}
pub fn eval_step(&self, mask_cache: &mut MaskCache) -> ControlFlow {
match self {
Statement::Assign(x) => {
x.eval_step(mask_cache);
ControlFlow::Continue
}
Statement::AssignDynamic(x) => {
x.eval_step(mask_cache);
ControlFlow::Continue
}
Statement::If(x) => x.eval_step(mask_cache),
Statement::For(x) => {
let r = &x.range;
let start = r.start.eval(mask_cache);
let mut end = r.end.eval(mask_cache);
if r.inclusive {
end += 1;
}
let mut step_body = |i: u64| -> ControlFlow {
let val = Value::new(i, x.var_width, x.var_signed);
unsafe {
write_native_value(x.var_ptr, x.var_native_bytes, x.var_use_4state, &val);
}
for s in &x.body {
if s.eval_step(mask_cache) == ControlFlow::Break {
return ControlFlow::Break;
}
}
ControlFlow::Continue
};
if r.reverse {
let mut i = end;
while i > start {
i -= r.step;
if step_body(i) == ControlFlow::Break {
break;
}
}
} else if let Some(op) = &r.op {
let mut i = start;
while i < end {
if step_body(i) == ControlFlow::Break {
break;
}
i = op.eval(i as usize, r.step as usize) as u64;
}
} else {
let mut i = start;
while i < end {
if step_body(i) == ControlFlow::Break {
break;
}
i += r.step;
}
}
ControlFlow::Continue
}
Statement::Break => ControlFlow::Break,
Statement::Binary(func, ff_values, comb_values) => unsafe {
func(*ff_values, *comb_values);
ControlFlow::Continue
},
Statement::BinaryBatch(func, args) => unsafe {
for &(ff_values, comb_values) in args {
func(ff_values, comb_values);
}
ControlFlow::Continue
},
Statement::SequentialBlock(body) => {
for s in body {
if s.eval_step(mask_cache) == ControlFlow::Break {
return ControlFlow::Break;
}
}
ControlFlow::Continue
}
Statement::SystemFunctionCall(x) => {
x.eval_step(mask_cache);
ControlFlow::Continue
}
Statement::TbMethodCall { .. } => ControlFlow::Continue,
}
}
pub fn gather_variable(&self, inputs: &mut Vec<*const u8>, outputs: &mut Vec<*const u8>) {
match self {
Statement::Assign(x) => x.gather_variable(inputs, outputs),
Statement::AssignDynamic(x) => x.gather_variable(inputs, outputs),
Statement::If(x) => x.gather_variable(inputs, outputs),
Statement::For(x) => {
for s in &x.body {
s.gather_variable(inputs, outputs);
}
}
Statement::Binary(_, _, _) | Statement::BinaryBatch(_, _) | Statement::Break => (),
Statement::SequentialBlock(body) => {
for s in body {
s.gather_variable(inputs, outputs);
}
}
Statement::SystemFunctionCall(x) => x.gather_variable(inputs),
Statement::TbMethodCall { .. } => (),
}
}
}
pub fn format_assert_message(
format_str: &str,
args: &[Expression],
mask_cache: &mut MaskCache,
) -> String {
if format_str.is_empty() && args.is_empty() {
return "assertion failed".to_string();
}
if format_str.is_empty() {
let values: Vec<_> = args.iter().map(|e| e.eval(mask_cache)).collect();
let parts: Vec<String> = values.iter().map(|v| v.format_hex()).collect();
return parts.join(" ");
}
if args.is_empty() {
return format_str.to_string();
}
let values: Vec<_> = args.iter().map(|e| e.eval(mask_cache)).collect();
format_display_string(format_str, &values)
}
fn format_display_string(format_str: &str, values: &[AnalyzerValue]) -> String {
let mut result = String::new();
let mut chars = format_str.chars().peekable();
let mut arg_idx = 0;
while let Some(ch) = chars.next() {
if ch == '%' {
if let Some(&spec) = chars.peek() {
chars.next();
match spec {
'%' => result.push('%'),
'h' | 'H' | 'x' | 'X' => {
if let Some(v) = values.get(arg_idx) {
result.push_str(&v.format_hex());
}
arg_idx += 1;
}
'd' | 'D' => {
if let Some(v) = values.get(arg_idx) {
result.push_str(&v.format_dec());
}
arg_idx += 1;
}
'o' | 'O' => {
if let Some(v) = values.get(arg_idx) {
result.push_str(&v.format_oct());
}
arg_idx += 1;
}
'b' | 'B' => {
if let Some(v) = values.get(arg_idx) {
result.push_str(&v.format_bin());
}
arg_idx += 1;
}
'c' | 'C' => {
if let Some(v) = values.get(arg_idx) {
let ch = (v.payload_u64() & 0xFF) as u8 as char;
result.push(ch);
}
arg_idx += 1;
}
's' | 'S' => {
if let Some(v) = values.get(arg_idx) {
result.push_str(&v.format_dec());
}
arg_idx += 1;
}
'm' | 'M' => {
result.push_str("<hierarchy>");
}
't' | 'T' => {
result.push('0');
}
_ => {
result.push('%');
result.push(spec);
}
}
} else {
result.push('%');
}
} else {
result.push(ch);
}
}
result
}
impl SystemFunctionCall {
pub fn eval_step(&self, mask_cache: &mut MaskCache) {
match self {
SystemFunctionCall::Display { format_str, args } => {
let values: Vec<_> = args.iter().map(|e| e.eval(mask_cache)).collect();
if format_str.is_empty() {
let parts: Vec<String> = values.iter().map(|v| v.format_hex()).collect();
output_buffer::println(&parts.join(" "));
} else if values.is_empty() {
output_buffer::println(format_str);
} else {
let output = format_display_string(format_str, &values);
output_buffer::println(&output);
}
}
SystemFunctionCall::Write { format_str, args } => {
let values: Vec<_> = args.iter().map(|e| e.eval(mask_cache)).collect();
if format_str.is_empty() {
let parts: Vec<String> = values.iter().map(|v| v.format_hex()).collect();
output_buffer::print(&parts.join(" "));
} else if values.is_empty() {
output_buffer::print(format_str);
} else {
let output = format_display_string(format_str, &values);
output_buffer::print(&output);
}
}
SystemFunctionCall::Readmemh {
filename,
elements,
width,
} => {
let values = parse_hex_file(filename, *width);
let count = values.len().min(elements.len());
for i in 0..count {
let (current, next, nb, use_4state) = elements[i];
unsafe { write_native_value(current, nb, use_4state, &values[i]) };
if let Some(next) = next {
unsafe { write_native_value(next, nb, use_4state, &values[i]) };
}
}
}
SystemFunctionCall::Assert {
kind,
condition,
format_str,
args,
} => {
let val = condition.eval(mask_cache);
if val.payload_u64() == 0 {
let msg = format_assert_message(format_str, args, mask_cache);
match kind {
AssertKind::Fatal => crate::assert_buffer::record_fatal(msg),
AssertKind::Continue => crate::assert_buffer::record_continue(msg),
}
}
}
SystemFunctionCall::Finish => {
}
}
}
pub fn gather_variable(&self, inputs: &mut Vec<*const u8>) {
match self {
SystemFunctionCall::Display { args, .. } | SystemFunctionCall::Write { args, .. } => {
for e in args {
let mut dummy_outputs = vec![];
e.gather_variable(inputs, &mut dummy_outputs);
}
}
SystemFunctionCall::Readmemh { .. } => {}
SystemFunctionCall::Assert { condition, .. } => {
let mut dummy_outputs = vec![];
condition.gather_variable(inputs, &mut dummy_outputs);
}
SystemFunctionCall::Finish => {}
}
}
}
#[derive(Clone, Debug)]
pub enum ProtoSystemFunctionCall {
Display {
format_str: String,
args: Vec<ProtoExpression>,
},
Write {
format_str: String,
args: Vec<ProtoExpression>,
},
Readmemh {
filename: String,
elements: Vec<ReadmemhElement>,
width: usize,
},
Assert {
kind: AssertKind,
condition: ProtoExpression,
format_str: String,
args: Vec<ProtoExpression>,
},
Finish,
}
#[derive(Clone, Debug)]
pub struct ProtoAssignDynamicStatement {
pub dst_base: VarOffset,
pub dst_stride: isize,
pub dst_num_elements: usize,
pub dst_index_expr: ProtoExpression,
pub dst_width: usize,
pub select: Option<(usize, usize)>,
pub dynamic_select: Option<ProtoDynamicBitSelect>,
pub rhs_select: Option<(usize, usize)>,
pub expr: ProtoExpression,
pub dst_ff_current_base_offset: isize,
}
impl ProtoAssignDynamicStatement {
#[cfg(not(target_family = "wasm"))]
pub fn can_build_binary(&self) -> bool {
if !self.expr.can_build_binary() || !self.dst_index_expr.can_build_binary() {
return false;
}
if let Some(dyn_sel) = &self.dynamic_select {
let full_width = dyn_sel.elem_width * dyn_sel.num_elements;
if full_width > 128 || !dyn_sel.index_expr.can_build_binary() {
return false;
}
full_width <= 64
} else {
self.dst_width <= 64
}
}
#[cfg(not(target_family = "wasm"))]
pub fn build_binary(
&self,
context: &mut CraneliftContext,
builder: &mut FunctionBuilder,
) -> Option<()> {
let (mut payload, mut mask_xz) = self.expr.build_binary(context, builder)?;
let nb = calc_native_bytes_for(self.dst_width, self.dst_base.is_ff());
let nb_i32 = nb as i32;
if let Some((beg, end)) = self.rhs_select {
let mask = ValueU64::gen_mask(beg - end + 1);
payload = builder.ins().ushr_imm(payload, end as i64);
payload = builder.ins().band_imm(payload, mask as i64);
if let Some(mxz) = mask_xz {
let mxz = builder.ins().ushr_imm(mxz, end as i64);
let mxz = builder.ins().band_imm(mxz, mask as i64);
mask_xz = Some(mxz);
}
}
let (idx_payload, _idx_mask_xz) = self.dst_index_expr.build_binary(context, builder)?;
let max_idx = builder
.ins()
.iconst(I64, (self.dst_num_elements as i64).saturating_sub(1));
let in_bounds = builder
.ins()
.icmp(IntCC::UnsignedLessThan, idx_payload, max_idx);
let clamped = builder.ins().select(in_bounds, idx_payload, max_idx);
let stride_val = builder.ins().iconst(I64, self.dst_stride as i64);
let byte_offset = builder.ins().imul(clamped, stride_val);
let base_addr = if self.dst_base.is_ff() {
context.ff_values
} else {
context.comb_values
};
let static_offset = builder.ins().iconst(I64, self.dst_base.raw() as i64);
let addr = builder.ins().iadd(base_addr, static_offset);
let addr = builder.ins().iadd(addr, byte_offset);
let load_mem_flag = MemFlags::trusted();
let store_mem_flag = MemFlags::trusted();
let load_native_to_i64 =
|builder: &mut FunctionBuilder, addr: cranelift::prelude::Value, off: i32| match nb {
1 => builder.ins().uload8(I64, load_mem_flag, addr, off),
2 => builder.ins().uload16(I64, load_mem_flag, addr, off),
4 => builder.ins().uload32(load_mem_flag, addr, off),
_ => builder.ins().load(I64, load_mem_flag, addr, off),
};
let store_i64_to_native = |builder: &mut FunctionBuilder,
v: cranelift::prelude::Value,
addr: cranelift::prelude::Value,
off: i32| match nb {
1 => {
builder.ins().istore8(store_mem_flag, v, addr, off);
}
2 => {
builder.ins().istore16(store_mem_flag, v, addr, off);
}
4 => {
builder.ins().istore32(store_mem_flag, v, addr, off);
}
_ => {
builder.ins().store(store_mem_flag, v, addr, off);
}
};
if let Some(dyn_sel) = &self.dynamic_select {
let shift = build_dynamic_select_shift(dyn_sel, context, builder)?;
let payload = builder.ins().ishl(payload, shift);
let elem_mask = gen_mask_for_width(dyn_sel.elem_width);
let mask_val = builder.ins().iconst(I64, elem_mask as i64);
let dyn_mask = builder.ins().ishl(mask_val, shift);
let not_mask = builder.ins().bnot(dyn_mask);
let org = load_native_to_i64(builder, addr, 0);
let org = builder.ins().band(org, not_mask);
let result = builder.ins().bor(payload, org);
store_i64_to_native(builder, result, addr, 0);
if let Some(mask_xz) = mask_xz {
let mask_xz = builder.ins().ishl(mask_xz, shift);
let org = load_native_to_i64(builder, addr, nb_i32);
let org = builder.ins().band(org, not_mask);
let result = builder.ins().bor(mask_xz, org);
store_i64_to_native(builder, result, addr, nb_i32);
}
} else if let Some((beg, end)) = self.select {
let mask = ValueU64::gen_mask_range(beg, end);
let payload = builder.ins().ishl_imm(payload, end as i64);
let org = load_native_to_i64(builder, addr, 0);
let org = builder.ins().band_imm(org, !mask as i64);
let result = builder.ins().bor(payload, org);
store_i64_to_native(builder, result, addr, 0);
if let Some(mask_xz) = mask_xz {
let mask_xz = builder.ins().ishl_imm(mask_xz, end as i64);
let org = load_native_to_i64(builder, addr, nb_i32);
let org = builder.ins().band_imm(org, !mask as i64);
let result = builder.ins().bor(mask_xz, org);
store_i64_to_native(builder, result, addr, nb_i32);
}
} else {
match self.dst_width {
8 => {
builder.ins().istore8(store_mem_flag, payload, addr, 0);
if let Some(mask_xz) = mask_xz {
builder.ins().istore8(store_mem_flag, mask_xz, addr, nb_i32);
}
}
16 => {
builder.ins().istore16(store_mem_flag, payload, addr, 0);
if let Some(mask_xz) = mask_xz {
builder
.ins()
.istore16(store_mem_flag, mask_xz, addr, nb_i32);
}
}
32 => {
builder.ins().istore32(store_mem_flag, payload, addr, 0);
if let Some(mask_xz) = mask_xz {
builder
.ins()
.istore32(store_mem_flag, mask_xz, addr, nb_i32);
}
}
64 => {
builder.ins().store(store_mem_flag, payload, addr, 0);
if let Some(mask_xz) = mask_xz {
builder.ins().store(store_mem_flag, mask_xz, addr, nb_i32);
}
}
_ => {
if self.dst_width >= 64 {
return None;
}
let mask = (1u64 << self.dst_width) - 1;
let payload = builder.ins().band_imm(payload, mask as i64);
match nb {
1 => {
builder.ins().istore8(store_mem_flag, payload, addr, 0);
}
2 => {
builder.ins().istore16(store_mem_flag, payload, addr, 0);
}
4 => {
builder.ins().istore32(store_mem_flag, payload, addr, 0);
}
_ => {
builder.ins().store(store_mem_flag, payload, addr, 0);
}
}
if let Some(mask_xz) = mask_xz {
let mask_xz = builder.ins().band_imm(mask_xz, mask as i64);
match nb {
1 => {
builder.ins().istore8(store_mem_flag, mask_xz, addr, nb_i32);
}
2 => {
builder
.ins()
.istore16(store_mem_flag, mask_xz, addr, nb_i32);
}
4 => {
builder
.ins()
.istore32(store_mem_flag, mask_xz, addr, nb_i32);
}
_ => {
builder.ins().store(store_mem_flag, mask_xz, addr, nb_i32);
}
}
}
}
}
}
Some(())
}
}
#[derive(Clone, Debug)]
pub struct CompiledBlockStatement {
pub func: FuncPtr,
pub ff_delta_bytes: isize,
pub comb_delta_bytes: isize,
pub input_offsets: Vec<VarOffset>,
pub output_offsets: Vec<VarOffset>,
pub ff_canonical_offsets: Vec<isize>,
pub stmt_deps: Vec<StmtDep>,
pub original_stmts: Vec<ProtoStatement>,
}
#[derive(Clone, Debug)]
pub enum ProtoForBound {
Const(u64),
Dynamic(ProtoExpression),
}
#[derive(Clone, Debug)]
pub enum ProtoForRange {
Forward {
start: ProtoForBound,
end: ProtoForBound,
inclusive: bool,
step: u64,
},
Reverse {
start: ProtoForBound,
end: ProtoForBound,
inclusive: bool,
step: u64,
},
Stepped {
start: ProtoForBound,
end: ProtoForBound,
inclusive: bool,
step: u64,
op: air::Op,
},
}
impl ProtoForRange {
fn is_const(&self) -> bool {
let (s, e) = match self {
ProtoForRange::Forward { start, end, .. }
| ProtoForRange::Reverse { start, end, .. }
| ProtoForRange::Stepped { start, end, .. } => (start, end),
};
matches!(s, ProtoForBound::Const(_)) && matches!(e, ProtoForBound::Const(_))
}
}
impl ProtoForBound {
pub fn as_const(&self) -> Option<u64> {
match self {
ProtoForBound::Const(v) => Some(*v),
ProtoForBound::Dynamic(_) => None,
}
}
}
#[derive(Clone, Debug)]
pub struct ProtoForStatement {
pub var_offset: VarOffset,
pub var_width: usize,
pub var_native_bytes: usize,
pub var_signed: bool,
pub range: ProtoForRange,
pub body: Vec<ProtoStatement>,
}
impl ProtoForStatement {
#[cfg(not(target_family = "wasm"))]
pub fn can_build_binary(&self) -> bool {
self.range.is_const() && self.body.iter().all(|s| s.can_build_binary())
}
#[cfg(not(target_family = "wasm"))]
pub fn build_binary(
&self,
context: &mut CraneliftContext,
builder: &mut FunctionBuilder,
_is_last: bool,
) -> Option<()> {
let header_block = builder.create_block();
let body_block = builder.create_block();
let exit_block = builder.create_block();
let base_addr = if self.var_offset.is_ff() {
context.ff_values
} else {
context.comb_values
};
let var_mem_offset = self.var_offset.raw() as i32;
let nb = self.var_native_bytes;
let (start_val, end_val, step_val, is_reverse) = match &self.range {
ProtoForRange::Forward {
start,
end,
inclusive,
step,
} => {
let mut e = end.as_const()?;
if *inclusive {
e += 1;
}
(start.as_const()?, e, *step, false)
}
ProtoForRange::Reverse {
start,
end,
inclusive,
step,
} => {
let mut e = end.as_const()?;
if *inclusive {
e += 1;
}
(start.as_const()?, e, *step, true)
}
ProtoForRange::Stepped {
start,
end,
inclusive,
step,
..
} => {
let mut e = end.as_const()?;
if *inclusive {
e += 1;
}
(start.as_const()?, e, *step, false)
}
};
let init_i = if is_reverse {
builder.ins().iconst(I64, end_val as i64)
} else {
builder.ins().iconst(I64, start_val as i64)
};
Self::store_counter(context, builder, init_i, base_addr, var_mem_offset, nb);
builder.ins().jump(header_block, &[]);
context.load_cache.clear();
builder.switch_to_block(header_block);
let i_val = Self::load_counter_as_i64(builder, base_addr, var_mem_offset, nb);
let cond = if is_reverse {
let start_const = builder.ins().iconst(I64, start_val as i64);
builder
.ins()
.icmp(IntCC::UnsignedGreaterThan, i_val, start_const)
} else {
let end_const = builder.ins().iconst(I64, end_val as i64);
builder
.ins()
.icmp(IntCC::UnsignedLessThan, i_val, end_const)
};
builder.ins().brif(cond, body_block, &[], exit_block, &[]);
context.load_cache.clear();
let prev_store_elim = context.store_elim_enabled;
context.store_elim_enabled = false;
builder.switch_to_block(body_block);
if is_reverse {
let i_cur = Self::load_counter_as_i64(builder, base_addr, var_mem_offset, nb);
let step_c = builder.ins().iconst(I64, step_val as i64);
let new_i = builder.ins().isub(i_cur, step_c);
Self::store_counter(context, builder, new_i, base_addr, var_mem_offset, nb);
}
for s in &self.body {
s.build_binary(context, builder, false)?;
}
if !is_reverse {
let i_cur = Self::load_counter_as_i64(builder, base_addr, var_mem_offset, nb);
let step_c = builder.ins().iconst(I64, step_val as i64);
let new_i = match &self.range {
ProtoForRange::Stepped { op, .. } => match op {
air::Op::Mul => builder.ins().imul(i_cur, step_c),
air::Op::LogicShiftL => builder.ins().ishl(i_cur, step_c),
_ => builder.ins().iadd(i_cur, step_c),
},
_ => builder.ins().iadd(i_cur, step_c),
};
Self::store_counter(context, builder, new_i, base_addr, var_mem_offset, nb);
}
builder.ins().jump(header_block, &[]);
context.load_cache.clear();
context.store_elim_enabled = prev_store_elim;
builder.switch_to_block(exit_block);
Some(())
}
#[cfg(not(target_family = "wasm"))]
fn load_counter_as_i64(
builder: &mut FunctionBuilder,
base_addr: cranelift::prelude::Value,
offset: i32,
nb: usize,
) -> cranelift::prelude::Value {
if nb <= 4 {
builder
.ins()
.uload32(MemFlags::trusted(), base_addr, offset)
} else if nb <= 8 {
builder
.ins()
.load(I64, MemFlags::trusted(), base_addr, offset)
} else {
let v = builder
.ins()
.load(I128, MemFlags::trusted(), base_addr, offset);
builder.ins().ireduce(I64, v)
}
}
#[cfg(not(target_family = "wasm"))]
fn store_counter(
context: &CraneliftContext,
builder: &mut FunctionBuilder,
val_i64: cranelift::prelude::Value,
base_addr: cranelift::prelude::Value,
offset: i32,
nb: usize,
) {
if nb <= 4 {
let v32 = builder.ins().ireduce(I32, val_i64);
builder
.ins()
.store(MemFlags::trusted(), v32, base_addr, offset);
if context.use_4state {
let zero = builder.ins().iconst(I32, 0);
builder
.ins()
.store(MemFlags::trusted(), zero, base_addr, offset + nb as i32);
}
} else if nb <= 8 {
builder
.ins()
.store(MemFlags::trusted(), val_i64, base_addr, offset);
if context.use_4state {
let zero = builder.ins().iconst(I64, 0);
builder
.ins()
.store(MemFlags::trusted(), zero, base_addr, offset + nb as i32);
}
} else {
let v128 = builder.ins().uextend(I128, val_i64);
builder
.ins()
.store(MemFlags::trusted(), v128, base_addr, offset);
if context.use_4state {
builder.ins().store(
MemFlags::trusted(),
context.zero_128,
base_addr,
offset + nb as i32,
);
}
}
}
}
#[derive(Clone, Debug)]
pub enum ProtoStatement {
Assign(ProtoAssignStatement),
AssignDynamic(ProtoAssignDynamicStatement),
If(ProtoIfStatement),
For(ProtoForStatement),
Break,
SystemFunctionCall(ProtoSystemFunctionCall),
CompiledBlock(CompiledBlockStatement),
SequentialBlock(Vec<ProtoStatement>),
TbMethodCall {
inst: StrId,
method: ProtoTbMethodKind,
},
}
impl ProtoStatement {
pub fn adjust_offsets(&mut self, ff_delta: isize, comb_delta: isize) {
match self {
ProtoStatement::Assign(x) => {
x.dst = x.dst.adjust(ff_delta, comb_delta);
if x.dst.is_ff() {
x.dst_ff_current_offset += ff_delta;
}
x.expr.adjust_offsets(ff_delta, comb_delta);
if let Some(dyn_sel) = &mut x.dynamic_select {
dyn_sel.index_expr.adjust_offsets(ff_delta, comb_delta);
}
}
ProtoStatement::AssignDynamic(x) => {
x.dst_base = x.dst_base.adjust(ff_delta, comb_delta);
if x.dst_base.is_ff() {
x.dst_ff_current_base_offset += ff_delta;
}
x.dst_index_expr.adjust_offsets(ff_delta, comb_delta);
x.expr.adjust_offsets(ff_delta, comb_delta);
if let Some(dyn_sel) = &mut x.dynamic_select {
dyn_sel.index_expr.adjust_offsets(ff_delta, comb_delta);
}
}
ProtoStatement::If(x) => {
if let Some(cond) = &mut x.cond {
cond.adjust_offsets(ff_delta, comb_delta);
}
for s in &mut x.true_side {
s.adjust_offsets(ff_delta, comb_delta);
}
for s in &mut x.false_side {
s.adjust_offsets(ff_delta, comb_delta);
}
}
ProtoStatement::SystemFunctionCall(x) => match x {
ProtoSystemFunctionCall::Display { args, .. }
| ProtoSystemFunctionCall::Write { args, .. } => {
for arg in args {
arg.adjust_offsets(ff_delta, comb_delta);
}
}
ProtoSystemFunctionCall::Readmemh { elements, .. } => {
for elem in elements {
elem.current = elem.current.adjust(ff_delta, comb_delta);
if let Some(next) = &mut elem.next_offset {
*next += if elem.current.is_ff() {
ff_delta
} else {
comb_delta
};
}
}
}
ProtoSystemFunctionCall::Assert {
condition, args, ..
} => {
condition.adjust_offsets(ff_delta, comb_delta);
for arg in args {
arg.adjust_offsets(ff_delta, comb_delta);
}
}
ProtoSystemFunctionCall::Finish => {}
},
ProtoStatement::CompiledBlock(_) => {
}
ProtoStatement::For(x) => {
x.var_offset = x.var_offset.adjust(ff_delta, comb_delta);
let adjust_bound = |b: &mut ProtoForBound| {
if let ProtoForBound::Dynamic(expr) = b {
expr.adjust_offsets(ff_delta, comb_delta);
}
};
match &mut x.range {
ProtoForRange::Forward { start, end, .. }
| ProtoForRange::Reverse { start, end, .. }
| ProtoForRange::Stepped { start, end, .. } => {
adjust_bound(start);
adjust_bound(end);
}
}
for s in &mut x.body {
s.adjust_offsets(ff_delta, comb_delta);
}
}
ProtoStatement::SequentialBlock(body) => {
for s in body {
s.adjust_offsets(ff_delta, comb_delta);
}
}
ProtoStatement::TbMethodCall { method, .. } => match method {
ProtoTbMethodKind::ClockNext { count, period } => {
if let Some(c) = count {
c.adjust_offsets(ff_delta, comb_delta);
}
if let Some(p) = period {
p.adjust_offsets(ff_delta, comb_delta);
}
}
ProtoTbMethodKind::ResetAssert { duration, .. } => {
if let Some(d) = duration {
d.adjust_offsets(ff_delta, comb_delta);
}
}
},
ProtoStatement::Break => {}
}
}
#[cfg(not(target_family = "wasm"))]
pub fn can_build_binary(&self) -> bool {
match self {
ProtoStatement::Assign(x) => x.can_build_binary(),
ProtoStatement::AssignDynamic(x) => x.can_build_binary(),
ProtoStatement::If(x) => x.can_build_binary(),
ProtoStatement::For(x) => x.can_build_binary(),
ProtoStatement::SystemFunctionCall(_) => false,
ProtoStatement::CompiledBlock(_) => false,
ProtoStatement::SequentialBlock(body) => body.iter().all(|s| s.can_build_binary()),
ProtoStatement::TbMethodCall { .. } => false,
ProtoStatement::Break => false,
}
}
pub fn token(&self) -> Option<TokenRange> {
match self {
ProtoStatement::Assign(x) => Some(x.token),
_ => None,
}
}
pub fn split_if_reset(self) -> Option<(Vec<ProtoStatement>, Vec<ProtoStatement>)> {
if let ProtoStatement::If(x) = self {
if x.cond.is_some() {
return None;
}
Some((x.true_side, x.false_side))
} else {
None
}
}
pub fn gather_variable_offsets(
&self,
inputs: &mut Vec<VarOffset>,
outputs: &mut Vec<VarOffset>,
) {
match self {
ProtoStatement::Assign(x) => {
x.expr.gather_variable_offsets(inputs);
if let Some(dyn_sel) = &x.dynamic_select {
dyn_sel.index_expr.gather_variable_offsets(inputs);
}
outputs.push(x.dst);
}
ProtoStatement::AssignDynamic(x) => {
x.dst_index_expr.gather_variable_offsets(inputs);
x.expr.gather_variable_offsets(inputs);
if let Some(dyn_sel) = &x.dynamic_select {
dyn_sel.index_expr.gather_variable_offsets(inputs);
}
outputs.push(x.dst_base);
if x.dst_num_elements > 1 {
let last_offset = VarOffset::new(
x.dst_base.is_ff(),
x.dst_base.raw() + x.dst_stride * (x.dst_num_elements as isize - 1),
);
outputs.push(last_offset);
}
}
ProtoStatement::If(x) => {
if let Some(cond) = &x.cond {
cond.gather_variable_offsets(inputs);
}
for s in &x.true_side {
s.gather_variable_offsets(inputs, outputs);
}
for s in &x.false_side {
s.gather_variable_offsets(inputs, outputs);
}
}
ProtoStatement::SystemFunctionCall(x) => match x {
ProtoSystemFunctionCall::Display { args, .. }
| ProtoSystemFunctionCall::Write { args, .. } => {
for arg in args {
arg.gather_variable_offsets(inputs);
}
}
ProtoSystemFunctionCall::Readmemh { .. } => {}
ProtoSystemFunctionCall::Assert {
condition, args, ..
} => {
condition.gather_variable_offsets(inputs);
for arg in args {
arg.gather_variable_offsets(inputs);
}
}
ProtoSystemFunctionCall::Finish => {}
},
ProtoStatement::CompiledBlock(x) => {
if !x.stmt_deps.is_empty() {
for (ins, outs) in &x.stmt_deps {
for &off in ins {
if !off.is_ff() {
inputs.push(VarOffset::Comb(off.raw()));
}
}
for &off in outs {
if !off.is_ff() {
outputs.push(VarOffset::Comb(off.raw()));
}
}
}
} else {
for &off in &x.input_offsets {
if !off.is_ff() {
inputs.push(VarOffset::Comb(off.raw()));
}
}
for &off in &x.output_offsets {
if !off.is_ff() {
outputs.push(VarOffset::Comb(off.raw()));
}
}
}
}
ProtoStatement::For(x) => {
for s in &x.body {
s.gather_variable_offsets(inputs, outputs);
}
}
ProtoStatement::SequentialBlock(body) => {
let mut all_ins = vec![];
let mut all_outs = vec![];
for s in body {
s.gather_variable_offsets(&mut all_ins, &mut all_outs);
}
let input_set: HashSet<VarOffset> = all_ins.iter().cloned().collect();
let output_set: HashSet<VarOffset> = all_outs.iter().cloned().collect();
let internal: HashSet<VarOffset> =
input_set.intersection(&output_set).cloned().collect();
for off in all_ins {
if !internal.contains(&off) {
inputs.push(off);
}
}
outputs.extend(all_outs);
}
ProtoStatement::TbMethodCall { .. } => {}
ProtoStatement::Break => {}
}
}
pub fn gather_variable_offsets_expanded(
&self,
inputs: &mut Vec<VarOffset>,
outputs: &mut Vec<VarOffset>,
) {
match self {
ProtoStatement::Assign(x) => {
x.expr.gather_variable_offsets_expanded(inputs);
if let Some(dyn_sel) = &x.dynamic_select {
dyn_sel.index_expr.gather_variable_offsets_expanded(inputs);
}
outputs.push(x.dst);
}
ProtoStatement::AssignDynamic(x) => {
x.dst_index_expr.gather_variable_offsets_expanded(inputs);
x.expr.gather_variable_offsets_expanded(inputs);
if let Some(dyn_sel) = &x.dynamic_select {
dyn_sel.index_expr.gather_variable_offsets_expanded(inputs);
}
for i in 0..x.dst_num_elements {
let off = VarOffset::new(
x.dst_base.is_ff(),
x.dst_base.raw() + x.dst_stride * (i as isize),
);
outputs.push(off);
}
}
ProtoStatement::If(x) => {
if let Some(cond) = &x.cond {
cond.gather_variable_offsets_expanded(inputs);
}
for s in &x.true_side {
s.gather_variable_offsets_expanded(inputs, outputs);
}
for s in &x.false_side {
s.gather_variable_offsets_expanded(inputs, outputs);
}
}
ProtoStatement::SystemFunctionCall(x) => match x {
ProtoSystemFunctionCall::Display { args, .. }
| ProtoSystemFunctionCall::Write { args, .. } => {
for arg in args {
arg.gather_variable_offsets_expanded(inputs);
}
}
ProtoSystemFunctionCall::Readmemh { .. } => {}
ProtoSystemFunctionCall::Assert { condition, .. } => {
condition.gather_variable_offsets_expanded(inputs);
}
ProtoSystemFunctionCall::Finish => {}
},
ProtoStatement::CompiledBlock(x) => {
if !x.original_stmts.is_empty() {
for s in &x.original_stmts {
s.gather_variable_offsets_expanded(inputs, outputs);
}
} else if !x.stmt_deps.is_empty() {
for (ins, outs) in &x.stmt_deps {
for &off in ins {
inputs.push(off);
}
for &off in outs {
outputs.push(off);
}
}
} else {
for &off in &x.input_offsets {
inputs.push(off);
}
for &off in &x.output_offsets {
outputs.push(off);
}
}
}
ProtoStatement::For(x) => {
for s in &x.body {
s.gather_variable_offsets_expanded(inputs, outputs);
}
}
ProtoStatement::SequentialBlock(body) => {
for s in body {
s.gather_variable_offsets_expanded(inputs, outputs);
}
}
ProtoStatement::TbMethodCall { .. } => {}
ProtoStatement::Break => {}
}
}
pub fn gather_ff_canonical_offsets(&self) -> HashSet<isize> {
let mut result = HashSet::default();
match self {
ProtoStatement::Assign(x) => {
if x.dst.is_ff() {
result.insert(x.dst_ff_current_offset);
}
}
ProtoStatement::AssignDynamic(x) => {
if x.dst_base.is_ff() {
result.insert(x.dst_ff_current_base_offset);
if x.dst_num_elements > 1 {
let last = x.dst_ff_current_base_offset
+ x.dst_stride * (x.dst_num_elements as isize - 1);
result.insert(last);
}
}
}
ProtoStatement::If(x) => {
for s in &x.true_side {
result.extend(s.gather_ff_canonical_offsets());
}
for s in &x.false_side {
result.extend(s.gather_ff_canonical_offsets());
}
}
ProtoStatement::For(x) => {
for s in &x.body {
result.extend(s.gather_ff_canonical_offsets());
}
}
ProtoStatement::SystemFunctionCall(_) => {}
ProtoStatement::CompiledBlock(x) => {
for off in &x.ff_canonical_offsets {
result.insert(*off);
}
}
ProtoStatement::SequentialBlock(body) => {
for s in body {
result.extend(s.gather_ff_canonical_offsets());
}
}
ProtoStatement::TbMethodCall { .. } => {}
ProtoStatement::Break => {}
}
result
}
pub unsafe fn apply_values_ptr(
&self,
ff_values_ptr: *mut u8,
ff_len: usize,
comb_values_ptr: *mut u8,
comb_len: usize,
use_4state: bool,
) -> Statement {
unsafe {
match self {
ProtoStatement::Assign(x) => Statement::Assign(x.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)),
ProtoStatement::AssignDynamic(x) => {
let nb = calc_native_bytes_for(x.dst_width, x.dst_base.is_ff());
let dst_base_ptr = if x.dst_base.is_ff() {
ff_values_ptr.offset(x.dst_base.raw())
} else {
comb_values_ptr.offset(x.dst_base.raw())
};
let dst_index_expr = x.dst_index_expr.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
);
let expr = x.expr.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
);
let dynamic_select =
x.dynamic_select.as_ref().map(|dyn_sel| DynamicBitSelect {
index_expr: Box::new(dyn_sel.index_expr.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)),
elem_width: dyn_sel.elem_width,
num_elements: dyn_sel.num_elements,
});
Statement::AssignDynamic(AssignDynamicStatement {
dst_base_ptr,
dst_stride: x.dst_stride,
dst_num_elements: x.dst_num_elements,
dst_index_expr,
dst_width: x.dst_width,
dst_native_bytes: nb,
dst_use_4state: use_4state,
select: x.select,
dynamic_select,
rhs_select: x.rhs_select,
expr,
})
}
ProtoStatement::If(x) => Statement::If(x.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)),
ProtoStatement::SystemFunctionCall(x) => match x {
ProtoSystemFunctionCall::Display { format_str, args } => {
let args = args
.iter()
.map(|a| {
a.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)
})
.collect();
Statement::SystemFunctionCall(SystemFunctionCall::Display {
format_str: format_str.clone(),
args,
})
}
ProtoSystemFunctionCall::Readmemh {
filename,
elements,
width,
} => {
let is_ff = elements.first().is_some_and(|e| e.current.is_ff());
let nb = calc_native_bytes_for(*width, is_ff);
let resolved: Vec<_> = elements
.iter()
.map(|elem| {
let current = if elem.current.is_ff() {
ff_values_ptr.offset(elem.current.raw())
} else {
comb_values_ptr.offset(elem.current.raw())
};
let next = elem.next_offset.map(|off| ff_values_ptr.offset(off));
(current, next, nb, use_4state)
})
.collect();
Statement::SystemFunctionCall(SystemFunctionCall::Readmemh {
filename: filename.clone(),
elements: resolved,
width: *width,
})
}
ProtoSystemFunctionCall::Assert {
kind,
condition,
format_str,
args,
} => {
let condition = condition.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
);
let args: Vec<_> = args
.iter()
.map(|e| {
e.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)
})
.collect();
Statement::SystemFunctionCall(SystemFunctionCall::Assert {
kind: *kind,
condition,
format_str: format_str.clone(),
args,
})
}
ProtoSystemFunctionCall::Write { format_str, args } => {
let args = args
.iter()
.map(|a| {
a.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)
})
.collect();
Statement::SystemFunctionCall(SystemFunctionCall::Write {
format_str: format_str.clone(),
args,
})
}
ProtoSystemFunctionCall::Finish => {
Statement::SystemFunctionCall(SystemFunctionCall::Finish)
}
},
ProtoStatement::CompiledBlock(x) => {
let adjusted_ff =
(ff_values_ptr as *const u8).wrapping_offset(x.ff_delta_bytes);
let adjusted_comb =
(comb_values_ptr as *const u8).wrapping_offset(x.comb_delta_bytes);
Statement::Binary(x.func, adjusted_ff, adjusted_comb)
}
ProtoStatement::For(x) => {
let var_ptr = if x.var_offset.is_ff() {
ff_values_ptr.offset(x.var_offset.raw())
} else {
comb_values_ptr.offset(x.var_offset.raw())
};
let body = x
.body
.iter()
.map(|s| {
s.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)
})
.collect();
let convert_bound = |b: &ProtoForBound| -> RuntimeForBound {
match b {
ProtoForBound::Const(v) => RuntimeForBound::Const(*v),
ProtoForBound::Dynamic(proto_expr) => {
RuntimeForBound::Dynamic(Box::new(proto_expr.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)))
}
}
};
let range = match &x.range {
ProtoForRange::Forward {
start,
end,
inclusive,
step,
} => RuntimeForRange {
start: convert_bound(start),
end: convert_bound(end),
inclusive: *inclusive,
step: *step,
op: None,
reverse: false,
},
ProtoForRange::Reverse {
start,
end,
inclusive,
step,
} => RuntimeForRange {
start: convert_bound(start),
end: convert_bound(end),
inclusive: *inclusive,
step: *step,
op: None,
reverse: true,
},
ProtoForRange::Stepped {
start,
end,
inclusive,
step,
op,
} => RuntimeForRange {
start: convert_bound(start),
end: convert_bound(end),
inclusive: *inclusive,
step: *step,
op: Some(*op),
reverse: false,
},
};
Statement::For(ForStatement {
var_ptr,
var_native_bytes: x.var_native_bytes,
var_use_4state: use_4state,
var_width: x.var_width,
var_signed: x.var_signed,
range,
body,
})
}
ProtoStatement::SequentialBlock(body) => {
let stmts = body
.iter()
.map(|s| {
s.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)
})
.collect();
Statement::SequentialBlock(stmts)
}
ProtoStatement::Break => Statement::Break,
ProtoStatement::TbMethodCall { inst, method } => {
let method = match method {
ProtoTbMethodKind::ClockNext { count, period } => {
let count = count.as_ref().map(|e| {
e.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)
});
let period = period.as_ref().map(|e| {
e.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)
});
TbMethodKind::ClockNext { count, period }
}
ProtoTbMethodKind::ResetAssert { clock, duration } => {
let duration = duration.as_ref().map(|e| {
e.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)
});
TbMethodKind::ResetAssert {
clock: *clock,
duration,
}
}
};
Statement::TbMethodCall {
inst: *inst,
method,
}
}
}
}
}
#[cfg(not(target_family = "wasm"))]
pub fn build_binary(
&self,
context: &mut CraneliftContext,
builder: &mut FunctionBuilder,
is_last: bool,
) -> Option<()> {
match self {
ProtoStatement::Assign(x) => x.build_binary(context, builder),
ProtoStatement::AssignDynamic(x) => {
let result = x.build_binary(context, builder);
context.load_cache.clear();
result
}
ProtoStatement::If(x) => x.build_binary(context, builder, is_last),
ProtoStatement::For(x) => x.build_binary(context, builder, is_last),
ProtoStatement::SystemFunctionCall(_) => None,
ProtoStatement::CompiledBlock(_) => None,
ProtoStatement::SequentialBlock(body) => {
for (i, s) in body.iter().enumerate() {
s.build_binary(context, builder, is_last && i == body.len() - 1)?;
}
Some(())
}
ProtoStatement::TbMethodCall { .. } => None,
ProtoStatement::Break => None,
}
}
}
#[derive(Clone)]
pub struct AssignStatement {
pub dst: *mut u8,
pub dst_width: usize,
pub dst_native_bytes: usize,
pub dst_use_4state: bool,
pub select: Option<(usize, usize)>,
pub dynamic_select: Option<DynamicBitSelect>,
pub rhs_select: Option<(usize, usize)>,
pub expr: Expression,
}
impl AssignStatement {
pub fn eval_step(&self, mask_cache: &mut MaskCache) {
let value = self.expr.eval(mask_cache);
let value = if let Some((beg, end)) = self.rhs_select {
value.select(beg, end)
} else {
value
};
if let Some(dyn_sel) = &self.dynamic_select {
let idx = dyn_sel
.index_expr
.eval(mask_cache)
.to_usize()
.unwrap_or(0)
.min(dyn_sel.num_elements.saturating_sub(1));
let end = idx * dyn_sel.elem_width;
let beg = end + dyn_sel.elem_width - 1;
let mut current = unsafe {
read_native_value(
self.dst,
self.dst_native_bytes,
self.dst_use_4state,
self.dst_width as u32,
false,
)
};
current.assign(value, beg, end);
unsafe {
write_native_value(
self.dst,
self.dst_native_bytes,
self.dst_use_4state,
¤t,
);
}
} else if let Some((beg, end)) = self.select {
let mut current = unsafe {
read_native_value(
self.dst,
self.dst_native_bytes,
self.dst_use_4state,
self.dst_width as u32,
false,
)
};
current.assign(value, beg, end);
unsafe {
write_native_value(
self.dst,
self.dst_native_bytes,
self.dst_use_4state,
¤t,
);
}
} else {
let mut value = value;
value.trunc(self.dst_width);
unsafe {
write_native_value(self.dst, self.dst_native_bytes, self.dst_use_4state, &value);
}
}
}
pub fn gather_variable(&self, inputs: &mut Vec<*const u8>, outputs: &mut Vec<*const u8>) {
self.expr.gather_variable(inputs, outputs);
if let Some(dyn_sel) = &self.dynamic_select {
dyn_sel.index_expr.gather_variable(inputs, &mut vec![]);
}
outputs.push(self.dst);
}
}
impl AssignDynamicStatement {
pub fn eval_step(&self, mask_cache: &mut MaskCache) {
if self.dst_num_elements == 0 {
return;
}
let idx_val = self.dst_index_expr.eval(mask_cache);
let idx = idx_val
.to_usize()
.unwrap_or(0)
.min(self.dst_num_elements.saturating_sub(1));
let dst = unsafe { self.dst_base_ptr.offset(self.dst_stride * idx as isize) };
let value = self.expr.eval(mask_cache);
let value = if let Some((beg, end)) = self.rhs_select {
value.select(beg, end)
} else {
value
};
if let Some(dyn_sel) = &self.dynamic_select {
let dyn_idx = dyn_sel
.index_expr
.eval(mask_cache)
.to_usize()
.unwrap_or(0)
.min(dyn_sel.num_elements.saturating_sub(1));
let end = dyn_idx * dyn_sel.elem_width;
let beg = end + dyn_sel.elem_width - 1;
let mut current = unsafe {
read_native_value(
dst,
self.dst_native_bytes,
self.dst_use_4state,
self.dst_width as u32,
false,
)
};
current.assign(value, beg, end);
unsafe {
write_native_value(dst, self.dst_native_bytes, self.dst_use_4state, ¤t)
};
} else if let Some((beg, end)) = self.select {
let mut current = unsafe {
read_native_value(
dst,
self.dst_native_bytes,
self.dst_use_4state,
self.dst_width as u32,
false,
)
};
current.assign(value, beg, end);
unsafe {
write_native_value(dst, self.dst_native_bytes, self.dst_use_4state, ¤t)
};
} else {
let mut value = value;
value.trunc(self.dst_width);
unsafe { write_native_value(dst, self.dst_native_bytes, self.dst_use_4state, &value) };
}
}
pub fn gather_variable(&self, inputs: &mut Vec<*const u8>, outputs: &mut Vec<*const u8>) {
self.dst_index_expr.gather_variable(inputs, &mut vec![]);
self.expr.gather_variable(inputs, &mut vec![]);
if let Some(dyn_sel) = &self.dynamic_select {
dyn_sel.index_expr.gather_variable(inputs, &mut vec![]);
}
for i in 0..self.dst_num_elements {
let ptr =
unsafe { self.dst_base_ptr.offset(self.dst_stride * i as isize) as *const u8 };
outputs.push(ptr);
}
}
}
#[derive(Clone, Debug)]
pub struct ProtoAssignStatement {
pub dst: VarOffset,
pub dst_width: usize,
pub select: Option<(usize, usize)>,
pub dynamic_select: Option<ProtoDynamicBitSelect>,
pub rhs_select: Option<(usize, usize)>,
pub expr: ProtoExpression,
pub dst_ff_current_offset: isize,
pub token: TokenRange,
}
impl ProtoAssignStatement {
#[cfg(not(target_family = "wasm"))]
pub fn can_build_binary(&self) -> bool {
if !self.expr.can_build_binary() {
return false;
}
if let Some(dyn_sel) = &self.dynamic_select
&& (dyn_sel.elem_width * dyn_sel.num_elements > 128
|| !dyn_sel.index_expr.can_build_binary())
{
return false;
}
true
}
pub unsafe fn apply_values_ptr(
&self,
ff_values_ptr: *mut u8,
ff_len: usize,
comb_values_ptr: *mut u8,
comb_len: usize,
use_4state: bool,
) -> AssignStatement {
unsafe {
let nb = calc_native_bytes_for(self.dst_width, self.dst.is_ff());
let _vs = if use_4state { nb * 2 } else { nb };
let dst = if self.dst.is_ff() {
#[cfg(debug_assertions)]
debug_assert!(
(self.dst.raw() as usize) + _vs <= ff_len,
"apply_values_ptr Assign: ff dst {} + vs {} > ff_len {} (width={})",
self.dst.raw(),
_vs,
ff_len,
self.dst_width,
);
ff_values_ptr.add(self.dst.raw() as usize)
} else {
#[cfg(debug_assertions)]
debug_assert!(
(self.dst.raw() as usize) + _vs <= comb_len,
"apply_values_ptr Assign: comb dst {} + vs {} > comb_len {} (width={})",
self.dst.raw(),
_vs,
comb_len,
self.dst_width,
);
comb_values_ptr.add(self.dst.raw() as usize)
};
let expr = self.expr.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
);
let dynamic_select = self
.dynamic_select
.as_ref()
.map(|dyn_sel| DynamicBitSelect {
index_expr: Box::new(dyn_sel.index_expr.apply_values_ptr(
ff_values_ptr,
ff_len,
comb_values_ptr,
comb_len,
use_4state,
)),
elem_width: dyn_sel.elem_width,
num_elements: dyn_sel.num_elements,
});
AssignStatement {
dst,
dst_width: self.dst_width,
dst_native_bytes: nb,
dst_use_4state: use_4state,
select: self.select,
dynamic_select,
rhs_select: self.rhs_select,
expr,
}
}
}
#[cfg(not(target_family = "wasm"))]
pub fn build_binary(
&self,
context: &mut CraneliftContext,
builder: &mut FunctionBuilder,
) -> Option<()> {
if self.dst_width > 128 {
return self.build_binary_wide(context, builder);
}
let known_bits = self.expr.effective_bits();
let wide = self.dst_width > 64;
let (mut payload, mut mask_xz) = self.expr.build_binary(context, builder)?;
let nb = calc_native_bytes_for(self.dst_width, self.dst.is_ff());
let nb_i32 = nb as i32;
if wide && self.expr.width() <= 64 && builder.func.dfg.value_type(payload) != I128 {
payload = builder.ins().uextend(I128, payload);
if let Some(mxz) = mask_xz
&& builder.func.dfg.value_type(mxz) != I128
{
mask_xz = Some(builder.ins().uextend(I128, mxz));
}
}
let known_bits = if let Some((beg, end)) = self.rhs_select {
beg - end + 1
} else {
known_bits
};
if let Some((beg, end)) = self.rhs_select {
let select_width = beg - end + 1;
let mask = gen_mask_for_width(select_width);
payload = builder.ins().ushr_imm(payload, end as i64);
payload = band_const(builder, payload, mask, wide);
if let Some(mxz) = mask_xz {
let mxz = builder.ins().ushr_imm(mxz, end as i64);
let mxz = band_const(builder, mxz, mask, wide);
mask_xz = Some(mxz);
}
}
let load_mem_flag = MemFlags::trusted();
let store_mem_flag = MemFlags::trusted();
let base_addr = if self.dst.is_ff() {
context.ff_values
} else {
context.comb_values
};
let dst_offset = self.dst.raw() as i32;
let cache_key = self.dst;
let load_native_to_native = |builder: &mut FunctionBuilder, off: i32| match nb {
1 => builder.ins().uload8(I64, load_mem_flag, base_addr, off),
2 => builder.ins().uload16(I64, load_mem_flag, base_addr, off),
4 => builder.ins().uload32(load_mem_flag, base_addr, off),
8 => builder.ins().load(I64, load_mem_flag, base_addr, off),
_ => builder.ins().load(I128, load_mem_flag, base_addr, off),
};
let store_native_to_native =
|builder: &mut FunctionBuilder, v: cranelift::prelude::Value, off: i32| match nb {
1 => {
builder.ins().istore8(store_mem_flag, v, base_addr, off);
}
2 => {
builder.ins().istore16(store_mem_flag, v, base_addr, off);
}
4 => {
builder.ins().istore32(store_mem_flag, v, base_addr, off);
}
_ => {
builder.ins().store(store_mem_flag, v, base_addr, off);
}
};
if let Some(dyn_sel) = &self.dynamic_select {
let shift = build_dynamic_select_shift(dyn_sel, context, builder)?;
let payload = builder.ins().ishl(payload, shift);
let elem_mask = gen_mask_for_width(dyn_sel.elem_width);
let mask_val = if wide {
iconst_128(builder, elem_mask)
} else {
builder.ins().iconst(I64, elem_mask as i64)
};
let dyn_mask = builder.ins().ishl(mask_val, shift);
let not_mask = builder.ins().bnot(dyn_mask);
let (org_payload, org_mask_xz) = if !context.disable_load_cache
&& let Some(&(cached_p, cached_m)) = context.load_cache.get(&cache_key)
{
(cached_p, cached_m)
} else {
let p = load_native_to_native(builder, dst_offset);
let m = if context.use_4state {
Some(load_native_to_native(builder, dst_offset + nb_i32))
} else {
None
};
(p, m)
};
let org = builder.ins().band(org_payload, not_mask);
let result = builder.ins().bor(payload, org);
store_native_to_native(builder, result, dst_offset);
let result_mask_xz = if let Some(mask_xz) = mask_xz {
let mask_xz = builder.ins().ishl(mask_xz, shift);
let z = if wide { context.zero_128 } else { context.zero };
let org_m = org_mask_xz.unwrap_or(z);
let org_m = builder.ins().band(org_m, not_mask);
let result_m = builder.ins().bor(mask_xz, org_m);
store_native_to_native(builder, result_m, dst_offset + nb_i32);
Some(result_m)
} else {
None
};
let fwd = if self.dst_width < 64 {
let m = gen_mask_for_width(self.dst_width);
band_const(builder, result, m, false)
} else {
result
};
let fwd_m = if self.dst_width < 64 {
result_mask_xz.map(|v| {
let m = gen_mask_for_width(self.dst_width);
band_const(builder, v, m, false)
})
} else {
result_mask_xz
};
context.load_cache.insert(cache_key, (fwd, fwd_m));
} else if let Some((beg, end)) = self.select {
let payload = builder.ins().ishl_imm(payload, end as i64);
let (org_payload, org_mask_xz) = if !context.disable_load_cache
&& let Some(&(cached_p, cached_m)) = context.load_cache.get(&cache_key)
{
(cached_p, cached_m)
} else {
let p = load_native_to_native(builder, dst_offset);
let m = if context.use_4state {
Some(load_native_to_native(builder, dst_offset + nb_i32))
} else {
None
};
(p, m)
};
let not_mask = if wide {
let mask = gen_mask_range_128(beg, end);
iconst_128(builder, !mask)
} else {
let mask = ValueU64::gen_mask_range(beg, end);
builder.ins().iconst(I64, !mask as i64)
};
let org = builder.ins().band(org_payload, not_mask);
let result = builder.ins().bor(payload, org);
store_native_to_native(builder, result, dst_offset);
let result_mask_xz = if let Some(mask_xz) = mask_xz {
let mask_xz = builder.ins().ishl_imm(mask_xz, end as i64);
let z = if wide { context.zero_128 } else { context.zero };
let org_m = org_mask_xz.unwrap_or(z);
let org_m = builder.ins().band(org_m, not_mask);
let result_m = builder.ins().bor(mask_xz, org_m);
store_native_to_native(builder, result_m, dst_offset + nb_i32);
Some(result_m)
} else {
None
};
let fwd = if self.dst_width < 64 {
let m = gen_mask_for_width(self.dst_width);
band_const(builder, result, m, false)
} else {
result
};
let fwd_m = if self.dst_width < 64 {
result_mask_xz.map(|v| {
let m = gen_mask_for_width(self.dst_width);
band_const(builder, v, m, false)
})
} else {
result_mask_xz
};
context.load_cache.insert(cache_key, (fwd, fwd_m));
} else {
let skip_store = context.store_elim_enabled
&& !context.disable_load_cache
&& context.store_elim_offsets.contains(&cache_key);
let post_rhs_clean_bits = if let Some((beg, end)) = self.rhs_select {
Some(beg - end + 1)
} else if self.expr.is_clean_to_width(self.dst_width) {
Some(self.dst_width)
} else {
None
};
let skip_writer_mask = post_rhs_clean_bits
.map(|w| w <= self.dst_width)
.unwrap_or(false);
if !skip_store {
let needs_trunc = known_bits > self.dst_width;
match self.dst_width {
8 => {
builder
.ins()
.istore8(store_mem_flag, payload, base_addr, dst_offset);
if let Some(mask_xz) = mask_xz {
builder.ins().istore8(
store_mem_flag,
mask_xz,
base_addr,
dst_offset + nb_i32,
);
}
}
16 => {
builder
.ins()
.istore16(store_mem_flag, payload, base_addr, dst_offset);
if let Some(mask_xz) = mask_xz {
builder.ins().istore16(
store_mem_flag,
mask_xz,
base_addr,
dst_offset + nb_i32,
);
}
}
32 => {
builder
.ins()
.istore32(store_mem_flag, payload, base_addr, dst_offset);
if let Some(mask_xz) = mask_xz {
builder.ins().istore32(
store_mem_flag,
mask_xz,
base_addr,
dst_offset + nb_i32,
);
}
}
64 => {
builder
.ins()
.store(store_mem_flag, payload, base_addr, dst_offset);
if let Some(mask_xz) = mask_xz {
builder.ins().store(
store_mem_flag,
mask_xz,
base_addr,
dst_offset + nb_i32,
);
}
}
128 => {
builder
.ins()
.store(store_mem_flag, payload, base_addr, dst_offset);
if let Some(mask_xz) = mask_xz {
builder.ins().store(
store_mem_flag,
mask_xz,
base_addr,
dst_offset + nb_i32,
);
}
}
_ => {
if self.dst_width > 128 {
return None;
}
let _ = needs_trunc;
let mask = gen_mask_for_width(self.dst_width);
let payload = if skip_writer_mask {
payload
} else {
band_const(builder, payload, mask, wide)
};
store_native_to_native(builder, payload, dst_offset);
if let Some(mask_xz) = mask_xz {
let mask = gen_mask_for_width(self.dst_width);
let mask_xz = if skip_writer_mask {
mask_xz
} else {
band_const(builder, mask_xz, mask, wide)
};
store_native_to_native(builder, mask_xz, dst_offset + nb_i32);
}
}
}
}
let fwd_p = if self.dst_width < 64 && !skip_writer_mask {
let m = gen_mask_for_width(self.dst_width);
band_const(builder, payload, m, false)
} else {
payload
};
let fwd_m = if self.dst_width < 64 && !skip_writer_mask {
mask_xz.map(|v| {
let m = gen_mask_for_width(self.dst_width);
band_const(builder, v, m, false)
})
} else {
mask_xz
};
context.load_cache.insert(cache_key, (fwd_p, fwd_m));
}
Some(())
}
#[cfg(not(target_family = "wasm"))]
fn build_binary_wide(
&self,
context: &mut CraneliftContext,
builder: &mut FunctionBuilder,
) -> Option<()> {
use crate::ir::expression::{emit_wide_apply_mask, is_wide_ptr};
if self.select.is_some() || self.rhs_select.is_some() {
return None;
}
let expr_width = self.expr.width();
let (payload, mask_xz) = self.expr.build_binary(context, builder)?;
let nb = calc_native_bytes_for(self.dst_width, self.dst.is_ff());
let n_words = nb / 8;
let flags = MemFlags::trusted();
let base_addr = if self.dst.is_ff() {
context.ff_values
} else {
context.comb_values
};
let dst_offset = self.dst.raw() as i32;
let src_ptr = if is_wide_ptr(expr_width) {
payload
} else {
use crate::ir::expression::ensure_wide_ptr_val;
ensure_wide_ptr_val(builder, payload, expr_width, nb)
};
emit_wide_apply_mask(context, builder, src_ptr, nb, self.dst_width);
for i in 0..n_words {
let off = (i * 8) as i32;
let val = builder.ins().load(I64, flags, src_ptr, off);
builder.ins().store(flags, val, base_addr, dst_offset + off);
}
if let Some(mask_xz) = mask_xz {
let mask_ptr = if is_wide_ptr(self.expr.width()) {
mask_xz
} else {
use crate::ir::expression::ensure_wide_ptr_val;
ensure_wide_ptr_val(builder, mask_xz, self.expr.width(), nb)
};
emit_wide_apply_mask(context, builder, mask_ptr, nb, self.dst_width);
for i in 0..n_words {
let off = (i * 8) as i32;
let val = builder.ins().load(I64, flags, mask_ptr, off);
builder
.ins()
.store(flags, val, base_addr, dst_offset + nb as i32 + off);
}
}
Some(())
}
}
#[derive(Clone)]
pub struct IfStatement {
pub cond: Option<Expression>,
pub true_side: Vec<Statement>,
pub false_side: Vec<Statement>,
}
impl IfStatement {
pub fn eval_step(&self, mask_cache: &mut MaskCache) -> ControlFlow {
let cond = if let Some(x) = &self.cond {
let cond = x.eval(mask_cache);
match &cond {
Value::U64(x) => (x.payload & !x.mask_xz) != 0,
Value::BigUint(x) => {
use veryl_analyzer::value::biguint_to_u128;
let payload = biguint_to_u128(&x.payload);
let mask_xz = biguint_to_u128(&x.mask_xz);
(payload & !mask_xz) != 0
}
}
} else {
false
};
if cond {
for x in &self.true_side {
if x.eval_step(mask_cache) == ControlFlow::Break {
return ControlFlow::Break;
}
}
} else {
for x in &self.false_side {
if x.eval_step(mask_cache) == ControlFlow::Break {
return ControlFlow::Break;
}
}
}
ControlFlow::Continue
}
pub fn gather_variable(&self, inputs: &mut Vec<*const u8>, outputs: &mut Vec<*const u8>) {
if let Some(x) = &self.cond {
x.gather_variable(inputs, outputs);
}
for x in &self.true_side {
x.gather_variable(inputs, outputs);
}
for x in &self.false_side {
x.gather_variable(inputs, outputs);
}
}
}
#[derive(Clone, Debug)]
pub struct ProtoIfStatement {
pub cond: Option<ProtoExpression>,
pub true_side: Vec<ProtoStatement>,
pub false_side: Vec<ProtoStatement>,
}
impl ProtoIfStatement {
#[cfg(not(target_family = "wasm"))]
pub fn can_build_binary(&self) -> bool {
if let Some(cond) = &self.cond
&& !cond.can_build_binary()
{
return false;
}
self.true_side.iter().all(|s| s.can_build_binary())
&& self.false_side.iter().all(|s| s.can_build_binary())
}
pub unsafe fn apply_values_ptr(
&self,
ff_values_ptr: *mut u8,
ff_len: usize,
comb_values_ptr: *mut u8,
comb_len: usize,
use_4state: bool,
) -> IfStatement {
unsafe {
let cond = self.cond.as_ref().map(|x| {
x.apply_values_ptr(ff_values_ptr, ff_len, comb_values_ptr, comb_len, use_4state)
});
let true_side: Vec<_> = self
.true_side
.iter()
.map(|x| {
x.apply_values_ptr(ff_values_ptr, ff_len, comb_values_ptr, comb_len, use_4state)
})
.collect();
let false_side: Vec<_> = self
.false_side
.iter()
.map(|x| {
x.apply_values_ptr(ff_values_ptr, ff_len, comb_values_ptr, comb_len, use_4state)
})
.collect();
IfStatement {
cond,
true_side,
false_side,
}
}
}
#[cfg(not(target_family = "wasm"))]
pub fn build_binary(
&self,
context: &mut CraneliftContext,
builder: &mut FunctionBuilder,
is_last: bool,
) -> Option<()> {
if std::env::var("VERYL_SWITCH_LOWER_DISABLE").ok().as_deref() != Some("1")
&& let Some(chain) = collect_eq_chain(self)
&& chain.arms.len() >= 4
&& let Some(()) = emit_switch_via_br_table(&chain, context, builder, is_last)
{
return Some(());
}
let true_block = builder.create_block();
let false_block = builder.create_block();
let final_block = builder.create_block();
if let Some(x) = &self.cond {
let (cond_payload, cond_mask_xz) = x.build_binary(context, builder)?;
let effective_cond = if let Some(mask_xz) = cond_mask_xz {
builder.ins().band_not(cond_payload, mask_xz)
} else {
cond_payload
};
builder
.ins()
.brif(effective_cond, true_block, &[], false_block, &[]);
}
context.load_cache.clear();
let prev_store_elim = context.store_elim_enabled;
context.store_elim_enabled = false;
builder.switch_to_block(true_block);
let len = self.true_side.len();
for (i, x) in self.true_side.iter().enumerate() {
let is_last = is_last && (i + 1 == len);
x.build_binary(context, builder, is_last)?;
}
if is_last {
builder.ins().return_(&[]);
} else {
builder.ins().jump(final_block, &[]);
}
context.load_cache.clear();
builder.switch_to_block(false_block);
let len = self.false_side.len();
for (i, x) in self.false_side.iter().enumerate() {
let is_last = is_last && (i + 1 == len);
x.build_binary(context, builder, is_last)?;
}
if is_last {
builder.ins().return_(&[]);
} else {
builder.ins().jump(final_block, &[]);
}
builder.switch_to_block(final_block);
context.load_cache.clear();
context.store_elim_enabled = prev_store_elim;
Some(())
}
}
#[cfg(not(target_family = "wasm"))]
struct EqChainArm<'a> {
value: u64,
body: &'a [ProtoStatement],
}
#[cfg(not(target_family = "wasm"))]
struct EqChain<'a> {
selector: &'a ProtoExpression,
arms: Vec<EqChainArm<'a>>,
default: &'a [ProtoStatement],
}
#[cfg(not(target_family = "wasm"))]
fn extract_eq_const(cond: &ProtoExpression) -> Option<(&ProtoExpression, u64)> {
let (x, op, y) = match cond {
ProtoExpression::Binary { x, op, y, .. } => (x.as_ref(), *op, y.as_ref()),
_ => return None,
};
if !matches!(
op,
veryl_analyzer::ir::Op::Eq | veryl_analyzer::ir::Op::EqWildcard
) {
return None;
}
fn try_extract<'b>(
val_side: &'b ProtoExpression,
var_side: &'b ProtoExpression,
) -> Option<(&'b ProtoExpression, u64)> {
match val_side {
ProtoExpression::Value { value, .. } => {
if value.is_xz() {
None
} else {
value.to_u64().map(|v| (var_side, v))
}
}
_ => None,
}
}
if let Some(r) = try_extract(y, x) {
return Some(r);
}
try_extract(x, y)
}
#[cfg(not(target_family = "wasm"))]
fn same_var_read(a: &ProtoExpression, b: &ProtoExpression) -> bool {
match (a, b) {
(
ProtoExpression::Variable {
var_offset: oa,
select: sa,
dynamic_select: dsa,
..
},
ProtoExpression::Variable {
var_offset: ob,
select: sb,
dynamic_select: dsb,
..
},
) => oa == ob && sa == sb && dsa.is_none() && dsb.is_none(),
_ => false,
}
}
#[cfg(not(target_family = "wasm"))]
fn collect_eq_chain(start: &ProtoIfStatement) -> Option<EqChain<'_>> {
let mut arms: Vec<EqChainArm<'_>> = Vec::new();
let mut selector: Option<&ProtoExpression> = None;
let mut current = start;
loop {
let cond = current.cond.as_ref()?;
let (var_expr, const_val) = match extract_eq_const(cond) {
Some(p) => p,
None => break,
};
if let ProtoExpression::Variable {
dynamic_select: Some(_),
..
} = var_expr
{
break;
}
if let Some(sel) = selector {
if !same_var_read(sel, var_expr) {
break;
}
} else {
selector = Some(var_expr);
}
arms.push(EqChainArm {
value: const_val,
body: ¤t.true_side,
});
if current.false_side.len() == 1
&& let ProtoStatement::If(next) = ¤t.false_side[0]
{
current = next;
continue;
}
break;
}
if arms.len() < 2 {
return None;
}
Some(EqChain {
selector: selector?,
arms,
default: ¤t.false_side[..],
})
}
#[cfg(not(target_family = "wasm"))]
fn emit_switch_via_br_table(
chain: &EqChain,
context: &mut CraneliftContext,
builder: &mut FunctionBuilder,
is_last: bool,
) -> Option<()> {
use cranelift::codegen::ir::JumpTableData;
const SWITCH_LOWER_LIMIT: u64 = 256;
let max = chain.arms.iter().map(|a| a.value).max()?;
if max >= SWITCH_LOWER_LIMIT {
return None;
}
let (sel_payload, sel_mask_xz) = chain.selector.build_binary(context, builder)?;
let sel_clean = if let Some(mask) = sel_mask_xz {
builder.ins().band_not(sel_payload, mask)
} else {
sel_payload
};
let arm_blocks: Vec<_> = chain.arms.iter().map(|_| builder.create_block()).collect();
let default_block = builder.create_block();
let final_block = builder.create_block();
let table_size = (max as usize) + 1;
let mut entries: Vec<_> = (0..table_size)
.map(|_| builder.func.dfg.block_call(default_block, &[]))
.collect();
for (i, arm) in chain.arms.iter().enumerate() {
entries[arm.value as usize] = builder.func.dfg.block_call(arm_blocks[i], &[]);
}
let default_call = builder.func.dfg.block_call(default_block, &[]);
let jt_data = JumpTableData::new(default_call, &entries);
let jt = builder.func.create_jump_table(jt_data);
builder.ins().br_table(sel_clean, jt);
let prev_store_elim = context.store_elim_enabled;
context.store_elim_enabled = false;
for (i, arm) in chain.arms.iter().enumerate() {
builder.switch_to_block(arm_blocks[i]);
context.load_cache.clear();
let len = arm.body.len();
for (j, s) in arm.body.iter().enumerate() {
let last = is_last && (j + 1 == len);
s.build_binary(context, builder, last)?;
}
if is_last {
builder.ins().return_(&[]);
} else {
builder.ins().jump(final_block, &[]);
}
}
builder.switch_to_block(default_block);
context.load_cache.clear();
let len = chain.default.len();
for (j, s) in chain.default.iter().enumerate() {
let last = is_last && (j + 1 == len);
s.build_binary(context, builder, last)?;
}
if is_last {
builder.ins().return_(&[]);
} else {
builder.ins().jump(final_block, &[]);
}
builder.switch_to_block(final_block);
context.load_cache.clear();
context.store_elim_enabled = prev_store_elim;
Some(())
}
fn extract_display_args(
context: &mut ConvContext,
inputs: &[SystemFunctionInput],
) -> Option<(String, Vec<ProtoExpression>)> {
let mut format_str = String::new();
let mut exprs = Vec::new();
let mut iter = inputs.iter();
if let Some(first) = iter.next() {
if is_string_literal(&first.0) {
format_str = extract_string_value(&first.0)?;
} else {
let proto: ProtoExpression = Conv::conv(context, &first.0).ok()?;
exprs.push(proto);
}
}
for input in iter {
let proto: ProtoExpression = Conv::conv(context, &input.0).ok()?;
exprs.push(proto);
}
Some((format_str, exprs))
}
fn factor_comptime(factor: &air::Factor) -> Option<&veryl_analyzer::ir::Comptime> {
match factor {
air::Factor::Value(x) => Some(x),
air::Factor::Variable(_, _, _, x) if x.is_const && x.evaluated => Some(x),
_ => None,
}
}
fn is_string_literal(expr: &air::Expression) -> bool {
if let air::Expression::Term(factor) = expr
&& let Some(comptime) = factor_comptime(factor.as_ref())
{
return comptime.r#type.kind == TypeKind::String;
}
false
}
fn extract_string_value(expr: &air::Expression) -> Option<String> {
if let air::Expression::Term(factor) = expr
&& let Some(comptime) = factor_comptime(factor.as_ref())
&& let ValueVariant::Numeric(value) = &comptime.value
{
return veryl_analyzer::value::byte_value_to_string(value);
}
None
}
impl Conv<&air::Statement> for Vec<ProtoStatement> {
fn conv(context: &mut ConvContext, src: &air::Statement) -> Result<Self, SimulatorError> {
let mut result = match src {
air::Statement::Assign(x) => Conv::conv(context, x)?,
air::Statement::FunctionCall(x) => Conv::conv(context, x.as_ref())?,
air::Statement::If(x) => {
let x: ProtoIfStatement = Conv::conv(context, x)?;
vec![ProtoStatement::If(x)]
}
air::Statement::IfReset(x) => {
let x: ProtoIfStatement = Conv::conv(context, x)?;
vec![ProtoStatement::If(x)]
}
air::Statement::SystemFunctionCall(x) => match &x.kind {
SystemFunctionKind::Display(inputs) => {
let (format_str, exprs) = extract_display_args(context, inputs).unwrap();
vec![ProtoStatement::SystemFunctionCall(
ProtoSystemFunctionCall::Display {
format_str,
args: exprs,
},
)]
}
SystemFunctionKind::Write(inputs) => {
let (format_str, exprs) = extract_display_args(context, inputs).unwrap();
vec![ProtoStatement::SystemFunctionCall(
ProtoSystemFunctionCall::Write {
format_str,
args: exprs,
},
)]
}
SystemFunctionKind::Readmemh(input, output) => {
let raw = extract_string_value(&input.0).unwrap();
let filename = raw.trim_matches('"').to_string();
let dst = &output.0[0];
let id = dst.id;
let scope = context.scope();
let meta = scope.variable_meta.get(&id).unwrap();
let width = meta.width;
let elements: Vec<ReadmemhElement> = meta
.elements
.iter()
.map(|elem| ReadmemhElement {
current: elem.current,
next_offset: if elem.is_ff() {
Some(elem.next_offset)
} else {
None
},
})
.collect();
vec![ProtoStatement::SystemFunctionCall(
ProtoSystemFunctionCall::Readmemh {
filename,
elements,
width,
},
)]
}
SystemFunctionKind::Assert { kind, cond, args } => {
let condition: ProtoExpression = Conv::conv(context, &cond.0)?;
let (format_str, exprs) = extract_display_args(context, args).unwrap();
vec![ProtoStatement::SystemFunctionCall(
ProtoSystemFunctionCall::Assert {
kind: *kind,
condition,
format_str,
args: exprs,
},
)]
}
SystemFunctionKind::Finish => {
vec![ProtoStatement::SystemFunctionCall(
ProtoSystemFunctionCall::Finish,
)]
}
_ => {
return Err(SimulatorError::unsupported_description(&x.comptime.token));
}
},
air::Statement::TbMethodCall(x) => {
let method = match &x.method {
air::TbMethod::ClockNext { count, period } => {
let count = if let Some(expr) = count {
Some(Conv::conv(context, expr)?)
} else {
None
};
let period = if let Some(expr) = period {
Some(Conv::conv(context, expr)?)
} else {
None
};
ProtoTbMethodKind::ClockNext { count, period }
}
air::TbMethod::ResetAssert { clock, duration } => {
let duration = if let Some(expr) = duration {
Some(Conv::conv(context, expr)?)
} else {
None
};
ProtoTbMethodKind::ResetAssert {
clock: *clock,
duration,
}
}
};
vec![ProtoStatement::TbMethodCall {
inst: x.inst,
method,
}]
}
air::Statement::For(x) => {
let scope = context.scope();
let meta = scope
.variable_meta
.get(&x.var_id)
.ok_or_else(|| SimulatorError::unsupported_description(&x.token))?;
let var_offset = meta.elements[0].current;
let var_width = meta.width;
let var_native_bytes = meta.native_bytes;
let var_signed = x.var_type.signed;
let mut body = vec![];
for stmt in &x.body {
let stmts: Vec<ProtoStatement> = Conv::conv(context, stmt)?;
body.extend(stmts);
}
let token = x.token;
let resolve_bound = |b: &air::ForBound,
ctx: &mut ConvContext|
-> Result<ProtoForBound, SimulatorError> {
match b {
air::ForBound::Const(v) => Ok(ProtoForBound::Const(*v as u64)),
air::ForBound::Expression(expr) => {
let proto_expr: ProtoExpression = Conv::conv(ctx, expr.as_ref())
.map_err(|_| SimulatorError::unsupported_description(&token))?;
Ok(ProtoForBound::Dynamic(proto_expr))
}
}
};
let range = match &x.range {
air::ForRange::Forward {
start,
end,
inclusive,
step,
} => ProtoForRange::Forward {
start: resolve_bound(start, context)?,
end: resolve_bound(end, context)?,
inclusive: *inclusive,
step: *step as u64,
},
air::ForRange::Reverse {
start,
end,
inclusive,
step,
} => ProtoForRange::Reverse {
start: resolve_bound(start, context)?,
end: resolve_bound(end, context)?,
inclusive: *inclusive,
step: *step as u64,
},
air::ForRange::Stepped {
start,
end,
inclusive,
step,
op,
} => ProtoForRange::Stepped {
start: resolve_bound(start, context)?,
end: resolve_bound(end, context)?,
inclusive: *inclusive,
step: *step as u64,
op: *op,
},
};
vec![ProtoStatement::For(ProtoForStatement {
var_offset,
var_width,
var_native_bytes,
var_signed,
range,
body,
})]
}
air::Statement::Unsupported(token) => {
return Err(SimulatorError::unsupported_description(token));
}
air::Statement::Break => vec![ProtoStatement::Break],
air::Statement::Null => vec![],
};
let mut pending = std::mem::take(&mut context.pending_statements);
if !pending.is_empty() {
pending.append(&mut result);
result = pending;
}
Ok(result)
}
}
impl Conv<&air::AssignStatement> for Vec<ProtoStatement> {
fn conv(context: &mut ConvContext, src: &air::AssignStatement) -> Result<Self, SimulatorError> {
let in_initial = context.in_initial;
if matches!(src.expr, air::Expression::ArrayLiteral(..)) {
let dst = &src.dst[0];
let scope = context.scope();
let meta = scope.variable_meta.get(&dst.id).unwrap();
let dst_type = meta.r#type.clone();
let mut expr_clone = src.expr.clone();
let array_exprs = eval_array_literal(
&mut scope.analyzer_context,
Some(&dst_type.array),
Some(dst_type.width()),
&mut expr_clone,
)
.unwrap()
.unwrap();
let mut result = Vec::new();
for array_expr in array_exprs {
let index = array_expr.to_var_index();
let select = array_expr.to_var_select();
let mut new_dst = dst.clone();
new_dst.index.append(&index);
new_dst.select = select;
let element_assign = air::AssignStatement {
dst: vec![new_dst],
width: src.width,
expr: array_expr.expr,
token: src.token,
};
let proto: ProtoAssignStatement = Conv::conv(context, &element_assign)?;
result.push(ProtoStatement::Assign(proto));
}
if in_initial {
append_ff_next_copies(&mut result);
}
return Ok(result);
}
if src.dst.len() <= 1 {
let stmt: ProtoStatement = Conv::conv(context, src)?;
let mut result = vec![stmt];
if in_initial {
append_ff_next_copies(&mut result);
}
return Ok(result);
}
let expr: ProtoExpression = Conv::conv(context, &src.expr)?;
let mut result = Vec::new();
let mut remaining = src.width.unwrap();
for dst in &src.dst {
let scope = context.scope();
let id = dst.id;
let meta = scope.variable_meta.get(&id).unwrap();
let select = if !dst.select.is_empty() {
dst.select
.eval_value(&mut scope.analyzer_context, &dst.comptime.r#type, false)
} else {
None
};
let dst_elem_width = if let Some((beg, end)) = select {
beg - end + 1
} else {
dst.comptime.r#type.total_width().unwrap()
};
let rhs_select = Some((remaining - 1, remaining - dst_elem_width));
remaining -= dst_elem_width;
let const_index = if dst.index.is_const() {
dst.index.eval_value(&mut scope.analyzer_context)
} else {
None
};
if let Some(idx_vals) = const_index {
let index = meta.r#type.array.calc_index(&idx_vals).unwrap();
let element = &meta.elements[index];
let is_ff = element.is_ff();
let dst_width = meta.width;
let dst_var = if is_ff {
if in_initial {
VarOffset::Ff(element.current_offset())
} else {
VarOffset::Ff(element.next_offset)
}
} else {
VarOffset::Comb(element.current_offset())
};
result.push(ProtoStatement::Assign(ProtoAssignStatement {
dst: dst_var,
dst_width,
select,
dynamic_select: None,
rhs_select,
expr: expr.clone(),
dst_ff_current_offset: element.current_offset(),
token: src.token,
}));
} else {
let array_shape = meta.r#type.array.clone();
let dyn_info = meta.dynamic_index_info().unwrap();
let num_elements = meta.elements.len();
let (base_current, base_next, stride, is_ff) = dyn_info;
let dst_base = if is_ff {
if in_initial {
VarOffset::Ff(base_current)
} else {
VarOffset::Ff(base_next)
}
} else {
VarOffset::Comb(base_current)
};
let dst_width = meta.width;
let index_proto = build_linear_index_expr(context, &array_shape, &dst.index)?;
result.push(ProtoStatement::AssignDynamic(ProtoAssignDynamicStatement {
dst_base,
dst_stride: stride,
dst_num_elements: num_elements,
dst_index_expr: index_proto,
dst_width,
select,
dynamic_select: None,
rhs_select,
expr: expr.clone(),
dst_ff_current_base_offset: base_current,
}));
}
}
if in_initial {
append_ff_next_copies(&mut result);
}
Ok(result)
}
}
fn append_ff_next_copies(stmts: &mut Vec<ProtoStatement>) {
let mut extras = Vec::new();
for s in stmts.iter() {
match s {
ProtoStatement::Assign(a) if a.dst.is_ff() => {
let nb = calc_native_bytes_for(a.dst_width, true) as isize;
let next_offset = a.dst_ff_current_offset + nb;
extras.push(ProtoStatement::Assign(ProtoAssignStatement {
dst: VarOffset::Ff(next_offset),
..a.clone()
}));
}
ProtoStatement::AssignDynamic(a) if a.dst_base.is_ff() => {
let nb = calc_native_bytes_for(a.dst_width, true) as isize;
let next_base = a.dst_ff_current_base_offset + nb;
extras.push(ProtoStatement::AssignDynamic(ProtoAssignDynamicStatement {
dst_base: VarOffset::Ff(next_base),
..a.clone()
}));
}
_ => {}
}
}
stmts.extend(extras);
}
impl Conv<&air::AssignStatement> for ProtoStatement {
fn conv(context: &mut ConvContext, src: &air::AssignStatement) -> Result<Self, SimulatorError> {
let dst = &src.dst[0];
let id = dst.id;
let in_initial = context.in_initial;
let (select, dst_width, const_index, need_dynamic_select, width_shape, kind_width) = {
let scope = context.scope();
let meta = scope.variable_meta.get(&id).unwrap();
let select = if !dst.select.is_empty() {
dst.select
.eval_value(&mut scope.analyzer_context, &dst.comptime.r#type, false)
} else {
None
};
let dst_width = meta.width;
let const_index = if dst.index.is_const() {
dst.index.eval_value(&mut scope.analyzer_context)
} else {
None
};
let need_dynamic = !dst.select.is_empty() && !dst.select.is_const();
let select = if need_dynamic { None } else { select };
let width_shape = meta.r#type.width().clone();
let kind_width = meta.r#type.kind.width().unwrap_or(1);
(
select,
dst_width,
const_index,
need_dynamic,
width_shape,
kind_width,
)
};
let dynamic_select = if need_dynamic_select {
Some(build_dynamic_bit_select(
context,
&width_shape,
&dst.select,
kind_width,
)?)
} else {
None
};
if let Some(idx_vals) = const_index {
let scope = context.scope();
let meta = scope.variable_meta.get(&id).unwrap();
let index = meta.r#type.array.calc_index(&idx_vals).unwrap();
let element = &meta.elements[index];
let is_ff = element.is_ff();
let current_offset = element.current_offset();
let dst = if is_ff {
if in_initial {
VarOffset::Ff(current_offset)
} else {
VarOffset::Ff(element.next_offset)
}
} else {
VarOffset::Comb(current_offset)
};
let expr: ProtoExpression = Conv::conv(context, &src.expr)?;
Ok(ProtoStatement::Assign(ProtoAssignStatement {
dst,
dst_width,
select,
dynamic_select,
rhs_select: None,
expr,
dst_ff_current_offset: current_offset,
token: src.token,
}))
} else {
let scope = context.scope();
let meta = scope.variable_meta.get(&id).unwrap();
let array_shape = meta.r#type.array.clone();
let dyn_info = meta.dynamic_index_info().unwrap();
let num_elements = meta.elements.len();
let (base_current, base_next, stride, is_ff) = dyn_info;
let dst_base = if is_ff {
if in_initial {
VarOffset::Ff(base_current)
} else {
VarOffset::Ff(base_next)
}
} else {
VarOffset::Comb(base_current)
};
let index_proto = build_linear_index_expr(context, &array_shape, &dst.index)?;
let expr: ProtoExpression = Conv::conv(context, &src.expr)?;
Ok(ProtoStatement::AssignDynamic(ProtoAssignDynamicStatement {
dst_base,
dst_stride: stride,
dst_num_elements: num_elements,
dst_index_expr: index_proto,
dst_width,
select,
dynamic_select,
rhs_select: None,
expr,
dst_ff_current_base_offset: base_current,
}))
}
}
}
impl Conv<&air::AssignStatement> for ProtoAssignStatement {
fn conv(context: &mut ConvContext, src: &air::AssignStatement) -> Result<Self, SimulatorError> {
let in_initial = context.in_initial;
let dst = &src.dst[0];
let id = dst.id;
let (
_index,
select,
is_ff,
current_offset,
next_offset,
dst_width,
need_dynamic_select,
width_shape,
kind_width,
) = {
let scope = context.scope();
let meta = scope.variable_meta.get(&id).unwrap();
let index = dst.index.eval_value(&mut scope.analyzer_context).unwrap();
let index = meta.r#type.array.calc_index(&index).unwrap();
let select = if !dst.select.is_empty() {
dst.select
.eval_value(&mut scope.analyzer_context, &dst.comptime.r#type, false)
} else {
None
};
let element = &meta.elements[index];
let is_ff = element.is_ff();
let current_offset = element.current_offset();
let next_offset = element.next_offset;
let dst_width = meta.width;
let need_dynamic = !dst.select.is_empty() && !dst.select.is_const();
let select = if need_dynamic { None } else { select };
let width_shape = meta.r#type.width().clone();
let kind_width = meta.r#type.kind.width().unwrap_or(1);
(
index,
select,
is_ff,
current_offset,
next_offset,
dst_width,
need_dynamic,
width_shape,
kind_width,
)
};
let dynamic_select = if need_dynamic_select {
Some(build_dynamic_bit_select(
context,
&width_shape,
&dst.select,
kind_width,
)?)
} else {
None
};
let dst_var = if is_ff {
if in_initial {
VarOffset::Ff(current_offset)
} else {
VarOffset::Ff(next_offset)
}
} else {
VarOffset::Comb(current_offset)
};
let expr: ProtoExpression = Conv::conv(context, &src.expr)?;
Ok(ProtoAssignStatement {
dst: dst_var,
dst_width,
select,
dynamic_select,
rhs_select: None,
expr,
dst_ff_current_offset: current_offset,
token: src.token,
})
}
}
impl Conv<&air::IfStatement> for ProtoIfStatement {
fn conv(context: &mut ConvContext, src: &air::IfStatement) -> Result<Self, SimulatorError> {
let cond: ProtoExpression = Conv::conv(context, &src.cond)?;
let mut true_side = vec![];
for x in &src.true_side {
let stmts: Vec<ProtoStatement> = Conv::conv(context, x)?;
true_side.extend(stmts);
}
let mut false_side = vec![];
for x in &src.false_side {
let stmts: Vec<ProtoStatement> = Conv::conv(context, x)?;
false_side.extend(stmts);
}
Ok(ProtoIfStatement {
cond: Some(cond),
true_side,
false_side,
})
}
}
impl Conv<&air::IfResetStatement> for ProtoIfStatement {
fn conv(
context: &mut ConvContext,
src: &air::IfResetStatement,
) -> Result<Self, SimulatorError> {
let mut true_side = vec![];
for x in &src.true_side {
let stmts: Vec<ProtoStatement> = Conv::conv(context, x)?;
true_side.extend(stmts);
}
let mut false_side = vec![];
for x in &src.false_side {
let stmts: Vec<ProtoStatement> = Conv::conv(context, x)?;
false_side.extend(stmts);
}
Ok(ProtoIfStatement {
cond: None,
true_side,
false_side,
})
}
}
impl Conv<&FunctionCall> for Vec<ProtoStatement> {
fn conv(context: &mut ConvContext, src: &FunctionCall) -> Result<Self, SimulatorError> {
if !context.expanding_functions.insert(src.id) {
let name = context
.scope()
.analyzer_context
.functions
.get(&src.id)
.unwrap()
.name
.to_string();
return Err(SimulatorError::recursive_function(
&name,
&src.comptime.token,
));
}
let mut result = Vec::new();
let func = context
.scope()
.analyzer_context
.functions
.get(&src.id)
.unwrap()
.clone();
let body = if let Some(ref idx) = src.index {
func.get_function(idx).unwrap()
} else {
func.get_function(&[]).unwrap()
};
for (var_path, expr) in &src.inputs {
let arg_var_id = body.arg_map.get(var_path).unwrap();
let proto_expr: ProtoExpression = Conv::conv(context, expr)?;
let scope = context.scope();
let meta = scope.variable_meta.get(arg_var_id).unwrap();
let element = &meta.elements[0];
result.push(ProtoStatement::Assign(ProtoAssignStatement {
dst: element.current,
dst_width: meta.width,
select: None,
dynamic_select: None,
rhs_select: None,
expr: proto_expr,
dst_ff_current_offset: 0, token: TokenRange::default(),
}));
}
let mut pending = std::mem::take(&mut context.pending_statements);
pending.append(&mut result);
result = pending;
for stmt in &body.statements {
let stmts: Vec<ProtoStatement> = Conv::conv(context, stmt)?;
result.extend(stmts);
}
for (var_path, destinations) in &src.outputs {
let arg_var_id = body.arg_map.get(var_path).unwrap();
let scope = context.scope();
let arg_meta = scope.variable_meta.get(arg_var_id).unwrap();
let arg_element = &arg_meta.elements[0];
let arg_expr = ProtoExpression::Variable {
var_offset: arg_element.current,
select: None,
dynamic_select: None,
width: arg_meta.width,
var_full_width: arg_meta.width,
expr_context: ExpressionContext {
width: arg_meta.width,
signed: false,
},
};
for dst in destinations {
let scope = context.scope();
let dst_meta = scope.variable_meta.get(&dst.id).unwrap();
let dst_index = dst.index.eval_value(&mut scope.analyzer_context).unwrap();
let dst_index = dst_meta.r#type.array.calc_index(&dst_index).unwrap();
let dst_element = &dst_meta.elements[dst_index];
let select = if !dst.select.is_empty() {
dst.select
.eval_value(&mut scope.analyzer_context, &dst.comptime.r#type, false)
} else {
None
};
let dst_var = if dst_element.is_ff() {
VarOffset::Ff(dst_element.next_offset)
} else {
VarOffset::Comb(dst_element.current_offset())
};
result.push(ProtoStatement::Assign(ProtoAssignStatement {
dst: dst_var,
dst_width: dst_meta.width,
select,
dynamic_select: None,
rhs_select: None,
expr: arg_expr.clone(),
dst_ff_current_offset: dst_element.current_offset(),
token: TokenRange::default(),
}));
}
}
context.expanding_functions.remove(&src.id);
Ok(result)
}
}
fn parse_hex_file(filename: &str, width: usize) -> Vec<AnalyzerValue> {
let content = match std::fs::read_to_string(filename) {
Ok(c) => c,
Err(e) => {
log::warn!("$readmemh: failed to read '{}': {}", filename, e);
return vec![];
}
};
parse_hex_content(&content, width)
}
pub fn parse_hex_content(content: &str, width: usize) -> Vec<AnalyzerValue> {
let bytes = content.as_bytes();
let mut result: Vec<AnalyzerValue> = Vec::with_capacity(bytes.len() / 4 + 1);
let mut i = 0usize;
let len = bytes.len();
let mut digits: Vec<u8> = Vec::with_capacity(32);
while i < len {
let c = bytes[i];
if c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' {
i += 1;
continue;
}
if c == b'/' && i + 1 < len {
let n = bytes[i + 1];
if n == b'/' {
i += 2;
while i < len && bytes[i] != b'\n' {
i += 1;
}
continue;
}
if n == b'*' {
i += 2;
while i + 1 < len && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
i += 1;
}
if i + 1 < len {
i += 2;
}
continue;
}
}
let start = i;
while i < len {
let c = bytes[i];
if c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' {
break;
}
if c == b'/' && i + 1 < len && (bytes[i + 1] == b'/' || bytes[i + 1] == b'*') {
break;
}
i += 1;
}
if start == i {
continue;
}
let tok = &bytes[start..i];
let parsed = if tok.contains(&b'_') {
digits.clear();
for &b in tok {
if b != b'_' {
digits.push(b);
}
}
if digits.is_empty() {
continue;
}
std::str::from_utf8(&digits)
.ok()
.and_then(|s| u64::from_str_radix(s, 16).ok())
} else {
std::str::from_utf8(tok)
.ok()
.and_then(|s| u64::from_str_radix(s, 16).ok())
};
if let Some(val) = parsed {
result.push(AnalyzerValue::new(val, width, false));
}
}
result
}