2
2
3
3
using Test
4
4
using 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
6
8
7
9
include (normpath (@__DIR__ , " irutils.jl" ))
8
10
@@ -12,7 +14,7 @@ include(normpath(@__DIR__, "irutils.jl"))
12
14
# # Test that domsort doesn't mangle single-argument phis (#29262)
13
15
let m = Meta. @lower 1 + 1
14
16
@assert Meta. isexpr (m, :thunk )
15
- src = m. args[1 ]:: Core. CodeInfo
17
+ src = m. args[1 ]:: CodeInfo
16
18
src. code = Any[
17
19
# block 1
18
20
Expr (:call , :opaque ),
47
49
# test that we don't stack-overflow in SNCA with large functions.
48
50
let m = Meta. @lower 1 + 1
49
51
@assert Meta. isexpr (m, :thunk )
50
- src = m. args[1 ]:: Core. CodeInfo
52
+ src = m. args[1 ]:: CodeInfo
51
53
code = Any[]
52
54
N = 2 ^ 15
53
55
for i in 1 : 2 : N
73
75
# SROA
74
76
# ====
75
77
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
+
76
106
struct ImmutableXYZ; x; y; z; end
77
107
mutable 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
+ # -------------------
78
118
79
- # should optimize away very basic cases
80
119
let src = code_typed1 ((Any,Any,Any)) do x, y, z
81
120
xyz = ImmutableXYZ (x, y, z)
82
121
xyz. x, xyz. y, xyz. z
83
122
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
85
128
end
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
+
86
143
let src = code_typed1 ((Any,Any,Any)) do x, y, z
87
144
xyz = MutableXYZ (x, y, z)
88
145
xyz. x, xyz. y, xyz. z
89
146
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
91
152
end
92
-
93
- # should handle simple mutabilities
94
153
let src = code_typed1 ((Any,Any,Any)) do x, y, z
95
154
xyz = MutableXYZ (x, y, z)
96
155
xyz. y = 42
97
156
xyz. x, xyz. y, xyz. z
98
157
end
99
- @test ! any (isnew, src. code )
158
+ @test is_scalar_replaced ( src)
100
159
@test any (src. code) do @nospecialize x
101
160
iscall ((src, tuple), x) &&
102
161
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
107
166
xyz. x, xyz. z = xyz. z, xyz. x
108
167
xyz. x, xyz. y, xyz. z
109
168
end
110
- @test ! any (isnew, src. code )
169
+ @test is_scalar_replaced ( src)
111
170
@test any (src. code) do @nospecialize x
112
171
iscall ((src, tuple), x) &&
113
172
x. args[2 : end ] == Any[#= z=# Core. Argument (4 ), #= y=# Core. Argument (3 ), #= x=# Core. Argument (2 )]
114
173
end
115
174
end
116
- # circumvent uninitialized fields as far as there is a solid `setfield!` definition
175
+
176
+ # uninitialized fields
177
+ # --------------------
178
+
179
+ # safe cases
117
180
let src = code_typed1 () do
118
181
r = Ref {Any} ()
119
182
r[] = 42
120
183
return r[]
121
184
end
122
- @test ! any (isnew, src. code )
185
+ @test is_scalar_replaced ( src)
123
186
end
124
187
let src = code_typed1 ((Bool,)) do cond
125
188
r = Ref {Any} ()
@@ -131,7 +194,7 @@ let src = code_typed1((Bool,)) do cond
131
194
return r[]
132
195
end
133
196
end
134
- @test ! any (isnew, src. code )
197
+ @test is_scalar_replaced ( src)
135
198
end
136
199
let src = code_typed1 ((Bool,)) do cond
137
200
r = Ref {Any} ()
@@ -142,7 +205,7 @@ let src = code_typed1((Bool,)) do cond
142
205
end
143
206
return r[]
144
207
end
145
- @test ! any (isnew, src. code )
208
+ @test is_scalar_replaced ( src)
146
209
end
147
210
let src = code_typed1 ((Bool,Bool,Any,Any,Any)) do c1, c2, x, y, z
148
211
r = Ref {Any} ()
@@ -157,7 +220,16 @@ let src = code_typed1((Bool,Bool,Any,Any,Any)) do c1, c2, x, y, z
157
220
end
158
221
return r[]
159
222
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
161
233
end
162
234
let src = code_typed1 ((Bool,)) do cond
163
235
r = Ref {Any} ()
@@ -167,7 +239,9 @@ let src = code_typed1((Bool,)) do cond
167
239
return r[]
168
240
end
169
241
# 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
171
245
end
172
246
let src = code_typed1 ((Bool,Bool,Any,Any)) do c1, c2, x, y
173
247
r = Ref {Any} ()
@@ -181,12 +255,16 @@ let src = code_typed1((Bool,Bool,Any,Any)) do c1, c2, x, y
181
255
return r[]
182
256
end
183
257
# 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
185
261
end
186
262
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
190
268
let src = code_typed1 ((Any,Any,Any)) do x, y, z
191
269
xyz = ImmutableXYZ (x, y, z)
192
270
outer = ImmutableOuter (xyz, xyz, xyz)
@@ -214,22 +292,21 @@ let src = code_typed1((Any,Any,Any)) do x, y, z
214
292
end
215
293
end
216
294
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
219
296
let src = code_typed1 ((Any,Any,Any)) do x, y, z
220
297
xyz = MutableXYZ (x, y, z)
221
298
t = (xyz,)
222
299
v = t[1 ]. x
223
300
v, v, v
224
301
end
225
- @test ! any (isnew, src. code )
302
+ @test is_scalar_replaced ( src)
226
303
end
227
304
let src = code_typed1 ((Any,Any,Any)) do x, y, z
228
305
xyz = MutableXYZ (x, y, z)
229
306
outer = ImmutableOuter (xyz, xyz, xyz)
230
307
outer. x. x, outer. y. y, outer. z. z
231
308
end
232
- @test ! any (isnew, src. code )
309
+ @test is_scalar_replaced ( src)
233
310
@test any (src. code) do @nospecialize x
234
311
iscall ((src, tuple), x) &&
235
312
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
240
317
# NOTE this test case isn't so robust and might be subject to future changes of the broadcasting implementation,
241
318
# in that case you don't really need to stick to keeping this test case around
242
319
simple_sroa (s) = broadcast (identity, Ref (s))
320
+ let src = code_typed1 (simple_sroa, (String,))
321
+ @test is_scalar_replaced (src)
322
+ end
243
323
s = Base. inferencebarrier (" julia" ):: String
244
324
simple_sroa (s)
245
325
# NOTE don't hard-code `"julia"` in `@allocated` clause and make sure to execute the
246
326
# compiled code for `simple_sroa`, otherwise everything can be folded even without SROA
247
327
@test @allocated (simple_sroa (s)) == 0
248
328
end
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
+
249
341
# FIXME : immutable(mutable(...)) case
250
342
let src = code_typed1 ((Any,Any,Any)) do x, y, z
251
343
xyz = ImmutableXYZ (x, y, z)
@@ -314,6 +406,25 @@ let src = code_typed1(compute_points)
314
406
@test ! any (isnew, src. code)
315
407
end
316
408
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
+
317
428
# comparison lifting
318
429
# ==================
319
430
454
565
# A SSAValue after the compaction line
455
566
let m = Meta. @lower 1 + 1
456
567
@assert Meta. isexpr (m, :thunk )
457
- src = m. args[1 ]:: Core. CodeInfo
568
+ src = m. args[1 ]:: CodeInfo
458
569
src. code = Any[
459
570
# block 1
460
571
nothing ,
517
628
let m = Meta. @lower 1 + 1
518
629
# Test that CFG simplify combines redundant basic blocks
519
630
@assert Meta. isexpr (m, :thunk )
520
- src = m. args[1 ]:: Core. CodeInfo
631
+ src = m. args[1 ]:: CodeInfo
521
632
src. code = Any[
522
633
Core. Compiler. GotoNode (2 ),
523
634
Core. Compiler. GotoNode (3 ),
542
653
let m = Meta. @lower 1 + 1
543
654
# Test that CFG simplify doesn't mess up when chaining past return blocks
544
655
@assert Meta. isexpr (m, :thunk )
545
- src = m. args[1 ]:: Core. CodeInfo
656
+ src = m. args[1 ]:: CodeInfo
546
657
src. code = Any[
547
658
Core. Compiler. GotoIfNot (Core. Compiler. Argument (2 ), 3 ),
548
659
Core. Compiler. GotoNode (4 ),
@@ -572,7 +683,7 @@ let m = Meta.@lower 1 + 1
572
683
# Test that CFG simplify doesn't try to merge every block in a loop into
573
684
# its predecessor
574
685
@assert Meta. isexpr (m, :thunk )
575
- src = m. args[1 ]:: Core. CodeInfo
686
+ src = m. args[1 ]:: CodeInfo
576
687
src. code = Any[
577
688
# Block 1
578
689
Core. Compiler. GotoNode (2 ),
0 commit comments