Skip to content

perf(jit): emit 13% less JS and keep less per method, cutting javac's heap by 13% - #88

Merged
Project516 merged 6 commits into
masterfrom
p516/jit-heap
Nov 10, 2026
Merged

Project516 merged 6 commits into
masterfrom
p516/jit-heap

Conversation

@Project516

Copy link
Copy Markdown
Owner

What this changes

The JIT emits less JavaScript and the VM keeps less per method, so javac and other large programs use less heap; a few hot JDK paths get faster natives.

  • Generated code shares constant slots and slow-call exit code, calls out of line after a failed inlining guard (passing up to four argument slots as parameters), and drops null checks on this and on a receiver a class guard already checked. Generated JS for javac compiling 30 files: 9.43 MB to 8.22 MB (-13%).
  • A method's bytecode view, attributes and LocalVariableTable are built only when read, and a compiled method's analysis is dropped unless the method can be inlined.
  • Unsafe's acquire, release and opaque accessors, getAndAdd, getAndSet and weak CAS run as natives, as HotSpot intrinsifies them. Threads never interleave inside a native here, so each is a plain read and write.
  • byte[] reads and writes of wider values compose bytes instead of making a DataView per array.

Tests

Heap and time measured master against branch, alternating runs:

  • Heap after javac compiles 30 files: about 167 MB to 146 MB (-13%). Javac wall time: the ranges overlap, no measurable change.
  • AtomicInteger.incrementAndGet, 1M calls: 373 ms to 128 ms.
  • ByteBuffer.wrap(new byte[16]), 300k putLong/getLong: 661 ms to 593 ms.
  • Slow OpenJDK tests (seconds): TransferTo 211 to 137, UUIDTest 329 to 221, EntryCount64k 70 to 66; Collisions and ZipFileSharedSourceTest show no change beyond the spread.
  • New programs compared with HotSpot: JitGuardMiss, ReflectAttrs, UnsafeAtomics, ByteArrayViews.
  • Full suite: 251 pass, 0 fail, 3 skipped. The 254 passing.txt OpenJDK tests under the touched directories pass.

Noticed, not fixed

  • MD5's implCompress0 is not a native yet and shows up hot in profiles of tests that hash a lot.
  • Per-call overhead (enter, the run loop) and ConcurrentHashMap.tabAt loops remain the largest costs in the slow tests.

@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 37 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 9a6f1082-34e2-430b-ae05-0b4e8ea8cc75
Commits

Reviewing files that changed from the base of the PR and between 8541426 and 8841153.

Files selected for processing: 8

  • src/classfile.ts
  • src/jit.ts
  • src/natives/misc.ts
  • src/runtime.ts
  • test/java/ByteArrayViews.java
  • test/java/JitGuardMiss.java
  • test/java/ReflectAttrs.java
  • test/java/UnsafeAtomics.java
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

… guard-miss calls

javac compiling 30 files: generated JS 9.43 MB to 8.22 MB (2491 methods),
heap after a run 167 MB to 160 MB (median of 3 each).

- one constant slot per value instead of one per use
- the slow tail of a call takes its exit codes from slowCall
- a call after a failed inlining guard goes through rt.callv
- no null check on `this`, or on a receiver already class-guarded
- deopt frames name the callee once, and return tails pass one letter
…able table only when read

javac compiling 30 files, heap used after a run: 161.4 MB to 154.1 MB
(median of 3). A snapshot showed 61k Uint8Array views and 15k attribute
maps; most methods are never run or reflected on.
… be inlined

javac compiling 30 files, heap used after a run: 154.1 MB to 146.7 MB
(median of 3). The analysis sets and maps of every compiled method were
kept for the life of the VM.
…nd weak CAS as natives

Microbenchmark, median of 5 processes: AtomicInteger.incrementAndGet x1M
373 ms to 128 ms, AtomicLong.addAndGet x1M 280 to 153 ms,
AtomicReference.getAndSet x1M 446 to 347 ms. HotSpot intrinsifies the same
methods; here they were Java loops over the volatile natives.
…ew per array

A heap ByteBuffer or VarHandle view over a fresh byte[] made a DataView for
it; short-lived arrays now go through byte arithmetic and a shared scratch.

@project516-review-bot project516-review-bot Bot 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.

This PR reduces heap usage by 13% through lazy parsing of classfile attributes (bytecode, LocalVariableTable, member attrs), JIT optimizations (constant sharing, shared slow-call exits, out-of-line guard-miss calls, null-check elimination via non-null tracking), and native implementations of Unsafe ordered/weak atomics plus byte[] composed access. Tests pass (251/0/3). The changes are largely correct but two areas need verification: the 4-argument limit in callv and the H.deliver type-character change.


review-bot, model nvidia/nemotron-3-ultra-550b-a55b:free, verdict approve

Comment thread src/natives/misc.ts Resolved
@Project516

Copy link
Copy Markdown
Owner Author

On the two points in the review summary, both checked against the code:

  • callv and the 4-slot limit: genGuardMiss (src/jit.ts) uses callv only when target.argSlots <= 4, receiver included, and callvMany with an array otherwise. A callee with fewer slots gets undefined for the unused ones, which its compiled function never reads.
  • H.deliver type character: deliver (src/interp.ts) only tests ret !== "V", ret === "J" || ret === "D", and through normalize, ret === "Z". Passing "L" for an array return, and the first letter otherwise, gives the same result for every return type.

@Project516
Project516 enabled auto-merge (squash) November 10, 2026 19:29
@Project516
Project516 merged commit 688c5d8 into master Nov 10, 2026
16 checks passed
@Project516
Project516 deleted the p516/jit-heap branch November 10, 2026 19:34
@Project516 Project516 mentioned this pull request Nov 10, 2026
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