Compiler generates unnecessary function wrappers for functions
Key details
Description
The CLJS compiler will generate a lot of unnecessary function wrappers for functions created inside a let (and in certain cases outside). These function wrappers are only necessary when inside a loop or when the function uses recur to work arround quirks in JS var. As far as I can tell this commit [1] changed the behavior to instead always do it.
This can generate quite a lot of "extra" code that the Closure Compiler will not eliminate.
A dummy example shows that these wrapper fns can get quite excessive argument lists even if they don't use any of the locals that function wrapper is meant to preserve.
(ns test.app)
(defn other [x])
(defn dummy [{:strs [foo bar coll] :as p}]
(let [x "test"
y "test"
z "test"
a (fn [i] i)
b (fn [i] i)
c (fn [i] i)]
(fn return []
(array a b c))
))
The generated code
// Compiled by ClojureScript 1.10.520 {}
goog.provide('test.app');
goog.require('cljs.core');
test.app.other = function test$app$other(x) {
return null;
};
test.app.dummy = function test$app$dummy(p__526) {
var map__527 = p__526;
var map__527__$1 = (!(map__527 == null)
? map__527.cljs$lang$protocol_mask$partition0$ & 64 ||
cljs.core.PROTOCOL_SENTINEL === map__527.cljs$core$ISeq$
? true
: false
: false)
? cljs.core.apply.call(null, cljs.core.hash_map, map__527)
: map__527;
var p = map__527__$1;
var foo = cljs.core.get.call(null, map__527__$1, 'foo');
var bar = cljs.core.get.call(null, map__527__$1, 'bar');
var coll = cljs.core.get.call(null, map__527__$1, 'coll');
var x = 'test';
var y = 'test';
var z = 'test';
// this could just be var a = function(i) { return i };
var a = (function(x, y, z, map__527, map__527__$1, p, foo, bar, coll) {
return function(i) {
return i;
};
})(x, y, z, map__527, map__527__$1, p, foo, bar, coll);
var b = (function(x, y, z, a, map__527, map__527__$1, p, foo, bar, coll) {
return function(i) {
return i;
};
})(x, y, z, a, map__527, map__527__$1, p, foo, bar, coll);
var c = (function(x, y, z, a, b, map__527, map__527__$1, p, foo, bar, coll) {
return function(i) {
return i;
};
})(x, y, z, a, b, map__527, map__527__$1, p, foo, bar, coll);
return (function(x, y, z, a, b, c, map__527, map__527__$1, p, foo, bar, coll) {
return function test$app$dummy_$_return() {
return [a, b, c];
};
})(x, y, z, a, b, c, map__527, map__527__$1, p, foo, bar, coll);
};
//# sourceMappingURL=app.js.map
I didn't do any benchmarks yet but I'm pretty sure this has a certain runtime overhead as well.
The code that causes this should be optimized to only emit those wrappers when actually needed and also only for the locals that were actually used.
Alternatively we could use JS const (or let) since nowadays >98% of Browsers in use support this [2] and the Closure Compiler will generated those function wrappers for us with certain :language-out settings (ie. the current default would).
The CLJS compiler will generate a lot of unnecessary function wrappers for functions created inside a
let(and in certain cases outside). These function wrappers are only necessary when inside aloopor when the function usesrecurto work arround quirks in JSvar. As far as I can tell this commit [1] changed the behavior to instead always do it.This can generate quite a lot of "extra" code that the Closure Compiler will not eliminate.
A dummy example shows that these wrapper fns can get quite excessive argument lists even if they don't use any of the locals that function wrapper is meant to preserve.
(ns test.app) (defn other [x]) (defn dummy [{:strs [foo bar coll] :as p}] (let [x "test" y "test" z "test" a (fn [i] i) b (fn [i] i) c (fn [i] i)] (fn return [] (array a b c)) ))The generated code
// Compiled by ClojureScript 1.10.520 {} goog.provide('test.app'); goog.require('cljs.core'); test.app.other = function test$app$other(x) { return null; }; test.app.dummy = function test$app$dummy(p__526) { var map__527 = p__526; var map__527__$1 = (!(map__527 == null) ? map__527.cljs$lang$protocol_mask$partition0$ & 64 || cljs.core.PROTOCOL_SENTINEL === map__527.cljs$core$ISeq$ ? true : false : false) ? cljs.core.apply.call(null, cljs.core.hash_map, map__527) : map__527; var p = map__527__$1; var foo = cljs.core.get.call(null, map__527__$1, 'foo'); var bar = cljs.core.get.call(null, map__527__$1, 'bar'); var coll = cljs.core.get.call(null, map__527__$1, 'coll'); var x = 'test'; var y = 'test'; var z = 'test'; // this could just be var a = function(i) { return i }; var a = (function(x, y, z, map__527, map__527__$1, p, foo, bar, coll) { return function(i) { return i; }; })(x, y, z, map__527, map__527__$1, p, foo, bar, coll); var b = (function(x, y, z, a, map__527, map__527__$1, p, foo, bar, coll) { return function(i) { return i; }; })(x, y, z, a, map__527, map__527__$1, p, foo, bar, coll); var c = (function(x, y, z, a, b, map__527, map__527__$1, p, foo, bar, coll) { return function(i) { return i; }; })(x, y, z, a, b, map__527, map__527__$1, p, foo, bar, coll); return (function(x, y, z, a, b, c, map__527, map__527__$1, p, foo, bar, coll) { return function test$app$dummy_$_return() { return [a, b, c]; }; })(x, y, z, a, b, c, map__527, map__527__$1, p, foo, bar, coll); }; //# sourceMappingURL=app.js.mapI didn't do any benchmarks yet but I'm pretty sure this has a certain runtime overhead as well.
The code that causes this should be optimized to only emit those wrappers when actually needed and also only for the locals that were actually used.
Alternatively we could use JS
const(orlet) since nowadays >98% of Browsers in use support this [2] and the Closure Compiler will generated those function wrappers for us with certain:language-outsettings (ie. the current default would).[1] https://github.com/clojure/clojurescript/commit/78d20eebbbad17d476fdce04f2afd7489a507df7
[2] https://caniuse.com/#feat=const