;; Inline-cache tests — verify the per-call-site IC slot fires on hot ;; sends and is invalidated by class-table mutations. (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. Counters exist ── (st-test "stats has :hits" (has-key? (st-ic-stats) :hits) true) (st-test "stats has :misses" (has-key? (st-ic-stats) :misses) true) (st-test "stats has :gen" (has-key? (st-ic-stats) :gen) true) ;; ── 2. Repeated send to user method hits the IC ── (st-class-define! "Pinger" "Object" (list)) (st-class-add-method! "Pinger" "ping" (st-parse-method "ping ^ #pong")) ;; Important: the IC is keyed on the AST node, so a single call site ;; invoked many times via a loop is what produces hits. Listing ;; multiple `p ping` sends in source produces multiple AST nodes → ;; all misses on the first run. (st-ic-reset-stats!) (evp "| p | p := Pinger new. 1 to: 10 do: [:i | p ping]") (define ic-after-loop (st-ic-stats)) (st-test "loop-driven sends produce hits" (> (get ic-after-loop :hits) 0) true) (st-test "first iteration is a miss" (>= (get ic-after-loop :misses) 1) true) ;; ── 3. Different receiver class causes a miss ── (st-class-define! "Cooer" "Object" (list)) (st-class-add-method! "Cooer" "ping" (st-parse-method "ping ^ #coo")) (st-ic-reset-stats!) (evp "| p c | p := Pinger new. c := Cooer new. ^ {p ping. c ping. p ping. c ping}") ;; First p ping → miss. c ping with same call site → miss (class changed). ;; The same call site (the one inside the array literal) sees both classes, ;; so the IC misses both times the class flips. (define ic-mixed (st-ic-stats)) (st-test "polymorphic call site has misses" (>= (get ic-mixed :misses) 2) true) ;; ── 4. Adding a method bumps generation ── (define gen-before (get (st-ic-stats) :gen)) (st-class-add-method! "Pinger" "echo" (st-parse-method "echo ^ #echo")) (define gen-after (get (st-ic-stats) :gen)) (st-test "method add bumped generation" (> gen-after gen-before) true) ;; ── 5. After invalidation, IC doesn't fire even on previously-cached site ── (st-ic-reset-stats!) (evp "| p | p := Pinger new. ^ p ping") ;; warm (evp "| p | p := Pinger new. ^ p ping") ;; should hit (st-class-add-method! "Pinger" "ping" (st-parse-method "ping ^ #newPong")) (evp "| p | p := Pinger new. ^ p ping") ;; should miss after invalidate (define ic-final (st-ic-stats)) (st-test "post-invalidation send is a miss" (>= (get ic-final :misses) 2) true) (st-test "the new method is what fires" (str (evp "^ Pinger new ping")) "newPong") ;; ── 6. Default IC generation starts at >= 0 ── (st-test "generation is non-negative" (>= (get (st-ic-stats) :gen) 0) true) (list st-test-pass st-test-fail)