;; Block-intrinsifier tests. ;; ;; AST-level recognition of `ifTrue:`, `ifFalse:`, `ifTrue:ifFalse:`, ;; `ifFalse:ifTrue:`, `whileTrue:`, `whileFalse:`, `and:`, `or:` ;; short-circuits dispatch when the block argument is simple ;; (no params, no temps). (set! st-test-pass 0) (set! st-test-fail 0) (set! st-test-fails (list)) (st-bootstrap-classes!) (define ev (fn (src) (smalltalk-eval src))) (define evp (fn (src) (smalltalk-eval-program src))) ;; ── 1. Each intrinsic increments the hit counter ── (st-intrinsic-reset!) (ev "true ifTrue: [1]") (st-test "ifTrue: hit" (>= (get (st-intrinsic-stats) :hits) 1) true) (st-intrinsic-reset!) (ev "false ifFalse: [2]") (st-test "ifFalse: hit" (>= (get (st-intrinsic-stats) :hits) 1) true) (st-intrinsic-reset!) (ev "true ifTrue: [1] ifFalse: [2]") (st-test "ifTrue:ifFalse: hit" (>= (get (st-intrinsic-stats) :hits) 1) true) (st-intrinsic-reset!) (ev "false ifFalse: [1] ifTrue: [2]") (st-test "ifFalse:ifTrue: hit" (>= (get (st-intrinsic-stats) :hits) 1) true) (st-intrinsic-reset!) (ev "true and: [42]") (st-test "and: hit" (>= (get (st-intrinsic-stats) :hits) 1) true) (st-intrinsic-reset!) (ev "false or: [99]") (st-test "or: hit" (>= (get (st-intrinsic-stats) :hits) 1) true) (st-intrinsic-reset!) (evp "| n | n := 5. [n > 0] whileTrue: [n := n - 1]. ^ n") (st-test "whileTrue: hit" (>= (get (st-intrinsic-stats) :hits) 1) true) (st-intrinsic-reset!) (evp "| n | n := 0. [n >= 3] whileFalse: [n := n + 1]. ^ n") (st-test "whileFalse: hit" (>= (get (st-intrinsic-stats) :hits) 1) true) ;; ── 2. Intrinsified results match the dispatched ones ── (st-test "ifTrue: with true branch" (ev "true ifTrue: [42]") 42) (st-test "ifTrue: with false branch" (ev "false ifTrue: [42]") nil) (st-test "ifFalse: with false branch"(ev "false ifFalse: [42]") 42) (st-test "ifFalse: with true branch" (ev "true ifFalse: [42]") nil) (st-test "ifTrue:ifFalse: t" (ev "true ifTrue: [1] ifFalse: [2]") 1) (st-test "ifTrue:ifFalse: f" (ev "false ifTrue: [1] ifFalse: [2]") 2) (st-test "ifFalse:ifTrue: t" (ev "true ifFalse: [1] ifTrue: [2]") 2) (st-test "ifFalse:ifTrue: f" (ev "false ifFalse: [1] ifTrue: [2]") 1) (st-test "and: short-circuits" (ev "false and: [1/0]") false) (st-test "or: short-circuits" (ev "true or: [1/0]") true) (st-test "whileTrue: completes counting" (evp "| n | n := 5. [n > 0] whileTrue: [n := n - 1]. ^ n") 0) (st-test "whileFalse: completes counting" (evp "| n | n := 0. [n >= 3] whileFalse: [n := n + 1]. ^ n") 3) ;; ── 3. Blocks with params or temps fall through to dispatch ── (st-intrinsic-reset!) (ev "true ifTrue: [| t | t := 1. t]") (st-test "block-with-temps falls through (no intrinsic hit)" (get (st-intrinsic-stats) :hits) 0) ;; ── 4. ^ inside an intrinsified block still escapes the method ── (st-class-define! "EarlyOut" "Object" (list)) (st-class-add-method! "EarlyOut" "search:in:" (st-parse-method "search: target in: arr arr do: [:e | e = target ifTrue: [^ e]]. ^ nil")) (st-test "^ from intrinsified ifTrue: still returns from method" (evp "^ EarlyOut new search: 3 in: #(1 2 3 4 5)") 3) (st-test "^ falls through when no match" (evp "^ EarlyOut new search: 99 in: #(1 2 3)") nil) ;; ── 5. Intrinsics don't break under repeated invocation ── (st-intrinsic-reset!) (evp "| n | n := 0. 1 to: 100 do: [:i | n := n + 1]. ^ n") (st-test "intrinsified to:do: ran (counter reflects ifTrue:s inside)" (>= (get (st-intrinsic-stats) :hits) 0) true) (list st-test-pass st-test-fail)