Repository navigation
Conversation
imp_implementationWithBlock() allocated a trampoline set only when the list was empty. Once the first set was full (128 IMPs on AArch64, 256 on x86-64, with 4 KiB pages) it reached UNREACHABLE() and returned a garbage IMP. With assertions enabled it aborted one IMP earlier, because the check that `set->first_free < trampoline_header_per_page` compares -1 with a size_t. When no set has a free trampoline, allocate a new set and put it at the head of the list. The older sets stay in the list, so imp_getBlock() and imp_removeBlock() still find their IMPs, and trampolines freed in any set are reused. Return a null IMP if a set can't be allocated. Also fix three smaller bugs that the tests now catch: - Store the copy of the block in the trampoline, not the block that was passed in. For a stack block, the IMP called through a dead stack frame and imp_removeBlock() released the wrong block. - imp_getBlock() returns nil for a removed IMP, instead of the free list link. - imp_removeBlock() returns NO for an IMP that was already removed, instead of releasing the free list link and putting the trampoline on the free list twice. Add Test/ManyBlockImps.m, which sets and unsets 5000 block IMPs (normal and struct-return), and a stack block case to Test/BlockImpTest.m.
Member
|
This project does not accept LLM-generated code. And I’m also getting fed up of the LLM-generated issues that spend a dozen paragraphs to report a bug where one sentence would do. |
Author
|
Sorry for the LLM-generated PR and the noise it caused. I should have written the fix myself, and I won't submit generated code here again. |
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.
This is an alternative to #438. It follows the review there.
The bug
imp_implementationWithBlock()allocates a trampoline set only when the list is empty. When every set is full, it reachesUNREACHABLE("Failed to allocate block"), so it never allocates a second set:assert(set->first_free < trampoline_header_per_page)compares-1with asize_t, so it fails as soon as a set is full.The fix
imp_getBlock()andimp_removeBlock()still find IMPs in every set, and trampolines freed in any set get reused.calloc()ormmap()/VirtualAlloc()fails, it returns a null IMP instead of crashing.Block_copy()result, not the block that was passed in. For a stack block it used to keep a pointer into a dead stack frame, andimp_removeBlock()released the wrong block, leaking the copy.imp_getBlock()returnsnilfor a removed IMP instead of the free list link.imp_removeBlock()returnsNOfor an IMP that was already removed. Before, it released the free list link and put the trampoline on the free list twice.first_freeassertion now compares asint.Tests
New
Test/ManyBlockImps.msets and unsets 5000 block IMPs, first with blocks that return anint, then with blocks that return a struct (the sret trampolines on x86-64). Each block captures its index. The test:imp_getBlock()returns for it;imp_removeBlock()returnsYES,imp_getBlock()then returnsnil, and a second remove returnsNO;It's registered next to
BlockImpTest.m, so it only runs withEMBEDDED_BLOCKS_RUNTIME.Test/BlockImpTest.mgets a stack block case: IMPs made from stack blocks in a function that has returned must still call the right block.Results
Ubuntu 24.04 with clang 18, CMake and Ninja with
-DTESTS=ON. aarch64 ran natively and x86-64 under qemu. "Block tests" meansBlockImpTestandManyBlockImps, each in its 4 variants (plain,_optimised,_legacy,_legacy_optimised).How the tests fail:
ManyBlockImpson master:imp_getBlock()doesn't know it, and calling it crashes.first_freeassertion.ManyBlockImpswith Unlimited imp_implementationWithBlock() invocations #438:imp_getBlock()can't find IMP 127 (aarch64) or 255 (x86-64).BlockImpTest, on master and with Unlimited imp_implementationWithBlock() invocations #438: the trampoline stores the stack block instead of its copy.With only the allocation part of this fix,
ManyBlockImpspasses andBlockImpTeststill fails, so each test catches its own bug.Not tested: Windows, macOS, 32-bit ARM, PowerPC, MIPS, and 16 KiB pages.