#![allow(clippy::result_large_err)]
pub mod lower;
use cljrs_types::error::CljxError::SerializationError;
use cljrs_types::error::CljxResult;
use cljrs_types::span::Span;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Display;
use std::sync::Arc;
thread_local! {
static INDENT: RefCell<i16> = const { RefCell::new(0) }
}
fn indent() -> String {
INDENT.with(|indent| (0..*indent.borrow()).map(|_| " ").collect())
}
fn indent_inc() {
INDENT.with(|indent| *indent.borrow_mut() += 2);
}
fn indent_dec() {
INDENT.with(|indent| *indent.borrow_mut() -= 2);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct VarId(pub u32);
impl Display for VarId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "v{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BlockId(pub u32);
impl fmt::Display for BlockId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "bb{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum KnownFn {
Vector,
HashMap,
HashSet,
List,
Assoc,
Dissoc,
Conj,
Disj,
Get,
Nth,
Count,
Contains,
Transient,
AssocBang,
ConjBang,
PersistentBang,
First,
Rest,
Next,
Cons,
Seq,
LazySeq,
Add,
Sub,
Mul,
Div,
Rem,
Eq,
Lt,
Gt,
Lte,
Gte,
IsNil,
IsSeq,
IsVector,
IsMap,
Str,
Deref,
Identical,
Println,
Pr,
AtomDeref,
AtomReset,
AtomSwap,
Apply,
Reduce2,
Reduce3,
Map,
Filter,
Mapv,
Filterv,
Some,
Every,
Into,
Into3,
GroupBy,
Partition2,
Partition3,
Partition4,
Frequencies,
Keep,
Remove,
MapIndexed,
Zipmap,
Juxt,
Comp,
Partial,
Complement,
Concat,
Range1,
Range2,
Range3,
Take,
Drop,
Reverse,
Sort,
SortBy,
Keys,
Vals,
Merge,
Update,
GetIn,
AssocIn,
IsNumber,
IsString,
IsKeyword,
IsSymbol,
IsBool,
IsInt,
Prn,
Print,
Atom,
TryCatchFinally,
SetBangVar,
WithBindings,
WithOutStr,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Effect {
Pure,
Alloc,
HeapRead,
HeapWrite,
IO,
UnknownCall,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Const {
Nil,
Bool(bool),
Long(i64),
Double(f64),
Str(Arc<str>),
Keyword(Arc<str>),
Symbol(Arc<str>),
Char(char),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Inst {
Const(VarId, Const),
LoadLocal(VarId, Arc<str>),
LoadGlobal(VarId, Arc<str>, Arc<str>),
LoadVar(VarId, Arc<str>, Arc<str>),
AllocVector(VarId, Vec<VarId>),
AllocMap(VarId, Vec<(VarId, VarId)>),
AllocSet(VarId, Vec<VarId>),
AllocList(VarId, Vec<VarId>),
AllocCons(VarId, VarId, VarId),
AllocClosure(VarId, ClosureTemplate, Vec<VarId>),
CallKnown(VarId, KnownFn, Vec<VarId>),
Call(VarId, VarId, Vec<VarId>),
CallDirect(VarId, Arc<str>, Vec<VarId>),
Deref(VarId, VarId),
DefVar(VarId, Arc<str>, Arc<str>, VarId),
SetBang(VarId, VarId),
Throw(VarId),
Phi(VarId, Vec<(BlockId, VarId)>),
Recur(Vec<VarId>),
SourceLoc(Span),
RegionStart(VarId),
RegionAlloc(VarId, VarId, RegionAllocKind, Vec<VarId>),
RegionEnd(VarId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RegionAllocKind {
Vector,
Map,
Set,
List,
Cons,
}
impl fmt::Display for RegionAllocKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Vector => write!(f, "vector"),
Self::Map => write!(f, "map"),
Self::Set => write!(f, "set"),
Self::List => write!(f, "list"),
Self::Cons => write!(f, "cons"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClosureTemplate {
pub name: Option<Arc<str>>,
pub arity_fn_names: Vec<Arc<str>>,
pub param_counts: Vec<usize>,
pub is_variadic: Vec<bool>,
pub capture_names: Vec<Arc<str>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Terminator {
Jump(BlockId),
Branch {
cond: VarId,
then_block: BlockId,
else_block: BlockId,
},
Return(VarId),
RecurJump { target: BlockId, args: Vec<VarId> },
Unreachable,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Block {
pub id: BlockId,
pub phis: Vec<Inst>,
pub insts: Vec<Inst>,
pub terminator: Terminator,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IrFunction {
pub name: Option<Arc<str>>,
pub params: Vec<(Arc<str>, VarId)>,
pub blocks: Vec<Block>,
pub next_var: u32,
pub next_block: u32,
pub span: Option<Span>,
pub subfunctions: Vec<IrFunction>,
}
impl IrFunction {
pub fn new(name: Option<Arc<str>>, span: Option<Span>) -> Self {
Self {
name,
params: Vec::new(),
blocks: Vec::new(),
next_var: 0,
next_block: 0,
span,
subfunctions: Vec::new(),
}
}
pub fn fresh_var(&mut self) -> VarId {
let id = VarId(self.next_var);
self.next_var += 1;
id
}
pub fn fresh_block(&mut self) -> BlockId {
let id = BlockId(self.next_block);
self.next_block += 1;
id
}
pub fn block_index(&self) -> Option<Vec<usize>> {
let is_identity = self
.blocks
.iter()
.enumerate()
.all(|(i, b)| b.id.0 as usize == i);
if is_identity {
return None; }
let max_id = self.blocks.iter().map(|b| b.id.0).max().unwrap_or(0);
let mut table = vec![0usize; max_id as usize + 1];
for (i, b) in self.blocks.iter().enumerate() {
table[b.id.0 as usize] = i;
}
Some(table)
}
pub fn serialize(&self) -> CljxResult<Vec<u8>> {
postcard::to_allocvec(self).map_err(|e| SerializationError {
message: e.to_string(),
})
}
pub fn deserialize(bytes: &[u8]) -> CljxResult<Self> {
postcard::from_bytes(bytes).map_err(|e| SerializationError {
message: e.to_string(),
})
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct IrBundle {
pub functions: HashMap<String, IrFunction>,
}
impl Display for IrBundle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("IrBundle {\n")?;
f.write_str(" functions: {\n")?;
indent_inc();
self.functions.iter().try_for_each(|(name, function)| {
f.write_fmt(format_args!(" \"{}\": {}", name, function))?;
Ok(())
})?;
indent_dec();
f.write_str(" }\n")?;
f.write_str("}\n")?;
Ok(())
}
}
impl IrBundle {
pub fn new() -> Self {
Self {
functions: HashMap::new(),
}
}
pub fn insert(&mut self, key: String, func: IrFunction) {
self.functions.insert(key, func);
}
pub fn get(&self, key: &str) -> Option<&IrFunction> {
self.functions.get(key)
}
pub fn len(&self) -> usize {
self.functions.len()
}
pub fn is_empty(&self) -> bool {
self.functions.is_empty()
}
}
impl Default for IrBundle {
fn default() -> Self {
Self::new()
}
}
pub fn serialize_bundle(bundle: &IrBundle) -> CljxResult<Vec<u8>> {
postcard::to_allocvec(bundle).map_err(|e| SerializationError {
message: e.to_string(),
})
}
pub fn deserialize_bundle(bytes: &[u8]) -> CljxResult<IrBundle> {
postcard::from_bytes(bytes).map_err(|e| SerializationError {
message: e.to_string(),
})
}
impl Inst {
pub fn effect(&self) -> Effect {
match self {
Inst::Const(..) | Inst::LoadLocal(..) | Inst::Phi(..) | Inst::SourceLoc(..) => {
Effect::Pure
}
Inst::LoadGlobal(..) | Inst::LoadVar(..) => Effect::HeapRead,
Inst::AllocVector(..)
| Inst::AllocMap(..)
| Inst::AllocSet(..)
| Inst::AllocList(..)
| Inst::AllocCons(..)
| Inst::AllocClosure(..) => Effect::Alloc,
Inst::CallKnown(_, known, _) => known.effect(),
Inst::Call(..) | Inst::CallDirect(..) => Effect::UnknownCall,
Inst::Deref(..) => Effect::HeapRead,
Inst::DefVar(..) => Effect::HeapWrite,
Inst::SetBang(..) => Effect::HeapWrite,
Inst::Throw(..) => Effect::UnknownCall, Inst::Recur(..) => Effect::Pure,
Inst::RegionStart(..) | Inst::RegionEnd(..) => Effect::Alloc,
Inst::RegionAlloc(..) => Effect::Alloc,
}
}
pub fn dst(&self) -> Option<VarId> {
match self {
Inst::Const(v, _)
| Inst::LoadLocal(v, _)
| Inst::LoadGlobal(v, _, _)
| Inst::LoadVar(v, _, _)
| Inst::AllocVector(v, _)
| Inst::AllocMap(v, _)
| Inst::AllocSet(v, _)
| Inst::AllocList(v, _)
| Inst::AllocCons(v, _, _)
| Inst::AllocClosure(v, _, _)
| Inst::CallKnown(v, _, _)
| Inst::Call(v, _, _)
| Inst::CallDirect(v, _, _)
| Inst::Deref(v, _)
| Inst::DefVar(v, _, _, _)
| Inst::Phi(v, _)
| Inst::RegionStart(v)
| Inst::RegionAlloc(v, _, _, _) => Some(*v),
Inst::SetBang(..)
| Inst::Throw(..)
| Inst::Recur(..)
| Inst::SourceLoc(..)
| Inst::RegionEnd(..) => None,
}
}
pub fn uses(&self) -> Vec<VarId> {
match self {
Inst::Const(..)
| Inst::LoadLocal(..)
| Inst::LoadGlobal(..)
| Inst::LoadVar(..)
| Inst::SourceLoc(..) => vec![],
Inst::AllocVector(_, elems) | Inst::AllocSet(_, elems) | Inst::AllocList(_, elems) => {
elems.clone()
}
Inst::AllocMap(_, pairs) => pairs.iter().flat_map(|(k, v)| [*k, *v]).collect(),
Inst::AllocCons(_, h, t) => vec![*h, *t],
Inst::AllocClosure(_, _, captures) => captures.clone(),
Inst::CallKnown(_, _, args) => args.clone(),
Inst::Call(_, callee, args) => {
let mut v = vec![*callee];
v.extend(args);
v
}
Inst::CallDirect(_, _, args) => args.clone(),
Inst::Deref(_, src) => vec![*src],
Inst::DefVar(_, _, _, val) => vec![*val],
Inst::SetBang(var, val) => vec![*var, *val],
Inst::Throw(val) => vec![*val],
Inst::Phi(_, entries) => entries.iter().map(|(_, v)| *v).collect(),
Inst::Recur(args) => args.clone(),
Inst::RegionStart(..) => vec![],
Inst::RegionAlloc(_, region, _, operands) => {
let mut v = vec![*region];
v.extend(operands);
v
}
Inst::RegionEnd(region) => vec![*region],
}
}
}
impl KnownFn {
pub fn effect(&self) -> Effect {
match self {
KnownFn::Get
| KnownFn::Nth
| KnownFn::Count
| KnownFn::Contains
| KnownFn::First
| KnownFn::Add
| KnownFn::Sub
| KnownFn::Mul
| KnownFn::Div
| KnownFn::Rem
| KnownFn::Eq
| KnownFn::Lt
| KnownFn::Gt
| KnownFn::Lte
| KnownFn::Gte
| KnownFn::IsNil
| KnownFn::IsSeq
| KnownFn::IsVector
| KnownFn::IsMap
| KnownFn::Identical => Effect::Pure,
KnownFn::Vector
| KnownFn::HashMap
| KnownFn::HashSet
| KnownFn::List
| KnownFn::Assoc
| KnownFn::Dissoc
| KnownFn::Conj
| KnownFn::Disj
| KnownFn::Cons
| KnownFn::Rest
| KnownFn::Next
| KnownFn::Seq
| KnownFn::LazySeq
| KnownFn::Str
| KnownFn::Transient
| KnownFn::PersistentBang => Effect::Alloc,
KnownFn::AssocBang | KnownFn::ConjBang => Effect::HeapWrite,
KnownFn::Deref | KnownFn::AtomDeref => Effect::HeapRead,
KnownFn::AtomReset | KnownFn::AtomSwap => Effect::HeapWrite,
KnownFn::Println | KnownFn::Pr => Effect::IO,
KnownFn::Apply => Effect::UnknownCall,
KnownFn::Concat
| KnownFn::Range1
| KnownFn::Range2
| KnownFn::Range3
| KnownFn::Take
| KnownFn::Drop
| KnownFn::Reverse => Effect::Alloc,
KnownFn::Sort | KnownFn::SortBy => Effect::UnknownCall,
KnownFn::Keys | KnownFn::Vals => Effect::Alloc,
KnownFn::Merge | KnownFn::Update | KnownFn::GetIn | KnownFn::AssocIn => Effect::Alloc,
KnownFn::IsNumber
| KnownFn::IsString
| KnownFn::IsKeyword
| KnownFn::IsSymbol
| KnownFn::IsBool
| KnownFn::IsInt => Effect::Pure,
KnownFn::Prn | KnownFn::Print => Effect::IO,
KnownFn::Atom => Effect::Alloc,
KnownFn::GroupBy
| KnownFn::Partition2
| KnownFn::Partition3
| KnownFn::Partition4
| KnownFn::Keep
| KnownFn::Remove
| KnownFn::MapIndexed => Effect::UnknownCall,
KnownFn::Juxt | KnownFn::Comp | KnownFn::Partial | KnownFn::Complement => {
Effect::UnknownCall
}
KnownFn::Frequencies | KnownFn::Zipmap => Effect::Alloc,
KnownFn::Reduce2
| KnownFn::Reduce3
| KnownFn::Map
| KnownFn::Filter
| KnownFn::Mapv
| KnownFn::Filterv
| KnownFn::Some
| KnownFn::Every
| KnownFn::Into
| KnownFn::Into3 => Effect::UnknownCall,
KnownFn::TryCatchFinally => Effect::UnknownCall,
KnownFn::SetBangVar => Effect::HeapWrite,
KnownFn::WithBindings | KnownFn::WithOutStr => Effect::UnknownCall,
}
}
}
impl fmt::Display for IrFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"{}fn {}({}):",
indent(),
self.name.as_deref().unwrap_or("<anon>"),
self.params
.iter()
.map(|(name, id)| format!("{name}: {id}"))
.collect::<Vec<_>>()
.join(", ")
)?;
indent_inc();
for block in &self.blocks {
writeln!(f, "{} {block}", indent())?;
}
indent_dec();
Ok(())
}
}
impl Display for Block {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{}{}:", self.id, indent())?;
for phi in &self.phis {
writeln!(f, "{} {phi}", indent())?;
}
for inst in &self.insts {
writeln!(f, "{} {inst}", indent())?;
}
write!(f, "{} {}", indent(), self.terminator)
}
}
impl Display for Inst {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Inst::Const(dst, c) => write!(f, "{dst} = const {c:?}"),
Inst::LoadLocal(dst, name) => write!(f, "{dst} = load_local {name:?}"),
Inst::LoadGlobal(dst, ns, name) => write!(f, "{dst} = load_global {ns}/{name}"),
Inst::LoadVar(dst, ns, name) => write!(f, "{dst} = load_var {ns}/{name}"),
Inst::AllocVector(dst, elems) => write!(f, "{dst} = alloc_vec {elems:?}"),
Inst::AllocMap(dst, pairs) => write!(f, "{dst} = alloc_map {pairs:?}"),
Inst::AllocSet(dst, elems) => write!(f, "{dst} = alloc_set {elems:?}"),
Inst::AllocList(dst, elems) => write!(f, "{dst} = alloc_list {elems:?}"),
Inst::AllocCons(dst, h, t) => write!(f, "{dst} = cons {h} {t}"),
Inst::AllocClosure(dst, tmpl, captures) => {
write!(f, "{dst} = closure {:?} captures={captures:?}", tmpl.name)
}
Inst::CallKnown(dst, func, args) => write!(f, "{dst} = call_known {func:?} {args:?}"),
Inst::Call(dst, callee, args) => write!(f, "{dst} = call {callee} {args:?}"),
Inst::CallDirect(dst, name, args) => write!(f, "{dst} = call_direct {name} {args:?}"),
Inst::Deref(dst, src) => write!(f, "{dst} = deref {src}"),
Inst::DefVar(dst, ns, name, val) => write!(f, "{dst} = def {ns}/{name} {val}"),
Inst::SetBang(var, val) => write!(f, "set! {var} {val}"),
Inst::Throw(val) => write!(f, "throw {val}"),
Inst::Phi(dst, entries) => write!(f, "{dst} = phi {entries:?}"),
Inst::Recur(args) => write!(f, "recur {args:?}"),
Inst::SourceLoc(span) => write!(f, "# {}:{}:{}", span.file, span.line, span.col),
Inst::RegionStart(dst) => write!(f, "{dst} = region_start"),
Inst::RegionAlloc(dst, region, kind, operands) => {
write!(f, "{dst} = region_alloc {region} {kind} {operands:?}")
}
Inst::RegionEnd(region) => write!(f, "region_end {region}"),
}
}
}
impl fmt::Display for Terminator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Terminator::Jump(target) => write!(f, "jump {target}"),
Terminator::Branch {
cond,
then_block,
else_block,
} => write!(f, "branch {cond} then={then_block} else={else_block}"),
Terminator::Return(val) => write!(f, "return {val}"),
Terminator::RecurJump { target, args } => {
write!(f, "recur_jump {target} {args:?}")
}
Terminator::Unreachable => write!(f, "unreachable"),
}
}
}
pub const COMPILER_IR_SOURCE: &str = include_str!("cljrs/compiler/ir.cljrs");
pub const COMPILER_KNOWN_SOURCE: &str = include_str!("cljrs/compiler/known.cljrs");
pub const COMPILER_ANF_SOURCE: &str = include_str!("cljrs/compiler/anf.cljrs");
pub const COMPILER_ESCAPE_SOURCE: &str = include_str!("cljrs/compiler/escape.cljrs");
pub const COMPILER_OPTIMIZE_SOURCE: &str = include_str!("cljrs/compiler/optimize.cljrs");
#[cfg(test)]
mod tests {
use super::*;
fn make_test_fn(name: &str, const_val: i64) -> IrFunction {
let mut f = IrFunction::new(Some(Arc::from(name)), None);
let dst = f.fresh_var();
let block_id = f.fresh_block();
f.blocks.push(Block {
id: block_id,
phis: vec![],
insts: vec![Inst::Const(dst, Const::Long(const_val))],
terminator: Terminator::Return(dst),
});
f
}
#[test]
fn test_ir_function_serialize_roundtrip() {
let f = make_test_fn("identity", 42);
let bytes = f.serialize().unwrap();
let f2 = IrFunction::deserialize(&bytes).unwrap();
assert_eq!(f2.name.as_deref(), Some("identity"));
assert_eq!(f2.blocks.len(), 1);
assert_eq!(f2.next_var, 1);
match &f2.blocks[0].insts[0] {
Inst::Const(_, Const::Long(v)) => assert_eq!(*v, 42),
other => panic!("expected Const(Long(42)), got {other:?}"),
}
}
#[test]
fn test_ir_function_with_closure_template() {
let mut f = IrFunction::new(Some(Arc::from("outer")), None);
let dst = f.fresh_var();
let capture = f.fresh_var();
let block_id = f.fresh_block();
f.blocks.push(Block {
id: block_id,
phis: vec![],
insts: vec![
Inst::Const(capture, Const::Str(Arc::from("hello"))),
Inst::AllocClosure(
dst,
ClosureTemplate {
name: Some(Arc::from("inner")),
arity_fn_names: vec![Arc::from("inner__0")],
param_counts: vec![1],
is_variadic: vec![false],
capture_names: vec![Arc::from("x")],
},
vec![capture],
),
],
terminator: Terminator::Return(dst),
});
let bytes = f.serialize().unwrap();
let f2 = IrFunction::deserialize(&bytes).unwrap();
match &f2.blocks[0].insts[1] {
Inst::AllocClosure(_, tmpl, captures) => {
assert_eq!(tmpl.name.as_deref(), Some("inner"));
assert_eq!(tmpl.param_counts, vec![1]);
assert_eq!(tmpl.is_variadic, vec![false]);
assert_eq!(captures.len(), 1);
}
other => panic!("expected AllocClosure, got {other:?}"),
}
}
#[test]
fn test_empty_bundle_roundtrip() {
let bundle = IrBundle::new();
assert!(bundle.is_empty());
let bytes = serialize_bundle(&bundle).unwrap();
let bundle2 = deserialize_bundle(&bytes).unwrap();
assert!(bundle2.is_empty());
assert_eq!(bundle2.len(), 0);
}
#[test]
fn test_bundle_single_function() {
let mut bundle = IrBundle::new();
bundle.insert("clojure.core/inc:1".to_string(), make_test_fn("inc", 1));
assert_eq!(bundle.len(), 1);
let bytes = serialize_bundle(&bundle).unwrap();
let bundle2 = deserialize_bundle(&bytes).unwrap();
assert_eq!(bundle2.len(), 1);
let f = bundle2.get("clojure.core/inc:1").unwrap();
assert_eq!(f.name.as_deref(), Some("inc"));
}
#[test]
fn test_bundle_multiple_functions() {
let mut bundle = IrBundle::new();
bundle.insert("clojure.core/inc:1".to_string(), make_test_fn("inc", 1));
bundle.insert("clojure.core/dec:1".to_string(), make_test_fn("dec", -1));
bundle.insert(
"clojure.core/identity:1".to_string(),
make_test_fn("identity", 0),
);
assert_eq!(bundle.len(), 3);
let bytes = serialize_bundle(&bundle).unwrap();
let bundle2 = deserialize_bundle(&bytes).unwrap();
assert_eq!(bundle2.len(), 3);
assert_eq!(
bundle2.get("clojure.core/inc:1").unwrap().name.as_deref(),
Some("inc")
);
assert_eq!(
bundle2.get("clojure.core/dec:1").unwrap().name.as_deref(),
Some("dec")
);
assert_eq!(
bundle2
.get("clojure.core/identity:1")
.unwrap()
.name
.as_deref(),
Some("identity")
);
assert!(bundle2.get("nonexistent").is_none());
}
#[test]
fn test_bundle_with_complex_ir() {
let mut f = IrFunction::new(Some(Arc::from("complex")), None);
let p0 = f.fresh_var();
let p1 = f.fresh_var();
f.params = vec![(Arc::from("x"), p0), (Arc::from("y"), p1)];
let entry = f.fresh_block();
let then_bb = f.fresh_block();
let else_bb = f.fresh_block();
let join_bb = f.fresh_block();
let cond_dst = f.fresh_var();
f.blocks.push(Block {
id: entry,
phis: vec![],
insts: vec![Inst::CallKnown(cond_dst, KnownFn::IsNil, vec![p0])],
terminator: Terminator::Branch {
cond: cond_dst,
then_block: then_bb,
else_block: else_bb,
},
});
f.blocks.push(Block {
id: then_bb,
phis: vec![],
insts: vec![],
terminator: Terminator::Jump(join_bb),
});
f.blocks.push(Block {
id: else_bb,
phis: vec![],
insts: vec![],
terminator: Terminator::Jump(join_bb),
});
let phi_dst = f.fresh_var();
f.blocks.push(Block {
id: join_bb,
phis: vec![Inst::Phi(phi_dst, vec![(then_bb, p1), (else_bb, p0)])],
insts: vec![],
terminator: Terminator::Return(phi_dst),
});
let mut bundle = IrBundle::new();
bundle.insert("test/complex:2".to_string(), f);
let bytes = serialize_bundle(&bundle).unwrap();
let bundle2 = deserialize_bundle(&bytes).unwrap();
let f2 = bundle2.get("test/complex:2").unwrap();
assert_eq!(f2.params.len(), 2);
assert_eq!(f2.blocks.len(), 4);
match &f2.blocks[0].terminator {
Terminator::Branch {
cond,
then_block,
else_block,
} => {
assert_eq!(*cond, cond_dst);
assert_eq!(*then_block, then_bb);
assert_eq!(*else_block, else_bb);
}
other => panic!("expected Branch, got {other:?}"),
}
assert_eq!(f2.blocks[3].phis.len(), 1);
match &f2.blocks[3].phis[0] {
Inst::Phi(dst, entries) => {
assert_eq!(*dst, phi_dst);
assert_eq!(entries.len(), 2);
}
other => panic!("expected Phi, got {other:?}"),
}
}
#[test]
fn test_bundle_with_subfunctions() {
let mut outer = make_test_fn("outer", 100);
let inner = make_test_fn("inner", 200);
outer.subfunctions.push(inner);
let mut bundle = IrBundle::new();
bundle.insert("test/outer:0".to_string(), outer);
let bytes = serialize_bundle(&bundle).unwrap();
let bundle2 = deserialize_bundle(&bytes).unwrap();
let f = bundle2.get("test/outer:0").unwrap();
assert_eq!(f.subfunctions.len(), 1);
assert_eq!(f.subfunctions[0].name.as_deref(), Some("inner"));
}
#[test]
fn test_deserialize_invalid_bytes() {
let result = IrFunction::deserialize(&[0xFF, 0xFE, 0xFD]);
assert!(result.is_err());
let result = deserialize_bundle(&[0xFF, 0xFE, 0xFD]);
assert!(result.is_err());
}
}