Skip to content

Commit 114a528

Browse files
rauhsswannodette
authored andcommitted
CLJS-2046: Optimize expression in call position
If a function expression like `(@x :m)` is seen, the compiler first binds the `@x` so efficient calls that check for the IFn protocol are emitted. Also add a test for CLJS-855
1 parent a6d0b54 commit 114a528

File tree

3 files changed

+60
-13
lines changed

3 files changed

+60
-13
lines changed

src/main/clojure/cljs/analyzer.cljc

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2859,7 +2859,18 @@
28592859
argc (count args)
28602860
fn-var? (-> fexpr :info :fn-var)
28612861
kw? (= 'cljs.core/Keyword (:tag fexpr))
2862-
cur-ns (-> env :ns :name)]
2862+
cur-ns (-> env :ns :name)
2863+
HO-invoke? (and (boolean *cljs-static-fns*)
2864+
(not fn-var?)
2865+
(not kw?)
2866+
(not (analyzed? f)))
2867+
;; function expressions, eg: ((deref m) x) or ((:x m) :a)
2868+
bind-f-expr? (and HO-invoke?
2869+
(not (symbol? f)))
2870+
;; Higher order invokes with (some) argument expressions. Bind the arguments
2871+
;; to avoid exponential complexity that is created by the IFn arity check branch.
2872+
bind-args? (and HO-invoke?
2873+
(not (all-values? args)))]
28632874
(when ^boolean fn-var?
28642875
(let [{:keys [^boolean variadic max-fixed-arity method-params name ns macro]} (:info fexpr)]
28652876
;; don't warn about invalid arity when when compiling a macros namespace
@@ -2881,19 +2892,20 @@
28812892
(warning :fn-deprecated env {:fexpr fexpr})))
28822893
(when (some? (-> fexpr :info :type))
28832894
(warning :invoke-ctor env {:fexpr fexpr}))
2884-
(if (or (not (boolean *cljs-static-fns*))
2885-
(not (symbol? f))
2886-
fn-var?
2887-
(analyzed? f)
2888-
(all-values? args))
2895+
(if (or bind-args? bind-f-expr?)
2896+
(let [arg-syms (when bind-args? (take argc (repeatedly gensym)))
2897+
f-sym (when bind-f-expr? (gensym "fexpr__"))
2898+
bindings (cond-> []
2899+
bind-args? (into (interleave arg-syms args))
2900+
bind-f-expr? (conj f-sym f))]
2901+
(analyze env
2902+
`(let [~@bindings]
2903+
(~(vary-meta (if bind-f-expr? f-sym f) assoc ::analyzed true)
2904+
~@(if bind-args? arg-syms args)))))
28892905
(let [ana-expr #(analyze enve %)
28902906
argexprs (map ana-expr args)]
28912907
{:env env :op :invoke :form form :f fexpr :args (vec argexprs)
2892-
:children (into [fexpr] argexprs)})
2893-
(let [arg-syms (take argc (repeatedly gensym))]
2894-
(analyze env
2895-
`(let [~@(vec (interleave arg-syms args))]
2896-
(~(vary-meta f assoc ::analyzed true) ~@arg-syms)))))))
2908+
:children (into [fexpr] argexprs)}))))
28972909

28982910
(defn parse-invoke
28992911
[env form]

src/test/cljs/cljs/invoke_test.cljs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@
99
([a] a)
1010
([a b] a))
1111

12+
(defn hof-fn-expr-should-be-bound
13+
[funexpr0 normal-arg]
14+
((complement funexpr0) normal-arg))
15+
16+
(defn hof-arg-should-be-bound
17+
[hofinvoke inv-arg0]
18+
(hofinvoke (inv-arg0)))
19+
20+
(defn hof-fn-expr+args-should-be-bound
21+
[funexpr1 inv-arg1]
22+
((complement funexpr1) (inv-arg1)))
23+
24+
;; A keyword should not be bound in a let:
25+
(def foo (delay
26+
(:dont-bind-this js/x)))
27+
1228
(multi-fn 2)
1329

1430
(gstr/urlEncode "foo")

src/test/clojure/cljs/compiler_tests.clj

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,27 @@
237237
;; closure js code must never use .call(
238238
(is (str/includes? content "goog.string.urlEncode("))
239239
;; js/goog.string.urlDecode should not use .call
240-
(is (str/includes? content "goog.string.urlDecode(")))
241-
))
240+
(is (str/includes? content "goog.string.urlDecode("))
241+
;; We should NOT emit a let binding for simple (:dont-bind-this js/x)
242+
(is (str/includes? content
243+
(str "new cljs.core.Keyword(null,\"dont-bind-this\",\"dont-bind-this\","
244+
"-140451389).cljs$core$IFn$_invoke$arity$1(x);")))
245+
;; CLJS-2046: Emit bindings for expressions like: (@m a0) or ((:x m) a0)
246+
;; The test: ((complement funexpr0) normal-arg)
247+
(is (re-find #"(?m)^.*var fexpr.*=.*cljs.core.complement\(funexpr0\);$"
248+
content))
249+
;; CLJS-855: Emit binding for expressions like:
250+
;; (hofinvoke (inv-arg0))
251+
(is (re-find #"(?m)^.*var .*=.*inv_arg0.cljs.core.IFn._invoke.arity.0 \?.*$"
252+
content))
253+
254+
;; Now test both (855,2046) together:
255+
;; ((complement funexpr1) (inv-arg1))
256+
(is (re-find #"(?m)^.*var fexpr.*=.*cljs.core.complement\(funexpr1\);$"
257+
content))
258+
(is (re-find #"(?m)^.*var .*=.*inv_arg1.cljs.core.IFn._invoke.arity.0 \?.*$"
259+
content)))))
260+
#_(test-vars [#'test-optimized-invoke-emit])
242261

243262
;; CLJS-1225
244263

0 commit comments

Comments
 (0)