Skip to content

Commit db76c21

Browse files
JeffBezansonJeff Bezanson
authored andcommitted
another improvement to closure analysis precision (#30972)
helps #15276
1 parent 58db74d commit db76c21

File tree

5 files changed

+100
-73
lines changed

5 files changed

+100
-73
lines changed

src/flisp/system.lsp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -611,11 +611,6 @@
611611
#;(define (table.values t)
612612
(table.foldl (lambda (k v z) (cons v z))
613613
() t))
614-
#;(define (table.clone t)
615-
(let ((nt (table)))
616-
(table.foldl (lambda (k v z) (put! nt k v))
617-
() t)
618-
nt))
619614
#;(define (table.invert t)
620615
(let ((nt (table)))
621616
(table.foldl (lambda (k v z) (put! nt v k))

src/julia-syntax.scm

Lines changed: 64 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2905,19 +2905,6 @@ f(x) = yt(x)
29052905
(or (assq s (car (lam:vinfo lam)))
29062906
(assq s (cadr (lam:vinfo lam)))))
29072907

2908-
(define (table.merge! l r)
2909-
(table.foreach (lambda (k v) (put! l k v))
2910-
r))
2911-
2912-
(define (table.delete-if! p t)
2913-
(let ((to-del '()))
2914-
(table.foreach (lambda (v _)
2915-
(if (p v)
2916-
(set! to-del (cons v to-del))))
2917-
t)
2918-
(for-each (lambda (v) (del! t v))
2919-
to-del)))
2920-
29212908
;; Try to identify never-undef variables, and then clear the `captured` flag for single-assigned,
29222909
;; never-undef variables to avoid allocating unnecessary `Box`es.
29232910
(define (lambda-optimize-vars! lam)
@@ -2931,81 +2918,90 @@ f(x) = yt(x)
29312918
(let ((am (all-methods-for ex stmts)))
29322919
(put! allmethods-table mn am)
29332920
am))))
2934-
(define (expr-uses-var ex v body)
2935-
(cond ((atom? ex) (expr-contains-eq v ex))
2936-
((assignment? ex) (expr-contains-eq v (caddr ex)))
2937-
((eq? (car ex) 'method)
2938-
(and (length> ex 2)
2939-
;; a method expression captures a variable if any methods for the
2940-
;; same function do.
2941-
(let* ((mn (method-expr-name ex))
2942-
(all-methods (if (local-in? mn lam)
2943-
(get-methods ex body)
2944-
(list ex))))
2945-
(any (lambda (ex)
2946-
(assq v (cadr (lam:vinfo (cadddr ex)))))
2947-
all-methods))))
2948-
(else (expr-contains-eq v ex))))
29492921
;; This does a basic-block-local dominance analysis to find variables that
29502922
;; are never used undef.
29512923
(let ((vi (car (lam:vinfo lam)))
29522924
(unused (table)) ;; variables not (yet) used (read from) in the current block
29532925
(live (table)) ;; variables that have been set in the current block
2954-
(seen (table)) ;; all variables we've seen assignments to
2955-
(b1vars '()) ;; vars set in first basic block
2956-
(first #t)) ;; are we in the first basic block?
2926+
(seen (table))) ;; all variables we've seen assignments to
29572927
;; Collect candidate variables: those that are captured (and hence we want to optimize)
29582928
;; and only assigned once. This populates the initial `unused` table.
29592929
(for-each (lambda (v)
29602930
(if (and (vinfo:capt v) (vinfo:sa v))
29612931
(put! unused (car v) #t)))
29622932
vi)
2933+
(define (restore old)
2934+
(table.foreach (lambda (k v)
2935+
(if (not (has? old k))
2936+
(put! unused k v)))
2937+
live)
2938+
(set! live old))
29632939
(define (kill)
29642940
;; when we see control flow, empty live set back into unused set
2965-
(if first
2966-
(begin (set! first #f)
2967-
(set! b1vars (table.keys live))))
2968-
(table.merge! unused live)
2969-
(set! live (table)))
2970-
(define (mark-used e)
2971-
;; remove variables used by `e` from the unused table
2972-
(table.delete-if! (lambda (v) (expr-uses-var e v (lam:body lam)))
2973-
unused))
2941+
(restore (table)))
2942+
(define (mark-used var)
2943+
;; remove variable from the unused table
2944+
(if (has? unused var)
2945+
(del! unused var)))
2946+
(define (assign! var)
2947+
(if (has? unused var)
2948+
;; When a variable is assigned, move it to the live set to protect
2949+
;; it from being removed from `unused`.
2950+
(begin (put! live var #t)
2951+
(put! seen var #t)
2952+
(del! unused var))))
29742953
(define (visit e)
2975-
(cond ((atom? e) (if (symbol? e) (mark-used e)))
2954+
;; returns whether e contained a symboliclabel
2955+
(cond ((atom? e) (if (symbol? e) (mark-used e))
2956+
#f)
29762957
((lambda-opt-ignored-exprs (car e))
2977-
#t)
2958+
#f)
29782959
((eq? (car e) 'scope-block)
29792960
(visit (cadr e)))
2980-
((eq? (car e) 'block)
2981-
(for-each visit (cdr e)))
2961+
((memq (car e) '(block call new _do_while))
2962+
(eager-any visit (cdr e)))
29822963
((eq? (car e) 'break-block)
29832964
(visit (caddr e)))
29842965
((eq? (car e) 'return)
2985-
(visit (cadr e))
2986-
(kill))
2987-
((memq (car e) '(break label symboliclabel symbolicgoto))
2988-
(kill))
2989-
((memq (car e) '(if elseif _while _do_while trycatch tryfinally))
2990-
(for-each (lambda (e)
2991-
(visit e)
2992-
(kill))
2993-
(cdr e)))
2966+
(begin0 (visit (cadr e))
2967+
(kill)))
2968+
((memq (car e) '(break label symbolicgoto))
2969+
(kill)
2970+
#f)
2971+
((eq? (car e) 'symboliclabel)
2972+
(kill)
2973+
#t)
2974+
((memq (car e) '(if elseif _while trycatch tryfinally))
2975+
(let ((prev (table.clone live)))
2976+
(if (eager-any (lambda (e) (begin0 (visit e)
2977+
(kill)))
2978+
(cdr e))
2979+
;; if there is a label inside, we could have skipped a prior
2980+
;; variable initialization
2981+
(begin (kill) #t)
2982+
(begin (restore prev) #f))))
2983+
((eq? (car e) '=)
2984+
(begin0 (visit (caddr e))
2985+
(assign! (cadr e))))
2986+
((eq? (car e) 'method)
2987+
(if (length> e 2)
2988+
(let* ((mn (method-expr-name e))
2989+
;; a method expression captures a variable if any methods for
2990+
;; the same function do.
2991+
(all-methods (if (local-in? mn lam)
2992+
(get-methods e (lam:body lam))
2993+
(list e))))
2994+
(for-each (lambda (ex)
2995+
(for-each mark-used
2996+
(map car (cadr (lam:vinfo (cadddr ex))))))
2997+
all-methods)
2998+
(assign! (cadr e))))
2999+
#f)
29943000
(else
2995-
(if (eq? (car e) '=)
2996-
(visit (caddr e))
2997-
(mark-used e))
2998-
(if (and (or (eq? (car e) '=)
2999-
(and (eq? (car e) 'method) (length> e 2)))
3000-
(has? unused (cadr e)))
3001-
;; When a variable is assigned, move it to the live set to protect
3002-
;; it from being removed from `unused`.
3003-
(begin (put! live (cadr e) #t)
3004-
(put! seen (cadr e) #t)
3005-
(del! unused (cadr e)))
3006-
;; in all other cases there's nothing to do except assert that
3007-
;; all expression heads have been handled.
3008-
#;(assert (memq (car e) '(= method new call foreigncall cfunction |::|)))))))
3001+
(eager-any visit (cdr e))
3002+
;; in all other cases there's nothing to do except assert that
3003+
;; all expression heads have been handled.
3004+
#;(assert (memq (car e) '(foreigncall cfunction |::|))))))
30093005
(visit (lam:body lam))
30103006
;; Finally, variables can be marked never-undef if they were set in the first block,
30113007
;; or are currently live, or are back in the unused set (because we've left the only
@@ -3014,7 +3010,7 @@ f(x) = yt(x)
30143010
(if (has? seen v)
30153011
(let ((vv (assq v vi)))
30163012
(vinfo:set-never-undef! vv #t))))
3017-
(append b1vars (table.keys live) (table.keys unused)))
3013+
(append (table.keys live) (table.keys unused)))
30183014
(for-each (lambda (v)
30193015
(if (and (vinfo:sa v) (vinfo:never-undef v))
30203016
(set-car! (cddr v) (logand (caddr v) (lognot 5)))))

src/utils.scm

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,18 @@
8585
(without (cdr alst) remove)))))
8686

8787
(define (caddddr x) (car (cdr (cdr (cdr (cdr x))))))
88+
89+
(define (table.clone t)
90+
(let ((nt (table)))
91+
(table.foldl (lambda (k v z) (put! nt k v))
92+
() t)
93+
nt))
94+
95+
;; `any`, but call predicate on every element in order no matter what
96+
(define (eager-any pred lst)
97+
(let loop ((lst lst)
98+
(any #f))
99+
(if (null? lst)
100+
any
101+
(loop (cdr lst)
102+
(or (pred (car lst)) any)))))

test/compiler/compiler.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1831,6 +1831,15 @@ function inbounds_30563()
18311831
end
18321832
@test Base.return_types(inbounds_30563, ()) == Any[Int]
18331833

1834+
function ifs_around_var_capture()
1835+
if false end
1836+
x = 1
1837+
if false end
1838+
f = y->x
1839+
f(0)
1840+
end
1841+
@test Base.return_types(ifs_around_var_capture, ()) == Any[Int]
1842+
18341843
# issue #27316 - inference shouldn't hang on these
18351844
f27316(::Vector) = nothing
18361845
f27316(::Any) = f27316(Any[][1]), f27316(Any[][1])

test/syntax.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,3 +1746,15 @@ let x = 0
17461746
@test (a=1, b=2, c=(x=3)) == (a=1, b=2, c=3)
17471747
@test x == 3
17481748
end
1749+
1750+
function capture_with_conditional_label()
1751+
@goto foo
1752+
x = 1
1753+
if false
1754+
@label foo
1755+
end
1756+
return y->x
1757+
end
1758+
let f = capture_with_conditional_label() # should not throw
1759+
@test_throws UndefVarError(:x) f(0)
1760+
end

0 commit comments

Comments
 (0)