use std::collections::HashSet;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DumpStage {
Ast,
Resolved,
Typed,
Instantiated,
Ir,
Bytecode,
}
impl DumpStage {
fn from_str(s: &str) -> Option<Self> {
match s.trim().to_lowercase().as_str() {
"ast" => Some(Self::Ast),
"resolved" => Some(Self::Resolved),
"typed" => Some(Self::Typed),
"instantiated" => Some(Self::Instantiated),
"ir" => Some(Self::Ir),
"bytecode" | "bc" => Some(Self::Bytecode),
_ => None,
}
}
fn label(&self) -> &'static str {
match self {
Self::Ast => "ast",
Self::Resolved => "resolved",
Self::Typed => "typed",
Self::Instantiated => "instantiated",
Self::Ir => "ir",
Self::Bytecode => "bytecode",
}
}
}
#[derive(Debug, Clone)]
enum FilterEntry {
Bare(String),
Qualified { module: String, name: String },
}
impl FilterEntry {
fn parse(s: &str) -> Self {
let s = s.trim();
if let Some((module, name)) = s.rsplit_once("::") {
FilterEntry::Qualified {
module: module.to_string(),
name: name.to_string(),
}
} else {
FilterEntry::Bare(s.to_string())
}
}
fn matches(&self, fn_name: &str, module_name: &str) -> bool {
match self {
FilterEntry::Bare(name) => fn_name == name,
FilterEntry::Qualified { module, name } => fn_name == name && module_name == module,
}
}
}
struct DumpConfig {
stages: HashSet<DumpStage>,
filter: Option<Vec<FilterEntry>>,
}
impl DumpConfig {
fn from_env() -> Self {
let stages = match std::env::var("CUTILE_DUMP") {
Ok(val) => {
let val = val.trim();
if val.eq_ignore_ascii_case("all") {
[
DumpStage::Ast,
DumpStage::Resolved,
DumpStage::Typed,
DumpStage::Instantiated,
DumpStage::Ir,
DumpStage::Bytecode,
]
.into_iter()
.collect()
} else {
val.split(',').filter_map(DumpStage::from_str).collect()
}
}
Err(_) => {
if std::env::var("TILE_IR_DUMP").is_ok() {
[DumpStage::Ir].into_iter().collect()
} else {
HashSet::new()
}
}
};
let filter = std::env::var("CUTILE_DUMP_FILTER")
.ok()
.map(|val| val.split(',').map(FilterEntry::parse).collect());
DumpConfig { stages, filter }
}
}
static CONFIG: OnceLock<DumpConfig> = OnceLock::new();
fn config() -> &'static DumpConfig {
CONFIG.get_or_init(DumpConfig::from_env)
}
pub fn should_dump(stage: DumpStage) -> bool {
config().stages.contains(&stage)
}
pub fn matches_filter(fn_name: &str, module_name: &str) -> bool {
match &config().filter {
None => true,
Some(entries) => entries.iter().any(|e| e.matches(fn_name, module_name)),
}
}
pub fn dump(stage: DumpStage, fn_name: &str, module_name: &str, content: &str) {
if !should_dump(stage) || !matches_filter(fn_name, module_name) {
return;
}
let label = stage.label();
let qualified = if module_name.is_empty() {
fn_name.to_string()
} else {
format!("{module_name}::{fn_name}")
};
eprintln!("=== CUTILE DUMP: {label} ({qualified}) ===");
eprintln!("{content}");
eprintln!("=== END {label} ===\n");
}
pub fn dump_module(stage: DumpStage, module_name: &str, content: &str) {
if !should_dump(stage) {
return;
}
if let Some(entries) = &config().filter {
let any_match = entries.iter().any(|e| match e {
FilterEntry::Qualified { module, .. } => module == module_name,
FilterEntry::Bare(_) => true, });
if !any_match {
return;
}
}
let label = stage.label();
eprintln!("=== CUTILE DUMP: {label} ({module_name}) ===");
eprintln!("{content}");
eprintln!("=== END {label} ===\n");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_stage_names() {
assert_eq!(DumpStage::from_str("ast"), Some(DumpStage::Ast));
assert_eq!(DumpStage::from_str("IR"), Some(DumpStage::Ir));
assert_eq!(
DumpStage::from_str(" Bytecode "),
Some(DumpStage::Bytecode)
);
assert_eq!(DumpStage::from_str("bc"), Some(DumpStage::Bytecode));
assert_eq!(DumpStage::from_str("nonsense"), None);
}
#[test]
fn filter_bare_name() {
let entry = FilterEntry::parse("my_kernel");
assert!(entry.matches("my_kernel", "any_module"));
assert!(entry.matches("my_kernel", "other_module"));
assert!(!entry.matches("other_kernel", "any_module"));
}
#[test]
fn filter_qualified_name() {
let entry = FilterEntry::parse("my_module::my_kernel");
assert!(entry.matches("my_kernel", "my_module"));
assert!(!entry.matches("my_kernel", "other_module"));
assert!(!entry.matches("other_kernel", "my_module"));
}
#[test]
fn filter_nested_path() {
let entry = FilterEntry::parse("cutile::core::reshape");
assert!(entry.matches("reshape", "cutile::core"));
}
}