D484 - One stub table across every module, not one table each

measured - 2026-09-03 (falsification test in orbistoun-thunk/tests/answers.rs)

D483 leaves the title's own modules placed and unrelocated. Relocating them needs each one's own imports answered, which means each one needs stubs. The obvious shape - a thunk table per module, at its own base - cannot work, and the reason is worth writing down before anything is built on it.

What the dispatch layer actually is

Every table behind a stub is a process-global OnceLock, indexed by the module's dynamic symbol index:

HANDLERS          FLOAT_HANDLERS    STUB_RETURNS      FORCED_RETURNS
COUNTS            READABLE          WRITABLE          FORCED
DUMPED_PER_IMPORT DATA_SYMBOLS

Service::build_thunks fills them, and install_handlers is documented as ignoring a second call: two guests in one process is not something this supports. That was written for the main executable and it is right for that.

It means a second build_thunks, for a title module, would silently do nothing - its handlers dropped, its is_implemented answers coming from the executable's table, indexed by a symbol index belonging to a different module. Every count the run reports would be wrong, and nothing would say so.

That is a stub-shaped failure one level up: plausible output from a table that was never installed.

The design

One table, with a slot range per module. Module M's symbol i occupies slot offset(M) + i. Every global table stays exactly as it is - one array, one index space, one install - and gains only the property that its index space now spans more than one module.

What that buys, beyond working at all:

What it costs: build_thunks takes a set of modules rather than one, and the mapping from (module, symbol index) to slot becomes a thing that has to be carried to relocation. That is the actual work of the next unit.

Why not the alternative

Making each global per-module - a map from module id to table - touches every accessor on the guest call path. Those run on a sysv64 frame with no room for a context (which is why they are globals in the first place), so a module id would have to be threaded through the stub itself. It is a much larger change to reach the same place, and it makes the hot path slower to answer a question that a flat index already answers.

Status

Measured, and the guard has been watched failing. answers.rs now installs a handler for a slot the first install left empty and asserts it does not take. Inverting the assertion fails with the message naming this decision, so the check is known to be able to reject - it is not a test that passes because nothing happened.