core: zero?/pos?/every? to 00-syntax, char? to the overlay; fix rest/next over sets and maps
Round 4 of the seed shrink. zero?, pos?, every? move to the syntax tier
(empty? and the analyzer use them — raw def+fn* per the file constraint);
char? joins the tagged-value predicates in 20-coll. coll? stays seed: host
set? doesn't cover sorted sets (filed jolt-dpn) and the tag check from the
overlay would hit the sorted-coll get trap. pos? guards number? explicitly —
the staged recompile emits bare > as the native janet op, which orders
strings (zero? gets the same guard; spec rows lock both plus neg?).
The canonical every? seq-walks its coll, which exposed that rest/next over
sets, phms, struct maps and sorted colls fell into core-rest's indexed
fall-through and walked the wrapper table's INTERNAL fields — (next #{1 2})
was (nil nil), (clojure.set/subset?) broke. core-rest now seqs those
representations (branches placed AFTER the hot vector/lazy paths; the first
ordering cost seq-pipe 4x). Suite rises 4700 -> 4703; baseline 4660 -> 4695.
even?/odd? are back in the seed after the bench A/B: (filter even? ...) pays
an extra call layer per element through the overlay (seq-pipe 262 -> 1100ms).
They join the perf-wall list with the lazy hot fns.
This commit is contained in:
parent
407d656240
commit
a1a9fd9949
6 changed files with 97 additions and 37 deletions
|
|
@ -14,6 +14,35 @@
|
||||||
;; (backend/recompile-defns!) once the analyzer is alive — same lifecycle as
|
;; (backend/recompile-defns!) once the analyzer is alive — same lifecycle as
|
||||||
;; the defmacro expanders.
|
;; the defmacro expanders.
|
||||||
|
|
||||||
|
;; zero?/pos?/every? live HERE (not 20-coll): empty? below calls zero?, and
|
||||||
|
;; the self-hosted analyzer — compiled right after the kernel tier — uses all
|
||||||
|
;; three. Raw def+fn* per the file constraint. zero? checks number? itself
|
||||||
|
;; (= doesn't throw); pos? inherits throwing from >.
|
||||||
|
(def zero?
|
||||||
|
(fn* zero? [x]
|
||||||
|
(if (number? x)
|
||||||
|
(= x 0)
|
||||||
|
(throw (str "zero? requires a number, got: " x)))))
|
||||||
|
|
||||||
|
;; pos? checks number? explicitly: this tier is recompiled by the staged pass,
|
||||||
|
;; where a bare (> x 0) emits the native janet op that happily orders strings
|
||||||
|
;; (the documented native-ops relaxation) — the guard keeps Clojure's throw.
|
||||||
|
(def pos?
|
||||||
|
(fn* pos? [x]
|
||||||
|
(if (number? x)
|
||||||
|
(> x 0)
|
||||||
|
(throw (str "pos? requires a number, got: " x)))))
|
||||||
|
|
||||||
|
;; Canonical every?: short-circuits on the first falsey result, so infinite
|
||||||
|
;; seqs with an early counterexample terminate.
|
||||||
|
(def every?
|
||||||
|
(fn* every? [pred coll]
|
||||||
|
(if (nil? (seq coll))
|
||||||
|
true
|
||||||
|
(if (pred (first coll))
|
||||||
|
(recur pred (next coll))
|
||||||
|
false))))
|
||||||
|
|
||||||
;; empty?/keys/vals live HERE (not 20-coll) because the expanders below call
|
;; empty?/keys/vals live HERE (not 20-coll) because the expanders below call
|
||||||
;; them at expansion time, which first happens during the kernel-tier compile.
|
;; them at expansion time, which first happens during the kernel-tier compile.
|
||||||
;; empty? keeps O(1) dispatch for counted things; only the lazy/list fallback
|
;; empty? keeps O(1) dispatch for counted things; only the lazy/list fallback
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,8 @@
|
||||||
;; neg? throws on non-numbers via <, as Clojure's Numbers.isNeg does.
|
;; neg? throws on non-numbers via <, as Clojure's Numbers.isNeg does.
|
||||||
(defn neg? [x] (< x 0))
|
(defn neg? [x] (< x 0))
|
||||||
|
|
||||||
;; even?/odd? accept any integral number (jolt has one number type, so 2.0
|
;; even?/odd? stay in the seed: (filter even? ...) is idiomatic-hot and the
|
||||||
;; counts) and throw otherwise — Clojure's IllegalArgumentException wording.
|
;; overlay versions cost an extra call layer per element (seq-pipe bench 4x).
|
||||||
(defn even? [n]
|
|
||||||
(if (integer? n)
|
|
||||||
(zero? (rem n 2))
|
|
||||||
(throw (str "Argument must be an integer: " n))))
|
|
||||||
|
|
||||||
(defn odd? [n] (not (even? n)))
|
|
||||||
|
|
||||||
;; Base is (hash-map), not the {} literal: a literal map is a struct that doesn't
|
;; Base is (hash-map), not the {} literal: a literal map is a struct that doesn't
|
||||||
;; canonicalize collection keys across representations (a {:a 1} literal vs
|
;; canonicalize collection keys across representations (a {:a 1} literal vs
|
||||||
|
|
@ -569,6 +563,7 @@
|
||||||
(defn record? [x] (some? (get x :jolt/deftype)))
|
(defn record? [x] (some? (get x :jolt/deftype)))
|
||||||
(defn uuid? [x] (= (get x :jolt/type) :jolt/uuid))
|
(defn uuid? [x] (= (get x :jolt/type) :jolt/uuid))
|
||||||
(defn inst? [x] (= (get x :jolt/type) :jolt/inst))
|
(defn inst? [x] (= (get x :jolt/type) :jolt/inst))
|
||||||
|
(defn char? [x] (= (get x :jolt/type) :jolt/char))
|
||||||
|
|
||||||
;; inst-ms: epoch milliseconds of an instant; throws on a non-inst (Clojure
|
;; inst-ms: epoch milliseconds of an instant; throws on a non-inst (Clojure
|
||||||
;; protocol behavior).
|
;; protocol behavior).
|
||||||
|
|
|
||||||
|
|
@ -229,9 +229,12 @@
|
||||||
(if (and (number? x) (= x x) (< (if (< x 0) (- x) x) math/inf) (= x (math/floor x))) x
|
(if (and (number? x) (= x x) (< (if (< x 0) (- x) x) math/inf) (= x (math/floor x))) x
|
||||||
(error (string op " requires an integer"))))
|
(error (string op " requires an integer"))))
|
||||||
|
|
||||||
(defn core-zero? [x] (= (need-num x "zero?") 0))
|
# zero? / pos? live in the syntax tier (core/00-syntax.clj) — empty? and the
|
||||||
(defn core-pos? [x] (> (need-num x "pos?") 0))
|
# analyzer use them; neg? lives in the collection tier (20-coll.clj).
|
||||||
# neg? / even? / odd? live in the Clojure collection tier (core/20-coll.clj).
|
# even?/odd? are PERF-WALL residents: (filter even? ...) is idiomatic and the
|
||||||
|
# overlay versions cost an extra call layer per element (seq-pipe bench 4x).
|
||||||
|
(defn core-even? [n] (= 0 (% (need-int n "even?") 2)))
|
||||||
|
(defn core-odd? [n] (not= 0 (% (need-int n "odd?") 2)))
|
||||||
|
|
||||||
# Finite integral number: NaN and the infinities are NOT integers (floor of
|
# Finite integral number: NaN and the infinities are NOT integers (floor of
|
||||||
# inf is inf, so the naive floor check wrongly accepted them).
|
# inf is inf, so the naive floor check wrongly accepted them).
|
||||||
|
|
@ -244,27 +247,8 @@
|
||||||
# empty? now lives in the syntax tier (core/00-syntax.clj): the expanders
|
# empty? now lives in the syntax tier (core/00-syntax.clj): the expanders
|
||||||
# call it, so it must exist before the kernel tier compiles.
|
# call it, so it must exist before the kernel tier compiles.
|
||||||
|
|
||||||
(defn core-every? [pred coll]
|
# every? lives in the syntax tier (core/00-syntax.clj) — the analyzer uses it;
|
||||||
# Short-circuit on the first false — and pull lazily so an infinite seq with an
|
# the canonical seq/first/next walk short-circuits lazy seqs the same way.
|
||||||
# early false (e.g. (every? pos? (range))) returns rather than hanging. Walks
|
|
||||||
# cells via realize-ls directly (core-first/lazy-from are defined later).
|
|
||||||
(if (lazy-seq? coll)
|
|
||||||
(do
|
|
||||||
(var cur coll) (var result true) (var go true)
|
|
||||||
(while (and result go)
|
|
||||||
(let [cell (realize-ls cur)]
|
|
||||||
(if (or (nil? cell) (= :jolt/pending cell) (= 0 (length cell)))
|
|
||||||
(set go false)
|
|
||||||
(if (pred (in cell 0))
|
|
||||||
(let [rt (in cell 1)]
|
|
||||||
(if (nil? rt) (set go false) (set cur (ls-rest-cached cur rt))))
|
|
||||||
(set result false)))))
|
|
||||||
result)
|
|
||||||
(do
|
|
||||||
(var result true)
|
|
||||||
(each x (realize-for-iteration coll)
|
|
||||||
(if (not (pred x)) (do (set result false) (break))))
|
|
||||||
result)))
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Math — Clojure semantics (variadic, / with one arg = reciprocal)
|
# Math — Clojure semantics (variadic, / with one arg = reciprocal)
|
||||||
|
|
@ -711,6 +695,8 @@
|
||||||
# Indexed collections: an O(1) lazy view from index 1 (Clojure: rest of a
|
# Indexed collections: an O(1) lazy view from index 1 (Clojure: rest of a
|
||||||
# vector is a seq, not a vector). Slicing per step made first/rest loops
|
# vector is a seq, not a vector). Slicing per step made first/rest loops
|
||||||
# over concrete collections O(n^2) — a 20k rest-loop took two seconds.
|
# over concrete collections O(n^2) — a 20k rest-loop took two seconds.
|
||||||
|
# These stay ABOVE the set/map branches: rest-of-vector is every seq loop's
|
||||||
|
# hot path and must not pay the wrapper-tag checks.
|
||||||
(pvec? coll) (let [a (pv->array coll)]
|
(pvec? coll) (let [a (pv->array coll)]
|
||||||
(if (<= (length a) 1) @[]
|
(if (<= (length a) 1) @[]
|
||||||
(make-lazy-seq (fn [] (indexed-cells a 1)))))
|
(make-lazy-seq (fn [] (indexed-cells a 1)))))
|
||||||
|
|
@ -718,6 +704,16 @@
|
||||||
(string? coll) (tuple ;(map make-char (string/bytes (string/slice coll 1))))
|
(string? coll) (tuple ;(map make-char (string/bytes (string/slice coll 1))))
|
||||||
(tuple? coll) (if (<= (length coll) 1) @[]
|
(tuple? coll) (if (<= (length coll) 1) @[]
|
||||||
(make-lazy-seq (fn [] (indexed-cells coll 1))))
|
(make-lazy-seq (fn [] (indexed-cells coll 1))))
|
||||||
|
# Sets, maps and sorted colls rest via their seq. Without these branches
|
||||||
|
# they fell into the indexed fall-through, which walked the wrapper table's
|
||||||
|
# INTERNAL fields — (next #{1 2}) was (nil nil) until the canonical every?
|
||||||
|
# started seq-walking sets (seed-shrink round 4).
|
||||||
|
(set? coll) (if (= 0 (coll :cnt)) @[] (core-rest (phs-seq coll)))
|
||||||
|
(phm? coll) (if (= 0 (coll :cnt)) @[] (core-rest (tuple ;(phm-entries coll))))
|
||||||
|
(core-sorted? coll) (core-rest ((sorted-op coll :seq) coll))
|
||||||
|
# plain struct maps (untagged literals) rest via entries too
|
||||||
|
(and (struct? coll) (nil? (get coll :jolt/type)))
|
||||||
|
(core-rest (tuple ;(map-entries-of coll)))
|
||||||
(if (<= (length coll) 1) @[]
|
(if (<= (length coll) 1) @[]
|
||||||
(make-lazy-seq (fn [] (indexed-cells coll 1))))))
|
(make-lazy-seq (fn [] (indexed-cells coll 1))))))
|
||||||
|
|
||||||
|
|
@ -2631,17 +2627,16 @@
|
||||||
"seq?" core-seq?
|
"seq?" core-seq?
|
||||||
"coll?" core-coll?
|
"coll?" core-coll?
|
||||||
"identical?" core-identical?
|
"identical?" core-identical?
|
||||||
"zero?" core-zero?
|
|
||||||
"pos?" core-pos?
|
|
||||||
"integer?" core-integer?
|
"integer?" core-integer?
|
||||||
"list?" core-list?
|
"list?" core-list?
|
||||||
"every?" core-every?
|
|
||||||
"+" core-+
|
"+" core-+
|
||||||
"-" core-sub
|
"-" core-sub
|
||||||
"*" core-*
|
"*" core-*
|
||||||
"/" core-/
|
"/" core-/
|
||||||
"inc" core-inc
|
"inc" core-inc
|
||||||
"dec" core-dec
|
"dec" core-dec
|
||||||
|
"even?" core-even?
|
||||||
|
"odd?" core-odd?
|
||||||
"mod" core-mod
|
"mod" core-mod
|
||||||
"rem" core-rem
|
"rem" core-rem
|
||||||
"quot" core-quot
|
"quot" core-quot
|
||||||
|
|
@ -2847,7 +2842,6 @@
|
||||||
"double" core-double
|
"double" core-double
|
||||||
"float" core-float
|
"float" core-float
|
||||||
"char" core-char
|
"char" core-char
|
||||||
"char?" core-char?
|
|
||||||
# Hash
|
# Hash
|
||||||
"hash" core-hash
|
"hash" core-hash
|
||||||
"atom" core-atom
|
"atom" core-atom
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,10 @@
|
||||||
# Raised 4004 -> 4034 / clean 66 -> 67 porting partition-all + repeatedly to the
|
# Raised 4004 -> 4034 / clean 66 -> 67 porting partition-all + repeatedly to the
|
||||||
# overlay, which required fixing two leniencies (a char is not callable; take
|
# overlay, which required fixing two leniencies (a char is not callable; take
|
||||||
# validates its count) — correct beyond those fns, so the suite rose broadly.
|
# validates its count) — correct beyond those fns, so the suite rose broadly.
|
||||||
(def baseline-pass 4660)
|
# Raised 4660 -> 4695 (observed 4703) after the seed-shrink rounds: rest/next
|
||||||
|
# over sets/maps/sorted colls fixed (they walked the wrapper table's internal
|
||||||
|
# fields), canonical halt-when/==/memfn. Margin covers rand-based suite jitter.
|
||||||
|
(def baseline-pass 4695)
|
||||||
# A file is "clean" when it ran with zero failures AND zero errors.
|
# A file is "clean" when it ran with zero failures AND zero errors.
|
||||||
(def baseline-clean-files 88)
|
(def baseline-clean-files 88)
|
||||||
# Per-file wall-clock budget (seconds). Normal files finish in well under 1s, so
|
# Per-file wall-clock budget (seconds). Normal files finish in well under 1s, so
|
||||||
|
|
|
||||||
|
|
@ -205,3 +205,25 @@
|
||||||
["string NOT" "false" "(ifn? \"s\")"]
|
["string NOT" "false" "(ifn? \"s\")"]
|
||||||
["number NOT" "false" "(ifn? 5)"]
|
["number NOT" "false" "(ifn? 5)"]
|
||||||
["nil NOT" "false" "(ifn? nil)"])
|
["nil NOT" "false" "(ifn? nil)"])
|
||||||
|
|
||||||
|
# zero?/pos? throw on non-numbers (Numbers.isZero/isPos), as in Clojure;
|
||||||
|
# every? short-circuits on the first falsey pred result, so an infinite seq
|
||||||
|
# with an early counterexample terminates. char? is the tagged-value check.
|
||||||
|
(defspec "predicates / numeric guards & every? (overlay moves)"
|
||||||
|
["zero? zero" "true" "(zero? 0)"]
|
||||||
|
["zero? nonzero" "false" "(zero? 3)"]
|
||||||
|
["zero? throws" :throws "(zero? :a)"]
|
||||||
|
["zero? throws on nil" :throws "(zero? nil)"]
|
||||||
|
["pos? positive" "true" "(pos? 2)"]
|
||||||
|
["pos? zero" "false" "(pos? 0)"]
|
||||||
|
["pos? throws" :throws "(pos? \"x\")"]
|
||||||
|
["neg? throws" :throws "(neg? \"x\")"]
|
||||||
|
["every? all pass" "true" "(every? odd? [1 3 5])"]
|
||||||
|
["every? one fails" "false" "(every? odd? [1 2 5])"]
|
||||||
|
["every? vacuous" "true" "(every? odd? [])"]
|
||||||
|
["every? nil coll" "true" "(every? odd? nil)"]
|
||||||
|
["every? infinite short-circuit" "false" "(every? pos? (range))"]
|
||||||
|
["char? char" "true" "(char? \\x)"]
|
||||||
|
["char? string" "false" "(char? \"x\")"]
|
||||||
|
["char? number" "false" "(char? 97)"]
|
||||||
|
["char? nil" "false" "(char? nil)"])
|
||||||
|
|
|
||||||
|
|
@ -290,3 +290,20 @@
|
||||||
"(loop [xs (seq (vec (range 20000))) n 0] (if xs (recur (next xs) (inc n)) n))"]
|
"(loop [xs (seq (vec (range 20000))) n 0] (if xs (recur (next xs) (inc n)) n))"]
|
||||||
["mapv scales (50k linear)" "50000" "(count (mapv inc (vec (range 50000))))"]
|
["mapv scales (50k linear)" "50000" "(count (mapv inc (vec (range 50000))))"]
|
||||||
["nested walk" "[2 3]" "(vec (rest (mapv inc [0 1 2])))"])
|
["nested walk" "[2 3]" "(vec (rest (mapv inc [0 1 2])))"])
|
||||||
|
|
||||||
|
# rest/next over sets, maps, and sorted colls used to fall into the indexed
|
||||||
|
# fall-through and walk the wrapper table's internal fields ((next #{1 2})
|
||||||
|
# was (nil nil)) — exposed when the canonical every? started seq-walking.
|
||||||
|
(defspec "sequences / rest & next over set-like colls"
|
||||||
|
["next of set" "true" "(let [n (next #{1 2})] (and (= 1 (count n)) (contains? #{1 2} (first n))))"]
|
||||||
|
["rest of set count" "1" "(count (rest #{1 2}))"]
|
||||||
|
["next of singleton set" "nil" "(next #{1})"]
|
||||||
|
["rest of empty set" "0" "(count (rest #{}))"]
|
||||||
|
["next of map" "true" "(let [n (next {:a 1 :b 2})] (and (= 1 (count n)) (map-entry? (first n))))"]
|
||||||
|
["next of singleton map" "nil" "(next {:a 1})"]
|
||||||
|
["rest of sorted-set" "(quote (2 3))" "(rest (sorted-set 3 1 2))"]
|
||||||
|
["next of sorted-map" "(quote ([2 :b]))" "(next (sorted-map 1 :a 2 :b))"]
|
||||||
|
["every? over set" "true" "(every? pos? #{1 2 3})"]
|
||||||
|
["every? over set false" "false" "(every? odd? #{1 2})"]
|
||||||
|
["every? over sorted-set" "true" "(every? pos? (sorted-set 1 2 3))"]
|
||||||
|
["every? over map entries" "true" "(every? map-entry? (seq {:a 1 :b 2}))"])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue