Slice/principle check reduced dav1d#5
Draft
oinoom wants to merge 8 commits into
Draft
Conversation
added 8 commits
April 16, 2026 00:21
The rewrite workflow was accidentally replacing the IA2-built dynamic loader with the host loader. `ldd` prints the interpreter with an absolute path on the left-hand side, and parse_ldd() treated that entry like an ordinary shared library dependency. The copy step then overwrote runtime/libia2/ld-linux-x86-64.so.2, which caused the rewritten binary to fail before decode even started. Skip absolute-name ldd entries and explicitly stage the loader produced by the IA2 glibc build into runtime/libia2. This keeps the interpreter embedded by the Meson link step and the loader present on disk consistent. This is a build-side fix only. It does not change dav1d runtime behavior; it preserves the IA2 loader that the build already intended to use.
After fixing the loader, the first strict-mode crash moved into ___pthread_once() during dav1d_open(). The once-control object was a file-static pthread_once_t in libdav1d's private compartment, but glibc's pthread_once() implementation also needs to read and update that state. Mark only the once-control word as IA2_SHARED_DATA. This keeps the synchronization object visible to both sides of the call without broadening the rest of libdav1d initialization state. This is the narrowest source-side fix for the observed pthread_once() fault.
Sharing the pthread_once_t was not sufficient on its own. The next strict-mode failure was still in the pthread_once path because the generated __wrap_pthread_once wrapper used the normal main-compartment PKRU during the call phase while glibc bookkeeping and the init_internal() callback were both active. Patch the generated __wrap_pthread_once block to use the union PKRU for the callback phase. That keeps libc's internal once machinery accessible while the callback initializes libdav1d state. The change stays local to pthread_once in rewrite.py. This is deliberately a surgical build-side workaround for the minimal reproduction rather than a broad runtime policy change.
ivf_read() runs in the tools compartment, but dav1d_data_create() would return a decoder-owned buffer from libdav1d. Passing that private buffer to fread() makes the tools side write through a pointer into decoder-owned memory, which is the strict-mode pointee-ownership fault we were hitting. Allocate the packet payload with shared_malloc() instead and transfer it into Dav1dData with dav1d_data_wrap() plus an explicit shared_free callback. This keeps the change narrow: only the demux-to-decoder handoff buffer becomes shared. This avoids teaching IA2 about pointee ownership for fread() while keeping the sharing decision explicit in dav1d.
Dav1d_open() initializes a pthread_attr_t in libdav1d and then passes it through glibc helpers such as pthread_attr_*(), get_stack_size_internal(), and pthread_create(). In strict IA2 mode that attribute object is shared mutable state crossing the libdav1d/libc boundary. Allocate the pthread_attr_t from the shared allocator and free it explicitly once thread setup is finished. The change is intentionally limited to the attribute object; thread stacks and the rest of Dav1dContext stay on their existing allocation paths. This keeps glibc's stack-size calculations and thread-creation path from touching compartment-private memory.
The remaining dlsym() call in get_stack_size_internal() is not a normal libc call. glibc's dlsym(RTLD_DEFAULT, ...) is caller-sensitive: it records the call site and uses that identity during symbol resolution. A normal IA2 call gate changes the apparent caller, while leaving dlsym() unwrapped would make dynamic loader code run under the active compartment's PKRU. This lookup is only a glibc-private workaround probe for __pthread_get_minstack, not part of dav1d's decode logic. Under IA2 we also switch worker threads onto IA2-managed stacks, so depending on glibc's internal stack/TLS accounting here is not a good contract. Disable just this probe when IA2 is enabled and document why. That keeps the fix narrow and avoids turning dlsym() into a special cross-compartment API before IA2 has a principled design for it.
After the earlier fixes, strict single-thread decode still failed when dav1d's memory-pool control blocks lived in the decoder compartment. The pool headers carry the mutex, refcount, and freelist pointers that are mutated through shared synchronization paths even though the payload buffers themselves can stay on the normal aligned allocation path. Allocate only Dav1dMemPool from shared memory and free it there as well. Buffer payloads continue to use the existing dav1d allocation flow. rewrite.py also keeps src/mem.c when constructing the rewritten tree so this source change survives the minimal reproduction workflow. With this change in place, the minimal stack reaches successful strict single-thread decode again.
Two pieces of the previous dav1d single-thread stack turned out to be broader than necessary. First, dav1d_open() does not need its static pthread_once_t control word in shared storage for single-thread decode. The real requirement is the rewrite.py patch that widens the generated __wrap_pthread_once PKRU mask. With that wrapper fix in place, strict single-thread decode still passes when initted is an ordinary file-static pthread_once_t. Reverting only that storage annotation and rebuilding preserved both dav1d --version and --threads 1 decode. Second, rewrite.py no longer needs to copy the IA2-built loader from external/glibc/sysroot/lib into runtime/libia2. The important loader fix is keeping parse_ldd() from overwriting the interpreter path with the host loader when ldd reports an absolute-name left-hand side. With that guard retained, the IA2 build already recreates the correct loader at runtime/libia2 during the rewrite/build flow. This was revalidated under the stricter condition that previously made loader handling fail: delete runtime/libia2/ld-linux-x86-64.so.2, rerun rewrite.py, then confirm that: - the recreated runtime/libia2 loader matches the IA2 sysroot loader - tools/dav1d requests the IA2 runtime loader as its interpreter - dav1d --version returns 0 - single-thread decode of test.ivf returns 0 No other dav1d single-thread carveouts were removed here because the other tested candidates all produced concrete crashes: - private pthread_attr_t crashed in pthread_attr_init - non-shared IVF payloads crashed in fread/memmove - re-enabling the glibc minstack dlsym probe crashed in dlsym - private Dav1dMemPool headers crashed in pthread_mutex_init - removing the pthread_once wrapper widening crashed in pthread_once
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.