use std::collections::HashMap;
use std::fs;
use std::io::Write;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SourceLoc {
pub file: String,
pub line: u32,
pub column: u32,
}
impl std::fmt::Display for SourceLoc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}:{}", self.file, self.line, self.column)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RemarkKind {
Passed,
Missed,
Analysis,
AnalysisFPCommute,
AnalysisAliasing,
Failure,
}
impl RemarkKind {
pub fn as_yaml_str(&self) -> &'static str {
match self {
RemarkKind::Passed => "Passed",
RemarkKind::Missed => "Missed",
RemarkKind::Analysis => "Analysis",
RemarkKind::AnalysisFPCommute => "AnalysisFPCommute",
RemarkKind::AnalysisAliasing => "AnalysisAliasing",
RemarkKind::Failure => "Failure",
}
}
pub fn as_char(&self) -> char {
match self {
RemarkKind::Passed => 'P',
RemarkKind::Missed => 'M',
RemarkKind::Analysis => 'A',
RemarkKind::AnalysisFPCommute => 'F',
RemarkKind::AnalysisAliasing => 'L',
RemarkKind::Failure => 'X',
}
}
}
impl std::fmt::Display for RemarkKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_yaml_str())
}
}
#[derive(Debug, Clone)]
pub struct OptimizationRemark {
pub pass_name: String,
pub kind: RemarkKind,
pub function: String,
pub location: Option<SourceLoc>,
pub message: String,
pub args: HashMap<String, String>,
}
impl OptimizationRemark {
pub fn new(
pass_name: &str,
kind: RemarkKind,
function: &str,
message: &str,
location: Option<SourceLoc>,
) -> Self {
Self {
pass_name: pass_name.to_string(),
kind,
function: function.to_string(),
location,
message: message.to_string(),
args: HashMap::new(),
}
}
pub fn with_arg(mut self, key: &str, value: &str) -> Self {
self.args.insert(key.to_string(), value.to_string());
self
}
pub fn to_diagnostic_string(&self) -> String {
let loc_str = match &self.location {
Some(loc) => format!(" at {}", loc),
None => String::new(),
};
format!(
"[{}] {} in {}{}: {}",
self.kind.as_char(),
self.pass_name,
self.function,
loc_str,
self.message
)
}
}
pub struct OptimizationRemarkEmitter {
pub remarks: Vec<OptimizationRemark>,
pub enabled: bool,
pub filter: String,
}
impl OptimizationRemarkEmitter {
pub fn new() -> Self {
Self {
remarks: Vec::new(),
enabled: false,
filter: String::new(),
}
}
pub fn emit(&mut self, remark: OptimizationRemark) {
if !self.enabled {
return;
}
if !self.filter.is_empty() && !Self::passes_filter(&remark.pass_name, &self.filter) {
return;
}
self.remarks.push(remark);
}
pub fn emit_remark(&mut self, pass: &str, func: &str, msg: &str) {
self.emit(OptimizationRemark::new(
pass,
RemarkKind::Passed,
func,
msg,
None,
));
}
pub fn emit_missed(&mut self, pass: &str, func: &str, msg: &str) {
self.emit(OptimizationRemark::new(
pass,
RemarkKind::Missed,
func,
msg,
None,
));
}
pub fn emit_analysis(&mut self, pass: &str, func: &str, msg: &str) {
self.emit(OptimizationRemark::new(
pass,
RemarkKind::Analysis,
func,
msg,
None,
));
}
pub fn enable(&mut self) {
self.enabled = true;
}
pub fn set_filter(&mut self, pattern: &str) {
self.filter = pattern.to_string();
}
pub fn count(&self) -> usize {
self.remarks.len()
}
pub fn count_by_kind(&self) -> HashMap<RemarkKind, usize> {
let mut counts = HashMap::new();
for r in &self.remarks {
*counts.entry(r.kind).or_insert(0) += 1;
}
counts
}
pub fn serialize_yaml(&self) -> String {
let mut output = String::from("--- !Analysis\n");
output.push_str("Passes:\n");
let mut by_pass: HashMap<String, Vec<&OptimizationRemark>> = HashMap::new();
for r in &self.remarks {
by_pass.entry(r.pass_name.clone()).or_default().push(r);
}
for (pass_name, remarks) in &by_pass {
output.push_str(&format!(" - Name: '{}'\n", pass_name));
output.push_str(&format!(
" PassSummary: '{} total remarks'\n",
remarks.len()
));
for r in remarks {
output.push_str(&format!(
" - {}: {}\n",
r.kind.as_yaml_str(),
r.message
));
output.push_str(&format!(" Function: '{}'\n", r.function));
if let Some(ref loc) = r.location {
output.push_str(&format!(
" DebugLoc: {{ File: '{}', Line: {}, Column: {} }}\n",
loc.file, loc.line, loc.column
));
}
for (key, val) in &r.args {
output.push_str(&format!(
" Arg: {{ '{}': '{}' }}\n",
key, val
));
}
}
}
output.push_str("...\n");
output
}
pub fn serialize_json(&self) -> String {
let mut output = String::from("[\n");
for (i, r) in self.remarks.iter().enumerate() {
output.push_str(" {\n");
output.push_str(&format!(
" \"Pass\": \"{}\",\n",
escape_json(&r.pass_name)
));
output.push_str(&format!(" \"Kind\": \"{}\",\n", r.kind.as_yaml_str()));
output.push_str(&format!(
" \"Function\": \"{}\",\n",
escape_json(&r.function)
));
output.push_str(&format!(" \"Message\": \"{}\"", escape_json(&r.message)));
if let Some(ref loc) = r.location {
output.push_str(",\n");
output.push_str(&format!(" \"File\": \"{}\",\n", escape_json(&loc.file)));
output.push_str(&format!(" \"Line\": {},\n", loc.line));
output.push_str(&format!(" \"Column\": {}", loc.column));
}
if !r.args.is_empty() {
output.push_str(",\n");
output.push_str(" \"Args\": {\n");
for (j, (key, val)) in r.args.iter().enumerate() {
output.push_str(&format!(
" \"{}\": \"{}\"{}",
escape_json(key),
escape_json(val),
if j + 1 < r.args.len() { "," } else { "" }
));
output.push('\n');
}
output.push_str(" }");
}
output.push('\n');
output.push_str(" }");
if i + 1 < self.remarks.len() {
output.push(',');
}
output.push('\n');
}
output.push_str("]\n");
output
}
pub fn write_to_file(&self, path: &str) -> Result<(), String> {
let content = if path.ends_with(".yaml") || path.ends_with(".yml") {
self.serialize_yaml()
} else {
self.serialize_json()
};
let mut file =
fs::File::create(path).map_err(|e| format!("Failed to create file: {}", e))?;
file.write_all(content.as_bytes())
.map_err(|e| format!("Failed to write file: {}", e))?;
Ok(())
}
pub fn clear(&mut self) {
self.remarks.clear();
}
fn passes_filter(name: &str, pattern: &str) -> bool {
if pattern.is_empty() || pattern == "*" {
return true;
}
if pattern.starts_with('*') && pattern.ends_with('*') && pattern.len() > 1 {
let inner = &pattern[1..pattern.len() - 1];
name.contains(inner)
} else if pattern.starts_with('*') {
name.ends_with(&pattern[1..])
} else if pattern.ends_with('*') {
name.starts_with(&pattern[..pattern.len() - 1])
} else {
name == pattern
}
}
}
impl Default for OptimizationRemarkEmitter {
fn default() -> Self {
Self::new()
}
}
fn escape_json(s: &str) -> String {
let mut result = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
c if c.is_control() => result.push_str(&format!("\\u{:04x}", c as u32)),
c => result.push(c),
}
}
result
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RemarkKindFull {
Passed,
Missed,
Analysis,
AnalysisFPCommute,
AnalysisAliasing,
Failure,
}
impl RemarkKindFull {
pub fn as_str(&self) -> &'static str {
match self {
RemarkKindFull::Passed => "Passed",
RemarkKindFull::Missed => "Missed",
RemarkKindFull::Analysis => "Analysis",
RemarkKindFull::AnalysisFPCommute => "AnalysisFPCommute",
RemarkKindFull::AnalysisAliasing => "AnalysisAliasing",
RemarkKindFull::Failure => "Failure",
}
}
pub fn to_simple(&self) -> RemarkKind {
match self {
RemarkKindFull::Passed => RemarkKind::Passed,
RemarkKindFull::Missed => RemarkKind::Missed,
RemarkKindFull::Analysis => RemarkKind::Analysis,
RemarkKindFull::AnalysisFPCommute => RemarkKind::AnalysisFPCommute,
RemarkKindFull::AnalysisAliasing => RemarkKind::AnalysisAliasing,
RemarkKindFull::Failure => RemarkKind::Failure,
}
}
}
impl std::fmt::Display for RemarkKindFull {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct YamlRemarkSerializer {
pub include_hotness: bool,
pub pretty: bool,
}
impl YamlRemarkSerializer {
pub fn new() -> Self {
Self {
include_hotness: false,
pretty: true,
}
}
pub fn serialize_one(&self, remark: &OptimizationRemark, hotness: Option<u64>) -> String {
let mut yaml = String::from("---\n");
yaml.push_str(&format!("Pass: {}\n", remark.pass_name));
yaml.push_str(&format!("Name: {}\n", remark.kind.as_yaml_str()));
yaml.push_str(&format!("Function: {}\n", remark.function));
if let Some(loc) = &remark.location {
yaml.push_str(&format!(
"DebugLoc: {{ File: {}, Line: {}, Column: {} }}\n",
loc.file, loc.line, loc.column
));
}
if let Some(h) = hotness {
yaml.push_str(&format!("Hotness: {}\n", h));
}
for (key, value) in &remark.args {
yaml.push_str(&format!(" - {}: '{}'\n", key, value));
}
yaml.push_str(&format!(" - String: '{}'\n", remark.message));
yaml.push_str("...\n");
yaml
}
pub fn serialize_many(&self, remarks: &[OptimizationRemark]) -> String {
let mut yaml = String::from("--- !Analysis\n");
for remark in remarks {
yaml.push_str(&format!("--- !{}\n", remark.kind.as_yaml_str()));
yaml.push_str(&format!("Pass: {}\n", remark.pass_name));
yaml.push_str(&format!("Name: {}\n", remark.kind.as_yaml_str()));
yaml.push_str(&format!("Function: {}\n", remark.function));
if let Some(loc) = &remark.location {
yaml.push_str(&format!(
"DebugLoc: {{ File: {}, Line: {}, Column: {} }}\n",
loc.file, loc.line, loc.column
));
}
if !remark.args.is_empty() {
yaml.push_str("Args:\n");
for (key, value) in &remark.args {
yaml.push_str(&format!(" - {}: '{}'\n", key, value));
}
}
yaml.push_str("...\n");
}
yaml
}
}
impl Default for YamlRemarkSerializer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct RemarkDiagnostic {
pub remark: OptimizationRemark,
pub hotness: Option<u64>,
pub should_display: bool,
pub severity: DiagnosticSeverity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticSeverity {
Ignored,
Note,
Remark,
Warning,
}
impl DiagnosticSeverity {
pub fn as_str(&self) -> &'static str {
match self {
DiagnosticSeverity::Ignored => "ignored",
DiagnosticSeverity::Note => "note",
DiagnosticSeverity::Remark => "remark",
DiagnosticSeverity::Warning => "warning",
}
}
}
impl RemarkDiagnostic {
pub fn new(remark: OptimizationRemark) -> Self {
let severity = match remark.kind {
RemarkKind::Passed => DiagnosticSeverity::Remark,
RemarkKind::Missed => DiagnosticSeverity::Warning,
RemarkKind::Failure => DiagnosticSeverity::Warning,
_ => DiagnosticSeverity::Note,
};
Self {
remark,
hotness: None,
should_display: true,
severity,
}
}
pub fn format_diagnostic(&self) -> String {
let mut diag = String::new();
if let Some(loc) = &self.remark.location {
diag.push_str(&format!("{}:{}:{}", loc.file, loc.line, loc.column));
}
diag.push_str(&format!(
": {}: {}",
self.severity.as_str(),
self.remark.message
));
if let Some(h) = self.hotness {
diag.push_str(&format!(" (hotness: {})", h));
}
if !self.remark.pass_name.is_empty() {
diag.push_str(&format!(" [{}]", self.remark.pass_name));
}
diag
}
pub fn with_hotness(mut self, hotness: u64) -> Self {
self.hotness = Some(hotness);
self
}
}
#[derive(Debug, Clone)]
pub struct OptViewerGenerator {
pub remarks_by_file: HashMap<String, Vec<RemarkDiagnostic>>,
pub line_annotations: bool,
}
impl OptViewerGenerator {
pub fn new() -> Self {
Self {
remarks_by_file: HashMap::new(),
line_annotations: true,
}
}
pub fn add_remark(&mut self, diagnostic: RemarkDiagnostic) {
if let Some(loc) = &diagnostic.remark.location {
self.remarks_by_file
.entry(loc.file.clone())
.or_default()
.push(diagnostic);
}
}
pub fn generate_index_html(&self) -> String {
let mut html = String::from(
"<!DOCTYPE html>\n<html><head><title>Optimization Remarks</title></head>\n<body>\n",
);
html.push_str("<h1>Optimization Remarks</h1>\n");
html.push_str("<table border='1'>\n");
html.push_str("<tr><th>File</th><th>Remarks</th></tr>\n");
let mut files: Vec<_> = self.remarks_by_file.keys().collect();
files.sort();
for file in files {
let count = self.remarks_by_file.get(file).map(|v| v.len()).unwrap_or(0);
html.push_str(&format!(
"<tr><td><a href='{}'>.html</a></td><td>{}</td></tr>\n",
file, count
));
}
html.push_str("</table>\n</body></html>");
html
}
pub fn generate_file_html(&self, filename: &str) -> String {
let mut html = format!(
"<!DOCTYPE html>\n<html><head><title>{} — Remarks</title></head>\n<body>\n",
filename
);
html.push_str(&format!("<h1>File: {}</h1>\n", filename));
if let Some(remarks) = self.remarks_by_file.get(filename) {
let mut by_line: HashMap<u32, Vec<&RemarkDiagnostic>> = HashMap::new();
for r in remarks {
if let Some(loc) = &r.remark.location {
by_line.entry(loc.line).or_default().push(r);
}
}
let mut lines: Vec<_> = by_line.keys().collect();
lines.sort();
for line in lines {
html.push_str(&format!("<h3>Line {}</h3>\n", line));
for r in by_line.get(line).unwrap_or(&vec![]) {
html.push_str(&format!(
"<p>{}: {}</p>\n",
r.remark.kind.as_yaml_str(),
r.remark.message
));
}
}
}
html.push_str("</body></html>");
html
}
pub fn total_remarks(&self) -> usize {
self.remarks_by_file.values().map(|v| v.len()).sum()
}
}
impl Default for OptViewerGenerator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct BitcodeRemarksSection {
pub embed_in_bitcode: bool,
pub section_name: String,
pub serialized_data: Vec<u8>,
}
impl BitcodeRemarksSection {
pub fn new() -> Self {
Self {
embed_in_bitcode: false,
section_name: "__LLVM_REMARKS".into(),
serialized_data: Vec::new(),
}
}
pub fn serialize_remarks(&mut self, remarks: &[OptimizationRemark]) {
let serializer = YamlRemarkSerializer::new();
let yaml = serializer.serialize_many(remarks);
self.serialized_data = yaml.into_bytes();
}
pub fn has_data(&self) -> bool {
!self.serialized_data.is_empty()
}
pub fn byte_size(&self) -> usize {
self.serialized_data.len()
}
}
impl Default for BitcodeRemarksSection {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct OptimizationRecordSaver {
pub enabled: bool,
pub output_file: String,
pub embed_in_bitcode: bool,
pub remarks: Vec<OptimizationRemark>,
pub serializer: YamlRemarkSerializer,
pub bitcode_section: BitcodeRemarksSection,
pub opt_viewer: OptViewerGenerator,
}
impl OptimizationRecordSaver {
pub fn new() -> Self {
Self {
enabled: false,
output_file: String::new(),
embed_in_bitcode: false,
remarks: Vec::new(),
serializer: YamlRemarkSerializer::new(),
bitcode_section: BitcodeRemarksSection::new(),
opt_viewer: OptViewerGenerator::new(),
}
}
pub fn record(&mut self, remark: OptimizationRemark) {
if !self.enabled {
return;
}
let diagnostic = RemarkDiagnostic::new(remark);
self.opt_viewer.add_remark(diagnostic.clone());
self.remarks.push(diagnostic.remark);
}
pub fn save(&self) -> std::io::Result<()> {
if self.remarks.is_empty() {
return Ok(());
}
let yaml = self.serializer.serialize_many(&self.remarks);
std::fs::write(&self.output_file, yaml)
}
pub fn save_opt_viewer(&self, output_dir: &str) -> std::io::Result<()> {
std::fs::create_dir_all(output_dir)?;
let index = self.opt_viewer.generate_index_html();
std::fs::write(format!("{}/index.html", output_dir), index)?;
for file in self.opt_viewer.remarks_by_file.keys() {
let file_html = self.opt_viewer.generate_file_html(file);
let safe_name = file.replace('/', "_").replace('\\', "_");
std::fs::write(format!("{}/{}.html", output_dir, safe_name), file_html)?;
}
Ok(())
}
pub fn count(&self) -> usize {
self.remarks.len()
}
}
impl Default for OptimizationRecordSaver {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct RemarkStats {
pub passed_count: usize,
pub missed_count: usize,
pub analysis_count: usize,
pub failure_count: usize,
pub by_pass: HashMap<String, usize>,
pub by_function: HashMap<String, usize>,
}
impl RemarkStats {
pub fn new() -> Self {
Self::default()
}
pub fn record(&mut self, remark: &OptimizationRemark) {
match remark.kind {
RemarkKind::Passed => self.passed_count += 1,
RemarkKind::Missed => self.missed_count += 1,
RemarkKind::Analysis => self.analysis_count += 1,
RemarkKind::Failure => self.failure_count += 1,
_ => {}
}
*self.by_pass.entry(remark.pass_name.clone()).or_insert(0) += 1;
*self.by_function.entry(remark.function.clone()).or_insert(0) += 1;
}
pub fn total(&self) -> usize {
self.passed_count + self.missed_count + self.analysis_count + self.failure_count
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_remark(kind: RemarkKind, pass: &str) -> OptimizationRemark {
OptimizationRemark::new(pass, kind, "test_func", "did something", None)
}
#[test]
fn test_emitter_disabled_by_default() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.emit_remark("inline", "foo", "inlined bar");
assert_eq!(emitter.count(), 0);
}
#[test]
fn test_emitter_enabled() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.emit_remark("inline", "foo", "inlined bar");
assert_eq!(emitter.count(), 1);
}
#[test]
fn test_emit_remark() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.emit_remark("inline", "foo", "inlined bar");
assert_eq!(emitter.remarks[0].kind, RemarkKind::Passed);
assert_eq!(emitter.remarks[0].pass_name, "inline");
}
#[test]
fn test_emit_missed() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.emit_missed("gvn", "foo", "no redundancy");
assert_eq!(emitter.remarks[0].kind, RemarkKind::Missed);
}
#[test]
fn test_emit_analysis() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.emit_analysis("aa", "foo", "may alias");
assert_eq!(emitter.remarks[0].kind, RemarkKind::Analysis);
}
#[test]
fn test_filter_exact() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.set_filter("inline");
emitter.emit_remark("inline", "foo", "msg1");
emitter.emit_remark("gvn", "bar", "msg2");
assert_eq!(emitter.count(), 1);
assert_eq!(emitter.remarks[0].pass_name, "inline");
}
#[test]
fn test_filter_prefix() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.set_filter("loop*");
emitter.emit_remark("loop-unroll", "foo", "msg1");
emitter.emit_remark("loop-vectorize", "bar", "msg2");
emitter.emit_remark("inline", "baz", "msg3");
assert_eq!(emitter.count(), 2);
}
#[test]
fn test_filter_suffix() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.set_filter("*vectorize");
emitter.emit_remark("slp-vectorize", "foo", "msg1");
emitter.emit_remark("loop-vectorize", "bar", "msg2");
emitter.emit_remark("inline", "baz", "msg3");
assert_eq!(emitter.count(), 2);
}
#[test]
fn test_filter_contains() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.set_filter("*unroll*");
emitter.emit_remark("loop-unroll", "foo", "msg1");
emitter.emit_remark("unroll-runtime", "bar", "msg2");
emitter.emit_remark("inline", "baz", "msg3");
assert_eq!(emitter.count(), 2);
}
#[test]
fn test_filter_star_matches_all() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.set_filter("*");
emitter.emit_remark("inline", "foo", "msg1");
emitter.emit_remark("gvn", "bar", "msg2");
assert_eq!(emitter.count(), 2);
}
#[test]
fn test_count_by_kind() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.emit_remark("p1", "f", "m");
emitter.emit_missed("p2", "f", "m");
emitter.emit_analysis("p3", "f", "m");
let counts = emitter.count_by_kind();
assert_eq!(counts.get(&RemarkKind::Passed), Some(&1));
assert_eq!(counts.get(&RemarkKind::Missed), Some(&1));
assert_eq!(counts.get(&RemarkKind::Analysis), Some(&1));
}
#[test]
fn test_serialize_yaml_empty() {
let emitter = OptimizationRemarkEmitter::new();
let yaml = emitter.serialize_yaml();
assert!(yaml.contains("!Analysis"));
assert!(yaml.contains("Passes"));
}
#[test]
fn test_serialize_yaml_with_remarks() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.emit_remark("inline", "foo", "inlined bar");
let yaml = emitter.serialize_yaml();
assert!(yaml.contains("inline"));
assert!(yaml.contains("foo"));
assert!(yaml.contains("inlined bar"));
}
#[test]
fn test_serialize_json_empty() {
let emitter = OptimizationRemarkEmitter::new();
let json = emitter.serialize_json();
assert_eq!(json, "[\n]\n");
}
#[test]
fn test_serialize_json_with_remarks() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.emit_remark("inline", "foo", "inlined bar");
let json = emitter.serialize_json();
assert!(json.contains("inline"));
assert!(json.contains("foo"));
assert!(json.contains("inlined bar"));
}
#[test]
fn test_remark_with_location() {
let loc = SourceLoc {
file: "test.c".to_string(),
line: 42,
column: 7,
};
let remark = OptimizationRemark::new("inline", RemarkKind::Passed, "foo", "msg", Some(loc));
let diag = remark.to_diagnostic_string();
assert!(diag.contains("test.c:42:7"));
}
#[test]
fn test_remark_with_args() {
let remark = OptimizationRemark::new("inline", RemarkKind::Passed, "foo", "msg", None)
.with_arg("cost", "5")
.with_arg("threshold", "10");
assert_eq!(remark.args.get("cost"), Some(&"5".to_string()));
assert_eq!(remark.args.get("threshold"), Some(&"10".to_string()));
}
#[test]
fn test_clear() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.emit_remark("p", "f", "m");
assert_eq!(emitter.count(), 1);
emitter.clear();
assert_eq!(emitter.count(), 0);
}
#[test]
fn test_to_diagnostic_string() {
let remark = make_remark(RemarkKind::Passed, "inline");
let diag = remark.to_diagnostic_string();
assert!(diag.starts_with("[P]"));
assert!(diag.contains("inline"));
assert!(diag.contains("test_func"));
}
#[test]
fn test_remark_kind_display() {
assert_eq!(format!("{}", RemarkKind::Passed), "Passed");
assert_eq!(format!("{}", RemarkKind::Missed), "Missed");
assert_eq!(format!("{}", RemarkKind::Analysis), "Analysis");
}
}