use jstd::{Identifier, registry::Registry, stable_arena::StableArena};
use rustc_hash::{FxHashMap, FxHashSet};
use std::{
borrow::Cow,
collections::BTreeSet,
fmt::{Display, Formatter},
marker::PhantomData,
};
mod footprint;
pub use footprint::{Footprint, RamBase, RamField, RamLocations, RamObject, RamRegion};
mod signature;
pub use signature::{
ArgMemKind, ExternArg, ExternArgmem, ExternInterface, ExternSlot, FunctionSignature, ParamAttrs,
};
use crate::{
context::Context,
error::{Error, ErrorTy, Result},
value::{
BasicBlock, BlockId, BlockRef, Instruction, InstructionId, LocalValueId, ModuleView,
QCodeView, Temp, TempId, TempSpace, TempSpaceId, Value, ValueId, VarnodeId,
block::EdgeData,
block::cfg::{EdgeId, LocalBlockId},
block_param::{BlockParam, BlockParamId, LocalParamId},
insn::{LocalInsnId, Mnemonic},
util::{
base_ref::{BaseRef, WithCtx, WithCtxMut},
named::{Named, Renameable, update_context_name},
},
},
};
#[derive(Identifier)]
pub struct FunctionId(u32);
#[derive(Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct FunctionInterface<'str> {
pub name: Cow<'str, str>,
pub address: Option<u64>,
pub is_external: bool,
pub signature: Option<FunctionSignature>,
#[serde(default)]
pub kind: FunctionKind,
#[serde(default)]
pub effects: FunctionEffects,
#[serde(default)]
pub import_ordinal: Option<u16>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct FunctionEffects {
#[serde(default)]
pub register: RegisterChannelState,
#[serde(default)]
pub memory: MemoryChannelState,
}
impl FunctionEffects {
pub fn materialized(&self) -> Option<&RegisterInterfaceMap> {
self.register.materialized()
}
pub fn is_solved(&self) -> bool {
self.register.is_solved()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum RegisterChannelState {
#[default]
Unsolved,
Top,
Solved(RegisterEffectSets),
Materialized(RegisterInterfaceMap),
}
impl RegisterChannelState {
pub fn materialized(&self) -> Option<&RegisterInterfaceMap> {
match self {
RegisterChannelState::Materialized(map) => Some(map),
_ => None,
}
}
pub fn is_solved(&self) -> bool {
matches!(
self,
RegisterChannelState::Solved(_) | RegisterChannelState::Materialized(_)
)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct MemoryChannelState {
#[serde(default)]
pub coarse: WrittenSpacesState,
#[serde(default)]
pub precise: Option<Footprint>,
#[serde(default)]
pub materialized: Option<MemoryInterfaceMap>,
}
impl MemoryChannelState {
pub fn materialized(&self) -> Option<&MemoryInterfaceMap> {
self.materialized.as_ref()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum WrittenSpacesState {
#[default]
Unstamped,
Unbounded,
Bounded(Vec<crate::space::SpaceId>),
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RegisterEffectSets {
#[serde(alias = "loads")]
pub reads: Vec<VarnodeId>,
#[serde(alias = "stores")]
pub writes: Vec<VarnodeId>,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DerivedOutput {
pub register: VarnodeId,
pub projection: crate::value::insn::Callee,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RegisterInterfaceMap {
pub inputs: Vec<VarnodeId>,
pub outputs: Vec<VarnodeId>,
pub returns: usize,
#[serde(default)]
pub projections: Vec<DerivedOutput>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct InterfaceSlot {
pub base: SlotBase,
pub offset: i64,
pub size: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SlotBase {
Arg(usize),
Global(u64),
Unmappable,
}
impl InterfaceSlot {
pub fn is_bindable(&self) -> bool {
!matches!(self.base, SlotBase::Unmappable)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct MemoryInterfaceMap {
pub inputs: Vec<InterfaceSlot>,
pub outputs: Vec<InterfaceSlot>,
}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct FunctionBody<'str> {
#[serde(skip)]
id: Option<FunctionId>,
root: Option<LocalBlockId>,
pub(crate) insns: StableArena<LocalInsnId, Instruction<'str>>,
pub(crate) blocks: StableArena<LocalBlockId, BasicBlock<'str>>,
#[serde(default)]
pub(crate) roster: Vec<LocalBlockId>,
pub(crate) params: StableArena<LocalParamId, BlockParam<'str>>,
pub(crate) edges: StableArena<EdgeId, EdgeData>,
pub(crate) temp_spaces: Registry<crate::value::LocalTempSpaceId, TempSpace>,
pub(crate) temps: Registry<crate::value::LocalTempId, Temp<'str>>,
pub instruction_addrs: BTreeSet<u64>,
#[serde(default)]
pub(crate) names: crate::context::NameTable<'str, LocalValueId>,
#[serde(default)]
pub(crate) users: FxHashMap<LocalValueId, Vec<LocalInsnId>>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct BodyArenaKindStats {
pub issued: usize,
pub live: usize,
pub dead: usize,
pub capacity: usize,
pub structural_bytes: usize,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct BodyArenaStats {
pub instructions: BodyArenaKindStats,
pub blocks: BodyArenaKindStats,
pub params: BodyArenaKindStats,
pub edges: BodyArenaKindStats,
}
impl BodyArenaKindStats {
fn stable_arena<Id: jstd::registry::Identifier, T>(arena: &StableArena<Id, T>) -> Self {
let issued = arena.issued_len();
let live = arena.len();
Self {
issued,
live,
dead: issued - live,
capacity: arena.capacity(),
structural_bytes: arena.structural_bytes(),
}
}
fn add_assign(&mut self, other: Self) {
self.issued += other.issued;
self.live += other.live;
self.dead += other.dead;
self.capacity += other.capacity;
self.structural_bytes += other.structural_bytes;
}
}
impl BodyArenaStats {
pub(crate) fn add_assign(&mut self, other: Self) {
self.instructions.add_assign(other.instructions);
self.blocks.add_assign(other.blocks);
self.params.add_assign(other.params);
self.edges.add_assign(other.edges);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WrittenSpaces<'a> {
Unstamped,
Unbounded,
Bounded(&'a [crate::space::SpaceId]),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum FunctionKind {
#[default]
Machine,
Lambda,
}
impl<'str> FunctionInterface<'str> {
pub fn new(name: Cow<'str, str>) -> Self {
Self {
name,
address: None,
is_external: false,
signature: None,
kind: FunctionKind::Machine,
effects: FunctionEffects::default(),
import_ordinal: None,
}
}
pub fn param_attr(&self, index: usize) -> Option<ParamAttrs> {
self.signature
.as_ref()
.and_then(|s| s.param_attrs.as_ref())
.and_then(|attrs| attrs.get(index))
.copied()
}
}
impl<'str> FunctionBody<'str> {
pub fn arena_stats(&self) -> BodyArenaStats {
BodyArenaStats {
instructions: BodyArenaKindStats::stable_arena(&self.insns),
blocks: BodyArenaKindStats::stable_arena(&self.blocks),
params: BodyArenaKindStats::stable_arena(&self.params),
edges: BodyArenaKindStats::stable_arena(&self.edges),
}
}
pub fn shrink_to_fit(&mut self) {
self.insns.shrink_to_fit();
self.blocks.shrink_to_fit();
self.params.shrink_to_fit();
self.edges.shrink_to_fit();
self.roster.shrink_to_fit();
for mut block in self.blocks.iter_mut() {
block.instructions.shrink_to_fit();
block.params.shrink_to_fit();
block.edges.shrink_to_fit();
}
for insns in self.users.values_mut() {
insns.shrink_to_fit();
}
self.users.shrink_to_fit();
}
pub fn install_id(&mut self, id: FunctionId) {
assert!(self.id.is_none(), "body already installed");
self.id = Some(id);
}
pub fn resolve_minted_callee(&mut self, slot: u32, real: FunctionId) -> usize {
let mut patched = 0;
for mut insn in self.insns.iter_mut() {
patched += usize::from(insn.mnemonic_mut().resolve_minted_callee(slot, real));
}
patched
}
pub fn resolve_minted_callees(
&mut self,
installed: &[FunctionId],
) -> std::result::Result<usize, u32> {
let mut patched = 0;
for mut insn in self.insns.iter_mut() {
let mnemonic = insn.mnemonic_mut();
let Some(slot) = mnemonic.minted_callee_slot() else {
continue;
};
let Some(&real) = installed.get(slot as usize) else {
return Err(slot);
};
mnemonic.resolve_minted_callee(slot, real);
patched += 1;
}
Ok(patched)
}
pub fn empty_with_id(id: FunctionId) -> Self {
Self {
id: Some(id),
root: None,
insns: StableArena::default(),
blocks: StableArena::default(),
roster: Vec::new(),
params: StableArena::default(),
edges: StableArena::default(),
temp_spaces: Registry::default(),
temps: Registry::default(),
instruction_addrs: BTreeSet::new(),
names: crate::context::NameTable::default(),
users: FxHashMap::default(),
}
}
pub fn detached() -> Self {
Self {
id: None,
root: None,
insns: StableArena::default(),
blocks: StableArena::default(),
roster: Vec::new(),
params: StableArena::default(),
edges: StableArena::default(),
temp_spaces: Registry::default(),
temps: Registry::default(),
instruction_addrs: BTreeSet::new(),
names: crate::context::NameTable::default(),
users: FxHashMap::default(),
}
}
pub fn id(&self) -> FunctionId {
self.id.expect("detached body: no registry id yet")
}
pub fn try_id(&self) -> Option<FunctionId> {
self.id
}
pub(crate) fn rehydrate_id(&mut self, id: FunctionId) {
self.id = Some(id);
}
pub(crate) fn local_users_of(&self, value: ValueId) -> &[LocalInsnId] {
self.users
.get(&value.strip_func())
.map(Vec::as_slice)
.unwrap_or(&[])
}
pub fn has_users(&self, value: ValueId) -> bool {
if value
.owning_function()
.is_some_and(|owner| owner != self.id())
{
return false;
}
!self.local_users_of(value).is_empty()
}
pub fn users_of(&self, value: ValueId) -> Vec<InstructionId> {
if value
.owning_function()
.is_some_and(|owner| owner != self.id())
{
return Vec::new();
}
self.local_users_of(value)
.iter()
.map(|&local| InstructionId::new(self.id(), local))
.collect()
}
pub fn user_map_entries(&self) -> impl Iterator<Item = (LocalValueId, &[LocalInsnId])> {
self.users.iter().map(|(v, u)| (*v, u.as_slice()))
}
pub fn root_id(&self) -> Option<LocalBlockId> {
self.root
}
pub fn set_root_id(&mut self, root: Option<LocalBlockId>) {
self.root = root;
}
pub fn block(&self, id: BlockId) -> &BasicBlock<'str> {
assert_eq!(id.func, self.id(), "block belongs to another function");
&self.blocks[id.local]
}
pub fn block_mut(&mut self, id: BlockId) -> &mut BasicBlock<'str> {
assert_eq!(id.func, self.id(), "block belongs to another function");
&mut self.blocks[id.local]
}
pub fn contains_block(&self, id: BlockId) -> bool {
id.func == self.id() && self.blocks.contains(id.local)
}
pub fn insn(&self, id: InstructionId) -> &Instruction<'str> {
assert_eq!(
id.func,
self.id(),
"instruction belongs to another function"
);
&self.insns[id.local]
}
pub fn insn_mut(&mut self, id: InstructionId) -> &mut Instruction<'str> {
assert_eq!(
id.func,
self.id(),
"instruction belongs to another function"
);
&mut self.insns[id.local]
}
pub fn contains_instruction(&self, id: InstructionId) -> bool {
id.func == self.id() && self.insns.contains(id.local)
}
pub fn block_param(&self, id: BlockParamId) -> &BlockParam<'str> {
assert_eq!(
id.func,
self.id(),
"block parameter belongs to another function"
);
&self.params[id.local]
}
pub fn block_param_mut(&mut self, id: BlockParamId) -> &mut BlockParam<'str> {
assert_eq!(
id.func,
self.id(),
"block parameter belongs to another function"
);
&mut self.params[id.local]
}
pub fn contains_block_param(&self, id: BlockParamId) -> bool {
id.func == self.id() && self.params.contains(id.local)
}
pub fn local_type_of(
&self,
shared: &crate::context::Shared<'str>,
id: crate::value::LocalValueId,
) -> crate::types::TypeId {
use crate::value::LocalValueId;
match id {
LocalValueId::Literal(id) => shared.values.literals[id].type_id,
LocalValueId::Bytes(id) => shared.values.bytes[id].type_id,
LocalValueId::Instruction(local) => self.insns[local].type_id,
LocalValueId::BlockParam(local) => self.params[local].type_id,
LocalValueId::Varnode(id) => shared
.values
.varnode_types
.get(&id)
.copied()
.unwrap_or_else(|| {
shared
.types
.get_or_make_int(shared.values.varnodes[id].size_bytes())
}),
LocalValueId::Temp(local) => shared.types.get_or_make_int(self.temps[local].size),
LocalValueId::Poison(id) => shared.values.poisons[id].type_id,
LocalValueId::BasicBlock(_) | LocalValueId::Function(_) => {
shared.types.get_or_make_int(0)
}
}
}
pub fn local_stored_type_of(
&self,
shared: &crate::context::Shared<'str>,
id: crate::value::LocalValueId,
) -> Option<crate::types::TypeId> {
use crate::value::LocalValueId;
match id {
LocalValueId::Literal(id) => Some(shared.values.literals[id].type_id),
LocalValueId::Bytes(id) => Some(shared.values.bytes[id].type_id),
LocalValueId::Instruction(local) => Some(self.insns[local].type_id),
LocalValueId::BlockParam(local) => Some(self.params[local].type_id),
LocalValueId::Varnode(id) => shared.values.varnode_types.get(&id).copied(),
LocalValueId::Poison(id) => Some(shared.values.poisons[id].type_id),
LocalValueId::Temp(_) | LocalValueId::BasicBlock(_) | LocalValueId::Function(_) => None,
}
}
pub fn push_temp_space(&mut self, space: TempSpace) -> TempSpaceId {
TempSpaceId::new(self.id(), self.temp_spaces.push(space))
}
pub fn push_temp(&mut self, temp: Temp<'str>) -> TempId {
assert!(
usize::from(temp.space) < self.temp_spaces.len(),
"temporary references a missing local space"
);
let name = temp.name.clone();
if let Some(name) = &name {
assert!(
!self.names.contains(name),
"temporary name {name:?} is already registered in this function"
);
}
let local = self.temps.push(temp);
if let Some(name) = name {
self.names
.register(name, LocalValueId::Temp(local), None)
.expect("temporary name was checked before insertion");
}
TempId::new(self.id(), local)
}
#[track_caller]
pub fn temp_space(&self, id: TempSpaceId) -> &TempSpace {
assert_eq!(
id.func,
self.id(),
"temporary space belongs to another function"
);
debug_assert!(
self.contains_temp_space(id),
"missing temporary space {id:?} in function {:?} (arena length {})",
self.id(),
self.temp_spaces.len()
);
&self.temp_spaces[id.local]
}
pub fn temp_spaces(&self) -> impl Iterator<Item = (TempSpaceId, &TempSpace)> + '_ {
let func = self.id();
self.temp_spaces
.iter()
.map(move |space| (TempSpaceId::new(func, space.id), space.inner))
}
pub fn contains_temp_space(&self, id: TempSpaceId) -> bool {
id.func == self.id() && usize::from(id.local) < self.temp_spaces.len()
}
#[track_caller]
pub fn temp(&self, id: TempId) -> &Temp<'str> {
assert_eq!(id.func, self.id(), "temporary belongs to another function");
debug_assert!(
self.contains_temp(id),
"missing temporary {id:?} in function {:?} (arena length {})",
self.id(),
self.temps.len()
);
&self.temps[id.local]
}
pub fn contains_temp(&self, id: TempId) -> bool {
id.func == self.id() && usize::from(id.local) < self.temps.len()
}
pub fn remove_block_param(&mut self, id: BlockParamId) {
assert!(
self.contains_block_param(id),
"cannot remove stale param {id:?}"
);
let key = ValueId::BlockParam(id).strip_func();
let name = self.params[id.local].name.clone();
if let Some(name) = name {
self.names.forget(name.as_ref());
}
self.users.remove(&key);
self.params.remove(id.local);
}
pub fn edge(&self, id: EdgeId) -> &EdgeData {
&self.edges[id]
}
pub fn push_insn(&mut self, insn: Instruction<'str>) -> InstructionId {
InstructionId::new(self.id(), self.push_insn_local(insn))
}
pub fn push_insn_local(&mut self, insn: Instruction<'str>) -> LocalInsnId {
let args: Vec<LocalValueId> = insn.mnemonic().args().into_iter().collect();
let local = self.insns.push(insn);
for arg in args {
self.users.entry(arg).or_default().push(local);
}
local
}
pub fn push_block(&mut self, block: BasicBlock<'str>) -> BlockId {
let func = self.id();
BlockId::new(func, self.push_block_local(block))
}
pub fn push_block_local(&mut self, block: BasicBlock<'str>) -> LocalBlockId {
let local = self.blocks.push(block);
self.roster.push(local);
local
}
pub fn make_block(&mut self) -> BlockId {
self.push_block(BasicBlock::detached())
}
pub fn make_block_local(&mut self) -> LocalBlockId {
self.push_block_local(BasicBlock::detached())
}
pub fn block_local(&self, block: LocalBlockId) -> &BasicBlock<'str> {
&self.blocks[block]
}
pub fn mnemonic_local(&self, insn: LocalInsnId) -> &Mnemonic {
self.insns[insn].mnemonic()
}
pub fn push_block_param(&mut self, param: BlockParam<'str>) -> BlockParamId {
let local = self.params.push(param);
BlockParamId::new(self.id(), local)
}
pub fn push_block_param_local(
&mut self,
block: LocalBlockId,
param: BlockParam<'str>,
) -> LocalParamId {
let local = self.params.push(param);
self.blocks[block].params.push(local);
local
}
pub fn append_insn_local(&mut self, block: LocalBlockId, insn: LocalInsnId) {
self.insns[insn].parent = Some(block);
self.blocks[block].instructions.push(insn);
}
pub fn push_mnemonic(
&mut self,
shared: &crate::context::Shared<'str>,
mnemonic: Mnemonic,
size: usize,
) -> InstructionId {
let type_id = shared.types.get_or_make_int(size);
let insn = Instruction::new(type_id, mnemonic);
self.push_insn(insn)
}
pub fn push_mnemonic_with_type(
&mut self,
mnemonic: Mnemonic,
type_id: crate::types::TypeId,
) -> InstructionId {
let insn = Instruction::new(type_id, mnemonic);
self.push_insn(insn)
}
pub fn push_mnemonic_with_type_local(
&mut self,
mnemonic: Mnemonic,
type_id: crate::types::TypeId,
) -> LocalInsnId {
self.push_insn_local(Instruction::new(type_id, mnemonic))
}
pub fn insert_insn_before(
&mut self,
block: BlockId,
before: InstructionId,
insn: InstructionId,
) {
let index = self
.block(block)
.instructions
.iter()
.position(|&local| InstructionId::new(block.func, local) == before)
.expect("before not in block");
self.insn_mut(insn).parent = Some(block.local);
self.block_mut(block)
.instructions
.insert(index, insn.localize(block.func));
}
pub fn move_insn_before(&mut self, insn: InstructionId, before: InstructionId) {
let id = self.id();
assert_eq!(insn.func, id, "instruction belongs to another function");
assert_eq!(
before.func, id,
"anchor instruction belongs to another function"
);
if insn == before {
return;
}
assert!(
!self.insn(insn).mnemonic().is_terminator(),
"moving a terminator requires updating its CFG edges"
);
let source = self
.insn(insn)
.parent
.map(|local| BlockId::new(id, local))
.expect("moved instruction must belong to a block");
let target = self
.insn(before)
.parent
.map(|local| BlockId::new(id, local))
.expect("anchor instruction must belong to a block");
let source_index = self
.block(source)
.instructions
.iter()
.position(|&local| local == insn.local)
.expect("moved instruction missing from its parent block");
let before_index = self
.block(target)
.instructions
.iter()
.position(|&local| local == before.local)
.expect("anchor instruction missing from its parent block");
let insert_index = if source == target && source_index < before_index {
before_index - 1
} else {
before_index
};
self.block_mut(source).instructions.remove(source_index);
self.block_mut(target)
.instructions
.insert(insert_index, insn.local);
self.insn_mut(insn).parent = Some(target.local);
}
pub fn add_cfg_edge(&mut self, from: BlockId, to: BlockId) -> EdgeId {
self.add_cfg_edge_local(from.local, to.local)
}
pub fn add_cfg_edge_local(&mut self, from: LocalBlockId, to: LocalBlockId) -> EdgeId {
let edge_id = self.edges.push(EdgeData { from, to });
self.blocks[from].edges.insert(edge_id);
self.blocks[to].edges.insert(edge_id);
edge_id
}
pub fn remove_cfg_edge(&mut self, edge_id: EdgeId) {
let EdgeData { from, to } = *self.edge(edge_id);
let func = self.id();
self.block_mut(BlockId::new(func, from))
.edges
.remove(&edge_id);
self.block_mut(BlockId::new(func, to))
.edges
.remove(&edge_id);
self.edges.remove(edge_id);
}
pub fn replace_all_uses_with(&mut self, old: ValueId, new: ValueId) {
if old == new {
return;
}
let Some(func) = old.owning_function() else {
return;
};
assert_eq!(
func,
self.id(),
"cannot replace uses of a value owned by another function"
);
if let Some(new_owner) = new.owning_function() {
assert_eq!(
new_owner,
self.id(),
"cannot replace uses with a value owned by another function"
);
}
let users = self.users_of(old);
let old = old.localize(func);
let new = new.localize(func);
for user in users {
self.insn_mut(user).mnemonic_mut().replace_value(old, new);
self.users.entry(new).or_default().push(user.localize(func));
}
self.users.remove(&old);
}
pub fn replace_instruction(&mut self, id: InstructionId, new: ValueId) {
if new == ValueId::Instruction(id) {
return;
}
self.replace_all_uses_with(ValueId::Instruction(id), new);
self.remove_instruction(id);
}
pub fn remove_instructions(&mut self, dead: &FxHashSet<LocalInsnId>) {
let mut ids: Vec<_> = dead.iter().copied().collect();
ids.sort_unstable();
let mut affected_args: FxHashSet<LocalValueId> = FxHashSet::default();
for &id in &ids {
assert!(
self.insns.contains(id),
"cannot remove stale instruction {id:?}"
);
affected_args.extend(self.insns[id].mnemonic().args());
}
for arg in affected_args {
let remove_key = if let Some(users) = self.users.get_mut(&arg) {
users.retain(|local| !dead.contains(local));
users.is_empty()
} else {
false
};
if remove_key {
self.users.remove(&arg);
}
}
for id in ids {
self.users.remove(&LocalValueId::Instruction(id));
self.insns.remove(id);
}
}
pub fn remove_instruction(&mut self, id: InstructionId) {
assert_eq!(
id.func,
self.id(),
"instruction belongs to another function"
);
let func = self.id();
let (parent, name, is_terminator, args) = {
let insn = self.insn(id);
(
insn.parent.map(|l| BlockId::new(self.id(), l)),
insn.name.clone(),
insn.mnemonic().is_terminator(),
insn.mnemonic().args().into_iter().collect::<Vec<_>>(),
)
};
if let Some(block_id) = parent {
self.block_mut(block_id)
.instructions
.retain(|&local| local != id.localize(block_id.func));
if is_terminator {
let mut succ: Vec<EdgeId> = {
let block = self.block(block_id);
block
.edges
.iter()
.copied()
.filter(|&e| self.edge(e).from == block_id.local)
.collect()
};
succ.sort_unstable();
for edge_id in succ {
self.remove_cfg_edge(edge_id);
}
}
}
if let Some(n) = name {
self.names.forget(n.as_ref());
}
for arg in args {
let remove_key = if let Some(users) = self.users.get_mut(&arg) {
users.retain(|&local| local != id.localize(func));
users.is_empty()
} else {
false
};
if remove_key {
self.users.remove(&arg);
}
}
self.users.remove(&ValueId::Instruction(id).strip_func());
self.insns.remove(id.local);
}
pub fn remove_block_instructions(&mut self, block_id: BlockId, dead: &FxHashSet<LocalInsnId>) {
assert_eq!(
block_id.func,
self.id(),
"block belongs to another function"
);
if dead.is_empty() {
return;
}
let mut names = Vec::new();
for &id in dead {
let insn = &self.insns[id];
assert!(
!insn.mnemonic().is_terminator(),
"bulk removal does not unlink CFG edges; {id:?} is a terminator"
);
if let Some(name) = insn.name.clone() {
names.push(name);
}
}
self.block_mut(block_id)
.instructions
.retain(|local| !dead.contains(local));
self.purge_instructions(dead, names);
}
fn purge_instructions(&mut self, dead: &FxHashSet<LocalInsnId>, names: Vec<Cow<'str, str>>) {
for name in names {
self.names.forget(name.as_ref());
}
let mut operands: FxHashSet<LocalValueId> = FxHashSet::default();
for &id in dead {
operands.extend(self.insns[id].mnemonic().args());
}
for arg in operands {
let now_empty = if let Some(users) = self.users.get_mut(&arg) {
users.retain(|local| !dead.contains(local));
users.is_empty()
} else {
false
};
if now_empty {
self.users.remove(&arg);
}
}
for &id in dead {
self.users.remove(&LocalValueId::Instruction(id));
self.insns.remove(id);
}
}
pub fn rehome_outgoing_edges(&mut self, keep: BlockId, remove: BlockId) {
let outgoing: Vec<EdgeId> = {
let block = self.block(remove);
block
.edges
.iter()
.copied()
.filter(|&e| self.edge(e).from == remove.local)
.collect()
};
for eid in outgoing {
self.edges[eid].from = keep.local;
self.block_mut(keep).edges.insert(eid);
self.block_mut(remove).edges.remove(&eid);
}
}
pub fn replace_instruction_mnemonic(&mut self, id: InstructionId, mnemonic: Mnemonic) {
assert_eq!(
id.func,
self.id(),
"instruction belongs to another function"
);
self.replace_instruction_mnemonic_local(id.local, mnemonic);
}
pub fn replace_instruction_mnemonic_local(&mut self, id: LocalInsnId, mnemonic: Mnemonic) {
let old_args = self.insns[id]
.mnemonic()
.args()
.into_iter()
.collect::<Vec<_>>();
for arg in old_args {
let now_empty = if let Some(users) = self.users.get_mut(&arg) {
users.retain(|&local| local != id);
users.is_empty()
} else {
false
};
if now_empty {
self.users.remove(&arg);
}
}
*self.insns[id].mnemonic_mut() = mnemonic;
let new_args = self.insns[id]
.mnemonic()
.args()
.into_iter()
.collect::<Vec<_>>();
for arg in new_args {
self.users.entry(arg).or_default().push(id);
}
}
pub fn rename_block_local(&mut self, block: LocalBlockId, name: Cow<'str, str>) -> Result<()> {
let target = LocalValueId::BasicBlock(block);
if let Some(existing) = self.names.get(&name) {
return if existing == target {
Ok(())
} else {
Err(Error::spanless(ErrorTy::DuplicateName(name.to_string())))
};
}
let old_name = self.blocks[block].local_name().map(str::to_owned);
self.names
.register(name.clone(), target, old_name.as_deref())?;
self.blocks[block].set_name(Some(name));
Ok(())
}
pub fn unroster_block(&mut self, block: BlockId) {
self.roster.retain(|&b| b != block.localize(block.func));
}
pub fn clear_block_instructions(&mut self, block: BlockId) {
assert_eq!(block.func, self.id(), "block belongs to another function");
let mut outgoing: Vec<EdgeId> = self
.block(block)
.edges
.iter()
.copied()
.filter(|&edge| self.edges[edge].from == block.local)
.collect();
outgoing.sort_unstable();
for edge in outgoing {
self.remove_cfg_edge(edge);
}
let insns = std::mem::take(&mut self.block_mut(block).instructions);
let dead: FxHashSet<LocalInsnId> = insns.iter().copied().collect();
let names: Vec<Cow<'str, str>> = insns
.iter()
.filter_map(|&local| self.insns[local].name.clone())
.collect();
self.purge_instructions(&dead, names);
}
pub fn delete_block(&mut self, block: BlockId) {
assert_eq!(block.func, self.id(), "block belongs to another function");
let mut edges: Vec<EdgeId> = self.block(block).edges.iter().copied().collect();
edges.sort_unstable();
for edge in edges {
self.remove_cfg_edge(edge);
}
let insns: Vec<InstructionId> = self
.block(block)
.instructions
.iter()
.map(|&local| InstructionId::new(self.id(), local))
.collect();
for insn in insns {
self.remove_instruction(insn);
}
let params: Vec<BlockParamId> = self
.block(block)
.params
.iter()
.map(|&local| BlockParamId::new(self.id(), local))
.collect();
for param in params {
self.remove_block_param(param);
}
let name = self.block(block).local_name().map(str::to_owned);
self.unroster_block(block);
if self.root == Some(block.local) {
self.root = None;
}
if let Some(name) = name {
self.names.forget(&name);
}
self.blocks.remove(block.local);
}
pub fn absorb_block(&mut self, keep: BlockId, other: BlockId, edge_ab: EdgeId) {
assert_eq!(
keep.func, other.func,
"cannot absorb across function arenas"
);
let (branch_id, branch_args) = self
.block(keep)
.instructions
.last()
.and_then(
|&local| match self.insn(InstructionId::new(keep.func, local)).mnemonic() {
Mnemonic::Branch(branch) if BlockId::new(keep.func, branch.target) == other => {
Some((InstructionId::new(keep.func, local), branch.args.clone()))
}
_ => None,
},
)
.expect("absorbed block must be reached by keep's terminal branch");
let other_params: Vec<_> = self
.block(other)
.params
.iter()
.map(|&local| BlockParamId::new(other.func, local))
.collect();
if !other_params.is_empty() {
assert_eq!(
other_params.len(),
branch_args.len(),
"cannot absorb block with {} params through branch with {} args",
other_params.len(),
branch_args.len()
);
for (param, arg) in other_params.iter().copied().zip(branch_args) {
self.replace_all_uses_with(ValueId::BlockParam(param), arg.qualify(keep.func));
}
}
self.remove_cfg_edge(edge_ab);
self.remove_instruction(branch_id);
let b_insns = std::mem::take(&mut self.block_mut(other).instructions);
for &local in &b_insns {
self.insn_mut(InstructionId::new(other.func, local)).parent = Some(keep.local);
}
self.block_mut(keep).instructions.extend(b_insns);
self.rehome_outgoing_edges(keep, other);
let (b_addr, b_extra, b_name) = {
let b = self.block(other);
(
b.address,
b.extra_addresses.clone(),
b.local_name().map(str::to_owned),
)
};
for param in other_params {
self.remove_block_param(param);
}
self.unroster_block(other);
if self.root == Some(other.local) {
self.root = Some(keep.local);
}
if let Some(name) = b_name {
self.names.forget(&name);
}
self.blocks.remove(other.local);
if let Some(addr) = b_addr {
self.block_mut(keep).extra_addresses.push(addr);
}
self.block_mut(keep).extra_addresses.extend(b_extra);
}
pub fn register_local_name(
&mut self,
shared: &crate::context::Shared<'str>,
id: ValueId,
name: Cow<'str, str>,
old_name: Option<&str>,
) -> Result<()> {
if id.name_scope_function().is_none() {
return match shared.get_named(&name) {
Some(existing) if existing == id => Ok(()),
Some(_) => Err(Error::spanless(ErrorTy::DuplicateName(name.to_string()))),
None => unimplemented!(
"a function body cannot register a global name (shared is read-only)"
),
};
}
self.register_body_name(id, name, old_name)
}
pub fn register_body_name(
&mut self,
id: ValueId,
name: Cow<'str, str>,
old_name: Option<&str>,
) -> Result<()> {
assert!(
id.name_scope_function().is_some(),
"register_body_name on a global-scoped value {id:?}"
);
if let Some(existing) = self.names.get(&name).map(|id| id.qualify(self.id())) {
return if existing == id {
Ok(())
} else {
Err(Error::spanless(ErrorTy::DuplicateName(name.to_string())))
};
}
self.names.register(name, id.localize(self.id()), old_name)
}
pub fn from_id<'ctx>(ctx: &'ctx Context<'str>, id: FunctionId) -> FunctionRef<'str, 'ctx> {
FunctionRef::new(ModuleView::new(ctx), id)
}
pub fn from_id_mut<'ctx>(
ctx: &'ctx mut Context<'str>,
id: FunctionId,
) -> FunctionMutRef<'str, 'ctx> {
FunctionMutRef::new(ctx, id)
}
pub fn from_name<'ctx>(
ctx: &'ctx Context<'str>,
name: &str,
) -> Option<FunctionRef<'str, 'ctx>> {
ctx.get_named(name)
.and_then(ValueId::as_function)
.map(|id| FunctionBody::from_id(ctx, id))
}
pub fn make<'ctx>(
ctx: &'ctx mut Context<'str>,
name: Cow<'str, str>,
) -> Result<FunctionMutRef<'str, 'ctx>> {
let id = FunctionId::from(ctx.bodies.len());
let pushed = ctx.push_function(
FunctionInterface::new(name.clone()),
FunctionBody::empty_with_id(id),
);
debug_assert_eq!(pushed, id);
ctx.update_name(name, id.into(), None)?;
Ok(Self::from_id_mut(ctx, id))
}
pub fn make_lambda<'ctx>(
ctx: &'ctx mut Context<'str>,
name: Cow<'str, str>,
) -> Result<FunctionMutRef<'str, 'ctx>> {
let mut function = Self::make(ctx, name)?;
function.interface_mut().kind = FunctionKind::Lambda;
function.set_is_pure(true);
function.set_register_effects(RegisterChannelState::Materialized(
RegisterInterfaceMap::default(),
));
Ok(function)
}
pub fn make_at_addr<'ctx>(
ctx: &'ctx mut Context<'str>,
address: u64,
name: Option<Cow<'str, str>>,
) -> FunctionMutRef<'str, 'ctx> {
let mut addresses = crate::address_index::AddressIndex::analyze(ctx);
Self::make_at_addr_indexed(ctx, &mut addresses, address, name)
}
pub fn make_at_addr_indexed<'ctx>(
ctx: &'ctx mut Context<'str>,
addresses: &mut crate::address_index::AddressIndex,
address: u64,
name: Option<Cow<'str, str>>,
) -> FunctionMutRef<'str, 'ctx> {
let name = name.unwrap_or_else(|| Cow::Owned(format!("fn_{address:x}")));
let id = FunctionId::from(ctx.bodies.len());
let pushed = ctx.push_function(
FunctionInterface::new(name.clone()),
FunctionBody::empty_with_id(id),
);
debug_assert_eq!(pushed, id);
Self::from_id_mut(ctx, id)
.with_name(name)
.expect("Function name is not unique")
.with_address_indexed(addresses, address)
.expect("Function address is not unique")
}
pub fn make_external<'ctx>(
ctx: &'ctx mut Context<'str>,
address: u64,
name: Option<Cow<'str, str>>,
) -> FunctionMutRef<'str, 'ctx> {
let mut f = Self::make_at_addr(ctx, address, name);
f.interface_mut().is_external = true;
f
}
pub fn make_external_indexed<'ctx>(
ctx: &'ctx mut Context<'str>,
addresses: &mut crate::address_index::AddressIndex,
address: u64,
name: Option<Cow<'str, str>>,
) -> FunctionMutRef<'str, 'ctx> {
let mut function = Self::make_at_addr_indexed(ctx, addresses, address, name);
function.interface_mut().is_external = true;
function
}
pub fn from_addr_or_create<'ctx>(
ctx: &'ctx mut Context<'str>,
address: u64,
) -> FunctionMutRef<'str, 'ctx> {
let mut addresses = crate::address_index::AddressIndex::analyze(ctx);
Self::from_addr_or_create_indexed(ctx, &mut addresses, address)
}
pub fn from_addr_or_create_indexed<'ctx>(
ctx: &'ctx mut Context<'str>,
addresses: &mut crate::address_index::AddressIndex,
address: u64,
) -> FunctionMutRef<'str, 'ctx> {
match addresses.function_at(address) {
Some(id) => Self::from_id_mut(ctx, id),
None => Self::make_at_addr_indexed(ctx, addresses, address, None),
}
}
}
impl<'s, 'ctx: 's, 'str: 'ctx, R> FunctionRef<'str, 'ctx, R>
where
R: QCodeView<'ctx, 'str>,
{
fn inner(&'s self) -> &'ctx FunctionBody<'str> {
self.view.function(self.id)
}
fn interface(&'s self) -> &'ctx FunctionInterface<'str> {
self.view.interface(self.id)
}
fn size(&self) -> usize {
0
}
pub fn address(&'s self) -> Option<u64> {
self.interface().address
}
pub fn is_external(&'s self) -> bool {
self.interface().is_external
}
pub fn import_ordinal(&'s self) -> Option<u16> {
self.interface().import_ordinal
}
pub fn signature(&'s self) -> Option<&'ctx FunctionSignature> {
self.interface().signature.as_ref()
}
pub fn users_of(&'s self, value: ValueId) -> Vec<InstructionId> {
let func = self.id;
if value.owning_function().is_some_and(|owner| owner != func) {
return Vec::new();
}
self.inner().users_of(value)
}
pub fn local_users_of(&'s self, value: ValueId) -> &'ctx [LocalInsnId] {
let func = self.id;
if value.owning_function().is_some_and(|owner| owner != func) {
return &[];
}
self.inner().local_users_of(value)
}
pub fn has_users(&'s self, value: ValueId) -> bool {
let func = self.id;
if value.owning_function().is_some_and(|owner| owner != func) {
return false;
}
self.inner().has_users(value)
}
pub fn user_map_entries(&'s self) -> impl Iterator<Item = (ValueId, Vec<InstructionId>)> + 's {
let func = self.id;
self.inner().user_map_entries().map(move |(v, u)| {
(
v.qualify(func),
u.iter()
.map(|&local| InstructionId::new(func, local))
.collect(),
)
})
}
pub fn local_named(&'s self, name: &str) -> Option<ValueId> {
self.inner().names.get(name).map(|id| id.qualify(self.id))
}
pub fn param_attr(&'s self, index: usize) -> Option<ParamAttrs> {
self.interface().param_attr(index)
}
pub fn param_attrs(&'s self) -> Option<&'ctx [ParamAttrs]> {
self.interface()
.signature
.as_ref()
.and_then(|s| s.param_attrs.as_deref())
}
pub fn written_spaces(&'s self) -> Option<&'ctx [crate::space::SpaceId]> {
match &self.interface().effects.memory.coarse {
WrittenSpacesState::Bounded(spaces) => Some(spaces),
_ => None,
}
}
pub fn written_spaces_state(&'s self) -> WrittenSpaces<'ctx> {
match &self.interface().effects.memory.coarse {
WrittenSpacesState::Unstamped => WrittenSpaces::Unstamped,
WrittenSpacesState::Unbounded => WrittenSpaces::Unbounded,
WrittenSpacesState::Bounded(spaces) => WrittenSpaces::Bounded(spaces),
}
}
pub fn is_reg_materialized(&'s self) -> bool {
matches!(
self.interface().effects.register,
RegisterChannelState::Materialized(_)
)
}
pub fn effects(&'s self) -> &'ctx FunctionEffects {
&self.interface().effects
}
pub fn is_pure(&'s self) -> bool {
self.interface()
.signature
.as_ref()
.is_some_and(|s| s.is_pure)
}
pub fn is_lambda(&'s self) -> bool {
self.interface().kind == FunctionKind::Lambda
}
pub fn kind(&'s self) -> FunctionKind {
self.interface().kind
}
pub fn extern_interface(&'s self) -> Option<&'ctx crate::value::ExternInterface> {
self.interface()
.signature
.as_ref()
.and_then(|s| s.extern_interface.as_ref())
}
pub fn argmem(&'s self) -> Option<&'ctx crate::value::ExternArgmem> {
self.interface()
.signature
.as_ref()
.and_then(|s| s.argmem.as_ref())
}
pub fn input_arg_name(&'s self, index: usize) -> Option<String> {
if let Some(root) = self.root()
&& let Some(name) = root
.params()
.nth(index)
.and_then(|p| p.name().map(str::to_owned))
{
return Some(name);
}
self.extern_interface()
.and_then(|iface| iface.args.get(index))
.and_then(|a| a.name.as_ref().map(|n| n.to_string()))
}
pub fn reads_unbounded_stack(&'s self) -> bool {
self.interface()
.signature
.as_ref()
.is_some_and(|s| s.reads_unbounded_stack)
}
pub fn frame_escapes_to_unbounded(&'s self) -> bool {
self.interface()
.signature
.as_ref()
.is_some_and(|s| s.frame_escapes_to_unbounded)
}
pub fn name(&'s self) -> &'ctx str {
self.interface().name.as_ref()
}
pub fn instruction_addrs(&'s self) -> impl Iterator<Item = u64> + 'ctx {
self.inner().instruction_addrs.iter().copied()
}
pub fn has_map(&'s self) -> bool {
self.blocks().any(|block| {
block
.instructions()
.any(|insn| matches!(insn.mnemonic(), Mnemonic::Map(_)))
})
}
pub fn has_scan(&'s self) -> bool {
self.blocks().any(|block| {
block
.instructions()
.any(|insn| matches!(insn.mnemonic(), Mnemonic::Scan(_)))
})
}
pub fn root(&'s self) -> Option<BlockRef<'str, 'ctx, R>> {
self.inner()
.root
.map(|local| BlockRef::new(self.view, BlockId::new(self.id, local)))
}
pub fn blocks(&'s self) -> impl Iterator<Item = BlockRef<'str, 'ctx, R>> + 's {
let view = self.view;
let mut ids = self.block_ids();
ids.sort_by_key(|&id| (BlockRef::new(view, id).address(), id.local));
ids.into_iter().map(move |id| BlockRef::new(view, id))
}
pub fn block_ids(&'s self) -> Vec<BlockId> {
let func = self.id;
self.inner()
.roster
.iter()
.copied()
.map(|local| BlockId::new(func, local))
.collect()
}
pub fn instruction_ids(&'s self) -> Vec<InstructionId> {
let func = self.id;
self.inner()
.insns
.iter()
.map(|i| InstructionId::new(func, i.id))
.collect()
}
pub fn edge_ids(&'s self) -> Vec<crate::value::block::EdgeId> {
self.inner().edges.iter().map(|e| e.id).collect()
}
pub fn iter(&'s self) -> BlockIter<'str, 'ctx, R> {
BlockIter {
view: self.view,
inner: self.block_ids().into_iter(),
marker: PhantomData,
}
}
fn fmt(&'s self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.is_external() {
return writeln!(f, "extern fn {};", self.name());
}
let keyword = match self.kind() {
FunctionKind::Machine => "fn",
FunctionKind::Lambda => "lambda",
};
writeln!(f, "{keyword} {}:", self.name())?;
for block in self.blocks() {
block.fmt(f)?;
}
Ok(())
}
}
#[derive(Clone, Copy)]
pub struct FunctionRef<'str, 'ctx, R = ModuleView<'ctx, 'str>> {
pub id: FunctionId,
pub(in crate::value) view: R,
marker: PhantomData<&'ctx &'str ()>,
}
impl<'str, 'ctx, R> FunctionRef<'str, 'ctx, R> {
pub fn new(view: R, id: FunctionId) -> Self {
Self {
id,
view,
marker: PhantomData,
}
}
pub fn id(&self) -> ValueId {
self.id.into()
}
}
impl<'str, 'ctx> FunctionRef<'str, 'ctx> {
pub fn from_id(ctx: &'ctx Context<'str>, id: FunctionId) -> Self {
Self::new(ModuleView::new(ctx), id)
}
}
impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 'ctx, 'str> for FunctionRef<'str, 'ctx> {
fn ctx(&'s self) -> &'ctx Context<'str> {
self.view.context()
}
}
impl<'str: 'ctx, 'ctx, R> Named for FunctionRef<'str, 'ctx, R>
where
R: QCodeView<'ctx, 'str>,
{
fn name(&self) -> Option<&str> {
Some(self.view.interface(self.id).name.as_ref())
}
}
impl<'str: 'ctx, 'ctx, R> Display for FunctionRef<'str, 'ctx, R>
where
R: QCodeView<'ctx, 'str>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
FunctionRef::fmt(self, f)
}
}
impl<'str: 'ctx, 'ctx, R> Value<'str, 'ctx> for FunctionRef<'str, 'ctx, R>
where
R: QCodeView<'ctx, 'str>,
{
fn id(&self) -> ValueId {
self.id()
}
fn size(&self) -> usize {
FunctionRef::size(self)
}
}
pub struct BlockIter<'str, 'ctx, R = ModuleView<'ctx, 'str>> {
view: R,
inner: std::vec::IntoIter<BlockId>,
marker: PhantomData<&'ctx &'str ()>,
}
impl<'str: 'ctx, 'ctx, R> Iterator for BlockIter<'str, 'ctx, R>
where
R: QCodeView<'ctx, 'str>,
{
type Item = BlockRef<'str, 'ctx, R>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|id| BlockRef::new(self.view, id))
}
}
impl<'str: 'ctx, 'ctx, R> IntoIterator for &FunctionRef<'str, 'ctx, R>
where
R: QCodeView<'ctx, 'str>,
{
type Item = BlockRef<'str, 'ctx, R>;
type IntoIter = BlockIter<'str, 'ctx, R>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub type FunctionMutRef<'str, 'ctx> = BaseRef<&'ctx mut Context<'str>, FunctionId>;
impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 's, 'str> for FunctionMutRef<'str, 'ctx> {
fn ctx(&'s self) -> &'s Context<'str> {
self.ctx
}
}
impl<'s, 'ctx: 's, 'str: 'ctx> WithCtxMut<'s, 'str> for FunctionMutRef<'str, 'ctx> {
fn ctx_mut(&'s mut self) -> &'s mut Context<'str> {
self.ctx
}
}
impl Display for FunctionMutRef<'_, '_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.as_ref().fmt(f)
}
}
impl<'ctx, 'str> Value<'str, 'ctx> for FunctionMutRef<'str, 'ctx> {
fn id(&self) -> ValueId {
self.id()
}
fn size(&self) -> usize {
self.as_ref().size()
}
}
impl Named for FunctionMutRef<'_, '_> {
fn name(&self) -> Option<&str> {
Some(self.ctx.interfaces[self.id].name.as_ref())
}
}
impl<'str, 'ctx> Renameable<'str, 'ctx> for FunctionMutRef<'str, 'ctx> {
fn rename(&mut self, name: Cow<'str, str>) -> Result<()> {
let id = self.id();
let old_name = self.ctx.interfaces[self.id].name.as_ref().to_owned();
update_context_name(id, self.ctx, name.clone(), Some(old_name.as_ref()))?;
self.ctx.interfaces[self.id].name = name;
Ok(())
}
}
impl<'str, 'ctx> FunctionMutRef<'str, 'ctx> {
pub fn as_ref(&self) -> FunctionRef<'str, '_> {
FunctionRef::new(ModuleView::new(self.ctx), self.id)
}
fn inner(&self) -> &FunctionBody<'str> {
self.ctx.function(self.id)
}
fn interface(&self) -> &FunctionInterface<'str> {
&self.ctx.interfaces[self.id]
}
fn address(&self) -> Option<u64> {
self.interface().address
}
pub fn name(&self) -> &str {
self.interface().name.as_ref()
}
pub fn blocks(&self) -> impl Iterator<Item = BlockRef<'str, '_>> {
self.as_ref().blocks().collect::<Vec<_>>().into_iter()
}
pub fn root(&self) -> Option<BlockRef<'str, '_>> {
self.as_ref().root()
}
pub(crate) fn inner_mut(&mut self) -> &mut FunctionBody<'str> {
&mut self.ctx.bodies[self.id]
}
pub(crate) fn interface_mut(&mut self) -> &mut FunctionInterface<'str> {
&mut self.ctx.interfaces[self.id]
}
fn set_address(&mut self, address: u64) -> Result<()> {
let mut addresses = crate::address_index::AddressIndex::analyze(&*self.ctx);
self.set_address_indexed(&mut addresses, address)
}
fn set_address_indexed(
&mut self,
addresses: &mut crate::address_index::AddressIndex,
address: u64,
) -> Result<()> {
let old_address = self.interface().address;
self.interface_mut().address = Some(address);
if let Err(error) = self
.ctx
.set_address_indexed(addresses, address, self.id.into())
{
self.interface_mut().address = old_address;
return Err(error);
}
Ok(())
}
fn with_address_indexed(
mut self,
addresses: &mut crate::address_index::AddressIndex,
address: u64,
) -> Result<Self> {
self.set_address_indexed(addresses, address)?;
Ok(self)
}
pub fn set_root(&mut self, id: BlockId) -> Result<()> {
assert_eq!(
id.func, self.id,
"cannot root a function at a block stored in another function arena"
);
self.add_block(id);
self.inner_mut().root = Some(id.localize(self.id));
let block_addr = BasicBlock::from_id(&*self.ctx, id).address();
let self_addr = self.address();
match (self_addr, block_addr) {
(Some(fn_addr), Some(block_addr)) if fn_addr != block_addr => {
return Err(Error::spanless(ErrorTy::FunctionRootAddressMismatch {
fn_addr,
block_addr,
}));
}
(None, Some(addr)) => {
self.set_address(addr)
.expect("This address should be valid");
}
(Some(addr), None) => {
BasicBlock::from_id_mut(self.ctx, id)
.set_address(addr)
.expect("This address should be valid");
}
_ => {}
}
Ok(())
}
pub fn make_root(&mut self) -> BlockRef<'str, '_> {
let func = self.id;
let root = BasicBlock::make(self.ctx, func).id;
self.set_root(root).expect("We just created the block");
BasicBlock::from_id(&*self.ctx, root)
}
pub fn ensure_root(&mut self, id: BlockId) -> Result<()> {
assert_eq!(
id.func, self.id,
"cannot ensure a function root from another function arena"
);
if let Some(root) = self.inner().root {
if root != id.localize(self.id) {
return Err(Error::spanless(ErrorTy::FunctionRootMismatch {
expected: BlockId::new(self.id, root),
actual: id,
}));
}
Ok(())
} else {
self.set_root(id)
}
}
pub fn set_external(&mut self, is_external: bool) {
self.interface_mut().is_external = is_external;
assert!(
self.inner().blocks.is_empty(),
"External functions should not have blocks"
);
}
pub fn set_import_ordinal(&mut self, ordinal: Option<u16>) {
self.interface_mut().import_ordinal = ordinal;
}
pub fn set_kind(&mut self, kind: FunctionKind) {
self.interface_mut().kind = kind;
if kind == FunctionKind::Lambda {
self.set_is_pure(true);
self.set_register_effects(RegisterChannelState::Materialized(
RegisterInterfaceMap::default(),
));
}
}
pub fn set_signature(&mut self, sig: FunctionSignature) {
self.ctx.interfaces[self.id].signature = Some(sig);
}
pub fn set_param_attrs(&mut self, attrs: Vec<ParamAttrs>) {
self.interface_mut()
.signature
.get_or_insert_default()
.param_attrs = Some(attrs);
}
pub fn clear_param_attrs(&mut self) {
if let Some(sig) = self.interface_mut().signature.as_mut() {
sig.param_attrs = None;
}
}
pub fn set_written_spaces(&mut self, spaces: Option<Vec<crate::space::SpaceId>>) {
let coarse = match spaces {
Some(spaces) => WrittenSpacesState::Bounded(spaces),
None => WrittenSpacesState::Unbounded,
};
let precise = self.interface_mut().effects.memory.precise.take();
self.set_memory_solved(coarse, precise);
}
pub fn set_extern_interface(&mut self, iface: crate::value::ExternInterface) {
self.interface_mut()
.signature
.get_or_insert_default()
.extern_interface = Some(iface);
}
pub fn set_argmem(&mut self, argmem: crate::value::ExternArgmem) {
self.interface_mut()
.signature
.get_or_insert_default()
.argmem = Some(argmem);
}
pub fn set_register_effects(&mut self, register: RegisterChannelState) {
self.interface_mut().effects.register = register;
}
pub fn set_memory_effects(&mut self, memory: MemoryChannelState) {
self.interface_mut().effects.memory = memory;
}
pub fn set_memory_solved(&mut self, coarse: WrittenSpacesState, precise: Option<Footprint>) {
let memory = &mut self.interface_mut().effects.memory;
memory.coarse = coarse;
memory.precise = precise;
}
pub fn set_memory_interface(&mut self, materialized: Option<MemoryInterfaceMap>) {
self.interface_mut().effects.memory.materialized = materialized;
}
pub fn set_is_pure(&mut self, value: bool) {
self.interface_mut()
.signature
.get_or_insert_default()
.is_pure = value;
}
pub fn set_reads_unbounded_stack(&mut self, value: bool) {
self.interface_mut()
.signature
.get_or_insert_default()
.reads_unbounded_stack = value;
}
pub fn set_frame_escapes_to_unbounded(&mut self, value: bool) {
self.interface_mut()
.signature
.get_or_insert_default()
.frame_escapes_to_unbounded = value;
}
pub fn add_instruction_addr(&mut self, addr: u64) {
self.inner_mut().instruction_addrs.insert(addr);
}
pub fn add_block(&mut self, id: BlockId) {
assert_eq!(
id.func, self.id,
"cannot add a block stored in another function arena"
);
let local = id.localize(self.id);
if !self.inner().roster.contains(&local) {
self.inner_mut().roster.push(local);
}
}
}
#[cfg(test)]
mod tests {
use wazabin_qcode_macro::qcode;
use super::*;
fn foreign_block_fixture() -> (Context<'static>, FunctionId, BlockId) {
let mut ctx = Context::new();
let owner = FunctionBody::make(&mut ctx, "block_owner".into())
.unwrap()
.id;
let destination = FunctionBody::make(&mut ctx, "block_destination".into())
.unwrap()
.id;
let block = BasicBlock::make(&mut ctx, owner).id;
(ctx, destination, block)
}
#[test]
fn raw_root_and_roster_are_local_while_refs_qualify_per_function() {
let mut ctx = Context::new();
let a = FunctionBody::make(&mut ctx, "local_root_a".into())
.unwrap()
.id;
let b = FunctionBody::make(&mut ctx, "local_root_b".into())
.unwrap()
.id;
let a_root = BasicBlock::make(&mut ctx, a).id;
let b_root = BasicBlock::make(&mut ctx, b).id;
assert_eq!(a_root.local, b_root.local, "arena-local ids should collide");
FunctionBody::from_id_mut(&mut ctx, a)
.set_root(a_root)
.unwrap();
FunctionBody::from_id_mut(&mut ctx, b)
.set_root(b_root)
.unwrap();
assert_eq!(ctx.bodies[a].root_id(), Some(a_root.local));
assert_eq!(ctx.bodies[b].root_id(), Some(b_root.local));
assert_eq!(ctx.bodies[a].roster, vec![a_root.local]);
assert_eq!(ctx.bodies[b].roster, vec![b_root.local]);
assert_eq!(
FunctionBody::from_id(&ctx, a).root().map(|root| root.id),
Some(a_root)
);
assert_eq!(
FunctionBody::from_id(&ctx, b).root().map(|root| root.id),
Some(b_root)
);
}
#[test]
#[should_panic(expected = "cannot add a block stored in another function arena")]
fn add_block_rejects_foreign_storage() {
let (mut ctx, destination, block) = foreign_block_fixture();
FunctionBody::from_id_mut(&mut ctx, destination).add_block(block);
}
#[test]
#[should_panic(expected = "cannot root a function at a block stored in another function arena")]
fn set_root_rejects_foreign_storage() {
let (mut ctx, destination, block) = foreign_block_fixture();
FunctionBody::from_id_mut(&mut ctx, destination)
.set_root(block)
.unwrap();
}
#[test]
#[should_panic(expected = "cannot ensure a function root from another function arena")]
fn ensure_root_rejects_foreign_storage() {
let (mut ctx, destination, block) = foreign_block_fixture();
FunctionBody::from_id_mut(&mut ctx, destination)
.ensure_root(block)
.unwrap();
}
#[test]
fn function_ref_users_of_rejects_foreign_owned_values() {
let mut ctx = Context::new();
qcode!(
ctx,
"
fn users_a:
<a_entry>
%a_def = i64 1 + i64 2;
%a_user = %a_def + i64 3;
return at %a_user;
fn users_b:
<b_entry>
%b_def = i64 1 + i64 2;
%b_user = %b_def + i64 3;
return at %b_user;
"
);
let a_ids = FunctionRef::from_id(&ctx, users_a)
.root()
.unwrap()
.instruction_ids();
let a_def = ValueId::Instruction(a_ids[0]);
assert_eq!(
FunctionRef::from_id(&ctx, users_a).users_of(a_def),
vec![a_ids[1]]
);
assert!(
FunctionRef::from_id(&ctx, users_b)
.users_of(a_def)
.is_empty()
);
let one = ctx.get_const(1, 8).id();
assert!(!FunctionRef::from_id(&ctx, users_b).users_of(one).is_empty());
}
fn colliding_body_ids() -> (
Context<'static>,
FunctionId,
FunctionId,
BlockId,
BlockId,
InstructionId,
InstructionId,
BlockParamId,
BlockParamId,
) {
let mut ctx = Context::new();
qcode!(
ctx,
"
fn raw_a:
<a_entry @a:i64>
%a_def = i64 1 + i64 2;
return at %a_def;
fn raw_b:
<b_entry @b:i64>
%b_def = i64 1 + i64 2;
return at %b_def;
"
);
let a_root = FunctionRef::from_id(&ctx, raw_a).root().unwrap();
let b_root = FunctionRef::from_id(&ctx, raw_b).root().unwrap();
let a_block = a_root.id;
let b_block = b_root.id;
let a_insn = a_root.instruction_ids()[0];
let b_insn = b_root.instruction_ids()[0];
let a_param = a_root.params().next().unwrap().id;
let b_param = b_root.params().next().unwrap().id;
assert_eq!(a_block.local, b_block.local);
assert_eq!(a_insn.local, b_insn.local);
assert_eq!(a_param.local, b_param.local);
(
ctx, raw_a, raw_b, a_block, b_block, a_insn, b_insn, a_param, b_param,
)
}
#[test]
fn replace_instruction_with_itself_is_a_noop() {
let mut ctx = Context::new();
qcode!(
ctx,
"
fn f:
<entry @a:i32>
%x = @a + 1;
%y = %x + 2;
return %y;
"
);
let root = FunctionBody::from_id(&ctx, f).root().unwrap().id;
let insns: Vec<InstructionId> = BasicBlock::from_id(&ctx, root)
.instruction_ids()
.into_iter()
.collect();
let x = insns[0];
let users_before = ctx.bodies[f].users_of(ValueId::Instruction(x));
assert!(!users_before.is_empty(), "x should have a user (%y)");
ctx.bodies[f].replace_instruction(x, ValueId::Instruction(x));
assert!(
ctx.bodies[f].insns.contains(x.local),
"x must survive a self-replacement"
);
assert_eq!(
ctx.bodies[f].users_of(ValueId::Instruction(x)),
users_before,
"x's users must be unchanged"
);
}
#[test]
fn body_users_of_rejects_foreign_owned_values() {
let (ctx, a, b, _, _, a_insn, _, _, _) = colliding_body_ids();
assert!(
ctx.bodies[b]
.users_of(ValueId::Instruction(a_insn))
.is_empty()
);
assert!(
!ctx.bodies[a]
.users_of(ValueId::Instruction(a_insn))
.is_empty()
);
}
#[test]
#[should_panic(expected = "cannot replace uses of a value owned by another function")]
fn body_replace_uses_rejects_foreign_old() {
let (mut ctx, _, b, _, _, a_insn, b_insn, _, _) = colliding_body_ids();
ctx.bodies[b]
.replace_all_uses_with(ValueId::Instruction(a_insn), ValueId::Instruction(b_insn));
}
#[test]
#[should_panic(expected = "cannot replace uses with a value owned by another function")]
fn body_replace_uses_rejects_foreign_new() {
let (mut ctx, _, b, _, _, a_insn, b_insn, _, _) = colliding_body_ids();
ctx.bodies[b]
.replace_all_uses_with(ValueId::Instruction(b_insn), ValueId::Instruction(a_insn));
}
#[test]
#[should_panic(expected = "block belongs to another function")]
fn body_block_access_rejects_colliding_foreign_id() {
let (ctx, _, b, a_block, _, _, _, _, _) = colliding_body_ids();
let _ = ctx.bodies[b].block(a_block);
}
#[test]
#[should_panic(expected = "instruction belongs to another function")]
fn body_insn_access_rejects_colliding_foreign_id() {
let (ctx, _, b, _, _, a_insn, _, _, _) = colliding_body_ids();
let _ = ctx.bodies[b].insn(a_insn);
}
#[test]
#[should_panic(expected = "block parameter belongs to another function")]
fn body_param_access_rejects_colliding_foreign_id() {
let (ctx, _, b, _, _, _, _, a_param, _) = colliding_body_ids();
let _ = ctx.bodies[b].block_param(a_param);
}
#[test]
fn make_function_creates_function_with_correct_name_root_address() {
let mut ctx = Context::new();
let f = FunctionBody::make(&mut ctx, "main".into()).unwrap();
assert_eq!(f.name(), "main");
}
#[test]
fn get_function_by_name_returns_correct_function() {
let mut ctx = Context::new();
let id = FunctionBody::make(&mut ctx, "foo".into()).unwrap().id();
let f = FunctionBody::from_name(&ctx, "foo").unwrap();
assert_eq!(f.id(), id);
assert_eq!(f.name(), "foo");
}
#[test]
fn get_function_by_name_returns_none_if_not_found() {
let ctx = Context::new();
assert!(FunctionBody::from_name(&ctx, "nonexistent").is_none());
}
#[test]
fn get_function_by_addr_returns_correct_function() {
let mut ctx = Context::new();
let id = FunctionBody::make_at_addr(&mut ctx, 0x2000, None).id();
let addresses = crate::address_index::AddressIndex::analyze(&ctx);
let f = FunctionBody::from_id(&ctx, addresses.function_at(0x2000).unwrap());
assert_eq!(f.id(), id);
assert_eq!(f.address(), Some(0x2000));
assert_eq!(f.name(), "fn_2000");
}
#[test]
fn get_function_by_addr_returns_none_if_missing() {
let ctx = Context::new();
let addresses = crate::address_index::AddressIndex::analyze(&ctx);
assert!(addresses.function_at(0xdeadbeef).is_none());
}
#[test]
fn add_block_via_function_mut_ref_updates_blocks_list() {
let mut ctx = Context::new();
let baz_id = FunctionBody::make(&mut ctx, "baz".into()).unwrap().id;
let root = BasicBlock::make(&mut ctx, baz_id).id;
let extra = BasicBlock::make(&mut ctx, baz_id).id;
let mut baz = FunctionBody::from_id_mut(&mut ctx, baz_id);
baz.add_block(root);
baz.add_block(extra);
let block_ids: Vec<_> = baz.blocks().map(|b| b.id).collect();
assert!(block_ids.contains(&root));
assert!(block_ids.contains(&extra));
}
#[test]
fn display_shows_function_name_and_block_contents() {
let mut ctx = Context::new();
FunctionBody::make(&mut ctx, "display_test".into()).unwrap();
let f = FunctionBody::from_name(&ctx, "display_test").unwrap();
let s = f.to_string();
assert!(s.contains("fn display_test:"));
}
#[test]
fn iter_yields_all_blocks() {
let mut ctx = Context::new();
let f_id = FunctionBody::make(&mut ctx, "iter_fn".into()).unwrap().id;
let root = BasicBlock::make(&mut ctx, f_id).id;
let extra = BasicBlock::make(&mut ctx, f_id).id;
let mut f = FunctionBody::from_id_mut(&mut ctx, f_id);
f.add_block(root);
f.add_block(extra);
let f = FunctionBody::from_name(&ctx, "iter_fn").unwrap();
let ids: Vec<_> = f.iter().map(|b| b.id).collect();
assert!(ids.contains(&root));
assert!(ids.contains(&extra));
}
#[test]
fn into_iterator_for_function_ref_matches_iter() {
let mut ctx = Context::new();
let f_id = FunctionBody::make(&mut ctx, "into_iter_fn".into())
.unwrap()
.id;
let b1 = BasicBlock::make(&mut ctx, f_id).id;
let b2 = BasicBlock::make(&mut ctx, f_id).id;
let mut f = FunctionBody::from_id_mut(&mut ctx, f_id);
f.add_block(b1);
f.add_block(b2);
let f = FunctionBody::from_name(&ctx, "into_iter_fn").unwrap();
let mut via_iter: Vec<usize> = f.iter().map(|b| usize::from(b.id.local)).collect();
let mut via_into: Vec<usize> = (&f).into_iter().map(|b| usize::from(b.id.local)).collect();
via_iter.sort();
via_into.sort();
assert_eq!(via_iter, via_into);
}
#[test]
fn qcode_fn_single_block_populates_function() {
let mut ctx = Context::new();
qcode!(
ctx,
"
fn simple:
<entry>
return at 0;
"
);
let f = FunctionBody::from_name(&ctx, "simple").unwrap();
assert_eq!(f.name(), "simple");
assert!(f.root().is_some());
assert_eq!(f.root().unwrap().name().unwrap(), "entry");
assert_eq!(f.blocks().count(), 1);
}
#[test]
fn qcode_fn_multi_block_populates_all_blocks() {
let mut ctx = Context::new();
qcode!(
ctx,
"
fn multiblock:
<bb1>
if i8 1 goto <bb2> else goto <bb3>;
<bb2>
goto <bb3>;
<bb3>
return at 0;
"
);
let f = FunctionBody::from_name(&ctx, "multiblock").unwrap();
assert_eq!(f.root().unwrap().name().unwrap(), "bb1");
let block_names: Vec<_> = f.blocks().filter_map(|b| b.name()).collect();
assert!(block_names.contains(&"bb1"), "missing bb1");
assert!(block_names.contains(&"bb2"), "missing bb2");
assert!(block_names.contains(&"bb3"), "missing bb3");
assert_eq!(f.blocks().count(), 3);
}
#[test]
fn qcode_fn_id_variable_is_set() {
let mut ctx = Context::new();
qcode!(
ctx,
"
fn myfn:
<start>
return at 0;
"
);
let by_name = FunctionBody::from_name(&ctx, "myfn").unwrap();
assert_eq!(by_name.name(), "myfn");
}
#[test]
fn indexed_address_registration_keeps_foreign_block_rootless() {
let mut ctx = Context::new();
let block_id = {
let __f = ctx.anon_function();
BasicBlock::make(&mut ctx, __f)
}
.id;
let mut addresses = crate::address_index::AddressIndex::analyze(&ctx);
addresses
.register(
&mut ctx,
0x1000,
crate::address_index::AddressTarget::Block(block_id),
)
.unwrap();
let fn_id = FunctionBody::make(&mut ctx, "fn_1000".into()).unwrap().id;
addresses
.register(
&mut ctx,
0x1000,
crate::address_index::AddressTarget::Function(fn_id),
)
.unwrap();
assert_eq!(addresses.function_at(0x1000), Some(fn_id));
assert_eq!(addresses.block_at(0x1000), None);
assert!(FunctionBody::from_id(&ctx, fn_id).root().is_none());
assert_ne!(block_id.func, fn_id);
}
}
#[cfg(test)]
mod memory_interface_tests {
use super::*;
fn slot() -> InterfaceSlot {
InterfaceSlot {
base: SlotBase::Arg(0),
offset: 8,
size: 8,
}
}
#[test]
fn memory_interface_round_trips_through_the_wire_format() {
let state = MemoryChannelState {
materialized: Some(MemoryInterfaceMap {
inputs: vec![slot()],
outputs: vec![InterfaceSlot {
base: SlotBase::Global(0x2000),
offset: 0,
size: 4,
}],
}),
..MemoryChannelState::default()
};
let config = bincode::config::standard();
let bytes = bincode::serde::encode_to_vec(&state, config).expect("encode memory state");
let (decoded, _): (MemoryChannelState, _) =
bincode::serde::decode_from_slice(&bytes, config).expect("decode memory state");
assert_eq!(decoded, state);
}
#[test]
fn default_memory_state_is_not_materialized() {
assert_eq!(MemoryChannelState::default().materialized(), None);
}
#[test]
fn stamping_written_spaces_preserves_the_materialized_interface() {
let mut ctx = Context::new();
let fid = FunctionBody::make(&mut ctx, "keeps_interface".into())
.unwrap()
.id;
let map = MemoryInterfaceMap {
inputs: vec![slot()],
outputs: vec![],
};
let mut body = FunctionBody::from_id_mut(&mut ctx, fid);
body.set_memory_effects(MemoryChannelState {
materialized: Some(map.clone()),
..MemoryChannelState::default()
});
body.set_written_spaces(None);
let effects = FunctionBody::from_id(&ctx, fid).effects().memory.clone();
assert_eq!(effects.materialized(), Some(&map));
assert_eq!(effects.coarse, WrittenSpacesState::Unbounded);
}
#[test]
fn an_unmappable_base_is_distinct_from_a_global_and_is_not_bindable() {
let unmappable = InterfaceSlot {
base: SlotBase::Unmappable,
offset: 0,
size: 8,
};
let global = InterfaceSlot {
base: SlotBase::Global(0),
offset: 0,
size: 8,
};
assert_ne!(unmappable, global);
assert!(!unmappable.is_bindable());
assert!(global.is_bindable());
assert!(
InterfaceSlot {
base: SlotBase::Arg(0),
offset: -8,
size: 8,
}
.is_bindable()
);
}
}