Fork me on GitHub
#cljs-dev
<
2022-08-07
>
borkdude11:08:03

Function calls like this:

(defn foo [x y] (+ x 1))

(foo 1 2)
are compiled to:
cljs.user.foo = (function cljs$user$foo(x,y){
  return (x + (1));
});
cljs.user.foo.call(null,(1),(2));
Are there any performance disadvantages to .call instead of compiling to a direct call?

borkdude11:08:06

I guess it's hard to tell in general as this relies on the behavior of the JS runtime

thheller11:08:04

with :static-fns true it'll skip the .call (true by default for :advanced, always true for shadow-cljs outside the REPL)

thheller11:08:52

it carries a small perf hit IIRC

borkdude11:08:41

Nice. Btw, I just noticed that Closure optimizes this:

function hello(name) {
  alert('Hello, ' + name);
}
hello.call(null, 'New user');
to:
alert("Hello, New user");
lol

thheller11:08:25

why lol? seems exactly right?

borkdude11:08:46

yes, just found it funny

mfikes00:08:33

@U04V15CAJ There is some higher-order stuff along these lines as well. For example see https://blog.fikesfarm.com/posts/2017-11-16-direct-higher-order-function-calls.html

borkdude08:08:53

@U04VDQDDY Really interesting. What is the default of fn-invoke-direct in 2022?

thheller09:08:19

still false

borkdude15:08:24

Just sharing this as info here, in case anyone has thoughts on this. https://github.com/thheller/shadow-cljs/commit/e8e0cf9973bafcd472ffa1066bbd8ae8c83e5cab @thheller has done some work on emitting ES6 modules via Closure, but unfortunately, binding in cljs.core generates a re-assignment of an import which isn't valid in ES6. Perhaps there's a way around it.

borkdude15:08:44

It would perhaps be a too big of a change to CLJS to model dynamic vars as mutable objects?

borkdude15:08:00

@thheller Another thought: along with a dynamic var foo one could emit set_foo and emit (set_foo x) instead of foo = x or so?

thheller17:08:01

the problem is more general than that. any set! on a "var" can cause this, so its not limited to binding.

thheller17:08:11

foo.bar.x = 1 gets renamed to var aA = 1; and all cross-chunk references are then export { aA } and import { aA } ... on the others. can't aA = 2 in the others since that is not allowed in ESM

thheller17:08:04

adding the setter would allow it technically but we can't do that for every var

thheller17:08:49

I think its just not worth using the chunk output type in closure for now. its relatively new so who knows what other issues it may have

👍 1
thheller17:08:43

var would also probably break given that it also generates a potentially cross-chunk assignment

borkdude17:08:50

ES6 needs mutable exports ;)

yes 2
thheller17:08:52

only reason the renamed-prefix-namespace "trick" (via $APP) is not ideal is because it probably hinders tree-shaking in esbuild and others

thheller17:08:11

otherwise its probably better than what the closure stuff does, as that generates quite a few exports which is not ideal if you want other places to consume those modules

borkdude17:08:34

Thanks for looking into this

thheller18:08:50

thanks for testing my attempts 😉 in the end the output is still a bit better than before, so at least got something out of it.

borkdude18:08:00

> adding the setter would allow it technically but we can't do that for every var why not actually? I think only top-level vars would be sufficient

thheller19:08:56

well given what :advanced does it just inline any function like that. so foo.bar.set_x = function(v) { foo.bar.x = v; } is just turned into foo.bar.x = v as one of the first steps of the optimizations

thheller19:08:06

and you start back where you started 😉

thheller19:08:32

but thats just my instinct, would need verification of course

borkdude19:08:52

I assume Closure would understand ES6 code that mutates stuff within the module and wouldn't mess it up like that?

thheller19:08:51

problem right now is that the ESM stuff is done at the very end of compilation