Repository: numba/numba

Issue:
How unsafe is inconsistent stack offset in dataflow analysis?
The following code:

``` python
from numba import jit

import warnings; warnings.simplefilter("error")

@jit
def f():
    if a:
        for i in range(10):
            pass

f()
```

Produces: 

```
Traceback (most recent call last):
  File "bug.py", line 11, in <module>
    f()
  File "/home/gmarkall/work/numba/numba/dispatcher.py", line 162, in _compile_for_args
    return self.compile(sig)
  File "/home/gmarkall/work/numba/numba/dispatcher.py", line 300, in compile
    flags=flags, locals=self.locals)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 565, in compile_extra
    return pipeline.compile_extra(func)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 303, in compile_extra
    return self.compile_bytecode(bc, func_attr=self.func_attr)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 312, in compile_bytecode
    return self._compile_bytecode()
  File "/home/gmarkall/work/numba/numba/compiler.py", line 552, in _compile_bytecode
    return pm.run(self.status)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 203, in run
    res = stage()
  File "/home/gmarkall/work/numba/numba/compiler.py", line 378, in stage_objectmode_frontend
    cres = self.frontend_looplift()
  File "/home/gmarkall/work/numba/numba/compiler.py", line 369, in frontend_looplift
    func_attr=self.func_attr)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 574, in compile_bytecode
    return pipeline.compile_bytecode(bc=bc, lifted=lifted, lifted_from=lifted_from, func_attr=func_attr)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 312, in compile_bytecode
    return self._compile_bytecode()
  File "/home/gmarkall/work/numba/numba/compiler.py", line 552, in _compile_bytecode
    return pm.run(self.status)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 203, in run
    res = stage()
  File "/home/gmarkall/work/numba/numba/compiler.py", line 327, in stage_analyze_bytecode
    self.interp = translate_stage(self.bc)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 645, in translate_stage
    interp = interpreter.Interpreter(bytecode=bytecode)
  File "/home/gmarkall/work/numba/numba/interpreter.py", line 102, in __init__
    self.dfa.run()
  File "/home/gmarkall/work/numba/numba/dataflow.py", line 26, in run
    self.infos[blk.offset] = self.run_on_block(blk)
  File "/home/gmarkall/work/numba/numba/dataflow.py", line 52, in run_on_block
    print(info.stack_offset)
RuntimeWarning: inconsistent stack offset for block(offset:32, outgoing: [], incoming: [0, 29])
```

The bytecode is: 

```
>          0    LOAD_GLOBAL(arg=0, lineno=7)
           3    POP_JUMP_IF_FALSE(arg=32, lineno=7)
           6    SETUP_LOOP(arg=23, lineno=8)
           9    LOAD_GLOBAL(arg=1, lineno=8)
          12    LOAD_CONST(arg=1, lineno=8)
          15    CALL_FUNCTION(arg=1, lineno=8)
          18    GET_ITER(arg=None, lineno=8)
>         19    FOR_ITER(arg=6, lineno=8)
          22    STORE_FAST(arg=0, lineno=8)
          25    JUMP_ABSOLUTE(arg=19, lineno=9)
>         28    POP_BLOCK(arg=None, lineno=9)
          29    JUMP_FORWARD(arg=0, lineno=9)
>         32    LOAD_CONST(arg=0, lineno=9)
          35    RETURN_VALUE(arg=None, lineno=9)
```

Does it matter if the stack offset is inconsistent in cases like this?



Discussion:
@gmarkall, are you seeing this on master?

I am, with both Python 2.7 and 3.4.

@gmarkall, I found the problem.  


Resolving patch:
diff --git a/numba/looplifting.py b/numba/looplifting.py
--- a/numba/looplifting.py
+++ b/numba/looplifting.py
@@ -170,7 +170,7 @@ def insert_loop_call(bytecode, loop, args, outer, outerlabels, returns,
             insertpt = insertpt.next()
     else:
         # No return value
-        poptop = ByteCodeInst.get(outer[-1].next, "POP_TOP", None)
+        poptop = ByteCodeInst.get(insertpt, "POP_TOP", None)
         poptop.lineno = loop[0].lineno
         insert_instruction(outer, poptop)
         insertpt = insertpt.next()


Test patch:
diff --git a/numba/tests/test_looplifting.py b/numba/tests/test_looplifting.py
--- a/numba/tests/test_looplifting.py
+++ b/numba/tests/test_looplifting.py
@@ -313,6 +313,20 @@ def test():
                 y += x
         self.assertEqual(test.py_func(), test())
 
+    def test_stack_offset_error_when_has_no_return(self):
+        from numba import jit
+        import warnings
+
+        def pyfunc(a):
+            if a:
+                for i in range(10):
+                    pass
+
+        with warnings.catch_warnings():
+            warnings.simplefilter("error")
+
+            cfunc = jit(forceobj=True)(pyfunc)
+            self.assertEqual(pyfunc(True), cfunc(True))
 
 if __name__ == '__main__':
     unittest.main()