Why does this code
(defn f [a]
(let [ret a]
(fn [])))
is being emitted with a closure around returning function?
cljs.user.f = (function cljs$user$f(a){
var ret = a;
return ((function (ret){
return (function (){
return null;
});
;})(ret))
});
Does it have something to do with local binding?Hey @dnolen, a regression surrounding the .call
/ ^js
revision: https://clojure.atlassian.net/browse/CLJS-3156
@roman01la Yeah, I suppose if you ask "What strategy does ClojureScript use to implement locally-scoped bindings for let
?" the answer is that it uses anonymous function closures to achieve that end.
I think you are asking a deeper question, though, about the enclosed value...
I think I’m trying to understand why is it emitted in this case where it seems like there’s no point in additional scope. But I guess this is just a general strategy and cljs doesn’t do any optimizations.
Oh, right. It is definitely true that the compiler often employs simple strategies that work generally, and oftentimes doesn't attempt to optimize for specific cases (relying on Closure). And oftentimes, code-gen optimizations that have been introduced are there simply because Closure or the underlying JavaScript VMs are not clever enough to optimize the emitted code. I know the above is not really saying much, but, its a true statement. 🙂
The simplest example of this notion is the generous emission of parentheses in the JavaScript. Honestly, no real effort is made to determine when they can be elided. And oftentimes we find we need to throw in extra parens to ensure the desired semantics for corner cases that are discovered.
@roman01la With the patch in https://clojure.atlassian.net/browse/CLJS-3077, here is what is generated instead:
cljs.user.f = (function cljs$user$f(a){
var ret = a;
return (function (){
return null;
});
});
@roman01la fns are loop/recur contexts and before we didn't know what we were going to see because only one pass
since we compile loop/recur to JS loops you get mutable locals
only way to avoid that is that close over the environment and pass the values of all locals
@mfikes I'm somewhat skeptical about 3156 - since we are using the function tag to determine if call can be eliminated
not the arguments - but your example is about tagging args
I mean the Canary test failed so something got caught - but it doesn't add up to me at the moment
Yeah, perhaps I'm mischaracterizing the root cause. All I really have in the ticket is a :sha
where it worked and the next :sha
where it broke
@mfikes is Canary run w/ optimizations or no?
I'll check... it is the Hoplon build that it failed in.
if so I'd be even more skeptical since there wouldn't be call or not anyway
direct fn method invoke
FWIW, the generated JavaScript ends up looking roughly like new PersistentHashSet(... its args)(the other args)
IIRC
that would imply that PersistentHashSet is being tagged js
Here is a quick example that is probably less sketchy
new cljs.core.PersistentHashSet(null, new cljs.core.PersistentArrayMap(null, 1, ["node",null], null), null).call(null,some_value_here)
This example reflects good code gen. I think the .call
gets removed and breaks it.
The code generated for the failing example in the ticket is:
new cljs.core.PersistentHashSet(null, new cljs.core.PersistentArrayMap(null, 1, ["node",null], null), null)(require("process").title)
The code generated for the previous :sha
is
new cljs.core.PersistentHashSet(null, new cljs.core.PersistentArrayMap(null, 1, ["node",null], null), null).call(null,require("process").title);
@mfikes I'm also a bit confused because - there was commit where I backed out that change
that code no longer exists in master - there's something a bit different now
Oh... OK, perhaps my git bisect randomly went down that path. Hah. Crap.
I just mean we cannot infer that's the breaking thing because it doesn't even exist anymore
so we know that commit is bad
OK. That ticket's invalid then. I'll cook up a new one based on how Hoplon is breaking with master.
https://github.com/clojure/clojurescript/commit/82fda4ab7305c83cc45dda6ff695bedfe0fe9c70
reflects what I wanted to do w/ master
this commit widened the scope https://github.com/clojure/clojurescript/commit/8f38049d543b04b8da55029f140b6577e3ec245a
so I'd be curious to know what code gen looks like w/ those commits
I confirmed that https://clojure.atlassian.net/browse/CLJS-3156 doesn't occur with the master :sha
@mfikes so Canary isn't borked?
Hoplon breaks with master
ok cool, so something else entirely
So, I did a git bisect, and accidentally ended up in the path of that interim broken commit.
(well not cool - but at least we know that's not the culprit)
I'm going to do another bisect to see if I can isolate it