Skip to content

perf(core): reuse persistent worker subprocesses across translation units#524

Open
hedgar2017 wants to merge 4 commits into
mainfrom
az-evm-assembly-worker-pool
Open

perf(core): reuse persistent worker subprocesses across translation units#524
hedgar2017 wants to merge 4 commits into
mainfrom
az-evm-assembly-worker-pool

Conversation

@hedgar2017

@hedgar2017 hedgar2017 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Replaces the fork/exec-per-translation-unit subprocess model with a pool of persistent worker subprocesses fed over pipes, and slims the per-unit IPC payload. Previously every translation unit fork/exec'd the 55 MB LLVM-linked binary, CBOR-encoded the whole payload, and each child built an unused N-thread rayon pool.

Local results with the raw openzeppelin standard JSON (average of 5 runs):

Case Before (solx-old) After (solx-new) Δ
dwarf 20.94s 19.55s −6.6%
no-dwarf 20.02s 18.87s −5.7%

@hedgar2017 hedgar2017 force-pushed the az-evm-assembly-worker-pool branch 4 times, most recently from f9371c4 to 2f00823 Compare July 9, 2026 16:19
Comment thread solx-codegen-evm/src/target_machine.rs Outdated
Comment thread solx-core/src/process/child.rs Outdated
Comment thread solx-core/src/process/pool.rs Outdated
Comment thread solx-core/src/process/pool.rs Outdated
Comment thread solx-core/src/process/session.rs
Comment thread solx-core/src/process/worker.rs Outdated
Comment thread solx/tests/cli/recursive_process.rs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors solx-core’s recursive subprocess compilation into a pool of persistent worker subprocesses using a framed CBOR protocol, aiming to eliminate per-translation-unit fork/exec and reduce IPC overhead while keeping outputs byte-identical.

Changes:

  • Introduces a framed CBOR channel and splits compilation payload into Session (sent once) + per-unit Job.
  • Adds a parent-side Pool/Worker for persistent subprocess reuse and a child-side long-lived worker loop.
  • Updates compilation pipeline call sites and LLVM option handling to avoid cross-job option leakage.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
solx/tests/cli/recursive_process.rs Updates CLI expectations for the new worker protocol/argument behavior.
solx-core/src/project/mod.rs Switches multi-pass compilation to dispatch jobs through the persistent worker pool.
solx-core/src/project/contract/mod.rs Adjusts compile_to_evm API to take output_selection by reference; removes per-unit stack handler install.
solx-core/src/process/mod.rs Replaces single-shot subprocess model with new process module structure for persistent workers.
solx-core/src/process/channel.rs Adds framed CBOR send/recv helpers shared by parent/child.
solx-core/src/process/session.rs Defines session payload shared across jobs and sent once per worker.
solx-core/src/process/job.rs Defines per-translation-unit job payload.
solx-core/src/process/pool.rs Adds worker pooling, reuse policy, and retry-on-reused-worker-death behavior.
solx-core/src/process/worker.rs Implements parent-side subprocess spawn + framed I/O.
solx-core/src/process/child.rs Implements child-side long-lived worker loop and stack-too-deep handler response path.
solx-core/src/process/output.rs Minor documentation cleanup for the output type.
solx-core/src/lib.rs Updates exports to expose new pool/session/job and subprocess entrypoint.
solx-core/src/compiler.rs Reorders initialization to avoid building rayon pool in worker subprocess mode.
solx-core/src/arguments.rs Updates --recursive-process semantics/docs and argument validation expectations.
solx-codegen-evm/src/target_machine.rs Ensures -evm-metadata-size is always passed to heal stale process-global LLVM options.
solx-codegen-evm/src/codegen/context/mod.rs Adjusts size-fallback flag handling and propagates metadata sizing into fallback settings.

Comment thread solx-core/src/process/channel.rs
Comment thread solx-core/src/process/channel.rs Outdated
@nebasuke nebasuke added the ci:compile-benchmark Run hyperfine compilation benchmark for this PR label Jul 13, 2026
…nits

Replace the fork/exec-per-unit model with a pool of persistent workers fed
over pipes. The per-unit CBOR payload is split into a Session (project-wide
data, sent once per worker) and a slim per-unit Job, and idle workers are
held in a std Mutex<Vec> and reused.

The process module is one entity per file: channel (the length-prefixed CBOR
frame codec, via FrameRead/FrameWrite extension traits), session, job, output,
pool (parent-side scheduler), worker (subprocess handle), and child (the
subprocess loop). The child runs one long-lived stack-sized thread that owns
the deserialized Session and compiles jobs until stdin closes, instead of
spawning a thread and re-cloning the session per job. Worker stderr is
inherited, so subprocess diagnostics stream straight to the parent.

