use std::sync::OnceLock;
mod config;
mod format;
pub use config::{
OperationAlternativesConfig, OperationFormatConfig, OperationGrammarConfig,
OperationShapeConfig, RegistryConfig, RegistryConfigError, RegistryLoadOptions,
};
pub(crate) use format::{FormatCapture, FormatStep, FormatTarget, OperationFormat};
use crate::{
SyntaxKind,
parser::DialectParser,
semantic::{Document, OperationId, RegisteredLowering, RegisteredLoweringContext},
};
pub type OperationParser = fn(&mut DialectParser<'_, '_>) -> Result<(), crate::CompactError>;
pub type OperationLowerer = fn(&RegisteredLoweringContext<'_>) -> Option<RegisteredLowering>;
pub type OperationVerifier = fn(&Document, OperationId) -> Result<(), &'static str>;
pub type OperationPrinter = fn(&Document, OperationId) -> Option<String>;
pub type ValueParser = fn(&str) -> bool;
pub type ValueLowerer = fn(&str) -> Option<String>;
pub type ValueVerifier = fn(&str) -> Result<(), &'static str>;
pub type ValuePrinter = fn(&str) -> Option<String>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RegionKind {
Ssacfg,
Graph,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SymbolDescriptor {
pub defines_symbol: bool,
pub symbol_table: bool,
pub uses_symbols: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RegionDescriptor {
pub kind: RegionKind,
pub isolated_from_above: bool,
pub requires_terminator: bool,
}
impl Default for RegionDescriptor {
fn default() -> Self {
Self {
kind: RegionKind::Ssacfg,
isolated_from_above: false,
requires_terminator: false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OperationSchema {
pub operands: OperandCount,
pub results: ResultCount,
pub required_attributes: &'static [&'static str],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResultCount {
Exact(usize),
Variadic,
}
impl ResultCount {
pub(crate) fn accepts(self, count: usize) -> bool {
matches!(self, Self::Variadic) || matches!(self, Self::Exact(expected) if expected == count)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OperandCount {
Exact(usize),
Variadic,
}
impl OperandCount {
pub(crate) fn accepts(self, count: usize) -> bool {
matches!(self, Self::Variadic) || matches!(self, Self::Exact(expected) if expected == count)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AssemblyProgram {
BuiltinModule,
FuncFunc,
FuncCall,
CfCondBr,
ArithConstant,
FuncReturn,
CfBr,
}
impl AssemblyProgram {
const fn operation_name(self) -> &'static str {
match self {
Self::BuiltinModule => "builtin.module",
Self::FuncFunc => "func.func",
Self::FuncCall => "func.call",
Self::CfCondBr => "cf.cond_br",
Self::ArithConstant => "arith.constant",
Self::FuncReturn => "func.return",
Self::CfBr => "cf.br",
}
}
pub(crate) const fn inherent_attribute(self) -> Option<&'static str> {
match self {
Self::BuiltinModule => Some("sym_name"),
Self::FuncFunc => Some("sym_name"),
Self::FuncCall => Some("callee"),
Self::CfCondBr => None,
Self::ArithConstant => Some("value"),
Self::FuncReturn | Self::CfBr => None,
}
}
pub(crate) fn lower(
self,
context: &RegisteredLoweringContext<'_>,
) -> Option<RegisteredLowering> {
match self {
Self::BuiltinModule => lower_module(context),
Self::FuncFunc => lower_function(context),
Self::FuncCall => lower_call(context),
Self::CfCondBr => lower_no_results(),
Self::ArithConstant => lower_arith_constant(context),
Self::FuncReturn => lower_return(context),
Self::CfBr => lower_no_results(),
}
}
pub(crate) fn verify(
self,
document: &Document,
operation: OperationId,
) -> Result<(), &'static str> {
match self {
Self::BuiltinModule => crate::semantic::verify_builtin_module(document, operation),
Self::FuncFunc => crate::semantic::verify_func_func(document, operation),
Self::FuncCall => crate::semantic::verify_func_call(document, operation),
Self::CfCondBr => crate::semantic::verify_cf_cond_br(document, operation),
Self::ArithConstant => verify_arith_constant(document, operation),
Self::FuncReturn => crate::semantic::verify_func_return(document, operation),
Self::CfBr => crate::semantic::verify_cf_br(document, operation),
}
}
pub(crate) fn print(self, document: &Document, operation: OperationId) -> Option<String> {
let descriptor = BASELINE_OPERATIONS
.iter()
.find(|descriptor| descriptor.assembly == Some(self))?;
if !descriptor
.schema
.operands
.accepts(document.operands(operation)?.len())
|| !descriptor
.schema
.results
.accepts(document.result_types(operation)?.len())
|| document.properties(operation)?.next().is_some()
|| document.operation_location(operation)?.is_some()
|| (!matches!(self, Self::BuiltinModule | Self::FuncFunc)
&& !document.operation_regions(operation)?.is_empty())
|| (!matches!(self, Self::CfBr | Self::CfCondBr)
&& !document.successors(operation)?.is_empty())
{
return None;
}
if self != Self::FuncReturn {
self.verify(document, operation).ok()?;
}
match self {
Self::BuiltinModule => print_module(document, operation),
Self::FuncFunc => print_function(document, operation),
Self::FuncCall => print_call(document, operation),
Self::CfCondBr => print_cond_branch(document, operation),
Self::ArithConstant => print_arith_constant(document, operation),
Self::FuncReturn => print_return(document, operation),
Self::CfBr => print_branch(document, operation),
}
}
}
const fn same_name(left: &str, right: &str) -> bool {
let left = left.as_bytes();
let right = right.as_bytes();
if left.len() != right.len() {
return false;
}
let mut index = 0;
while index < left.len() {
if left[index] != right[index] {
return false;
}
index += 1;
}
true
}
const fn same_names(left: &[&str], right: &[&str]) -> bool {
if left.len() != right.len() {
return false;
}
let mut index = 0;
while index < left.len() {
if !same_name(left[index], right[index]) {
return false;
}
index += 1;
}
true
}
#[derive(Clone, Copy)]
pub struct OperationDescriptor {
pub name: &'static str,
pub syntax_kind: SyntaxKind,
pub parse: Option<OperationParser>,
pub lower: Option<OperationLowerer>,
pub verify: Option<OperationVerifier>,
pub print: Option<OperationPrinter>,
pub assembly: Option<AssemblyProgram>,
pub schema: OperationSchema,
pub regions: &'static [RegionDescriptor],
pub symbols: SymbolDescriptor,
pub is_terminator: bool,
}
pub struct TypeDescriptor {
pub name: &'static str,
pub parse: Option<ValueParser>,
pub lower: Option<ValueLowerer>,
pub verify: Option<ValueVerifier>,
pub print: Option<ValuePrinter>,
}
pub struct AttributeDescriptor {
pub name: &'static str,
pub parse: Option<ValueParser>,
pub lower: Option<ValueLowerer>,
pub verify: Option<ValueVerifier>,
pub print: Option<ValuePrinter>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum DeclarativeRegistryError {
InvalidOperationName(String),
InvalidOperationFormat { name: String, reason: String },
ConflictingShape(String),
ConflictingFormat(String),
ConflictingAlternatives(String),
ConflictingCallTargetAttribute(String),
InvalidCallTargetAttribute(String),
InvalidOperationAlternatives(String),
UnknownOperation(String),
DuplicateOperation(String),
EmptyOperation,
CoreOperation(String),
RegisteredOperation(String),
UnknownPreset(String),
DuplicatePreset(String),
UnresolvedImports,
}
impl std::fmt::Display for DeclarativeRegistryError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ConflictingShape(name) => {
write!(formatter, "conflicting operation shapes for: {name}")
}
Self::ConflictingFormat(name) => {
write!(formatter, "conflicting operation formats for: {name}")
}
Self::ConflictingAlternatives(name) => {
write!(formatter, "conflicting operation alternatives for: {name}")
}
Self::ConflictingCallTargetAttribute(name) => {
write!(formatter, "conflicting call-target attributes for: {name}")
}
Self::InvalidCallTargetAttribute(name) => write!(
formatter,
"callee_attribute requires direct-call grammar and a dotted ASCII attribute name: {name}"
),
Self::InvalidOperationAlternatives(name) => {
write!(
formatter,
"operation alternatives require at least two valid grammars: {name}"
)
}
Self::InvalidOperationName(name) => {
write!(formatter, "invalid custom operation name: {name:?}")
}
Self::InvalidOperationFormat { name, reason } => {
write!(formatter, "invalid operation format for {name}: {reason}")
}
Self::UnknownOperation(name) => {
write!(formatter, "unknown declarative operation: {name}")
}
Self::DuplicateOperation(name) => {
write!(formatter, "duplicate declarative operation: {name}")
}
Self::EmptyOperation => write!(formatter, "operation name must not be empty"),
Self::CoreOperation(name) => {
write!(
formatter,
"custom operation conflicts with core operation: {name}"
)
}
Self::RegisteredOperation(name) => write!(
formatter,
"custom operation conflicts with registered operation: {name}"
),
Self::UnknownPreset(name) => write!(formatter, "unknown registry preset: {name}"),
Self::DuplicatePreset(name) => write!(formatter, "duplicate registry preset: {name}"),
Self::UnresolvedImports => write!(
formatter,
"registry imports require a filesystem loader such as DialectRegistry::from_config_file"
),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OperationShape {
FuncLike,
CallLike,
BinaryOperands,
OptionalTypedOperands,
AttrFirstOptionalTypedOperands,
UnaryOperand,
VariadicOperands,
LiteralAttribute,
OperandClauses,
RegionClauses,
}
impl OperationShape {
pub const fn name(self) -> &'static str {
match self {
Self::FuncLike => "func_like",
Self::CallLike => "call_like",
Self::BinaryOperands => "binary_operands",
Self::OptionalTypedOperands => "optional_typed_operands",
Self::AttrFirstOptionalTypedOperands => "attr_first_optional_typed_operands",
Self::UnaryOperand => "unary_operand",
Self::VariadicOperands => "variadic_operands",
Self::LiteralAttribute => "literal_attribute",
Self::OperandClauses => "operand_clauses",
Self::RegionClauses => "region_clauses",
}
}
}
impl std::error::Error for DeclarativeRegistryError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum OperationGrammar {
Shape(OperationShape),
Format(OperationFormat),
}
impl OperationGrammar {
fn symbols(&self) -> SymbolDescriptor {
match self {
Self::Shape(OperationShape::FuncLike) => SymbolDescriptor {
defines_symbol: true,
..SymbolDescriptor::default()
},
Self::Shape(OperationShape::CallLike) => SymbolDescriptor {
uses_symbols: true,
..SymbolDescriptor::default()
},
Self::Format(format) if format.captures_callee() => SymbolDescriptor {
uses_symbols: true,
..SymbolDescriptor::default()
},
_ => SymbolDescriptor::default(),
}
}
pub(crate) fn is_direct_call(&self) -> bool {
matches!(self, Self::Shape(OperationShape::CallLike))
|| matches!(self, Self::Format(format) if format.captures_callee())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OperationAlternative<'a> {
Shape(OperationShape),
Format(&'a str),
}
type OwnedOperationAlternatives = Box<[(String, Box<[OperationGrammar]>)]>;
#[derive(Clone)]
pub struct DialectRegistry {
operations: &'static [OperationDescriptor],
types: &'static [TypeDescriptor],
attributes: &'static [AttributeDescriptor],
operation_shapes: Option<Box<[(String, OperationShape)]>>,
operation_formats: Option<Box<[(String, OperationFormat)]>>,
operation_alternatives: Option<OwnedOperationAlternatives>,
call_target_attributes: Option<Box<[(String, String)]>>,
module_alias: bool,
}
impl DialectRegistry {
pub const EMPTY: Self = Self::new(&[], &[], &[]);
pub const fn new(
operations: &'static [OperationDescriptor],
types: &'static [TypeDescriptor],
attributes: &'static [AttributeDescriptor],
) -> Self {
let registry = Self {
operations,
types,
attributes,
operation_shapes: None,
operation_formats: None,
operation_alternatives: None,
call_target_attributes: None,
module_alias: false,
};
let mut index = 0;
while index < operations.len() {
assert!(
!operations[index].name.is_empty(),
"operation name must not be empty"
);
assert!(
operations[index].assembly.is_some() || operations[index].parse.is_some(),
"registered custom operation needs syntax handling"
);
if let Some(program) = operations[index].assembly {
assert!(
same_name(operations[index].name, program.operation_name()),
"assembly program is attached to the wrong operation"
);
let schema = operations[index].schema;
match program {
AssemblyProgram::BuiltinModule => {
assert!(
matches!(schema.operands, OperandCount::Exact(0))
&& matches!(schema.results, ResultCount::Exact(0))
&& same_names(schema.required_attributes, &[])
&& operations[index].regions.len() == 1
&& matches!(operations[index].regions[0].kind, RegionKind::Ssacfg)
&& operations[index].regions[0].isolated_from_above
&& !operations[index].regions[0].requires_terminator
&& operations[index].symbols.defines_symbol
&& operations[index].symbols.symbol_table
&& !operations[index].symbols.uses_symbols
&& !operations[index].is_terminator,
"module schema is inconsistent"
);
}
AssemblyProgram::FuncFunc => {
assert!(
matches!(schema.operands, OperandCount::Exact(0))
&& matches!(schema.results, ResultCount::Exact(0))
&& same_names(
schema.required_attributes,
&["sym_name", "function_type"]
)
&& operations[index].regions.len() == 1
&& matches!(operations[index].regions[0].kind, RegionKind::Ssacfg)
&& operations[index].regions[0].isolated_from_above
&& operations[index].regions[0].requires_terminator
&& operations[index].symbols.defines_symbol
&& !operations[index].symbols.symbol_table
&& !operations[index].symbols.uses_symbols
&& !operations[index].is_terminator,
"function schema is inconsistent"
);
}
AssemblyProgram::FuncCall => {
assert!(
matches!(schema.operands, OperandCount::Variadic)
&& matches!(schema.results, ResultCount::Variadic)
&& same_names(schema.required_attributes, &["callee"])
&& operations[index].regions.is_empty()
&& operations[index].symbols.uses_symbols
&& !operations[index].symbols.defines_symbol
&& !operations[index].symbols.symbol_table
&& !operations[index].is_terminator,
"call schema is inconsistent"
);
}
AssemblyProgram::CfCondBr => {
assert!(
matches!(schema.operands, OperandCount::Variadic)
&& matches!(schema.results, ResultCount::Exact(0))
&& same_names(schema.required_attributes, &[])
&& operations[index].regions.is_empty()
&& !operations[index].symbols.defines_symbol
&& !operations[index].symbols.symbol_table
&& !operations[index].symbols.uses_symbols
&& operations[index].is_terminator,
"conditional branch schema is inconsistent"
);
}
AssemblyProgram::ArithConstant => {
assert!(
matches!(schema.operands, OperandCount::Exact(0))
&& matches!(schema.results, ResultCount::Exact(1))
&& same_names(schema.required_attributes, &["value"])
&& operations[index].regions.is_empty()
&& !operations[index].symbols.defines_symbol
&& !operations[index].symbols.symbol_table
&& !operations[index].symbols.uses_symbols
&& !operations[index].is_terminator,
"typed attribute schema is inconsistent"
);
}
AssemblyProgram::FuncReturn => {
assert!(
matches!(schema.operands, OperandCount::Variadic)
&& matches!(schema.results, ResultCount::Exact(0))
&& same_names(schema.required_attributes, &[])
&& operations[index].regions.is_empty()
&& !operations[index].symbols.defines_symbol
&& !operations[index].symbols.symbol_table
&& !operations[index].symbols.uses_symbols
&& operations[index].is_terminator,
"optional operand schema is inconsistent"
);
}
AssemblyProgram::CfBr => {
assert!(
matches!(schema.operands, OperandCount::Exact(0))
&& matches!(schema.results, ResultCount::Exact(0))
&& same_names(schema.required_attributes, &[])
&& operations[index].regions.is_empty()
&& !operations[index].symbols.defines_symbol
&& !operations[index].symbols.symbol_table
&& !operations[index].symbols.uses_symbols
&& operations[index].is_terminator,
"successor schema is inconsistent"
);
}
}
}
index += 1;
}
registry
}
pub fn operation(&self, name: &str) -> Option<&OperationDescriptor> {
self.operations
.iter()
.find(|descriptor| descriptor.name == name)
}
pub fn operation_names(&self) -> impl Iterator<Item = &str> + '_ {
self.operations
.iter()
.map(|descriptor| descriptor.name)
.chain(
self.operation_shapes
.iter()
.flatten()
.map(|(name, _)| name.as_str()),
)
.chain(
self.operation_formats
.iter()
.flatten()
.map(|(name, _)| name.as_str()),
)
.chain(
self.operation_alternatives
.iter()
.flatten()
.map(|(name, _)| name.as_str()),
)
}
pub fn operation_shape(&self, name: &str) -> Option<OperationShape> {
self.operation_shapes
.as_deref()?
.iter()
.find_map(|(candidate, shape)| (candidate == name).then_some(*shape))
}
pub(crate) fn compiled_operation_format(&self, name: &str) -> Option<&OperationFormat> {
self.operation_formats
.as_deref()?
.iter()
.find_map(|(candidate, format)| (candidate == name).then_some(format))
}
pub fn operation_format(&self, name: &str) -> Option<&str> {
self.compiled_operation_format(name)
.map(OperationFormat::description)
}
pub(crate) fn operation_grammars(&self, name: &str) -> Option<&[OperationGrammar]> {
self.operation_alternatives
.as_deref()?
.iter()
.find_map(|(candidate, alternatives)| {
(candidate == name).then_some(alternatives.as_ref())
})
}
pub fn operation_alternatives(&self, name: &str) -> Option<Vec<OperationAlternative<'_>>> {
Some(
self.operation_grammars(name)?
.iter()
.map(|grammar| match grammar {
OperationGrammar::Shape(shape) => OperationAlternative::Shape(*shape),
OperationGrammar::Format(format) => {
OperationAlternative::Format(format.description())
}
})
.collect(),
)
}
pub fn call_target_attribute(&self, name: &str) -> Option<&str> {
if self
.operation(name)
.and_then(|descriptor| descriptor.assembly)
== Some(AssemblyProgram::FuncCall)
{
return Some("callee");
}
if let Some(attribute) = self
.call_target_attributes
.as_deref()
.and_then(|entries| entries.iter().find(|(candidate, _)| candidate == name))
.map(|(_, attribute)| attribute.as_str())
{
return Some(attribute);
}
if self.operation_shape(name) == Some(OperationShape::CallLike)
|| self
.compiled_operation_format(name)
.is_some_and(OperationFormat::captures_callee)
|| self
.operation_grammars(name)
.is_some_and(|grammars| grammars.iter().all(OperationGrammar::is_direct_call))
{
Some("callee")
} else {
None
}
}
pub(crate) fn with_call_target_attributes(mut self, attributes: Vec<(String, String)>) -> Self {
let attributes = attributes
.into_iter()
.filter(|(_, attribute)| attribute != "callee")
.collect::<Vec<_>>();
self.call_target_attributes =
(!attributes.is_empty()).then(|| attributes.into_boxed_slice());
self
}
pub fn with_operation_shapes(
operation_shapes: &[(&str, OperationShape)],
) -> Result<Self, DeclarativeRegistryError> {
Self {
operations: CORE_OPERATIONS,
types: &[],
attributes: &[],
operation_shapes: None,
operation_formats: None,
operation_alternatives: None,
call_target_attributes: None,
module_alias: true,
}
.extend_operation_shapes(operation_shapes)
.map_err(|error| match error {
DeclarativeRegistryError::RegisteredOperation(name) => {
DeclarativeRegistryError::CoreOperation(name)
}
error => error,
})
}
pub fn extend_operation_shapes(
&self,
operation_shapes: &[(&str, OperationShape)],
) -> Result<Self, DeclarativeRegistryError> {
let mut shapes = self
.operation_shapes
.as_deref()
.unwrap_or_default()
.to_vec();
shapes.reserve(operation_shapes.len());
for &(name, shape) in operation_shapes {
if name.is_empty() {
return Err(DeclarativeRegistryError::EmptyOperation);
}
let source = crate::source::Source::new(name.as_bytes())
.map_err(|_| DeclarativeRegistryError::InvalidOperationName(name.to_owned()))?;
let lexed = crate::lexer::lex(&source);
if !lexed.diagnostics().is_empty()
|| lexed.tokens().len() != 2
|| lexed.tokens()[0].kind() != crate::lexer::TokenKind::BareIdentifier
{
return Err(DeclarativeRegistryError::InvalidOperationName(
name.to_owned(),
));
}
if self.custom_operation(name).is_some() {
return Err(DeclarativeRegistryError::RegisteredOperation(
name.to_owned(),
));
}
if self.compiled_operation_format(name).is_some()
|| self.operation_grammars(name).is_some()
|| shapes.iter().any(|(candidate, _)| candidate == name)
{
return Err(DeclarativeRegistryError::DuplicateOperation(
name.to_owned(),
));
}
shapes.push((name.to_owned(), shape));
}
Ok(Self {
operations: self.operations,
types: self.types,
attributes: self.attributes,
operation_shapes: Some(shapes.into_boxed_slice()),
operation_formats: self.operation_formats.clone(),
operation_alternatives: self.operation_alternatives.clone(),
call_target_attributes: self.call_target_attributes.clone(),
module_alias: self.module_alias,
})
}
pub(crate) fn extend_operation_formats(
&self,
operation_formats: &[(&str, &str)],
) -> Result<Self, DeclarativeRegistryError> {
let mut formats = self
.operation_formats
.as_deref()
.unwrap_or_default()
.to_vec();
formats.reserve(operation_formats.len());
for &(name, description) in operation_formats {
validate_custom_operation_name(self, name)?;
if self.operation_shape(name).is_some()
|| self.operation_grammars(name).is_some()
|| formats.iter().any(|(candidate, _)| candidate == name)
{
return Err(DeclarativeRegistryError::DuplicateOperation(
name.to_owned(),
));
}
let format = OperationFormat::parse(description).map_err(|reason| {
DeclarativeRegistryError::InvalidOperationFormat {
name: name.to_owned(),
reason,
}
})?;
formats.push((name.to_owned(), format));
}
Ok(Self {
operations: self.operations,
types: self.types,
attributes: self.attributes,
operation_shapes: self.operation_shapes.clone(),
operation_formats: Some(formats.into_boxed_slice()),
operation_alternatives: self.operation_alternatives.clone(),
call_target_attributes: self.call_target_attributes.clone(),
module_alias: self.module_alias,
})
}
pub(crate) fn extend_operation_alternatives(
&self,
entries: &[(String, Vec<config::OperationGrammarConfig>)],
) -> Result<Self, DeclarativeRegistryError> {
let mut alternatives = self
.operation_alternatives
.as_deref()
.unwrap_or_default()
.to_vec();
for (name, descriptions) in entries {
validate_custom_operation_name(self, name)?;
if descriptions.len() < 2
|| self.operation_shape(name).is_some()
|| self.compiled_operation_format(name).is_some()
|| alternatives.iter().any(|(candidate, _)| candidate == name)
{
return Err(if descriptions.len() < 2 {
DeclarativeRegistryError::InvalidOperationAlternatives(name.clone())
} else {
DeclarativeRegistryError::DuplicateOperation(name.clone())
});
}
let mut compiled = Vec::with_capacity(descriptions.len());
for description in descriptions {
match (&description.shape, &description.format) {
(Some(shape), None) => compiled.push(OperationGrammar::Shape(*shape)),
(None, Some(format)) => compiled.push(OperationGrammar::Format(
OperationFormat::parse(format).map_err(|reason| {
DeclarativeRegistryError::InvalidOperationFormat {
name: name.clone(),
reason,
}
})?,
)),
_ => {
return Err(DeclarativeRegistryError::InvalidOperationAlternatives(
name.clone(),
));
}
}
}
if compiled.first().is_some_and(|first| {
compiled
.iter()
.any(|grammar| grammar.symbols() != first.symbols())
}) {
return Err(DeclarativeRegistryError::InvalidOperationAlternatives(
name.clone(),
));
}
alternatives.push((name.clone(), compiled.into_boxed_slice()));
}
Ok(Self {
operations: self.operations,
types: self.types,
attributes: self.attributes,
operation_shapes: self.operation_shapes.clone(),
operation_formats: self.operation_formats.clone(),
operation_alternatives: Some(alternatives.into_boxed_slice()),
call_target_attributes: self.call_target_attributes.clone(),
module_alias: self.module_alias,
})
}
pub fn type_descriptor(&self, name: &str) -> Option<&TypeDescriptor> {
self.types.iter().find(|descriptor| descriptor.name == name)
}
pub fn attribute_descriptor(&self, name: &str) -> Option<&AttributeDescriptor> {
self.attributes
.iter()
.find(|descriptor| descriptor.name == name)
}
pub fn region(&self, operation: &str, index: usize) -> RegionDescriptor {
self.operation(operation)
.and_then(|descriptor| descriptor.regions.get(index))
.copied()
.unwrap_or_default()
}
pub fn symbols(&self, operation: &str) -> SymbolDescriptor {
self.operation(operation)
.map(|descriptor| descriptor.symbols)
.unwrap_or_else(|| match self.operation_shape(operation) {
Some(OperationShape::FuncLike) => SymbolDescriptor {
defines_symbol: true,
..SymbolDescriptor::default()
},
Some(OperationShape::CallLike) => SymbolDescriptor {
uses_symbols: true,
..SymbolDescriptor::default()
},
_ => self
.operation_grammars(operation)
.and_then(|grammars| grammars.first())
.map(OperationGrammar::symbols)
.unwrap_or_default(),
})
}
pub fn baseline() -> &'static Self {
BASELINE_REGISTRY.get_or_init(|| {
let mut registry = DialectRegistry::new(BASELINE_OPERATIONS, &[], &[])
.extend_operation_shapes(BASELINE_OPERATION_SHAPES)
.expect("baseline operation shapes must be valid");
registry.module_alias = true;
registry
})
}
pub fn core() -> &'static Self {
&CORE_REGISTRY
}
pub(crate) fn custom_operation(&self, spelling: &str) -> Option<&OperationDescriptor> {
self.operation(spelling).or_else(|| match spelling {
"module" if self.module_alias => self.operation("builtin.module"),
"return" => self.operation("func.return"),
_ => None,
})
}
pub fn declarative(operation_names: &[&str]) -> Result<Self, DeclarativeRegistryError> {
let mut selected = 0_u8;
for &name in operation_names {
let index = BASELINE_OPERATIONS
.iter()
.position(|descriptor| descriptor.name == name)
.or_else(|| {
BASELINE_OPERATION_SHAPES
.iter()
.position(|(candidate, _)| *candidate == name)
.map(|index| BASELINE_OPERATIONS.len() + index)
})
.ok_or_else(|| DeclarativeRegistryError::UnknownOperation(name.to_owned()))?;
let bit = 1_u8 << index;
if selected & bit != 0 {
return Err(DeclarativeRegistryError::DuplicateOperation(
name.to_owned(),
));
}
selected |= bit;
}
let operations: &'static [OperationDescriptor] =
DECLARATIVE_OPERATION_SETS[usize::from(selected)].get_or_init(|| {
BASELINE_OPERATIONS
.iter()
.enumerate()
.filter(|(index, _)| selected & (1_u8 << index) != 0)
.map(|(_, descriptor)| *descriptor)
.collect::<Vec<_>>()
.into_boxed_slice()
});
Ok(Self {
operations,
types: &[],
attributes: &[],
operation_shapes: Some(
BASELINE_OPERATION_SHAPES
.iter()
.enumerate()
.filter(|(index, _)| {
selected & (1_u8 << (BASELINE_OPERATIONS.len() + index)) != 0
})
.map(|(_, (name, shape))| ((*name).to_owned(), *shape))
.collect::<Vec<_>>()
.into_boxed_slice(),
),
operation_formats: None,
operation_alternatives: None,
call_target_attributes: None,
module_alias: operation_names.contains(&"builtin.module"),
})
}
pub(crate) fn content_identity(&self) -> u64 {
fn mix(mut hash: u64, bytes: &[u8]) -> u64 {
for byte in bytes {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
let mut hash = 0xcbf29ce484222325;
for descriptor in self.operations {
hash = mix(hash, descriptor.name.as_bytes());
hash = mix(hash, &[descriptor.symbols.defines_symbol as u8]);
hash = mix(hash, &[descriptor.symbols.symbol_table as u8]);
hash = mix(hash, &[descriptor.symbols.uses_symbols as u8]);
hash = mix(hash, &(descriptor.regions.len() as u64).to_le_bytes());
for region in descriptor.regions {
hash = mix(hash, &[matches!(region.kind, RegionKind::Graph) as u8]);
hash = mix(hash, &[region.isolated_from_above as u8]);
}
}
if let Some(shapes) = &self.operation_shapes {
for (name, shape) in shapes.iter() {
hash = mix(hash, name.as_bytes());
hash = mix(hash, &[*shape as u8]);
}
}
if let Some(formats) = &self.operation_formats {
for (name, format) in formats.iter() {
hash = mix(hash, name.as_bytes());
hash = mix(hash, &format.identity_bytes().collect::<Vec<_>>());
}
}
if let Some(alternatives) = &self.operation_alternatives {
for (name, grammars) in alternatives.iter() {
hash = mix(hash, name.as_bytes());
for grammar in grammars {
match grammar {
OperationGrammar::Shape(shape) => hash = mix(hash, &[0, *shape as u8]),
OperationGrammar::Format(format) => {
hash = mix(hash, &[1]);
hash = mix(hash, &format.identity_bytes().collect::<Vec<_>>());
}
}
}
}
}
if let Some(attributes) = &self.call_target_attributes {
for (name, attribute) in attributes.iter() {
hash = mix(hash, name.as_bytes());
hash = mix(hash, attribute.as_bytes());
}
}
for descriptor in self.types {
hash = mix(hash, descriptor.name.as_bytes());
}
for descriptor in self.attributes {
hash = mix(hash, descriptor.name.as_bytes());
}
hash
}
}
fn validate_custom_operation_name(
registry: &DialectRegistry,
name: &str,
) -> Result<(), DeclarativeRegistryError> {
if name.is_empty() {
return Err(DeclarativeRegistryError::EmptyOperation);
}
let source = crate::source::Source::new(name.as_bytes())
.map_err(|_| DeclarativeRegistryError::InvalidOperationName(name.to_owned()))?;
let lexed = crate::lexer::lex(&source);
if !lexed.diagnostics().is_empty()
|| lexed.tokens().len() != 2
|| lexed.tokens()[0].kind() != crate::lexer::TokenKind::BareIdentifier
{
return Err(DeclarativeRegistryError::InvalidOperationName(
name.to_owned(),
));
}
if registry.custom_operation(name).is_some() {
return Err(DeclarativeRegistryError::RegisteredOperation(
name.to_owned(),
));
}
Ok(())
}
fn lower_arith_constant(context: &RegisteredLoweringContext<'_>) -> Option<RegisteredLowering> {
let spelling = context.spelling();
let (_, tail) = spelling.split_once("arith.constant")?;
let tail = tail.trim();
let (value, ty) = tail.rsplit_once(':')?;
let value = value.split('{').next().unwrap_or(value).trim();
let ty = ty.trim();
let compatible = if value.parse::<i128>().is_ok() {
ty == "index"
|| ty
.strip_prefix('i')
.is_some_and(|width| width.parse::<u32>().is_ok())
|| ty
.strip_prefix("si")
.or_else(|| ty.strip_prefix("ui"))
.is_some_and(|width| width.parse::<u32>().is_ok())
} else if value.parse::<f64>().is_ok() {
ty.strip_prefix('f')
.is_some_and(|width| width.parse::<u32>().is_ok())
} else {
true
};
(!value.is_empty() && !ty.is_empty()).then(|| RegisteredLowering {
result_types: compatible.then(|| ty.to_owned()).into_iter().collect(),
function_type: format!("() -> {ty}"),
attributes: vec![("value", value.to_owned())],
})
}
fn lower_module(context: &RegisteredLoweringContext<'_>) -> Option<RegisteredLowering> {
let mut attributes = Vec::new();
if let Some(symbol) = context.leading_symbol() {
attributes.push(("sym_name", symbol.to_owned()));
}
Some(RegisteredLowering {
result_types: Vec::new(),
function_type: "() -> ()".into(),
attributes,
})
}
fn argument_type<'a>(spelling: &'a str, attributes: Option<&str>) -> Option<&'a str> {
let (_, ty) = spelling.split_once(':')?;
let end = attributes
.and_then(|attributes| spelling.rfind(attributes))
.or_else(|| {
ty.find(" loc(")
.map(|offset| spelling.len() - ty.len() + offset)
})
.unwrap_or(spelling.len());
Some(spelling[spelling.len() - ty.len()..end].trim())
}
fn function_signature(context: &RegisteredLoweringContext<'_>) -> Option<String> {
let inputs = context
.arguments()
.map(|(argument, attributes)| argument_type(argument, attributes))
.collect::<Option<Vec<_>>>()?
.join(", ");
let results = if let Some(tail) = context.function_results() {
let tail = tail.strip_prefix("->")?.trim_start();
let tail = tail.trim_start();
let raw = if tail.starts_with('(') {
let end = matching_delimiter(tail, 0, '(', ')')?;
&tail[..=end]
} else {
tail.split_whitespace().next().unwrap_or(tail).trim()
};
if raw.starts_with('(') {
let inner = &raw[1..raw.len() - 1];
let types = split_top_level(inner)
.iter()
.map(|result| strip_top_level_attribute(result).trim())
.collect::<Vec<_>>();
format!("({})", types.join(", "))
} else {
strip_top_level_attribute(raw).trim().to_owned()
}
} else {
"()".to_owned()
};
Some(format!("({inputs}) -> {results}"))
}
fn split_top_level(value: &str) -> Vec<&str> {
let mut result = Vec::new();
let mut start = 0;
let mut depth = 0i32;
let mut quoted = false;
let mut escaped = false;
for (index, character) in value.char_indices() {
if quoted {
if character == '"' && !escaped {
quoted = false;
}
escaped = character == '\\' && !escaped;
if character != '\\' {
escaped = false;
}
continue;
}
match character {
'"' => quoted = true,
'(' | '[' | '{' | '<' => depth += 1,
')' | ']' | '}' => depth -= 1,
'>' if depth > 0 => depth -= 1,
',' if depth == 0 => {
result.push(value[start..index].trim());
start = index + 1;
}
_ => {}
}
}
if !value[start..].trim().is_empty() {
result.push(value[start..].trim());
}
result
}
fn split_top_level_keyword<'a>(value: &'a str, separator: &str) -> Option<(&'a str, &'a str)> {
let mut depth = 0i32;
let mut quoted = false;
let mut escaped = false;
let bytes = value.as_bytes();
for index in 0..bytes.len().saturating_sub(separator.len() - 1) {
let byte = bytes[index];
if quoted {
if escaped {
escaped = false;
} else if byte == b'\\' {
escaped = true;
} else if byte == b'"' {
quoted = false;
}
continue;
}
if byte == b'"' {
quoted = true;
continue;
}
match byte {
b'(' | b'[' | b'{' | b'<' => depth += 1,
b')' | b']' | b'}' => depth -= 1,
b'>' if index == 0 || bytes[index - 1] != b'-' => depth -= 1,
_ => {}
}
if depth == 0
&& index > 0
&& &bytes[index..index + separator.len()] == separator.as_bytes()
&& bytes[index - 1].is_ascii_whitespace()
&& bytes
.get(index + separator.len())
.is_some_and(u8::is_ascii_whitespace)
{
return Some((&value[..index], &value[index + separator.len()..]));
}
}
None
}
fn split_top_level_to(value: &str) -> Option<(&str, &str)> {
split_top_level_keyword(value, "to")
}
fn split_top_level_colon(value: &str) -> Option<(&str, &str)> {
let mut depth = 0i32;
let mut quoted = false;
let mut escaped = false;
let mut colon = None;
for (index, character) in value.char_indices() {
if quoted {
if character == '"' && !escaped {
quoted = false;
}
escaped = character == '\\' && !escaped;
if character != '\\' {
escaped = false;
}
continue;
}
match character {
'"' => quoted = true,
'(' | '[' | '{' | '<' => depth += 1,
')' | ']' | '}' | '>' => depth -= 1,
':' if depth == 0 => colon = Some(index),
_ => {}
}
}
colon.map(|index| (&value[..index], &value[index + 1..]))
}
fn attribute_groups<'a>(groups: impl IntoIterator<Item = Option<&'a str>>) -> Option<String> {
let groups = groups
.into_iter()
.flatten()
.map(str::to_owned)
.collect::<Vec<_>>();
(!groups.is_empty()).then(|| format!("[{}]", groups.join(", ")))
}
fn strip_top_level_attribute(value: &str) -> &str {
top_level_attribute(value)
.map(|(start, _)| &value[..start])
.unwrap_or(value)
}
fn top_level_attribute(value: &str) -> Option<(usize, &str)> {
let mut depth = 0usize;
let mut quoted = false;
let mut escaped = false;
for (index, character) in value.char_indices() {
if quoted {
if character == '"' && !escaped {
quoted = false;
}
escaped = character == '\\' && !escaped;
if character != '\\' {
escaped = false;
}
continue;
}
match character {
'"' => quoted = true,
'(' | '[' | '<' => depth += 1,
')' | ']' | '>' => depth = depth.saturating_sub(1),
'{' if depth == 0 => {
let end = matching_delimiter(value, index, '{', '}')?;
return Some((index, &value[index..=end]));
}
'{' => depth += 1,
'}' => depth = depth.saturating_sub(1),
_ => {}
}
}
None
}
fn matching_delimiter(spelling: &str, open: usize, left: char, right: char) -> Option<usize> {
let mut depth = 0usize;
let mut quoted = false;
let mut escaped = false;
for (offset, character) in spelling[open..].char_indices() {
if quoted {
if character == '"' && !escaped {
quoted = false;
}
escaped = character == '\\' && !escaped;
if character != '\\' {
escaped = false;
}
continue;
}
if character == '"' {
quoted = true;
continue;
}
if character == left {
depth += 1;
} else if character == right {
depth -= 1;
if depth == 0 {
return Some(open + offset);
}
}
}
None
}
fn lower_function(context: &RegisteredLoweringContext<'_>) -> Option<RegisteredLowering> {
lower_func_like("func.func", context)
}
fn lower_func_like(
_operation: &str,
context: &RegisteredLoweringContext<'_>,
) -> Option<RegisteredLowering> {
let symbol = context.leading_symbol()?.to_owned();
let signature = function_signature(context)?;
let mut attributes = vec![("sym_name", symbol), ("function_type", signature)];
if let Some(visibility @ ("public" | "private" | "nested")) = context.visibility() {
attributes.push(("sym_visibility", format!("\"{visibility}\"")));
}
if let Some(groups) = attribute_groups(context.arguments().map(|(_, attributes)| attributes)) {
attributes.push(("arg_attrs", groups));
}
if let Some(results) = context.function_results() {
let results = results.strip_prefix("->")?.trim();
let groups = split_top_level(
results
.strip_prefix('(')
.and_then(|value| value.strip_suffix(')'))
.unwrap_or(results),
)
.into_iter()
.map(|result| top_level_attribute(result).map(|(_, group)| group));
if let Some(groups) = attribute_groups(groups) {
attributes.push(("res_attrs", groups));
}
}
Some(RegisteredLowering {
result_types: Vec::new(),
function_type: "() -> ()".into(),
attributes,
})
}
fn lower_call(context: &RegisteredLoweringContext<'_>) -> Option<RegisteredLowering> {
lower_call_like("func.call", context)
}
fn lower_call_like(
_operation: &str,
context: &RegisteredLoweringContext<'_>,
) -> Option<RegisteredLowering> {
let callee = context.leading_symbol()?.to_owned();
let tail = context.function_type()?;
let (inputs, results) = crate::semantic::split_arrow(tail)?;
let result_types = crate::semantic::split_registered_types(results);
Some(RegisteredLowering {
result_types,
function_type: format!("{} -> {}", inputs.trim(), results.trim()),
attributes: vec![("callee", callee)],
})
}
pub(crate) fn lower_operation_shape(
shape: OperationShape,
operation: &str,
context: &RegisteredLoweringContext<'_>,
callee_attribute: Option<&str>,
) -> Option<DeclarativeLowering> {
let lowering = match shape {
OperationShape::FuncLike => lower_func_like(operation, context),
OperationShape::CallLike => lower_call_like(operation, context),
OperationShape::BinaryOperands => lower_binary_operands(operation, context),
OperationShape::OptionalTypedOperands => lower_optional_typed_operands(operation, context),
OperationShape::AttrFirstOptionalTypedOperands => {
lower_optional_typed_operands(operation, context)
}
OperationShape::UnaryOperand => lower_unary_operand(operation, context),
OperationShape::VariadicOperands => lower_variadic_operands(operation, context),
OperationShape::LiteralAttribute => lower_literal_attribute(operation, context),
OperationShape::OperandClauses => lower_operand_clauses(operation, context),
OperationShape::RegionClauses => lower_region_clauses(operation, context),
}?;
let mut lowering: DeclarativeLowering = lowering.into();
if shape == OperationShape::CallLike
&& let Some((name, _)) = lowering
.attributes
.iter_mut()
.find(|(name, _)| name == "callee")
{
*name = callee_attribute.unwrap_or("callee").to_owned();
}
Some(lowering)
}
pub(crate) struct DeclarativeLowering {
pub(crate) result_types: Vec<String>,
pub(crate) function_type: String,
pub(crate) attributes: Vec<(String, String)>,
}
impl From<RegisteredLowering> for DeclarativeLowering {
fn from(lowering: RegisteredLowering) -> Self {
Self {
result_types: lowering.result_types,
function_type: lowering.function_type,
attributes: lowering
.attributes
.into_iter()
.map(|(name, spelling)| (name.to_owned(), spelling))
.collect(),
}
}
}
pub(crate) fn lower_operation_format(
format: &OperationFormat,
context: &RegisteredLoweringContext<'_>,
callee_attribute: Option<&str>,
) -> Option<DeclarativeLowering> {
if format.types().len() != context.format_types().len() {
return None;
}
let mut inputs = vec![None; context.operand_count()];
let mut results = vec![None; context.result_count()];
for (capture, spelling) in format.types().iter().zip(context.format_types()) {
let spelling = spelling.trim();
if capture.is_per_operand_list() {
let types = crate::semantic::split_registered_types(spelling);
if types.len() != inputs.len() {
return None;
}
for (slot, ty) in inputs.iter_mut().zip(types) {
if slot.replace(ty).is_some() {
return None;
}
}
continue;
}
let aggregate = capture.targets().len() == 1
&& matches!(
capture.targets()[0],
FormatTarget::Operands | FormatTarget::Results
);
let captured_types = crate::semantic::split_registered_types(spelling);
for target in capture.targets() {
match *target {
FormatTarget::Operands => {
let assigned = if aggregate && captured_types.len() > 1 {
captured_types.clone()
} else if captured_types.len() == 1 {
std::iter::repeat_n(captured_types[0].clone(), inputs.len()).collect()
} else {
return None;
};
if assigned.len() != inputs.len() {
return None;
}
for (slot, ty) in inputs.iter_mut().zip(assigned) {
if slot.replace(ty).is_some() {
return None;
}
}
}
FormatTarget::Operand(index) => {
if captured_types.len() != 1
|| inputs
.get_mut(index)?
.replace(captured_types[0].clone())
.is_some()
{
return None;
}
}
FormatTarget::Results => {
let assigned = if capture.targets().len() > 1 && captured_types.len() == 1 {
std::iter::repeat_n(captured_types[0].clone(), results.len()).collect()
} else if aggregate {
captured_types.clone()
} else {
return None;
};
if assigned.len() != results.len() {
return None;
}
for (slot, ty) in results.iter_mut().zip(assigned) {
if slot.replace(ty).is_some() {
return None;
}
}
}
FormatTarget::Result => {
if captured_types.len() != 1
|| results.len() != 1
|| results[0].replace(captured_types[0].clone()).is_some()
{
return None;
}
}
FormatTarget::Attribute(_) => {}
}
}
}
let inputs = inputs.into_iter().collect::<Option<Vec<_>>>()?;
let results = results.into_iter().collect::<Option<Vec<_>>>()?;
let result_text = type_list_text(&results);
let mut attributes = Vec::new();
let captured_attributes = format.captured_attributes().collect::<Vec<_>>();
if captured_attributes.len() != context.literal_values().len() {
return None;
}
for (name, value) in captured_attributes
.into_iter()
.zip(context.literal_values())
{
let value = value.trim();
let value = strip_top_level_attribute(value).trim();
attributes.push((name.to_owned(), value.to_owned()));
}
if format.captures_callee() {
attributes.push((
callee_attribute.unwrap_or("callee").to_owned(),
context.leading_symbol()?.trim().to_owned(),
));
}
Some(DeclarativeLowering {
result_types: results,
function_type: format!("({}) -> {result_text}", inputs.join(", ")),
attributes,
})
}
fn verify_arith_constant(document: &Document, operation: OperationId) -> Result<(), &'static str> {
let results = document
.result_types(operation)
.ok_or("missing result list")?;
if !document
.operands(operation)
.is_some_and(|values| values.is_empty())
|| results.len() != 1
{
return Err("arith.constant expects zero operands and one result");
}
let value = document
.attribute_id(operation, "value")
.ok_or("arith.constant requires `value`")?;
let result = document
.type_value(results[0])
.ok_or("arith.constant has an invalid result type")?;
match (document.attribute_value(value), result) {
(
Some(crate::semantic::AttributeValue::Integer(_)),
crate::semantic::TypeValue::Integer { .. } | crate::semantic::TypeValue::Index,
)
| (Some(crate::semantic::AttributeValue::Float(_)), crate::semantic::TypeValue::Float(_)) =>
{}
(Some(crate::semantic::AttributeValue::Integer(_)), _) => {
return Err("arith.constant integer value requires an integer or index result");
}
(Some(crate::semantic::AttributeValue::Float(_)), _) => {
return Err("arith.constant floating-point value requires a floating-point result");
}
_ => return Err("arith.constant value must be an integer or floating-point attribute"),
}
Ok(())
}
fn print_attribute_dictionary(
document: &Document,
operation: OperationId,
excluded: &[&str],
) -> Option<String> {
let entries = document
.attributes(operation)?
.filter(|(name, _)| !excluded.contains(name))
.map(|(name, value)| format!("{name} = {value}"))
.collect::<Vec<_>>();
Some(if entries.is_empty() {
String::new()
} else {
format!(" {{{}}}", entries.join(", "))
})
}
fn print_arith_constant(document: &Document, operation: OperationId) -> Option<String> {
let value = document
.attributes(operation)?
.find_map(|(name, value)| (name == "value").then_some(value))?;
if value.contains(':') {
return None;
}
let ty = document
.result_types(operation)?
.first()
.and_then(|ty| document.type_spelling(*ty))?;
let dictionary = print_attribute_dictionary(document, operation, &["value"])?;
Some(format!("arith.constant {value}{dictionary} : {ty}"))
}
fn lower_binary_operands(
operation: &str,
context: &RegisteredLoweringContext<'_>,
) -> Option<RegisteredLowering> {
lower_typed_operands(operation, context, 2)
}
fn lower_unary_operand(
operation: &str,
context: &RegisteredLoweringContext<'_>,
) -> Option<RegisteredLowering> {
lower_typed_operands(operation, context, 1)
}
fn lower_typed_operands(
operation: &str,
context: &RegisteredLoweringContext<'_>,
operand_count: usize,
) -> Option<RegisteredLowering> {
let tail = context.assembly_spelling().split_once(operation)?.1;
let (_, ty) = tail.rsplit_once(':')?;
let ty = ty.split_once("//").map_or(ty, |(ty, _)| ty).trim();
let (function_type, result_types) = if let Some((input, result)) = split_top_level_to(ty) {
let input = input.trim();
let inputs = std::iter::repeat_n(input, operand_count)
.collect::<Vec<_>>()
.join(", ");
let result = result.trim();
(format!("({inputs}) -> {result}"), vec![result.into()])
} else if let Some((inputs, results)) = crate::semantic::split_arrow(ty) {
let inputs = inputs.trim();
let input_types = crate::semantic::split_registered_types(inputs);
let normalized_inputs = if !inputs.starts_with('(') && input_types.len() == 1 {
format!(
"({})",
std::iter::repeat_n(input_types[0].as_str(), operand_count)
.collect::<Vec<_>>()
.join(", ")
)
} else {
inputs.to_owned()
};
(
format!("{normalized_inputs} -> {}", results.trim()),
crate::semantic::split_registered_types(results),
)
} else {
let inputs = std::iter::repeat_n(ty, operand_count)
.collect::<Vec<_>>()
.join(", ");
(format!("({inputs}) -> {ty}"), vec![ty.into()])
};
Some(RegisteredLowering {
result_types,
function_type,
attributes: Vec::new(),
})
}
fn lower_variadic_operands(
operation: &str,
context: &RegisteredLoweringContext<'_>,
) -> Option<RegisteredLowering> {
let spelling = context.assembly_spelling();
let (prefix, tail) = spelling.split_once(operation)?;
let (_, ty) = tail.rsplit_once(':')?;
let ty = ty.trim();
if let Some((inputs, results)) = crate::semantic::split_arrow(ty) {
return Some(RegisteredLowering {
result_types: crate::semantic::split_registered_types(results),
function_type: format!("{} -> {}", inputs.trim(), results.trim()),
attributes: Vec::new(),
});
}
if split_top_level_to(ty).is_some() {
let operand_count = tail.bytes().filter(|byte| *byte == b'%').count();
return lower_typed_operands(operation, context, operand_count);
}
if prefix.contains('=') {
let operand_count = tail.bytes().filter(|byte| *byte == b'%').count();
return lower_typed_operands(operation, context, operand_count);
}
let inputs = if ty.starts_with('(') {
ty.to_owned()
} else {
format!("({ty})")
};
Some(RegisteredLowering {
result_types: Vec::new(),
function_type: format!("{inputs} -> ()"),
attributes: Vec::new(),
})
}
fn lower_operand_clauses(
operation: &str,
context: &RegisteredLoweringContext<'_>,
) -> Option<RegisteredLowering> {
if let Some(function_type) = context.function_type() {
let (inputs, results) = crate::semantic::split_arrow(function_type)?;
return Some(RegisteredLowering {
result_types: crate::semantic::split_registered_types(results),
function_type: format!("{} -> {}", inputs.trim(), results.trim()),
attributes: Vec::new(),
});
}
if let Some((_, results)) = crate::semantic::split_arrow(context.assembly_spelling()) {
let result_types = crate::semantic::split_registered_types(results);
let inputs =
std::iter::repeat_n("!zirium.unparsed<>", context.operand_count()).collect::<Vec<_>>();
return Some(RegisteredLowering {
function_type: format!(
"({}) -> {}",
inputs.join(", "),
type_list_text(&result_types)
),
result_types,
attributes: Vec::new(),
});
}
let tail = context.assembly_spelling().split_once(operation)?.1;
let (_, type_tail) = tail.rsplit_once(':')?;
let type_tail = type_tail.trim();
if let Some((input, result)) = split_top_level_to(type_tail) {
let inputs = std::iter::repeat_n(input.trim(), context.operand_count()).collect::<Vec<_>>();
return Some(RegisteredLowering {
result_types: vec![result.trim().to_owned()],
function_type: format!("({}) -> {}", inputs.join(", "), result.trim()),
attributes: Vec::new(),
});
}
let types = crate::semantic::split_registered_types(type_tail);
let result = types.last()?.clone();
let inputs = if types.len() == 2 && context.operand_count() == 3 {
vec![types[0].clone(), types[1].clone(), types[1].clone()]
} else if types.len() == context.operand_count() && types.len() > 1 {
types.clone()
} else {
std::iter::repeat_n(result.as_str(), context.operand_count())
.map(str::to_owned)
.collect()
};
let result_types = if types.len() == context.operand_count() && types.len() > 1 {
types
} else {
vec![result.clone()]
};
Some(RegisteredLowering {
result_types: result_types.clone(),
function_type: format!(
"({}) -> {}",
inputs.join(", "),
type_list_text(&result_types)
),
attributes: Vec::new(),
})
}
fn type_list_text(types: &[String]) -> String {
match types {
[single] => single.clone(),
_ => format!("({})", types.join(", ")),
}
}
fn lower_region_clauses(
_operation: &str,
context: &RegisteredLoweringContext<'_>,
) -> Option<RegisteredLowering> {
if let Some(function_type) = context.function_type() {
let (inputs, results) = crate::semantic::split_arrow(function_type)?;
return Some(RegisteredLowering {
result_types: crate::semantic::split_registered_types(results),
function_type: format!("{} -> {}", inputs.trim(), results.trim()),
attributes: Vec::new(),
});
}
let input_types =
std::iter::repeat_n("!zirium.unparsed<>", context.operand_count()).collect::<Vec<_>>();
let result_types = std::iter::repeat_n("!zirium.unparsed<>".to_owned(), context.result_count())
.collect::<Vec<_>>();
Some(RegisteredLowering {
function_type: format!(
"({}) -> {}",
input_types.join(", "),
type_list_text(&result_types)
),
result_types,
attributes: Vec::new(),
})
}
fn lower_literal_attribute(
operation: &str,
context: &RegisteredLoweringContext<'_>,
) -> Option<RegisteredLowering> {
let tail = context.assembly_spelling().split_once(operation)?.1.trim();
let (attribute_prefix, result) = tail.rsplit_once(':')?;
let literal = context.literal_value()?.trim();
let attribute = if matches!(
literal.split_once('<').map(|(name, _)| name.trim()),
Some("dense" | "sparse" | "dense_resource")
) {
literal
} else {
strip_top_level_attribute(attribute_prefix).trim()
};
let result = result.trim();
Some(RegisteredLowering {
result_types: vec![result.into()],
function_type: format!("() -> {result}"),
attributes: vec![("value", attribute.to_owned())],
})
}
fn lower_no_results() -> Option<RegisteredLowering> {
Some(RegisteredLowering {
result_types: Vec::new(),
function_type: "() -> ()".into(),
attributes: Vec::new(),
})
}
fn lower_return(context: &RegisteredLoweringContext<'_>) -> Option<RegisteredLowering> {
let tail = context
.assembly_spelling()
.split_once(context.mnemonic())?
.1
.trim();
let input = tail
.rsplit_once(':')
.map(|(_, types)| types.trim())
.unwrap_or("()");
let input = if input.starts_with('(') {
input.to_owned()
} else {
format!("({input})")
};
Some(RegisteredLowering {
result_types: Vec::new(),
function_type: format!("{input} -> ()"),
attributes: Vec::new(),
})
}
fn lower_optional_typed_operands(
operation: &str,
context: &RegisteredLoweringContext<'_>,
) -> Option<RegisteredLowering> {
let tail = context.assembly_spelling().split_once(operation)?.1.trim();
let input = if context.operand_count() == 0 {
"()"
} else {
split_top_level_colon(tail)?.1.trim()
};
let input = if input.starts_with('(') {
input.to_owned()
} else {
format!("({input})")
};
Some(RegisteredLowering {
result_types: Vec::new(),
function_type: format!("{input} -> ()"),
attributes: Vec::new(),
})
}
fn print_return(document: &Document, operation: OperationId) -> Option<String> {
let operands = document.typed_operands_spelling(operation)?;
let dictionary = print_attribute_dictionary(document, operation, &[])?;
let body = operands
.find(" :")
.map(|colon| format!("{}{dictionary}{}", &operands[..colon], &operands[colon..]))
.unwrap_or_else(|| format!("{operands}{dictionary}"));
Some(format!("func.return{body}"))
}
fn print_branch(document: &Document, operation: OperationId) -> Option<String> {
Some(format!(
"cf.br {}{}",
document.successor_spelling(operation)?,
print_attribute_dictionary(document, operation, &[])?
))
}
fn print_module(document: &Document, operation: OperationId) -> Option<String> {
let symbol = document
.attributes(operation)?
.find_map(|(name, value)| (name == "sym_name").then_some(value));
if symbol.is_some_and(|symbol| !symbol.starts_with('@')) {
return None;
}
let dictionary = print_attribute_dictionary(document, operation, &["sym_name"])?;
Some(format!(
"builtin.module{}{} ",
symbol.map(|value| format!(" {value}")).unwrap_or_default(),
if dictionary.is_empty() {
String::new()
} else {
format!(" attributes{dictionary}")
}
))
}
fn print_function(document: &Document, operation: OperationId) -> Option<String> {
let symbol = document
.attributes(operation)?
.find_map(|(name, value)| (name == "sym_name").then_some(value))?;
if !symbol.starts_with('@') {
return None;
}
let signature = document
.attributes(operation)?
.find_map(|(name, value)| (name == "function_type").then_some(value))?;
let mut signature = signature
.strip_prefix("type<")
.and_then(|value| value.strip_suffix('>'))
.unwrap_or(signature)
.to_owned();
if let Some(entry) = document
.operation_regions(operation)?
.first()
.and_then(|region| document.region(*region))
.and_then(|region| region.blocks(document))
.and_then(|blocks| blocks.first())
{
let arguments = document
.block_argument_types(*entry)?
.iter()
.enumerate()
.map(|(argument, ty)| {
Some(format!(
"{}: {}",
document.value_spelling(crate::semantic::ValueReference::Resolved(
crate::semantic::ValueId::BlockArgument {
block: *entry,
argument: argument as u32,
},
))?,
document.type_spelling(*ty)?,
))
})
.collect::<Option<Vec<_>>>()?
.join(", ");
let inputs_end = matching_delimiter(&signature, 0, '(', ')')?;
signature = format!("({arguments}){}", &signature[inputs_end + 1..]);
}
let no_inline = document
.attribute_id(operation, "no_inline")
.and_then(|id| document.attribute_value(id))
.is_some_and(is_unit_attribute);
let dictionary = print_attribute_dictionary(
document,
operation,
&["sym_name", "function_type", "sym_visibility", "no_inline"],
)?;
let dictionary = if no_inline {
if dictionary.is_empty() {
" {no_inline}".to_owned()
} else {
format!("{}{}, no_inline}}", &dictionary[..dictionary.len() - 1], "")
}
} else {
dictionary
};
let visibility = document
.attributes(operation)?
.find_map(|(name, value)| (name == "sym_visibility").then_some(value.trim_matches('"')))
.map(|value| format!(" {value}"))
.unwrap_or_default();
Some(format!(
"func.func{visibility} {symbol}{signature}{} ",
if dictionary.is_empty() {
String::new()
} else {
format!(" attributes{dictionary}")
}
))
}
fn is_unit_attribute(value: &crate::semantic::AttributeValue) -> bool {
matches!(value, crate::semantic::AttributeValue::Opaque(bytes) if bytes.as_ref() == b"unit")
}
fn print_call(document: &Document, operation: OperationId) -> Option<String> {
let callee = document
.attributes(operation)?
.find_map(|(name, value)| (name == "callee").then_some(value))?;
let operands = document
.operands(operation)?
.iter()
.map(|operand| document.value_spelling(*operand))
.collect::<Option<Vec<_>>>()?
.join(", ");
let dictionary = print_attribute_dictionary(document, operation, &["callee"])?;
Some(format!(
"func.call {callee}({operands}){dictionary} : {}",
document.type_spelling(document.function_type(operation)?)?
))
}
fn print_cond_branch(document: &Document, operation: OperationId) -> Option<String> {
let operands = document.operands(operation)?;
let condition = document.value_spelling(*operands.first()?)?;
let successors = document.successors(operation)?;
if successors.len() != 2 {
return None;
}
let dictionary = print_attribute_dictionary(document, operation, &[])?;
Some(format!(
"cf.cond_br {condition}, {}, {}{dictionary}",
document.successor_spelling_at(operation, 0)?,
document.successor_spelling_at(operation, 1)?
))
}
static MODULE_REGIONS: &[RegionDescriptor] = &[RegionDescriptor {
kind: RegionKind::Ssacfg,
isolated_from_above: true,
requires_terminator: false,
}];
static FUNCTION_REGIONS: &[RegionDescriptor] = &[RegionDescriptor {
kind: RegionKind::Ssacfg,
isolated_from_above: true,
requires_terminator: true,
}];
static BUILTIN_MODULE: OperationDescriptor = OperationDescriptor {
name: "builtin.module",
syntax_kind: SyntaxKind::DialectOperation,
parse: None,
lower: None,
verify: None,
print: None,
assembly: Some(AssemblyProgram::BuiltinModule),
schema: OperationSchema {
operands: OperandCount::Exact(0),
results: ResultCount::Exact(0),
required_attributes: &[],
},
regions: MODULE_REGIONS,
symbols: SymbolDescriptor {
defines_symbol: true,
symbol_table: true,
uses_symbols: false,
},
is_terminator: false,
};
static FUNC_FUNC: OperationDescriptor = OperationDescriptor {
name: "func.func",
syntax_kind: SyntaxKind::DialectOperation,
parse: None,
lower: None,
verify: None,
print: None,
assembly: Some(AssemblyProgram::FuncFunc),
schema: OperationSchema {
operands: OperandCount::Exact(0),
results: ResultCount::Exact(0),
required_attributes: &["sym_name", "function_type"],
},
regions: FUNCTION_REGIONS,
symbols: SymbolDescriptor {
defines_symbol: true,
symbol_table: false,
uses_symbols: false,
},
is_terminator: false,
};
static FUNC_CALL: OperationDescriptor = OperationDescriptor {
name: "func.call",
syntax_kind: SyntaxKind::DialectOperation,
parse: None,
lower: None,
verify: None,
print: None,
assembly: Some(AssemblyProgram::FuncCall),
schema: OperationSchema {
operands: OperandCount::Variadic,
results: ResultCount::Variadic,
required_attributes: &["callee"],
},
regions: &[],
symbols: SymbolDescriptor {
defines_symbol: false,
symbol_table: false,
uses_symbols: true,
},
is_terminator: false,
};
static CF_COND_BR: OperationDescriptor = OperationDescriptor {
name: "cf.cond_br",
syntax_kind: SyntaxKind::DialectOperation,
parse: None,
lower: None,
verify: None,
print: None,
assembly: Some(AssemblyProgram::CfCondBr),
schema: OperationSchema {
operands: OperandCount::Variadic,
results: ResultCount::Exact(0),
required_attributes: &[],
},
regions: &[],
symbols: SymbolDescriptor {
defines_symbol: false,
symbol_table: false,
uses_symbols: false,
},
is_terminator: true,
};
static ARITH_CONSTANT: OperationDescriptor = OperationDescriptor {
name: "arith.constant",
syntax_kind: SyntaxKind::DialectOperation,
parse: None,
lower: None,
verify: None,
print: None,
assembly: Some(AssemblyProgram::ArithConstant),
schema: OperationSchema {
operands: OperandCount::Exact(0),
results: ResultCount::Exact(1),
required_attributes: &["value"],
},
regions: &[],
symbols: SymbolDescriptor {
defines_symbol: false,
symbol_table: false,
uses_symbols: false,
},
is_terminator: false,
};
static FUNC_RETURN: OperationDescriptor = OperationDescriptor {
name: "func.return",
syntax_kind: SyntaxKind::DialectOperation,
parse: None,
lower: None,
verify: None,
print: None,
assembly: Some(AssemblyProgram::FuncReturn),
schema: OperationSchema {
operands: OperandCount::Variadic,
results: ResultCount::Exact(0),
required_attributes: &[],
},
regions: &[],
symbols: SymbolDescriptor {
defines_symbol: false,
symbol_table: false,
uses_symbols: false,
},
is_terminator: true,
};
static CF_BR: OperationDescriptor = OperationDescriptor {
name: "cf.br",
syntax_kind: SyntaxKind::DialectOperation,
parse: None,
lower: None,
verify: None,
print: None,
assembly: Some(AssemblyProgram::CfBr),
schema: OperationSchema {
operands: OperandCount::Exact(0),
results: ResultCount::Exact(0),
required_attributes: &[],
},
regions: &[],
symbols: SymbolDescriptor {
defines_symbol: false,
symbol_table: false,
uses_symbols: false,
},
is_terminator: true,
};
static BASELINE_OPERATIONS: &[OperationDescriptor] = &[
BUILTIN_MODULE,
FUNC_FUNC,
FUNC_RETURN,
FUNC_CALL,
ARITH_CONSTANT,
CF_BR,
CF_COND_BR,
];
static BASELINE_OPERATION_SHAPES: &[(&str, OperationShape)] =
&[("arith.addi", OperationShape::BinaryOperands)];
static CORE_OPERATIONS: &[OperationDescriptor] =
&[BUILTIN_MODULE, FUNC_FUNC, FUNC_RETURN, FUNC_CALL];
static DECLARATIVE_OPERATION_SETS: [OnceLock<Box<[OperationDescriptor]>>; 256] =
[const { OnceLock::new() }; 256];
static BASELINE_REGISTRY: OnceLock<DialectRegistry> = OnceLock::new();
static CORE_REGISTRY: DialectRegistry = DialectRegistry {
operations: CORE_OPERATIONS,
types: &[],
attributes: &[],
operation_shapes: None,
operation_formats: None,
operation_alternatives: None,
call_target_attributes: None,
module_alias: true,
};