我正在开发这个工具,用户可以在[config files|content-text files|etc]中定义并包含自己的"模板"(如胡子等),这些模板可以引用其他模板,从而引发循环。就在我准备创建一个"最大循环"设置时,我意识到用runghc程序过了一会儿就退出了,只显示了<<loop>>
的告别消息。这对我来说已经足够好了,但也引发了一些思考:
-
GHC或运行时如何实际检测到它卡在循环中,以及如何区分所需的长时间运行操作和意外的无限循环?停顿的问题仍然是我上次检查的问题。。
-
是否有任何(时间或迭代)限制可以自定义设置为编译器或运行时?
-
这是仅
runghc
-还是存在于所有最终编译输出中? -
当构建版本禁用这种明显的内置循环检测时,会在很久以后设置任何
-o
(优化)标志吗?
当然,所有的事情我都很难弄清楚,但谁知道呢,也许有人已经对此进行了更详细的研究。。("haskell" "<<loop>>"
很难在谷歌/ddg上搜索,因为它们去掉了尖括号,然后显示"如何在Haskell中循环"等的结果。)
这是在GHC中实现的STG运行时的一个简单"改进"。我会分享我所了解的,但GHC专家可能会提供更有用、更准确的信息。
GHC在进行了几次优化后,编译成一种名为Core的中间语言。你可以使用ghc -ddump-simpl ...
看到它
粗略地说,在Core中,一个未赋值的绑定(如let x = 1+y+x in f x
)创建了一个thunk。在某个地方分配一些内存来表示闭包,并使x
指向它
当(并且如果)x
被f
强制时,则对thunk进行评估。改进如下:在评估开始之前,x
的thunk被一个称为BLACKHOLE
的特殊值覆盖。在x
被评估(到WHNF)之后,黑洞再次被实际值覆盖(因此,如果例如f x = x+x
,我们不会重新计算)。
如果黑洞被强迫,则<<loop>>
被触发。这实际上是一个IO异常(这些异常也可以在纯代码中引发,所以这很好)。
示例:
let x = 1+x in 2*x -- <<loop>>
let g x = g (x+1) in g 0 -- diverges
let h x = h (10-x) in h 0 -- diverges, even if h 0 -> h 10 -> h 0 -> ...
let g0 = g10 ; g10 = g0 in g0 -- <<loop>>
注意,h 0
的每个调用都被认为是一个不同的thunk,因此没有黑洞在那里被强迫。
棘手的部分是,了解哪些thunk实际上是在Core中创建的并不是一件小事,因为GHC可以在发出Core之前执行几个优化。因此,我们应该将<<loop>>
视为奖金,而不是GHC的既定/硬性保证。未来的新优化可能会用实际的非终止取代一些<<loop>>
。
如果你想在谷歌上搜索一些东西,"GHC,黑洞,STG"应该是不错的关键词。