Skip to content

Performance benchmarks and optimisations#184

Merged
tomusdrw merged 8 commits into
mainfrom
td-perf2
Feb 28, 2026
Merged

Performance benchmarks and optimisations#184
tomusdrw merged 8 commits into
mainfrom
td-perf2

Conversation

@tomusdrw

Copy link
Copy Markdown
Owner

Top 10 Trace Improvements (PR vs Baseline)

Trace Baseline (ms) main (ms) PR (ms) Improvement
trace-002 30.25 28.90 26.60 +12.1%
trace-007 28.55 28.65 25.92 +9.2%
trace-027 84.62 85.95 77.12 +8.9%
trace-008 83.66 82.42 76.28 +8.8%
trace-019 41.30 40.68 37.68 +8.8%
trace-003 205.10 204.12 187.45 +8.6%
trace-006 71.65 71.42 65.84 +8.1%
trace-001 89.61 91.90 82.49 +8.0%
trace-022 110.46 112.93 101.77 +7.9%
trace-042 41.67 40.37 38.51 +7.6%

Baseline - Pre #178 commit.

@coderabbitai

coderabbitai Bot commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds CI benchmark jobs and local benchmark scripts plus a comparator; changes Gas from signed i64 to unsigned u64 and consolidates GasCounter into a single class; updates VM gas fields and related API signatures to use the new Gas type; replaces some bounds-checked instruction/page accesses with unchecked calls and adds a fast-path and explicit jump handling in the interpreter; removes lazy page initialization and eager allocation flag, introduces a fixed-size PageCache and always-allocate path in Arena; adds a reusable EXTENDED_BUF for program decoding; and small test, lint, and .gitignore adjustments.

Possibly related PRs

  • JS build #178: Also modifies gas types and the Gas/GasCounter implementation and updates VM gas-related initializations and usages.
  • Fix fuzzer #83: Adjusts GasCounter subtraction/underflow semantics (clamp-to-zero behavior), overlapping the consolidated GasCounter behavior changes.
  • Re-execute ecalli traces #160: Changes PVM lifecycle/API surface (pvmResume and VM pause/output types), directly related to the API gas-type signature updates.

Suggested reviewers

  • mateuszsikora
  • DrEverr
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: it introduces benchmarking infrastructure and applies multiple performance optimizations across the codebase.
Description check ✅ Passed The description provides benchmark results showing performance improvements but lacks technical details about the code changes and optimizations implemented.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch td-perf2

Comment @coderabbitai help to get the list of available commands and usage tips.

@tomusdrw
tomusdrw marked this pull request as ready for review February 27, 2026 23:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
assembly/gas.ts (1)

10-14: ⚠️ Potential issue | 🔴 Critical

Change Gas type from i64 to u64 to prevent gas accounting bypass.

The Gas type is signed (i64), allowing external APIs to accept and set negative gas values. When sub() is called with a negative input (e.g., sub(-50) with gas=100), the subtraction increases the counter to 150 rather than decreasing it. The post-operation clamp at line 23 only catches negative results, not the increase case, breaking gas accounting invariants and enabling unlimited gas execution.

APIs expose this directly: vmInit() accepts input.gas: i64 and pvmResume() accepts gas: i64 parameters, both calling set() without validation.

Harden by changing Gas to u64, removing redundant i64() conversions, and reordering sub() to check overflow before subtraction:

Suggested fix
-/** Gas type. */
-export type Gas = i64;
+/** Gas type. */
+export type Gas = u64;

 /** Create a new gas counter instance. */