Because LLVM command-line options are process-global and now survive across
units in one worker, always emit -evm-metadata-size to heal a stale value,
and carry metadata_size into the size-fallback settings; reset IS_SIZE_FALLBACK
per job. A worker is retired when its job set a spill-area size (occurrence-
gated cl-option) or carried extra llvm_options, and never reused after a
stack-too-deep response since its handler has already exited the process. When
a reused worker dies mid-job, the job is retried once on a fresh worker so the
death is not mis-attributed to the contract.

Install the stack-error handler once per worker process instead of per unit,
and build the rayon pool only in the parent, after the recursive-process
branch, since workers compile a single unit and never use it.
@hedgar2017 hedgar2017 force-pushed the az-evm-assembly-worker-pool branch from 2f00823 to 8276ba7 Compare July 13, 2026 14:35
@hedgar2017 hedgar2017 added ci:integration Trigger integration tests workflow on PR ci:slang Trigger slang unit tests on PR ci:integration-benchmark-full Run the full benchmark matrix (all compilers + comparisons) in integration tests labels Jul 13, 2026
@hedgar2017 hedgar2017 marked this pull request as ready for review July 13, 2026 14:45
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

📊 solx Tester Report

➡️ Download

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

📊 Hardhat Projects Report

➡️ Download

Worker::execute now returns a plain crate::Result<EVMOutput> — a subprocess I/O failure folds into Error::Generic — so the ? drops a dead or errored worker and the pool is just pop-or-spawn, run, and return. This removes the retry, the WorkerError enum, dispatch, and worker_failed.

Whether a worker may be reused lives on the data owners: Session::allows_worker_reuse (no extra llvm_options) and Job::allows_worker_reuse (no stack-too-deep spill area), each documenting the process-global cl-option it guards against. The cached reuse_workers field is removed.

run_multi_pass_pipeline breaks the loop with its value instead of a declare-then-assign, and no longer threads the contract name.
@hedgar2017 hedgar2017 requested review from a team and nebasuke July 13, 2026 17:32
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

Compile-time benchmark (--standard-json, EVMLA pipeline)

pr: solx v0.1.4, LLVM-based Solidity compiler for the EVM, Front end: solc, LLVM build: 9537dbed1cab68b483aba62a4560ba6a9dd78231
main: solx v0.1.4, LLVM-based Solidity compiler for the EVM, Front end: solc, LLVM build: cec9acbdc2799e5607ea57c4f1617ca8579afbf9
release: solx v0.1.4, LLVM-based Solidity compiler for the EVM, Front end: solc, LLVM build: aaa40607301776680f66f4119b5f564a8416cca0

ens-verifiable-factory-solx--solx-legacy-dwarf

binary mean ± σ min … max vs baseline
pr 2.949 s ± 0.015 s 2.934 s … 2.963 s 1.004×
main 2.937 s ± 0.032 s 2.916 s … 2.973 s 1.000×
release 6.635 s ± 0.064 s 6.565 s … 6.689 s 2.259×

openzeppelin-contracts-0.34--solx-legacy-dwarf

binary mean ± σ min … max vs baseline
pr 32.938 s ± 0.175 s 32.743 s … 33.082 s 0.959×
main 34.345 s ± 0.218 s 34.163 s … 34.586 s 1.000×
release 241.584 s ± 15.953 s 227.797 s … 259.058 s 7.034×

uniswap-v4-core-solx--solx-legacy-dwarf

binary mean ± σ min … max vs baseline
pr 16.970 s ± 0.048 s 16.914 s … 17.002 s 0.997×
main 17.029 s ± 0.032 s 16.992 s … 17.049 s 1.000×
release 84.769 s ± 4.238 s 80.623 s … 89.093 s 4.978×

Reset the process-global LLVM command-line option occurrences before every parse (new LLVMResetAllOptionOccurrences, exposed through llvm-sys and inkwell), so a translation unit never inherits an option a previous one set in the same persistent worker.

This removes the whole option-leak layer: the -evm-metadata-size=0 heal is gone, and the pool reuses a worker unconditionally on success — Session/Job::allows_worker_reuse and the spill/llvm_options gating are deleted.

Points solx-llvm, llvm-sys, and inkwell at their az-reset-option-occurrences branches. Byte-identical on the EVMLA smoke; CLI suite 397/397.
@github-actions

Copy link
Copy Markdown

📊 Foundry Projects Report

➡️ Download

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:compile-benchmark Run hyperfine compilation benchmark for this PR ci:integration Trigger integration tests workflow on PR ci:integration-benchmark-full Run the full benchmark matrix (all compilers + comparisons) in integration tests ci:slang Trigger slang unit tests on PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants