Skip to content

bazel: add a GraalVM native-image server - #30282

Open
sluongng wants to merge 5 commits into
bazelbuild:masterfrom
sluongng:sluongng/graalvm-native-upstream
Open

bazel: add a GraalVM native-image server#30282
sluongng wants to merge 5 commits into
bazelbuild:masterfrom
sluongng:sluongng/graalvm-native-upstream

Conversation

@sluongng

@sluongng sluongng commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds an opt-in Linux x86-64 Bazel server compiled ahead of time
with Oracle GraalVM Native Image 25.1.3. The regular JVM-based Bazel
distribution remains the default and its behavior is unchanged.

The result is a native Bazel server that starts, loads, and analyzes a
representative incremental workload faster while using materially less
resident memory:

Variant Full sequence mean vs. JVM Peak RSS vs. JVM
JVM 38.023 s 4174.3 MiB
Native 30.362 s 20.1% faster 2682.9 MiB 35.7% lower
Native + PGO 29.611 s 22.1% faster 2363.0 MiB 43.4% lower

The benchmark covers 30 sequential revisions with three complete
repetitions per variant, warm disk and repository caches, and equal
8 GiB committed heaps. It runs build --nobuild //src:bazel-bin-dev,
so these numbers specifically measure process/server startup, loading,
and analysis rather than execution-heavy build throughput.

Prerequisites merged separately

Three compatibility fixes previously carried at the bottom of this
stack have landed separately and are now provided by the rebased
master:

  • #30284 registers the
    private tar marker charset.
  • #30285 tolerates
    runtimes without garbage-collector MXBeans.
  • #30286 handles a
    missing java.home property.

Those changes remain prerequisites for the native server, but no longer
appear in this PR's diff.

Commit stack

  1. build: add a Bazel native-image rule

    Adds the native-image rule, GraalVM 25.1.3 toolchain pin,
    reachability metadata generation, JNI/reflection configuration, and
    the libmanagement_ext.so side output needed by the G1 image.

  2. bazel: launch a native-image server

    Packages the native server into a Bazel binary and adds a dedicated
    client path that preserves Bazel's client/server and logging
    protocols without passing JVM-only startup arguments.

  3. repository: interpret Wasm in native images

    Keeps Chicory's compiled path on the JVM and falls back to its
    interpreter under Native Image, where dynamic class definition is
    unavailable.

  4. test/shell: exercise native Bazel

    Routes a focused shell/integration suite through the native binary,
    covering startup options, client behavior, sandboxing, remote
    execution, Unicode, Wasm, run/test behavior, and reduced-JDK
    packaging.

  5. benchmark: compare native Bazel with PGO

    Adds reproducible JVM, native, instrumented, and PGO benchmark
    targets and preserves the generated timing, profile, and memory
    artifacts.

The support is intentionally scoped and opt-in:

  • The current platform is Linux x86-64 with an x86-64-v2 image.
  • The packaged non-PGO binary is approximately 332 MiB, compared with
    approximately 168 MiB for JVM Bazel; the measured PGO package is
    approximately 292 MiB.
  • A native-image build is expensive: the latest local build took
    3m32s and peaked at 11.5 GiB RSS. The image and PGO targets are
    therefore tagged manual and are only built when explicitly
    requested or by the native integration configuration.
  • PGO profiles are generated by the benchmark harness rather than
    checked into the repository.
  • Native Image currently reports experimental bundle and deprecated
    reachability-option warnings. These are visible maintenance points,
    not hidden assumptions.

Motivation

Bazel commands in incremental development loops can spend a meaningful
fraction of their time starting the server, loading packages, and
performing analysis. This prototype demonstrates that an AOT-compiled
server can reduce both latency and resident memory while retaining
Bazel's existing client/server architecture and exercising
representative integration behavior.

This is not a proposal to replace JVM Bazel. It establishes an opt-in,
tested, and reproducible foundation for evaluating native Bazel,
measuring its tradeoffs, and deciding which pieces should be generalized
or moved into shared GraalVM rules over time.

Build API Changes

