Skip to content

Yieldpoint

opencode-agent[bot] edited this page May 10, 2026 · 2 revisions

Yieldpoint

A compiler-injected checkpoint in compiled code that triggers INT 0x30, invoking the context-switch handler in VmProcessor.reschedule(). Yieldpoints enable cooperative scheduling; methods marked @Uninterruptible have yieldpoints suppressed.

Overview

Yieldpoints are the cooperative mechanism by which JNode's thread scheduler gains control during normal code execution. Unlike preemptive scheduling (which uses timer interrupts), yieldpoints are inserted by the JIT compiler at strategic points in compiled bytecode. When a yieldpoint fires, it triggers INT 0x30, which transfers control to VmProcessor.reschedule() to potentially switch to another thread.

This design achieves several goals:

  • Low overhead: The common path (no switch needed) is just a CMP + conditional jump, optimized for branch prediction
  • Deterministic behavior: @Uninterruptible methods never yield, ensuring atomic operations
  • Integration with TSI: The Thread Switch Indicator flags control when switches can occur

Key Components

File/Class Role
VmProcessor (line 419) Contains reschedule() — the @Uninterruptible method called when yieldpoint fires
X86CompilerConstants.YIELDPOINT_INTNO = 0x30 The interrupt vector used for yieldpoints (line 78)
X86CompilerHelper.writeYieldPoint() Generates the CMP + INT sequence in compiled code (line 319)
vm-ints.asm (line 23) Low-level stub_yieldPointHandler that saves state and calls Java
VmProcessor.TSI_* flags Control when switches are allowed (line 186-207)
MethodPragmaFlags.isUninterruptible() Controls whether yieldpoints are suppressed in a method

How It Works

Compiler Insertion

The L1/L1a JIT compilers call X86CompilerHelper.writeYieldPoint() to insert a yieldpoint check at method entry and in loop headers. The generated assembly:

; Check if TSI_SWITCH_REQUESTED flag is set
cmp dword [fs:offset], TSISWITCH_REQUESTED  ; 32-bit
; or
cmp r12, offset, TSISWITCH_REQUESTED        ; 64-bit (r12 = PROCESSOR64)
je no_yp      ; Jump if flag NOT set (optimized for common case)
int 0x30      ; Trigger yieldpoint interrupt
no_yp:        ; Continue normal execution

The code is optimized for the case where no switch is needed (predict NOT taken).

Interrupt Handling

When INT 0x30 executes, the assembly handler in vm-ints.asm (stub_yieldPointHandler, line 23) saves context and calls VmProcessor.reschedule():

@Uninterruptible
final void reschedule() {
    // Get current thread
    final VmThread current = currentThread;
    
    // Add current to ready queue if still running
    if (current.isRunning()) {
        scheduler.addToReadyQueue(current, false, getIdString());
    }
    
    // Wake up any sleeping threads
    VmThread newThread = scheduler.popFirstSleepingThread();
    if (newThread == null) {
        newThread = scheduler.popFirstReadyThread();
    }
    
    // Switch to new thread
    newThread.wakeUpByScheduler();
    this.nextThread = newThread;
}

TSI Flags

The Thread Switch Indicator (VmProcessor.threadSwitchIndicator) is a 4-bit flags word:

Flag Value Purpose
TSI_SWITCH_NEEDED 0x0001 Bit set by timer interrupt to request switch
TSI_SYSTEM_READY 0x0002 Set after VM initialization completes
TSI_SWITCH_ACTIVE 0x0004 Set during context switch to prevent re-entry
TSI_BLOCK_SWITCH 0x0008 Set to temporarily block switches (e.g., in native code)

The combined TSI_SWITCH_REQUESTED = TSI_SWITCH_NEEDED | TSI_SYSTEM_READY is the value the compiler checks.

Suppression with @Uninterruptible

Methods annotated with @Uninterruptible have yieldpoints suppressed entirely. The compiler checks method.isUninterruptible() before emitting yieldpoint code (line 320 in X86CompilerHelper). This ensures atomic execution of critical sections like GC marking, lock acquisition, and interrupt handling.

Gotchas

  • Yieldpoint storms: If many threads are runnable and the scheduler round-robins quickly, frequent yieldpoints can cause performance degradation. The scheduler includes a "same thread priority count" check to detect possible infinite loops (line 473 in VmProcessor).
  • Blocking vs. yielding: A thread calling Object.wait() blocks (removes from ready queue) but does NOT use yieldpoints — it uses Object.wait() semantics. Yieldpoints only trigger on active execution.
  • Timeslice vs. yieldpoint: There are two interrupt sources: INT 0x30 (yieldpoint) and INT 0x33 (timeslice). Timeslice uses the same reschedule() path but is triggered by the timer interrupt, not by compiled code.
  • No re-entrancy: TSI_SWITCH_ACTIVE prevents nested switches; if a yieldpoint fires while reschedule() is executing, it is silently ignored.
  • 64-bit register: In 64-bit mode, the processor pointer is in R12 (defined as PROCESSOR64 in X86CompilerConstants), not the typical C calling convention.

Related Pages

  • Core-Thread-Scheduling — Parent concept covering the hybrid preemptive/cooperative model
  • TSI — Thread Switch Indicator: atomic flags that gate yieldpoint triggers, assembly-level flag manipulation
  • JIT-Compilers — Where yieldpoints are inserted into compiled code
  • Assembly-Files — Where vm-ints.asm and kernel.asm are defined
  • Thread-Scheduling — Overview of thread scheduling mechanisms
  • Interrupt-Handling — How interrupts (including yieldpoints) are routed

Clone this wiki locally