@@ -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)
@@ -3964,7 +3972,10 @@ f(x) = yt(x)
39643972 ;; it from being removed from `unused`.
39653973 (begin (put! live var #t)
39663974 (put! seen var #t)
3967- (del! unused var))))
3975+ (del! unused var)))
3976+ ;; Also track assignments to ifa candidates (captured non-sa variables)
3977+ (if (has? ifa var)
3978+ (put! live var #t)))
39683979 (define (declare! var)
39693980 (if (has? unused var)
39703981 (put! decl var #t)))
@@ -3997,7 +4008,74 @@ f(x) = yt(x)
39974008 ((eq? (car e) 'symboliclabel)
39984009 (kill)
39994010 #t)
4000- ((memq (car e) '(if elseif trycatch tryfinally trycatchelse))
4011+ ((eq? (car e) 'if)
4012+ ;; Special handling for if-else: track variables assigned in ALL branches.
4013+ ;; If a captured variable is assigned in ALL branches (and not used before
4014+ ;; assignment in any), it's effectively single-assigned per control path.
4015+ (let ((prev (table.clone live)))
4016+ (cond
4017+ ;; if-else with exactly 3 args (cond, then, else) and we have candidates
4018+ ((and (length= e 4) has-ifa)
4019+ (let ((has-label #f)
4020+ (all-assigned #f))
4021+ ;; Visit condition
4022+ (if (visit (cadr e)) (set! has-label #t))
4023+ (let ((pre-branch-live (table.clone live)))
4024+ (kill)
4025+ ;; Visit then-branch
4026+ (if (visit (caddr e)) (set! has-label #t))
4027+ (set! all-assigned (table.clone live))
4028+ ;; Process else-branch (may be elseif chain)
4029+ (let process-else ((else-expr (cadddr e)))
4030+ (set! live (table.clone pre-branch-live))
4031+ (kill)
4032+ (cond
4033+ ;; else-branch is an elseif
4034+ ((and (pair? else-expr) (eq? (car else-expr) 'elseif)
4035+ (length= else-expr 4))
4036+ ;; Visit elseif condition
4037+ (if (visit (cadr else-expr)) (set! has-label #t))
4038+ (kill)
4039+ ;; Visit elseif then-branch
4040+ (if (visit (caddr else-expr)) (set! has-label #t))
4041+ ;; Intersect with all-assigned
4042+ (let ((branch-assigned live))
4043+ (table.foreach
4044+ (lambda (var _)
4045+ (if (not (has? branch-assigned var))
4046+ (del! all-assigned var)))
4047+ all-assigned))
4048+ ;; Process nested else
4049+ (process-else (cadddr else-expr)))
4050+ ;; else-branch is regular expression (final else)
4051+ (else
4052+ (if (visit else-expr) (set! has-label #t))
4053+ ;; Intersect with all-assigned
4054+ (let ((branch-assigned live))
4055+ (table.foreach
4056+ (lambda (var _)
4057+ (if (not (has? branch-assigned var))
4058+ (del! all-assigned var)))
4059+ all-assigned)))))
4060+ ;; Mark variables assigned in all branches as effectively single-assigned
4061+ (table.foreach
4062+ (lambda (var _)
4063+ (if (has? all-assigned var)
4064+ (begin
4065+ (put! seen var #t)
4066+ (put! unused var #t)
4067+ (del! ifa var))))
4068+ ifa)
4069+ (kill)
4070+ (if has-label
4071+ (begin (kill) #t)
4072+ (begin (restore prev) #f)))))
4073+ ;; No ifa candidates - use default handling
4074+ (else
4075+ (if (eager-any (lambda (e) (begin0 (visit e) (kill))) (cdr e))
4076+ (begin (kill) #t)
4077+ (begin (restore prev) #f))))))
4078+ ((memq (car e) '(elseif trycatch tryfinally trycatchelse))
40014079 (let ((prev (table.clone live)))
40024080 (if (eager-any (lambda (e) (begin0 (visit e)
40034081 (kill)))
@@ -4048,10 +4126,18 @@ f(x) = yt(x)
40484126 (let ((vv (assq v vi)))
40494127 (vinfo: set-never-undef! vv #t))))
40504128 (append (table.keys live) (table.keys unused)))
4129+ ;; Clear captured flag for single-assigned never-undef variables
40514130 (for-each (lambda (v)
40524131 (if (and (vinfo: sa v) (vinfo: never-undef v))
40534132 (set-car! (cddr v) (logand (caddr v) (lognot 5)))))
40544133 vi)
4134+ ;; Also clear captured flag for variables that were assigned in all branches of an if-else
4135+ ;; (these are in `unused` but not `ifa`, and have never-undef set)
4136+ (for-each (lambda (var)
4137+ (let ((vv (assq var vi)))
4138+ (if (and vv (vinfo: never-undef vv) (not (has? ifa var)))
4139+ (set-car! (cddr vv) (logand (caddr vv) (lognot 5))))))
4140+ (table.keys unused))
40554141 lam))
40564142
40574143(define (is-var-boxed? v lam)
0 commit comments