#![cfg(feature = "cuda-oxide-backend")]
use std::collections::HashMap;
use pliron::{
basic_block::BasicBlock,
builtin::{
op_interfaces::OneRegionInterface,
ops::FuncOp,
types::{FP32Type, FP64Type, FunctionType, IntegerType, Signedness},
},
context::{Context, Ptr},
derive::pliron_op,
op::Op,
operation::Operation,
r#type::{TypeObj, TypePtr},
value::Value,
};
use thiserror::Error;
use crate::lowered_ir::{LoweredBlockId, LoweredFunction, LoweredOp, LoweredType, LoweredValueId};
#[pliron_op(name = "twasm.addi", format, verifier = "succ")]
pub struct AddIOp;
#[pliron_op(name = "twasm.subi", format, verifier = "succ")]
pub struct SubIOp;
#[pliron_op(name = "twasm.muli", format, verifier = "succ")]
pub struct MulIOp;
#[pliron_op(name = "twasm.addf", format, verifier = "succ")]
pub struct AddFOp;
#[pliron_op(name = "twasm.subf", format, verifier = "succ")]
pub struct SubFOp;
#[pliron_op(name = "twasm.mulf", format, verifier = "succ")]
pub struct MulFOp;
#[pliron_op(name = "twasm.load", format, verifier = "succ")]
pub struct LoadOp;
#[pliron_op(name = "twasm.store", format, verifier = "succ")]
pub struct StoreOp;
#[pliron_op(name = "twasm.br", format, verifier = "succ")]
pub struct BrOp;
#[pliron_op(name = "twasm.cond_br", format, verifier = "succ")]
pub struct CondBrOp;
#[pliron_op(name = "twasm.return", format, verifier = "succ")]
pub struct ReturnOp;
#[pliron_op(name = "twasm.select", format, verifier = "succ")]
pub struct SelectOp;
#[pliron_op(name = "twasm.bitcast", format, verifier = "succ")]
pub struct BitcastOp;
#[pliron_op(name = "twasm.trunci", format, verifier = "succ")]
pub struct TruncIOp;
#[pliron_op(name = "twasm.extui", format, verifier = "succ")]
pub struct ExtendUOp;
#[pliron_op(name = "twasm.extsi", format, verifier = "succ")]
pub struct ExtendSOp;
#[pliron_op(name = "twasm.alloca", format, verifier = "succ")]
pub struct AllocaOp;
#[derive(Debug, Error)]
pub enum PlironConversionError {
#[error("pliron_lowering: variant {variant} not yet wired (TODO)")]
NotYetWired {
variant: &'static str,
},
#[error("pliron_lowering: pliron context error: {0}")]
Context(String),
#[error("pliron_lowering: undefined SSA value id {0}")]
UndefinedValue(LoweredValueId),
#[error("pliron_lowering: undefined block id {0}")]
UndefinedBlock(LoweredBlockId),
}
fn translate_type(
ctx: &mut Context,
ty: &LoweredType,
) -> Result<Ptr<TypeObj>, PlironConversionError> {
match ty {
LoweredType::I8 => Ok(IntegerType::get(ctx, 8, Signedness::Signless).into()),
LoweredType::I16 => Ok(IntegerType::get(ctx, 16, Signedness::Signless).into()),
LoweredType::I32 => Ok(IntegerType::get(ctx, 32, Signedness::Signless).into()),
LoweredType::I64 => Ok(IntegerType::get(ctx, 64, Signedness::Signless).into()),
LoweredType::F32 => Ok(FP32Type::get(ctx).into()),
LoweredType::F64 => Ok(FP64Type::get(ctx).into()),
LoweredType::Ptr => Ok(IntegerType::get(ctx, 64, Signedness::Signless).into()),
LoweredType::Bool => Ok(IntegerType::get(ctx, 1, Signedness::Signless).into()),
LoweredType::V128 { .. } => Err(PlironConversionError::NotYetWired {
variant: "LoweredType::V128",
}),
}
}
const TWASM_OFFSET_ATTR: &str = "twasm.byte_offset";
fn set_load_store_offset(ctx: &mut Context, op: Ptr<Operation>, offset: i32) {
if let Ok(key) = pliron::identifier::Identifier::try_new(TWASM_OFFSET_ATTR.to_string()) {
let attr = pliron::builtin::attributes::StringAttr::new(offset.to_string());
op.deref_mut(ctx).attributes.set(key, attr);
}
}
struct ConversionState {
values: HashMap<LoweredValueId, Value>,
blocks: HashMap<LoweredBlockId, Ptr<BasicBlock>>,
}
impl ConversionState {
fn new() -> Self {
Self {
values: HashMap::new(),
blocks: HashMap::new(),
}
}
fn get_value(&self, id: LoweredValueId) -> Result<Value, PlironConversionError> {
self.values
.get(&id)
.copied()
.ok_or(PlironConversionError::UndefinedValue(id))
}
fn get_block(&self, id: LoweredBlockId) -> Result<Ptr<BasicBlock>, PlironConversionError> {
self.blocks
.get(&id)
.copied()
.ok_or(PlironConversionError::UndefinedBlock(id))
}
fn map_value(&mut self, id: LoweredValueId, value: Value) {
self.values.insert(id, value);
}
fn map_block(&mut self, id: LoweredBlockId, block: Ptr<BasicBlock>) {
self.blocks.insert(id, block);
}
}
pub fn lowered_function_to_pliron(
ctx: &mut Context,
func: &LoweredFunction,
) -> Result<Ptr<Operation>, PlironConversionError> {
let param_types: Vec<Ptr<TypeObj>> = func
.signature
.params
.iter()
.map(|t| translate_type(ctx, t))
.collect::<Result<_, _>>()?;
let return_types: Vec<Ptr<TypeObj>> = func
.signature
.returns
.iter()
.map(|t| translate_type(ctx, t))
.collect::<Result<_, _>>()?;
let func_ty: TypePtr<FunctionType> = FunctionType::get(ctx, param_types.clone(), return_types);
let name_ident: pliron::identifier::Identifier = func
.name
.as_str()
.try_into()
.map_err(|e| PlironConversionError::Context(format!("invalid function name: {e}")))?;
let func_op = FuncOp::new(ctx, name_ident, func_ty);
let func_op_ptr = func_op.get_operation();
let mut state = ConversionState::new();
let entry_block = func_op.get_entry_block(ctx);
let entry_lowered = func
.blocks
.iter()
.find(|b| b.id == func.entry)
.ok_or(PlironConversionError::UndefinedBlock(func.entry))?;
{
let entry_block_ref = entry_block.deref(ctx);
for (idx, (param_id, _)) in entry_lowered.params.iter().enumerate() {
if idx >= entry_block_ref.get_num_arguments() {
return Err(PlironConversionError::Context(format!(
"entry block has {} params but FuncOp entry block has {} args",
entry_lowered.params.len(),
entry_block_ref.get_num_arguments()
)));
}
state
.values
.insert(*param_id, entry_block_ref.get_argument(idx));
}
}
state.map_block(entry_lowered.id, entry_block);
let region = func_op.get_region(ctx);
for lowered_block in &func.blocks {
if lowered_block.id == func.entry {
continue;
}
let arg_types: Vec<Ptr<TypeObj>> = lowered_block
.params
.iter()
.map(|(_, ty)| translate_type(ctx, ty))
.collect::<Result<_, _>>()?;
let label = format!("bb{}", lowered_block.id).as_str().try_into().ok();
let bb = BasicBlock::new(ctx, label, arg_types);
bb.insert_at_back(region, ctx);
{
let bb_ref = bb.deref(ctx);
for (idx, (param_id, _)) in lowered_block.params.iter().enumerate() {
state.values.insert(*param_id, bb_ref.get_argument(idx));
}
}
state.map_block(lowered_block.id, bb);
}
for lowered_block in &func.blocks {
let bb = state.get_block(lowered_block.id)?;
for op in &lowered_block.ops {
emit_op(ctx, &mut state, bb, op)?;
}
}
Ok(func_op_ptr)
}
fn emit_op(
ctx: &mut Context,
state: &mut ConversionState,
bb: Ptr<BasicBlock>,
op: &LoweredOp,
) -> Result<(), PlironConversionError> {
match op {
LoweredOp::AddI {
ty,
lhs,
rhs,
result,
} => emit_binary::<AddIOp>(ctx, state, bb, ty, *lhs, *rhs, *result),
LoweredOp::SubI {
ty,
lhs,
rhs,
result,
} => emit_binary::<SubIOp>(ctx, state, bb, ty, *lhs, *rhs, *result),
LoweredOp::MulI {
ty,
lhs,
rhs,
result,
} => emit_binary::<MulIOp>(ctx, state, bb, ty, *lhs, *rhs, *result),
LoweredOp::AddF {
ty,
lhs,
rhs,
result,
} => emit_binary::<AddFOp>(ctx, state, bb, ty, *lhs, *rhs, *result),
LoweredOp::SubF {
ty,
lhs,
rhs,
result,
} => emit_binary::<SubFOp>(ctx, state, bb, ty, *lhs, *rhs, *result),
LoweredOp::MulF {
ty,
lhs,
rhs,
result,
} => emit_binary::<MulFOp>(ctx, state, bb, ty, *lhs, *rhs, *result),
LoweredOp::Load {
ty,
base,
offset,
result,
} => {
let base_v = state.get_value(*base)?;
let result_ty = translate_type(ctx, ty)?;
let pop = Operation::new(
ctx,
<LoadOp as Op>::get_concrete_op_info(),
vec![result_ty],
vec![base_v],
vec![],
0,
);
set_load_store_offset(ctx, pop, *offset);
pop.insert_at_back(bb, ctx);
let v = pop.deref(ctx).get_result(0);
state.map_value(*result, v);
Ok(())
}
LoweredOp::Store {
ty: _,
value,
base,
offset,
} => {
let v = state.get_value(*value)?;
let base_v = state.get_value(*base)?;
let pop = Operation::new(
ctx,
<StoreOp as Op>::get_concrete_op_info(),
vec![],
vec![v, base_v],
vec![],
0,
);
set_load_store_offset(ctx, pop, *offset);
pop.insert_at_back(bb, ctx);
Ok(())
}
LoweredOp::Br { target, args } => {
let target_bb = state.get_block(*target)?;
let operand_vals: Vec<Value> = args
.iter()
.map(|id| state.get_value(*id))
.collect::<Result<_, _>>()?;
let pop = Operation::new(
ctx,
<BrOp as Op>::get_concrete_op_info(),
vec![],
operand_vals,
vec![target_bb],
0,
);
pop.insert_at_back(bb, ctx);
Ok(())
}
LoweredOp::CondBr {
cond,
then_target,
then_args,
else_target,
else_args,
} => {
let cond_v = state.get_value(*cond)?;
let then_bb = state.get_block(*then_target)?;
let else_bb = state.get_block(*else_target)?;
let mut operand_vals = vec![cond_v];
for id in then_args.iter().chain(else_args.iter()) {
operand_vals.push(state.get_value(*id)?);
}
let pop = Operation::new(
ctx,
<CondBrOp as Op>::get_concrete_op_info(),
vec![],
operand_vals,
vec![then_bb, else_bb],
0,
);
pop.insert_at_back(bb, ctx);
Ok(())
}
LoweredOp::Return { values } => {
let operand_vals: Vec<Value> = values
.iter()
.map(|id| state.get_value(*id))
.collect::<Result<_, _>>()?;
let pop = Operation::new(
ctx,
<ReturnOp as Op>::get_concrete_op_info(),
vec![],
operand_vals,
vec![],
0,
);
pop.insert_at_back(bb, ctx);
Ok(())
}
LoweredOp::StackAlloc { ty, bytes, result } => {
let ptr_ty = translate_type(ctx, &LoweredType::Ptr)?;
let pop = Operation::new(
ctx,
<AllocaOp as Op>::get_concrete_op_info(),
vec![ptr_ty],
vec![],
vec![],
0,
);
set_stack_alloc_attrs(ctx, pop, ty, *bytes);
pop.insert_at_back(bb, ctx);
let v = pop.deref(ctx).get_result(0);
state.map_value(*result, v);
Ok(())
}
LoweredOp::Select {
ty,
cond,
then_v,
else_v,
result,
} => {
let cond_v = state.get_value(*cond)?;
let then_val = state.get_value(*then_v)?;
let else_val = state.get_value(*else_v)?;
let result_ty = translate_type(ctx, ty)?;
let pop = Operation::new(
ctx,
<SelectOp as Op>::get_concrete_op_info(),
vec![result_ty],
vec![cond_v, then_val, else_val],
vec![],
0,
);
pop.insert_at_back(bb, ctx);
let v = pop.deref(ctx).get_result(0);
state.map_value(*result, v);
Ok(())
}
LoweredOp::Bitcast {
to_ty, src, result, ..
} => emit_unary_cast::<BitcastOp>(ctx, state, bb, to_ty, *src, *result),
LoweredOp::TruncI {
to_ty, src, result, ..
} => emit_unary_cast::<TruncIOp>(ctx, state, bb, to_ty, *src, *result),
LoweredOp::ExtendU {
to_ty, src, result, ..
} => emit_unary_cast::<ExtendUOp>(ctx, state, bb, to_ty, *src, *result),
LoweredOp::ExtendS {
to_ty, src, result, ..
} => emit_unary_cast::<ExtendSOp>(ctx, state, bb, to_ty, *src, *result),
LoweredOp::DivS { .. } => Err(PlironConversionError::NotYetWired { variant: "DivS" }),
LoweredOp::DivU { .. } => Err(PlironConversionError::NotYetWired { variant: "DivU" }),
LoweredOp::RemS { .. } => Err(PlironConversionError::NotYetWired { variant: "RemS" }),
LoweredOp::RemU { .. } => Err(PlironConversionError::NotYetWired { variant: "RemU" }),
LoweredOp::DivF { .. } => Err(PlironConversionError::NotYetWired { variant: "DivF" }),
LoweredOp::Fma { .. } => Err(PlironConversionError::NotYetWired { variant: "Fma" }),
LoweredOp::FNeg { .. } => Err(PlironConversionError::NotYetWired { variant: "FNeg" }),
LoweredOp::FAbs { .. } => Err(PlironConversionError::NotYetWired { variant: "FAbs" }),
LoweredOp::Switch { .. } => Err(PlironConversionError::NotYetWired { variant: "Switch" }),
LoweredOp::VMin { .. } => Err(PlironConversionError::NotYetWired { variant: "VMin" }),
LoweredOp::VMax { .. } => Err(PlironConversionError::NotYetWired { variant: "VMax" }),
LoweredOp::VSplat { .. } => Err(PlironConversionError::NotYetWired { variant: "VSplat" }),
LoweredOp::VSelect { .. } => Err(PlironConversionError::NotYetWired { variant: "VSelect" }),
LoweredOp::VAllTrue { .. } => Err(PlironConversionError::NotYetWired {
variant: "VAllTrue",
}),
LoweredOp::VAnyTrue { .. } => Err(PlironConversionError::NotYetWired {
variant: "VAnyTrue",
}),
}
}
fn emit_unary_cast<O: Op>(
ctx: &mut Context,
state: &mut ConversionState,
bb: Ptr<BasicBlock>,
to_ty: &LoweredType,
src: LoweredValueId,
result: LoweredValueId,
) -> Result<(), PlironConversionError> {
let src_v = state.get_value(src)?;
let result_ty = translate_type(ctx, to_ty)?;
let pop = Operation::new(
ctx,
<O as Op>::get_concrete_op_info(),
vec![result_ty],
vec![src_v],
vec![],
0,
);
pop.insert_at_back(bb, ctx);
let v = pop.deref(ctx).get_result(0);
state.map_value(result, v);
Ok(())
}
fn set_stack_alloc_attrs(ctx: &mut Context, op: Ptr<Operation>, ty: &LoweredType, bytes: u32) {
if let Ok(key) = pliron::identifier::Identifier::try_new("twasm.alloca_bytes".to_string()) {
let attr = pliron::builtin::attributes::StringAttr::new(bytes.to_string());
op.deref_mut(ctx).attributes.set(key, attr);
}
if let Ok(key) = pliron::identifier::Identifier::try_new("twasm.alloca_elem".to_string()) {
let attr = pliron::builtin::attributes::StringAttr::new(format!("{ty}"));
op.deref_mut(ctx).attributes.set(key, attr);
}
}
fn emit_binary<O: Op>(
ctx: &mut Context,
state: &mut ConversionState,
bb: Ptr<BasicBlock>,
ty: &LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
) -> Result<(), PlironConversionError> {
let lhs_v = state.get_value(lhs)?;
let rhs_v = state.get_value(rhs)?;
let result_ty = translate_type(ctx, ty)?;
let pop = Operation::new(
ctx,
<O as Op>::get_concrete_op_info(),
vec![result_ty],
vec![lhs_v, rhs_v],
vec![],
0,
);
pop.insert_at_back(bb, ctx);
let v = pop.deref(ctx).get_result(0);
state.map_value(result, v);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lowered_ir::{LoweredBlock, LoweredSignature};
use pliron::linked_list::ContainsLinkedList;
fn addi_fn() -> LoweredFunction {
let mut f = LoweredFunction::new(
"add",
LoweredSignature {
params: vec![LoweredType::I32, LoweredType::I32],
returns: vec![LoweredType::I32],
},
);
let mut b0 = LoweredBlock::new(0);
b0.params = vec![(1, LoweredType::I32), (2, LoweredType::I32)];
b0.ops.push(LoweredOp::AddI {
ty: LoweredType::I32,
lhs: 1,
rhs: 2,
result: 3,
});
b0.ops.push(LoweredOp::Return { values: vec![3] });
f.blocks.push(b0);
f.entry = 0;
f
}
fn addf_fn() -> LoweredFunction {
let mut f = LoweredFunction::new(
"addf",
LoweredSignature {
params: vec![LoweredType::F32, LoweredType::F32],
returns: vec![LoweredType::F32],
},
);
let mut b0 = LoweredBlock::new(0);
b0.params = vec![(1, LoweredType::F32), (2, LoweredType::F32)];
b0.ops.push(LoweredOp::AddF {
ty: LoweredType::F32,
lhs: 1,
rhs: 2,
result: 3,
});
b0.ops.push(LoweredOp::Return { values: vec![3] });
f.blocks.push(b0);
f.entry = 0;
f
}
#[test]
fn convert_addi_single_block() {
let mut ctx = Context::new();
let func = addi_fn();
let op =
lowered_function_to_pliron(&mut ctx, &func).expect("AddI conversion should succeed");
let op_ref = op.deref(&ctx);
let opid = Operation::get_opid(op, &ctx);
assert_eq!(
opid.to_string(),
"builtin.func",
"expected builtin.func, got {opid}"
);
assert_eq!(op_ref.regions().count(), 1);
let region = op_ref.get_region(0);
let block_count = region.deref(&ctx).iter(&ctx).count();
assert_eq!(block_count, 1, "expected 1 block, got {block_count}");
let entry = region.deref(&ctx).get_head().expect("entry block");
let entry_ops: Vec<_> = entry.deref(&ctx).iter(&ctx).collect();
assert_eq!(
entry_ops.len(),
2,
"expected 2 ops, got {}",
entry_ops.len()
);
let first = entry_ops[0];
let first_id = Operation::get_opid(first, &ctx);
assert_eq!(first_id.to_string(), "twasm.addi");
let second = entry_ops[1];
let second_id = Operation::get_opid(second, &ctx);
assert_eq!(second_id.to_string(), "twasm.return");
}
#[test]
fn convert_addf_single_block() {
let mut ctx = Context::new();
let func = addf_fn();
let op =
lowered_function_to_pliron(&mut ctx, &func).expect("AddF conversion should succeed");
let op_ref = op.deref(&ctx);
let opid = Operation::get_opid(op, &ctx);
assert_eq!(opid.to_string(), "builtin.func");
let region = op_ref.get_region(0);
let entry = region.deref(&ctx).get_head().expect("entry block");
let entry_ops: Vec<_> = entry.deref(&ctx).iter(&ctx).collect();
assert_eq!(entry_ops.len(), 2);
let first_id = Operation::get_opid(entry_ops[0], &ctx);
assert_eq!(first_id.to_string(), "twasm.addf");
}
#[test]
fn load_offset_is_recorded_not_dropped() {
let mut ctx = Context::new();
let mut f = LoweredFunction::new(
"load_off",
LoweredSignature {
params: vec![LoweredType::Ptr],
returns: vec![LoweredType::I32],
},
);
let mut b0 = LoweredBlock::new(0);
b0.params = vec![(1, LoweredType::Ptr)];
b0.ops.push(LoweredOp::Load {
ty: LoweredType::I32,
base: 1,
offset: 16,
result: 2,
});
b0.ops.push(LoweredOp::Return { values: vec![2] });
f.blocks.push(b0);
f.entry = 0;
let op = lowered_function_to_pliron(&mut ctx, &f).expect("load+return must convert");
let region = op.deref(&ctx).get_region(0);
let entry = region.deref(&ctx).get_head().expect("entry block");
let entry_ops: Vec<_> = entry.deref(&ctx).iter(&ctx).collect();
let load_op = entry_ops[0];
assert_eq!(Operation::get_opid(load_op, &ctx).to_string(), "twasm.load");
let key = pliron::identifier::Identifier::try_new(TWASM_OFFSET_ATTR.to_string()).unwrap();
let recorded = load_op
.deref(&ctx)
.attributes
.get::<pliron::builtin::attributes::StringAttr>(&key)
.map(|a| String::from(a.clone()));
assert_eq!(
recorded.as_deref(),
Some("16"),
"load offset 16 must be recorded on the twasm.load op, got {recorded:?}"
);
}
#[test]
fn select_is_wired() {
let mut ctx = Context::new();
let mut f = LoweredFunction::new(
"sel",
LoweredSignature {
params: vec![LoweredType::Bool, LoweredType::I32, LoweredType::I32],
returns: vec![LoweredType::I32],
},
);
let mut b0 = LoweredBlock::new(0);
b0.params = vec![
(1, LoweredType::Bool),
(2, LoweredType::I32),
(3, LoweredType::I32),
];
b0.ops.push(LoweredOp::Select {
ty: LoweredType::I32,
cond: 1,
then_v: 2,
else_v: 3,
result: 4,
});
b0.ops.push(LoweredOp::Return { values: vec![4] });
f.blocks.push(b0);
f.entry = 0;
let op = lowered_function_to_pliron(&mut ctx, &f).expect("select must convert");
let region = op.deref(&ctx).get_region(0);
let entry = region.deref(&ctx).get_head().expect("entry block");
let entry_ops: Vec<_> = entry.deref(&ctx).iter(&ctx).collect();
assert_eq!(
Operation::get_opid(entry_ops[0], &ctx).to_string(),
"twasm.select"
);
}
#[test]
fn integer_extend_is_wired() {
let mut ctx = Context::new();
let mut f = LoweredFunction::new(
"ext",
LoweredSignature {
params: vec![LoweredType::I32],
returns: vec![LoweredType::I64],
},
);
let mut b0 = LoweredBlock::new(0);
b0.params = vec![(1, LoweredType::I32)];
b0.ops.push(LoweredOp::ExtendU {
from_ty: LoweredType::I32,
to_ty: LoweredType::I64,
src: 1,
result: 2,
});
b0.ops.push(LoweredOp::Return { values: vec![2] });
f.blocks.push(b0);
f.entry = 0;
let op = lowered_function_to_pliron(&mut ctx, &f).expect("extend must convert");
let region = op.deref(&ctx).get_region(0);
let entry = region.deref(&ctx).get_head().expect("entry block");
let entry_ops: Vec<_> = entry.deref(&ctx).iter(&ctx).collect();
assert_eq!(
Operation::get_opid(entry_ops[0], &ctx).to_string(),
"twasm.extui"
);
}
#[test]
fn not_yet_wired_for_vmin() {
let mut ctx = Context::new();
let mut f = LoweredFunction::new(
"vmin_stub",
LoweredSignature {
params: vec![],
returns: vec![],
},
);
let mut b0 = LoweredBlock::new(0);
b0.ops.push(LoweredOp::VMin {
lane_ty: LoweredType::I32,
signed: true,
lhs: 1,
rhs: 2,
result: 3,
});
b0.ops.push(LoweredOp::Return { values: vec![] });
f.blocks.push(b0);
let err = lowered_function_to_pliron(&mut ctx, &f).expect_err("VMin should not be wired");
match err {
PlironConversionError::NotYetWired { variant } => assert_eq!(variant, "VMin"),
other => panic!("expected NotYetWired{{ variant: VMin }}, got {other:?}"),
}
}
#[test]
fn convert_load_store_round_trip() {
let mut ctx = Context::new();
let mut f = LoweredFunction::new(
"load_store",
LoweredSignature {
params: vec![LoweredType::Ptr, LoweredType::I32],
returns: vec![],
},
);
let mut b0 = LoweredBlock::new(0);
b0.params = vec![(1, LoweredType::Ptr), (2, LoweredType::I32)];
b0.ops.push(LoweredOp::Load {
ty: LoweredType::I32,
base: 1,
offset: 0,
result: 3,
});
b0.ops.push(LoweredOp::Store {
ty: LoweredType::I32,
value: 3,
base: 1,
offset: 0,
});
b0.ops.push(LoweredOp::Return { values: vec![] });
f.blocks.push(b0);
let op =
lowered_function_to_pliron(&mut ctx, &f).expect("Load/Store conversion should succeed");
let region = op.deref(&ctx).get_region(0);
let entry = region.deref(&ctx).get_head().expect("entry block");
let entry_ops: Vec<_> = entry.deref(&ctx).iter(&ctx).collect();
assert_eq!(entry_ops.len(), 3, "expected load + store + return");
assert_eq!(
Operation::get_opid(entry_ops[0], &ctx).to_string(),
"twasm.load"
);
assert_eq!(
Operation::get_opid(entry_ops[1], &ctx).to_string(),
"twasm.store"
);
assert_eq!(
Operation::get_opid(entry_ops[2], &ctx).to_string(),
"twasm.return"
);
}
#[test]
fn convert_multi_block_branch() {
let mut ctx = Context::new();
let mut f = LoweredFunction::new(
"br_thru",
LoweredSignature {
params: vec![LoweredType::I32],
returns: vec![LoweredType::I32],
},
);
let mut entry = LoweredBlock::new(0);
entry.params = vec![(1, LoweredType::I32)];
entry.ops.push(LoweredOp::Br {
target: 1,
args: vec![1],
});
let mut bb1 = LoweredBlock::new(1);
bb1.params = vec![(2, LoweredType::I32)];
bb1.ops.push(LoweredOp::Return { values: vec![2] });
f.blocks.push(entry);
f.blocks.push(bb1);
f.entry = 0;
let op = lowered_function_to_pliron(&mut ctx, &f)
.expect("multi-block conversion should succeed");
let region = op.deref(&ctx).get_region(0);
let block_count = region.deref(&ctx).iter(&ctx).count();
assert_eq!(block_count, 2, "expected 2 blocks, got {block_count}");
}
#[test]
fn undefined_block_target_surfaces() {
let mut ctx = Context::new();
let mut f = LoweredFunction::new(
"bad_br",
LoweredSignature {
params: vec![],
returns: vec![],
},
);
let mut b0 = LoweredBlock::new(0);
b0.ops.push(LoweredOp::Br {
target: 99,
args: vec![],
});
f.blocks.push(b0);
let err =
lowered_function_to_pliron(&mut ctx, &f).expect_err("missing target should surface");
match err {
PlironConversionError::UndefinedBlock(99) => {}
other => panic!("expected UndefinedBlock(99), got {other:?}"),
}
}
}