Skip to content

Compile in-process on OCaml domains in OCaml rewatch - #8781

Draft
cknitt wants to merge 22 commits into
rewatch-ocaml-portfrom
rewatch-ocaml-in-process
Draft

cknitt wants to merge 22 commits into
rewatch-ocaml-portfrom
rewatch-ocaml-in-process

Conversation

@cknitt

@cknitt cknitt commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Stacked on #8780.

This PR moves compilation into the OCaml rewatch process. Instead of starting bsc for every parse and compile, rescript links the compiler and runs requests on a bounded pool of OCaml 5 domains. That removes process start-up and pipe handling, which make the subprocess port about 6% slower than Rust on clean builds. It also opens the door to sharing compiler state between requests later.

Changes

  • The compiler's process-wide mutable state is made request-scoped. Clflags, Js_config, warnings, environment and CRC caches, predefined types, Ident stamps, printers, diagnostics and backend caches move into domain-local state. Almost all of it lives in Request_local keys: every request starts with fresh values, as a new bsc process would, and the previous values are restored afterwards. Only Ident stamps, the request context, output capture and the order-sensitive predefined types keep explicit handling. The driver moves from rescript_compiler_main.ml to rescript_compiler_driver.ml, so bsc and rewatch share it.
  • Each in-process request's compiler output goes to request-owned buffers, written in text mode so that Windows output keeps bsc's CRLF line endings. Output written before an escaping exception is kept and reported with the failed module. The standalone bsc still writes directly to its stdout and stderr.
  • Rewatch schedules independent parse and compile requests on worker domains. By default that's at most 8, and one fewer than the CPU count; REWATCH_COMPILER_DOMAINS overrides it. Artifact publication stays on the scheduling domain. PPXs and hooks still run as external processes.
  • rescript format also runs on the embedded compiler. PPXs, js-post-build, --after-build and the lock-owner check are the only remaining subprocesses. RESCRIPT_BSC_EXE now only affects rescript-rust, and CONTRIBUTING.md says how to test a local compiler instead.
  • A PPX's temporary AST files are removed when the PPX fails or the build is interrupted, not only on success.
  • lib/bs/compiler-info.json still records the bsc.exe shipped next to rescript.exe. The VS Code extension reads bsc_path to find bsc.exe and the other platform binaries.

Known issues before this can leave draft

  • An earlier domain build once produced a differing output that was never explained. Review since found and fixed shared or leaked state:

    • Js_analyzer's free-variable accumulator was shared between domains, so Js_shake could drop a live function definition; this reproduced once in CI;
    • Parmatch.Conv's name counter, Ctype's abbreviation memo and levels, and the parser hooks for error messages.

    An audit of top-level mutable records, visitors and tables found no other live case. Regression tests cover the Js_analyzer race and the leaked Ctype memo.

  • The first Ctrl-C waits for modules that are compiling in-process to finish; a second one releases the build lock and exits at once. The focused suite tests this with one large module.

ThreadSanitizer

An OCaml 5.5.0 ThreadSanitizer build of rescript reported no data races in compiler or rewatch code on clean builds of rewatch/testrepo (4 domains), tests/tests (2, 8 and 12 domains), the gentype example and the React PPX tests, or in the rewatch unit tests. Two reports appeared in some tests/tests runs, both inside the runtime's parallel minor GC (oldify_one against pool_allocate). With the shared Js_analyzer accumulator restored, the same tests/tests build reports 91 races, all in Js_analyzer, so the tool does catch this class of bug.

Tests

tests/rewatch_ounit_tests/compiler_driver_tests.ml checks that each kind of request-scoped state is isolated between consecutive and concurrent requests. The focused suite tests with one large module that a second build waits for the build lock of an in-process build, and that a failing PPX leaves no temporary files. A unit test checks that an exception escaping the compiler fails only its own module. rewatch-ocaml/bench compares clean-build artifacts across domain counts; it does not run in CI.

🤖 Generated with Claude Code

@cknitt
cknitt added this pull request to stack #8782 October 10, 2026 16:36
@codecov

codecov Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.08037% with 347 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (rewatch-ocaml-port@9e4fb4b). Learn more about missing BASE report.

Files with missing lines Patch % Lines
compiler/bsc/rescript_compiler_driver.ml 73.26% 73 Missing ⚠️
compiler/ml/ctype.ml 81.99% 47 Missing ⚠️
compiler/ml/printtyp.ml 60.34% 46 Missing ⚠️
compiler/gentype/debug.ml 51.21% 20 Missing ⚠️
compiler/ml/env.ml 83.47% 19 Missing ⚠️
compiler/ext/compiler_request_output.ml 72.00% 14 Missing ⚠️
compiler/ml/typecore.ml 91.73% 10 Missing ⚠️
compiler/core/js_implementation.ml 79.06% 9 Missing ⚠️
compiler/ml/cmi_format.ml 50.00% 8 Missing ⚠️
compiler/ml/ast_mapper.ml 53.33% 7 Missing ⚠️
... and 51 more
Additional details and impacted files
@@                  Coverage Diff                  @@
##             rewatch-ocaml-port    #8781   +/-   ##
=====================================================
  Coverage                      ?   80.48%           
=====================================================
  Files                         ?      452           
  Lines                         ?    61778           
  Branches                      ?        0           
=====================================================
  Hits                          ?    49723           
  Misses                        ?    12055           
  Partials                      ?        0           
