D616 - The record asked the wrong thread

Status: measured Date: 2026-09-08

What the mapping list said

ORBISTOUN_TRACE_MAPS names, for every mapping the guest was given, the call it was made during:

26  call 459349  0x740001980000 +0x80000  r arena  during libc::memcpy
31  call 459433  0x740001b00000 +0x40000  r asked-for  during libkernel::scePthreadAttrInit
33  call 459525  0x740001b80000 +0x40000  r asked-for  during libc::memcpy
35  call 459591  0x740001c40000 +0x40000  r asked-for  during libkernel::scePthreadCreate

memcpy maps nothing. Nor does scePthreadAttrInit. Those mappings were made by sceKernelReserveVirtualRange and sceKernelMapDirectMemory on one guest thread and labelled with whatever a different thread had most recently entered.

Why

orbistoun_thunk::last_call reads the whole recording ring and answers with the newest call by sequence - across every thread:

let newest = (0..MAX_RECORDED_CALLS)
    .filter(|i| RING[*i].load(Ordering::Relaxed) != 0)
    .max_by_key(|i| RING_SEQ[*i].load(Ordering::Relaxed))?;

That is the right answer to "what was this process doing", which is the fault handler's question and the reason the function exists. It is the wrong answer to "what am I inside", which is what the mapping record asks - and while the guest was single-threaded the two agreed, so the difference had no way to show.

The list is truthful about where and how much; it was wrong about who, in the field a reader uses to decide which implementation to look at.

The change

current_call is a thread-local u32 - the import this thread is inside, plus one, zero for none - set around the handler and restored after. A stack rather than a store, because calls nest through callbacks, and restoring the outer value keeps its identity for the rest of its body.

One word in thread-local storage, written twice per call. Recording must not allocate and must not change what it observes (principle 9); this does neither.

last_call is unchanged and keeps its own meaning, which is now written down beside it. Two questions that were being answered by one function are two functions.

26  call 459728  0x740001e00000 +0x80000  r arena      during libkernel::sceKernelReserveVirtualRange
32  call 459978  0x740002080000 +0x400000  r asked-for  during libkernel::sceKernelMapDirectMemory

And the queue item it retires

"The mapping sequence still varies where the import count no longer does" has been on the work list since D604. It is not a defect to fix.

Two runs of one build agree on the first twenty-six mappings exactly and diverge from the point the guest's second thread starts allocating. The count differs too - 61 mappings in one run, 57 in the next - because each thread gets as far as a wall-clock budget lets it. That is a multithreaded guest under a time limit, and removing it needs a deterministic scheduler, which is a concept nobody has proposed.

What matters is already stable: the distinct import count, which is what every progress comparison rests on, has been 193 four times and 196-197 four times since D604 fixed it. The mapping order was never the measurement.