mod builder;
pub mod equivalence;
mod name;
mod state;
use alloc::rc::Rc;
use core::{
fmt,
ptr::{DynMetadata, NonNull, Pointee},
sync::atomic::AtomicU32,
};
use smallvec::SmallVec;
pub use self::{
builder::{GenericOperationBuilder, OperationBuilder},
name::{AttrInfo, OperationName, ParseAssemblyFn},
state::{OperationState, PendingSuccessorInfo},
};
use super::{
effects::{
Effect, EffectInstance, HasRecursiveMemoryEffects, MemoryEffect, MemoryEffectOpInterface,
},
*,
};
use crate::{
Attribute, AttributeRef, AttributeRegistration, Forward, NamedAttribute, ProgramPoint,
attributes::OpAttribute, dialects::builtin::attributes::SymbolRefAttr,
patterns::RewritePatternSet,
};
pub type OperationRef = UnsafeIntrusiveEntityRef<Operation>;
pub type OpList = EntityList<Operation>;
pub type OpCursor<'a> = EntityListCursor<'a, Operation>;
pub type OpCursorMut<'a> = EntityListCursorMut<'a, Operation>;
#[derive(Spanned)]
pub struct Operation {
context: NonNull<Context>,
name: OperationName,
offset: usize,
order: AtomicU32,
#[span]
pub span: SourceSpan,
pub attrs: EntityMap<OpAttribute>,
pub operands: OpOperandStorage,
pub results: OpResultStorage,
pub successors: OpSuccessorStorage,
pub regions: RegionList,
}
impl Eq for Operation {}
impl PartialEq for Operation {
fn eq(&self, other: &Self) -> bool {
core::ptr::addr_eq(self, other)
}
}
impl core::hash::Hash for Operation {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::ptr::hash(self, state)
}
}
impl fmt::Debug for Operation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Operation")
.field_with("name", |f| write!(f, "{}", &self.name()))
.field("offset", &self.offset)
.field("order", &self.order)
.field("attrs", &self.attrs)
.field("block", &self.parent().as_ref().map(|b| b.borrow().id()))
.field_with("operands", |f| {
let mut list = f.debug_list();
for operand in self.operands().all() {
list.entry(&operand.borrow());
}
list.finish()
})
.field("results", &self.results)
.field("successors", &self.successors)
.finish_non_exhaustive()
}
}
impl fmt::Debug for OperationRef {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fmt::Debug::fmt(&self.borrow(), f)
}
}
impl fmt::Display for OperationRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.borrow().name())
}
}
impl AsRef<dyn Op> for Operation {
fn as_ref(&self) -> &dyn Op {
self.name.upcast(self.container()).unwrap()
}
}
impl AsMut<dyn Op> for Operation {
fn as_mut(&mut self) -> &mut dyn Op {
self.name.upcast_mut(self.container().cast_mut()).unwrap()
}
}
impl Entity for Operation {}
impl EntityWithParent for Operation {
type Parent = Block;
}
impl EntityListItem for Operation {
fn on_inserted(this: OperationRef, _cursor: &mut EntityListCursorMut<'_, Self>) {
if this.name().implements::<dyn Symbol>() {
let parent = this.nearest_symbol_table();
if let Some(mut parent) = parent
&& parent.name().implements::<dyn SymbolTable>()
{
let mut symbol_table = parent.borrow_mut();
let sym_manager = symbol_table.as_trait_mut::<dyn SymbolTable>().unwrap();
let mut sym_manager = sym_manager.symbol_manager_mut();
let symbol_ref = this.borrow().as_symbol_ref().unwrap();
let is_new = sym_manager.insert_new(symbol_ref, ProgramPoint::Invalid);
assert!(
is_new,
"Unable to insert {} in symbol table of {}: symbol {} is already registered \
to another operation {}.",
this.name(),
parent.name(),
symbol_ref.borrow().name(),
sym_manager.lookup(symbol_ref.borrow().name()).unwrap().borrow().name()
);
}
}
let order_offset = core::mem::offset_of!(Operation, order);
unsafe {
let ptr = UnsafeIntrusiveEntityRef::as_ptr(&this);
let order_ptr = ptr.byte_add(order_offset).cast::<AtomicU32>();
(*order_ptr).store(Self::INVALID_ORDER, core::sync::atomic::Ordering::Release);
}
}
fn on_transfer(_this: OperationRef, _from: &mut EntityList<Self>, to: &mut EntityList<Self>) {
let mut to = to.parent();
to.borrow_mut().invalidate_op_order();
}
fn on_removed(this: OperationRef, _list: &mut EntityListCursorMut<'_, Self>) {
if this.name().implements::<dyn Symbol>() {
let parent = this.nearest_symbol_table();
if let Some(mut parent) = parent {
if parent.name().implements::<dyn SymbolTable>() {
let mut symbol_table = parent.borrow_mut();
let sym_manager = symbol_table.as_trait_mut::<dyn SymbolTable>().unwrap();
let mut sym_manager = sym_manager.symbol_manager_mut();
let symbol_ref = this.borrow().as_symbol_ref().unwrap();
sym_manager.remove(symbol_ref);
};
}
}
}
}
impl EntityParent<Region> for Operation {
fn offset() -> usize {
core::mem::offset_of!(Operation, regions)
}
}
impl Operation {
#[doc(hidden)]
pub unsafe fn uninit<T: Op>(context: Rc<Context>, name: OperationName, offset: usize) -> Self {
assert!(name.is::<T>());
Self {
context: unsafe { NonNull::new_unchecked(Rc::as_ptr(&context).cast_mut()) },
name,
offset,
order: AtomicU32::new(0),
span: Default::default(),
attrs: Default::default(),
operands: Default::default(),
results: Default::default(),
successors: Default::default(),
regions: Default::default(),
}
}
}
impl OperationRef {
pub fn insert_at_start(self, mut block: BlockRef) {
assert!(
self.parent().is_none(),
"cannot insert operation that is already attached to another block"
);
{
let mut block = block.borrow_mut();
block.body_mut().push_front(self);
}
}
pub fn insert_at_end(self, mut block: BlockRef) {
assert!(
self.parent().is_none(),
"cannot insert operation that is already attached to another block"
);
{
let mut block = block.borrow_mut();
block.body_mut().push_back(self);
}
}
pub fn insert_before(self, before: OperationRef) {
assert!(
self.parent().is_none(),
"cannot insert operation that is already attached to another block"
);
let mut block = before.parent().expect("'before' block is not attached to a block");
{
let mut block = block.borrow_mut();
let block_body = block.body_mut();
let mut cursor = unsafe { block_body.cursor_mut_from_ptr(before) };
cursor.insert_before(self);
}
}
pub fn insert_after(self, after: OperationRef) {
assert!(
self.parent().is_none(),
"cannot insert operation that is already attached to another block"
);
let mut block = after.parent().expect("'after' block is not attached to a block");
{
let mut block = block.borrow_mut();
let block_body = block.body_mut();
let mut cursor = unsafe { block_body.cursor_mut_from_ptr(after) };
cursor.insert_after(self);
}
}
}
impl OperationRef {
pub fn name(&self) -> OperationName {
let ptr = OperationRef::as_ptr(self);
unsafe {
let name_ptr = core::ptr::addr_of!((*ptr).name);
OperationName::clone(&*name_ptr)
}
}
pub fn as_trait_ref<Trait>(self) -> Option<UnsafeIntrusiveEntityRef<Trait>>
where
Trait: ?Sized + Pointee<Metadata = DynMetadata<Trait>> + 'static,
{
let ptr = self.name().upcast_raw::<Trait>(OperationRef::into_raw(self).cast())?;
let (_, metadata) = ptr.to_raw_parts();
Some(unsafe { self.cast_unsized_unchecked::<Trait>(metadata) })
}
pub fn try_downcast_op<T: Op>(self) -> Result<UnsafeIntrusiveEntityRef<T>, Self> {
if self.name().is::<T>() {
Ok(unsafe { self.cast_unchecked::<T>() })
} else {
Err(self)
}
}
pub fn parent_op(&self) -> Option<OperationRef> {
self.parent_region().and_then(|region| region.parent())
}
pub fn parent_region(&self) -> Option<RegionRef> {
self.grandparent()
}
pub fn nearest_symbol_table(&self) -> Option<OperationRef> {
let mut parent = self.parent_op();
while let Some(parent_op) = parent.take() {
if parent_op.name().implements::<dyn SymbolTable>() {
return Some(parent_op);
}
parent = parent_op.parent_op();
}
None
}
}
impl Operation {
pub fn name(&self) -> OperationName {
self.name.clone()
}
pub fn dialect(&self) -> Rc<dyn Dialect> {
self.context().get_registered_dialect(self.name.dialect())
}
#[inline]
pub fn set_span(&mut self, span: SourceSpan) {
self.span = span;
}
#[inline(always)]
pub fn context(&self) -> &Context {
unsafe { self.context.as_ref() }
}
pub fn context_rc(&self) -> Rc<Context> {
unsafe {
let ptr = self.context.as_ptr().cast_const();
Rc::increment_strong_count(ptr);
Rc::from_raw(ptr)
}
}
}
impl Operation {
pub fn verify(&self) -> Result<(), Report> {
let dyn_op: &dyn Op = self.as_ref();
dyn_op.verify(self.context())
}
pub fn recursively_verify(&self) -> Result<(), Report> {
self.postwalk(|op: &Operation| op.verify().into()).into_result()
}
}
impl Operation {
pub(super) const fn container(&self) -> *const () {
unsafe {
let ptr = self as *const Self;
ptr.byte_sub(self.offset).cast()
}
}
#[inline(always)]
pub fn as_operation_ref(&self) -> OperationRef {
unsafe { OperationRef::from_raw(self.container().cast()) }
}
#[inline]
pub fn is<T: 'static>(&self) -> bool {
self.name.is::<T>()
}
#[inline]
pub fn implements<Trait>(&self) -> bool
where
Trait: ?Sized + Pointee<Metadata = DynMetadata<Trait>> + 'static,
{
self.name.implements::<Trait>()
}
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
self.name.downcast_ref::<T>(self.container())
}
pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.name.downcast_mut::<T>(self.container().cast_mut())
}
pub fn as_trait<Trait>(&self) -> Option<&Trait>
where
Trait: ?Sized + Pointee<Metadata = DynMetadata<Trait>> + 'static,
{
self.name.upcast(self.container())
}
pub fn as_trait_mut<Trait>(&mut self) -> Option<&mut Trait>
where
Trait: ?Sized + Pointee<Metadata = DynMetadata<Trait>> + 'static,
{
self.name.upcast_mut(self.container().cast_mut())
}
}
impl Operation {
#[inline]
pub fn has_properties(&self) -> bool {
!self.name.properties().is_empty()
}
pub fn properties(&self) -> impl Iterator<Item = NamedAttribute> {
struct PropertyIter<'a> {
op: &'a Operation,
infos: &'a [AttrInfo],
index: usize,
}
impl Iterator for PropertyIter<'_> {
type Item = NamedAttribute;
fn next(&mut self) -> Option<Self::Item> {
let index = self.index;
if let Some(info) = self.infos.get(index) {
self.index += 1;
let value = unsafe { (info.get_raw)(self.op.container(), info) };
Some(NamedAttribute {
name: info.name,
value,
})
} else {
None
}
}
}
let infos = self.name.properties();
PropertyIter {
op: self,
infos,
index: 0,
}
}
#[inline]
pub fn has_property(&self, name: impl Into<interner::Symbol>) -> bool {
self.name.has_property(name.into())
}
#[track_caller]
pub fn get_property<'a>(
&'a self,
name: impl Into<interner::Symbol>,
) -> EntityRef<'a, dyn Attribute> {
let name = name.into();
let Some(prop) = self.name.get_property(name) else {
panic!("{name} is not a valid property for '{}'", &self.name);
};
unsafe { (prop.get)(self.container(), prop, core::marker::PhantomData) }
}
#[track_caller]
pub fn get_property_mut<'a>(
&'a mut self,
name: impl Into<interner::Symbol>,
) -> EntityMut<'a, dyn Attribute> {
let name = name.into();
let Some(prop) = self.name.get_property(name) else {
panic!("{name} is not a valid property for '{}'", &self.name);
};
unsafe { (prop.get_mut)(self.container().cast_mut(), prop, core::marker::PhantomData) }
}
#[track_caller]
pub fn set_property(
&mut self,
name: impl Into<interner::Symbol>,
value: AttributeRef,
) -> Result<(), Report> {
let name = name.into();
let Some(prop) = self.name.get_property(name) else {
panic!("{name} is not a valid property for '{}'", &self.name);
};
unsafe { (prop.try_from)(self.container().cast_mut(), prop, value) }
}
}
impl Operation {
#[inline]
pub fn has_attributes(&self) -> bool {
!self.attrs.is_empty()
}
#[inline(always)]
pub fn attributes(&self) -> &EntityMap<OpAttribute> {
&self.attrs
}
#[inline(always)]
pub fn attributes_mut(&mut self) -> &mut EntityMap<OpAttribute> {
&mut self.attrs
}
pub fn get_attribute(&self, name: impl Into<interner::Symbol>) -> Option<AttributeRef> {
let name = name.into();
if let Some(prop) = self.name.get_property(name) {
Some(unsafe { (prop.get_raw)(self.container(), prop) })
} else {
let cursor = self.attrs.find(&name);
if let Some(found) = cursor.get() {
Some(found.as_named_attribute().value)
} else {
None
}
}
}
pub fn get_typed_attribute<T>(
&self,
name: impl Into<interner::Symbol>,
) -> Option<UnsafeIntrusiveEntityRef<T>>
where
T: AttributeRegistration,
{
self.get_attribute(name.into())
.and_then(|attr_ref| attr_ref.try_downcast_attr::<T>().ok())
}
pub fn has_attribute(&self, name: impl Into<interner::Symbol>) -> bool {
self.get_attribute(name.into()).is_some()
}
pub fn set_attribute(&mut self, name: impl Into<interner::Symbol>, value: AttributeRef) {
let name = name.into();
let attr = OpAttribute::from(NamedAttribute::new(name, value));
let attr = self.context().alloc_map_item(attr);
self.attrs.insert(attr);
}
pub fn remove_attribute(&mut self, name: impl Into<interner::Symbol>) {
let name = name.into();
self.attrs.find_mut(&name).remove();
}
}
impl Operation {
pub fn set_symbol_attribute(
&mut self,
attr_name: impl Into<interner::Symbol>,
symbol: impl AsSymbolRef,
) {
let attr_name = attr_name.into();
let symbol = symbol.as_symbol_ref();
let (data_ptr, _) = SymbolRef::as_ptr(&symbol).to_raw_parts();
assert!(
!core::ptr::addr_eq(data_ptr, self.container()),
"a symbol cannot use itself, except via nested operations"
);
let owner = self.as_operation_ref();
let context = self.context_rc();
if self.has_property(attr_name) {
let mut prop = self.get_property_mut(attr_name);
let prop = prop.downcast_mut::<SymbolRefAttr>().unwrap();
prop.set_symbol(symbol);
} else if let Some(mut attr) = self
.get_attribute(attr_name)
.and_then(|attr| attr.try_downcast_attr::<SymbolRefAttr>().ok())
{
attr.borrow_mut().set_symbol(symbol);
} else {
let path = symbol.borrow().path();
let symbol_ref = crate::dialects::builtin::attributes::SymbolRef::new(path, None);
let mut attr = context.create_attribute::<SymbolRefAttr, _>(symbol_ref);
let user = context.alloc_tracked(SymbolUse::new(owner, attr));
attr.borrow_mut().set_user(user);
self.set_attribute(attr_name, attr.as_attribute_ref());
attr.borrow_mut().link(symbol);
}
}
fn unsafe_set_symbol_property(
&mut self,
attr_name: interner::Symbol,
symbol: impl AsSymbolRef,
) {
let symbol = symbol.as_symbol_ref();
let (data_ptr, _) = SymbolRef::as_ptr(&symbol).to_raw_parts();
assert!(
!core::ptr::addr_eq(data_ptr, self.container()),
"a symbol cannot use itself, except via nested operations"
);
let context = self.context_rc();
let owner = self.as_operation_ref();
let mut attr = {
let symbol = symbol.borrow();
let mut attr = context.create_attribute::<SymbolRefAttr, _>(
crate::dialects::builtin::attributes::SymbolRef::new(symbol.path(), None),
);
let user = context.alloc_tracked(SymbolUse::new(owner, attr));
attr.borrow_mut().set_user(user);
attr
};
self.set_property(attr_name, attr)
.expect("not a valid value for this operation property");
attr.borrow_mut().link(symbol);
}
}
impl Operation {
#[inline]
pub fn parent(&self) -> Option<BlockRef> {
self.as_operation_ref().parent()
}
pub fn parent_region(&self) -> Option<RegionRef> {
self.as_operation_ref().parent_region()
}
pub fn parent_op(&self) -> Option<OperationRef> {
self.as_operation_ref().parent_op()
}
pub fn nearest_parent_op<T: Op>(&self) -> Option<UnsafeIntrusiveEntityRef<T>> {
let mut parent = self.parent_op();
while let Some(op) = parent.take() {
parent = op.parent_op();
if let Ok(op) = op.try_downcast_op::<T>() {
return Some(op);
}
}
None
}
}
impl Operation {
pub fn prewalk_all<F>(&self, callback: F)
where
F: FnMut(&Operation),
{
Walk::<Operation>::prewalk_all::<Forward, _>(self, callback);
}
pub fn prewalk<F, B>(&self, callback: F) -> WalkResult<B>
where
F: FnMut(&Operation) -> WalkResult<B>,
{
Walk::<Operation>::prewalk::<Forward, _, _>(self, callback)
}
pub fn postwalk_all<F>(&self, callback: F)
where
F: FnMut(&Operation),
{
Walk::<Operation>::postwalk_all::<Forward, _>(self, callback);
}
pub fn postwalk<F, B>(&self, callback: F) -> WalkResult<B>
where
F: FnMut(&Operation) -> WalkResult<B>,
{
Walk::<Operation>::postwalk::<Forward, _, _>(self, callback)
}
}
impl Operation {
#[inline]
pub fn has_regions(&self) -> bool {
!self.regions.is_empty()
}
#[inline]
pub fn num_regions(&self) -> usize {
self.regions.len()
}
#[inline(always)]
pub fn regions(&self) -> &RegionList {
&self.regions
}
#[inline(always)]
pub fn regions_mut(&mut self) -> &mut RegionList {
&mut self.regions
}
pub fn region(&self, index: usize) -> EntityRef<'_, Region> {
let mut cursor = self.regions.front();
let mut count = 0;
while !cursor.is_null() {
if index == count {
return cursor.into_borrow().unwrap();
}
cursor.move_next();
count += 1;
}
panic!("invalid region index {index}: out of bounds");
}
pub fn region_mut(&mut self, index: usize) -> EntityMut<'_, Region> {
let mut cursor = self.regions.front_mut();
let mut count = 0;
while !cursor.is_null() {
if index == count {
return cursor.into_borrow_mut().unwrap();
}
cursor.move_next();
count += 1;
}
panic!("invalid region index {index}: out of bounds");
}
}
impl Operation {
#[inline]
pub fn has_successors(&self) -> bool {
!self.successors.is_empty()
}
#[inline]
pub fn num_successors(&self) -> usize {
self.successors.len()
}
#[inline(always)]
pub fn successors(&self) -> &OpSuccessorStorage {
&self.successors
}
#[inline(always)]
pub fn successors_mut(&mut self) -> &mut OpSuccessorStorage {
&mut self.successors
}
#[inline]
pub fn successor_group(&self, index: usize) -> OpSuccessorRange<'_> {
self.successors.group(index)
}
#[inline]
pub fn successor_group_mut(&mut self, index: usize) -> OpSuccessorRangeMut<'_> {
self.successors.group_mut(index)
}
#[inline]
pub fn keyed_successor_group<T>(&self, index: usize) -> KeyedSuccessorRange<'_, T>
where
T: KeyedSuccessor,
{
let range = self.successors.group(index);
KeyedSuccessorRange::new(range, &self.operands)
}
#[inline]
pub fn keyed_successor_group_mut<T>(&mut self, index: usize) -> KeyedSuccessorRangeMut<'_, T>
where
T: KeyedSuccessor,
{
let range = self.successors.group_mut(index);
KeyedSuccessorRangeMut::new(range, &mut self.operands)
}
#[inline]
pub fn successor_in_group(&self, group_index: usize, index: usize) -> OpSuccessor<'_> {
let info = &self.successors.group(group_index)[index];
OpSuccessor {
dest: info.block,
arguments: self.operands.group(info.operand_group as usize),
}
}
#[inline]
pub fn successor_in_group_mut(
&mut self,
group_index: usize,
index: usize,
) -> OpSuccessorMut<'_> {
let info = &self.successors.group(group_index)[index];
OpSuccessorMut {
dest: info.block,
arguments: self.operands.group_mut(info.operand_group as usize),
}
}
#[inline]
#[track_caller]
pub fn successor(&self, index: usize) -> OpSuccessor<'_> {
let info = &self.successors[index];
OpSuccessor {
dest: info.block,
arguments: self.operands.group(info.operand_group as usize),
}
}
#[inline]
#[track_caller]
pub fn successor_mut(&mut self, index: usize) -> OpSuccessorMut<'_> {
let info = self.successors[index];
OpSuccessorMut {
dest: info.block,
arguments: self.operands.group_mut(info.operand_group as usize),
}
}
pub fn successor_iter(&self) -> impl DoubleEndedIterator<Item = OpSuccessor<'_>> + '_ {
self.successors.iter().map(|info| OpSuccessor {
dest: info.block,
arguments: self.operands.group(info.operand_group as usize),
})
}
}
impl Operation {
#[inline]
pub fn has_operands(&self) -> bool {
!self.operands.is_empty()
}
#[inline]
pub fn num_operands(&self) -> usize {
self.operands.len()
}
#[inline]
pub fn operands(&self) -> &OpOperandStorage {
&self.operands
}
#[inline]
pub fn operands_mut(&mut self) -> &mut OpOperandStorage {
&mut self.operands
}
pub fn set_operands(&mut self, operands: impl IntoIterator<Item = ValueRef>) {
self.operands.clear();
let context = self.context_rc();
let owner = self.as_operation_ref();
self.operands.extend(
operands
.into_iter()
.enumerate()
.map(|(index, value)| context.make_operand(value, owner, index as u8)),
);
}
pub fn replaces_uses_of_with(&mut self, from: ValueRef, to: ValueRef) {
if ValueRef::ptr_eq(&from, &to) {
return;
}
for operand in self.operands.iter_mut() {
debug_assert!(operand.is_linked());
if ValueRef::ptr_eq(&from, &operand.borrow().value.unwrap()) {
operand.borrow_mut().set(to);
}
}
}
pub fn replace_all_uses_with(&mut self, values: impl ExactSizeIterator<Item = ValueRef>) {
assert_eq!(self.num_results(), values.len());
for (result, replacement) in self.results.iter_mut().zip(values) {
if (*result as ValueRef) == replacement {
continue;
}
result.borrow_mut().replace_all_uses_with(replacement);
}
}
pub fn replace_uses_with_if<F, V>(&mut self, values: V, should_replace: F)
where
V: ExactSizeIterator<Item = ValueRef>,
F: Fn(&OpOperandImpl) -> bool,
{
assert_eq!(self.num_results(), values.len());
for (result, replacement) in self.results.iter_mut().zip(values) {
let mut result = *result as ValueRef;
if result == replacement {
continue;
}
result.borrow_mut().replace_uses_with_if(replacement, &should_replace);
}
}
}
impl Operation {
#[inline]
pub fn has_results(&self) -> bool {
!self.results.is_empty()
}
#[inline]
pub fn num_results(&self) -> usize {
self.results.len()
}
#[inline]
pub fn results(&self) -> &OpResultStorage {
&self.results
}
#[inline]
pub fn results_mut(&mut self) -> &mut OpResultStorage {
&mut self.results
}
#[inline]
pub fn get_result(&self, index: usize) -> &OpResultRef {
&self.results[index]
}
pub fn is_used(&self) -> bool {
self.results.iter().any(|result| result.borrow().is_used())
}
pub fn has_exactly_one_use(&self) -> bool {
let mut used_by = None;
for result in self.results.iter() {
let result = result.borrow();
if !result.is_used() {
continue;
}
for used in result.iter_uses() {
if used_by.as_ref().is_some_and(|user| !OperationRef::eq(user, &used.owner)) {
return false;
} else if used_by.is_none() {
used_by = Some(used.owner);
}
}
}
used_by.is_some()
}
pub fn is_used_outside_of_block(&self, block: &BlockRef) -> bool {
self.results
.iter()
.any(|result| result.borrow().is_used_outside_of_block(block))
}
pub fn is_trivially_dead(&self) -> bool {
!self.is_used() && self.would_be_trivially_dead()
}
pub fn would_be_trivially_dead(&self) -> bool {
if self.implements::<dyn crate::traits::Terminator>() || self.implements::<dyn Symbol>() {
false
} else {
self.would_be_trivially_dead_even_if_terminator()
}
}
pub fn would_be_trivially_dead_even_if_terminator(&self) -> bool {
use crate::effects::RecursiveEffectIterator;
let this_op = self.as_operation_ref();
let effects = RecursiveEffectIterator::<MemoryEffect>::new(this_op);
for (effecting_op, effect) in effects {
let Some(effect) = effect else {
return false;
};
match effect.effect() {
MemoryEffect::Read => (),
MemoryEffect::Allocate => match effect.value() {
Some(value) => {
let is_defined_by_op =
value.borrow().get_defining_op().is_some_and(|op| op == effecting_op);
let is_droppable =
matches!(effect.effect(), MemoryEffect::Allocate) && is_defined_by_op;
if !is_droppable {
return false;
}
}
None => return false,
},
_ => return false,
}
}
true
}
pub fn is_memory_effect_free(&self) -> bool {
if let Some(mem_interface) = self.as_trait::<dyn MemoryEffectOpInterface>() {
if !mem_interface.has_no_effect() {
return false;
}
if !self.implements::<dyn HasRecursiveMemoryEffects>() {
return true;
}
} else if !self.implements::<dyn HasRecursiveMemoryEffects>() {
return false;
}
for region in self.regions() {
let walk_result = region.prewalk(|op| {
if !op.is_memory_effect_free() {
WalkResult::Break(())
} else {
WalkResult::Continue(())
}
});
if walk_result.was_interrupted() {
return false;
}
}
true
}
pub fn get_effects_recursively<T: Effect>(&self) -> Option<SmallVec<[EffectInstance<T>; 2]>> {
use crate::effects::RecursiveEffectIterator;
let mut effects = SmallVec::<[_; 2]>::default();
for (_, effect) in RecursiveEffectIterator::new(self.as_operation_ref()) {
effects.push(effect?);
}
Some(effects)
}
pub fn has_memory_effect(&self, expected_effect: MemoryEffect) -> bool {
let Some(mem_interface) = self.as_trait::<dyn MemoryEffectOpInterface>() else {
return false;
};
mem_interface.effects().any(|effect| effect.effect() == expected_effect)
}
pub fn has_single_memory_effect(&self, expected_effect: MemoryEffect) -> bool {
let Some(mem_interface) = self.as_trait::<dyn MemoryEffectOpInterface>() else {
return false;
};
if mem_interface.has_no_effect() {
return false;
}
for effect in mem_interface.effects() {
if effect.effect() != expected_effect {
return false;
}
}
true
}
}
impl Operation {
#[inline]
pub fn erase(&mut self) {
self.remove();
self.successors.clear();
self.operands.clear();
}
pub fn remove(&mut self) {
if let Some(mut parent) = self.parent() {
let mut block = parent.borrow_mut();
let body = block.body_mut();
let mut cursor = unsafe { body.cursor_mut_from_ptr(self.as_operation_ref()) };
cursor.remove();
}
}
pub fn move_to(&mut self, mut ip: ProgramPoint) {
let this = self.as_operation_ref();
if let Some(op) = ip.operation() {
if op == this {
return;
}
assert!(ip.block().is_some(), "cannot insert an operation relative to an orphaned op");
}
self.remove();
{
let mut cursor = ip.cursor_mut().expect("insertion point is invalid/unset");
cursor.insert_after(self.as_operation_ref());
}
}
pub fn drop_all_references(&mut self) {
self.operands.clear();
{
let mut region_cursor = self.regions.front_mut();
while let Some(mut region) = region_cursor.as_pointer() {
region.borrow_mut().drop_all_references();
region_cursor.move_next();
}
}
self.successors.clear();
}
pub fn drop_all_defined_value_uses(&mut self) {
for result in self.results.iter_mut() {
let mut res = result.borrow_mut();
res.uses_mut().clear();
}
let mut regions = self.regions.front_mut();
while let Some(mut region) = regions.as_pointer() {
let mut region = region.borrow_mut();
let blocks = region.body_mut();
let mut cursor = blocks.front_mut();
while let Some(mut block) = cursor.as_pointer() {
block.borrow_mut().drop_all_defined_value_uses();
cursor.move_next();
}
regions.move_next();
}
}
pub fn drop_all_uses(&mut self) {
for result in self.results.iter_mut() {
result.borrow_mut().uses_mut().clear();
}
}
}
impl Operation {
const INVALID_ORDER: u32 = u32::MAX;
const ORDER_STRIDE: u32 = 5;
pub fn is_ancestor_of(&self, other: &Self) -> bool {
core::ptr::addr_eq(self, other) || Self::is_a_proper_ancestor_of_b(self, other)
}
pub fn is_proper_ancestor_of(&self, other: &Self) -> bool {
Self::is_a_proper_ancestor_of_b(self, other)
}
fn is_a_proper_ancestor_of_b(a: &Self, b: &Self) -> bool {
let a = a.as_operation_ref();
let mut next = b.parent_op();
while let Some(b) = next.take() {
if OperationRef::ptr_eq(&a, &b) {
return true;
}
}
false
}
pub fn is_before_in_block(&self, other: &OperationRef) -> bool {
use core::sync::atomic::Ordering;
let block = self.parent().expect("operations without parent blocks have no order");
let other = other.borrow();
assert!(
other
.parent()
.as_ref()
.is_some_and(|other_block| BlockRef::ptr_eq(&block, other_block)),
"expected both operations to have the same parent block"
);
if !block.borrow().is_op_order_valid() {
Self::recompute_block_order(block);
} else {
self.update_order_if_necessary();
other.update_order_if_necessary();
}
self.order.load(Ordering::Relaxed) < other.order.load(Ordering::Relaxed)
}
fn update_order_if_necessary(&self) {
use core::sync::atomic::Ordering;
assert!(self.parent().is_some(), "expected valid parent");
let block = self.parent().unwrap();
if self.has_valid_order() || block.borrow().body().iter().count() == 1 {
return;
}
let this = self.as_operation_ref();
let prev = this.prev();
let next = this.next();
assert!(prev.is_some() || next.is_some(), "expected more than one operation in block");
if next.is_none() {
let prev = prev.unwrap();
let prev = prev.borrow();
let prev_order = prev.order.load(Ordering::Acquire);
if prev_order == Self::INVALID_ORDER {
return Self::recompute_block_order(block);
}
self.order.store(prev_order + Self::ORDER_STRIDE, Ordering::Release);
return;
}
if prev.is_none() {
let next = next.unwrap();
let next = next.borrow();
let next_order = next.order.load(Ordering::Acquire);
match next_order {
Self::INVALID_ORDER | 0 => {
return Self::recompute_block_order(block);
}
order if order <= Self::ORDER_STRIDE => {
self.order.store(order / 2, Ordering::Release);
}
_ => {
self.order.store(Self::ORDER_STRIDE, Ordering::Release);
}
}
return;
}
let prev = prev.unwrap().borrow().order.load(Ordering::Acquire);
let next = next.unwrap().borrow().order.load(Ordering::Acquire);
if prev == Self::INVALID_ORDER || next == Self::INVALID_ORDER {
return Self::recompute_block_order(block);
}
if prev + 1 == next {
return Self::recompute_block_order(block);
}
self.order.store(prev + ((next - prev) / 2), Ordering::Release);
}
fn recompute_block_order(block: BlockRef) {
use core::sync::atomic::Ordering;
let block = block.borrow();
let mut cursor = block.body().front();
let mut index = 0;
while let Some(op) = cursor.as_pointer() {
index += Self::ORDER_STRIDE;
cursor.move_next();
let ptr = OperationRef::as_ptr(&op);
unsafe {
let order_addr = core::ptr::addr_of!((*ptr).order);
(*order_addr).store(index, Ordering::Release);
}
}
block.mark_op_order_valid();
}
#[inline]
pub(crate) fn order(&self) -> Option<u32> {
use core::sync::atomic::Ordering;
match self.order.load(Ordering::Acquire) {
Self::INVALID_ORDER => None,
order => Some(order),
}
}
#[inline]
#[allow(unused)]
pub(crate) fn get_or_compute_order(&self) -> u32 {
use core::sync::atomic::Ordering;
if let Some(order) = self.order() {
return order;
}
Self::recompute_block_order(
self.parent().expect("cannot compute block ordering for orphaned operation"),
);
self.order.load(Ordering::Acquire)
}
#[inline(always)]
pub(super) fn has_valid_order(&self) -> bool {
self.order().is_some()
}
}
impl Operation {
#[inline]
pub fn populate_canonicalization_patterns(
&self,
rewrites: &mut RewritePatternSet,
context: Rc<Context>,
) {
self.name.populate_canonicalization_patterns(rewrites, context);
}
}
impl crate::traits::Foldable for Operation {
fn fold(&self, results: &mut smallvec::SmallVec<[OpFoldResult; 1]>) -> FoldResult {
use crate::traits::Foldable;
if let Some(foldable) = self.as_trait::<dyn Foldable>() {
foldable.fold(results)
} else {
FoldResult::Failed
}
}
fn fold_with<'operands>(
&self,
operands: &[Option<AttributeRef>],
results: &mut smallvec::SmallVec<[OpFoldResult; 1]>,
) -> FoldResult {
use crate::traits::Foldable;
if let Some(foldable) = self.as_trait::<dyn Foldable>() {
foldable.fold_with(operands, results)
} else {
FoldResult::Failed
}
}
}
#[cfg(test)]
mod tests {
use crate::{AsCallableSymbolRef, AsSymbolRef, Type, testing::Test};
#[test]
fn borrowed_operation_roundtrips_symbol_and_callable_handles() {
let mut test = Test::named("borrowed_operation_roundtrips_symbol_and_callable_handles")
.in_module("test");
let function = test.define_function("callee", &[], &[Type::I32]);
let op_ref = function.as_operation_ref();
let symbol_ref = {
let op = op_ref.borrow();
op.as_symbol_ref().expect("expected function operation to be a symbol")
};
assert_eq!(symbol_ref.borrow().as_operation_ref(), op_ref);
let callable_ref = {
let function = function.borrow();
function.as_callable_symbol_ref()
};
assert_eq!(callable_ref.borrow().as_operation_ref(), op_ref);
let symbol_from_callable = function.as_symbol_ref();
assert_eq!(symbol_from_callable.borrow().as_operation_ref(), op_ref);
}
}