babashka

borkdude 2025-08-31T10:38:37.111299Z

I'm taking a look at this test suite https://github.com/jank-lang/clojure-test-suite and trying to get bb compatible with this. One of the things that differed with JVM Clojure in bb was this:

(/ ##Inf 0) => ##Inf ;; in bb Divide by zero
Why do I get in JVM Clojure a divide by zero in this first example below but not in the second?
((fn
     ([x] (/ 1 x))
     ([x y] (. clojure.lang.Numbers (divide x y)))
     ([x y & more]
      (reduce / (/ x y) more)))
   ##Inf 0) ;; divide by zero

(. clojure.lang.Numbers (divide ##Inf 0)) ;; ##Inf 

dgr 2025-09-03T19:40:21.196829Z

Sorry for the lag. Yea, @borkdude, it’s probably all related to boxing. I was finding all sorts of subtle things related to boxing. As you’re aware, Clojure JVM tries to keep things unboxed and relies on the underlying JVM to detect issues. When things are boxed, it often uses its own code paths to try to detect issues. I found some, ahem, nuances around #NaNs that generated some #clojure discussion a few months back when I was writing those tests. I won’t claim I covered every possible case as I wasn’t specifically trying to force both boxing and unboxing for every test, but I did try to capture differences when I found them, at least so they would be documented, even if never fixed. It looks like you guys worked this out a couple days ago. If there’s still more questions, I’m happy to answer with what I know, which is probably less than you already do.

👍 1
borkdude 2025-08-31T10:41:00.460699Z

Is this perhaps something that should be fixed in Clojure itself?

borkdude 2025-08-31T10:54:17.678159Z

I guess it has something to do with boxing:

(defn f [x y]
       (. clojure.lang.Numbers (divide x y)))
(f ##Inf 0) ;; throws

borkdude 2025-08-31T11:04:19.779349Z

This doesn't throw:

(defn f [x y]
       (let [y (double y)]
         (. clojure.lang.Numbers (divide x y))))
     (f ##Inf 0)

borkdude 2025-08-31T11:08:06.014409Z

and this throws:

(defn f [x y]
       (let [y (long y)]
         (. clojure.lang.Numbers (divide x y))))
(f ##Inf 0)

borkdude 2025-08-31T11:32:42.788919Z

Maybe a reasonable fix for SCI would be to use this replacement for inline division

f (condp identical? f
                                                / (let [div (fn [x y]
                                                              (if (double? x)
                                                                (let [x (double x)]
                                                                  (/ x y))
                                                                (/ x y)))]
                                                    (fn
                                                      ([x] (div 1 x))
                                                      ([x y] (div x y))
                                                      ([x y & more]
                                                       (reduce / (div x y) more))))
                                                f)

Bob B 2025-08-31T14:56:27.343709Z

just digging around in the clojure code, it looks like double y calls doubleValue on x and then uses the division operator, so it's getting java's division. The long overload casts the long to an Object and then uses the (Object, Object) overload, which has the divide by zero check. I assume that basically everything in bb will be boxed (??), so we'd always get the (Object, Object) overload, which has the zero check but not an ##Inf check

borkdude 2025-08-31T15:08:09.690579Z

right. I can fix this to align with the jank/clojure-test-suite (I have a fix in a branch) but this would maybe cause other behavior for boxed arguments that before always threw. so maybe there isn't a good solution overall

Bob B 2025-08-31T15:14:12.660959Z

Yeah, I haven't thought through all the possibilities, but I wonder if that fix would flip the tables, and maybe some permutations that would throw in JVM clj will start succeeding in bb. It feels like to make the behavior consistent with JVM, bb would need to decide whether or not the number would have been boxed.

borkdude 2025-08-31T15:15:43.956919Z

> that would throw in JVM clj will start succeeding in bb yeah exactly > need to decide whether or not the number would have been boxed. indeed. not impossible. if the thing has a type tag (or inferred) or it's coming from a literal

borkdude 2025-08-31T15:15:55.189159Z

but not sure if it's worth the trouble

borkdude 2025-08-31T15:16:45.040389Z

for now I made some tweaks to the test suite for bb as it passes right now: https://github.com/jank-lang/clojure-test-suite/pull/94

borkdude 2025-08-31T15:20:54.631929Z

btw the "fix" is in the SCI branch div-by-zero

borkdude 2025-08-31T15:21:02.896559Z

if you're curious

borkdude 2025-08-31T15:21:26.719979Z

I can maybe fix it for cases where SCI is absolutely sure the value would have been unboxed

borkdude 2025-08-31T15:21:40.367759Z

I believe this can only be true for local values, not for function return values, right?

borkdude 2025-08-31T15:27:24.225319Z

except when those function return values are inlined, like inc etc

borkdude 2025-08-31T15:27:31.941499Z

which make it a bit more complex

borkdude 2025-08-31T15:28:14.288959Z

but not impossible

Bob B 2025-08-31T15:29:05.278489Z

I think so.. the wild world of boxing has never been a strong suit of mine.I assume literals would always be unboxed, and anything else is potentially indeterminate. The 'was this inlined' thing feels to me, at least superficially, like it's wandering toward trying to make a determination about 'boxedness', but that's just an opinion. 🙂

borkdude 2025-08-31T15:30:05.273869Z

do you mean in SCI?

borkdude 2025-08-31T15:30:35.388419Z

the "was this inlined" thing has been there for a long time to speed up things a little. you don't have to do through the var in that case, since clojure also inlines it

borkdude 2025-08-31T15:30:52.027009Z

but it can also be used to determine if something was unboxed in clojure

borkdude 2025-08-31T15:30:56.547429Z

maybe

Bob B 2025-08-31T15:32:47.894089Z

oh... if there's already a point there, then that's cool - I made the (faulty) assumption that it would be a specialization for trying to handle this edge case, which made it feel like it might've been significant effort to make a net new determination to handle an edge case.

borkdude 2025-08-31T15:33:33.494649Z

the specialization is just for / but this is inside the check for inlined vars

borkdude 2025-08-31T15:34:24.318159Z

we could have other specialized functions but so far the ##Inf and ##NaN thing is the only thing that has come up (in an earlier issue as well)

Bob B 2025-08-31T15:37:57.552099Z

Yeah, I'm with ya... I was missing the fact that "was this inlined" already existed. Is that the only test that failed? That test suite seems to have a number of "surprisingly, X happens" comments in the numeric functions, so it's maybe a testament to bb's parity if there's only one edge case identified where it's different. 🙂

borkdude 2025-08-31T15:40:02.289859Z

there were some others like (byte 128.1) . the inlined version of clojure checks this (in the compiler I think), but bb always goes through vars and those don't check

jeaye 2025-08-31T17:33:26.048929Z

@droberts3 For vis, since he cooked these up.