Only mark a closure dynamic-extent once every reader has been checked - #34
Open
dg1sbg wants to merge 1 commit into
Open
Only mark a closure dynamic-extent once every reader has been checked#34dg1sbg wants to merge 1 commit into
dg1sbg wants to merge 1 commit into
Conversation
DETERMINE-CLOSURE-EXTENT set the ENCLOSE's extent to :dynamic from inside the loop over the variable's readers, so the mutation happened as soon as the first reader turned out to be a DX call. A later reader that escapes bails out with RETURN-FROM, which does not undo it, and the closure stays marked :dynamic. The backend then builds it with cc_stack_enclose over an alloca in the enclosing frame -- and it escapes anyway. Using it once that frame is gone is a use-after-return; under Clasp it surfaces as EXT:BUS-ERROR. Which reader is seen first decides whether this fires, and that order is unspecified: Set/set.lisp picks the hashset representation, because the #+(or) guard on :listset is always false, so DOSET walks an EQ hash table and the order follows object addresses. Compiling (lambda (x) (let ((g (lambda (y) (+ x y)))) (mapcar g '(1 2 3)) g)) 300 times with Clasp's native compiler stack-allocated the returned closure 154 times. MAPCAR carries the :dx-call attribute, and the RETURNI is the escape. Move the assignment after the loop, where the conclusion it records has actually been established. A variable with no readers now qualifies too, which is correct -- a value that is never read cannot escape; previously the loop body simply never ran. Introduced in 3884e93.
Author
|
Standalone and independently valuable; #35 builds on it but this needs no part of that. Clasp-side consumer, for context: clasp-developers/clasp#1821. |
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.
DETERMINE-CLOSURE-EXTENTsets the enclose's extent to:dynamicfrom inside the loop over the variable's readers, so the mutation happens as soon as the first reader turns out to be a DX call. A later reader that escapes bails out withRETURN-FROM, which does not undo it, and the closure stays marked:dynamic.The backend then builds it on the stack — under Clasp,
cc_stack_encloseover anallocain the enclosing frame — and it escapes anyway. Using it after that frame is gone is a use-after-return.Why it is intermittent
Which reader is visited first decides whether this fires, and that order is unspecified.
Set/set.lispselects the hashset representation, because the guard on:listsetis#+(or)and therefore always false, soDOSETwalks anEQhash table and the order follows object addresses.Compiling
300 times with Clasp's native compiler stack-allocated the returned closure 154 times.
MAPCARcarries the:dx-callattribute, and theRETURNIis the escape. Calling one of the resulting closures gives a hard fault —SEGMENTATION-VIOLATIONon one GC variant,EXT:BUS-ERRORon the other.The fix
Move the assignment after the loop, where the conclusion it records has actually been established.
A variable with no readers now qualifies as well, which is correct — a value that is never read cannot escape. Previously the loop body simply never ran, so the case was missed rather than decided.
Verification
Through Clasp, since that is the client I have:
Origin
Introduced in 3884e93, which fixed the related but distinct problem in #12 — that a closure reachable through an escaping sibling could be stack-allocated. That fix added the reader walk; this one corrects when the walk commits its result.