Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
* text=auto eol=lf
*.sol linguist-language=Solidity
*.yul linguist-language=Yul
*.mir linguist-language=Rust
*.evmir linguist-language=Assembly
/tests/ui/codegen/mir/**/*.stdout linguist-language=Diff whitespace=-trailing-space
/tests/ui/codegen/mir/pipeline/**/*.stdout linguist-language=Rust whitespace=trailing-space
/tests/ui/codegen/mir/pipeline/pipeline.stdout linguist-language=Diff whitespace=-trailing-space
/tests/ui/codegen/evm-ir/**/*.stdout linguist-language=Diff whitespace=-trailing-space
/testdata/* linguist-vendored
/testdata/**/* linguist-vendored
/tests/foundry/** linguist-vendored
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ criterion = { package = "codspeed-criterion-compat", version = "4", default-feat
"cargo_bench_support",
] }
regex = "1.10"
diff = "0.1"
snapbox = { version = "1.2", features = ["json"] }
tempfile = "3.9"

Expand Down
17 changes: 13 additions & 4 deletions crates/cli/src/commands/evm_opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
//! resulting EVM IR.
//!
//! This is the backend-IR equivalent of `solar mir-opt`. It currently accepts
//! EVM IR files (`.evmir`) and prints the canonical parser/printer output.
//! EVM IR files (`.evmir`) and prints the canonical parser/printer output. With
//! `-Zpass-diff`, it instead prints a line-oriented before-and-after diff for
//! each pass.

use super::print_pass_diff;
use clap::ValueHint;
use solar_codegen::backend::evm::ir;
use solar_config::CompileOpts;
Expand Down Expand Up @@ -78,16 +81,22 @@ fn run_pipeline(sess: &Session, module: &mut ir::Module, name: &str, args: &EvmO
};
let pipeline_label = selected_pass_list_label(&args.passes, ",");
for (index, &pass) in args.passes.iter().enumerate() {
let before = sess.opts.unstable.pass_diff.then(|| module.to_text().to_string());
if let Some(pass) = pass {
ir::run_pass(module, pass, options);
}
if args.print_after_each || index + 1 == args.passes.len() {
if before.is_some() || args.print_after_each || index + 1 == args.passes.len() {
ir::validate(dcx, module);
if dcx.has_errors().is_err() {
break;
}
let label = if args.print_after_each { pass_label(pass) } else { &pipeline_label };
print_module(module, name, label);
if let Some(before) = before {
let after = module.to_text().to_string();
print_pass_diff(name, pass_label(pass), &before, &after);
} else {
let label = if args.print_after_each { pass_label(pass) } else { &pipeline_label };
print_module(module, name, label);
}
}
}
}
Expand Down
46 changes: 35 additions & 11 deletions crates/cli/src/commands/mir_opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
//! This is the Solar equivalent of LLVM's `opt`. It accepts either a Solidity
//! file (`.sol`) — which is parsed, lowered to MIR, and then transformed — or a
//! textual MIR file (`.mir`) — which is parsed directly. After running the
//! requested pass pipeline, it prints the resulting MIR.
//! requested pass pipeline, it prints the resulting MIR. With `-Zpass-diff`,
//! it instead prints a line-oriented before-and-after diff for each pass.
//!
//! It is an unstable, internal tool used by the `Mir` test mode; it is not part
//! of the stable CLI surface.

use super::print_pass_diff;
use clap::ValueHint;
use solar_codegen::{
lower,
Expand Down Expand Up @@ -121,15 +123,15 @@ fn selected_pass_list_label(passes: &[Option<&PassInfo>], separator: &str) -> St
passes.iter().copied().map(pass_label).format(separator).to_string()
}

/// Prints a module with a header indicating which pass(es) produced it.
fn print_module(module: &Module, name: &str, after: &str) {
println!("// === {name} (after {after}) ===");
print!("{}", module.to_text());
}

/// Runs the pass pipeline on a single module and emits output.
/// Used for both .sol contracts and .mir input.
fn run_pipeline(module: &mut Module, name: &str, args: &MirOptArgs, time_passes: bool) {
fn run_pipeline(
module: &mut Module,
name: &str,
args: &MirOptArgs,
pass_diff: bool,
time_passes: bool,
) {
if args.pipeline_default {
let mut options = PipelineOptions::default();
options.print_after_each = args.print_after_each;
Expand All @@ -146,16 +148,26 @@ fn run_pipeline(module: &mut Module, name: &str, args: &MirOptArgs, time_passes:
options.time_passes = time_passes;
let pipeline_label = args.pipeline_label(&passes);
for (index, &pass) in passes.iter().enumerate() {
let before = pass_diff.then(|| module.to_text().to_string());
if let Some(pass) = pass {
run_pass(module, pass, options);
}
if args.print_after_each || index + 1 == passes.len() {
if let Some(before) = before {
let after = module.to_text().to_string();
print_pass_diff(name, pass_label(pass), &before, &after);
} else if args.print_after_each || index + 1 == passes.len() {
let label = if args.print_after_each { pass_label(pass) } else { &pipeline_label };
print_module(module, name, label);
}
}
}

/// Prints a module with a header indicating which pass(es) produced it.
fn print_module(module: &Module, name: &str, after: &str) {
println!("// === {name} (after {after}) ===");
print!("{}", module.to_text());
}

/// Process a `.mir` input: read file, parse, run passes, print.
fn process_mir(sess: &Session, args: &MirOptArgs) -> solar_interface::Result {
let source = sess
Expand All @@ -167,7 +179,13 @@ fn process_mir(sess: &Session, args: &MirOptArgs) -> solar_interface::Result {
// diagnostic instead of tripping the post-pass validator ICE.
validate(&sess.dcx, &module);
if sess.dcx.has_errors().is_ok() {
run_pipeline(&mut module, &args.input, args, sess.opts.unstable.time_passes);
run_pipeline(
&mut module,
&args.input,
args,
sess.opts.unstable.pass_diff,
sess.opts.unstable.time_passes,
);
}
Ok(())
}
Expand All @@ -191,7 +209,13 @@ fn process_sol(compiler: &mut CompilerRef<'_>, args: &MirOptArgs) -> solar_inter
}
let mut module = lower::lower_contract(gcx, id);
let name = gcx.contract_fully_qualified_name(id).to_string();
run_pipeline(&mut module, &name, args, gcx.sess.opts.unstable.time_passes);
run_pipeline(
&mut module,
&name,
args,
gcx.sess.opts.unstable.pass_diff,
gcx.sess.opts.unstable.time_passes,
);
}
Ok(())
}
Expand Down
7 changes: 7 additions & 0 deletions crates/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! CLI command runners.

use crate::args::{Args, Subcommands};
use solar_data_structures::fmt::line_diff;
use std::process::ExitCode;

pub mod compile;
Expand All @@ -9,6 +10,12 @@ pub(crate) mod evm_opt;
mod lsp;
pub(crate) mod mir_opt;

fn print_pass_diff(name: &str, pass: &str, before: &str, after: &str) {
let before = format!("// === {name} (before {pass}) ===\n{before}");
let after = format!("// === {name} (after {pass}) ===\n{after}");
print!("{}", line_diff(&before, &after));
}

pub(crate) fn run(args: Args) -> ExitCode {
let Args { commands, compile } = args;
match commands {
Expand Down
4 changes: 4 additions & 0 deletions crates/config/src/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,10 @@ pub struct UnstableOpts {
#[cfg_attr(feature = "clap", arg(long))]
pub mir_print_after_each: bool,

/// Print a before-and-after diff for each pass explicitly selected by `mir-opt` or `evm-opt`.
#[cfg_attr(feature = "clap", arg(long))]
pub pass_diff: bool,

/// Print the time spent in each MIR and EVM IR pass.
#[cfg_attr(feature = "clap", arg(long))]
pub time_passes: bool,
Expand Down
1 change: 1 addition & 0 deletions crates/data-structures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ workspace = true

[dependencies]
bumpalo.workspace = true
diff.workspace = true
oxc_index.workspace = true
indexmap.workspace = true
parking_lot.workspace = true
Expand Down
19 changes: 19 additions & 0 deletions crates/data-structures/src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ use std::{

pub use fmt::*;

/// Formats a line-oriented diff with the same prefixes as rustc MIR tests.
pub fn line_diff<'a>(before: &'a str, after: &'a str) -> impl fmt::Display + 'a {
from_fn(move |f| {
for result in diff::lines(before, after) {
match result {
diff::Result::Left(line) => writeln!(f, "- {line}")?,
diff::Result::Right(line) => writeln!(f, "+ {line}")?,
diff::Result::Both(line, _) => writeln!(f, " {line}")?,
}
}
Ok(())
})
}

/// Creates a formatter from a function.
pub fn from_fn<F>(f: F) -> FromFn<F>
where
Expand Down Expand Up @@ -156,4 +170,9 @@ mod tests {
let formatted = values.iter().format_with(" | ", |f, value| write!(f, "#{value}"));
assert_eq!(formatted.to_string(), "#1 | #2 | #3");
}

#[test]
fn test_line_diff() {
assert_eq!(line_diff("a\nb\n", "a\nc\n").to_string(), " a\n- b\n+ c\n \n");
}
}
3 changes: 3 additions & 0 deletions tests/ui/cli/Zhelp.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ Options:
-Zmir-print-after-each
Print MIR after every MIR optimization pass during codegen

-Zpass-diff
Print a before-and-after diff for each pass explicitly selected by `mir-opt` or `evm-opt`

-Ztime-passes
Print the time spent in each MIR and EVM IR pass

Expand Down
30 changes: 17 additions & 13 deletions tests/ui/codegen/evm-ir/block-layout/block_layout.stdout
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
// === ROOT/tests/ui/codegen/evm-ir/block-layout/block_layout.evmir (after block-layout) ===
@module module
bb0 (entry):
push 1
push bb1
jumpi
jump bb2
bb2:
jump bb3
bb3:
stop
bb1:
stop
- // === ROOT/tests/ui/codegen/evm-ir/block-layout/block_layout.evmir (before block-layout) ===
+ // === ROOT/tests/ui/codegen/evm-ir/block-layout/block_layout.evmir (after block-layout) ===
@module module
bb0 (entry):
push 1
push bb1
jumpi
jump bb2
- bb1:
- stop
bb2:
jump bb3
bb3:
+ stop
+ bb1:
stop

24 changes: 14 additions & 10 deletions tests/ui/codegen/evm-ir/block-layout/block_layout_cold.stdout
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// === ROOT/tests/ui/codegen/evm-ir/block-layout/block_layout_cold.evmir (after block-layout) ===
@module module
bb0 (entry):
jump bb2
bb2:
stop
bb1 [cold]:
push 0
push 0
revert
- // === ROOT/tests/ui/codegen/evm-ir/block-layout/block_layout_cold.evmir (before block-layout) ===
+ // === ROOT/tests/ui/codegen/evm-ir/block-layout/block_layout_cold.evmir (after block-layout) ===
@module module
bb0 (entry):
jump bb2
+ bb2:
+ stop
bb1 [cold]:
push 0
push 0
revert
- bb2:
- stop

Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
// === ROOT/tests/ui/codegen/evm-ir/block-layout/structural_jumpi_fallthrough.evmir (after block-layout) ===
@module module
bb0 (entry):
push bb3
pop
push bb3
pop
push 1
jumpi bb2, bb1
bb1:
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffff
stop
bb3:
invalid
bb2:
stop
- // === ROOT/tests/ui/codegen/evm-ir/block-layout/structural_jumpi_fallthrough.evmir (before block-layout) ===
+ // === ROOT/tests/ui/codegen/evm-ir/block-layout/structural_jumpi_fallthrough.evmir (after block-layout) ===
@module module
bb0 (entry):
push bb3
pop
push bb3
pop
push 1
jumpi bb2, bb1
bb1:
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
push 0xffffffffffff
stop
- bb2:
- stop
bb3:
invalid
+ bb2:
+ stop

24 changes: 14 additions & 10 deletions tests/ui/codegen/evm-ir/block-layout/time_passes.stdout
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// === ROOT/tests/ui/codegen/evm-ir/block-layout/time_passes.evmir (after block-layout) ===
@module module
bb0 (entry):
jump bb2
bb2:
stop
bb1 [cold]:
push 0
push 0
revert
- // === ROOT/tests/ui/codegen/evm-ir/block-layout/time_passes.evmir (before block-layout) ===
+ // === ROOT/tests/ui/codegen/evm-ir/block-layout/time_passes.evmir (after block-layout) ===
@module module
bb0 (entry):
jump bb2
+ bb2:
+ stop
bb1 [cold]:
push 0
push 0
revert
- bb2:
- stop

Loading
Loading