请分享您的想法,参与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的背景下可能会有实际问题,因为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报告)
...