Reproducing Solar's 100× Solidity Compilation Benchmark, Stage by Stage

Reproducing Solar's 100× Solidity Compilation Benchmark, Stage by Stage

pr0h0••25 min read•coding
AI Usage (90%)
On this page

Introduction

This post is a stage-by-stage guide to reproducing the claim that Solar, a new Solidity compiler, compiles up to 100× faster than solc — and to deciding which Solidity workloads actually benefit. A compiler that claims 100× over the reference Ethereum compiler is either a real engineering event or a measurement that changed shape halfway through. Both happen. The difference matters to anyone who has watched a Foundry build eat four minutes of a CI runner.

The public detail here is thin. A Paradigm Policy item, published 2 November 2026 under the headline "Solar: 100× faster Solidity," describes Solar as a new Solidity compiler and puts its speedup at up to 100×, framing compilation speed as a developer-experience lever for smart-contract workflows. That is what the source says. It does not publish a methodology, a corpus, a phase breakdown, or a bytecode-equivalence check — at least not in anything I can verify from the material I have.

So this post does two things. First, it separates what is confirmed from what is inference and what is simply unknown, and refuses to blur them. Second, it gives you the harness I would actually run before quoting a number like this in a slide deck: a phase map, a pinned environment, a corpus design, per-phase instrumentation, and the correctness gates that decide whether a speedup is worth anything at all.

My position up front: "100×" is almost certainly not a per-phase compiler constant. It is a composite of four different measurements — pipeline stage, optimization flag, warm-vs-cold state, and serial-vs-parallel execution — and at least one of those is usually an apples-to-oranges comparison. That is a falsifiable claim. The rest of this post is how you falsify it.

What the Solar "100× Faster Solidity" Source Claims — and What It Leaves Out

The headline, its publisher, and the missing benchmark methodology

The available source is a single item: publisher Paradigm Policy, published 2026-11-02, headline "Solar: 100× faster Solidity," summary asserting up to 100× faster compilation by Solar, a new Solidity compiler. The snippet is one line. There is no linked benchmark repository, no corpus description, no solc baseline version, and no statement of whether the comparison is end-to-end build time or a single pipeline phase.

That absence is not damning — marketing pages are not papers — but it means the number is currently unfalsifiable by a reader, which is exactly the condition under which compiler benchmarks go wrong.

⚠️

Do not repeat a headline number as if it were a measured property of a tool. A speedup without a baseline version, a corpus, and a flag set is a claim about a benchmark run, not about a compiler.

Splitting the claim into confirmed, inferred, and unknown

StatusStatementBasis
ConfirmedA compiler called Solar exists publicly and is described as a Solidity compilerSource item text
ConfirmedThe claim attributed to it is "up to 100× faster" compilationSource item text
ConfirmedPublisher is Paradigm Policy; item dated 2026-11-02Source item metadata
InferredSolar is likely implemented in Rust and ships as a native CLI binaryEcosystem framing around the project name; not stated in the source I have
InferredThe 100× figure is likely a best-case comparison, not an average across a project build"up to" phrasing plus normal benchmark reporting practice
UnknownWhich solc version is the baselineNot in source
UnknownWhether optimization was enabled in either compilerNot in source
UnknownCorpus size, contract complexity, source-unit countNot in source
UnknownWhether the 100× is cold single-file, warm single-file, or whole-project parallelNot in source
UnknownWhether emitted bytecode was verified equivalentNot in source

Every Unknown row has to be pinned down before you can reproduce anything. If you cannot pin it, you cannot reproduce it — you can only re-benchmark and report your own numbers.

Why a "100×" Solidity Compilation Claim Compresses Four Different Measurements

Phase map: lexing, parsing, semantic analysis, optimization, codegen

Solidity compilation is not a single operation. The documented solc pipeline runs roughly:

  1. Scanner/lexer — source text to tokens.
  2. Parser — tokens to an AST.
  3. Analysis — import resolution, name resolution, type checking, inheritance linearization (Solidity uses C3 linearization), overload resolution.
  4. Intermediate generation — optionally to Yul IR, which is what --via-ir selects.
  5. Optimization — Yul optimizer and/or the opcode-based optimizer, parameterized by --optimize and --optimize-runs.
  6. Code generation and assembly — EVM bytecode, plus the CBOR metadata blob appended to the deployed output.