No.

This adds internal build and test targets plus a test-only build
setting. It does not change a public Starlark API, provider, native
rule, default command-line behavior, or the JVM distribution.

Under the native-image server only, the existing Wasm compilation mode
becomes best-effort and falls back to interpretation because Native
Image cannot define new classes dynamically.

@sluongng

Copy link
Copy Markdown
Contributor Author

@fmeum Would you be willing to take a look at this GraalVM native-image prototype? The PR is structured as an eight-commit walkthrough and includes the benchmark scope and tradeoffs in the description.

@fmeum
fmeum self-requested a review July 15, 2026 10:48
// * is internal to CompressedTarFunction.
// This is best served by a cryptographically random UUID, generated at startup.
private static final String NAME = UUID.randomUUID().toString();
// Native Image needs a stable name to register this charset ahead of time. The marker remains

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The second part of the comment is written like a PR description, but it's a code comment. Replace "remains" with "is"

@fmeum

fmeum commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

@sluongng The first three commits LGTM with one comment, could you send them as separate PRs?

@sluongng

Copy link
Copy Markdown
Contributor Author

yeah let me cherry-pick them to separate PRs

@sluongng
sluongng force-pushed the sluongng/graalvm-native-upstream branch from cbbf732 to 2eb8024 Compare July 15, 2026 11:29
copybara-service Bot pushed a commit that referenced this pull request Jul 16, 2026
This change is needed because some Java runtimes omit the java.home
system property. Passing that null value into Bazel path encoding makes
bazel info fail instead of reporting the available runtime metadata.

Return unknown when the property is absent, matching the existing
fallback for an unencodable value. Cover the exact output while leaving
normal JVM path normalization unchanged.

Extracted from #30282.

Closes #30286.

PiperOrigin-RevId: 949017883
Change-Id: I8491dd68298640d72cc0eb621410da0d367da89f
copybara-service Bot pushed a commit that referenced this pull request Jul 17, 2026
This change is needed because some Java runtimes expose no garbage
collector MXBeans. Bazel treats an empty list as an unsupported
collector and aborts server startup before it can run a command.

Treat an empty bean list as an environment without memory-pressure
notifications. Log a warning when the listener is created because
optional caches cannot be limited based on heap usage and peak memory
may be higher. Preserve the existing error for a non-empty list of
unknown collectors, so ordinary HotSpot diagnostics remain unchanged.

Cover listener initialization, reset, and warning output with no
collector beans.

Extracted from #30282.

Closes #30285.

PiperOrigin-RevId: 949540973
Change-Id: I9f6cbf775544d4efe44014b94e0c5b74c8858f3f
copybara-service Bot pushed a commit that referenced this pull request Jul 21, 2026
Commons Compress decodes UStar names with a caller-selected charset
but always decodes PAX paths as UTF-8. Bazel uses a private marker
charset to distinguish those sources and preserve raw UStar bytes
without corrupting PAX Unicode names.

Native Image cannot resolve that charset when its lookup name changes
at startup and its provider is hidden from enumeration. Give the
charset a stable private name, keep the collision marker random,
and expose the provider charset for ahead-of-time registration.

Add a Latin-1-only PAX regression because names outside Latin-1 masked
the lookup ambiguity in the existing test.

Extracted from #30282.

Closes #30284.

PiperOrigin-RevId: 951408843
Change-Id: I96e3e0e322ec6d833987b77631b8f588048c4009
@sluongng
sluongng force-pushed the sluongng/graalvm-native-upstream branch from 2eb8024 to 58c1528 Compare July 22, 2026 08:07
sluongng added 5 commits July 22, 2026 10:37
Bazel cannot use the general rules_graalvm native_image rule because
its server needs generated reachability metadata and the
libmanagement_ext.so side output. The earlier genrule prototype also hid
inputs and resource requirements from remote execution.

Add a Bazel-specific Starlark rule that models metadata generation,
bundle creation, and image compilation as declared actions. Pin Oracle
GraalVM 25.1.3 for Linux x86-64 and preserve the G1 side outputs needed
by the packaged server.

