22
33using Test
44using Base. Meta
5- using Core: PhiNode, SSAValue, GotoNode, PiNode, QuoteNode, ReturnNode, GotoIfNot
5+ import Core:
6+ CodeInfo, Argument, SSAValue, GotoNode, GotoIfNot, PiNode, PhiNode,
7+ QuoteNode, ReturnNode
68
79include (normpath (@__DIR__ , " irutils.jl" ))
810
@@ -12,7 +14,7 @@ include(normpath(@__DIR__, "irutils.jl"))
1214# # Test that domsort doesn't mangle single-argument phis (#29262)
1315let m = Meta. @lower 1 + 1
1416 @assert Meta. isexpr (m, :thunk )
15- src = m. args[1 ]:: Core. CodeInfo
17+ src = m. args[1 ]:: CodeInfo
1618 src. code = Any[
1719 # block 1
1820 Expr (:call , :opaque ),
4749# test that we don't stack-overflow in SNCA with large functions.
4850let m = Meta. @lower 1 + 1
4951 @assert Meta. isexpr (m, :thunk )
50- src = m. args[1 ]:: Core. CodeInfo
52+ src = m. args[1 ]:: CodeInfo
5153 code = Any[]
5254 N = 2 ^ 15
5355 for i in 1 : 2 : N
7375# SROA
7476# ====
7577
78+ import Core. Compiler: widenconst
79+
80+ is_load_forwarded (src:: CodeInfo ) = ! any (iscall ((src, getfield)), src. code)
81+ is_scalar_replaced (src:: CodeInfo ) =
82+ is_load_forwarded (src) && ! any (iscall ((src, setfield!)), src. code) && ! any (isnew, src. code)
83+
84+ function is_load_forwarded (@nospecialize (T), src:: CodeInfo )
85+ for i in 1 : length (src. code)
86+ x = src. code[i]
87+ if iscall ((src, getfield), x)
88+ widenconst (argextype (x. args[1 ], src)) <: T && return false
89+ end
90+ end
91+ return true
92+ end
93+ function is_scalar_replaced (@nospecialize (T), src:: CodeInfo )
94+ is_load_forwarded (T, src) || return false
95+ for i in 1 : length (src. code)
96+ x = src. code[i]
97+ if iscall ((src, setfield!), x)
98+ widenconst (argextype (x. args[1 ], src)) <: T && return false
99+ elseif isnew (x)
100+ widenconst (argextype (SSAValue (i), src)) <: T && return false
101+ end
102+ end
103+ return true
104+ end
105+
76106struct ImmutableXYZ; x; y; z; end
77107mutable struct MutableXYZ; x; y; z; end
108+ struct ImmutableOuter{T}; x:: T ; y:: T ; z:: T ; end
109+ mutable struct MutableOuter{T}; x:: T ; y:: T ; z:: T ; end
110+ struct ImmutableRef{T}; x:: T ; end
111+ Base. getindex (r:: ImmutableRef ) = r. x
112+ mutable struct SafeRef{T}; x:: T ; end
113+ Base. getindex (s:: SafeRef ) = getfield (s, 1 )
114+ Base. setindex! (s:: SafeRef , x) = setfield! (s, 1 , x)
115+
116+ # simple immutability
117+ # -------------------
78118
79- # should optimize away very basic cases
80119let src = code_typed1 ((Any,Any,Any)) do x, y, z
81120 xyz = ImmutableXYZ (x, y, z)
82121 xyz. x, xyz. y, xyz. z
83122 end
84- @test ! any (isnew, src. code)
123+ @test is_scalar_replaced (src)
124+ @test any (src. code) do @nospecialize x
125+ iscall ((src, tuple), x) &&
126+ x. args[2 : end ] == Any[#= x=# Core. Argument (2 ), #= y=# Core. Argument (3 ), #= z=# Core. Argument (4 )]
127+ end
85128end
129+ let src = code_typed1 ((Any,Any,Any)) do x, y, z
130+ xyz = (x, y, z)
131+ xyz[1 ], xyz[2 ], xyz[3 ]
132+ end
133+ @test is_scalar_replaced (src)
134+ @test any (src. code) do @nospecialize x
135+ iscall ((src, tuple), x) &&
136+ x. args[2 : end ] == Any[#= x=# Core. Argument (2 ), #= y=# Core. Argument (3 ), #= z=# Core. Argument (4 )]
137+ end
138+ end
139+
140+ # simple mutability
141+ # -----------------
142+
86143let src = code_typed1 ((Any,Any,Any)) do x, y, z
87144 xyz = MutableXYZ (x, y, z)
88145 xyz. x, xyz. y, xyz. z
89146 end
90- @test ! any (isnew, src. code)
147+ @test is_scalar_replaced (src)
148+ @test any (src. code) do @nospecialize x
149+ iscall ((src, tuple), x) &&
150+ x. args[2 : end ] == Any[#= x=# Core. Argument (2 ), #= y=# Core. Argument (3 ), #= z=# Core. Argument (4 )]
151+ end
91152end
92-
93- # should handle simple mutabilities
94153let src = code_typed1 ((Any,Any,Any)) do x, y, z
95154 xyz = MutableXYZ (x, y, z)
96155 xyz. y = 42
97156 xyz. x, xyz. y, xyz. z
98157 end
99- @test ! any (isnew, src. code )
158+ @test is_scalar_replaced ( src)
100159 @test any (src. code) do @nospecialize x
101160 iscall ((src, tuple), x) &&
102161 x. args[2 : end ] == Any[#= x=# Core. Argument (2 ), 42 , #= x=# Core. Argument (4 )]
@@ -107,19 +166,23 @@ let src = code_typed1((Any,Any,Any)) do x, y, z
107166 xyz. x, xyz. z = xyz. z, xyz. x
108167 xyz. x, xyz. y, xyz. z
109168 end
110- @test ! any (isnew, src. code )
169+ @test is_scalar_replaced ( src)
111170 @test any (src. code) do @nospecialize x
112171 iscall ((src, tuple), x) &&
113172 x. args[2 : end ] == Any[#= z=# Core. Argument (4 ), #= y=# Core. Argument (3 ), #= x=# Core. Argument (2 )]
114173 end
115174end
116- # circumvent uninitialized fields as far as there is a solid `setfield!` definition
175+
176+ # uninitialized fields
177+ # --------------------
178+
179+ # safe cases
117180let src = code_typed1 () do
118181 r = Ref {Any} ()
119182 r[] = 42
120183 return r[]
121184 end
122- @test ! any (isnew, src. code )
185+ @test is_scalar_replaced ( src)
123186end
124187let src = code_typed1 ((Bool,)) do cond
125188 r = Ref {Any} ()
@@ -131,7 +194,7 @@ let src = code_typed1((Bool,)) do cond
131194 return r[]
132195 end
133196 end
134- @test ! any (isnew, src. code )
197+ @test is_scalar_replaced ( src)
135198end
136199let src = code_typed1 ((Bool,)) do cond
137200 r = Ref {Any} ()
@@ -142,7 +205,7 @@ let src = code_typed1((Bool,)) do cond
142205 end
143206 return r[]
144207 end
145- @test ! any (isnew, src. code )
208+ @test is_scalar_replaced ( src)
146209end
147210let src = code_typed1 ((Bool,Bool,Any,Any,Any)) do c1, c2, x, y, z
148211 r = Ref {Any} ()
@@ -157,7 +220,16 @@ let src = code_typed1((Bool,Bool,Any,Any,Any)) do c1, c2, x, y, z
157220 end
158221 return r[]
159222 end
160- @test ! any (isnew, src. code)
223+ @test is_scalar_replaced (src)
224+ end
225+
226+ # unsafe cases
227+ let src = code_typed1 () do
228+ r = Ref {Any} ()
229+ return r[]
230+ end
231+ @test count (isnew, src. code) == 1
232+ @test count (iscall ((src, getfield)), src. code) == 1
161233end
162234let src = code_typed1 ((Bool,)) do cond
163235 r = Ref {Any} ()
@@ -167,7 +239,9 @@ let src = code_typed1((Bool,)) do cond
167239 return r[]
168240 end
169241 # N.B. `r` should be allocated since `cond` might be `false` and then it will be thrown
170- @test any (isnew, src. code)
242+ @test count (isnew, src. code) == 1
243+ @test count (iscall ((src, setfield!)), src. code) == 1
244+ @test count (iscall ((src, getfield)), src. code) == 1
171245end
172246let src = code_typed1 ((Bool,Bool,Any,Any)) do c1, c2, x, y
173247 r = Ref {Any} ()
@@ -181,12 +255,16 @@ let src = code_typed1((Bool,Bool,Any,Any)) do c1, c2, x, y
181255 return r[]
182256 end
183257 # N.B. `r` should be allocated since `c2` might be `false` and then it will be thrown
184- @test any (isnew, src. code)
258+ @test count (isnew, src. code) == 1
259+ @test count (iscall ((src, setfield!)), src. code) == 2
260+ @test count (iscall ((src, getfield)), src. code) == 1
185261end
186262
187- # should include a simple alias analysis
188- struct ImmutableOuter{T}; x:: T ; y:: T ; z:: T ; end
189- mutable struct MutableOuter{T}; x:: T ; y:: T ; z:: T ; end
263+ # aliased load forwarding
264+ # -----------------------
265+ # TODO fix broken examples with EscapeAnalysis
266+
267+ # OK: immutable(immutable(...)) case
190268let src = code_typed1 ((Any,Any,Any)) do x, y, z
191269 xyz = ImmutableXYZ (x, y, z)
192270 outer = ImmutableOuter (xyz, xyz, xyz)
@@ -214,22 +292,21 @@ let src = code_typed1((Any,Any,Any)) do x, y, z
214292 end
215293end
216294
217- # FIXME our analysis isn't yet so powerful at this moment: may be unable to handle nested objects well
218- # OK: mutable(immutable(...)) case
295+ # OK (mostly): immutable(mutable(...)) case
219296let src = code_typed1 ((Any,Any,Any)) do x, y, z
220297 xyz = MutableXYZ (x, y, z)
221298 t = (xyz,)
222299 v = t[1 ]. x
223300 v, v, v
224301 end
225- @test ! any (isnew, src. code )
302+ @test is_scalar_replaced ( src)
226303end
227304let src = code_typed1 ((Any,Any,Any)) do x, y, z
228305 xyz = MutableXYZ (x, y, z)
229306 outer = ImmutableOuter (xyz, xyz, xyz)
230307 outer. x. x, outer. y. y, outer. z. z
231308 end
232- @test ! any (isnew, src. code )
309+ @test is_scalar_replaced ( src)
233310 @test any (src. code) do @nospecialize x
234311 iscall ((src, tuple), x) &&
235312 x. args[2 : end ] == Any[#= x=# Core. Argument (2 ), #= y=# Core. Argument (3 ), #= y=# Core. Argument (4 )]
@@ -240,12 +317,27 @@ let # this is a simple end to end test case, which demonstrates allocation elimi
240317 # NOTE this test case isn't so robust and might be subject to future changes of the broadcasting implementation,
241318 # in that case you don't really need to stick to keeping this test case around
242319 simple_sroa (s) = broadcast (identity, Ref (s))
320+ let src = code_typed1 (simple_sroa, (String,))
321+ @test is_scalar_replaced (src)
322+ end
243323 s = Base. inferencebarrier (" julia" ):: String
244324 simple_sroa (s)
245325 # NOTE don't hard-code `"julia"` in `@allocated` clause and make sure to execute the
246326 # compiled code for `simple_sroa`, otherwise everything can be folded even without SROA
247327 @test @allocated (simple_sroa (s)) == 0
248328end
329+ let # FIXME : some nested example
330+ src = code_typed1 ((Int,)) do x
331+ Ref (Ref (x))[][]
332+ end
333+ @test_broken is_scalar_replaced (src)
334+
335+ src = code_typed1 ((Int,)) do x
336+ Ref (Ref (Ref (Ref (Ref (Ref (Ref (Ref (Ref (Ref ((x)))))))))))[][][][][][][][][][]
337+ end
338+ @test_broken is_scalar_replaced (src)
339+ end
340+
249341# FIXME : immutable(mutable(...)) case
250342let src = code_typed1 ((Any,Any,Any)) do x, y, z
251343 xyz = ImmutableXYZ (x, y, z)
@@ -314,6 +406,25 @@ let src = code_typed1(compute_points)
314406 @test ! any (isnew, src. code)
315407end
316408
409+ # preserve elimination
410+ # --------------------
411+
412+ let src = code_typed1 ((String,)) do s
413+ ccall (:some_ccall , Cint, (Ptr{String},), Ref (s))
414+ end
415+ @test count (isnew, src. code) == 0
416+ end
417+
418+ # if the mutable struct is directly used, we shouldn't eliminate it
419+ let src = code_typed1 () do
420+ a = MutableXYZ (- 512275808 ,882558299 ,- 2133022131 )
421+ b = Int32 (42 )
422+ ccall (:some_ccall , Cvoid, (MutableXYZ, Int32), a, b)
423+ return a. x
424+ end
425+ @test count (isnew, src. code) == 1
426+ end
427+
317428# comparison lifting
318429# ==================
319430
454565# A SSAValue after the compaction line
455566let m = Meta. @lower 1 + 1
456567 @assert Meta. isexpr (m, :thunk )
457- src = m. args[1 ]:: Core. CodeInfo
568+ src = m. args[1 ]:: CodeInfo
458569 src. code = Any[
459570 # block 1
460571 nothing ,
517628let m = Meta. @lower 1 + 1
518629 # Test that CFG simplify combines redundant basic blocks
519630 @assert Meta. isexpr (m, :thunk )
520- src = m. args[1 ]:: Core. CodeInfo
631+ src = m. args[1 ]:: CodeInfo
521632 src. code = Any[
522633 Core. Compiler. GotoNode (2 ),
523634 Core. Compiler. GotoNode (3 ),
542653let m = Meta. @lower 1 + 1
543654 # Test that CFG simplify doesn't mess up when chaining past return blocks
544655 @assert Meta. isexpr (m, :thunk )
545- src = m. args[1 ]:: Core. CodeInfo
656+ src = m. args[1 ]:: CodeInfo
546657 src. code = Any[
547658 Core. Compiler. GotoIfNot (Core. Compiler. Argument (2 ), 3 ),
548659 Core. Compiler. GotoNode (4 ),
@@ -572,7 +683,7 @@ let m = Meta.@lower 1 + 1
572683 # Test that CFG simplify doesn't try to merge every block in a loop into
573684 # its predecessor
574685 @assert Meta. isexpr (m, :thunk )
575- src = m. args[1 ]:: Core. CodeInfo
686+ src = m. args[1 ]:: CodeInfo
576687 src. code = Any[
577688 # Block 1
578689 Core. Compiler. GotoNode (2 ),
0 commit comments