【问题标题】:How does the <<loop>> error "work" in detail?<<loop>> 错误如何详细“工作”?
【发布时间】:2017-01-07 03:29:51
【问题描述】:

我正在开发这个工具,用户可以在其中定义并包含在 [config files |内容文本文件 |等]他们自己的“模板”(如小胡子等),这些可以引用其他人,所以他们可以诱导一个循环。就在我正要创建“最大循环”设置时,我用 runghc 意识到程序在一段时间后就退出了,只有&lt;&lt;loop&gt;&gt; 的告别消息。这对我来说实际上已经足够了,但引发了一些思考:

  • GHC 或运行时如何实际检测到它卡在一个循环中,它如何区分想要的长时间运行的操作和意外的无限循环?停止问题仍然是我上次检查的问题..

  • 可以为编译器或运行时自定义设置的任何(时间或迭代)限制?

  • 这是runghc-only 还是存在于所有最终编译输出中?

  • 当构建版本禁用此明显的内置循环检测时,任何-o(优化)标志是否会设置得更晚?

所有的东西我当然可以通过困难的方式弄清楚,但谁知道也许有人已经更详细地研究了这个..(对于"haskell" "&lt;&lt;loop&gt;&gt;" 很难用 google/ddg 搜索,因为它们会去掉尖括号,然后显示结果“如何在 Haskell 中循环”等)

【问题讨论】:

  • 搜索 ghc loop 会得到一些相关结果,包括 Haskell program outputs `<<loop>>`this Haskell Cafe message
  • 尽管存在停止问题,但显然可以在有限数量的情况下检测到非终止(即证明程序未终止或在有限数量的时间是可能的)。简而言之,&lt;&lt;loop&gt;&gt; 表示 GHC 在简化后找到了 let x = x in x 形式的表达式,并将其重写为 throw .. 其中.. 是一个特殊异常,它会产生您在评估时看到的消息 - 我不记得了这个异常叫什么,但你可能没有捕捉到它。
  • @user2407038 我不相信这是真的。我很确定&lt;&lt;loop&gt;&gt; 检测完全是在运行时进行的,它会根据自身检测值的任何情况,而不仅仅是被简单地定义为自身。
  • AFAIK GHC 在评估一个 thunk 时,只要发现它必须评估相同的 thunk 才能获得一个值,就会引发 &lt;&lt;loop&gt;&gt;。这比@user2407038 描述的更笼统。可以捕获许多未终止的情况,停止问题只是说我们无法捕获所有

标签: haskell ghc


【解决方案1】:

这是对在 GHC 中实现的 STG 运行时的简单“改进”。我会分享我的理解,但 GHC 专家可能会提供更有用和更准确的信息。

GHC 在经过多次优化后编译成一种称为 Core 的中间语言。你可以使用ghc -ddump-simpl ...查看它

非常粗略地,在 Core 中,未评估的绑定(如 let x = 1+y+x in f x)会创建一个 thunk。在某处分配了一些内存来表示闭包,并让x 指向它。

xf 强制时(并且如果),则计算thunk。这是改进:在评估开始之前,x 的 thunk 被称为 BLACKHOLE 的特殊值覆盖。在评估x(到WHNF)之后,黑洞会再次被实际值覆盖(因此我们不会重新计算它,例如f x = x+x)。

如果黑洞被强迫,&lt;&lt;loop&gt;&gt; 被触发。这实际上是一个 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,因此不会出现黑洞。

棘手的部分是,要了解在 Core 中实际创建了哪些 thunk 并不是一件容易的事,因为 GHC 可以在发出 Core 之前执行多项优化。因此,我们应该将&lt;&lt;loop&gt;&gt; 视为奖励,而不是 GHC 的给定/硬保证。未来的新优化可能会用实际的不终止来取代一些&lt;&lt;loop&gt;&gt;s。

如果你想 google 一些东西,“GHC, blackhole, STG”应该是不错的关键词。

【讨论】:

  • 一个小修正:当一个线程进入黑洞时,它会将自己放入一个待唤醒的线程列表并阻塞(返回给调度器)。毕竟,另一个线程可能正在评估我们想要评估的同一个 thunk。 GHC 在检测到死锁时发送&lt;&lt;loop&gt;&gt; 异常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-18
  • 2020-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-31
相关资源
最近更新 更多