The trio the author decided on 2026-09-20 is now all built: def is CL's defparameter — its initialiser runs on every daemon re-run, unguarded, so an edited initialiser repaints the same storage on C-c C-c plus re-run — defonce (Clojure's name for CL's defvar, per the author) initialises once behind the .init~once. flag, and defconst stays the image. One parse arm reads both forms; the difference is Ast.reinit, carried to Tast.global's grerun. Emit.startup_plan gives a def no guard flag, and Check.check_global lifts every def initialiser — zero and literal included — into global/<n>, so the host's startup reaches it through the function cell and a re-evaluated def swaps it (Session's def_inits; Emit.redefinition declares the cell for a non-sibling target). The old defvar spelling is refused with the rename and both compiling spellings, and every program, test, doc and editor list is swept — except sand.flan, the author's live WIP, whose seven defvar lines are flagged in FIX.org and keep its three dependent tests red on this branch.
110 lines
5.6 KiB
Plaintext
110 lines
5.6 KiB
Plaintext
;;;; Implicit widening, and the only thing worth pinning about it: the bits.
|
|
;;;; FIX.org 2026-09-20, "Implicit widening".
|
|
;;;;
|
|
;;;; A widening conversion is admitted exactly when it cannot change the
|
|
;;;; number, so every row here has an answer that is also the answer the source
|
|
;;;; type had. Which means the test is entirely about the *emitted* cast being
|
|
;;;; the right one: a sign-extension where the source is signed, a
|
|
;;;; zero-extension where it is not, and the float conversions picking sitofp
|
|
;;;; against uitofp by the same rule. Each of those is a separate instruction
|
|
;;;; on both backends and choosing the wrong one gives a wrong number rather
|
|
;;;; than a wrong type, which no type test would catch.
|
|
;;;;
|
|
;;;; The rows are picked so that a mistake is visible in the printed value:
|
|
;;;;
|
|
;;;; -5 i8 to i64 sext; a zext prints 251
|
|
;;;; -1 i32 to i64 sext; a zext prints 4294967295
|
|
;;;; 255 u8 to i16 zext; a sext prints -1
|
|
;;;; 4000000000 u32 zext to i64; a sext prints -294967296
|
|
;;;; 4294967295 u32 to f64, exact; a signed conversion prints -1
|
|
;;;; -2000000000 i32 to f64; a uitofp prints 2294967296
|
|
;;;;
|
|
;;;; Everything goes through a global rather than a literal, because a literal
|
|
;;;; is built at the wanted width by the literal rule and would never reach a
|
|
;;;; cast at all.
|
|
|
|
(defonce w-i8neg i8 -5)
|
|
(defonce w-i8pos i8 127)
|
|
(defonce w-i16neg i16 -300)
|
|
(defonce w-i32neg i32 -2000000000)
|
|
(defonce w-i32one i32 1)
|
|
(defonce w-i32all i32 -1)
|
|
(defonce w-u8max u8 255)
|
|
(defonce w-u16max u16 65535)
|
|
(defonce w-u32big u32 4000000000)
|
|
(defonce w-u32max u32 4294967295)
|
|
(defonce w-i64big i64 5000000000)
|
|
(defonce w-f32half f32 0.5)
|
|
|
|
;; Widening at a parameter. Each of these is a plain typed function and the
|
|
;; call sites below hand it a narrower type with no cast written anywhere.
|
|
(defn w-take-i64 [x i64] i64 x)
|
|
(defn w-take-i16 [x i16] i16 x)
|
|
(defn w-take-u64 [x u64] u64 x)
|
|
(defn w-take-f64 [x f64] f64 x)
|
|
(defn w-take-f32 [x f32] f32 x)
|
|
|
|
;; Widening at a return position: the body is an i32 and the signature is i64.
|
|
(defn w-ret-widened [] i64 w-i32neg)
|
|
|
|
;; Widening in a binary operator, both orders. The first is the direction
|
|
;; [expect] already served; the second is the one the join rule added.
|
|
(defn w-add-wide-first [] i64 (+ w-i64big w-i32one))
|
|
(defn w-add-narrow-first [] i64 (+ w-i32one w-i64big))
|
|
|
|
(defn main [args [string]] i32
|
|
;; ── integer to integer ──────────────────────────────────────────
|
|
(println (w-take-i64 w-i8neg)) ;; -5
|
|
(println (w-take-i64 w-i8pos)) ;; 127
|
|
(println (w-take-i64 w-i16neg)) ;; -300
|
|
(println (w-take-i64 w-i32all)) ;; -1
|
|
(println (w-take-i64 w-i32neg)) ;; -2000000000
|
|
(println (w-take-i16 w-u8max)) ;; 255
|
|
(println (w-take-i64 w-u8max)) ;; 255
|
|
(println (w-take-i64 w-u16max)) ;; 65535
|
|
(println (w-take-i64 w-u32big)) ;; 4000000000
|
|
(println (w-take-i64 w-u32max)) ;; 4294967295
|
|
(println (w-take-u64 w-u32big)) ;; 4000000000
|
|
(println (w-take-u64 w-u8max)) ;; 255
|
|
|
|
;; ── a widened return ────────────────────────────────────────────
|
|
(println (w-ret-widened)) ;; -2000000000
|
|
|
|
;; ── integer to float, exact only ────────────────────────────────
|
|
(println (w-take-f64 w-i32neg)) ;; -2000000000.0
|
|
(println (w-take-f64 w-u32max)) ;; 4294967295.0
|
|
(println (w-take-f64 w-i8neg)) ;; -5.0
|
|
(println (w-take-f32 w-i16neg)) ;; -300.0
|
|
(println (w-take-f32 w-u16max)) ;; 65535.0
|
|
|
|
;; The printer answers %g, which rounds an f64 long before the bits it is
|
|
;; carrying run out, so the exactness the int-to-float boundary is chosen for
|
|
;; is asserted by subtraction rather than by reading the digits. Each of
|
|
;; these is the difference between the widened value and the number it is
|
|
;; supposed to be, and a conversion that lost anything answers something
|
|
;; other than the last unit.
|
|
(println (- (w-take-f64 w-u32max) 4294967294.0)) ;; 1
|
|
(println (- (w-take-f64 w-i32neg) -1999999999.0)) ;; -1
|
|
(println (- (w-take-f32 w-u16max) 65534.0)) ;; 1
|
|
|
|
;; ── float to float ──────────────────────────────────────────────
|
|
(println (w-take-f64 w-f32half)) ;; 0.5
|
|
|
|
;; ── the binary join, both operand orders ────────────────────────
|
|
(println (w-add-wide-first)) ;; 5000000001
|
|
(println (w-add-narrow-first)) ;; 5000000001
|
|
;; The narrower operand is the first one, and the sum is an i64 even though
|
|
;; nothing on this line is annotated.
|
|
(println (+ w-i32neg w-i64big)) ;; 3000000000
|
|
;; A comparison joins the same way, and the widened -1 must still be -1.
|
|
(println (< w-i32all w-i64big)) ;; true
|
|
;; min and max over two widths answer at the wider one.
|
|
(println (max w-i8neg w-i16neg)) ;; -5
|
|
(println (min w-i8neg w-i32neg)) ;; -2000000000
|
|
;; A count narrower than the value widens to it; the value's width decides.
|
|
(println (<< w-i64big w-i8pos)) ;; 0 -- masked to 127 mod 64 = 63
|
|
;; An expectation reaches the operands, so this adds at i64 rather than
|
|
;; wrapping at i32 and widening the sum afterwards.
|
|
(println (w-take-i64 (+ w-i32neg w-i32neg))) ;; -4000000000
|
|
0)
|