Skip to content

[Host/Memory] Bound repeated executable compilation - #1167

Draft
brandonpayton wants to merge 4 commits into
mainfrom
fix/executable-module-cache-standalone-qk044
Draft

[Host/Memory] Bound repeated executable compilation#1167
brandonpayton wants to merge 4 commits into
mainfrom
fix/executable-module-cache-standalone-qk044

Conversation

@brandonpayton

@brandonpayton brandonpayton commented Jul 31, 2026

Copy link
Copy Markdown
Member

Why

Stock Brew starts many short-lived Ruby and utility processes while it
taps a repository. Kandelo retires each process address space, but
repeated launches still create heavy host-runtime work.

Tracing that path found one avoidable cost: every spawn and exec
compiled the executable's WebAssembly bytes again, even when the same
Ruby binary had been compiled moments earlier.

This change bounds and reuses that compiled code. It is an independent
host-runtime improvement, but Chromium evidence now shows that it is
not the complete fix for the Brew renderer crash.

What changed

  • Share one compiled-executable cache between the Node and browser
    kernel-worker paths.
  • Identify executable content by exact byte length and SHA-256, so
    aliases share code while changed bytes cannot run stale code.
  • Keep at most eight modules representing 64 MiB of source bytes.
  • Compile an oversized executable without retaining it.
  • Coalesce concurrent compilation of identical bytes.
  • Remove failed entries so a later launch can retry.
  • Fence asynchronous lookups across cache clearing.
  • Clear the cache when the host initializes or is destroyed.
  • Preserve the existing initial-loader error for malformed WebAssembly.

The cache retains compiled modules, not process address spaces. Every
process still receives a fresh WebAssembly.Memory, and pthreads still
share only their owning process's memory.

Evidence

A matched Node stock-Brew measurement used the same exact public
fixture with and without this cache:

  • maximum RSS fell from 12,614,729,728 to 10,356,031,488 bytes;
  • sampled process-tree mean fell from 8.366 to 5.900 GiB;
  • sampled process-tree maximum fell from 10.430 to 6.991 GiB;
  • user CPU time fell from 703.87 to 439.39 seconds.

The maximum-RSS change is 17.91 percent. The user-CPU change is
37.58 percent. These are bounded Node results, not broad performance
claims.

Focused cache, rootfs-export, and spawn-parity validation passed 22 of
22 tests. Host type checking and projection checks also passed.

Chromium result

Draft diagnostic PR #1168 ran the exact public core and canary
lifecycle on fresh Chromium runners. Both reached the same bounded
cache state:

  • 160 executable requests;
  • 149 hits and 11 misses;
  • 3 evictions;
  • 8 retained entries representing 34,963,976 source bytes.

Both renderers still crashed during the common first-party brew tap
phase, with no cgroup OOM event. The cache works, but another source of
short-lived process pressure remains.

The sealed fixture uses the older fork-heavy Ruby bottle. It does not
contain #1166's direct-posix_spawn path. Per-launch hashing of fresh
executable snapshots also remains under investigation.

Keep this pull request in draft until the revised Ruby lifecycle and
the browser cache cost are separated. Do not use its Node result alone
to claim that Chromium is fixed.

Scope

This changes the shared host runtime for Node and browser. It does not
change the kernel, POSIX behavior, guest ABI, package contents,
bottles, or VFS format.

Repeated spawn and exec of the same VFS executable compiled another
WebAssembly.Module every time. Short-lived Homebrew commands could
therefore leave many compiled-code allocations awaiting engine
collection after their process memory had retired.

Key one bounded, cross-host LRU by the complete prepared byte
snapshot length and SHA-256. This lets aliases share compilation while
a write or replacement always selects new code. Coalesce concurrent
misses, compile oversized modules uncached, and preserve the existing
malformed-initial-image trap path.

Cover content aliases, same-length replacement, concurrent reuse,
eviction, failed compilation, and a real Node VFS replacement.
Precompiling initial executables in the kernel Worker must not change
how a malformed initial module fails. A CompileError still belongs to
the process-Worker loader path, while exec and posix_spawn reject bad
modules during preflight.

Launch a header-valid but truncated module through the real Node host.
Verify that it creates the process, exits through the loader-error path,
and reports the existing host diagnostic.
Keep the new cache and its focused unit tests consistent with the host
TypeScript formatting contract.
Document the measured working set behind the default limits.

Clarify that source size is a portable proxy, not native-code size.

Lock both defaults with a focused policy test.
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