use crate::attributes::{AggregateAttr, SplatAttr, SymbolAddrAttr};
use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use pliron::{
arg_err,
attribute::{Attribute, verify_attr},
builtin::{
attr_interfaces::{OutlinedAttr, TypedAttrInterface},
ops::ModuleOp,
},
combine::{Parser, attempt, between, choice, not_followed_by, parser::char::spaces, token},
context::{Context, Ptr},
derive::{attr_interface_impl, pliron_attr},
dict_key,
graph::walkers::{
IRNode, WALKCONFIG_PREORDER_FORWARD,
interruptible::{WalkResult, immutable::walk_op, walk_advance, walk_break},
},
indented_block,
irfmt::{
parsers::{delimited_list_parser, list_parser},
printers::iter_with_sep,
},
location::{Located, Location},
op::Op,
operation::Operation,
parsable::{IntoParseResult, Parsable, ParseResult, StateStream},
printable::{self, ListSeparator, Printable, indented_nl},
result::{Error, Result},
symbol_table::SymbolTableCollection,
utils::vec_exns::VecExtns,
verify_err, verify_error,
};
use thiserror::Error;
pub type MdNodeId = u32;
fn write_md_quoted(f: &mut core::fmt::Formatter<'_>, s: &str) -> core::fmt::Result {
write!(f, "\"")?;
for c in s.chars() {
match c {
'\\' | '"' => write!(f, "\\{c}")?,
_ => write!(f, "{c}")?,
}
}
write!(f, "\"")
}
dict_key!(
ATTR_KEY_MD_TABLE, "llvm_metadata_defs"
);
dict_key!(
ATTR_KEY_NAMED_MD, "llvm_named_metadata"
);
dict_key!(
ATTR_KEY_MD_ATTACHMENTS, "llvm_metadata"
);
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum MdOperandAttr {
Null,
String(String),
Node(MdNodeId),
Constant(Box<dyn TypedAttrInterface>),
}
impl Printable for MdOperandAttr {
fn fmt(
&self,
ctx: &Context,
state: &printable::State,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
match self {
MdOperandAttr::Null => write!(f, "null"),
MdOperandAttr::String(s) => {
write!(f, "!")?;
write_md_quoted(f, s)
}
MdOperandAttr::Node(id) => write!(f, "#{id}"),
MdOperandAttr::Constant(attr) => attr.fmt(ctx, state, f),
}
}
}
impl Parsable for MdOperandAttr {
type Arg = ();
type Parsed = Self;
fn parse<'a>(
state_stream: &mut StateStream<'a>,
_arg: Self::Arg,
) -> ParseResult<'a, Self::Parsed> {
choice((
attempt(
pliron::combine::parser::char::string("null")
.skip(not_followed_by(pliron::combine::parser::char::alpha_num())),
)
.map(|_| MdOperandAttr::Null),
token('!')
.with(String::parser(()))
.map(MdOperandAttr::String),
token('#')
.with(MdNodeId::parser(()))
.map(MdOperandAttr::Node),
<Box<dyn TypedAttrInterface>>::parser(()).map(MdOperandAttr::Constant),
))
.parse_stream(state_stream)
.into()
}
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum MdNodeAttr {
Tuple {
distinct: bool,
operands: Vec<MdOperandAttr>,
},
}
impl MdNodeAttr {
pub fn new_tuple(operands: Vec<MdOperandAttr>) -> Self {
MdNodeAttr::Tuple {
distinct: false,
operands,
}
}
pub fn new_distinct_tuple(operands: Vec<MdOperandAttr>) -> Self {
MdNodeAttr::Tuple {
distinct: true,
operands,
}
}
pub fn is_distinct(&self) -> bool {
let MdNodeAttr::Tuple { distinct, .. } = self;
*distinct
}
pub fn operands(&self) -> &[MdOperandAttr] {
let MdNodeAttr::Tuple { operands, .. } = self;
operands
}
}
impl Printable for MdNodeAttr {
fn fmt(
&self,
ctx: &Context,
state: &printable::State,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
let MdNodeAttr::Tuple { distinct, operands } = self;
if *distinct {
write!(f, "distinct ")?;
}
write!(f, "!{{")?;
iter_with_sep(operands.iter(), ListSeparator::CharSpace(',')).fmt(ctx, state, f)?;
write!(f, "}}")
}
}
impl Parsable for MdNodeAttr {
type Arg = ();
type Parsed = Self;
fn parse<'a>(
state_stream: &mut StateStream<'a>,
_arg: Self::Arg,
) -> ParseResult<'a, Self::Parsed> {
pliron::combine::optional(attempt(
pliron::combine::parser::char::string("distinct").skip(spaces()),
))
.and(token('!').with(between(
token('{').skip(spaces()),
spaces().with(token('}')),
list_parser(',', MdOperandAttr::parser(())),
)))
.map(|(distinct, operands)| MdNodeAttr::Tuple {
distinct: distinct.is_some(),
operands,
})
.parse_stream(state_stream)
.into()
}
}
#[pliron_attr(name = "llvm.md_table", verifier = "succ")]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Default)]
pub struct MdTableAttr(Vec<MdNodeAttr>);
#[attr_interface_impl]
impl OutlinedAttr for MdTableAttr {}
impl MdTableAttr {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, node: MdNodeAttr) -> MdNodeId {
self.0.push_back(node) as MdNodeId
}
pub fn push_uniqued(&mut self, node: MdNodeAttr) -> MdNodeId {
if !node.is_distinct()
&& let Some((id, _)) = self.iter().find(|(_, existing)| **existing == node)
{
return id;
}
self.push(node)
}
pub fn reserve(&mut self) -> MdNodeId {
self.push(MdNodeAttr::new_tuple(Vec::new()))
}
pub fn set(&mut self, id: MdNodeId, node: MdNodeAttr) {
self.0[id as usize] = node;
}
pub fn get(&self, id: MdNodeId) -> Option<&MdNodeAttr> {
self.0.get(id as usize)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (MdNodeId, &MdNodeAttr)> {
self.0
.iter()
.enumerate()
.map(|(idx, node)| (idx as MdNodeId, node))
}
}
impl Printable for MdTableAttr {
fn fmt(
&self,
ctx: &Context,
state: &printable::State,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
write!(f, "[")?;
indented_block!(state, {
for (id, node) in self.iter() {
if id != 0 {
write!(f, ",")?;
}
write!(f, "{}#{id} = ", indented_nl(state))?;
node.fmt(ctx, state, f)?;
}
});
if !self.is_empty() {
write!(f, "{}", indented_nl(state))?;
}
write!(f, "]")
}
}
#[derive(Debug, Error)]
#[error("Metadata table entry {0} is out of order; entries must be #0, #1, ... in order")]
pub struct MdTableParseErr(MdNodeId);
impl Parsable for MdTableAttr {
type Arg = ();
type Parsed = Self;
fn parse<'a>(
state_stream: &mut StateStream<'a>,
_arg: Self::Arg,
) -> ParseResult<'a, Self::Parsed> {
let loc = state_stream.loc();
let entry = token('#')
.with(MdNodeId::parser(()))
.skip(spaces())
.skip(token('='))
.skip(spaces())
.and(MdNodeAttr::parser(()));
let (entries, _) = delimited_list_parser('[', ']', ',', entry)
.parse_stream(state_stream)
.into_result()?;
let mut table = MdTableAttr::new();
for (id, node) in entries {
if id as usize != table.len() {
return Err(pliron::input_error!(loc, MdTableParseErr(id))).into_parse_result();
}
table.push(node);
}
Ok(table).into_parse_result()
}
}
#[pliron_attr(name = "llvm.md_attachments", verifier = "succ")]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Default)]
pub struct MdAttachmentsAttr(Vec<(String, MdNodeId)>);
#[attr_interface_impl]
impl OutlinedAttr for MdAttachmentsAttr {}
impl MdAttachmentsAttr {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, kind: &str) -> Option<MdNodeId> {
self.0
.iter()
.find(|(k, _)| k == kind)
.map(|(_, node)| *node)
}
pub fn set(&mut self, kind: impl Into<String>, node: MdNodeId) {
let kind = kind.into();
match self.0.iter_mut().find(|(k, _)| *k == kind) {
Some(entry) => entry.1 = node,
None => self.0.push((kind, node)),
}
}
pub fn remove(&mut self, kind: &str) {
self.0.retain(|(k, _)| k != kind);
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, MdNodeId)> {
self.0.iter().map(|(kind, node)| (kind.as_str(), *node))
}
}
impl Printable for MdAttachmentsAttr {
fn fmt(
&self,
_ctx: &Context,
_state: &printable::State,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
write!(f, "[")?;
for (idx, (kind, node)) in self.0.iter().enumerate() {
if idx != 0 {
write!(f, ", ")?;
}
write_md_quoted(f, kind)?;
write!(f, " = #{node}")?;
}
write!(f, "]")
}
}
impl Parsable for MdAttachmentsAttr {
type Arg = ();
type Parsed = Self;
fn parse<'a>(
state_stream: &mut StateStream<'a>,
_arg: Self::Arg,
) -> ParseResult<'a, Self::Parsed> {
let entry = String::parser(())
.skip(spaces())
.skip(token('='))
.skip(spaces())
.and(token('#').with(MdNodeId::parser(())));
delimited_list_parser('[', ']', ',', entry)
.map(MdAttachmentsAttr)
.parse_stream(state_stream)
.into()
}
}
#[pliron_attr(name = "llvm.named_md", verifier = "succ")]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Default)]
pub struct NamedMdAttr(Vec<(String, Vec<MdNodeId>)>);
#[attr_interface_impl]
impl OutlinedAttr for NamedMdAttr {}
impl NamedMdAttr {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, name: &str) -> Option<&[MdNodeId]> {
self.0
.iter()
.find(|(n, _)| n == name)
.map(|(_, nodes)| nodes.as_slice())
}
pub fn push(&mut self, name: impl Into<String>, node: MdNodeId) {
let name = name.into();
match self.0.iter_mut().find(|(n, _)| *n == name) {
Some(entry) => entry.1.push(node),
None => self.0.push((name, alloc::vec![node])),
}
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &[MdNodeId])> {
self.0
.iter()
.map(|(name, nodes)| (name.as_str(), nodes.as_slice()))
}
}
impl Printable for NamedMdAttr {
fn fmt(
&self,
_ctx: &Context,
_state: &printable::State,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
write!(f, "[")?;
for (idx, (name, nodes)) in self.0.iter().enumerate() {
if idx != 0 {
write!(f, ", ")?;
}
write_md_quoted(f, name)?;
write!(f, " = [")?;
for (idx, node) in nodes.iter().enumerate() {
if idx != 0 {
write!(f, ", ")?;
}
write!(f, "#{node}")?;
}
write!(f, "]")?;
}
write!(f, "]")
}
}
impl Parsable for NamedMdAttr {
type Arg = ();
type Parsed = Self;
fn parse<'a>(
state_stream: &mut StateStream<'a>,
_arg: Self::Arg,
) -> ParseResult<'a, Self::Parsed> {
let nodes = delimited_list_parser('[', ']', ',', token('#').with(MdNodeId::parser(())));
let entry = String::parser(())
.skip(spaces())
.skip(token('='))
.skip(spaces())
.and(nodes);
delimited_list_parser('[', ']', ',', entry)
.map(NamedMdAttr)
.parse_stream(state_stream)
.into()
}
}
pub fn get_metadata_table(ctx: &Context, module_op: ModuleOp) -> Option<MdTableAttr> {
module_op
.get_operation()
.deref(ctx)
.attributes
.get::<MdTableAttr>(&ATTR_KEY_MD_TABLE)
.cloned()
}
pub fn set_metadata_table(ctx: &Context, module_op: ModuleOp, table: MdTableAttr) {
module_op
.get_operation()
.deref_mut(ctx)
.attributes
.set(ATTR_KEY_MD_TABLE.clone(), table);
}
pub fn get_named_metadata(ctx: &Context, module_op: ModuleOp) -> Option<NamedMdAttr> {
module_op
.get_operation()
.deref(ctx)
.attributes
.get::<NamedMdAttr>(&ATTR_KEY_NAMED_MD)
.cloned()
}
pub fn set_named_metadata(ctx: &Context, module_op: ModuleOp, named: NamedMdAttr) {
module_op
.get_operation()
.deref_mut(ctx)
.attributes
.set(ATTR_KEY_NAMED_MD.clone(), named);
}
pub fn get_attachments(ctx: &Context, op: Ptr<Operation>) -> Option<MdAttachmentsAttr> {
op.deref(ctx)
.attributes
.get::<MdAttachmentsAttr>(&ATTR_KEY_MD_ATTACHMENTS)
.cloned()
}
pub fn set_attachments(ctx: &Context, op: Ptr<Operation>, attachments: MdAttachmentsAttr) {
op.deref_mut(ctx)
.attributes
.set(ATTR_KEY_MD_ATTACHMENTS.clone(), attachments);
}
pub fn attach_metadata(ctx: &Context, op: Ptr<Operation>, kind: impl Into<String>, node: MdNodeId) {
let mut attachments = get_attachments(ctx, op).unwrap_or_default();
attachments.set(kind, node);
set_attachments(ctx, op, attachments);
}
pub fn find_enclosing_module(ctx: &Context, op: Ptr<Operation>) -> Option<ModuleOp> {
let mut cur = Some(op);
while let Some(op) = cur {
if let Some(module_op) = Operation::get_op::<ModuleOp>(op, ctx) {
return Some(module_op);
}
cur = op.deref(ctx).get_parent_op(ctx);
}
None
}
pub fn find_metadata_table(ctx: &Context, op: Ptr<Operation>) -> Option<MdTableAttr> {
find_enclosing_module(ctx, op).and_then(|module_op| get_metadata_table(ctx, module_op))
}
#[derive(Debug, Error)]
pub enum MdAddErr {
#[error("Cannot add a metadata node for an operation that is not inside a module")]
NoEnclosingModule,
}
pub fn add_metadata_node(ctx: &Context, op: Ptr<Operation>, node: MdNodeAttr) -> Result<MdNodeId> {
let Some(module_op) = find_enclosing_module(ctx, op) else {
let loc = op.deref(ctx).loc();
return arg_err!(loc, MdAddErr::NoEnclosingModule);
};
let mut table = get_metadata_table(ctx, module_op).unwrap_or_default();
let node_id = table.push_uniqued(node);
set_metadata_table(ctx, module_op, table);
Ok(node_id)
}
pub fn attach_new_metadata(
ctx: &Context,
op: Ptr<Operation>,
kind: impl Into<String>,
node: MdNodeAttr,
) -> Result<MdNodeId> {
let node_id = add_metadata_node(ctx, op, node)?;
attach_metadata(ctx, op, kind, node_id);
Ok(node_id)
}
#[derive(Debug, Error)]
pub enum MetadataVerifyErr {
#[error("Metadata node #{0} is not in the module's metadata table")]
DanglingNodeRef(MdNodeId),
#[error("Metadata is attached here, but the module has no metadata table")]
NoTable,
#[error("Metadata refers to \"{0}\", which is not a symbol of this module")]
UndefinedSymbol(String),
}
fn check_symbols_resolve(
ctx: &Context,
module_op: ModuleOp,
symbol_tables: &mut SymbolTableCollection,
attr: &dyn Attribute,
loc: &Location,
) -> Result<()> {
if let Some(symbol_addr) = attr.downcast_ref::<SymbolAddrAttr>() {
let symbol = symbol_addr.symbol();
if symbol_tables
.lookup_symbol_in_table(ctx, Box::new(module_op), symbol)
.is_none()
{
verify_err!(
loc.clone(),
MetadataVerifyErr::UndefinedSymbol(symbol.to_string())
)?;
}
} else if let Some(aggregate) = attr.downcast_ref::<AggregateAttr>() {
for element in aggregate.elements() {
check_symbols_resolve(ctx, module_op, symbol_tables, &**element, loc)?;
}
} else if let Some(splat) = attr.downcast_ref::<SplatAttr>() {
check_symbols_resolve(ctx, module_op, symbol_tables, splat.element(), loc)?;
}
Ok(())
}
pub fn verify_metadata(ctx: &Context, module_op: ModuleOp) -> Result<()> {
let module_op_ptr = module_op.get_operation();
let table = get_metadata_table(ctx, module_op).unwrap_or_default();
let num_nodes = table.len() as MdNodeId;
let loc = module_op_ptr.deref(ctx).loc();
let check = |node: MdNodeId, loc: Location| -> Result<()> {
if node >= num_nodes {
verify_err!(loc, MetadataVerifyErr::DanglingNodeRef(node))?;
}
Ok(())
};
let mut symbol_tables = SymbolTableCollection::new();
for (_, node) in table.iter() {
for operand in node.operands() {
match operand {
MdOperandAttr::Node(id) => check(*id, loc.clone())?,
MdOperandAttr::Constant(attr) => {
verify_attr(&**attr, ctx).map_err(|mut err| {
if err.loc.is_unknown() {
err.set_loc(loc.clone());
}
err
})?;
check_symbols_resolve(ctx, module_op, &mut symbol_tables, &**attr, &loc)?
}
MdOperandAttr::Null | MdOperandAttr::String(_) => (),
}
}
}
if let Some(named) = get_named_metadata(ctx, module_op) {
for (_, nodes) in named.iter() {
for node in nodes {
check(*node, loc.clone())?;
}
}
}
let mut state = (num_nodes, get_metadata_table(ctx, module_op).is_some());
let walk_result: WalkResult<Error> = walk_op(
ctx,
&mut state,
&WALKCONFIG_PREORDER_FORWARD,
module_op_ptr,
|ctx: &Context,
(num_nodes, has_table): &mut (MdNodeId, bool),
node: IRNode|
-> WalkResult<Error> {
let IRNode::Operation(op) = node else {
return walk_advance();
};
let Some(attachments) = get_attachments(ctx, op) else {
return walk_advance();
};
let loc = op.deref(ctx).loc();
if !*has_table && !attachments.is_empty() {
return walk_break(verify_error!(loc, MetadataVerifyErr::NoTable));
}
for (_, node) in attachments.iter() {
if node >= *num_nodes {
return walk_break(verify_error!(
loc.clone(),
MetadataVerifyErr::DanglingNodeRef(node)
));
}
}
walk_advance()
},
);
match walk_result {
WalkResult::Break(err) => Err(err),
WalkResult::Continue(_) => Ok(()),
}
}