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
|
||||
;; 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
|
||||
;; 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
|
||||
|
|
|
|||
|
|
@ -14,14 +14,8 @@
|
|||
;; neg? throws on non-numbers via <, as Clojure's Numbers.isNeg does.
|
||||
(defn neg? [x] (< x 0))
|
||||
|
||||
;; even?/odd? accept any integral number (jolt has one number type, so 2.0
|
||||
;; counts) and throw otherwise — Clojure's IllegalArgumentException wording.
|
||||
(defn even? [n]
|
||||
(if (integer? n)
|
||||
(zero? (rem n 2))
|
||||
(throw (str "Argument must be an integer: " n))))
|
||||
|
||||
(defn odd? [n] (not (even? n)))
|
||||
;; even?/odd? stay in the seed: (filter even? ...) is idiomatic-hot and the
|
||||
;; overlay versions cost an extra call layer per element (seq-pipe bench 4x).
|
||||
|
||||
;; 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
|
||||
|
|
@ -569,6 +563,7 @@
|
|||
(defn record? [x] (some? (get x :jolt/deftype)))
|
||||
(defn uuid? [x] (= (get x :jolt/type) :jolt/uuid))
|
||||
(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
|
||||
;; protocol behavior).
|
||||
|
|
|
|||
|
|
@ -229,9 +229,12 @@
|
|||
(if (and (number? x) (= x x) (< (if (< x 0) (- x) x) math/inf) (= x (math/floor x))) x
|
||||
(error (string op " requires an integer"))))
|
||||
|
||||
(defn core-zero? [x] (= (need-num x "zero?") 0))
|
||||
(defn core-pos? [x] (> (need-num x "pos?") 0))
|
||||
# neg? / even? / odd? live in the Clojure collection tier (core/20-coll.clj).
|
||||
# zero? / pos? live in the syntax tier (core/00-syntax.clj) — empty? and the
|
||||
# analyzer use them; neg? lives in the collection tier (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
|
||||
# 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
|
||||
# call it, so it must exist before the kernel tier compiles.
|
||||
|
||||
(defn core-every? [pred coll]
|
||||
# Short-circuit on the first false — and pull lazily so an infinite seq with an
|
||||
# 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)))
|
||||
# every? lives in the syntax tier (core/00-syntax.clj) — the analyzer uses it;
|
||||
# the canonical seq/first/next walk short-circuits lazy seqs the same way.
|
||||
|
||||
# ============================================================
|
||||
# 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
|
||||
# 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.
|
||||
# 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)]
|
||||
(if (<= (length 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))))
|
||||
(tuple? coll) (if (<= (length 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) @[]
|
||||
(make-lazy-seq (fn [] (indexed-cells coll 1))))))
|
||||
|
||||
|
|
@ -2631,17 +2627,16 @@
|
|||
"seq?" core-seq?
|
||||
"coll?" core-coll?
|
||||
"identical?" core-identical?
|
||||
"zero?" core-zero?
|
||||
"pos?" core-pos?
|
||||
"integer?" core-integer?
|
||||
"list?" core-list?
|
||||
"every?" core-every?
|
||||
"+" core-+
|
||||
"-" core-sub
|
||||
"*" core-*
|
||||
"/" core-/
|
||||
"inc" core-inc
|
||||
"dec" core-dec
|
||||
"even?" core-even?
|
||||
"odd?" core-odd?
|
||||
"mod" core-mod
|
||||
"rem" core-rem
|
||||
"quot" core-quot
|
||||
|
|
@ -2847,7 +2842,6 @@
|
|||
"double" core-double
|
||||
"float" core-float
|
||||
"char" core-char
|
||||
"char?" core-char?
|
||||
# Hash
|
||||
"hash" core-hash
|
||||
"atom" core-atom
|
||||
|
|
|
|||
|
|
@ -43,7 +43,10 @@
|
|||
# Raised 4004 -> 4034 / clean 66 -> 67 porting partition-all + repeatedly to the
|
||||
# overlay, which required fixing two leniencies (a char is not callable; take
|
||||
# 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.
|
||||
(def baseline-clean-files 88)
|
||||
# Per-file wall-clock budget (seconds). Normal files finish in well under 1s, so
|
||||
|
|
|
|||
|
|
@ -205,3 +205,25 @@
|
|||
["string NOT" "false" "(ifn? \"s\")"]
|
||||
["number NOT" "false" "(ifn? 5)"]
|
||||
["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))"]
|
||||
["mapv scales (50k linear)" "50000" "(count (mapv inc (vec (range 50000))))"]
|
||||
["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