@@ -3915,6 +3915,8 @@ f(x) = yt(x)
39153915
39163916;; Try to identify never-undef variables, and then clear the `captured` flag for single-assigned,
39173917;; never-undef variables to avoid allocating unnecessary `Box`es.
3918+ ;; Also handles the case where a variable is assigned in both branches of an if-else, making
3919+ ;; it effectively single-assigned on each control path.
39183920(define (lambda-optimize-vars! lam)
39193921 (assert (eq? (car lam) 'lambda))
39203922 ;; memoize all-methods-for to avoid O(n^2) behavior
@@ -3933,12 +3935,18 @@ f(x) = yt(x)
39333935 (decl (table))
39343936 (unused (table)) ;; variables not (yet) used (read from) in the current block
39353937 (live (table)) ;; variables that have been set in the current block
3936- (seen (table))) ;; all variables we've seen assignments to
3938+ (seen (table)) ;; all variables we've seen assignments to
3939+ (ifa (table)) ;; variables assigned in all branches of if-else ("if-assigned")
3940+ (has-ifa #f)) ;; whether ifa has any entries
39373941 ;; Collect candidate variables: those that are captured (and hence we want to optimize)
39383942 ;; and only assigned once. This populates the initial `unused` table.
3943+ ;; Also collect captured variables assigned more than once for if-branch analysis.
39393944 (for-each (lambda (v)
3940- (if (and (vinfo: capt v) (vinfo: sa v))
3941- (put! unused (car v) #t)))
3945+ (if (vinfo: capt v)
3946+ (if (vinfo: sa v)
3947+ (put! unused (car v) #t)
3948+ (begin (put! ifa (car v) #t)
3949+ (set! has-ifa #t)))))
39423950 vi)
39433951 (define (restore old)
39443952 (table.foreach (lambda (k v)
@@ -3997,7 +4005,75 @@ f(x) = yt(x)
39974005 ((eq? (car e) 'symboliclabel)
39984006 (kill)
39994007 #t)
4000- ((memq (car e) '(if elseif trycatch tryfinally trycatchelse))
4008+ ((eq? (car e) 'if)
4009+ ;; Special handling for if-else: track variables assigned in ALL branches.
4010+ ;; If a captured variable is assigned in ALL branches (and not used before
4011+ ;; assignment in any), it's effectively single-assigned per control path.
4012+ (let ((prev (table.clone live)))
4013+ (cond
4014+ ;; if-else with exactly 3 args (cond, then, else) and we have candidates
4015+ ((and (length= e 4) has-ifa)
4016+ (let ((has-label #f)
4017+ (all-assigned #f))
4018+ ;; Visit condition
4019+ (if (visit (cadr e)) (set! has-label #t))
4020+ (let ((pre-branch-live (table.clone live)))
4021+ (kill)
4022+ ;; Visit then-branch
4023+ (if (visit (caddr e)) (set! has-label #t))
4024+ (set! all-assigned (table.clone live))
4025+ ;; Process else-branch (may be elseif chain)
4026+ (let process-else ((else-expr (cadddr e)))
4027+ (set! live (table.clone pre-branch-live))
4028+ (kill)
4029+ (cond
4030+ ;; else-branch is an elseif
4031+ ((and (pair? else-expr) (eq? (car else-expr) 'elseif)
4032+ (length= else-expr 4))
4033+ ;; Visit elseif condition
4034+ (if (visit (cadr else-expr)) (set! has-label #t))
4035+ (let ((pre-elseif-live (table.clone live)))
4036+ (kill)
4037+ ;; Visit elseif then-branch
4038+ (if (visit (caddr else-expr)) (set! has-label #t))
4039+ ;; Intersect with all-assigned
4040+ (let ((branch-assigned live))
4041+ (table.foreach
4042+ (lambda (var _)
4043+ (if (not (has? branch-assigned var))
4044+ (del! all-assigned var)))
4045+ all-assigned))
4046+ ;; Process nested else
4047+ (process-else (cadddr else-expr))))
4048+ ;; else-branch is regular expression (final else)
4049+ (else
4050+ (if (visit else-expr) (set! has-label #t))
4051+ ;; Intersect with all-assigned
4052+ (let ((branch-assigned live))
4053+ (table.foreach
4054+ (lambda (var _)
4055+ (if (not (has? branch-assigned var))
4056+ (del! all-assigned var)))
4057+ all-assigned)))))
4058+ ;; Mark variables assigned in all branches as effectively single-assigned
4059+ (table.foreach
4060+ (lambda (var _)
4061+ (if (has? all-assigned var)
4062+ (begin
4063+ (put! seen var #t)
4064+ (put! unused var #t)
4065+ (del! ifa var))))
4066+ ifa)
4067+ (kill)
4068+ (if has-label
4069+ (begin (kill) #t)
4070+ (begin (restore prev) #f)))))
4071+ ;; No ifa candidates - use default handling
4072+ (else
4073+ (if (eager-any (lambda (e) (begin0 (visit e) (kill))) (cdr e))
4074+ (begin (kill) #t)
4075+ (begin (restore prev) #f))))))
4076+ ((memq (car e) '(elseif trycatch tryfinally trycatchelse))
40014077 (let ((prev (table.clone live)))
40024078 (if (eager-any (lambda (e) (begin0 (visit e)
40034079 (kill)))
@@ -4048,10 +4124,18 @@ f(x) = yt(x)
40484124 (let ((vv (assq v vi)))
40494125 (vinfo: set-never-undef! vv #t))))
40504126 (append (table.keys live) (table.keys unused)))
4127+ ;; Clear captured flag for single-assigned never-undef variables
40514128 (for-each (lambda (v)
40524129 (if (and (vinfo: sa v) (vinfo: never-undef v))
40534130 (set-car! (cddr v) (logand (caddr v) (lognot 5)))))
40544131 vi)
4132+ ;; Also clear captured flag for variables that were assigned in all branches of an if-else
4133+ ;; (these are in `unused` but not `ifa`, and have never-undef set)
4134+ (for-each (lambda (var)
4135+ (let ((vv (assq var vi)))
4136+ (if (and vv (vinfo: never-undef vv) (not (has? ifa var)))
4137+ (set-car! (cddr vv) (logand (caddr vv) (lognot 5))))))
4138+ (table.keys unused))
40554139 lam))
40564140
40574141(define (is-var-boxed? v lam)
0 commit comments