Skip to content

Give the HLE InitThread libkernel's thread setup - #287

Open
Fiztba wants to merge 1 commit into
ran-j:mainfrom
Fiztba:hle-initthread
Open

Fiztba wants to merge 1 commit into
ran-j:mainfrom
Fiztba:hle-initthread

Conversation

@Fiztba

@Fiztba Fiztba commented Oct 13, 2026

Copy link
Copy Markdown

The runtime's InitThread handler returned the main thread's id and nothing
else. libkernel's InitThread, which _InitSys runs before main, creates a
semaphore and a priority-0 topThread waiting on it, then lowers its caller
to priority 1. A game bound to the handler kept its bootstrap at priority 0,
so RotateThreadReadyQueue(1) never let its priority-1 workers run. King's
Field IV's opening movie stalled on its first frame with the decoder starved.

EeScheduler::initThread() now:

  • creates the semaphore (init 0, max 255) and a priority-0 thread record
    waiting on it, so later semaphore/thread ids, and the status and
    priorities ReferThreadStatus reports, match the hardware (its entry,
    stack and gp read as 0);
  • lowers the caller to priority 1 through changePriority (the handler then
    calls transferIfRequested, like ChangeThreadPriority);
  • returns the new thread's id, and −1 on a second call.

The stand-in never runs. topThread exists only to replay requests that
libkernel's i* wrappers post to it, and the runtime binds iWakeupThread
and iRotateThreadReadyQueue to handlers that act on the scheduler directly.
For this reason, bind InitThread together with those wrappers, never alone:
with the handler the game's own topThread never exists, so a recompiled
wrapper that posts to it fails (returns −1). iSuspendThread has no handler
and is not in the exporter's list; a game that suspends the current thread
from an interrupt through it would need one.

Not in this PR: createThread still rejects priority 0, so a recompiled
(non-stubbed) libkernel InitThread fails the same way. That is a
separate one-line change (priority < 0).

Tests: a ps2xTest kernel case runs InitThread from a main thread, checks the
returned id, topThread's wait state and priority, the caller's priority, the
−1 repeat call and the next thread id, then shows RotateThreadReadyQueue(1)
yielding to a priority-1 worker (with the old handler the trace is
{1, 2, 3}: the worker never runs).

The ps2xTest case and its control were run on main 2c5fbb9 (437/438 pass;
the one failure, "VU0 macro mappings cover all S1/S2 enums", reads
instructions.h from the working directory and fails without this change
too). The game check below ran on the holyland patch series (which also
includes Vulkan GS and accepts priority 0 in createThread) with this
commit applied.

Verified on King's Field IV with InitThread bound to the handler: the
opening movie plays to its natural end and gameplay is reached by tick 12000.
The movie frames (ticks 500–7500) are pixel-identical to a build that
recompiles the game's own InitThread, over two runs of each. Later 3D frames
show the same scenes but vary from run to run in both builds, so they do not
compare. The old handler stalls at tick ~2200 (0 flips/s).

🤖 Generated with Claude Code

libkernel's InitThread, which _InitSys runs before main, creates a
semaphore and a priority-zero topThread that waits on it, then lowers
its caller to priority one. The runtime handler did neither and
returned the main thread's id. A game whose InitThread is bound to it
keeps its bootstrap thread at priority zero, so RotateThreadReadyQueue
yields to its priority-one workers never let them run: King's Field IV's
opening movie stalls with its decoder thread starved.

topThread only replays requests the libkernel i* wrappers post to it.
The runtime binds those wrappers (iWakeupThread, iRotateThreadReadyQueue)
to handlers that act on the scheduler directly, so its stand-in never
has to run. It still takes the semaphore and thread ids the real one
holds and waits on its semaphore, so later ids, and the status and
priorities ReferThreadStatus reports, match the hardware. The handler lowers the caller to priority one,
returns the new thread's id, and returns -1 if called again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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