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
).
the compiler never actually knows the return type
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
if the function isn't inlined, there is just the function call, which the compiler doesn't know the return type of
@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...
right
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.
Which means the compiler is doing some amount of "pre-evaluation" in there.
it is hard to say exactly, because the the compilation is failing so you can't check the bytecode
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
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=>
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=>
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?I am not sure, I don't recall the specifics of inlining, but I don't think it is any kind of pre-evaluation
it is the reflection
(which you don't get a warning about)
interesting
how did the bottleneck present itself?
what did the code look like roughly? would like to bank it in case i run into something like this
@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.
Iโm just saying, it was easy to have reflection here, that had no warnings
Consider:
(fn [xs] (map alength xs)) ;;= no warnings
(fn [xs] (map #(alength %) xs)) ;;= now has warnings
So the inline stuff is subtle
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.
the static method call that boolean is inlined to is overloaded for Object and boolean arguments, but doesn't have a method for longs
(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)
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=>
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).
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 ๐
This lack of warning was a source of a large perf bottleneck in our codebase the other day. Hah. Was with alength