Jesbus
Jesbus

Reputation: 815

How to disable the <<loop>> exception when compiling Haskell with GHC?

If my program reaches an infinite loop, I would like it to actually get stuck: run forever, run out of memory or crash with a stack overflow.

I don't want it to quit immediately with the <<loop>> error message. How do I disable the run-time infinite loop detection?

Upvotes: 5

Views: 460

Answers (2)

K. A. Buhr
K. A. Buhr

Reputation: 50819

Here's a horrible hack that might work. You can create a fake info table for the non-termination exception that's created when a blackhole loop is detected and make it loop.

Suppose you have a Haskell program:

-- Loop.hs
foo :: Int
foo = foo
main = print $ foo

If you compile this with:

$ ghc -O2 Loop.hs

and run it, it'll generate a Loop: <<loop>> error.

But, if you create an assembly file:

# halt_and_catch_fire.s
# version for Linux x86_64 only
.globl base_ControlziExceptionziBase_nonTermination_closure
.section .data
.align 8
base_ControlziExceptionziBase_nonTermination_closure:
    .quad loop
.section .text
.align 8
    .quad 0
    .quad 14            # FUN_STATIC
loop:   jmp loop

and compile it with your Haskell program (with appropriate linker flags to ignore the duplicate definition):

$ ghc -O2 Loop.hs halt_and_catch_fire.s -optl -zmuldefs

and run it, it'll lock up.

Note that the above assembly works on x86_64 Linux. On any other architecture (including 32-bit Linux), it would need to be modified, as the closure layout is very architecture-dependent.

Upvotes: 3

Dan Robertson
Dan Robertson

Reputation: 4360

This isn’t really an answer but wouldn’t fit in a comment. In this situation one should ask what infinite loop detection is and how it works. I’m going to explain it by converting from Haskell to a Haskell-look-alike language with strict (non lazy) evaluation (think eg JavaScript with Haskell syntax) and then explaining it.

Haskell:

let f () = f () in f ()

Convert to strict:

let f () = make_thunk (\() -> eval_thunk f ()) in eval_thunk (make_thunk (\() -> f ()))

Now let’s do an optimisation:

let f () = eval_thunk f_unit
    f_unit = make_thunk (\() -> f ())
in
eval_thunk f_unit

Now let’s write down a fake definition for thunks:

data ThunkInner a = Done a | Fresh (() -> a) | Working
data Thunk a = Thunk { mutable inner :: ThunkInner a }
make_thunk f = Thunk (Fresh f)
eval_thunk t = case t.inner of
  | Done a -> a
  | Fresh f -> (t.inner <- Working; let result = f () in t.inner <- Done result; result)
  | Working -> error "<<loop>>"

So we see that we get an infinite loop when we try to evaluate something as part of working out its value. You can trace the above program by hand to see how this error could arise.

But what if that optimisation hadn’t been made? Well then (assuming you don’t optimise to strictness) you would get a stack overflow and could report this as a <<loop>> error. If it were optimised to be truly strict then maybe you would get a stack overflow error or maybe you would get an infinite loop.

One might ask how to avoid this error and the answer could be:

  1. Don’t write infinite loops
  2. Maybe compile with no optimisations

But you say that sometimes infinite loops are useful because eg event loops or long lived servers. The reply is that in Haskell infinite loops are not useful because to be useful you need IO. Consider a function be_useful :: IO () and now one can write:

f = be_useful >>= \() -> f

And now there are two steps in doing f:

  1. Evaluating the thunk
  2. Doing the IO actions in that value
  3. Doing the next IO action (computing the thunk again) and so on.

This need not <<loop>>.

Upvotes: 2

Related Questions