Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions src/sci/impl/jit.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -352,22 +352,25 @@
(def ^:private eq-eq ==)
(def ^:private eq-eq-gen (bin-op "==="))

(defn- tri-op [op]
;; + - * fold left at arity 3, like the cljs.core macros
(fn [a] (str "(" (nth a 0) op (nth a 1) op (nth a 2) ")")))
(defn- n-op [op]
;; + - * fold left at every arity, like the cljs.core macros, so the
;; chained JS operator is exact in floating point too
(fn [a] (str "(" (.join (to-array a) op) ")")))

(def ^:private op-gens-3
(def ^:private op-gens-n
(doto (js/Map.)
(.set + (tri-op "+"))
(.set - (tri-op "-"))
(.set * (tri-op "*"))))
(.set + (n-op "+"))
(.set - (n-op "-"))
(.set * (n-op "*"))
(.set unchecked-add (n-op "+"))
(.set unchecked-subtract (n-op "-"))
(.set unchecked-multiply (n-op "*"))))

(defn- op-gen [f n]
(case n
1 (.get op-gens-1 f)
2 (.get op-gens-2 f)
3 (.get op-gens-3 f)
nil))
(when (> n 2) (.get op-gens-n f))))

(defn- emit-call [st amb [op callee children stack]]
(let [n (count children)
Expand Down
35 changes: 34 additions & 1 deletion test/sci/jit_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"Differential tests for the JS codegen tier: the same program evaluated
jitted and interpreted must agree on values, error messages and error
locations."
(:require [clojure.test :as t :refer [deftest is testing]]
(:require [clojure.string :as str]
[clojure.test :as t :refer [deftest is testing]]
[sci.core :as sci]
[sci.impl.jit :as jit]))

Expand Down Expand Up @@ -103,6 +104,38 @@
[interp jitted] (eval-both src)]
(is (= interp jitted) src)))))

(deftest jit-nary-operator-parity-test
(testing "n-ary + - * agree with the interpreter, left-folded, at every arity"
(let [vals-src ["0" "1" "-1" "0.1" "0.5" "##NaN" "##Inf" "1e308" "1000000"
"\"a\"" "nil" "true" "[1 2]" "{:a 1}" ":k"]
n-vals (count vals-src)]
(doseq [op '[+ - * unchecked-add unchecked-subtract unchecked-multiply]
arity (range 3 9)
offset (range n-vals)]
(let [args (map #(nth vals-src (mod (+ offset %) n-vals)) (range arity))
params (map #(str "x" %) (range arity))
src (str "((fn [" (str/join " " params) "] ("
op " " (str/join " " params) ")) "
(str/join " " args) ")")
[interp jitted] (eval-both src)]
(is (= interp jitted) src))))))

(deftest jit-nary-arithmetic-inlines-test
(testing "n-ary + - * emit a chained operator, not a variadic call"
(doseq [[op sep] '[[+ "+"] [- "-"] [* "*"]]
arity (range 2 9)
:when (do (jit/enable!) (jit/enabled?))]
(let [params (map #(str "x" %) (range arity))
src (str "(fn [" (str/join " " params) "] ("
op " " (str/join " " params) "))")]
(vreset! jit/last-srcs [])
(vreset! jit/collect-srcs? true)
(apply (sci/eval-string* (sci/init {}) src) (repeat arity 2))
(vreset! jit/collect-srcs? false)
(let [js (apply str @jit/last-srcs)
chained (str/join sep (map #(str "t" %) (range arity)))]
(is (str/includes? js chained) src))))))

(deftest jit-var-mutation-visibility-test
;; BOTH modes cache var derefs keyed on sci.impl.vars/var-epoch, so
;; interp/jit agreement alone can't catch a missed bump — each case
Expand Down
Loading