请在2024 年 Clojure 状态调查中分享您的想法!

欢迎!请参阅关于页面,了解更多关于如何使用本网站的信息。

0
ClojureScript
当编译更复杂的 core.async 代码时,遇到神秘的堆栈溢出错误的风险很高。

以下是一个这样的近期情况
https://dev.clojure.org/jira/browse/CLJS-3030?focusedCommentId=51115&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-51115

可能的原因在于 `cond` 的宏展开方式。问题是编译器的堆栈深度与测试/表达式对的数量成线性关系

{code:none}
> (macroexpand-1 '(cond (test1) (body1) (test2) (body2) (test3) (body3)))
(if (test1) (body1) (clojure.core/cond (test2) (body2) (test3) (body3)))


完整的宏展开会导致一系列的 'if' 语句,并且对每个新的测试/表达式对的分析在编译器堆栈上更深。

这里[1]是一个最小复现案例。

这是一个普遍问题,但在核心异步(core.async)的上下文中,这可能会成为一个真正的问题,因为核心异步为其状态机生成大量的 cond 表达式,可能数百个状态。当嵌套时,这种情况会加剧。

--

最好以两种方式解决这个问题
a) 尝试实现一个更友好的堆栈 cond 宏(我认为问题不在于 core.async 生成代码的方式)
   或者尝试实现一种“尾调用优化”的分析方法,这不会在下降到最后的子形式时增加堆栈(如果可能的话)
b) 使用用户友好的解释来处理堆栈溢出异常 - 例如,跟踪对 `analyze_form` 的内部递归调用次数,并在异常发生时尝试传达可能发生的情况,并(理想的情况下)指出哪种形式/宏展开导致了堆栈的增长

[1] https://gist.github.com/darwin/8dda48153fcbd3bc3e2d700f3e0eea00


==> _compile.sh <==
#!/usr/bin/env bash

clj -Srepro -m cljs.main -co @compiler-opts.edn -c repro

==> compiler-opts.edn <==
{:source-paths ["."]
 :warnings {:single-segment-namespace false}}
==> deps.edn <==
{:paths ["."]
 :deps {org.clojure/clojure {:mvn/version "1.10.0"}
        org.clojure/clojurescript {:mvn/version "1.10.439"}}}
==> repro.clj <==
(ns repro)

(defmacro fat-cond [n]
  (let [clauses (repeat n [`false `(cljs.core/do)])]
    `(cond
       ~@(flatten clauses))))
==> repro.cljs <==
(ns repro
  (:require-macros [repro :refer [fat-cond]]))

(fat-cond 10000)

1 答案

0
参考: https://clojure.atlassian.net/browse/CLJS-3042(由darwin报告)
...