use midenc_hir::{
Context, OpPrinter, Report, UnsafeIntrusiveEntityRef, Verify,
derive::{EffectOpInterface, OpParser, OpPrinter, operation},
dialects::debuginfo::attributes::{ExpressionAttr, VariableAttr},
effects::{
DebugEffect, DebugEffectOpInterface, EffectOpInterface, MemoryEffect,
MemoryEffectOpInterface,
},
smallvec,
traits::{AnyType, Transparent},
};
use super::DebugInfoDialect;
pub type DebugValueRef = UnsafeIntrusiveEntityRef<DebugValue>;
pub type DebugDeclareRef = UnsafeIntrusiveEntityRef<DebugDeclare>;
pub type DebugKillRef = UnsafeIntrusiveEntityRef<DebugKill>;
#[derive(EffectOpInterface, OpParser, OpPrinter)]
#[operation(
dialect = DebugInfoDialect,
traits(Transparent),
implements(DebugEffectOpInterface, MemoryEffectOpInterface, OpPrinter)
)]
pub struct DebugValue {
#[operand]
#[effects(DebugEffect(DebugEffect::Read, DebugEffect::Write))]
value: AnyType,
#[attr]
#[effects(DebugEffect(DebugEffect::Write))]
variable: VariableAttr,
#[attr]
expression: ExpressionAttr,
}
impl EffectOpInterface<MemoryEffect> for DebugValue {
fn effects(&self) -> midenc_hir::effects::EffectIterator<MemoryEffect> {
midenc_hir::effects::EffectIterator::from_smallvec(smallvec![])
}
}
impl Verify<dyn DebugEffectOpInterface> for DebugValue {
fn verify(&self, _context: &Context) -> Result<(), Report> {
let value = self.value().as_value_ref();
let value = value.borrow();
let is_orphaned = if let Some(defining_op) = value.get_defining_op() {
defining_op.borrow().parent().is_none()
} else {
value.parent_block().is_none()
};
if is_orphaned {
return Err(Report::msg(
"di.debug_value operand refers to an erased SSA value; salvage the debug value or \
erase the debug op before removing its producer",
));
}
Ok(())
}
}
#[derive(EffectOpInterface, OpParser, OpPrinter)]
#[operation(
dialect = DebugInfoDialect,
traits(Transparent),
implements(DebugEffectOpInterface, MemoryEffectOpInterface, OpPrinter)
)]
pub struct DebugDeclare {
#[attr]
#[effects(DebugEffect(DebugEffect::Allocate))]
variable: VariableAttr,
#[attr]
#[effects(DebugEffect(DebugEffect::Write))]
expression: ExpressionAttr,
}
impl EffectOpInterface<MemoryEffect> for DebugDeclare {
fn effects(&self) -> midenc_hir::effects::EffectIterator<MemoryEffect> {
midenc_hir::effects::EffectIterator::from_smallvec(smallvec![])
}
}
#[derive(EffectOpInterface, OpParser, OpPrinter)]
#[operation(
dialect = DebugInfoDialect,
traits(Transparent),
implements(DebugEffectOpInterface, MemoryEffectOpInterface, OpPrinter)
)]
pub struct DebugKill {
#[attr]
#[effects(DebugEffect(DebugEffect::Free))]
variable: VariableAttr,
}
impl EffectOpInterface<MemoryEffect> for DebugKill {
fn effects(&self) -> midenc_hir::effects::EffectIterator<MemoryEffect> {
midenc_hir::effects::EffectIterator::from_smallvec(smallvec![])
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use midenc_hir::{
Builder, Report, SourceSpan, Type, ValueRef,
dialects::{
builtin::BuiltinOpBuilder,
debuginfo::{
DIBuilder, DebugInfoDialect,
attributes::Variable,
transform::{SalvageAction, erase_debug_info, salvage_debug_info},
},
test::TestOpBuilder,
},
interner::Symbol,
testing::Test,
};
#[test]
fn debug_value_verifier_rejects_erased_producer() -> Result<(), Report> {
let mut test =
Test::new("debug_value_verifier_rejects_erased_producer", &[Type::U32], &[Type::U32]);
test.context().get_or_register_dialect::<DebugInfoDialect>();
let (mut producer_op, debug_op) = {
let mut builder = test.function_builder();
let entry = builder.entry_block();
let builder = builder.builder_mut();
builder.set_insertion_point_to_end(entry);
let input = entry.borrow().arguments()[0] as ValueRef;
let value = builder.add(input, input, SourceSpan::UNKNOWN)?;
let producer_op = value.borrow().get_defining_op().unwrap();
let variable =
Variable::new(Symbol::intern("x"), Symbol::intern("test.rs"), 1, Some(1));
let debug_op =
builder.debug_value(value, variable, SourceSpan::UNKNOWN)?.as_operation_ref();
builder.ret([input], SourceSpan::UNKNOWN)?;
(producer_op, debug_op)
};
producer_op.borrow_mut().erase();
let err = debug_op.borrow().verify().expect_err("expected dangling debug value to fail");
assert!(err.to_string().contains("di.debug_value operand refers to an erased SSA value"));
Ok(())
}
#[test]
fn erase_debug_info_removes_debug_value_before_producer_erasure() -> Result<(), Report> {
let mut test = Test::new(
"erase_debug_info_removes_debug_value_before_producer_erasure",
&[Type::U32],
&[Type::U32],
);
test.context().get_or_register_dialect::<DebugInfoDialect>();
let (value, mut producer_op, debug_op) = {
let mut builder = test.function_builder();
let entry = builder.entry_block();
let builder = builder.builder_mut();
builder.set_insertion_point_to_end(entry);
let input = entry.borrow().arguments()[0] as ValueRef;
let value = builder.add(input, input, SourceSpan::UNKNOWN)?;
let producer_op = value.borrow().get_defining_op().unwrap();
let variable =
Variable::new(Symbol::intern("x"), Symbol::intern("test.rs"), 1, Some(1));
let debug_op =
builder.debug_value(value, variable, SourceSpan::UNKNOWN)?.as_operation_ref();
builder.ret([input], SourceSpan::UNKNOWN)?;
(value, producer_op, debug_op)
};
erase_debug_info(&value);
producer_op.borrow_mut().erase();
assert!(debug_op.borrow().parent().is_none());
test.function().as_operation_ref().borrow().recursively_verify()?;
Ok(())
}
#[test]
fn salvage_debug_info_rehomes_debug_value_before_producer_erasure() -> Result<(), Report> {
let mut test = Test::new(
"salvage_debug_info_rehomes_debug_value_before_producer_erasure",
&[Type::U32],
&[Type::U32],
);
test.context().get_or_register_dialect::<DebugInfoDialect>();
let (old_value, replacement, mut producer_op) = {
let mut builder = test.function_builder();
let entry = builder.entry_block();
let builder = builder.builder_mut();
builder.set_insertion_point_to_end(entry);
let input = entry.borrow().arguments()[0] as ValueRef;
let old_value = builder.add(input, input, SourceSpan::UNKNOWN)?;
let producer_op = old_value.borrow().get_defining_op().unwrap();
let replacement = builder.mul(input, input, SourceSpan::UNKNOWN)?;
let variable =
Variable::new(Symbol::intern("x"), Symbol::intern("test.rs"), 1, Some(1));
builder.debug_value(old_value, variable, SourceSpan::UNKNOWN)?;
builder.ret([replacement], SourceSpan::UNKNOWN)?;
(old_value, replacement, producer_op)
};
let mut builder = test.function_builder();
builder.builder_mut().set_insertion_point_after(producer_op);
salvage_debug_info(
&old_value,
&SalvageAction::WithExpression {
new_value: replacement,
ops: Default::default(),
},
builder.builder_mut(),
);
producer_op.borrow_mut().erase();
test.function().as_operation_ref().borrow().recursively_verify()?;
Ok(())
}
}