Keep Bazel internal paths in Latin-1 strings while retaining UTF-8
native encoding. Register the private tar charset and defer platform,
Netty, and encoding state whose image-build initialization is unsafe.
Enable native thread-dump monitoring so SIGQUIT preserves the existing
debugging contract.

This commit defines the build mechanism and auditable metadata.
Packaging and remote-provider placement remain separate concerns.
The existing Bazel package always installs A-server.jar and starts it
through a JVM. A compiled server therefore needs a matching archive
and client launch path before it can serve normal Bazel commands.

Add a native package that installs A-server-native and its required JNI
library without carrying the redundant server jar or build diagnostics.
Teach a dedicated client build to start that executable without JVM-only
arguments, while leaving the existing JVM package and client unchanged.

Pass the common startup protocol and logging properties to the native
server. Select Latin-1 for Linux JNU path conversion while retaining
a UTF-8 process locale. Reload LogManager configuration at native
startup because Native Image initializes it while building the image.

Limit the artifact to Linux x86-64. Provider-specific placement and
resource hints remain outside this portable package.
This change is needed because Chicory compilation defines Java
classes at runtime, which the closed-world GraalVM image does not
support with the selected G1 runtime. Enabling the repository Wasm
compilation flag aborts an otherwise portable native Bazel operation.

Treat compile as an optimization hint under Native Image and use the
Chicory interpreter there. Keep compiled execution unchanged on the JVM,
and update the flag and API documentation to state the native behavior.

Exercise the interpreter while the compilation feature flag is enabled
so the native compatibility path cannot silently regress.
This change is needed because the shell harness always selects the
packaged JVM Bazel, so it cannot measure native-image compatibility
through existing integration tests.

Add a build setting and runfiles wrapper that select the native binary.
Keep its large output and install state outside constrained test
sandboxes, capture server diagnostics, and avoid cleanup commands when a
test never started an inner Bazel server.

Retain the existing JVM assertions and add native contracts instead of
skips. Prove that server_javabase remains accepted without executing a
JVM, stall logging initialization to exercise the client startup
timeout, and require SIGQUIT to emit a native thread dump without
restarting the server. Every selected native test function now executes
assertions.
This change is needed because native-image performance claims are not
actionable without a repeatable comparison against the JVM baseline
and the non-PGO native binary.

Add instrumented and profile-guided image targets plus a Hyperfine
report runner. Train over an incremental commit sequence, prewarm
disk and repository caches in an unmeasured pass, restart daemons
between runs, and retain Bazel command and memory profiles for later
attribution.

Require a dedicated detached worktree before any reset or clean
operation.  Keep generated PGO data outside the source tree except
for the checked, temporary copy consumed by the PGO build. Leave
remote build configuration as an explicit caller choice.
@sluongng
sluongng force-pushed the sluongng/graalvm-native-upstream branch from 58c1528 to 2a4af3a Compare July 22, 2026 08:37
@sluongng
sluongng marked this pull request as ready for review July 22, 2026 08:38
@sluongng
sluongng requested a review from a team as a code owner July 22, 2026 08:38
@github-actions github-actions Bot added team-ExternalDeps External dependency handling, remote repositiories, WORKSPACE file. team-Rules-CPP Issues for C++ rules team-Remote-Exec Issues and PRs for the Execution (Remote) team awaiting-review PR is awaiting review from an assigned reviewer team-Loading-API BUILD file and macro processing: labels, package(), visibility, glob labels Jul 22, 2026
@sluongng

Copy link
Copy Markdown
Contributor Author

@fmeum, the first 3 commits are now in master, so I rebased this PR to get rid of them.

The setup now has 5 instead of 8 commits.

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

Labels

awaiting-review PR is awaiting review from an assigned reviewer team-ExternalDeps External dependency handling, remote repositiories, WORKSPACE file. team-Loading-API BUILD file and macro processing: labels, package(), visibility, glob team-Remote-Exec Issues and PRs for the Execution (Remote) team team-Rules-CPP Issues for C++ rules

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants