use jstd::registry::Registry;
use rustc_hash::{FxHashMap, FxHashSet};
use std::borrow::Cow;
use crate::{
context::{Context, Shared},
error::Result,
value::{
BasicBlock, BodyView, FunctionBody, FunctionId, Instruction, ModuleView, QCodeView,
ValueId,
block::{BlockId, EdgeId},
block_param::BlockParam,
block_param::BlockParamId,
function::FunctionInterface,
insn::{InstructionId, LocalInsnId, Mnemonic},
util::body_mut::BodyMut,
},
};
pub trait QCodeMut<'str> {
type View<'v>: QCodeView<'v, 'str>
where
Self: 'v,
'str: 'v;
fn function_mut(&mut self, id: FunctionId) -> &mut FunctionBody<'str>;
fn body(&self, id: FunctionId) -> &FunctionBody<'str>;
fn shr(&self) -> &Shared<'str>;
fn interfaces(&self) -> &Registry<FunctionId, FunctionInterface<'str>>;
fn view(&self) -> Self::View<'_>;
fn instruction_mut(&mut self, id: InstructionId) -> &mut Instruction<'str> {
&mut self.function_mut(id.func).insns[id.local]
}
fn block_mut(&mut self, id: BlockId) -> &mut BasicBlock<'str> {
&mut self.function_mut(id.func).blocks[id.local]
}
fn block_param_mut(&mut self, id: BlockParamId) -> &mut BlockParam<'str> {
&mut self.function_mut(id.func).params[id.local]
}
fn register_body_name(
&mut self,
id: ValueId,
name: Cow<'str, str>,
old_name: Option<&str>,
) -> Result<()> {
let func = id
.name_scope_function()
.expect("register_body_name on a global-scoped value");
self.function_mut(func)
.register_body_name(id, name, old_name)
}
fn remove_block_param(&mut self, id: BlockParamId) {
self.function_mut(id.func).remove_block_param(id);
}
fn insert_insn_before(&mut self, block: BlockId, before: InstructionId, insn: InstructionId) {
self.function_mut(block.func)
.insert_insn_before(block, before, insn);
}
fn move_insn_before(&mut self, insn: InstructionId, before: InstructionId) {
self.function_mut(insn.func).move_insn_before(insn, before);
}
fn add_cfg_edge(&mut self, from: BlockId, to: BlockId) -> EdgeId {
debug_assert_eq!(
from.func, to.func,
"cross-function CFG edge {from:?} -> {to:?} (strict IR locality, ruling 2)"
);
self.function_mut(from.func).add_cfg_edge(from, to)
}
fn replace_all_uses_with(&mut self, old: impl Into<ValueId>, new: impl Into<ValueId>) {
let old = old.into();
let new = new.into();
if old == new {
return;
}
let Some(func) = old.owning_function() else {
return;
};
self.function_mut(func).replace_all_uses_with(old, new);
}
fn remove_instruction(&mut self, id: InstructionId) {
self.function_mut(id.func).remove_instruction(id);
}
fn replace_instruction(&mut self, id: InstructionId, new: impl Into<ValueId>) {
self.function_mut(id.func)
.replace_instruction(id, new.into());
}
fn remove_instructions(&mut self, dead: &FxHashSet<InstructionId>) {
let mut by_func: FxHashMap<FunctionId, FxHashSet<LocalInsnId>> = FxHashMap::default();
for &id in dead {
by_func.entry(id.func).or_default().insert(id.local);
}
for (func, dead) in by_func {
self.function_mut(func).remove_instructions(&dead);
}
}
fn rehome_outgoing_edges(&mut self, keep: BlockId, remove: BlockId) {
self.function_mut(keep.func)
.rehome_outgoing_edges(keep, remove);
}
fn replace_instruction_mnemonic(&mut self, id: InstructionId, mnemonic: Mnemonic) {
self.function_mut(id.func)
.replace_instruction_mnemonic(id, mnemonic);
}
fn unroster_block(&mut self, block: BlockId) {
self.function_mut(block.func).unroster_block(block);
}
fn delete_block(&mut self, block: BlockId) {
self.function_mut(block.func).delete_block(block);
}
fn absorb_block(&mut self, keep: BlockId, other: BlockId, edge_ab: EdgeId) {
self.function_mut(keep.func)
.absorb_block(keep, other, edge_ab);
}
}
impl<'str, H: QCodeMut<'str>> QCodeMut<'str> for &mut H {
type View<'v>
= H::View<'v>
where
Self: 'v,
'str: 'v;
fn function_mut(&mut self, id: FunctionId) -> &mut FunctionBody<'str> {
(**self).function_mut(id)
}
fn body(&self, id: FunctionId) -> &FunctionBody<'str> {
(**self).body(id)
}
fn shr(&self) -> &Shared<'str> {
(**self).shr()
}
fn interfaces(&self) -> &Registry<FunctionId, FunctionInterface<'str>> {
(**self).interfaces()
}
fn view(&self) -> Self::View<'_> {
(**self).view()
}
}
impl<'str> QCodeMut<'str> for Context<'str> {
type View<'v>
= ModuleView<'v, 'str>
where
Self: 'v,
'str: 'v;
fn function_mut(&mut self, id: FunctionId) -> &mut FunctionBody<'str> {
&mut self.bodies[id]
}
fn body(&self, id: FunctionId) -> &FunctionBody<'str> {
&self.bodies[id]
}
fn shr(&self) -> &Shared<'str> {
&self.shared
}
fn interfaces(&self) -> &Registry<FunctionId, FunctionInterface<'str>> {
&self.interfaces
}
fn view(&self) -> ModuleView<'_, 'str> {
ModuleView::new(self)
}
}
impl<'a, 'str> QCodeMut<'str> for BodyMut<'a, 'str> {
type View<'v>
= BodyView<'v, 'str>
where
Self: 'v,
'str: 'v;
fn function_mut(&mut self, id: FunctionId) -> &mut FunctionBody<'str> {
BodyMut::function_mut(self, id)
}
fn body(&self, id: FunctionId) -> &FunctionBody<'str> {
BodyMut::function(self, id)
}
fn shr(&self) -> &Shared<'str> {
self.shared
}
fn interfaces(&self) -> &Registry<FunctionId, FunctionInterface<'str>> {
self.interfaces
}
fn view(&self) -> BodyView<'_, 'str> {
BodyMut::view(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::{BasicBlock, insn::InstructionId};
fn absorb_forwarding_pair<'str>(host: &mut impl QCodeMut<'str>, func: FunctionId) {
let (keep, other, edge) = {
let view = host.view();
let ids = view.function_ref(func).block_ids();
let [keep, other] = ids[..] else {
panic!("expected exactly two rostered blocks");
};
let edge = *view.block(keep).edges.iter().next().expect("edge");
(keep, other, edge)
};
host.absorb_block(keep, other, edge);
}
fn forwarding_pair(ctx: &mut Context<'_>) -> (FunctionId, InstructionId) {
let func = FunctionBody::make(ctx, "f".into()).unwrap().id;
let keep = BasicBlock::make(ctx, func).id;
let other = BasicBlock::make(ctx, func).id;
ctx.bodies[func].set_root_id(Some(keep.local));
let value = ctx.get_const(7, 8).id();
let ret = ctx.builder(other).push_return(value).id;
ctx.builder(keep).push_branch(other);
(func, ret)
}
#[test]
fn module_host_runs_generic_transform() {
let mut ctx = Context::new();
let (func, ret) = forwarding_pair(&mut ctx);
absorb_forwarding_pair(&mut ctx, func);
assert_eq!(ctx.view().function_ref(func).block_ids().len(), 1);
assert!(ctx.contains_instruction(ret));
}
#[test]
fn checked_out_host_runs_generic_transform() {
let mut ctx = Context::new();
let (func, ret) = forwarding_pair(&mut ctx);
{
let mut host = BodyMut::new(&mut ctx.bodies[func], &ctx.shared, &ctx.interfaces);
absorb_forwarding_pair(&mut host, func);
assert_eq!(
QCodeMut::view(&host).function_ref(func).block_ids().len(),
1
);
}
assert!(ctx.contains_instruction(ret));
}
#[test]
fn shared_value_rauw_is_a_noop() {
let mut ctx = Context::new();
let (_, ret) = forwarding_pair(&mut ctx);
let lit = ctx.get_const(7, 8).id();
let other = ctx.get_const(9, 8).id();
QCodeMut::replace_all_uses_with(&mut ctx, lit, other);
assert!(ctx.contains_instruction(ret));
}
}