clojure-dev

Issues: https://clojure.atlassian.net/browse/CLJ | Guide: https://insideclojure.org/2015/05/01/contributing-clojure/
seancorfield 2020-07-24T16:47:03.411500Z

This one's pretty interesting https://ask.clojure.org/index.php/9499/inconsistent-unmatching-primitive-recur-arg -- I was trying to help @kenny with that last night and it seems that under some situations with inline functions, the compiler does something that loses the primitive return type and instead uses a promoted type (effectively Object).

2020-07-24T16:56:28.412200Z

the compiler never actually knows the return type

2020-07-24T16:57:19.412900Z

what happens is if the function is inlined, there is no function call, just the static method call which the compiler can see the type of

2020-07-24T16:58:13.413800Z

if the function isn't inlined, there is just the function call, which the compiler doesn't know the return type of

seancorfield 2020-07-24T17:07:08.417Z

@hiredman This situation is a bit more complex tho'. It knows the boolean primitive type of the local but for (boolean x) it thinks that returns boolean only in some situations...

2020-07-24T17:07:57.418Z

right

seancorfield 2020-07-24T17:08:11.418600Z

So having figured out x (pos? y) should be treated as a boolean primitive, when you recur with (boolean 1) it fails the primitive type check in the compiler, but with (boolean nil) it accepts it.

seancorfield 2020-07-24T17:08:41.419300Z

Which means the compiler is doing some amount of "pre-evaluation" in there.

2020-07-24T17:08:58.419800Z

it is hard to say exactly, because the the compilation is failing so you can't check the bytecode

2020-07-24T17:09:29.420900Z

but for some reason the compiler is deciding not to inline the call to boolean on (boolean 1) which is why you aren't getting return type information

seancorfield 2020-07-24T17:12:38.421200Z

user=> (loop [x (boolean (first []))] (when x (recur true)))
Syntax error (IllegalArgumentException) compiling fn* at (REPL:25:1).
 recur arg for primitive local: x is not matching primitive, had: java.lang.Boolean, needed: boolean
user=> (loop [x (boolean (first []))] (when x (recur (boolean true))))
nil
user=> (loop [x (boolean (first []))] (when x (recur (boolean 1))))
Syntax error (IllegalArgumentException) compiling fn* at (REPL:27:1).
 recur arg for primitive local: x is not matching primitive, had: Object, needed: boolean
user=> (loop [x (boolean (first []))] (when x (recur (boolean nil))))
nil
user=> (loop [x (boolean (first []))] (when x (recur (boolean (first [])))))
nil
user=> 

seancorfield 2020-07-24T17:14:05.421900Z

It's not fooled by this either:

user=> (loop [x (boolean (first []))] (when x (recur (boolean (long 1)))))
Syntax error (IllegalArgumentException) compiling fn* at (REPL:30:1).
 recur arg for primitive local: x is not matching primitive, had: Object, needed: boolean
user=> 

seancorfield 2020-07-24T17:14:42.422500Z

But

user=> (loop [x (boolean (first []))] (when x (recur (boolean (+ 1)))))
nil
user=> 
So I guess it is doing some literal expression "pre-evaluation" and not others here?

2020-07-24T17:19:45.423500Z

I am not sure, I don't recall the specifics of inlining, but I don't think it is any kind of pre-evaluation

2020-07-24T17:23:17.424200Z

it is the reflection

2020-07-24T17:23:27.424500Z

(which you don't get a warning about)

devn 2020-07-28T15:56:29.430700Z

interesting

devn 2020-07-28T15:56:41.430900Z

how did the bottleneck present itself?

devn 2020-07-28T15:57:13.431100Z

what did the code look like roughly? would like to bank it in case i run into something like this

2020-07-28T16:17:59.431300Z

@devn When something was taking a long time, I did some CPU sampling with a profiler (visualvm here) and saw a lot of reflection being used. When looking at the stack for that reflection it was coming out of alength. Granted, this isnโ€™t always a problem - this was an actual hot spot and it was being called a lot across a lot of data.

2020-07-28T16:18:11.431500Z

Iโ€™m just saying, it was easy to have reflection here, that had no warnings

2020-07-28T16:18:29.431800Z

Consider:

(fn [xs] (map alength xs)) ;;= no warnings
(fn [xs] (map #(alength %) xs)) ;;= now has warnings

2020-07-28T16:19:32.432100Z

So the inline stuff is subtle

devn 2020-07-31T03:52:06.435300Z

Thanks for sharing that. It is indeed subtle. I think a long time ago I profiled something like this and tinkered with it enough to fix the performance issue by accident, but did not recognize a hidden reflection issue.

๐Ÿ‘ 1
2020-07-24T17:24:44.425600Z

the static method call that boolean is inlined to is overloaded for Object and boolean arguments, but doesn't have a method for longs

2020-07-24T17:26:40.426800Z

(boolean (+ 1)) gets the object overload, (boolean 1) looks for a method that takes a long and doesn't find it and does something different (not sure exactly what)

2020-07-24T17:28:40.427100Z

Clojure 1.10.1
user=> (set! *warn-on-reflection* true)
true
user=> (loop [x (boolean (first []))] (when x (recur (boolean 1))))
Reflection warning, NO_SOURCE_PATH:1:47 - call to static method booleanCast on clojure.lang.RT can't be resolved (argument types: long).
Syntax error (IllegalArgumentException) compiling fn* at (REPL:1:1).
 recur arg for primitive local: x is not matching primitive, had: Object, needed: boolean
user=> (loop [x (boolean (first []))] (when x (recur (boolean (+ 1)))))
nil
user=> 

seancorfield 2020-07-24T17:31:00.427900Z

Ah! That makes more sense than what I was thinking was happening (that the compiler was a lot smarter about inlining than it clearly is).

seancorfield 2020-07-24T17:31:48.428800Z

I was imagining it was doing the sort of smart code inlining that I had worked on back in the '80s and '90s when I wrote compilers and virtual machine runtimes for a living ๐Ÿ™‚

2020-07-24T17:57:18.429500Z

This lack of warning was a source of a large perf bottleneck in our codebase the other day. Hah. Was with alength