Welcome! Please see the About page for a little more info on how this works.

0 votes
in ClojureScript by
There is a real chance of running into cryptic stackoverflow errors when compiling more complex core.async code.

One such recent situation:
https://dev.clojure.org/jira/browse/CLJS-3030?focusedCommentId=51115&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-51115

The likely cause is in the way how `cond` is macro-expanded. The problem is that compiler stack depth is linear in number of test/expr pairs:

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


Full macro-expansion leads to a chain of 'if' statements and analysis of each new test/expr pair happens deeper on the compiler stack.

Here[1] is a minimal repro case.

This is a general problem but it can be a real issue in the context of core.async which generates very large cond form for its state machine, potentially hundreds of states. It compounds when nested.

--

It would be nice if this was addressed in two ways:
a) an attempt to implement more stack-friendly cond macro (I don't think the fault is in how core.async generates the code)
   or an attempt to implement some kind of "tail spot" analysis which would not grow stack when descending into last sub-form (if possible)
b) handle stackoverflow exception with user-friendly explanation - e.g. track number of internal recursive calls to `analyze_form` and in case of an exception try to communicate what likely happened and (ideally) which form/macroexpansion was responsible for such stack growth

[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 Answer

0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJS-3042 (reported by darwin)
...