From f6680132b503b4b54dcc9c83b5f62dbf555e49c6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 08:31:38 +0000 Subject: [PATCH 1/2] perf(ir): optimize block predecessor mapping from O(N^2) to O(N) Co-authored-by: Tcode-Motion <188012755+Tcode-Motion@users.noreply.github.com> --- .jules/bolt.md | 3 ++ Cargo.lock | 61 +++++++++++++++++++++++++++- compiler/ir/Cargo.toml | 5 +++ compiler/ir/benches/builder_bench.rs | 38 +++++++++++++++++ compiler/ir/src/builder.rs | 16 ++++---- 5 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 compiler/ir/benches/builder_bench.rs diff --git a/.jules/bolt.md b/.jules/bolt.md index 5034e016..4478fbf6 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -20,3 +20,6 @@ ## 2024-08-01 - Bytecode Disassembler String Allocation Optimization **Learning:** Formatting directly into a string buffer inside a tight loop with `write!(buffer, ...)` avoids unnecessary string heap allocations compared to `buffer.push_str(&format!(...))`. **Action:** Always prefer formatting directly into the target String buffer when concatenating strings in loops in performance-sensitive paths like debuggers or disassemblers. +## 2024-03-24 - Bolt: O(N) block predecessor mapping optimization +**Learning:** O(N^2) behavior in compiler IR graph traversals (`contains` calls inside loops) can severely degrade performance on large inputs. Grouping by target first into a `HashMap` reduces it to O(N). +**Action:** When reconstructing graph edges (predecessors/successors) or processing lists against each other, group items with a HashMap or IndexMap instead of using nested loops. diff --git a/Cargo.lock b/Cargo.lock index aa72fe2b..410920a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1047,6 +1047,32 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot 0.5.0", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + [[package]] name = "criterion" version = "0.8.2" @@ -1058,7 +1084,7 @@ dependencies = [ "cast", "ciborium", "clap", - "criterion-plot", + "criterion-plot 0.8.2", "itertools 0.13.0", "num-traits", "oorandom", @@ -1072,6 +1098,16 @@ dependencies = [ "walkdir", ] +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "criterion-plot" version = "0.8.2" @@ -2033,12 +2069,32 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -3876,6 +3932,7 @@ dependencies = [ name = "techscript_ir" version = "2.0.0" dependencies = [ + "criterion 0.5.1", "serde", "techscript_ast", "techscript_common", @@ -3928,7 +3985,7 @@ dependencies = [ name = "techscript_lsp" version = "2.0.0" dependencies = [ - "criterion", + "criterion 0.8.2", "serde", "serde_json", "techscript_ast", diff --git a/compiler/ir/Cargo.toml b/compiler/ir/Cargo.toml index 862a54bf..8bc0f91d 100644 --- a/compiler/ir/Cargo.toml +++ b/compiler/ir/Cargo.toml @@ -16,3 +16,8 @@ serde = { workspace = true } techscript_lexer = { path = "../lexer" } techscript_parser = { path = "../parser" } techscript_semantic = { path = "../semantic" } +criterion = "0.5" + +[[bench]] +name = "builder_bench" +harness = false diff --git a/compiler/ir/benches/builder_bench.rs b/compiler/ir/benches/builder_bench.rs new file mode 100644 index 00000000..070e8b16 --- /dev/null +++ b/compiler/ir/benches/builder_bench.rs @@ -0,0 +1,38 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use techscript_ir::builder::IRBuilder; +use techscript_ir::types::IRType; + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("seal_function 10000 blocks", |b| { + b.iter(|| { + let mut builder = IRBuilder::new(); + builder.start_function("bench_func".to_string(), IRType::Void); + + let num_blocks = 10000; + let mut blocks = vec![]; + for i in 0..num_blocks { + let block_id = builder.new_block(format!("block_{}", i)); + blocks.push(block_id); + } + + for i in 0..(num_blocks - 1) { + builder.enter_block(blocks[i], format!("block_{}", i)); + builder.emit_terminator( + techscript_ir::instruction::TerminatorKind::Jump(blocks[i + 1]), + techscript_common::Span { start: 0, end: 0 }, + ); + } + + builder.enter_block(blocks[num_blocks - 1], format!("block_{}", num_blocks - 1)); + builder.emit_terminator( + techscript_ir::instruction::TerminatorKind::Return(None), + techscript_common::Span { start: 0, end: 0 }, + ); + + builder.seal_function(); + }); + }); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/compiler/ir/src/builder.rs b/compiler/ir/src/builder.rs index b7ebaf52..8274ee9d 100644 --- a/compiler/ir/src/builder.rs +++ b/compiler/ir/src/builder.rs @@ -263,15 +263,15 @@ impl IRBuilder { } // Map predecessors - for i in 0..num_blocks { - let block_id = func.blocks[i].id; - let mut preds = Vec::new(); - for other in &func.blocks { - if other.successors.contains(&block_id) { - preds.push(other.id); - } + let mut pred_map = HashMap::new(); + for block in &func.blocks { + for &succ in &block.successors { + pred_map.entry(succ).or_insert_with(Vec::new).push(block.id); } - func.blocks[i].predecessors = preds; + } + + for block in &mut func.blocks { + block.predecessors = pred_map.remove(&block.id).unwrap_or_default(); } self.functions.push(func); From df19764d7570c076e65ed85e73e23bcbfd8ebdd7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 08:37:40 +0000 Subject: [PATCH 2/2] perf(ir): fix benchmark import to resolve unused_import warning Co-authored-by: Tcode-Motion <188012755+Tcode-Motion@users.noreply.github.com> --- compiler/ir/benches/builder_bench.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/ir/benches/builder_bench.rs b/compiler/ir/benches/builder_bench.rs index 070e8b16..3a1c5864 100644 --- a/compiler/ir/benches/builder_bench.rs +++ b/compiler/ir/benches/builder_bench.rs @@ -1,4 +1,4 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{criterion_group, criterion_main, Criterion}; use techscript_ir::builder::IRBuilder; use techscript_ir::types::IRType;