Skip to content

Commit 4041928

Browse files
committed
update EA: fixes from code review
* fix `aliasset` * fix the simple dimension analysis * update `AliasInfo` correctly * handle circular references correctly
1 parent 04a6d4c commit 4041928

File tree

3 files changed

+111
-25
lines changed

3 files changed

+111
-25
lines changed

base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,7 @@ end
763763
estate::EscapeState, change::EscapeChange)
764764
(; xidx, xinfo) = change
765765
anychanged = _propagate_escape_change!(op, estate, xidx, xinfo)
766+
# COMBAK is there a more efficient method of escape information equalization on aliasset?
766767
aliases = getaliases(xidx, estate)
767768
if aliases !== nothing
768769
for aidx in aliases
@@ -809,11 +810,6 @@ end
809810
yroot = find_root!(estate.aliasset, yidx)
810811
if xroot yroot
811812
union!(estate.aliasset, xroot, yroot)
812-
xinfo = estate.escapes[xidx]
813-
yinfo = estate.escapes[yidx]
814-
xyinfo = xinfo ₑ yinfo
815-
estate.escapes[xidx] = xyinfo
816-
estate.escapes[yidx] = xyinfo
817813
return true
818814
end
819815
return false
@@ -851,6 +847,10 @@ function add_alias_change!(astate::AnalysisState, @nospecialize(x), @nospecializ
851847
yidx = iridx(y, estate)
852848
if xidx !== nothing && yidx !== nothing && !isaliased(xidx, yidx, astate.estate)
853849
pushfirst!(astate.changes, AliasChange(xidx, yidx))
850+
# add new escape change here so that it's shared among the expanded `aliasset` in `propagate_escape_change!`
851+
xinfo = estate.escapes[xidx]
852+
yinfo = estate.escapes[yidx]
853+
add_escape_change!(astate, x, xinfo ₑ yinfo)
854854
end
855855
return nothing
856856
end
@@ -937,6 +937,8 @@ function compute_frameinfo(ir::IRCode)
937937
end
938938
arrayinfo[idx] = dims
939939
elseif arrayinfo !== nothing
940+
# TODO this super limited alias analysis is able to handle only very simple cases
941+
# this should be replaced with a proper forward dimension analysis
940942
if isa(stmt, PhiNode)
941943
values = stmt.values
942944
local dims = nothing
@@ -946,12 +948,13 @@ function compute_frameinfo(ir::IRCode)
946948
if isa(val, SSAValue) && haskey(arrayinfo, val.id)
947949
if dims === nothing
948950
dims = arrayinfo[val.id]
949-
elseif dims arrayinfo[val.id]
950-
dims = nothing
951-
break
951+
continue
952+
elseif dims == arrayinfo[val.id]
953+
continue
952954
end
953955
end
954956
end
957+
@goto next_stmt
955958
end
956959
if dims !== nothing
957960
arrayinfo[idx] = dims
@@ -1066,6 +1069,8 @@ function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any})
10661069
add_escape_change!(astate, arg, info)
10671070
end
10681071
end
1072+
# we should disable the alias analysis on this newly introduced object
1073+
add_escape_change!(astate, ret, EscapeInfo(retinfo, true))
10691074
end
10701075
end
10711076

