Repository navigation
Interpret calls from compiled code to cold methods without unwinding the caller - #27
Conversation
|
No actionable comments were generated in the recent review. Summary
Merge Risk:
|
There was a problem hiding this comment.
This PR optimizes compiled-to-cold calls by interpreting the callee nested on the JS stack (via callNested) instead of returning to the run loop. A direct flag on frames routes returns to t.ret and exceptions to JavaThrow. Fallback to the run loop occurs for synchronized methods, depth limits, native frames, thread waits, or budget exhaustion. Performance improves significantly (3.18M → 0.57M run loop entries). However, raise throws JavaThrow immediately when popping a direct frame, without continuing to unwind outer direct frames. This breaks exception handlers in nested direct calls (e.g., an interpreted caller catching an exception from a deeper direct callee).
review-bot, model nvidia/nemotron-3-ultra-550b-a55b:free, verdict request_changes
There was a problem hiding this comment.
This PR optimizes calls from compiled code to cold methods by interpreting them nested on the JS stack (via callNested) instead of returning to the run loop. The interpreter now marks such frames direct so returns go to t.ret and exceptions become JavaThrow. slowCall delegates to hooks.callNested for non-native methods. A new test NestedCalls.java covers results, exceptions, monitors, recursion, stack overflow, and thread waits. However, the callNested loop wraps only JIT calls in a try-catch; interpret calls can also throw JavaThrow from raise for direct frames, which would escape and crash the compiled caller. Also, the depth check uses t.base which is not visible in the provided JThread definition and should be verified.
review-bot, model nvidia/nemotron-3-ultra-550b-a55b:free, verdict request_changes
There was a problem hiding this comment.
The PR reworks how compiled code calls cold methods, moving from the run-loop save/restore pattern to nested interpretation on the JS stack. A new callNested function interprets callee methods directly with a direct frame marker, returning the result to t.ret or PUSHED/INTERPRET depending on whether the call can finish in place. The thread budget model shifts from per-method budget to a shared t.slice, affecting both compiled backward branches and in-place interpreted instructions. Performance results show reduced run loop entries and improved wall/user time.
review-bot, model nvidia/nemotron-3.5-lightning:free, verdict approve
When compiled code called a method that was not compiled yet, the caller saved its state into its frame and returned to the run loop. The interpreter then ran the callee, and the caller resumed from the saved state. javac has hundreds of visitor methods that each get fewer than 500 calls, so compiling 30 files made 3.2 million of these trips.
Now
slowCallruns such a callee in the interpreter right away, nested on the JS stack the way a direct compiled call runs (callNestedininterp.ts). The callee's frame is markeddirect:t.ret.raisethrows it to the compiled caller as aJavaThrow.directmark.callNestedthen returnsPUSHED, and the caller saves its state as it does for a native that waits.Thread fairness. Each compiled method used to yield after 4000 of its own backward branches, and loops in the methods it called did not count. Interpreted callees used to run in the run loop, so their instructions counted against the thread's slice and ended it. Doing that work in place let a thread spinning on compiled calls hold the CPU:
java/util/concurrent/ConcurrentHashMap/ToArray.javatimed out because its checking thread starved the threads filling the map. NowJThread.sliceholds what is left of the run loop's budget while compiled code runs. Every backward branch in compiled code and every instruction interpreted in place spend it, so loop work anywhere on the stack ends the slice.Results on javac compiling 30 files, 3 interleaved rounds:
Peak memory is about the same, and javac's output is byte-identical.
bench/Bench.javais unchanged within noise. The ConcurrentHashMap jtreg tests pass, ToArray in 6.4 s.test/java/NestedCalls.javacovers compiled code calling cold methods:Tests:
pnpm testpasses (130 tests).JW_JIT_STATSnow also prints the total number of run loop entries.