use std::fmt;
use std::sync::Arc;
use cljrs_types::span::Span;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VarId(pub u32);
impl fmt::Display for VarId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "v{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
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)]
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)]
pub enum Const {
Nil,
Bool(bool),
Long(i64),
Double(f64),
Str(Arc<str>),
Keyword(Arc<str>),
Symbol(Arc<str>),
Char(char),
}
#[derive(Debug, Clone)]
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)]
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)]
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)]
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)]
pub struct Block {
pub id: BlockId,
pub phis: Vec<Inst>,
pub insts: Vec<Inst>,
pub terminator: Terminator,
}
#[derive(Debug)]
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
}
}
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 {}({}):",
self.name.as_deref().unwrap_or("<anon>"),
self.params
.iter()
.map(|(name, id)| format!("{name}: {id}"))
.collect::<Vec<_>>()
.join(", ")
)?;
for block in &self.blocks {
writeln!(f, " {block}")?;
}
Ok(())
}
}
impl fmt::Display for Block {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{}:", self.id)?;
for phi in &self.phis {
writeln!(f, " {phi}")?;
}
for inst in &self.insts {
writeln!(f, " {inst}")?;
}
write!(f, " {}", self.terminator)
}
}
impl fmt::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"),
}
}
}