Files with missing lines Coverage Δ
analysis/src/module_resolution.ml 18.18% <100.00%> (ø)
compiler/common/js_config.ml 81.81% <100.00%> (ø)
compiler/core/bs_conditional_initial.ml 100.00% <ø> (ø)
compiler/core/config_util.ml 93.75% <100.00%> (ø)
compiler/core/ir_diagnostics.ml 92.59% <100.00%> (ø)
compiler/core/js_analyzer.ml 82.91% <100.00%> (ø)
compiler/core/js_cmj_format.ml 94.64% <100.00%> (ø)
compiler/core/js_dump.ml 92.55% <100.00%> (ø)
compiler/core/js_dump_program.ml 98.46% <100.00%> (ø)
compiler/core/lam_beta_reduce_util.ml 96.87% <100.00%> (ø)
... and 103 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cknitt
cknitt force-pushed the rewatch-ocaml-in-process branch 2 times, most recently from 236cf9b to 3de8d08 Compare October 10, 2026 18:59
@pkg-pr-new

pkg-pr-new Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript@8781

@rescript/belt

npm i https://pkg.pr.new/@rescript/belt@8781

@rescript/darwin-arm64

npm i https://pkg.pr.new/@rescript/darwin-arm64@8781

@rescript/darwin-x64

npm i https://pkg.pr.new/@rescript/darwin-x64@8781

@rescript/linux-arm64

npm i https://pkg.pr.new/@rescript/linux-arm64@8781

@rescript/linux-x64

npm i https://pkg.pr.new/@rescript/linux-x64@8781

@rescript/runtime

npm i https://pkg.pr.new/@rescript/runtime@8781

@rescript/win32-x64

npm i https://pkg.pr.new/@rescript/win32-x64@8781

commit: 20777a1

@cknitt
cknitt force-pushed the rewatch-ocaml-in-process branch from 0ba9249 to 6bcbfad Compare October 12, 2026 05:57
@cknitt
cknitt force-pushed the rewatch-ocaml-in-process branch from 6bcbfad to 6a72ee9 Compare October 12, 2026 06:08
@cknitt
cknitt force-pushed the rewatch-ocaml-in-process branch 2 times, most recently from 99ad4fe to 20777a1 Compare October 12, 2026 19:03
cknitt and others added 18 commits October 12, 2026 22:16
Signed-off-by: Christoph Knittel <ck@cca.io>
Signed-off-by: Christoph Knittel <ck@cca.io>
compiler-info.json records the bundled bsc.exe again, which editor tooling
uses to find the platform binaries. RESCRIPT_BSC_EXE only affects Rust.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Knittel <ck@cca.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Knittel <ck@cca.io>
Concurrent requests shared the counter and could reuse a name within one
exhaustiveness check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Knittel <ck@cca.io>
Ctype's abbreviation memos and levels, and PPX cookies, now start fresh for
each request. Parser hooks are installed once; Typetexp's unused pre_univars
is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Knittel <ck@cca.io>
On Windows this gives the same CRLF line endings as a bsc process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Knittel <ck@cca.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Knittel <ck@cca.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Knittel <ck@cca.io>
Concurrent requests mixed their sets, so Js_shake could drop a live
function definition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Knittel <ck@cca.io>
Not reached concurrently today; same pattern as the Js_analyzer race.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Knittel <ck@cca.io>
Request_local keys get a fresh value per request and are restored after it,
replacing the per-module with_fresh and reset functions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Knittel <ck@cca.io>
Partial_class_expr is gone (#8793).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZbCuRQ1Tn31bnKzLj5e72
Signed-off-by: Christoph Knittel <ck@cca.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZbCuRQ1Tn31bnKzLj5e72
Signed-off-by: Christoph Knittel <ck@cca.io>
bsc.exe writes to its own stdout and stderr again; only in-process requests
capture. A request that raises still reports what it wrote before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZbCuRQ1Tn31bnKzLj5e72
Signed-off-by: Christoph Knittel <ck@cca.io>
The first signal still waits for modules compiling in-process; a second one
releases the held build locks and exits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZbCuRQ1Tn31bnKzLj5e72
Signed-off-by: Christoph Knittel <ck@cca.io>
setup_env and the drivers set the same flags again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZbCuRQ1Tn31bnKzLj5e72
Signed-off-by: Christoph Knittel <ck@cca.io>
In-process requests run in a different directory than the process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZbCuRQ1Tn31bnKzLj5e72
Signed-off-by: Christoph Knittel <ck@cca.io>
cknitt and others added 4 commits October 12, 2026 22:16
Threads may handle INT and TERM in either order, so the immediate exit reports 130 or 143. A two-second deadline still rules out the graceful stop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZbCuRQ1Tn31bnKzLj5e72
Signed-off-by: Christoph Knittel <ck@cca.io>
The cleanup also runs when the build is interrupted while a PPX runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZbCuRQ1Tn31bnKzLj5e72
Signed-off-by: Christoph Knittel <ck@cca.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZbCuRQ1Tn31bnKzLj5e72
Signed-off-by: Christoph Knittel <ck@cca.io>
The large module from the second-signal test keeps the first build compiling. That test now checks the real output name, Slow.js.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZbCuRQ1Tn31bnKzLj5e72
Signed-off-by: Christoph Knittel <ck@cca.io>
@cknitt
cknitt force-pushed the rewatch-ocaml-in-process branch from 20777a1 to ea7a294 Compare October 12, 2026 22:27

This branch has not been deployed

No deployments
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