
Reproducing Solar's 100× Solidity Compilation Benchmark, Stage by Stage
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
| Status | Statement | Basis |
|---|---|---|
| Confirmed | A compiler called Solar exists publicly and is described as a Solidity compiler | Source item text |
| Confirmed | The claim attributed to it is "up to 100× faster" compilation | Source item text |
| Confirmed | Publisher is Paradigm Policy; item dated 2026-11-02 | Source item metadata |
| Inferred | Solar is likely implemented in Rust and ships as a native CLI binary | Ecosystem framing around the project name; not stated in the source I have |
| Inferred | The 100× figure is likely a best-case comparison, not an average across a project build | "up to" phrasing plus normal benchmark reporting practice |
| Unknown | Which solc version is the baseline | Not in source |
| Unknown | Whether optimization was enabled in either compiler | Not in source |
| Unknown | Corpus size, contract complexity, source-unit count | Not in source |
| Unknown | Whether the 100× is cold single-file, warm single-file, or whole-project parallel | Not in source |
| Unknown | Whether emitted bytecode was verified equivalent | Not 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:
- Scanner/lexer — source text to tokens.
- Parser — tokens to an AST.
- Analysis — import resolution, name resolution, type checking, inheritance linearization (Solidity uses C3 linearization), overload resolution.
- Intermediate generation — optionally to Yul IR, which is what
--via-irselects. - Optimization — Yul optimizer and/or the opcode-based optimizer, parameterized by
--optimizeand--optimize-runs. - 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:
| Shape | What it measures | Typical inflation source |
|---|---|---|
| Cold single file | Process spawn + full pipeline on one file, empty page cache | Tiny absolute times; fixup and startup dominate |
| Warm single file | Full pipeline, binary and file cache hot | Optimizer differences show clearly here |
| Whole project | All source units, imports resolved, artifacts written | Parallelism 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.
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:
# 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:
# 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": { "*": { "*": [] } } }
}
JSONExpected 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 clock | Max 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.
// 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:
| Flag | Effect |
|---|---|
--optimize | Enables the optimizer |
--optimize-runs | Trades deployment cost against runtime gas (default 200) |
--via-ir | Routes 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
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?
| Config | Wall clock (mean ± σ) | Bytecode size | Gas vs baseline |
|---|---|---|---|
A: solc, no opt | your number | measure | n/a |
B: solc, --optimize | your number | measure | 0 (baseline) |
C: solc, runs=1e6 | your number | measure | measure |
D: solc, --via-ir | your number | measure | measure |
| E: candidate defaults | your number | measure | measure |
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:
| Stage | Baseline share of wall clock | Candidate share | Stage speedup |
|---|---|---|---|
| Parse | measure | measure | compute |
| Analysis | measure | measure | compute |
| Optimize | measure | measure | compute |
| Codegen + emit | measure | measure | compute |
| End to end | 100% | 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
| Workload | What dominates | Does a faster compiler help? | What to measure |
|---|---|---|---|
| CI test builds | Full build + optimizer + test execution | Partly — test execution and node startup often dominate the job | Total job time, not compile step |
| Editor feedback | Cold start + incremental recompile of one file | Yes, if cold start is small and recompiles are incremental | p95 latency on keystroke-triggered builds |
| Single-file scripts | Process startup, parse | Yes, but absolute times are tiny; savings are milliseconds | Cold run, page cache dropped |
| Monorepos | Source-unit count, analysis, optimizer | Mostly via parallelism and caching | Wall 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
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 match | Gas vs baseline | Verdict |
|---|---|---|
| Identical | Identical | Speedup is real and free |
| Differs | Equal or lower | Speedup is real; audit the diff, then accept |
| Differs | Higher | Speedup is a regression in disguise; reject |
| Differs | Unknown | Not 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/bafter each run. An empty output directory is a 100× speedup. - Is the stop-after flag actually applied? Empty
contractsin 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 0and--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.lockbetween 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
solcversion, 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
- Solidity documentation — the compiler pipeline and
--via-ir(official docs on the Yul IR pipeline) - Solidity documentation — using the compiler, CLI options including
--stop-afterand--standard-json(official docs) - Solidity documentation — optimizer settings and
--optimize-runs(official docs) - hyperfine — command-line benchmarking tool (tooling docs;
--warmup,--prepare,--export-json) - Foundry book —
forge snapshotand gas reporting (tooling docs) - Paradigm Policy, "Solar: 100× faster Solidity," 2 November 2026 (the source item discussed in this post; no methodology published in the version I could access)
More posts

Reproducing the Rust tsc '2x Faster' Claim Across Cold Builds, Incremental Rebuilds, --watch, and tsserver
A hands-on reproducibility check of the claim that an AI-written Rust port of the TypeScript compiler runs 2x faster than the Go-based native port, measuring cold builds, incremental rebuilds, --watch cycles, and tsserver workloads under controlled conditions.

Splitting Router and Worker Roles in a Node.js Coding Agent: Lessons from dsh-superagent
DeepSeek's open-source dsh-superagent splits coding-agent responsibilities between a router and worker in Node.js, offering an inspectable alternative to single-loop agents like Claude Code and a reusable TypeScript blueprint.

Provable Inference for LLMs: Latency Costs and Failure Modes It Misses
Anthropic's progress on provable inference for LLMs promises formal guarantees beyond empirical evals, but this walkthrough breaks down what those guarantees actually cover, the latency and engineering overhead they impose, and the failure modes they still cannot catch.
Loading comments…