@@ -1107,28 +1112,30 @@ function escape_new!(astate::AnalysisState, pc::Int, args::Vector{Any})
11071112
# fields are known precisely: propagate escape information imposed on recorded possibilities to the exact field values
11081113
infos = AliasInfo.infos
11091114
nf = length(infos)
1110-
objinfo = ignore_aliasinfo(objinfo)
1115+
objinfo = ignore_aliasinfo(objinfo)
11111116
for i in 2:nargs
11121117
i-1 > nf && break # may happen when e.g. ϕ-node merges values with different types
11131118
arg = args[i]
11141119
add_alias_escapes!(astate, arg, infos[i-1])
11151120
push!(infos[i-1], -pc) # record def
11161121
# propagate the escape information of this object ignoring field information
1117-
add_escape_change!(astate, arg, objinfo)
1122+
add_escape_change!(astate, arg, objinfo)
11181123
add_liveness_change!(astate, arg, pc)
11191124
end
1125+
add_escape_change!(astate, obj, EscapeInfo(objinfo, AliasInfo)) # update with new AliasInfo
11201126
elseif isa(AliasInfo, Unindexable) && !AliasInfo.array
11211127
# fields are known partially: propagate escape information imposed on recorded possibilities to all fields values
11221128
info = AliasInfo.info
1123-
objinfo = ignore_aliasinfo(objinfo)
1129+
objinfo = ignore_aliasinfo(objinfo)
11241130
for i in 2:nargs
11251131
arg = args[i]
11261132
add_alias_escapes!(astate, arg, info)
11271133
push!(info, -pc) # record def
11281134
# propagate the escape information of this object ignoring field information
1129-
add_escape_change!(astate, arg, objinfo)
1135+
add_escape_change!(astate, arg, objinfo)
11301136
add_liveness_change!(astate, arg, pc)
11311137
end
1138+
add_escape_change!(astate, obj, EscapeInfo(objinfo, AliasInfo)) # update with new AliasInfo
11321139
else
11331140
# this object has been used as array, but it is allocated as struct here (i.e. should throw)
11341141
# update obj's field information and just handle this case conservatively
@@ -1405,23 +1412,19 @@ function escape_builtin!(::typeof(getfield), astate::AnalysisState, pc::Int, arg
14051412
isa(AliasInfo, Unindexable) && @goto record_unindexable_use
14061413
@label record_indexable_use
14071414
push!(AliasInfo.infos[fidx], pc) # record use
1408-
objinfo = EscapeInfo(objinfo, AliasInfo)
1409-
add_escape_change!(astate, obj, objinfo)
1415+
add_escape_change!(astate, obj, EscapeInfo(objinfo, AliasInfo)) # update with new AliasInfo
14101416
elseif isa(AliasInfo, Unindexable) && !AliasInfo.array
14111417
@label record_unindexable_use
14121418
push!(AliasInfo.info, pc) # record use
1413-
objinfo = EscapeInfo(objinfo, AliasInfo)
1414-
add_escape_change!(astate, obj, objinfo)
1419+
add_escape_change!(astate, obj, EscapeInfo(objinfo, AliasInfo)) # update with new AliasInfo
14151420
else
14161421
# this object has been used as array, but it is used as struct here (i.e. should throw)
14171422
# update obj's field information and just handle this case conservatively
14181423
objinfo = escape_unanalyzable_obj!(astate, obj, objinfo)
14191424
@label conservative_propagation
1420-
# the field couldn't be analyzed precisely: propagate the escape information
1421-
# imposed on the return value of this `getfield` call to the object itself
1422-
# as the most conservative propagation
1423-
ssainfo = estate[SSAValue(pc)]
1424-
add_escape_change!(astate, obj, ssainfo)
1425+
# at the extreme case, a field of `obj` may point to `obj` itself
1426+
# so add the alias change here as the most conservative propagation
1427+
add_alias_change!(astate, obj, SSAValue(pc))
14251428
end
14261429
return false
14271430
end
@@ -1457,7 +1460,7 @@ function escape_builtin!(::typeof(setfield!), astate::AnalysisState, pc::Int, ar
14571460
add_alias_escapes!(astate, val, AliasInfo.infos[fidx])
14581461
push!(AliasInfo.infos[fidx], -pc) # record def
14591462
objinfo = EscapeInfo(objinfo, AliasInfo)
1460-
add_escape_change!(astate, obj, objinfo)
1463+
add_escape_change!(astate, obj, objinfo) # update with new AliasInfo
14611464
# propagate the escape information of this object ignoring field information
14621465
add_escape_change!(astate, val, ignore_aliasinfo(objinfo))
14631466
elseif isa(AliasInfo, Unindexable) && !AliasInfo.array
@@ -1466,7 +1469,7 @@ function escape_builtin!(::typeof(setfield!), astate::AnalysisState, pc::Int, ar
14661469
add_alias_escapes!(astate, val, AliasInfo.info)
14671470
push!(AliasInfo.info, -pc) # record def
14681471
objinfo = EscapeInfo(objinfo, AliasInfo)
1469-
add_escape_change!(astate, obj, objinfo)
1472+
add_escape_change!(astate, obj, objinfo) # update with new AliasInfo
14701473
# propagate the escape information of this object ignoring field information
14711474
add_escape_change!(astate, val, ignore_aliasinfo(objinfo))
14721475
else
@@ -1544,8 +1547,9 @@ function escape_builtin!(::typeof(arrayref), astate::AnalysisState, pc::Int, arg
15441547
# update ary's element information and just handle this case conservatively
15451548
aryinfo = escape_unanalyzable_obj!(astate, ary, aryinfo)
15461549
@label conservative_propagation
1547-
ssainfo = estate[SSAValue(pc)]
1548-
add_escape_change!(astate, ary, ssainfo)
1550+
# at the extreme case, an element of `ary` may point to `ary` itself
1551+
# so add the alias change here as the most conservative propagation
1552+
add_alias_change!(astate, ary, SSAValue(pc))
15491553
end
15501554
return true
15511555
end

test/compiler/EscapeAnalysis/EscapeAnalysis.jl

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,44 @@ end
11751175
end
11761176
@test has_all_escape(result.state[Argument(2)]) # x
11771177
end
1178+
# circular reference
1179+
let result = code_escapes() do
1180+
x = Ref{Any}()
1181+
x[] = x
1182+
return x[]
1183+
end
1184+
i = only(findall(isnew, result.ir.stmts.inst))
1185+
r = only(findall(isreturn, result.ir.stmts.inst))
1186+
@test has_return_escape(result.state[SSAValue(i)], r)
1187+
end
1188+
let result = @eval Module() begin
1189+
const Rx = Ref{Any}()
1190+
Rx[] = Rx
1191+
$code_escapes() do
1192+
r = Rx[]::Base.RefValue{Any}
1193+
return r[]
1194+
end
1195+
end
1196+
r = only(findall(isreturn, result.ir.stmts.inst))
1197+
for i in findall(iscall((result.ir, getfield)), result.ir.stmts.inst)
1198+
@test has_return_escape(result.state[SSAValue(i)], r)
1199+
end
1200+
end
1201+
let result = @eval Module() begin
1202+
@noinline function genr()
1203+
r = Ref{Any}()
1204+
r[] = r
1205+
return r
1206+
end
1207+
$code_escapes() do
1208+
x = genr()
1209+
return x[]
1210+
end
1211+
end
1212+
i = only(findall(isinvoke(:genr), result.ir.stmts.inst))
1213+
r = only(findall(isreturn, result.ir.stmts.inst))
1214+
@test has_return_escape(result.state[SSAValue(i)], r)
1215+
end
11781216

11791217
# dynamic semantics
11801218
# -----------------
@@ -1975,6 +2013,45 @@ end
19752013
@test_broken !has_return_escape(result.state[SSAValue(i)], r)
19762014
@test !is_load_forwardable(result.state[SSAValue(i)])
19772015
end
2016+
2017+
# circular reference
2018+
let result = code_escapes() do
2019+
xs = Vector{Any}(undef, 1)
2020+
xs[1] = xs
2021+
return xs[1]
2022+
end
2023+
i = only(findall(isarrayalloc, result.ir.stmts.inst))
2024+
r = only(findall(isreturn, result.ir.stmts.inst))
2025+
@test has_return_escape(result.state[SSAValue(i)], r)
2026+
end
2027+
let result = @eval Module() begin
2028+
const Ax = Vector{Any}(undef, 1)
2029+
Ax[1] = Ax
2030+
$code_escapes() do
2031+
xs = Ax[1]::Vector{Any}
2032+
return xs[1]
2033+
end
2034+
end
2035+
r = only(findall(isreturn, result.ir.stmts.inst))
2036+
for i in findall(iscall((result.ir, Core.arrayref)), result.ir.stmts.inst)
2037+
@test has_return_escape(result.state[SSAValue(i)], r)
2038+
end
2039+
end
2040+
let result = @eval Module() begin
2041+
@noinline function genxs()
2042+
xs = Vector{Any}(undef, 1)
2043+
xs[1] = xs
2044+
return xs
2045+
end
2046+
$code_escapes() do
2047+
xs = genxs()
2048+
return xs[1]
2049+
end
2050+
end
2051+
i = only(findall(isinvoke(:genxs), result.ir.stmts.inst))
2052+
r = only(findall(isreturn, result.ir.stmts.inst))
2053+
@test has_return_escape(result.state[SSAValue(i)], r)
2054+
end
19782055
end
19792056

19802057
# demonstrate array primitive support with a realistic end to end example

test/compiler/EscapeAnalysis/setup.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ function iscall((ir, f), @nospecialize(x))
5353
end
5454
iscall(pred::Function, @nospecialize(x)) = Meta.isexpr(x, :call) && pred(x.args[1])
5555

56+
# check if `x` is a statically-resolved call of a function whose name is `sym`
57+
isinvoke(y) = @nospecialize(x) -> isinvoke(y, x)
58+
isinvoke(sym::Symbol, @nospecialize(x)) = isinvoke(mi->mi.def.name===sym, x)
59+
isinvoke(pred::Function, @nospecialize(x)) = Meta.isexpr(x, :invoke) && pred(x.args[1]::Core.MethodInstance)
60+
5661
"""
5762
is_load_forwardable(x::EscapeInfo) -> Bool
5863

0 commit comments

Comments
 (0)