D561 - The per-thread scheduling record D523 said would be needed
Status: guest-observed Date: 2026-09-04
The prediction, and its arrival
D523 accepted scePthreadSetaffinity, dropped what it was
given, and said exactly why that was tolerable and exactly when it would
stop being:
orbistoun keeps no per-thread scheduling record - so the mask is accepted and dropped. The moment something does, this needs the per-thread record rather than a wider
Ok.
Something does. PPSA02664 calls scePthreadGetschedparam
34 times, and a get is a read-back: a setter that
dropped what it was given would answer with a value the guest never set.
So ThreadRecord gains a policy and a cancellation state
beside the priority it already carried.
What the guest fixed, and what it did not
Arities came from the run, the same way D516, D523 and D524 got
theirs - the register holding 0x7fff_0001, orbistoun's own
placeholder, marks where the arguments stop:
| Call | Arity | From |
|---|---|---|
scePthreadGetschedparam(thread, policy, param) |
3 | already recorded; the run agrees |
scePthreadSetschedparam(thread, policy, param) |
3 | handle, 0x4000, a stack pointer |
scePthreadSetprio(thread, priority) |
2 | handle, 0x100 |
scePthreadRename(thread, name) |
2 | handle, a pointer |
pthread_setcancelstate(state, oldstate) |
2 | 1, a stack pointer |
scePthreadAttrGetstackaddr was left
alone, and that is the decision worth recording. Its
arg0 and arg1 in the run are
byte-for-byte identical to
scePthreadAttrGet's, and its arg0 is
a thread handle where an attribute object belongs. Nothing here
explains that; D527 is this project mistaking a register that survived a
tail-jump for an argument, and one unexplained coincidence is not enough
to implement against.
The four-byte rule, in the place it bites
The guest passes policy at 0x…c86c and
param at 0x…c868 - four bytes
apart, because a sched_param is a single
int and a caller putting both on its stack puts them
adjacent. An eight-byte write to either destroys the other, and the
guest would then read a policy it never set with nothing in any trace to
say why.
That is D272's lesson arriving somewhere unmissable, and it is guarded with a sentinel either side rather than by inspection.
The policy is stored and not interpreted
PPSA02664 passes 0x4000. That is no POSIX policy
constant - those are small integers - so it is a vendor value, nothing
here knows what it selects, and nothing here pretends to. Storing it
verbatim is what lets a get hand back what a set was given, which is the
whole of what a setter promises. Neither policy nor priority is
applied: which host thread runs when is the host scheduler's
business, exactly as scePthreadAttrSetaffinity already
records for affinity.
What it changed
| before | after | |
|---|---|---|
| unimplemented functions called | 35 | 26 |
| calls landing on a stub | 914 | 32 |
The problem this exposes in the record
None of it moves a recorded metric.
Status::standing is an integer percentage of calls that
reached an implementation, and 914 stubbed calls out of 419,091 already
rounded to 100% - so does 32. Reach, imports and frames are unchanged. A
96% reduction in calls resting on placeholders is invisible to the
compatibility record, which is a gap in the record rather than in the
work, and is recorded here rather than fixed in the same change.
What this does not establish
That any of it is correct, only that it round-trips. A get hands back what a set stored; if the vendor's default policy is not zero, a guest reading one before setting one gets a value no console would give it. Zero is written as "nobody said" because no lawful source here gives the default, and that is the assumption most likely to be wrong.
Nor that the priority is a plain int.
POSIX's sched_param is, and the guest's own spacing agrees,
but a vendor field past it would sit beyond what this writes and read as
whatever the caller left on its stack.