Bug #1044465 reported by Douglas Katzman on 2012-08-31
This bug affects 1 person
Affects Status Importance Assigned to Milestone

Bug Description

If a variable which is supposed to be a structure, but might be inferred to be nil (but not actually ever nil at runtime), is declared dynamic-extent, the compiler crashes. This hugely-stripped-down example shows something a little bit silly that our code does, which perhaps it ought not, but regardless should compile.

;; Minimal example:
(declaim (inline make-a-rec))
(defstruct (my-rec (:constructor make-a-rec (someslot))) someslot)

(defun hairyfun (rec1 rec2)
  (declare (ignore rec1 rec2))
  ;; complicated stuff elided

(defmacro my-obj (x) `(when ,x (make-a-rec ,x)))

(defun mumble-p (cptr-a cptr-b)
  (let ((r8-a (my-obj cptr-a)) (r8-b (my-obj cptr-b)))
    (declare (dynamic-extent r8-a r8-b))
    (hairyfun r8-a r8-b)))
;; end of example

A few things can workaround the problem for us:
- remove the inline declamation, of course getting "cannot stack allocate", but no crash; or different compiler policy around this code achieves the same thing
- remove the "WHEN" around the object constructor and refactor MUMBLE-P to test whether it should make the objects at all (because in our case HAIRYFUN needs valid objects, so it would be a bug in our code if we ever got there without - however the rest of the scaffolding provided by MY-OBJ is fairly generic and generally useful, so we can't "just" do that)
- assert essentially the same around the invocation of MY-OBJ by writing (let ((r8-a (my-obj (the (not null)

Tested on 2 versions of sbcl:
Pristine sources:
- SBCL (Darwin 10.8.0 Darwin Kernel Version 10.8.0: Tue Jun 7 16:33:36 PDT 2011; root:xnu-1504.15.3~1/RELEASE_I386 i386)

Customized by cracauer@google
- (Linux #2 SMP Fri May 18 03:37:30 PDT 2012 x86_64 GNU/Linux)

Stas Boukarev (stassats) on 2014-05-11
Changed in sbcl:
status: New → Triaged
importance: Undecided → High
Stas Boukarev (stassats) wrote :

(defun foo (x)
  (let ((a (if x
               (list (list x))
               (list (list x)))))
    (declare (dynamic-extent a))
    (prin1 a)
So, a problem is with IF and nested DXes.

Changed in sbcl:
assignee: nobody → Stas Boukarev (stassats)

It turns out that UPDATE-UVL-LIVE-SETS is a backwards flow-sensitive analysis, propagating :UNKNOWN LVARs (unknown-values and dynamic-extent stack packets) from where they are known to be consumed back to where they are created. In Stas' reduced test case, each branch of the IF creates two dynamic-extent LVARs, one of which is shared with the other branch. The analysis starts by looking for the start of all three LVARs, each branch kills two and leaves one, and when we get to the IF we end up with the UNION of the two branches and keep propagating the two LVARs that were only killed on one branch, all the way back to the beginning. Boom.

Nastier, control-flow wise:
(defun foo (x y)
  (dotimes (i 2)
    (block bar
      (let ((a (if x
                   (list (list x))
                   (list (list x))))
            (b (if y x (return-from bar x))))
        (declare (dynamic-extent a))
        (prin1 a)

This example is worse because of the introduction of the RETURN-FROM to force an exit after allocating the DX values but BEFORE entering their environment (which would normally cause the DX values to simply be popped off) combined with a loop to cause the backwards flow analysis to propagate the LVAR lifetimes back around through the RETURN-FROM from the other side. On the upside, if a fixed STACK phase can handle this nastier case any further bugs would have to be VERY subtle.

Changed in sbcl:
status: Triaged → Fix Committed
Stas Boukarev (stassats) on 2015-02-18
Changed in sbcl:
assignee: Stas Boukarev (stassats) → nobody
Changed in sbcl:
status: Fix Committed → Fix Released
To post a comment you must log in.
This report contains Public information  Edit
Everyone can see this information.

Other bug subscribers