A speedup claimed for "compilation" could live in any one of these. A 100× improvement in the lexer would be remarkable and nearly irrelevant to your build time, because lexing is a small share of wall clock on real contracts. A 100× improvement in end-to-end build time on a large project is a different, much harder claim, and it would almost certainly involve caching or parallelism rather than a faster algorithm in one phase.

The three benchmark shapes people conflate

Most compiler speedup numbers come from one of three shapes, and they are not interchangeable:

ShapeWhat it measuresTypical inflation source
Cold single fileProcess spawn + full pipeline on one file, empty page cacheTiny absolute times; fixup and startup dominate
Warm single fileFull pipeline, binary and file cache hotOptimizer differences show clearly here
Whole projectAll source units, imports resolved, artifacts writtenParallelism and incremental caching

If Solar's 100× comes from the whole-project shape with a parallel build and solc's baseline was serial, you are measuring two things at once: per-file compiler speed and the scaling factor. That is legitimate engineering — parallel builds are real wins — but it is not a "100× faster compiler." It is a "100× faster build," and the two get quoted interchangeably.

Building the Reproduction Harness for a Solar vs solc Benchmark

Pinned environment and toolchain inventory

Pin everything. Solidity compiler benchmarking with an unpinned toolchain measures your package manager's release schedule, not the compiler.

pin.sh — record the exact environment
set -euo pipefail
OUT=env-$(date -u +%Y%m%dT%H%M%SZ)
mkdir -p "$OUT"

uname -a                       > "$OUT/uname.txt"
lscpu                          > "$OUT/lscpu.txt"
cat /proc/meminfo | head -5    > "$OUT/meminfo.txt"
nproc                          > "$OUT/nproc.txt"

## Baseline compiler: pin an exact version, do not use "latest"
solc --version                 > "$OUT/solc-version.txt"

## Solar (or whatever you are testing): record the binary hash
sha256sum "$(command -v solar)" > "$OUT/solar-sha256.txt" 2>/dev/null || true
solar --version                 > "$OUT/solar-version.txt" 2>/dev/null || true

hyperfine --version            > "$OUT/hyperfine-version.txt"
node --version                 > "$OUT/node-version.txt"
forge --version                > "$OUT/forge-version.txt" 2>/dev/null || true

echo "wrote $OUT"

The solar --version line is marked || true because I do not know Solar's CLI surface. That is a real gap, not a stylistic hedge: the first thing I would do is run solar --help and record the subcommands, because the harness below assumes flags Solar may not have. Adapt, don't assume.

Corpus design — synthetic stress files versus real contract sets

You need two corpora, and mixing them is how you get a number nobody can reproduce.

Synthetic stress files isolate a phase. A file with 50,000 trivial statements stresses the parser. A file with a 40-deep inheritance chain stresses linearization and name lookup. These do not resemble production code, and that is the point.

Real contract sets tell you what users feel. Pull a pinned commit of a public repo — an ERC-20/ERC-4626 library, a lending protocol, an NFT collection with many source units — and record the commit SHA in your environment directory. Do not fetch main on the day you benchmark.

## Corpus manifest — commit SHAs, not branch names
cat > corpus.lock <<'EOF'
synthetic/parse-stress.sol     # 50k trivial statements
synthetic/inherit-40.sol       # 40-level chain
real/token                      9f2c1e0   # pinned commit
real/lending                    4b7a3d1   # pinned commit
EOF

hyperfine setup, warmup policy, and why cold-start matters for a Rust binary

hyperfine defaults to 10 runs with no warmup, which is usually wrong in both directions. Here is what I use and why:

bench-single.sh
# Warm: measures steady-state pipeline cost, hides process startup
hyperfine --warmup 5 --runs 30 --export-json out/warm-single.json 'solc --bin --optimize corpus/real/A.sol -o /tmp/a > /dev/null' 'solar build corpus/real/A.sol > /dev/null'

## Cold-ish: clear the page cache between runs (needs root),
## so every run reads the binary and sources from disk again
sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'
hyperfine --warmup 0 --runs 10 --prepare 'sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"' --export-json out/cold-single.json 'solc --bin corpus/real/A.sol -o /tmp/a > /dev/null'

Two things get hidden by a warmup of 5. First, process startup. A statically linked Rust binary typically pays a millisecond or less to reach main, whereas a Node-based or JVM-based tool pays far more — so if the comparison is native-vs-managed, warm runs hide the single biggest structural advantage. Second, dynamic linking of large native dependencies, which shows up on the first invocation and then disappears into the page cache.

💪

Report warm and cold numbers separately, in the same table. If a claim only survives in the warm column, it is a claim about steady-state CPU throughput, not about what a developer waits for when they hit save.

Instrumenting per-phase timing instead of trusting wall clock

Wall clock tells you the total. It does not tell you where the total went, and without a phase breakdown you cannot check whether a claimed speedup is even physically capable of moving the end-to-end number.

For the baseline, solc gives you two useful levers: --stop-after parsing (documented values are parsing and parsing-and-imports), which cuts the pipeline short, and --standard-json, which lets you select outputs precisely so you are not timing artifact writes you do not need.

For the compiler under test, use a sampling profiler rather than printf timing:

## Sampling profile, folded stacks for flamegraph
perf record -g -F 999 -- ./target/release/solar build corpus/real/A.sol
perf script | stackcollapse-perf.pl | flamegraph.pl > out/solar.svg

## Syscall-level check: is time going to compute or to I/O?
strace -c -f solar build corpus/real/A.sol

strace -c is the cheap sanity check that catches a benchmark which is really measuring disk. If read/openat dominate the syscall summary, your "compiler speedup" is a file-handling difference, and the page cache state is the independent variable you forgot to control.

Stage 1 — Parsing and Lexing Compilation Speed

Isolating parse-only work

Against solc, --stop-after parsing is your parse-only proxy:

stage1-parse.sh
# Baseline: parse only, no analysis, no codegen
solc --standard-json <<'JSON' > out/stage1-solc.json
{
"language": "Solidity",
"sources": { "A.sol": { "urls": ["corpus/synthetic/parse-stress.sol"] } },
"settings": { "stopAfter": "parsing", "outputSelection": { "*": { "*": [] } } }
}
JSON

Expected signal: the returned contracts object should be empty and no bytecode should appear anywhere. If you see bytecode, the stop-after setting did not apply and you have just measured a full compile. That mistake is common enough that I check for it every time rather than trusting the flag.

Timing the parse stage with hyperfine

hyperfine --warmup 3 --runs 20 \
  --export-json out/stage1.json \
  'solc --stop-after parsing corpus/synthetic/parse-stress.sol > /dev/null' \
  'solar parse corpus/synthetic/parse-stress.sol > /dev/null'

The output you want is not the mean, it is the spread. hyperfine prints a mean, a standard deviation, and a min/max range. A 100× claim whose standard deviation is wider than the gap between the two tools is a claim about noise. I have seen exactly that on small inputs where process startup dominates and both compilers are effectively measuring fork/exec.

Reading the result: why parse speed rarely explains end-to-end wins

Even if parsing were infinitely fast, the end-to-end ceiling is set by the non-parse share of the pipeline. That is Amdahl's law, and it is unforgiving:

Parse share of baseline wall clockMax end-to-end speedup with a free parser
5%1.05×
10%1.11×
25%1.33×
50%2.00×

(Arithmetic, not measurement — substitute your own phase share from the profiler.)

On real contracts, parse time is usually a small slice. A dramatic parse-stage win is a nice signal that the frontend is well engineered, but it cannot by itself produce a 100× end-to-end number. If the headline is end-to-end, the win has to be somewhere else — or it has to come from not doing the work at all: caching, skipping optimization, or not writing artifacts.

Stage 2 — Semantic Analysis and Name Resolution Costs

A contract shaped to stress imports, inheritance, and symbol lookup

This is where a compiler frontend earns its keep. The stress file should have three properties: many source units, a deep linearization chain, and heavy cross-unit symbol references.

gen-inherit.js — emit a deep chain
// Generates base0.sol .. baseN.sol plus a diamond-ish leaf contract.
const fs = require("node:fs");
const N = Number(process.argv[2] ?? 40);

for (let i = 0; i < N; i++) {
const parent = i === 0 ? "" : ` is Base${i - 1}`;
const call = i === 0 ? "" : `    function f${i}() public virtual returns (uint256) { return super.f${i}() + ${i}; }
`;
fs.writeFileSync(
  `corpus/synthetic/base${i}.sol`,
  `// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Base${i}${parent} {
${call}}
`
);
}

const leafImports = Array.from({ length: N }, (_, i) => `import "./base${i}.sol";`).join("
");
const refs = Array.from({ length: N }, (_, i) => `    function g${i}() public pure returns (uint256) { return ${i}; }`).join("
");
fs.writeFileSync(
"corpus/synthetic/leaf.sol",
`// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
${leafImports}

contract Leaf is Base${N - 1} {
${refs}
}
`
);
console.log("wrote", N + 1, "source units");
node gen-inherit.js 40
node gen-inherit.js 80
node gen-inherit.js 160

Sweeping N instead of testing one value is the whole point. Semantic analysis cost in this shape is not linear in source-unit count, and the curve is where the interesting differences live.

Timing semantic analysis across source-unit counts

hyperfine --warmup 3 --runs 20 --export-json out/stage2.json \
  'solc --stop-after parsing-and-imports corpus/synthetic/leaf.sol > /dev/null' \
  'solar analyze corpus/synthetic/leaf.sol > /dev/null'

Note the baseline flag: parsing-and-imports resolves imports but stops before full analysis, so the difference between this run and a full solc build brackets the analysis stage. If solar has no equivalent "stop after analysis" flag, you can only bound the stage, not isolate it — say so in your write-up rather than implying you measured it.

Failure modes worth watching: deep inheritance chains and source-unit count

Two things break this benchmark silently.

Linearization blowup. Solidity's C3 linearization can produce surprising cost curves as the hierarchy widens. A benchmark that only tests a 3-level chain will show nothing; a 60-level chain can dominate. Sweep the depth and plot it.

Source-unit count as the hidden variable. A project with 200 tiny files and one with 20 large files can have identical total bytes and wildly different analysis cost, because per-unit overhead (import resolution, unit setup) is paid 200 times instead of 20. If your corpus and the source's corpus differ on this axis, your numbers are not comparable, even if both are "real contracts."

Stage 3 — Optimization Passes and Flag Parity

Where the compiler actually spends its time

On real contracts with the optimizer on, the optimizer frequently dominates wall clock. This is the stage where a legitimate order-of-magnitude difference is most plausible, and also where the most misleading comparisons get made — because the easiest way to compile 100× faster is to not optimize.

solc exposes three relevant levers:

FlagEffect
--optimizeEnables the optimizer
--optimize-runsTrades deployment cost against runtime gas (default 200)
--via-irRoutes through the Yul IR pipeline, enabling a different optimizer

Changing any of these changes both compile-time performance and emitted bytecode. A compile-time comparison across different flag sets is not a compiler-speed comparison; it is a code-quality comparison wearing a stopwatch.

Comparing optimization settings and pass pipelines

stage3-opt.sh
CONTRACT=corpus/real/lending/src/Pool.sol

## A: no optimizer
hyperfine --warmup 3 --runs 15 --export-json out/stage3-none.json "solc --bin $CONTRACT -o /tmp/a > /dev/null"

## B: optimizer, default runs
hyperfine --warmup 3 --runs 15 --export-json out/stage3-opt200.json "solc --bin --optimize $CONTRACT -o /tmp/b > /dev/null"

## C: optimizer, high runs
hyperfine --warmup 3 --runs 15 --export-json out/stage3-opt1m.json "solc --bin --optimize --optimize-runs 1000000 $CONTRACT -o /tmp/c > /dev/null"

## D: Yul IR pipeline
hyperfine --warmup 3 --runs 15 --export-json out/stage3-viair.json "solc --bin --optimize --via-ir $CONTRACT -o /tmp/d > /dev/null"

Run A through D, then run the candidate compiler at its default settings. Only A-to-A and B-to-B comparisons are meaningful. If the candidate's default is equivalent to B and the baseline number was taken from D, you have found your multiple.

Timing the optimizer at each flag setting

Report the four-row table with your own timings, and add a fifth column: does the bytecode match?

ConfigWall clock (mean ± σ)Bytecode sizeGas vs baseline
A: solc, no optyour numbermeasuren/a
B: solc, --optimizeyour numbermeasure0 (baseline)
C: solc, runs=1e6your numbermeasuremeasure
D: solc, --via-iryour numbermeasuremeasure
E: candidate defaultsyour numbermeasuremeasure

The gas column decides whether E is a better compiler or just a faster one. I won't fill these in — I haven't run Solar and won't invent numbers.

Stage 4 — Code Generation and Bytecode Emission

What codegen covers in a Solidity compiler

Codegen turns the analyzed (and possibly optimized) representation into EVM bytecode, then assembles it with jump-destination resolution — the assembler has to know the byte offset of every jump target before it can emit PUSH2 <dest> labels. solc then appends a CBOR-encoded metadata blob to the deployed bytecode, and the final two bytes of the output encode that blob's length.

That metadata blob is the single most common reason a naive bytecode diff reports spurious differences. It embeds a hash of the sources and settings, so two builds of the same contract from different paths can differ in the last few dozen bytes and be functionally identical.

Timing full builds with artifact emission

## Full build, artifact written to disk — the realistic end-to-end shape
hyperfine --warmup 3 --runs 20 --export-json out/stage4.json \
  'solc --bin --optimize corpus/real/lending/src/Pool.sol -o /tmp/full > /dev/null' \
  'solar build corpus/real/lending > /dev/null'

Watch for whether artifacts are written. A compiler that emits to stdout and one that writes a JSON artifact directory are doing different amounts of I/O. If your corpus is large, artifact writing can be a significant fraction of the "build" — and it is also the easiest thing to omit accidentally in one of the two commands.

Assembling the Stage Numbers Into an End-to-End Speedup

Where the multiple comes from and where it collapses

Put your stage shares in one table and check the arithmetic:

StageBaseline share of wall clockCandidate shareStage speedup
Parsemeasuremeasurecompute
Analysismeasuremeasurecompute
Optimizemeasuremeasurecompute
Codegen + emitmeasuremeasurecompute
End to end100%100%must equal measured

If the product of the stage weights does not reproduce your measured end-to-end ratio, one of three things is true: you missed a stage (artifact I/O, process startup, dependency resolution), your phase isolation is leaking, or the measured end-to-end number came from a different benchmark shape.

Amdahl check: the ceiling implied by the phase breakdown

Do this before you publish anything. Take the baseline phase shares and ask: if the candidate made stage k instantaneous and left everything else untouched, what is the best possible end-to-end number?

For a 100× end-to-end claim, that stage must carry at least 99% of baseline wall clock: a stage representing fraction p of runtime, made infinitely fast, caps total speedup at 1/(1−p), and 1/(1−p) = 100 gives p = 0.99.

Does any single phase plausibly hold 99% of a real Solidity build? On a heavily optimized large project the optimizer can hold a majority — but 99% is a stretch. That would mean parsing, analysis, and codegen together are negligible, which is implausible for non-trivial source. Which pushes the explanation elsewhere: the 100× is most likely a whole-project, warm-cache or parallel comparison, not a per-phase constant. (Inference, clearly labeled — testable with the harness above.)

Which Solidity Workloads Actually Benefit From a Faster Compiler

Workload table — CI test builds, editor feedback, single-file scripts, monorepos

WorkloadWhat dominatesDoes a faster compiler help?What to measure
CI test buildsFull build + optimizer + test executionPartly — test execution and node startup often dominate the jobTotal job time, not compile step
Editor feedbackCold start + incremental recompile of one fileYes, if cold start is small and recompiles are incrementalp95 latency on keystroke-triggered builds
Single-file scriptsProcess startup, parseYes, but absolute times are tiny; savings are millisecondsCold run, page cache dropped
MonoreposSource-unit count, analysis, optimizerMostly via parallelism and cachingWall clock at fixed core count

The editor-feedback row is where I think the real product value sits, and it is also where headline "100×" numbers are least informative. Going from 900 ms to 9 ms is a 100× win and it feels great. Going from 4 minutes to 2.4 seconds in CI is a 100× win that changes team behavior. Those are different engineering accomplishments even if the ratio matches.

The parallel-compilation multiplier and how it inflates headline numbers

If the candidate compiles K source units concurrently and the baseline was invoked serially, wall-clock ratio = per-file speedup × parallel scaling. On a 16-core box with good scaling, the second factor alone is up to ~16× before any compiler improvement.

To separate them, pin the core count for both:

## Force single-core for both compilers
taskset -c 0 hyperfine --warmup 3 --runs 10 \
  --export-json out/serial.json \
  'solc --bin --optimize corpus/real/lending/src/Pool.sol -o /tmp/a > /dev/null' \
  'solar build --jobs 1 corpus/real/lending > /dev/null'

If the 100× survives at -c 0 with --jobs 1, it is algorithmic. If it collapses to 8×, it was mostly parallelism — still a real win, just a different one, and it deserves a different sentence in the write-up.

Correctness Gates Before You Trust Any Speedup

Bytecode equivalence and gas-diff checks against a baseline compiler

A faster compiler that emits different bytecode is not a faster compiler. It is a different compiler, and every user's gas cost, contract size limit, and deployed bytecode hash just changed.

Two gates, in order. First, semantic equivalence: does the contract still pass its test suite? Second, output equivalence: is the emitted bytecode identical once metadata is stripped?

📝

Bytecode equality is the stronger gate, but it fails legitimately when the compiler makes different — even better — optimization choices. Treat bytecode inequality as "investigate," not "broken," and treat gas regression as the real failure.

A minimal equivalence harness

compare-bytecode.mjs
import { readFileSync } from "node:fs";

// solc appends CBOR metadata; the final 2 bytes are the metadata length.
// Strip it before comparing, or every diff is a false positive.
function stripMetadata(hex) {
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
if (clean.length < 4) return clean;
const cborLen = parseInt(clean.slice(-4), 16);
if (!Number.isFinite(cborLen)) return clean;
const total = cborLen * 2 + 4;
if (total > clean.length) return clean; // no metadata present
return clean.slice(0, clean.length - total);
}

const [, , baselinePath, candidatePath] = process.argv;
const baseline = stripMetadata(readFileSync(baselinePath, "utf8").trim());
const candidate = stripMetadata(readFileSync(candidatePath, "utf8").trim());

if (baseline === candidate) {
console.log("MATCH  bytecode identical after metadata strip");
process.exit(0);
}

// Find the first divergence so the diff is readable
let i = 0;
while (i < baseline.length && i < candidate.length && baseline[i] === candidate[i]) i++;
console.log(`DIFF   at hex nibble ${i}`);
console.log(`  baseline : ...${baseline.slice(Math.max(0, i - 20), i + 40)}`);
console.log(`  candidate: ...${candidate.slice(Math.max(0, i - 20), i + 40)}`);
console.log(`  sizes    : ${baseline.length / 2} vs ${candidate.length / 2} bytes`);
process.exit(1);

Then the gas gate, using a real test suite rather than solc --gas estimates:

## Run the same tests against both builds, snapshot gas per test
forge snapshot --snap out/gas-baseline.txt
## ... swap the compiler, rebuild, rerun ...
forge snapshot --snap out/gas-candidate.txt
forge snapshot --diff out/gas-baseline.txt out/gas-candidate.txt

--gas on solc gives estimates — useful for a quick look, not for a release decision. Actual test execution is the number that matters.

What a speedup is worth if the output differs

Rank the outcomes honestly:

Bytecode matchGas vs baselineVerdict
IdenticalIdenticalSpeedup is real and free
DiffersEqual or lowerSpeedup is real; audit the diff, then accept
DiffersHigherSpeedup is a regression in disguise; reject
DiffersUnknownNot yet a result

Row three is the one that ends up in production. A compiler that builds in 2% of the time and costs every Ethereum user 3% more gas on every transaction is a bad trade for anything on mainnet — gas is money, permanently, for the life of the contract. Compile time is paid once per build by developers who are already context-switching. I will take the slower compiler with the better bytecode for anything that gets deployed, and the faster compiler for the editor loop.

Reproducing This Yourself: Command Checklist and Sanity Checks

Step-by-step command checklist

## 1. Pin the environment
mkdir -p out && ./pin.sh

## 2. Lock the corpus by commit SHA
cat > corpus.lock <<'EOF'
synthetic/parse-stress.sol
real/token   9f2c1e0
real/lending 4b7a3d1
EOF

## 3. Generate synthetic stress files
node gen-inherit.js 40
node gen-inherit.js 80
node gen-inherit.js 160

## 4. Baseline: isolate each stage with solc stop-after flags
solc --stop-after parsing            corpus/synthetic/parse-stress.sol > /dev/null
solc --stop-after parsing-and-imports corpus/synthetic/leaf.sol        > /dev/null

## 5. Baseline: full pipeline at four optimizer settings
for cfg in "" "--optimize" "--optimize --optimize-runs 1000000" "--optimize --via-ir"; do
  echo "== solc $cfg"
  hyperfine --warmup 3 --runs 15 --export-json "out/base$(echo $cfg | tr ' ' '_').json" \
    "solc --bin $cfg corpus/real/lending/src/Pool.sol -o /tmp/b > /dev/null"
done

## 6. Candidate: same corpus, same flags, same core count
taskset -c 0 hyperfine --warmup 3 --runs 15 --export-json out/cand.json \
  'solar build --jobs 1 corpus/real/lending > /dev/null'

## 7. Correctness gate
node compare-bytecode.mjs out/baseline.bin out/candidate.bin
forge snapshot --diff out/gas-baseline.txt out/gas-candidate.txt

Sanity checks that catch bad benchmarks before they reach a slide

  • Does the "faster" command actually produce the same artifact? ls -la /tmp/b after each run. An empty output directory is a 100× speedup.
  • Is the stop-after flag actually applied? Empty contracts in the standard-JSON output, no bytecode anywhere.
  • Is the standard deviation smaller than the effect? If σ is 30% of the mean and the gap is 20%, you measured the machine.
  • Does the ratio survive taskset -c 0 and --jobs 1? If not, it is a parallelism result.
  • Does the ratio survive drop_caches? If not, it is a page-cache result.
  • Did you run the baseline on the same corpus commit? Compare corpus.lock between runs.
  • Is the absolute time plausible? If the baseline is 40 ms for a large project, you are not compiling the project.

What I could not verify from the available source

To be explicit, since the whole post is about not blurring this line:

  • Solar's implementation language, architecture, and whether it is a full compiler, a frontend only, or a compilation server. The Rust framing above is inference from ecosystem context, not a sourced fact.
  • The baseline solc version, compiler flags, and corpus used behind the 100× figure.
  • Whether the 100× refers to cold single-file, warm single-file, or whole-project compilation.
  • Whether bytecode equivalence was checked.

And to be equally clear: I have not run Solar. No Solar-specific timing in this post is a measurement. Every table cell marked measure is a slot for your numbers.

What I'd Do With This Today

If I were evaluating Solar for a team, I would ignore the headline and run three experiments in order.

First, the editor loop. p95 latency from save to typecheck-complete on a mid-size contract, cold process, single file. This is where a 10× win is transformative and a 100× win is indistinguishable from a 10× win, because it is already fast enough to feel instant. If Solar does well here, adopt it for development regardless of what it does elsewhere.

Second, the CI build with cores pinned to 1. This tells me whether the speedup is algorithmic or parallel. If it is parallel, I want to know how it scales to 4 and 8 cores, because CI runners are not 64-core boxes and the scaling curve near the bottom is what I actually get.

Third, the correctness gate. Bytecode diff with metadata stripped, then the full test suite, then a gas snapshot diff. Nothing merges until those three are clean. I do not care if the compiler is 100× or 1000× faster if the gas numbers move in the wrong direction.

The thing I would not do is put "100×" in a slide. Even in the best case, that number is specific to a benchmark shape that is not my benchmark shape, and the first engineer who reproduces it on their project and gets 6× will reasonably stop trusting the rest of the deck.

Conclusion

The claim in the source is narrow: Paradigm Policy, 2 November 2026, "Solar: 100× faster Solidity," a new Solidity compiler described as up to 100× faster. That is a claim about a benchmark. It is not yet a claim about a compiler, because the measurement conditions are not public in the material I have.

The engineering position I will defend: a 100× end-to-end result cannot come from a single pipeline phase unless that phase holds ~99% of baseline wall clock, which is implausible for non-trivial Solidity source. So the plausible explanations are warm-cache incremental builds, parallel execution, or a flag mismatch between the two sides — all legitimate wins that deserve accurate labels.

The harness above is how you figure out which one it is: pin the environment, lock the corpus by SHA, isolate stages with --stop-after, sweep optimizer settings instead of testing one, force single-core to separate parallelism from algorithms, and refuse to trust any ratio that does not survive a bytecode and gas diff.

A speedup is a number. A speedup with equivalent bytecode, unchanged gas, a pinned baseline, and a stated core count is a result.

Further Reading

Share this post

More posts

Loading comments…