-export function gasCounter(gas: i64): GasCounter {
+export function gasCounter(gas: Gas): GasCounter {
   return new GasCounter(gas);
 }

 export class GasCounter {
   constructor(private gas: Gas) {}

   set(g: Gas): void {
-    this.gas = i64(g);
+    this.gas = g;
   }

   get(): Gas {
     return this.gas;
   }

   `@inline`
   sub(g: Gas): boolean {
-    this.gas = this.gas - i64(g);
-    if (this.gas < i64(0)) {
-      this.gas = i64(0);
+    if (this.gas < g) {
+      this.gas = 0;
       return true;
     }
+    this.gas -= g;
     return false;
   }
 }

Also update VmInput.gas and VmOutput.gas, VmPause.gas types to u64 in api-types.ts, and pvmResume() signature in api-utils.ts:231.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assembly/gas.ts` around lines 10 - 14, Change the Gas type from signed i64 to
unsigned u64 across the assembly code and public APIs to prevent negative gas
bypass: update the Gas type alias and replace all i64(...) casts in the Gas
class (constructor, set) with u64 usage, remove redundant conversions, and
update the sub() method to check for overflow/underflow before performing
subtraction (i.e., validate requested decrement against current gas and clamp to
zero or return error) so a negative input cannot increase gas. Also update
related API types and signatures to use u64: VmInput.gas, VmOutput.gas,
VmPause.gas in api-types.ts and the pvmResume(...) signature in api-utils.ts
(and any vmInit(...) call sites) so callers cannot pass signed values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/build.yml:
- Line 66: The YAML comments "# Checkout base branch for baseline" and the other
mis-indented comment at the same section need to be indented to match the
surrounding mapping keys so the linter accepts the file; edit the workflow file
to align those comments with the adjacent mapping entries (same indentation
level as the nearest key/step under the job or step block), ensuring both
comment lines are shifted to the correct column to match their related mapping
content.
- Around line 82-86: The benchmark comparison uses inconsistent paths: PR
results are copied to ../results.json while baseline is placed in a different
location, but the "Compare benchmarks" step runs npm run bench:compare
../baseline.json ../results.json which won't find the files; update the workflow
so both files are written/read from the same consistent location (either copy
baseline.json and results.json into the same directory the compare step expects,
or change the bench:compare invocation to the actual paths where baseline and
results were written) and verify the npm script name (bench:compare) remains the
argument order baseline then results.
- Around line 73-77: The workflow step "Run baseline benchmark" is running
benchmarks in ./baseline without building dependencies; update the step to first
install and build the baseline checkout by running npm ci (or npm install) and
npm run build inside ./baseline before running npm run bench:baseline so
bench/run.ts can correctly import ../build/release.js and benchmark the built
artifact.

In `@assembly/gas.ts`:
- Line 5: Change the gasCounter factory signature to use the existing Gas type
alias instead of a raw i64 so the API stays consistent and future-proofs type
changes: update the function declaration for gasCounter(gas: i64): GasCounter to
gasCounter(gas: Gas): GasCounter and ensure any callsites and imports/aliases
referencing Gas are present and compile with the new parameter type.

In `@assembly/memory.ts`:
- Around line 165-173: The loop over the Map iterator uses an untyped loop
variable `i`; change its declaration to a fixed-size type (u32) to match
AssemblyScript conventions and avoid `@ts-ignore` suppression. Specifically, in
the block that gets `const keys = pages.keys();` and iterates `for (let i = 0; i
< keys.length; i++) { const key = keys[i]; this.cache.insert(key,
pages.get(key)); }`, declare `i` as `let i: u32 = 0` (and update the comparison
and increment accordingly if needed) and remove the unnecessary `@ts-ignore`
comments so the code type-checks without suppressions.

In `@bench/compare.ts`:
- Line 108: Guard the percentage calculations against zero denominators by
checking baselineTrace.medianMs (and the other baseline fields used at the other
two sites) before doing the division: compute diffPercent as follows — if
baselineTrace.medianMs === 0 then set diffPercent to 0 when diffMs === 0, or to
Number.POSITIVE_INFINITY/Number.NEGATIVE_INFINITY depending on diffMs sign,
otherwise compute (diffMs / baselineTrace.medianMs) * 100; apply the same
zero-check logic to the analogous calculations at the other two locations (the
computations at lines 160 and 168) so you never divide by zero and downstream
classification/reporting sees a well-defined numeric value.
- Around line 39-40: The parsed threshold (const threshold =
parseFloat(values.threshold!)) must be validated before use: after parsing,
check that threshold is a finite number and not negative (e.g.,
Number.isFinite(threshold) && threshold >= 0); if the value is NaN, infinite, or
negative, emit a clear error message referencing the --threshold arg
(values.threshold) and terminate with a non-zero exit (or throw) so regression
checks don't run with an invalid threshold. Ensure this validation occurs
immediately after parseFloat and before any comparisons that use threshold.

In `@bench/run.ts`:
- Around line 48-50: Parsed iteration values ITERATIONS and WARMUP are not being
validated, which allows NaN/0/negative values to produce an empty samples array
and break median/min/max/p95 computations; validate the parsed integers right
after parsing (ensure ITERATIONS is an integer >= 1 and WARMUP is an integer >=
0 and < ITERATIONS), throw or exit with a clear error if invalid, and
additionally guard the stats computation (where median/min/max/p95 are
calculated from samples) to handle an empty samples array by skipping stats or
returning a clear error so computations never run on empty data.

---

Outside diff comments:
In `@assembly/gas.ts`:
- Around line 10-14: Change the Gas type from signed i64 to unsigned u64 across
the assembly code and public APIs to prevent negative gas bypass: update the Gas
type alias and replace all i64(...) casts in the Gas class (constructor, set)
with u64 usage, remove redundant conversions, and update the sub() method to
check for overflow/underflow before performing subtraction (i.e., validate
requested decrement against current gas and clamp to zero or return error) so a
negative input cannot increase gas. Also update related API types and signatures
to use u64: VmInput.gas, VmOutput.gas, VmPause.gas in api-types.ts and the
pvmResume(...) signature in api-utils.ts (and any vmInit(...) call sites) so
callers cannot pass signed values.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ef04361 and 8f893b0.

⛔ Files ignored due to path filters (63)
  • bench/traces/trace-001.log is excluded by !**/*.log
  • bench/traces/trace-002.log is excluded by !**/*.log
  • bench/traces/trace-003.log is excluded by !**/*.log
  • bench/traces/trace-004.log is excluded by !**/*.log
  • bench/traces/trace-005.log is excluded by !**/*.log
  • bench/traces/trace-006.log is excluded by !**/*.log
  • bench/traces/trace-007.log is excluded by !**/*.log
  • bench/traces/trace-008.log is excluded by !**/*.log
  • bench/traces/trace-009.log is excluded by !**/*.log
  • bench/traces/trace-010.log is excluded by !**/*.log
  • bench/traces/trace-011.log is excluded by !**/*.log
  • bench/traces/trace-012.log is excluded by !**/*.log
  • bench/traces/trace-013.log is excluded by !**/*.log
  • bench/traces/trace-014.log is excluded by !**/*.log
  • bench/traces/trace-015.log is excluded by !**/*.log
  • bench/traces/trace-016.log is excluded by !**/*.log
  • bench/traces/trace-017.log is excluded by !**/*.log
  • bench/traces/trace-018.log is excluded by !**/*.log
  • bench/traces/trace-019.log is excluded by !**/*.log
  • bench/traces/trace-020.log is excluded by !**/*.log
  • bench/traces/trace-021.log is excluded by !**/*.log
  • bench/traces/trace-022.log is excluded by !**/*.log
  • bench/traces/trace-023.log is excluded by !**/*.log
  • bench/traces/trace-024.log is excluded by !**/*.log
  • bench/traces/trace-025.log is excluded by !**/*.log
  • bench/traces/trace-026.log is excluded by !**/*.log
  • bench/traces/trace-027.log is excluded by !**/*.log
  • bench/traces/trace-028.log is excluded by !**/*.log
  • bench/traces/trace-029.log is excluded by !**/*.log
  • bench/traces/trace-030.log is excluded by !**/*.log
  • bench/traces/trace-031.log is excluded by !**/*.log
  • bench/traces/trace-032.log is excluded by !**/*.log
  • bench/traces/trace-033.log is excluded by !**/*.log
  • bench/traces/trace-034.log is excluded by !**/*.log
  • bench/traces/trace-035.log is excluded by !**/*.log
  • bench/traces/trace-036.log is excluded by !**/*.log
  • bench/traces/trace-037.log is excluded by !**/*.log
  • bench/traces/trace-038.log is excluded by !**/*.log
  • bench/traces/trace-039.log is excluded by !**/*.log
  • bench/traces/trace-040.log is excluded by !**/*.log
  • bench/traces/trace-041.log is excluded by !**/*.log
  • bench/traces/trace-042.log is excluded by !**/*.log
  • bench/traces/trace-043.log is excluded by !**/*.log
  • bench/traces/trace-044.log is excluded by !**/*.log
  • bench/traces/trace-045.log is excluded by !**/*.log
  • bench/traces/trace-046.log is excluded by !**/*.log
  • bench/traces/trace-047.log is excluded by !**/*.log
  • bench/traces/trace-048.log is excluded by !**/*.log
  • bench/traces/trace-049.log is excluded by !**/*.log
  • bench/traces/trace-050.log is excluded by !**/*.log
  • bench/traces/trace-051.log is excluded by !**/*.log
  • bench/traces/trace-052.log is excluded by !**/*.log
  • bench/traces/trace-053.log is excluded by !**/*.log
  • bench/traces/trace-054.log is excluded by !**/*.log
  • bench/traces/trace-055.log is excluded by !**/*.log
  • bench/traces/trace-056.log is excluded by !**/*.log
  • bench/traces/trace-057.log is excluded by !**/*.log
  • bench/traces/trace-058.log is excluded by !**/*.log
  • bench/traces/trace-059.log is excluded by !**/*.log
  • bench/traces/trace-060.log is excluded by !**/*.log
  • bench/traces/trace-061.log is excluded by !**/*.log
  • bench/traces/trace-062.log is excluded by !**/*.log
  • bench/traces/trace-063.log is excluded by !**/*.log
📒 Files selected for processing (12)
  • .github/workflows/build.yml
  • assembly/gas.ts
  • assembly/instructions/outcome.ts
  • assembly/interpreter.ts
  • assembly/memory-page.ts
  • assembly/memory.ts
  • assembly/program.ts
  • bench/.gitignore
  • bench/compare.ts
  • bench/run.ts
  • biome.jsonc
  • package.json

Comment thread .github/workflows/build.yml Outdated
Comment thread .github/workflows/build.yml Outdated
Comment thread .github/workflows/build.yml
Comment thread assembly/gas.ts Outdated
Comment thread assembly/memory.ts
Comment thread bench/compare.ts
Comment thread bench/compare.ts Outdated
Comment thread bench/run.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
assembly/api-utils.ts (1)

101-108: 🧹 Nitpick | 🔵 Trivial

Inconsistency: runProgram still uses i64 while pvmResume uses Gas.

For API consistency, consider updating runProgram to use Gas type as well.

 export function runProgram(
   program: StandardProgram,
-  initialGas: i64 = 0,
+  initialGas: Gas = u64(0),
   programCounter: u32 = 0,
   logs: boolean = false,
   useSbrkGas: boolean = false,
   dumpMemory: boolean = false,
 ): VmOutput {
   const vmInput = new VmInput(program.program, program.memory, program.registers);
-  vmInput.gas = i64(initialGas);
+  vmInput.gas = initialGas;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assembly/api-utils.ts` around lines 101 - 108, Update runProgram to accept
and propagate the Gas type instead of i64: change the parameter initialGas's
type from i64 to Gas and ensure VmInput.gas is assigned using that Gas value
(convert only if VmInput.gas expects a different internal representation).
Locate the runProgram function and adjust the signature and the assignment to
vmInput.gas accordingly (referencing runProgram, VmInput, VmOutput, initialGas,
and Gas) so it matches pvmResume's API consistency.
assembly/api-debugger.ts (1)

116-129: ⚠️ Potential issue | 🟡 Minor

Inconsistent return type: getGasLeft returns i64 while setGasLeft accepts Gas (u64).

This asymmetry can cause data loss or unexpected behavior when round-tripping gas values. Since Gas is now u64, the getter should return Gas for consistency.

Proposed fix
-export function getGasLeft(): i64 {
+export function getGasLeft(): Gas {
   if (interpreter === null) {
-    return i64(0);
+    return u64(0);
   }
   const int = <Interpreter>interpreter;
   return int.gas.get();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assembly/api-debugger.ts` around lines 116 - 129, The getter getGasLeft
currently returns i64 while setGasLeft uses Gas (u64), causing an inconsistent
API; change getGasLeft's return type to Gas and have it return the interpreter's
gas via int.gas.get() as a Gas (preserve Interpreter and interpreter usage and
keep setGasLeft unchanged), i.e., update the getGasLeft signature from i64 to
Gas and ensure the returned value is the u64 Gas value returned by
int.gas.get().
♻️ Duplicate comments (1)
.github/workflows/build.yml (1)

79-88: ⚠️ Potential issue | 🔴 Critical

Benchmark artifact paths are still inconsistent and can break comparison.

baseline.json and results.json are written/read from different directories, so bench:compare may fail to locate one of them.

Proposed fix
       - name: Run PR benchmark
         run: |
           npm run bench:ci
-          cp ./bench/results.json ../results.json
+          cp ./bench/results.json ./results.json
@@
       - name: Compare benchmarks
         run: |
-          npm run bench:compare ../baseline.json ../results.json -- --threshold 5
+          npm run bench:compare ./baseline.json ./results.json -- --threshold 5
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build.yml around lines 79 - 88, The workflow writes
baseline.json to the repository root (cp ./bench/baseline.json ../baseline.json)
but reads results.json from a different path in the Compare step, causing
bench:compare to fail; make the artifact paths consistent by producing both
artifacts in the same location before running npm run bench:compare (adjust the
copy in the "Run PR benchmark" step or the paths passed to npm run
bench:compare) so that the filenames baseline.json and results.json referenced
by the "Run PR benchmark" and "Compare benchmarks" steps resolve to the same
directory.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/build.yml:
- Around line 67-71: The checkout step using actions/checkout@v4 currently sets
ref: ${{ github.base_ref }} which uses a moving branch name; change that ref to
the fixed commit SHA from the PR payload by replacing ref: ${{ github.base_ref
}} with ref: ${{ github.event.pull_request.base.sha }} so the baseline checkout
(path: ./baseline) uses an immutable commit for reproducible results.

In `@bench/compare.ts`:
- Around line 172-180: The template literals that print the difference are not
wrapped in console.log, causing syntax errors; locate the block that computes
totalDiffPercent (using totalDiff and baselineSuite.summary.totalTraceMedianMs)
and wrap each standalone template literal/string output in console.log calls
(e.g., replace the bare template literal lines that end with `);` with
console.log(templateString)) so the formatted strings are actually logged and
the stray `);` is removed.

In `@bench/run.ts`:
- Line 219: The call to runProgram(exe, gas, pc, false, false, true) is passing
true for the dumpMemory flag which adds memory-dump overhead to benchmarks;
update the invocation of runProgram (the call site using variables exe, gas, pc)
to pass false for the dumpMemory parameter so the call becomes runProgram(...,
false) for that flag, ensuring accurate benchmark timing without memory dump
overhead.
- Around line 95-121: The benchRun function is missing its closing brace and
return; close the function after computing med/min/max/p95 and return a
BenchResult object (matching the BenchResult shape used elsewhere) e.g. include
name and numeric fields medianMs, minMs, maxMs, p95Ms (values from med, min,
max, p95) so formatResult can consume it; ensure benchRun ends before the
formatResult function declaration and keep references to median(samples) and
percentile(samples, 95) unchanged.

---

Outside diff comments:
In `@assembly/api-debugger.ts`:
- Around line 116-129: The getter getGasLeft currently returns i64 while
setGasLeft uses Gas (u64), causing an inconsistent API; change getGasLeft's
return type to Gas and have it return the interpreter's gas via int.gas.get() as
a Gas (preserve Interpreter and interpreter usage and keep setGasLeft
unchanged), i.e., update the getGasLeft signature from i64 to Gas and ensure the
returned value is the u64 Gas value returned by int.gas.get().

In `@assembly/api-utils.ts`:
- Around line 101-108: Update runProgram to accept and propagate the Gas type
instead of i64: change the parameter initialGas's type from i64 to Gas and
ensure VmInput.gas is assigned using that Gas value (convert only if VmInput.gas
expects a different internal representation). Locate the runProgram function and
adjust the signature and the assignment to vmInput.gas accordingly (referencing
runProgram, VmInput, VmOutput, initialGas, and Gas) so it matches pvmResume's
API consistency.

---

Duplicate comments:
In @.github/workflows/build.yml:
- Around line 79-88: The workflow writes baseline.json to the repository root
(cp ./bench/baseline.json ../baseline.json) but reads results.json from a
different path in the Compare step, causing bench:compare to fail; make the
artifact paths consistent by producing both artifacts in the same location
before running npm run bench:compare (adjust the copy in the "Run PR benchmark"
step or the paths passed to npm run bench:compare) so that the filenames
baseline.json and results.json referenced by the "Run PR benchmark" and "Compare
benchmarks" steps resolve to the same directory.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f55e883 and f803b7d.

📒 Files selected for processing (9)
  • .github/workflows/build.yml
  • assembly/api-debugger.ts
  • assembly/api-types.ts
  • assembly/api-utils.ts
  • assembly/gas.ts
  • assembly/instructions.ts
  • bench/compare.ts
  • bench/run.ts
  • biome.jsonc

Comment thread .github/workflows/build.yml
Comment thread bench/compare.ts
Comment thread bench/run.ts
Comment thread bench/run.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
.github/workflows/build.yml (2)

67-71: ⚠️ Potential issue | 🟠 Major

Use immutable base SHA for reproducible baseline checkout.

Using github.base_ref checks out a moving branch tip, so benchmark baselines can drift between runs. Use the PR payload SHA instead.

Suggested fix
       - name: Checkout base branch for baseline
         uses: actions/checkout@v4
         with:
-          ref: ${{ github.base_ref }}
+          ref: ${{ github.event.pull_request.base.sha }}
           path: ./baseline
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build.yml around lines 67 - 71, Replace the moving ref
currently used in the checkout step named "Checkout base branch for baseline"
(uses: actions/checkout@v4 with ref: ${{ github.base_ref }}) with the immutable
PR payload SHA; change the ref to use github.event.pull_request.base.sha so the
baseline checkout is reproducible across runs and does not drift.

84-88: ⚠️ Potential issue | 🔴 Critical

Benchmark comparison paths are inconsistent and can fail.

At Line 84, PR results are copied to the parent directory, while baseline is written to repo root (from Line 79). Then compare reads both from parent (../...), so baseline path is wrong.

Suggested fix
       - name: Run PR benchmark
         run: |
           npm run bench:ci
-          cp ./bench/results.json ../results.json
+          cp ./bench/results.json ./results.json
       # Compare results
       - name: Compare benchmarks
         run: |
-          npm run bench:compare ../baseline.json ../results.json -- --threshold 5
+          npm run bench:compare ./baseline.json ./results.json -- --threshold 5
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build.yml around lines 84 - 88, The benchmark compare step
is using inconsistent paths between the copy command (cp ./bench/results.json
../results.json) and the compare invocation (npm run bench:compare
../baseline.json ../results.json -- --threshold 5); fix by making both sides use
the same location — either write the PR results to the repo-root or change the
compare arguments to read from the directory where baseline and results actually
reside. Update the cp command or the npm run bench:compare arguments accordingly
so "Compare benchmarks" references the exact same baseline and results file
locations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/build.yml:
- Line 29: The inline comment spacing on the ref line (the line containing "ref:
ba76542dbf7a0c72d414a87ad2e30ce4da380448 # New test vectors.") violates
YAMLlint; update that line to use two spaces before the inline comment so it
reads "ref: ba76542dbf7a0c72d414a87ad2e30ce4da380448  # New test vectors." to
satisfy the linter.

---

Duplicate comments:
In @.github/workflows/build.yml:
- Around line 67-71: Replace the moving ref currently used in the checkout step
named "Checkout base branch for baseline" (uses: actions/checkout@v4 with ref:
${{ github.base_ref }}) with the immutable PR payload SHA; change the ref to use
github.event.pull_request.base.sha so the baseline checkout is reproducible
across runs and does not drift.
- Around line 84-88: The benchmark compare step is using inconsistent paths
between the copy command (cp ./bench/results.json ../results.json) and the
compare invocation (npm run bench:compare ../baseline.json ../results.json --
--threshold 5); fix by making both sides use the same location — either write
the PR results to the repo-root or change the compare arguments to read from the
directory where baseline and results actually reside. Update the cp command or
the npm run bench:compare arguments accordingly so "Compare benchmarks"
references the exact same baseline and results file locations.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f803b7d and 6379542.

📒 Files selected for processing (1)
  • .github/workflows/build.yml

Comment thread .github/workflows/build.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
.github/workflows/build.yml (2)

67-71: ⚠️ Potential issue | 🟠 Major

Use a fixed SHA instead of a branch name for baseline checkout.

${{ github.base_ref }} is a branch name that points to the current HEAD of the base branch. If the base branch advances before this workflow runs, the baseline will differ between runs, making results non-reproducible.

Use ${{ github.event.pull_request.base.sha }} instead, which is a fixed commit SHA from the pull request payload.

Proposed fix
       - name: Checkout base branch for baseline
         uses: actions/checkout@v4
         with:
-          ref: ${{ github.base_ref }}
+          ref: ${{ github.event.pull_request.base.sha }}
           path: ./baseline
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build.yml around lines 67 - 71, The workflow is checking
out the base branch using ref: ${{ github.base_ref }}, which is mutable and
makes baselines non-reproducible; update the checkout step that uses
actions/checkout@v4 (the "Checkout base branch for baseline" step with path:
./baseline) to use the fixed commit SHA from the PR payload by replacing ref:
${{ github.base_ref }} with ref: ${{ github.event.pull_request.base.sha }} and
ensure this job runs on an event that includes the pull_request payload so the
base.sha value is available.

79-92: ⚠️ Potential issue | 🔴 Critical

Benchmark comparison paths are inconsistent and will break the job.

Path analysis:

  • Line 79: Baseline runs in ./baseline, copies to ../baseline.json → ends up at repo root
  • Line 88: PR benchmark runs at repo root, copies to ../results.json → ends up at parent of repo root
  • Line 92: Compare reads ../baseline.json and ../results.json → looks in parent of repo root

The baseline file is written to repo root but the compare step looks for it in the parent directory.

Proposed fix
       - name: Run baseline benchmark
         run: |
           cd ./baseline
           npm ci
           npm run build
           if npm run bench:baseline 2>/dev/null; then
-            cp ./bench/baseline.json ../baseline.json
+            cp ./bench/baseline.json ./baseline.json
           else
             echo "bench:baseline script not found on base branch, skipping comparison"
-            echo '{}' > ../baseline.json
+            echo '{}' > ./baseline.json
           fi
       # Run benchmarks on current PR
       - name: Run PR benchmark
         run: |
           npm run bench:ci
-          cp ./bench/results.json ../results.json
+          cp ./bench/results.json ./results.json
       # Compare results
       - name: Compare benchmarks
         run: |
-          npm run bench:compare ../baseline.json ../results.json -- --threshold 5
+          npm run bench:compare ./baseline/baseline.json ./results.json -- --threshold 5
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build.yml around lines 79 - 92, The benchmark steps use
inconsistent relative paths causing the compare step to miss files; make the
baseline, PR results, and compare all reference the same locations (e.g.,
./baseline.json and ./results.json). Concretely, update the cp in the baseline
branch-check block (currently "cp ./bench/baseline.json ../baseline.json") and
the PR benchmark cp (currently "cp ./bench/results.json ../results.json") so
both write to the same directory (e.g., "cp ./bench/baseline.json
./baseline.json" and "cp ./bench/results.json ./results.json"), and update the
Compare benchmarks invocation (currently "npm run bench:compare ../baseline.json
../results.json -- --threshold 5") to use those same paths ("npm run
bench:compare ./baseline.json ./results.json -- --threshold 5") so the "Run PR
benchmark" and "Compare benchmarks" steps find the files consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.github/workflows/build.yml:
- Around line 67-71: The workflow is checking out the base branch using ref: ${{
github.base_ref }}, which is mutable and makes baselines non-reproducible;
update the checkout step that uses actions/checkout@v4 (the "Checkout base
branch for baseline" step with path: ./baseline) to use the fixed commit SHA
from the PR payload by replacing ref: ${{ github.base_ref }} with ref: ${{
github.event.pull_request.base.sha }} and ensure this job runs on an event that
includes the pull_request payload so the base.sha value is available.
- Around line 79-92: The benchmark steps use inconsistent relative paths causing
the compare step to miss files; make the baseline, PR results, and compare all
reference the same locations (e.g., ./baseline.json and ./results.json).
Concretely, update the cp in the baseline branch-check block (currently "cp
./bench/baseline.json ../baseline.json") and the PR benchmark cp (currently "cp
./bench/results.json ../results.json") so both write to the same directory
(e.g., "cp ./bench/baseline.json ./baseline.json" and "cp ./bench/results.json
./results.json"), and update the Compare benchmarks invocation (currently "npm
run bench:compare ../baseline.json ../results.json -- --threshold 5") to use
those same paths ("npm run bench:compare ./baseline.json ./results.json --
--threshold 5") so the "Run PR benchmark" and "Compare benchmarks" steps find
the files consistently.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6379542 and d894dad.

📒 Files selected for processing (2)
  • .github/workflows/build.yml
  • assembly/gas.ts

@tomusdrw
tomusdrw merged commit 41e43f6 into main Feb 28, 2026
3 of 4 checks passed
@tomusdrw
tomusdrw deleted the td-perf2 branch February 28, 2026 10:42
This was referenced Feb 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant