From: Patrick S. <pa...@nk...> - 2012-05-16 00:59:51
|
In looking over the errors generated by a cl-test-grid run, I looked into the trivial-backtrace test that was failing. [1] I think that I've uncovered a subtle bug in the SBCL compiler. [1] http://cl-test-grid.appspot.com/blob?key=100025 I'm using almost-the-latest SBCL (1.0.55.0-abb03f9-dirty) compiled for 64-bit with threading on MacOSX Lion (10.7.3). This is the simplest I can seem to get the same problem to arise is this: (defun absorbs () (let ((ret 'no-error)) (handler-case (let ((y 0)) (/ y)) (division-by-zero (c) (setf ret c))) ret)) When that compiles, it gives no warnings. When invoked, it returns NO-ERROR. This, on the other hand, gives me a warning at compile time and returns an instance of the division-by-zero error when invoked. (defun throws () (let ((ret 'no-error)) (handler-case (let* ((y 0) (z (/ y))) z) (division-by-zero (c) (setf ret c))) ret)) Here is the compile time "style" warning. ; Lisp error during constant folding: ; arithmetic error DIVISION-BY-ZERO signalled ; Operation was SB-KERNEL::DIVISION, operands (1 0). I spent some time trying to track it down. I started with the :TRACE-FILE option to COMPILE-FILE. The first place that they differ materially is here: absorbs.trace:165-169 IR1 block 11 start c104 start stack: dv94 dv86 dv82 dv68 dv60 104> entry NIL end stack: dv94 dv86 dv82 dv68 dv60 successors c105 throws.trace:165-173 IR1 block 11 start c104 start stack: dv94 dv86 dv82 dv68 dv60 104> entry NIL 105>106: / {GLOBAL-FUNCTION} 107>108: '1 109>110: '0 111> full combination v106 v108 v110 end stack: dv94 dv86 dv82 dv68 dv60 successors c112 This makes me think that the ABSORBS function is optimizing away the divide entirely. I haven't been able to track down exactly why that would be. I suppose that I spent some time looking at how the #'/ clause was getting optimized in IR1-OPTIMIZE, but I didn't spend any real time looking at how the HANDLER-CASE was getting optimized. What I saw with the #'/ clause was that the (NODE-LVAR NODE) check in this block was NIL for the ABSORBS case and an LVAR instance for the THROWS case: (let ((attr (fun-info-attributes info))) (when (and (ir1-attributep attr foldable) ;; KLUDGE: The next test could be made more sensitive, ;; only suppressing constant-folding of functions with ;; CALL attributes when they're actually passed ;; function arguments. -- WHN 19990918 (not (ir1-attributep attr call)) (every #'constant-lvar-p args) (node-lvar node)) (constant-fold-call node) (return-from ir1-optimize-combination))) I'm guessing that's just because the HANDLER-CASE's return value is not the return value of the enclosing LET block. Regardless, so it isn't constant-folded. Somehow or other, it eventually ends up getting excised. I'm assuming that also because it isn't the return value of the enclosing LET block that something decides it can be pruned. But, I'm not seeing where that would be. So... I present this here both to report the bug and for any tidbits any of y'all would like to share about where the optimizer might be mussing this one up. Thanks, Patrick |
From: Stas B. <sta...@gm...> - 2012-05-16 02:52:37
|
Patrick Stein <pa...@nk...> writes: > In looking over the errors generated by a cl-test-grid run, I looked > into the trivial-backtrace test that was failing. [1] I think that > I've uncovered a subtle bug in the SBCL compiler. > > [1] http://cl-test-grid.appspot.com/blob?key=100025 > > I'm using almost-the-latest SBCL (1.0.55.0-abb03f9-dirty) compiled for > 64-bit with threading on MacOSX Lion (10.7.3). > > This is the simplest I can seem to get the same problem to arise is > this: > > (defun absorbs () > (let ((ret 'no-error)) > (handler-case > (let ((y 0)) > (/ y)) > (division-by-zero (c) (setf ret c))) > ret)) > > When that compiles, it gives no warnings. When invoked, it returns > NO-ERROR. Yes, this is intended, the compiler elides division operations when the result is not used, and when safety is not high enough, even if it's divided by zero. Add (declare (optimize safety )) to get the error. -- With best regards, Stas. |
From: Patrick S. <pa...@nk...> - 2012-05-16 03:00:16
|
So, there's no accounting for the fact that this division might have side-effects? … Patrick On May 15, 2012, at 9:52 PM, Stas Boukarev wrote: > Patrick Stein <pa...@nk...> writes: > >> In looking over the errors generated by a cl-test-grid run, I looked >> into the trivial-backtrace test that was failing. [1] I think that >> I've uncovered a subtle bug in the SBCL compiler. >> >> [1] http://cl-test-grid.appspot.com/blob?key=100025 >> >> I'm using almost-the-latest SBCL (1.0.55.0-abb03f9-dirty) compiled for >> 64-bit with threading on MacOSX Lion (10.7.3). >> >> This is the simplest I can seem to get the same problem to arise is >> this: >> >> (defun absorbs () >> (let ((ret 'no-error)) >> (handler-case >> (let ((y 0)) >> (/ y)) >> (division-by-zero (c) (setf ret c))) >> ret)) >> >> When that compiles, it gives no warnings. When invoked, it returns >> NO-ERROR. > Yes, this is intended, the compiler elides division operations when > the result is not used, and when safety is not high enough, even if it's > divided by zero. Add (declare (optimize safety )) to get the error. > > -- > With best regards, Stas. |
From: Stas B. <sta...@gm...> - 2012-05-16 03:02:59
|
Patrick Stein <pa...@nk...> writes: > So, there's no accounting for the fact that this division might have side-effects? > > … Patrick It doesn't have side-effects, except for the error, which i don't think anyone would desire. If you want to check everything, run with (safety 3), which is the definition of safe code by CLHS. -- With best regards, Stas. |
From: Peter K. <ps...@cs...> - 2012-05-16 03:35:54
|
On Wed, May 16, 2012 at 07:02:49AM +0400, Stas Boukarev wrote: > Patrick Stein <pa...@nk...> writes: > > > So, there's no accounting for the fact that this division might have side-effects? > > > > … Patrick > It doesn't have side-effects, except for the error, which i don't think > anyone would desire. If you want to check everything, run with (safety > 3), which is the definition of safe code by CLHS. If one were writing a video game where one didn't want to check for a division by zero to speed up the computation (just handling the condition in a handler-bind when it happens) that would be a plenty good reason to want the divide-by-zero condition. I've often been surprised by the times that SBCL's default safety settings don't cause certain conditions, like unbound-variable conditions and such, to be signaled at the appropriate times. I know now to set saftey to 3 to get safe code, but it was a surprising road to find that out. I figured I would just get safe code by default. Thank you. -pete |
From: Stas B. <sta...@gm...> - 2012-05-16 03:43:12
|
Peter Keller <ps...@cs...> writes: > On Wed, May 16, 2012 at 07:02:49AM +0400, Stas Boukarev wrote: >> Patrick Stein <pa...@nk...> writes: >> >> > So, there's no accounting for the fact that this division might have side-effects? >> > >> > … Patrick >> It doesn't have side-effects, except for the error, which i don't think >> anyone would desire. If you want to check everything, run with (safety >> 3), which is the definition of safe code by CLHS. > > If one were writing a video game where one didn't want to check for a > division by zero to speed up the computation (just handling the condition > in a handler-bind when it happens) that would be a plenty good reason > to want the divide-by-zero condition. If you were to sped up the computation that would imply that you care about the result. And handler-bind would only slow things down. (defun does-not-absorb () (let ((ret 'no-error)) (list (handler-case (let ((y 0)) (/ y)) (division-by-zero (c) (setf ret c))) ret))) By return the result, the division is not elided and the error is signalled. > I've often been surprised by the times that SBCL's default safety settings > don't cause certain conditions, like unbound-variable conditions and such, > to be signaled at the appropriate times. I know now to set saftey to 3 to > get safe code, but it was a surprising road to find that out. I figured I > would just get safe code by default. I can't even contrive any reasons for this to ever be useful. safety 1 in SBCL is very reasonable, unless your code is contorted. -- With best regards, Stas. |
From: Peter K. <ps...@cs...> - 2012-05-16 04:27:20
|
On Wed, May 16, 2012 at 07:43:01AM +0400, Stas Boukarev wrote: > Peter Keller <ps...@cs...> writes: > > On Wed, May 16, 2012 at 07:02:49AM +0400, Stas Boukarev wrote: > >> Patrick Stein <pa...@nk...> writes: > >> > So, there's no accounting for the fact that this division might have side-effects? > >> > … Patrick > >> It doesn't have side-effects, except for the error, which i don't think > >> anyone would desire. If you want to check everything, run with (safety > >> 3), which is the definition of safe code by CLHS. > > If one were writing a video game where one didn't want to check for a > > division by zero to speed up the computation (just handling the condition > > in a handler-bind when it happens) that would be a plenty good reason > > to want the divide-by-zero condition. > If you were to sped up the computation that would imply that you care > about the result. And handler-bind would only slow things down. > > (defun does-not-absorb () > (let ((ret 'no-error)) > (list (handler-case > (let ((y 0)) > (/ y)) > (division-by-zero (c) (setf ret c))) > ret))) > > By return the result, the division is not elided and the error is signalled. I see what you mean by using the return of the result. I stand corrected. > > I've often been surprised by the times that SBCL's default safety settings > > don't cause certain conditions, like unbound-variable conditions and such, > > to be signaled at the appropriate times. I know now to set saftey to 3 to > > get safe code, but it was a surprising road to find that out. I figured I > > would just get safe code by default. > I can't even contrive any reasons for this to ever be useful. safety 1 > in SBCL is very reasonable, unless your code is contorted. A while ago I was writing an assembler for an embedded architecture where common lisp read the opcodes in the form of sexps (as actual common lisp). If I used symbols (in the assembler's DSL, but also in the lisp variable namespace) that weren't defined, I'd handle the unbound-variable conditions and poke them into a symbol table for later assignment to addresses when the opcodes were actually assembled. It simplified not having to write more structure around the opcode list to predefine the variables or walk over the opcode list try and pick out the symbols that were variables in the assembly DSL (which was hard). I'm not claiming that I chose a good way to do that project, but it was a place where I had to modify SBCL's safety to 3 to get the right behavior. Up until then, it didn't even occurr to me that SBCL's safety wasn't already 3. Later, -pete |
From: Paul K. <pv...@pv...> - 2012-05-16 07:37:43
|
On Wed, May 16, 2012 at 6:27 AM, Peter Keller <ps...@cs...> wrote: > On Wed, May 16, 2012 at 07:43:01AM +0400, Stas Boukarev wrote: [...] >> (defun does-not-absorb () >> (let ((ret 'no-error)) >> (list (handler-case >> (let ((y 0)) >> (/ y)) >> (division-by-zero (c) (setf ret c))) >> ret))) >> >> By return the result, the division is not elided and the error is signalled. > > I see what you mean by using the return of the result. I stand corrected. [...] > A while ago I was writing an assembler for an embedded architecture > where common lisp read the opcodes in the form of sexps (as actual common > lisp). If I used symbols (in the assembler's DSL, but also in the lisp > variable namespace) that weren't defined, I'd handle the unbound-variable > conditions and poke them into a symbol table for later assignment to > addresses when the opcodes were actually assembled. [...] > I'm not claiming that I chose a good way to do that project, but it was > a place where I had to modify SBCL's safety to 3 to get the right behavior. > Up until then, it didn't even occurr to me that SBCL's safety wasn't already 3. In both cases, the code depends on undefined behaviour (I can only imagine that you had functions that simply read variables without using their value in an attempt to trigger unbound variable conditions). CL's default level for all optimization levels is 1; for SBCL, that level means that we don't crash on undefined behaviour, and instead signal conditions. In these cases, the potential error has been optimised away: there's no crash to replace with a condition anymore. I, and many other users I suppose, would be peeved it SBCL disabled simple optimisations like dead code elimination just to consistently signal a condition in the face of undefined behaviour. Optimisations in SBCL guarantee that you'll get the result and side effects your code defines, and, when safety isn't 0, conditions instead of random behaviour if runtime errors would happen. There aren't many potential optimisations one can perform if they wish to signal exactly the same conditions as a naïve interpreter. Paul Khuong |
From: Patrick S. <pa...@nk...> - 2012-05-16 12:24:04
|
For me, SBCL warns me when it removes unreachable code. This usually turns up bugs in my typing or logic or paren-grouping. I guess I was expecting to see some indication that dead code was removed, too. If I have dead code, why would I want I there? And, if I do want it there, I'd like to know if it isn't. But, maybe I'm being too optimistic. Maybe everything would be warning me everywhere... like the "else" clause of an IF when expanded from an WHEN macro. -- Patrick <pa...@nk...> On May 16, 2012, at 2:37 AM, Paul Khuong <pv...@pv...> wrote: > I, and many other users I suppose, would be peeved it SBCL disabled > simple optimisations like dead code elimination just to consistently > signal a condition in the face of undefined behaviour |