D966 - A descriptor a dispatch selects from a table at run time binds every record

Status: decided Date: 2026-10-25 known_by: guest-observed

A dispatch's buffer access can read through a descriptor the program selects from a table while it runs: an s_buffer_load_dwordx4 through the table's own descriptor, at an offset register the program computes, loads the four words, and a buffer access reads through them. The tracer follows those four registers from the load, through scalar moves and across blocks, and gives every access reading through a record of one table - one table descriptor, one place in its records - one slot, wherever the accesses lie. Any write to one of the four registers ends it.

Where only one access reads through the table's records and the host's run up to it finds the descriptor (D753), the access binds that descriptor's range as before. Otherwise the table is bound whole. The host reads the table's descriptor from the dispatch's user data, and each of its records at the record's stride, the load's immediate into it. Each record's descriptor names the range the access's reads reach through it, by its own bounds rules, and those ranges' pages go into the dispatch's global buffer of pages (D961). A dispatch with no global access of its own has one for them. The record's own slot binds nothing. The module reads through the descriptor the program loaded: it applies that descriptor's bounds, forms the guest address from its base, and finds the word in the pages. A word the pages do not hold reads zero, and where the descriptor's bounds admit the access it is an escape, so the dispatch's result is refused (D950). A record naming no buffer, a base or a size of zero, binds nothing, as the hardware reads zero through it.

A format load converts by the fourth word its records share in format and selects (D738). Records that disagree leave the slot without one, and a format load through it is refused.

A table is bound whole only where it can be: its descriptor resolves, its records are at least sixteen bytes apart with room for a descriptor at the load's immediate, it holds at most 256 records, every record is mapped, and no record's range is unbounded or larger than one buffer binds. Otherwise the access is found as before (D900, D914, D951), and its refusal says why the table was not bound. Only loads read through a selected record; a store or atomic through one is refused by name, since nothing is written back from the pages it reads.

A dispatch's s_buffer_load through a traced descriptor at an offset register now binds the descriptor's whole buffer, every byte its records admit, and every such load through that descriptor shares the slot. A scalar buffer load reads zero past its descriptor's records whatever the offset, so the whole buffer holds every byte it can read, and the descriptor's words are the dispatch's, the same at every access. The table's own load is one of these.

The tracer's scalar loads forget the registers they write, and no more. A load's width is its name's; it had forgotten at least four registers, so a two-word load lost the pair after it.

Why: Grand Theft Auto V's compute programs select per-lane buffers from descriptor tables through waterfalls. The program at 0x74005b857200 fetches with buffer_load_format_x at 0xd7c through s[12:15], loaded at 0xd70 by s_buffer_load_dwordx4 s[12:15], s[20:23], vcc_lo, the offset v_readfirstlane of a lane's record number shifted by four. No host run of the program can know a lane's value, so the access was refused (D753), and the wave runs could not follow its branches on the execution mask (D758). The program at 0x74005b858800 was refused for 100 buffers, 88 of them computed, more than the 64 a stage binds (D733): its vector accesses read through records of the table in s[28:31], copied between register quads and across blocks, and its scalar loads read s[48:51], s[24:27] and others at lane-derived offsets, each a slot of its own. With the tables' records sharing a slot each and the scalar loads sharing their descriptor's, 29 buffers remain, 7 of them computed. The program at 0x74005b854a00 fetches at 0x5e0 through s[8:11], loaded from s[4:5] + 0x60, the user data's table; s_buffer_load_dwordx2 s[2:3] at 0x25c had made the tracer forget s[4:5], so the fetch was taken for a computed one.

Binding a table whole is bounded at 256 records. The tables Grand Theft Auto V's programs at 0x74005b857200 and 0x74005b858800 select from hold 131072, and are refused by that bound; the refusal counts the records naming a buffer and the pages they reach.

Amended by D967: the bound refuses only binding the table whole, not the access. A table of more than 256 records, or one whose records disagree in format where the program converts by format, leaves the dispatch to be prepared device-addressed (D964), where the module reads the selected record's descriptor as it runs and converts by the format it names. A table that can be bound whole still is: its records are read once and its accesses read bound pages.

Rejected: