use crate::register_intrinsic;
use crate::types::{TypeId, TypeManager};
use crate::value::ValueId;
use crate::value::insn::{Intrinsic, IntrinsicId, Mnemonic, Simplified};
use crate::value::{BodyView, QCodeView};
struct At;
enum IdxRel {
Equal,
Distinct,
Unknown,
}
fn index_rel(view: BodyView<'_, '_>, a: ValueId, b: ValueId) -> IdxRel {
if a == b {
return IdxRel::Equal;
}
if let (ValueId::Literal(x), ValueId::Literal(y)) = (a, b) {
return if view.shared().values.literals[x].value == view.shared().values.literals[y].value {
IdxRel::Equal
} else {
IdxRel::Distinct
};
}
IdxRel::Unknown
}
impl Intrinsic for At {
fn name(&self) -> &'static str {
"at"
}
fn arity(&self) -> usize {
2
}
fn result_type(&self, types: &TypeManager, args: &[TypeId]) -> TypeId {
types.seq_elem_of(args[0]).unwrap_or(args[0])
}
fn eval(&self, _args: &[(u128, usize)], _out_size: usize) -> Option<u128> {
None
}
fn simplify(
&self,
view: BodyView<'_, '_>,
_id: IntrinsicId,
out_size: usize,
args: &[ValueId],
) -> Option<Simplified> {
let &[arr, index] = args else {
return None;
};
simplify_at(view, out_size, arr, index)
}
}
fn simplify_at(
view: BodyView<'_, '_>,
out_size: usize,
arr: ValueId,
index: ValueId,
) -> Option<Simplified> {
{
if let (ValueId::Bytes(bid), ValueId::Literal(ilit)) = (arr, index) {
let arr_ty = view.shared().values.bytes[bid].type_id;
if let Some((elem, count)) = view.shared().types.array_of(arr_ty) {
let esz = view.shared().types.size_of(elem);
let i = view.shared().values.literals[ilit].value as usize;
if i < count {
let off = i * esz;
let data = &view.shared().values.bytes[bid].data;
let mut buf = [0u8; 8];
buf[..esz].copy_from_slice(&data[off..off + esz]);
let v = u64::from_le_bytes(buf);
return Some(Simplified::Value(view.shared().get_const(v, out_size)));
}
}
}
let ValueId::Instruction(iid) = arr else {
return None;
};
let Mnemonic::Intrinsic(app) = view.instruction(iid).mnemonic() else {
return None;
};
let name = app.id.name();
let args: Vec<ValueId> = app.args.iter().map(|a| a.qualify(iid.func)).collect();
match name {
"insert" => {
let (base, ins_idx, val) = (args[0], args[1], args[2]);
match index_rel(view, ins_idx, index) {
IdxRel::Equal => Some(Simplified::Value(val)),
IdxRel::Distinct => Some(forward(view, out_size, base, index)),
IdxRel::Unknown => None,
}
}
"singleton" => Some(Simplified::Value(args[0])),
"splat" => Some(Simplified::Value(args[0])),
"concat" => {
let (a, b) = (args[0], args[1]);
let ValueId::Literal(jlit) = index else {
return None;
};
let j = view.shared().values.literals[jlit].value;
let a_ty = view.type_of(a);
let (_, len_a) = view.shared().types.array_of(a_ty)?;
if j < len_a as u64 {
Some(forward(view, out_size, a, index))
} else {
let shifted = view.shared().get_const(j - len_a as u64, 8);
Some(forward(view, out_size, b, shifted))
}
}
_ => None,
}
}
}
fn forward(view: BodyView<'_, '_>, out_size: usize, base: ValueId, index: ValueId) -> Simplified {
simplify_at(view, out_size, base, index).unwrap_or_else(|| {
Simplified::Expression(Mnemonic::Intrinsic(crate::value::insn::IntrinsicApp {
id: IntrinsicId::from_name("at").unwrap(),
args: vec![base.strip_func(), index.strip_func()],
}))
})
}
register_intrinsic!(At);
#[cfg(test)]
mod tests {
use super::*;
use crate::context::Context;
use crate::types::TypeRequest;
use crate::value::insn::IntrinsicId;
use crate::value::{BasicBlock, BodyView, FunctionId, LocalValueId, ValueId};
fn body_view<'ctx, 'str: 'ctx>(
ctx: &'ctx Context<'str>,
function: FunctionId,
) -> BodyView<'ctx, 'str> {
BodyView::new(&ctx.bodies[function], &ctx.shared, &ctx.interfaces)
}
fn at_id() -> IntrinsicId {
IntrinsicId::from_name("at").unwrap()
}
#[test]
fn result_type_is_element_type() {
let types = TypeManager::default();
let i32 = types.get_or_make_int(4);
let arr = types.get_or_make_array(i32, 5);
let i64 = types.get_or_make_int(8);
assert_eq!(at_id().desc().result_type(&types, &[arr, i64]), i32);
}
#[test]
fn at_forwards_same_index() {
let mut ctx = Context::new();
let i32 = ctx.shared.types.get_or_make_int(4);
let arr_ty = ctx.shared.types.get_or_make_array(i32, 4);
let blk = {
let __f = ctx.anon_function();
ctx.get_or_make_block(0x1000, __f)
};
let a = BasicBlock::from_id_mut(&mut ctx, blk).push_param(16).id;
ctx.block_param_mut(a).type_id = arr_ty;
let i = ctx.get_const(2, 8).id();
let v = ctx.get_const(0x77, 4).id();
let insert_id = IntrinsicId::from_name("insert").unwrap();
let ins = {
let mut b = ctx.builder(blk);
b.push_intrinsic(insert_id, vec![ValueId::BlockParam(a), i, v])
.id()
};
match at_id()
.desc()
.simplify(body_view(&ctx, blk.func), at_id(), 4, &[ins, i])
{
Some(Simplified::Value(got)) => assert_eq!(got, v),
other => panic!("expected v, got {other:?}"),
}
}
#[test]
fn at_bypasses_distinct_index() {
let mut ctx = Context::new();
let i32 = ctx.shared.types.get_or_make_int(4);
let arr_ty = ctx.shared.types.get_or_make_array(i32, 4);
let blk = {
let __f = ctx.anon_function();
ctx.get_or_make_block(0x1000, __f)
};
let a = BasicBlock::from_id_mut(&mut ctx, blk).push_param(16).id;
ctx.block_param_mut(a).type_id = arr_ty;
let i = ctx.get_const(2, 8).id();
let j = ctx.get_const(3, 8).id();
let v = ctx.get_const(0x77, 4).id();
let insert_id = IntrinsicId::from_name("insert").unwrap();
let ins = {
let mut b = ctx.builder(blk);
b.push_intrinsic(insert_id, vec![ValueId::BlockParam(a), i, v])
.id()
};
match at_id()
.desc()
.simplify(body_view(&ctx, blk.func), at_id(), 4, &[ins, j])
{
Some(Simplified::Expression(Mnemonic::Intrinsic(app))) => {
assert_eq!(app.id.name(), "at");
assert_eq!(
app.args,
vec![ValueId::BlockParam(a).strip_func(), j.strip_func()]
);
}
other => panic!("expected at(a, j), got {other:?}"),
}
}
#[test]
fn at_forwards_through_singleton() {
let mut ctx = Context::new();
let v = ctx.get_const(0x99, 4).id();
let value_ty = ctx.shared.types.get_int(4);
ctx.shared
.types
.create_requested_types(&[TypeRequest::array(value_ty, 1)]);
let sing_id = IntrinsicId::from_name("singleton").unwrap();
let blk = {
let __f = ctx.anon_function();
ctx.get_or_make_block(0x1000, __f)
};
let sing = {
let mut b = ctx.builder(blk);
b.push_intrinsic(sing_id, vec![v]).id()
};
let idx = ctx.get_const(0, 8).id();
match at_id()
.desc()
.simplify(body_view(&ctx, blk.func), at_id(), 4, &[sing, idx])
{
Some(Simplified::Value(got)) => assert_eq!(got, v),
other => panic!("expected v, got {other:?}"),
}
}
#[test]
fn at_picks_concat_side() {
let mut ctx = Context::new();
let i32 = ctx.shared.types.get_or_make_int(4);
let a_ty = ctx.shared.types.get_or_make_array(i32, 1);
let b_ty = ctx.shared.types.get_or_make_array(i32, 3);
ctx.shared
.types
.create_requested_types(&[TypeRequest::array(i32, 4)]);
let blk = {
let __f = ctx.anon_function();
ctx.get_or_make_block(0x1000, __f)
};
let a = BasicBlock::from_id_mut(&mut ctx, blk).push_param(4).id;
ctx.block_param_mut(a).type_id = a_ty;
let b = BasicBlock::from_id_mut(&mut ctx, blk).push_param(12).id;
ctx.block_param_mut(b).type_id = b_ty;
let concat_id = IntrinsicId::from_name("concat").unwrap();
let cat = {
let mut bl = ctx.builder(blk);
bl.push_intrinsic(
concat_id,
vec![ValueId::BlockParam(a), ValueId::BlockParam(b)],
)
.id()
};
let j0 = ctx.get_const(0, 8).id();
match at_id()
.desc()
.simplify(body_view(&ctx, blk.func), at_id(), 4, &[cat, j0])
{
Some(Simplified::Expression(Mnemonic::Intrinsic(app))) => {
assert_eq!(app.id.name(), "at");
assert_eq!(
app.args,
vec![ValueId::BlockParam(a).strip_func(), j0.strip_func()]
);
}
other => panic!("expected at(a, 0), got {other:?}"),
}
let j2 = ctx.get_const(2, 8).id();
match at_id()
.desc()
.simplify(body_view(&ctx, blk.func), at_id(), 4, &[cat, j2])
{
Some(Simplified::Expression(Mnemonic::Intrinsic(app))) => {
assert_eq!(app.id.name(), "at");
let LocalValueId::Literal(l) = app.args[1] else {
panic!("expected literal shifted index");
};
assert_eq!(app.args[0], ValueId::BlockParam(b).strip_func());
assert_eq!(ctx.shared.values.literals[l].value, 1);
}
other => panic!("expected at(b, 1), got {other:?}"),
}
}
#[test]
fn at_reads_constant_bytes() {
let mut ctx = Context::new();
let function = ctx.anon_function();
let i32 = ctx.shared.types.get_or_make_int(4);
let arr_ty = ctx.shared.types.get_or_make_array(i32, 3);
let mut data = Vec::new();
for w in [0x11u32, 0x22, 0x33] {
data.extend_from_slice(&w.to_le_bytes());
}
let bid = ctx.get_bytes(data).id();
if let ValueId::Bytes(b) = bid {
ctx.shared.values.bytes[b].type_id = arr_ty;
}
let idx = ctx.get_const(2, 8).id();
match at_id()
.desc()
.simplify(body_view(&ctx, function), at_id(), 4, &[bid, idx])
{
Some(Simplified::Value(ValueId::Literal(l))) => {
assert_eq!(ctx.shared.values.literals[l].value, 0x33);
}
other => panic!("expected literal 0x33, got {other:?}"),
}
}
}