|
| 1 | +//! A helper script for testing the assembly output. |
| 2 | +//! |
| 3 | +//! Similar to `trybuild` and `compiletest`, except specialized to our setup! |
| 4 | +
|
| 5 | +use cargo_metadata::Message; |
| 6 | +use std::env; |
| 7 | +use std::env::args; |
| 8 | +use std::fmt::Write; |
| 9 | +use std::fs; |
| 10 | +use std::io; |
| 11 | +use std::path::Path; |
| 12 | +use std::process::{Command, Stdio}; |
| 13 | + |
| 14 | +fn strip_lines(data: &str, starts_with: &str) -> String { |
| 15 | + data.lines() |
| 16 | + .filter(|line| !line.trim_start().starts_with(starts_with)) |
| 17 | + .collect::<Vec<_>>() |
| 18 | + .join("\n") |
| 19 | +} |
| 20 | + |
| 21 | +fn strip_section(data: &str, section: &str) -> String { |
| 22 | + let mut res = String::with_capacity(data.len()); |
| 23 | + let mut in_removed_section = false; |
| 24 | + for line in data.lines() { |
| 25 | + // This only works for the __LLVM sections we're interested in |
| 26 | + if line == "" { |
| 27 | + in_removed_section = false; |
| 28 | + } |
| 29 | + if line.trim().starts_with(".section") { |
| 30 | + if line.contains(section) { |
| 31 | + in_removed_section = true; |
| 32 | + write!(res, "; Stripped {section} section\n").unwrap(); |
| 33 | + } else { |
| 34 | + in_removed_section = false; |
| 35 | + } |
| 36 | + } |
| 37 | + if !in_removed_section { |
| 38 | + res.push_str(line); |
| 39 | + res.push('\n'); |
| 40 | + } |
| 41 | + } |
| 42 | + res |
| 43 | +} |
| 44 | + |
| 45 | +fn read_assembly<P: AsRef<Path>>(path: P) -> io::Result<String> { |
| 46 | + let s = fs::read_to_string(path)?; |
| 47 | + let workspace_dir = Path::new(env!("CARGO_MANIFEST_DIR")) |
| 48 | + .parent() |
| 49 | + .unwrap() |
| 50 | + .as_os_str() |
| 51 | + .to_str() |
| 52 | + .unwrap(); |
| 53 | + let s = s.replace(workspace_dir, "$WORKSPACE"); |
| 54 | + let s = strip_lines(&s, ".cfi_"); |
| 55 | + let s = strip_lines(&s, ".macosx_version_"); |
| 56 | + let s = strip_lines(&s, ".ios_version_"); |
| 57 | + let s = strip_lines(&s, ".build_version"); |
| 58 | + // We remove the __LLVM,__bitcode and __LLVM,__cmdline sections because |
| 59 | + // they're uninteresting for out use-case. |
| 60 | + // |
| 61 | + // See https://github.com/rust-lang/rust/blob/1.59.0/compiler/rustc_codegen_llvm/src/back/write.rs#L978-L1074 |
| 62 | + Ok(strip_section(&s, "__LLVM")) |
| 63 | +} |
| 64 | + |
| 65 | +fn main() { |
| 66 | + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); |
| 67 | + let should_overwrite = option_env!("TEST_OVERWRITE").is_some(); |
| 68 | + let host = env!("TARGET"); |
| 69 | + |
| 70 | + for entry in manifest_dir.join("assembly").read_dir().unwrap() { |
| 71 | + let package_path = entry.unwrap().path(); |
| 72 | + let package = package_path.file_name().unwrap().to_str().unwrap(); |
| 73 | + |
| 74 | + println!("Testing {package}."); |
| 75 | + |
| 76 | + let result = Command::new(std::env::var("CARGO").unwrap_or("cargo".into())) |
| 77 | + // .arg("+nightly") |
| 78 | + // .arg("-Zbuild-std") |
| 79 | + // .arg("-vv") |
| 80 | + .arg("rustc") |
| 81 | + .arg(format!("--package={package}")) |
| 82 | + .args(args().skip(2)) |
| 83 | + .arg("--release") |
| 84 | + .arg("--message-format=json-render-diagnostics") |
| 85 | + .arg("--") |
| 86 | + .arg("--emit=asm") |
| 87 | + .arg("-Cllvm-args=--x86-asm-syntax=intel") |
| 88 | + .stdout(Stdio::piped()) |
| 89 | + .stderr(Stdio::inherit()) |
| 90 | + .output() |
| 91 | + .unwrap(); |
| 92 | + |
| 93 | + let artifact = Message::parse_stream(&*result.stdout) |
| 94 | + .find_map(|message| { |
| 95 | + if let Message::CompilerArtifact(artifact) = message.unwrap() { |
| 96 | + // Brittle! |
| 97 | + if artifact.target.name == package && artifact.filenames.len() == 2 { |
| 98 | + let path = artifact.filenames[1].clone(); |
| 99 | + let stem = path.file_stem().unwrap().strip_prefix("lib").unwrap(); |
| 100 | + return Some(path.with_file_name(format!("{stem}.s"))); |
| 101 | + } |
| 102 | + } |
| 103 | + None |
| 104 | + }) |
| 105 | + .unwrap_or_else(|| { |
| 106 | + panic!( |
| 107 | + "Could not find package data:\n{}", |
| 108 | + String::from_utf8_lossy(&result.stdout) |
| 109 | + ) |
| 110 | + }); |
| 111 | + |
| 112 | + // Very brittle! |
| 113 | + let target = artifact |
| 114 | + .components() |
| 115 | + .map(|component| component.as_str()) |
| 116 | + .skip_while(|&component| component != "target") |
| 117 | + .skip(1) |
| 118 | + .next() |
| 119 | + .unwrap_or(host); |
| 120 | + |
| 121 | + println!("Target {target}."); |
| 122 | + |
| 123 | + let expected_file = package_path.join("expected").join(format!("{target}.s")); |
| 124 | + |
| 125 | + let actual = read_assembly(&artifact).unwrap(); |
| 126 | + if should_overwrite { |
| 127 | + fs::write(expected_file, actual).unwrap(); |
| 128 | + } else if let Ok(expected) = read_assembly(expected_file) { |
| 129 | + if expected != actual { |
| 130 | + eprintln!("\n===Expected===\n{}\n===Actual===\n{}", expected, actual); |
| 131 | + panic!("Expected and actual did not match."); |
| 132 | + } |
| 133 | + } else { |
| 134 | + panic!("Missing assembly output for target {}:\n{}", target, actual); |
| 135 | + } |
| 136 | + } |
| 137 | +} |
0 commit comments