use cranelift::{
codegen::ir::{BlockArg, MemFlagsData as MemFlags},
prelude::*,
};
use super::core::{TransValue, cast_type, get_chunk_as_i64, get_cl_type, promote_to_physical};
use super::{SIRTranslator, TranslationState, get_byte_size};
use crate::{RegionedAbsoluteAddr, RegisterId, SIROffset, STABLE_REGION, TriggerIdWithKind};
fn isub_from_imm(state: &mut TranslationState, imm: i64, rhs: Value) -> Value {
let lhs = state.builder.ins().iconst(types::I64, imm);
state.builder.ins().isub(lhs, rhs)
}
fn scalar_access_type(op_width: usize, max_bit_shift: usize) -> Type {
let total_bits_needed = op_width.saturating_add(max_bit_shift);
if total_bits_needed <= 8 {
types::I8
} else if total_bits_needed <= 16 {
types::I16
} else if total_bits_needed <= 32 {
types::I32
} else {
types::I64
}
}
fn max_bit_shift(offset: &SIROffset) -> usize {
match offset {
SIROffset::Static(bit_offset) | SIROffset::PackedElements { bit_offset, .. } => {
bit_offset & 7
}
SIROffset::Dynamic(_) => 7,
SIROffset::Element {
element_width,
bit_offset,
..
} => {
if element_width % 8 == 0 {
bit_offset & 7
} else {
7
}
}
}
}
fn logical_bit_offset(state: &mut TranslationState, offset: &SIROffset) -> Value {
fn register_value(state: &mut TranslationState, register: &RegisterId) -> Value {
let value = state.regs[register].first_value(state.builder);
let width = state.register_map[register].width();
promote_to_physical(state, value, width, false, types::I64)
}
match offset {
SIROffset::Static(value)
| SIROffset::PackedElements {
bit_offset: value, ..
} => state.builder.ins().iconst(types::I64, *value as i64),
SIROffset::Dynamic(reg) => register_value(state, reg),
SIROffset::Element {
index,
element_width,
bit_offset,
dynamic_bit_offset,
} => {
let index = register_value(state, index);
let scaled = state.builder.ins().imul_imm_s(index, *element_width as i64);
let with_static = state.builder.ins().iadd_imm_s(scaled, *bit_offset as i64);
if let Some(dynamic) = dynamic_bit_offset {
let dynamic = register_value(state, dynamic);
state.builder.ins().iadd(with_static, dynamic)
} else {
with_static
}
}
}
}
fn packed_byte_and_shift(state: &mut TranslationState, offset: &SIROffset) -> (Value, Value) {
let total_bit_offset = logical_bit_offset(state, offset);
(
state.builder.ins().ushr_imm_s(total_bit_offset, 3),
state.builder.ins().band_imm_s(total_bit_offset, 7),
)
}
impl SIRTranslator {
fn copy_sparse_chunk(
&self,
state: &mut TranslationState,
src_base: i64,
dst_base: i64,
byte_index: Value,
byte_len: usize,
) {
let mut copied = 0usize;
for (bytes, ty) in [
(8usize, types::I64),
(4, types::I32),
(2, types::I16),
(1, types::I8),
] {
while copied + bytes <= byte_len {
let src = state
.builder
.ins()
.iadd_imm_s(state.mem_ptr, src_base + copied as i64);
let src = state.builder.ins().iadd(src, byte_index);
let dst = state
.builder
.ins()
.iadd_imm_s(state.mem_ptr, dst_base + copied as i64);
let dst = state.builder.ins().iadd(dst, byte_index);
let value = state.builder.ins().load(ty, MemFlags::new(), src, 0);
state.builder.ins().store(MemFlags::new(), value, dst, 0);
copied += bytes;
}
}
}
pub(super) fn translate_sparse_commit_inst(
&self,
state: &mut TranslationState,
src_addr: &RegionedAbsoluteAddr,
dst_addr: &RegionedAbsoluteAddr,
_op_width: usize,
) {
debug_assert_eq!(src_addr.region, crate::SPARSE_WORKING_REGION);
debug_assert_eq!(dst_addr.region, STABLE_REGION);
let abs = src_addr.absolute_addr();
let sparse = &self.layout.sparse_layouts[&abs];
let byte_size = get_byte_size(self.layout.widths[&abs]);
let src_base = (self.layout.sparse_base_offset + self.layout.sparse_offsets[&abs]) as i64;
let dst_base = self.layout.offsets[&abs] as i64;
let plane_count = if self.options.four_state && self.layout.is_4states[&abs] {
2
} else {
1
};
let last_chunk = sparse.chunk_count.saturating_sub(1);
let last_len = byte_size.saturating_sub(last_chunk * 8);
for summary_index in 0..sparse.summary_word_count {
let summary_addr = state.builder.ins().iadd_imm_s(
state.mem_ptr,
(sparse.summary_words_offset + summary_index * 8) as i64,
);
let summary_bits =
state
.builder
.ins()
.load(types::I64, MemFlags::new(), summary_addr, 0);
let zero = state.builder.ins().iconst(types::I64, 0);
state
.builder
.ins()
.store(MemFlags::new(), zero, summary_addr, 0);
let summary_loop = state.builder.create_block();
let summary_body = state.builder.create_block();
let summary_done = state.builder.create_block();
state.builder.append_block_param(summary_loop, types::I64);
state
.builder
.ins()
.jump(summary_loop, &[BlockArg::Value(summary_bits)]);
state.builder.switch_to_block(summary_loop);
let bits = state.builder.block_params(summary_loop)[0];
let nonzero = state.builder.ins().icmp_imm_s(IntCC::NotEqual, bits, 0);
state
.builder
.ins()
.brif(nonzero, summary_body, &[], summary_done, &[]);
state.builder.switch_to_block(summary_body);
let bit = state.builder.ins().ctz(bits);
let word_index = state
.builder
.ins()
.iadd_imm_s(bit, (summary_index * 64) as i64);
let dirty_byte_index = state.builder.ins().ishl_imm_s(word_index, 3);
let dirty_addr = state
.builder
.ins()
.iadd_imm_s(state.mem_ptr, sparse.dirty_words_offset as i64);
let dirty_addr = state.builder.ins().iadd(dirty_addr, dirty_byte_index);
let dirty_bits = state
.builder
.ins()
.load(types::I64, MemFlags::new(), dirty_addr, 0);
state
.builder
.ins()
.store(MemFlags::new(), zero, dirty_addr, 0);
let dirty_loop = state.builder.create_block();
let dirty_body = state.builder.create_block();
let dirty_done = state.builder.create_block();
state.builder.append_block_param(dirty_loop, types::I64);
state
.builder
.ins()
.jump(dirty_loop, &[BlockArg::Value(dirty_bits)]);
state.builder.switch_to_block(dirty_loop);
let dirty = state.builder.block_params(dirty_loop)[0];
let dirty_nonzero = state.builder.ins().icmp_imm_s(IntCC::NotEqual, dirty, 0);
state
.builder
.ins()
.brif(dirty_nonzero, dirty_body, &[], dirty_done, &[]);
state.builder.switch_to_block(dirty_body);
let dirty_bit = state.builder.ins().ctz(dirty);
let chunk_base = state.builder.ins().ishl_imm_s(word_index, 6);
let chunk = state.builder.ins().iadd(chunk_base, dirty_bit);
let byte_index = state.builder.ins().ishl_imm_s(chunk, 3);
if last_len == 8 {
for plane in 0..plane_count {
let delta = (plane * byte_size) as i64;
self.copy_sparse_chunk(
state,
src_base + delta,
dst_base + delta,
byte_index,
8,
);
}
} else {
let full = state.builder.create_block();
let partial = state.builder.create_block();
let copied = state.builder.create_block();
let is_last =
state
.builder
.ins()
.icmp_imm_s(IntCC::Equal, chunk, last_chunk as i64);
state.builder.ins().brif(is_last, partial, &[], full, &[]);
state.builder.switch_to_block(full);
for plane in 0..plane_count {
let delta = (plane * byte_size) as i64;
self.copy_sparse_chunk(
state,
src_base + delta,
dst_base + delta,
byte_index,
8,
);
}
state.builder.ins().jump(copied, &[]);
state.builder.switch_to_block(partial);
for plane in 0..plane_count {
let delta = (plane * byte_size) as i64;
self.copy_sparse_chunk(
state,
src_base + delta,
dst_base + delta,
byte_index,
last_len,
);
}
state.builder.ins().jump(copied, &[]);
state.builder.switch_to_block(copied);
state.builder.seal_block(full);
state.builder.seal_block(partial);
state.builder.seal_block(copied);
}
let one_less = state.builder.ins().iadd_imm_s(dirty, -1);
let remaining_dirty = state.builder.ins().band(dirty, one_less);
state
.builder
.ins()
.jump(dirty_loop, &[BlockArg::Value(remaining_dirty)]);
state.builder.switch_to_block(dirty_done);
let one_less = state.builder.ins().iadd_imm_s(bits, -1);
let remaining_summary = state.builder.ins().band(bits, one_less);
state
.builder
.ins()
.jump(summary_loop, &[BlockArg::Value(remaining_summary)]);
state.builder.switch_to_block(summary_done);
state.builder.seal_block(summary_body);
state.builder.seal_block(dirty_body);
state.builder.seal_block(dirty_loop);
state.builder.seal_block(dirty_done);
state.builder.seal_block(summary_loop);
state.builder.seal_block(summary_done);
}
}
fn prepare_sparse_store(
&self,
state: &mut TranslationState,
addr: &RegionedAbsoluteAddr,
offset: &SIROffset,
width: usize,
) {
let abs = addr.absolute_addr();
let sparse = &self.layout.sparse_layouts[&abs];
let stable_base = self.layout.offsets[&abs] as i64;
let sparse_base =
(self.layout.sparse_base_offset + self.layout.sparse_offsets[&abs]) as i64;
let byte_size = get_byte_size(self.layout.widths[&abs]) as i64;
let bit_offset = logical_bit_offset(state, offset);
let start_chunk = state.builder.ins().ushr_imm_s(bit_offset, 6);
let end_bit = state
.builder
.ins()
.iadd_imm_s(bit_offset, width.saturating_sub(1) as i64);
let end_chunk = state.builder.ins().ushr_imm_s(end_bit, 6);
for chunk_delta in 0..(width.div_ceil(64) + 1) {
let candidate = state
.builder
.ins()
.iadd_imm_s(start_chunk, chunk_delta as i64);
let valid =
state
.builder
.ins()
.icmp(IntCC::UnsignedLessThanOrEqual, candidate, end_chunk);
let chunk = state.builder.ins().select(valid, candidate, start_chunk);
let dirty_word = state.builder.ins().ushr_imm_s(chunk, 6);
let dirty_index = state.builder.ins().ishl_imm_s(dirty_word, 3);
let dirty_addr = state
.builder
.ins()
.iadd_imm_s(state.mem_ptr, sparse.dirty_words_offset as i64);
let dirty_addr = state.builder.ins().iadd(dirty_addr, dirty_index);
let dirty_bits = state
.builder
.ins()
.load(types::I64, MemFlags::new(), dirty_addr, 0);
let bit_in_word = state.builder.ins().band_imm_s(chunk, 63);
let one = state.builder.ins().iconst(types::I64, 1);
let dirty_mask = state.builder.ins().ishl(one, bit_in_word);
let dirty_test = state.builder.ins().band(dirty_bits, dirty_mask);
let was_dirty = state
.builder
.ins()
.icmp_imm_s(IntCC::NotEqual, dirty_test, 0);
let data_index = state.builder.ins().ishl_imm_s(chunk, 3);
for plane_delta in [0, byte_size].into_iter().take(
if self.options.four_state && self.layout.is_4states[&abs] {
2
} else {
1
},
) {
let stable_addr = state
.builder
.ins()
.iadd_imm_s(state.mem_ptr, stable_base + plane_delta);
let stable_addr = state.builder.ins().iadd(stable_addr, data_index);
let sparse_addr = state
.builder
.ins()
.iadd_imm_s(state.mem_ptr, sparse_base + plane_delta);
let sparse_addr = state.builder.ins().iadd(sparse_addr, data_index);
let stable = state
.builder
.ins()
.load(types::I64, MemFlags::new(), stable_addr, 0);
let working = state
.builder
.ins()
.load(types::I64, MemFlags::new(), sparse_addr, 0);
let initialized = state.builder.ins().select(was_dirty, working, stable);
state
.builder
.ins()
.store(MemFlags::new(), initialized, sparse_addr, 0);
}
let new_dirty = state.builder.ins().bor(dirty_bits, dirty_mask);
state
.builder
.ins()
.store(MemFlags::new(), new_dirty, dirty_addr, 0);
let summary_word = state.builder.ins().ushr_imm_s(dirty_word, 6);
let summary_index = state.builder.ins().ishl_imm_s(summary_word, 3);
let summary_addr = state
.builder
.ins()
.iadd_imm_s(state.mem_ptr, sparse.summary_words_offset as i64);
let summary_addr = state.builder.ins().iadd(summary_addr, summary_index);
let summary_bits =
state
.builder
.ins()
.load(types::I64, MemFlags::new(), summary_addr, 0);
let summary_bit = state.builder.ins().band_imm_s(dirty_word, 63);
let summary_mask = state.builder.ins().ishl(one, summary_bit);
let new_summary = state.builder.ins().bor(summary_bits, summary_mask);
state
.builder
.ins()
.store(MemFlags::new(), new_summary, summary_addr, 0);
}
}
pub(super) fn translate_load_inst(
&self,
state: &mut TranslationState,
dst: &RegisterId,
addr: &RegionedAbsoluteAddr,
offset: &SIROffset,
op_width: &usize,
) {
let d_phys_width = state.register_map[dst].width();
let abs = addr.absolute_addr();
let (mem_base, base_offset_bytes) = (state.mem_ptr, self.layout.region_base_offset(addr));
if let SIROffset::Static(val) = offset {
let byte_off = (val >> 3) as i64;
let bit_shift = val & 7;
let static_addr = state
.builder
.ins()
.iadd_imm_s(mem_base, (base_offset_bytes as i64) + byte_off);
if bit_shift == 0 {
if d_phys_width <= 64 && matches!(*op_width, 8 | 16 | 32 | 64) {
let v = self.translate_load_native_aligned(
state,
static_addr,
*op_width,
d_phys_width,
);
if self.options.four_state {
let m = if self.layout.is_4states[&abs] {
let var_byte_size = get_byte_size(self.layout.widths[&abs]);
let static_addr_m = state
.builder
.ins()
.iadd_imm_s(static_addr, var_byte_size as i64);
self.translate_load_native_aligned(
state,
static_addr_m,
*op_width,
d_phys_width,
)
} else {
state.builder.ins().iconst(types::I64, 0)
};
state.regs.insert(
*dst,
TransValue::FourState {
values: vec![v],
masks: vec![m],
},
);
} else {
state.regs.insert(*dst, TransValue::TwoState(vec![v]));
}
return;
}
if d_phys_width > 64 && (*op_width).is_multiple_of(64) {
let chunks_v = self.translate_load_multi_word_aligned_words(
state,
static_addr,
*op_width,
d_phys_width,
);
if self.options.four_state {
let chunks_m = if self.layout.is_4states[&abs] {
let var_byte_size = get_byte_size(self.layout.widths[&abs]);
let static_addr_m = state
.builder
.ins()
.iadd_imm_s(static_addr, var_byte_size as i64);
self.translate_load_multi_word_aligned_words(
state,
static_addr_m,
*op_width,
d_phys_width,
)
} else {
let zero_chunk = state.builder.ins().iconst(types::I64, 0);
vec![zero_chunk; d_phys_width.div_ceil(64)]
};
state.regs.insert(
*dst,
TransValue::FourState {
values: chunks_v,
masks: chunks_m,
},
);
} else {
state.regs.insert(*dst, TransValue::TwoState(chunks_v));
}
return;
}
}
}
let (byte_offset_val, bit_shift_val) = packed_byte_and_shift(state, offset);
let final_addr = state
.builder
.ins()
.iadd_imm_s(mem_base, base_offset_bytes as i64);
let final_addr = state.builder.ins().iadd(final_addr, byte_offset_val);
let res_chunks_v = if d_phys_width <= 64 {
vec![self.translate_load_native(
state,
final_addr,
bit_shift_val,
*op_width,
d_phys_width,
max_bit_shift(offset),
)]
} else {
self.translate_load_multi_word(
state,
final_addr,
bit_shift_val,
*op_width,
d_phys_width,
)
};
if self.options.four_state {
let is_var_4state = self.layout.is_4states[&abs];
let res_chunks_m = if is_var_4state {
let var_byte_size = get_byte_size(self.layout.widths[&abs]);
let final_addr_m = state
.builder
.ins()
.iadd_imm_s(final_addr, var_byte_size as i64);
if d_phys_width <= 64 {
vec![self.translate_load_native(
state,
final_addr_m,
bit_shift_val,
*op_width,
d_phys_width,
max_bit_shift(offset),
)]
} else {
self.translate_load_multi_word(
state,
final_addr_m,
bit_shift_val,
*op_width,
d_phys_width,
)
}
} else {
let zero_chunk = state.builder.ins().iconst(types::I64, 0);
if d_phys_width <= 64 {
vec![zero_chunk]
} else {
vec![zero_chunk; d_phys_width.div_ceil(64)]
}
};
state.regs.insert(
*dst,
TransValue::FourState {
values: res_chunks_v,
masks: res_chunks_m,
},
);
} else {
state.regs.insert(*dst, TransValue::TwoState(res_chunks_v));
}
}
pub(super) fn translate_store_inst(
&self,
state: &mut TranslationState,
addr: &RegionedAbsoluteAddr,
offset: &SIROffset,
op_width: &usize,
src_reg: &RegisterId,
triggers: &[TriggerIdWithKind],
comb_capture_sites: &[u32],
) {
if addr.region == crate::SPARSE_WORKING_REGION && *op_width != 0 {
self.prepare_sparse_store(state, addr, offset, *op_width);
}
if *op_width == 0 {
if self.options.emit_triggers && !triggers.is_empty() {
let abs = addr.absolute_addr();
let base_offset = self.layout.offsets[&abs];
let new_val = state.builder.ins().load(
types::I8,
MemFlags::trusted(),
state.mem_ptr,
base_offset as i32,
);
let old_val = state.trigger_old_values[&(abs, addr.region)];
for trigger in triggers {
let trigger_byte_idx = trigger.id / 8;
let trigger_bit_idx = trigger.id % 8;
let trigger_offset = self.layout.triggered_bits_offset + trigger_byte_idx;
let cmp_ty = state.builder.func.dfg.value_type(new_val);
let old_val_cast = cast_type(state.builder, old_val, cmp_ty);
let changed = state
.builder
.ins()
.icmp(IntCC::NotEqual, new_val, old_val_cast);
let bit_mask = state
.builder
.ins()
.iconst(types::I8, (1u64 << trigger_bit_idx) as i64);
let zero = state.builder.ins().iconst(types::I8, 0);
let selected = state.builder.ins().select(changed, bit_mask, zero);
let old_trig = state.builder.ins().load(
types::I8,
MemFlags::trusted(),
state.mem_ptr,
trigger_offset as i32,
);
let new_trig = state.builder.ins().bor(old_trig, selected);
state.builder.ins().store(
MemFlags::trusted(),
new_trig,
state.mem_ptr,
trigger_offset as i32,
);
}
}
return;
}
let s_phys_width = state.register_map[src_reg].width();
let abs = addr.absolute_addr();
let base_offset_bytes = self.layout.region_base_offset(addr);
let mem_base = state.mem_ptr;
let (byte_offset_val, bit_shift_val) = packed_byte_and_shift(state, offset);
let final_addr = state
.builder
.ins()
.iadd_imm_s(mem_base, base_offset_bytes as i64);
let final_addr = state.builder.ins().iadd(final_addr, byte_offset_val);
let captures_four_state = self.options.four_state && self.layout.is_4states[&abs];
let old_value_chunks = if comb_capture_sites.is_empty() {
Vec::new()
} else if s_phys_width <= 64 {
vec![self.translate_load_native(
state,
final_addr,
bit_shift_val,
*op_width,
s_phys_width,
max_bit_shift(offset),
)]
} else {
let is_static_aligned = matches!(offset, SIROffset::Static(v) if v & 7 == 0);
let full_word_store = (*op_width).is_multiple_of(64);
if is_static_aligned && full_word_store {
self.translate_load_multi_word_aligned_words(
state,
final_addr,
*op_width,
s_phys_width,
)
} else {
self.translate_load_multi_word(
state,
final_addr,
bit_shift_val,
*op_width,
s_phys_width,
)
}
};
let old_mask_chunks = if comb_capture_sites.is_empty() || !captures_four_state {
Vec::new()
} else {
let var_byte_size = get_byte_size(self.layout.widths[&abs]);
let final_addr_m = state
.builder
.ins()
.iadd_imm_s(final_addr, var_byte_size as i64);
if s_phys_width <= 64 {
vec![self.translate_load_native(
state,
final_addr_m,
bit_shift_val,
*op_width,
s_phys_width,
max_bit_shift(offset),
)]
} else {
let is_static_aligned = matches!(offset, SIROffset::Static(v) if v & 7 == 0);
let full_word_store = (*op_width).is_multiple_of(64);
if is_static_aligned && full_word_store {
self.translate_load_multi_word_aligned_words(
state,
final_addr_m,
*op_width,
s_phys_width,
)
} else {
self.translate_load_multi_word(
state,
final_addr_m,
bit_shift_val,
*op_width,
s_phys_width,
)
}
}
};
let v_chunks = state.regs[src_reg].load_value_chunks(state.builder);
let m_chunks = if self.options.four_state {
state.regs[src_reg]
.load_mask_chunks(state.builder)
.unwrap_or_else(|| {
let s_phys_ty = get_cl_type(s_phys_width);
v_chunks
.iter()
.map(|_| state.builder.ins().iconst(s_phys_ty, 0))
.collect()
})
} else {
vec![]
};
if s_phys_width <= 64 {
let val_v = v_chunks[0];
let no_rmw_width = matches!(*op_width, 8 | 16 | 32 | 64);
let is_static_aligned = matches!(offset, SIROffset::Static(v) if v & 7 == 0);
if no_rmw_width && is_static_aligned {
self.translate_store_native_aligned(state, final_addr, *op_width, val_v);
if self.options.four_state && self.layout.is_4states[&abs] {
let var_byte_size = get_byte_size(self.layout.widths[&abs]);
let final_addr_m = state
.builder
.ins()
.iadd_imm_s(final_addr, var_byte_size as i64);
self.translate_store_native_aligned(
state,
final_addr_m,
*op_width,
m_chunks[0],
);
}
} else {
self.translate_store_native(
state,
final_addr,
bit_shift_val,
*op_width,
val_v,
max_bit_shift(offset),
);
if self.options.four_state && self.layout.is_4states[&abs] {
let var_byte_size = get_byte_size(self.layout.widths[&abs]);
let final_addr_m = state
.builder
.ins()
.iadd_imm_s(final_addr, var_byte_size as i64);
self.translate_store_native(
state,
final_addr_m,
bit_shift_val,
*op_width,
m_chunks[0],
max_bit_shift(offset),
);
}
}
} else {
let is_static_aligned = matches!(offset, SIROffset::Static(v) if v & 7 == 0);
let full_word_store = (*op_width).is_multiple_of(64);
if is_static_aligned && full_word_store {
self.translate_store_multi_word_aligned_words(
state, final_addr, *op_width, &v_chunks,
);
if self.options.four_state && self.layout.is_4states[&abs] {
let var_byte_size = get_byte_size(self.layout.widths[&abs]);
let final_addr_m = state
.builder
.ins()
.iadd_imm_s(final_addr, var_byte_size as i64);
self.translate_store_multi_word_aligned_words(
state,
final_addr_m,
*op_width,
&m_chunks,
);
}
} else {
self.translate_store_multi_word_from_chunks(
state,
final_addr,
bit_shift_val,
*op_width,
&v_chunks,
);
if self.options.four_state && self.layout.is_4states[&abs] {
let var_byte_size = get_byte_size(self.layout.widths[&abs]);
let final_addr_m = state
.builder
.ins()
.iadd_imm_s(final_addr, var_byte_size as i64);
self.translate_store_multi_word_from_chunks(
state,
final_addr_m,
bit_shift_val,
*op_width,
&m_chunks,
);
}
}
}
if !comb_capture_sites.is_empty() {
let new_value_chunks = if s_phys_width <= 64 {
vec![self.translate_load_native(
state,
final_addr,
bit_shift_val,
*op_width,
s_phys_width,
max_bit_shift(offset),
)]
} else {
let is_static_aligned = matches!(offset, SIROffset::Static(v) if v & 7 == 0);
let full_word_store = (*op_width).is_multiple_of(64);
if is_static_aligned && full_word_store {
self.translate_load_multi_word_aligned_words(
state,
final_addr,
*op_width,
s_phys_width,
)
} else {
self.translate_load_multi_word(
state,
final_addr,
bit_shift_val,
*op_width,
s_phys_width,
)
}
};
let mut changed =
self.translate_chunks_changed(state, &old_value_chunks, &new_value_chunks);
if captures_four_state {
let var_byte_size = get_byte_size(self.layout.widths[&abs]);
let final_addr_m = state
.builder
.ins()
.iadd_imm_s(final_addr, var_byte_size as i64);
let new_mask_chunks = if s_phys_width <= 64 {
vec![self.translate_load_native(
state,
final_addr_m,
bit_shift_val,
*op_width,
s_phys_width,
max_bit_shift(offset),
)]
} else {
let is_static_aligned = matches!(offset, SIROffset::Static(v) if v & 7 == 0);
let full_word_store = (*op_width).is_multiple_of(64);
if is_static_aligned && full_word_store {
self.translate_load_multi_word_aligned_words(
state,
final_addr_m,
*op_width,
s_phys_width,
)
} else {
self.translate_load_multi_word(
state,
final_addr_m,
bit_shift_val,
*op_width,
s_phys_width,
)
}
};
let mask_changed =
self.translate_chunks_changed(state, &old_mask_chunks, &new_mask_chunks);
changed = state.builder.ins().bor(changed, mask_changed);
}
self.translate_enable_comb_capture_sites(state, changed, comb_capture_sites);
}
self.translate_trigger_detection(state, addr, offset, op_width, triggers);
}
fn translate_chunks_changed(
&self,
state: &mut TranslationState,
old_chunks: &[Value],
new_chunks: &[Value],
) -> Value {
let first_ty = state.builder.func.dfg.value_type(old_chunks[0]);
let first_new = cast_type(state.builder, new_chunks[0], first_ty);
let mut changed = state
.builder
.ins()
.icmp(IntCC::NotEqual, old_chunks[0], first_new);
for (old, new) in old_chunks.iter().zip(new_chunks.iter()).skip(1) {
let old_ty = state.builder.func.dfg.value_type(*old);
let new = cast_type(state.builder, *new, old_ty);
let chunk_changed = state.builder.ins().icmp(IntCC::NotEqual, *old, new);
changed = state.builder.ins().bor(changed, chunk_changed);
}
changed
}
fn translate_enable_comb_capture_sites(
&self,
state: &mut TranslationState,
changed: Value,
site_ids: &[u32],
) {
let enabled_ptr = state.builder.ins().load(
types::I64,
MemFlags::new(),
state.mem_ptr,
celox_state_layout::STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET as i32,
);
let one = state.builder.ins().iconst(types::I8, 1);
for &site_id in site_ids {
let old =
state
.builder
.ins()
.load(types::I8, MemFlags::new(), enabled_ptr, site_id as i32);
let next = state.builder.ins().select(changed, one, old);
state
.builder
.ins()
.store(MemFlags::new(), next, enabled_ptr, site_id as i32);
}
}
pub(super) fn translate_comb_capture_enable_if_changed(
&self,
state: &mut TranslationState,
old: &RegisterId,
new: &RegisterId,
sites: &[u32],
) {
if sites.is_empty() {
return;
}
let old_value_chunks = state.regs[old].load_value_chunks(state.builder);
let new_value_chunks = state.regs[new].load_value_chunks(state.builder);
let mut changed =
self.translate_chunks_changed(state, &old_value_chunks, &new_value_chunks);
if self.options.four_state {
let old_mask_chunks = state.regs[old]
.load_mask_chunks(state.builder)
.unwrap_or_else(|| {
old_value_chunks
.iter()
.map(|chunk| {
let ty = state.builder.func.dfg.value_type(*chunk);
state.builder.ins().iconst(ty, 0)
})
.collect()
});
let new_mask_chunks = state.regs[new]
.load_mask_chunks(state.builder)
.unwrap_or_else(|| {
new_value_chunks
.iter()
.map(|chunk| {
let ty = state.builder.func.dfg.value_type(*chunk);
state.builder.ins().iconst(ty, 0)
})
.collect()
});
let mask_changed =
self.translate_chunks_changed(state, &old_mask_chunks, &new_mask_chunks);
changed = state.builder.ins().bor(changed, mask_changed);
}
self.translate_enable_comb_capture_sites(state, changed, sites);
}
pub(super) fn translate_commit_inst(
&self,
state: &mut TranslationState,
src_addr: &RegionedAbsoluteAddr,
dst_addr: &RegionedAbsoluteAddr,
offset: &SIROffset,
op_width: &usize,
triggers: &[TriggerIdWithKind],
) {
let src_abs = src_addr.absolute_addr();
let (src_mem_base, src_base_offset_bytes) =
(state.mem_ptr, self.layout.region_base_offset(src_addr));
let dst_abs = dst_addr.absolute_addr();
let (dst_mem_base, dst_base_offset_bytes) =
(state.mem_ptr, self.layout.region_base_offset(dst_addr));
if let SIROffset::Static(bit_off) = offset
&& bit_off % 8 == 0
&& op_width.is_multiple_of(8)
{
let byte_off = bit_off / 8;
let byte_len = get_byte_size(*op_width);
let src_addr_val = state
.builder
.ins()
.iadd_imm_s(src_mem_base, (src_base_offset_bytes + byte_off) as i64);
let dst_addr_val = state
.builder
.ins()
.iadd_imm_s(dst_mem_base, (dst_base_offset_bytes + byte_off) as i64);
self.translate_copy_bytes(state, src_addr_val, dst_addr_val, byte_len);
if self.options.four_state {
let var_byte_size_src = get_byte_size(self.layout.widths[&src_abs]);
let var_byte_size_dst = get_byte_size(self.layout.widths[&dst_abs]);
let src_addr_m = state
.builder
.ins()
.iadd_imm_s(src_addr_val, var_byte_size_src as i64);
let dst_addr_m = state
.builder
.ins()
.iadd_imm_s(dst_addr_val, var_byte_size_dst as i64);
self.translate_copy_bytes(state, src_addr_m, dst_addr_m, byte_len);
}
return;
}
let (byte_offset_val, bit_shift_val) = packed_byte_and_shift(state, offset);
let src_final_addr = state
.builder
.ins()
.iadd_imm_s(src_mem_base, src_base_offset_bytes as i64);
let src_final_addr = state.builder.ins().iadd(src_final_addr, byte_offset_val);
let dst_final_addr = state
.builder
.ins()
.iadd_imm_s(dst_mem_base, dst_base_offset_bytes as i64);
let dst_final_addr = state.builder.ins().iadd(dst_final_addr, byte_offset_val);
let phys_width = self.layout.widths[&src_abs];
if phys_width <= 64 {
let val = self.translate_load_native(
state,
src_final_addr,
bit_shift_val,
*op_width,
phys_width,
max_bit_shift(offset),
);
self.translate_store_native(
state,
dst_final_addr,
bit_shift_val,
*op_width,
val,
max_bit_shift(offset),
);
if self.options.four_state {
let var_byte_size_src = get_byte_size(self.layout.widths[&src_abs]);
let var_byte_size_dst = get_byte_size(self.layout.widths[&dst_abs]);
let src_final_addr_m = state
.builder
.ins()
.iadd_imm_s(src_final_addr, var_byte_size_src as i64);
let dst_final_addr_m = state
.builder
.ins()
.iadd_imm_s(dst_final_addr, var_byte_size_dst as i64);
let val_m = self.translate_load_native(
state,
src_final_addr_m,
bit_shift_val,
*op_width,
phys_width,
max_bit_shift(offset),
);
self.translate_store_native(
state,
dst_final_addr_m,
bit_shift_val,
*op_width,
val_m,
max_bit_shift(offset),
);
}
} else {
let chunks = self.translate_load_multi_word(
state,
src_final_addr,
bit_shift_val,
*op_width,
phys_width,
);
self.translate_store_multi_word_from_chunks(
state,
dst_final_addr,
bit_shift_val,
*op_width,
&chunks,
);
if self.options.four_state {
let var_byte_size_src = get_byte_size(self.layout.widths[&src_abs]);
let var_byte_size_dst = get_byte_size(self.layout.widths[&dst_abs]);
let src_final_addr_m = state
.builder
.ins()
.iadd_imm_s(src_final_addr, var_byte_size_src as i64);
let dst_final_addr_m = state
.builder
.ins()
.iadd_imm_s(dst_final_addr, var_byte_size_dst as i64);
let chunks_m = self.translate_load_multi_word(
state,
src_final_addr_m,
bit_shift_val,
*op_width,
phys_width,
);
self.translate_store_multi_word_from_chunks(
state,
dst_final_addr_m,
bit_shift_val,
*op_width,
&chunks_m,
);
}
}
self.translate_trigger_detection(state, dst_addr, offset, op_width, triggers);
}
fn translate_copy_bytes(
&self,
state: &mut TranslationState,
src_addr: Value,
dst_addr: Value,
byte_len: usize,
) {
let mut offset = 0usize;
while offset + 8 <= byte_len {
let v = state
.builder
.ins()
.load(types::I64, MemFlags::new(), src_addr, offset as i32);
state
.builder
.ins()
.store(MemFlags::new(), v, dst_addr, offset as i32);
offset += 8;
}
let rem = byte_len - offset;
if rem >= 4 {
let v = state
.builder
.ins()
.load(types::I32, MemFlags::new(), src_addr, offset as i32);
state
.builder
.ins()
.store(MemFlags::new(), v, dst_addr, offset as i32);
offset += 4;
}
let rem = byte_len - offset;
if rem >= 2 {
let v = state
.builder
.ins()
.load(types::I16, MemFlags::new(), src_addr, offset as i32);
state
.builder
.ins()
.store(MemFlags::new(), v, dst_addr, offset as i32);
offset += 2;
}
if byte_len - offset >= 1 {
let v = state
.builder
.ins()
.load(types::I8, MemFlags::new(), src_addr, offset as i32);
state
.builder
.ins()
.store(MemFlags::new(), v, dst_addr, offset as i32);
}
}
fn translate_store_native(
&self,
state: &mut TranslationState,
addr: Value, bit_shift: Value, op_width: usize, src_val: Value, max_bit_shift: usize,
) {
if op_width.saturating_add(max_bit_shift) > 64 {
self.translate_store_multi_word_from_chunks(
state,
addr,
bit_shift,
op_width,
&[src_val],
);
return;
}
let access_ty = scalar_access_type(op_width, max_bit_shift);
let m_raw = if op_width >= 64 {
!0u64
} else {
(1u64 << op_width) - 1
};
let mask_val = state.builder.ins().iconst(access_ty, m_raw as i64);
let src_aligned = cast_type(state.builder, src_val, access_ty);
let shift_amt = cast_type(state.builder, bit_shift, access_ty);
let old_val = state
.builder
.ins()
.load(access_ty, MemFlags::new(), addr, 0);
let shifted_mask = state.builder.ins().ishl(mask_val, shift_amt);
let inv_mask = state.builder.ins().bnot(shifted_mask);
let shifted_src = state.builder.ins().ishl(src_aligned, shift_amt);
let masked_src = state.builder.ins().band(shifted_src, shifted_mask);
let preserved_part = state.builder.ins().band(old_val, inv_mask);
let combined = state.builder.ins().bor(masked_src, preserved_part);
state
.builder
.ins()
.store(MemFlags::new(), combined, addr, 0);
}
fn translate_store_native_aligned(
&self,
state: &mut TranslationState,
addr: Value,
op_width: usize,
src_val: Value,
) {
let ty = match op_width {
8 => types::I8,
16 => types::I16,
32 => types::I32,
64 => types::I64,
_ => unreachable!("aligned native store width must be 8/16/32/64"),
};
let v = cast_type(state.builder, src_val, ty);
state.builder.ins().store(MemFlags::new(), v, addr, 0);
}
fn translate_load_native(
&self,
state: &mut TranslationState,
addr: Value,
bit_shift: Value,
op_width: usize,
d_phys_width: usize,
max_bit_shift: usize,
) -> Value {
if op_width.saturating_add(max_bit_shift) > 64 {
return self
.translate_load_multi_word(state, addr, bit_shift, op_width, d_phys_width)
.into_iter()
.next()
.expect("a scalar load produces one chunk");
}
let access_ty = scalar_access_type(op_width, max_bit_shift);
let raw_val = state
.builder
.ins()
.load(access_ty, MemFlags::new(), addr, 0);
let shift_amt = cast_type(state.builder, bit_shift, access_ty);
let aligned_val = state.builder.ins().ushr(raw_val, shift_amt);
let m_raw = if op_width >= 64 {
!0u64 } else {
(1u64 << op_width) - 1
};
let masked_val = state.builder.ins().band_imm_s(aligned_val, m_raw as i64);
cast_type(state.builder, masked_val, get_cl_type(d_phys_width))
}
fn translate_load_native_aligned(
&self,
state: &mut TranslationState,
addr: Value,
op_width: usize,
d_phys_width: usize,
) -> Value {
let ty = match op_width {
8 => types::I8,
16 => types::I16,
32 => types::I32,
64 => types::I64,
_ => unreachable!("aligned native load width must be 8/16/32/64"),
};
let raw = state.builder.ins().load(ty, MemFlags::new(), addr, 0);
cast_type(state.builder, raw, get_cl_type(d_phys_width))
}
fn translate_load_multi_word_aligned_words(
&self,
state: &mut TranslationState,
addr: Value,
op_width: usize,
d_phys_width: usize,
) -> Vec<Value> {
let num_phys_chunks = d_phys_width.div_ceil(64);
let needed_logic_chunks = op_width / 64;
let mut res_chunks = Vec::with_capacity(num_phys_chunks);
for i in 0..num_phys_chunks {
if i < needed_logic_chunks {
let v = state
.builder
.ins()
.load(types::I64, MemFlags::new(), addr, (i * 8) as i32);
res_chunks.push(v);
} else {
res_chunks.push(state.builder.ins().iconst(types::I64, 0));
}
}
res_chunks
}
fn translate_load_multi_word(
&self,
state: &mut TranslationState,
addr: Value,
bit_shift: Value,
op_width: usize,
d_phys_width: usize,
) -> Vec<Value> {
let bit_shift_i64 = cast_type(state.builder, bit_shift, types::I64);
let inv_bit_shift = isub_from_imm(state, 64, bit_shift_i64);
let has_bit_shift = state
.builder
.ins()
.icmp_imm_s(IntCC::NotEqual, bit_shift, 0);
let num_phys_chunks = d_phys_width.div_ceil(64);
let needed_logic_chunks = op_width.div_ceil(64);
let mut res_chunks = Vec::with_capacity(num_phys_chunks);
for i in 0..num_phys_chunks {
if i < needed_logic_chunks {
let cur_mem =
state
.builder
.ins()
.load(types::I64, MemFlags::new(), addr, (i * 8) as i32);
let nxt_mem = state.builder.ins().load(
types::I64,
MemFlags::new(),
addr,
((i + 1) * 8) as i32,
);
let low = state.builder.ins().ushr(cur_mem, bit_shift_i64);
let high = state.builder.ins().ishl(nxt_mem, inv_bit_shift);
let combined = state.builder.ins().bor(low, high);
let val = state.builder.ins().select(has_bit_shift, combined, cur_mem);
let is_last_valid = i == needed_logic_chunks - 1;
let remaining_bits = op_width % 64;
let final_chunk = if is_last_valid && remaining_bits > 0 {
let mask = (1u64 << remaining_bits) - 1;
state.builder.ins().band_imm_s(val, mask as i64)
} else {
val
};
res_chunks.push(final_chunk);
} else {
res_chunks.push(state.builder.ins().iconst(types::I64, 0));
}
}
res_chunks
}
fn translate_store_multi_word_aligned_words(
&self,
state: &mut TranslationState,
addr: Value,
op_width: usize,
chunks: &[Value],
) {
let words = op_width / 64;
for i in 0..words {
let chunk = get_chunk_as_i64(state.builder, chunks, i);
let v = cast_type(state.builder, chunk, types::I64);
state
.builder
.ins()
.store(MemFlags::new(), v, addr, (i * 8) as i32);
}
}
fn translate_store_multi_word_from_chunks(
&self,
state: &mut TranslationState,
addr: Value,
bit_shift: Value,
op_width: usize,
chunks: &[Value],
) {
let bit_shift_i64 = cast_type(state.builder, bit_shift, types::I64);
let inv_bit_shift = isub_from_imm(state, 64, bit_shift_i64);
let has_bit_shift = state
.builder
.ins()
.icmp_imm_s(IntCC::NotEqual, bit_shift_i64, 0);
let total_end_bit = state
.builder
.ins()
.iadd_imm_s(bit_shift_i64, op_width as i64);
let max_dst_chunks = (op_width + 7).div_ceil(64);
for i in 0..max_dst_chunks {
let chunk_start_bit = (i * 64) as i64;
let is_in_range = state.builder.ins().icmp_imm_s(
IntCC::UnsignedGreaterThan,
total_end_bit,
chunk_start_bit,
);
let write_block = state.builder.create_block();
let next_block = state.builder.create_block();
state
.builder
.ins()
.brif(is_in_range, write_block, &[], next_block, &[]);
state.builder.switch_to_block(write_block);
let cur_src = get_chunk_as_i64(state.builder, chunks, i);
let prev_src = if i > 0 {
Some(get_chunk_as_i64(state.builder, chunks, i - 1))
} else {
None
}
.unwrap_or_else(|| state.builder.ins().iconst(types::I64, 0));
let x = state.builder.ins().ishl(cur_src, bit_shift_i64);
let y = state.builder.ins().ushr(prev_src, inv_bit_shift);
let x = state.builder.ins().bor(x, y);
let val = state.builder.ins().select(has_bit_shift, x, cur_src);
let chunk_addr = state.builder.ins().iadd_imm_s(addr, (i * 8) as i64);
self.perform_rmw_i64_dynamic(state, chunk_addr, val, i, bit_shift_i64, total_end_bit);
state.builder.ins().jump(next_block, &[]);
state.builder.switch_to_block(next_block);
state.builder.seal_block(write_block);
state.builder.seal_block(next_block);
}
}
fn perform_rmw_i64_dynamic(
&self,
state: &mut TranslationState,
addr: Value,
val: Value,
chunk_idx: usize,
bit_shift: Value, total_end_bit: Value, ) {
let chunk_start_bit = (chunk_idx * 64) as i64;
let m1 = state.builder.ins().iconst(types::I64, -1);
let start_mask = state.builder.ins().ishl(m1, bit_shift);
let rel_end = state
.builder
.ins()
.iadd_imm_s(total_end_bit, -chunk_start_bit);
let shift_amt = state.builder.ins().band_imm_s(rel_end, 63);
let end_mask = state.builder.ins().ishl(m1, shift_amt);
let end_mask = state.builder.ins().bnot(end_mask);
let is_past_end =
state
.builder
.ins()
.icmp_imm_s(IntCC::SignedGreaterThanOrEqual, rel_end, 64);
let end_mask = state.builder.ins().select(is_past_end, m1, end_mask);
let final_mask = if chunk_idx == 0 {
state.builder.ins().band(start_mask, end_mask)
} else {
end_mask
};
let is_in_range = state
.builder
.ins()
.icmp_imm_s(IntCC::SignedGreaterThan, rel_end, 0);
let zero = state.builder.ins().iconst(types::I64, 0);
let final_mask = state.builder.ins().select(is_in_range, final_mask, zero);
let old = state
.builder
.ins()
.load(types::I64, MemFlags::new(), addr, 0);
let masked_val = state.builder.ins().band(val, final_mask);
let inv_mask = state.builder.ins().bnot(final_mask);
let preserved_old = state.builder.ins().band(old, inv_mask);
let combined = state.builder.ins().bor(masked_val, preserved_old);
state
.builder
.ins()
.store(MemFlags::new(), combined, addr, 0);
}
fn translate_trigger_detection(
&self,
state: &mut TranslationState,
addr: &RegionedAbsoluteAddr,
offset: &SIROffset,
op_width: &usize,
triggers: &[TriggerIdWithKind],
) {
if triggers.is_empty() || !self.options.emit_triggers {
return;
}
let abs = addr.absolute_addr();
let pre_loaded = state.trigger_old_values[&(abs, addr.region)];
let mask = if *op_width >= 64 {
!0u64
} else {
(1u64 << op_width) - 1
};
let old_val = match offset {
SIROffset::Static(v) | SIROffset::PackedElements { bit_offset: v, .. } => {
if *v == 0 {
state.builder.ins().band_imm_s(pre_loaded, mask as i64)
} else {
let shifted = state.builder.ins().ushr_imm_s(pre_loaded, *v as i64);
state.builder.ins().band_imm_s(shifted, mask as i64)
}
}
SIROffset::Dynamic(_) | SIROffset::Element { .. } => {
let total_bit_offset = logical_bit_offset(state, offset);
let shifted = state.builder.ins().ushr(pre_loaded, total_bit_offset);
let mask_val = state.builder.ins().iconst(types::I64, mask as i64);
state.builder.ins().band(shifted, mask_val)
}
};
let (_mem_base, base_offset_bytes) = (state.mem_ptr, self.layout.region_base_offset(addr));
let (byte_offset_val, bit_shift_val) = packed_byte_and_shift(state, offset);
let actual_final_addr = state
.builder
.ins()
.iadd_imm_s(state.mem_ptr, base_offset_bytes as i64);
let actual_final_addr = state.builder.ins().iadd(actual_final_addr, byte_offset_val);
let new_val = self.translate_load_native(
state,
actual_final_addr,
bit_shift_val,
*op_width,
64,
max_bit_shift(offset),
);
for trigger in triggers {
let triggered = match trigger.kind {
crate::DomainKind::ClockPosedge => {
let c1 = state.builder.ins().icmp_imm_s(IntCC::Equal, old_val, 0);
let c2 = state.builder.ins().icmp_imm_s(IntCC::Equal, new_val, 1);
state.builder.ins().band(c1, c2)
}
crate::DomainKind::ClockNegedge => {
let c1 = state.builder.ins().icmp_imm_s(IntCC::Equal, old_val, 1);
let c2 = state.builder.ins().icmp_imm_s(IntCC::Equal, new_val, 0);
state.builder.ins().band(c1, c2)
}
crate::DomainKind::ResetAsyncHigh => {
state.builder.ins().icmp_imm_s(IntCC::Equal, new_val, 1)
}
crate::DomainKind::ResetAsyncLow => {
state.builder.ins().icmp_imm_s(IntCC::Equal, new_val, 0)
}
crate::DomainKind::Other => {
state.builder.ins().icmp(IntCC::NotEqual, old_val, new_val)
}
};
let triggered_block = state.builder.create_block();
let merge_block = state.builder.create_block();
state
.builder
.ins()
.brif(triggered, triggered_block, &[], merge_block, &[]);
state.builder.switch_to_block(triggered_block);
let byte_idx = trigger.id / 8;
let bit_idx = trigger.id % 8;
let bit_ptr = state.builder.ins().iadd_imm_s(
state.mem_ptr,
(self.layout.triggered_bits_offset + byte_idx) as i64,
);
let old_byte = state
.builder
.ins()
.load(types::I8, MemFlags::new(), bit_ptr, 0);
let set_bit = state
.builder
.ins()
.iconst(types::I8, (1u8 << bit_idx) as i64);
let new_byte = state.builder.ins().bor(old_byte, set_bit);
state
.builder
.ins()
.store(MemFlags::new(), new_byte, bit_ptr, 0);
state.builder.ins().jump(merge_block, &[]);
state.builder.switch_to_block(merge_block);
state.builder.seal_block(triggered_block);
state.builder.seal_block(merge_block);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn static_scalar_access_uses_the_known_intra_byte_shift() {
let boundary = SIROffset::Static(186);
assert_eq!(max_bit_shift(&boundary), 2);
assert_eq!(scalar_access_type(6, max_bit_shift(&boundary)), types::I8);
let dynamic = SIROffset::Dynamic(RegisterId(0));
assert_eq!(max_bit_shift(&dynamic), 7);
assert_eq!(scalar_access_type(6, max_bit_shift(&dynamic)), types::I16);
}
}