diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 96af1da7..cd9f21a9 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -14,6 +14,28 @@ concurrency: cancel-in-progress: true jobs: + codspeed: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Install cargo-codspeed + uses: taiki-e/install-action@v2 + with: + tool: cargo-codspeed + - name: Build the benchmark target(s) + run: cargo codspeed build --profile profiling -p solar-bench criterion + - name: Run the benchmarks + uses: CodSpeedHQ/action@v3 + with: + run: cargo codspeed run -p solar-bench criterion + token: ${{ secrets.CODSPEED_TOKEN }} + iai: runs-on: ubuntu-latest env: diff --git a/Cargo.lock b/Cargo.lock index e8b0fad3..7d1655e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -568,6 +568,28 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "codspeed" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "450a0e9df9df1c154156f4344f99d8f6f6e69d0fc4de96ef6e2e68b2ec3bce97" +dependencies = [ + "colored", + "libc", + "serde_json", +] + +[[package]] +name = "codspeed-criterion-compat" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb1a6cb9c20e177fde58cdef97c1c7c9264eb1424fe45c4fccedc2fb078a569" +dependencies = [ + "codspeed", + "colored", + "criterion", +] + [[package]] name = "color-eyre" version = "0.6.3" @@ -2545,7 +2567,7 @@ dependencies = [ name = "solar-bench" version = "0.1.0" dependencies = [ - "criterion", + "codspeed-criterion-compat", "iai-callgrind", "paste", "semver 1.0.23", diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 45ea57e3..ab5e0e36 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -22,7 +22,7 @@ slang_solidity = "=0.18.3" semver.workspace = true [dev-dependencies] -criterion = "0.5" +criterion = { package = "codspeed-criterion-compat", version = "2.7" } iai-callgrind = "0.14" paste.workspace = true @@ -30,8 +30,8 @@ paste.workspace = true ci = [] [[bench]] -name = "bench" -path = "benches/bench.rs" +name = "criterion" +path = "benches/criterion.rs" harness = false [[bench]] diff --git a/benches/README.md b/benches/README.md index b392bd5f..10a32c48 100644 --- a/benches/README.md +++ b/benches/README.md @@ -8,7 +8,7 @@ Run with: ```bash # Criterion -cargo criterion -p solar-bench --bench bench -- --quiet --format terse |& tee benches/tables.in +cargo criterion -p solar-bench --bench criterion -- --quiet --format terse |& tee benches/tables.in ./benches/tables.py ./benches/README.md < benches/tables.in # iai - requires `valgrind` and `iai-callgrind-runner` @@ -20,6 +20,8 @@ invoking other commands such as `cargo test`. ## Results +You view the Solar-only results on [codspeed.io](https://codspeed.io/paradigmxyz/solar). + The following results were achieved on: - Target: `x86_64-unknown-linux-gnu` - CPU: AMD Ryzen 9 7950X; diff --git a/benches/benches/bench.rs b/benches/benches/criterion.rs similarity index 67% rename from benches/benches/bench.rs rename to benches/benches/criterion.rs index cbaa1419..551fa31b 100644 --- a/benches/benches/bench.rs +++ b/benches/benches/criterion.rs @@ -18,12 +18,17 @@ fn parser_benches(c: &mut Criterion) { for &Source { name: sname, path: _, src } in get_srcs() { for &parser in PARSERS { let pname = parser.name(); + let mk_id = |id: &str| { + if PARSERS.len() == 1 { + format!("{sname}/{id}") + } else { + format!("{sname}/{pname}/{id}") + } + }; if parser.can_lex() { - let id = format!("{sname}/{pname}/lex"); - g.bench_function(id, |b| b.iter(|| parser.lex(src))); + g.bench_function(mk_id("lex"), |b| b.iter(|| parser.lex(src))); } - let id = format!("{sname}/{pname}/parse"); - g.bench_function(id, |b| b.iter(|| parser.parse(src))); + g.bench_function(mk_id("parse"), |b| b.iter(|| parser.parse(src))); } eprintln!(); } diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 7bef4701..b00d18fe 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -6,7 +6,9 @@ use std::{ process::Stdio, }; -pub const PARSERS: &[&dyn Parser] = &[&Solc, &Solar, &Solang, &Slang]; +#[allow(unexpected_cfgs)] +pub const PARSERS: &[&dyn Parser] = + if cfg!(codspeed) { &[&Solar] } else { &[&Solc, &Solar, &Solang, &Slang] }; pub fn get_srcs() -> &'static [Source] { static CACHE: std::sync::OnceLock> = std::sync::OnceLock::new(); diff --git a/crates/ast/src/ast/item.rs b/crates/ast/src/ast/item.rs index 34490f3f..0f0b505a 100644 --- a/crates/ast/src/ast/item.rs +++ b/crates/ast/src/ast/item.rs @@ -391,7 +391,7 @@ pub struct ItemFunction<'ast> { } impl ItemFunction<'_> { - /// Returns `true` if the function is implemented + /// Returns `true` if the function is implemented. pub fn is_implemented(&self) -> bool { self.body.is_some() } diff --git a/crates/sema/src/ast_passes.rs b/crates/sema/src/ast_passes.rs index f80e6d31..9bd962fc 100644 --- a/crates/sema/src/ast_passes.rs +++ b/crates/sema/src/ast_passes.rs @@ -19,24 +19,24 @@ pub fn validate(sess: &Session, ast: &ast::SourceUnit<'_>) { /// AST validator. struct AstValidator<'sess, 'ast> { - span: Span, + item_span: Span, dcx: &'sess DiagCtxt, contract: Option<&'ast ast::ItemContract<'ast>>, function_kind: Option, in_unchecked_block: bool, - in_loop_depth: u64, - placeholder_count: u64, + loop_depth: u32, + placeholder_count: u32, } impl<'sess> AstValidator<'sess, '_> { fn new(sess: &'sess Session) -> Self { Self { - span: Span::DUMMY, + item_span: Span::DUMMY, dcx: &sess.dcx, contract: None, function_kind: None, in_unchecked_block: false, - in_loop_depth: 0, + loop_depth: 0, placeholder_count: 0, } } @@ -48,7 +48,7 @@ impl<'sess> AstValidator<'sess, '_> { } fn in_loop(&self) -> bool { - self.in_loop_depth != 0 + self.loop_depth != 0 } fn check_single_statement_variable_declaration(&self, stmt: &ast::Stmt<'_>) { @@ -98,7 +98,7 @@ impl<'ast> Visit<'ast> for AstValidator<'_, 'ast> { type BreakValue = Never; fn visit_item(&mut self, item: &'ast ast::Item<'ast>) -> ControlFlow { - self.span = item.span; + self.item_span = item.span; self.walk_item(item) } @@ -147,15 +147,15 @@ impl<'ast> Visit<'ast> for AstValidator<'_, 'ast> { ("experimental", Some("SMTChecker")) => {} ("experimental", Some("solidity")) => { let msg = "experimental solidity features are not supported"; - self.dcx().err(msg).span(self.span).emit(); + self.dcx().err(msg).span(self.item_span).emit(); } _ => { - self.dcx().err("unknown pragma").span(self.span).emit(); + self.dcx().err("unknown pragma").span(self.item_span).emit(); } } } ast::PragmaTokens::Verbatim(_) => { - self.dcx().err("unknown pragma").span(self.span).emit(); + self.dcx().err("unknown pragma").span(self.item_span).emit(); } } ControlFlow::Continue(()) @@ -166,10 +166,10 @@ impl<'ast> Visit<'ast> for AstValidator<'_, 'ast> { ast::StmtKind::While(_, body) | ast::StmtKind::DoWhile(body, _) | ast::StmtKind::For { body, .. } => { - self.in_loop_depth += 1; + self.loop_depth += 1; self.check_single_statement_variable_declaration(body); let r = self.walk_stmt(stmt); - self.in_loop_depth -= 1; + self.loop_depth -= 1; return r; } ast::StmtKind::If(_cond, then, else_) => { @@ -263,12 +263,12 @@ impl<'ast> Visit<'ast> for AstValidator<'_, 'ast> { if contract.kind.is_interface() && !func.header.modifiers.is_empty() { self.dcx() .err("functions in interfaces cannot have modifiers") - .span(self.span) + .span(self.item_span) .emit(); } else if !func.is_implemented() && !func.header.modifiers.is_empty() { self.dcx() .err("functions without implementation cannot have modifiers") - .span(self.span) + .span(self.item_span) .emit(); } } @@ -277,14 +277,14 @@ impl<'ast> Visit<'ast> for AstValidator<'_, 'ast> { if self.contract.is_some_and(|c| c.kind.is_library()) { self.dcx() .err("libraries cannot have receive ether functions") - .span(self.span) + .span(self.item_span) .emit(); } if !func.header.state_mutability.is_payable() { self.dcx() .err("receive ether function must be payable") - .span(self.span) + .span(self.item_span) .help("add `payable` state mutability") .emit(); } @@ -292,7 +292,7 @@ impl<'ast> Visit<'ast> for AstValidator<'_, 'ast> { if !func.header.parameters.is_empty() { self.dcx() .err("receive ether function cannot take parameters") - .span(self.span) + .span(self.item_span) .emit(); } } @@ -308,7 +308,7 @@ impl<'ast> Visit<'ast> for AstValidator<'_, 'ast> { } { self.dcx() .err("no visibility specified") - .span(self.span) + .span(self.item_span) .help(format!("add `{suggested_visibility}` to the declaration")) .emit(); } @@ -317,12 +317,12 @@ impl<'ast> Visit<'ast> for AstValidator<'_, 'ast> { if self.contract.is_none() && func.kind.is_function() { if !func.is_implemented() { - self.dcx().err("free functions must be implemented").span(self.span).emit(); + self.dcx().err("free functions must be implemented").span(self.item_span).emit(); } if let Some(visibility) = func.header.visibility { self.dcx() .err("free functions cannot have visibility") - .span(self.span) + .span(self.item_span) .help(format!("remove `{visibility}` from the declaration")) .emit(); } @@ -355,23 +355,23 @@ impl<'ast> Visit<'ast> for AstValidator<'_, 'ast> { if self.contract.is_none() && !with_typ { self.dcx() .err("the type has to be specified explicitly at file level (cannot use `*`)") - .span(self.span) + .span(self.item_span) .emit(); } if *global && !with_typ { self.dcx() .err("can only globally attach functions to specific types") - .span(self.span) + .span(self.item_span) .emit(); } if *global && self.contract.is_some() { - self.dcx().err("`global` can only be used at file level").span(self.span).emit(); + self.dcx().err("`global` can only be used at file level").span(self.item_span).emit(); } if let Some(contract) = self.contract { if contract.kind.is_interface() { self.dcx() .err("the `using for` directive is not allowed inside interfaces") - .span(self.span) + .span(self.item_span) .emit(); } }