使用 GHC 编译 Haskell 时如何禁用 <<loop>> 异常?
How to disable the <<loop>> exception when compiling Haskell with GHC?
如果我的程序进入无限循环,我希望它真的卡住:运行 永远,运行 内存不足或因堆栈溢出而崩溃。
我不希望它立即退出并显示 <<loop>>
错误消息。如何禁用 运行 次无限循环检测?
这不是真正的答案,但不适合发表评论。在这种情况下,人们应该问无限循环检测是什么以及它是如何工作的。我将通过从 Haskell 转换为具有严格(非懒惰)评估的 Haskell 相似语言来解释它(想想 JavaScript 和 Haskell 语法)然后解释一下。
Haskell:
let f () = f () in f ()
转换为严格:
let f () = make_thunk (\() -> eval_thunk f ()) in eval_thunk (make_thunk (\() -> f ()))
现在让我们做一个优化:
let f () = eval_thunk f_unit
f_unit = make_thunk (\() -> f ())
in
eval_thunk f_unit
现在让我们写下 thunk 的假定义:
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>>"
所以我们看到,当我们尝试评估某物作为计算其价值的一部分时,我们得到了一个无限循环。你可以手工跟踪上面的程序,看看这个错误是怎么产生的。
但是如果没有进行优化呢?那么(假设你没有优化到严格)你会得到一个堆栈溢出并且可以将其报告为 <<loop>>
错误。如果它被优化为真正严格,那么你可能会得到一个堆栈溢出错误或者你可能会得到一个无限循环。
有人可能会问如何避免这个错误,答案可能是:
- 不要写无限循环
- 可能编译时没有优化
但是您说有时无限循环很有用,因为例如事件循环或长寿命服务器。答案是在 Haskell 中无限循环没有用,因为要有用你需要 IO
。考虑一个函数 be_useful :: IO ()
,现在可以写:
f = be_useful >>= \() -> f
现在有两个步骤 f
:
- 评估 thunk
- 在该值中执行 IO 操作
- 执行下一个 IO 操作(再次计算 thunk)等等。
这个不用<<loop>>
.
这里有一个可能有用的可怕技巧。您可以为检测到黑洞循环时创建的非终止异常创建一个假信息 table 并使其循环。
假设你有一个Haskell程序:
-- Loop.hs
foo :: Int
foo = foo
main = print $ foo
如果你编译它:
$ ghc -O2 Loop.hs
和运行它,它会产生一个Loop: <<loop>>
错误。
但是,如果您创建一个程序集文件:
# 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
并使用您的 Haskell 程序编译它(使用适当的链接器标志以忽略重复定义):
$ ghc -O2 Loop.hs halt_and_catch_fire.s -optl -zmuldefs
和运行它,它会锁定。
请注意,上述程序集适用于 x86_64 Linux。在任何其他架构(包括 32 位 Linux)上,它需要修改,因为闭包布局非常依赖于架构。
如果我的程序进入无限循环,我希望它真的卡住:运行 永远,运行 内存不足或因堆栈溢出而崩溃。
我不希望它立即退出并显示 <<loop>>
错误消息。如何禁用 运行 次无限循环检测?
这不是真正的答案,但不适合发表评论。在这种情况下,人们应该问无限循环检测是什么以及它是如何工作的。我将通过从 Haskell 转换为具有严格(非懒惰)评估的 Haskell 相似语言来解释它(想想 JavaScript 和 Haskell 语法)然后解释一下。
Haskell:
let f () = f () in f ()
转换为严格:
let f () = make_thunk (\() -> eval_thunk f ()) in eval_thunk (make_thunk (\() -> f ()))
现在让我们做一个优化:
let f () = eval_thunk f_unit
f_unit = make_thunk (\() -> f ())
in
eval_thunk f_unit
现在让我们写下 thunk 的假定义:
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>>"
所以我们看到,当我们尝试评估某物作为计算其价值的一部分时,我们得到了一个无限循环。你可以手工跟踪上面的程序,看看这个错误是怎么产生的。
但是如果没有进行优化呢?那么(假设你没有优化到严格)你会得到一个堆栈溢出并且可以将其报告为 <<loop>>
错误。如果它被优化为真正严格,那么你可能会得到一个堆栈溢出错误或者你可能会得到一个无限循环。
有人可能会问如何避免这个错误,答案可能是:
- 不要写无限循环
- 可能编译时没有优化
但是您说有时无限循环很有用,因为例如事件循环或长寿命服务器。答案是在 Haskell 中无限循环没有用,因为要有用你需要 IO
。考虑一个函数 be_useful :: IO ()
,现在可以写:
f = be_useful >>= \() -> f
现在有两个步骤 f
:
- 评估 thunk
- 在该值中执行 IO 操作
- 执行下一个 IO 操作(再次计算 thunk)等等。
这个不用<<loop>>
.
这里有一个可能有用的可怕技巧。您可以为检测到黑洞循环时创建的非终止异常创建一个假信息 table 并使其循环。
假设你有一个Haskell程序:
-- Loop.hs
foo :: Int
foo = foo
main = print $ foo
如果你编译它:
$ ghc -O2 Loop.hs
和运行它,它会产生一个Loop: <<loop>>
错误。
但是,如果您创建一个程序集文件:
# 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
并使用您的 Haskell 程序编译它(使用适当的链接器标志以忽略重复定义):
$ ghc -O2 Loop.hs halt_and_catch_fire.s -optl -zmuldefs
和运行它,它会锁定。
请注意,上述程序集适用于 x86_64 Linux。在任何其他架构(包括 32 位 Linux)上,它需要修改,因为闭包布局非常依赖于架构。