Skip to content

Commit e5e9e90

Browse files
author
Ian Atol
committed
Improvements to memory_opt, change jl_typeof attribute, always-copy on BoundsError
1 parent b0d43f5 commit e5e9e90

File tree

4 files changed

+160
-41
lines changed

4 files changed

+160
-41
lines changed

base/boot.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,14 @@ struct BoundsError <: Exception
279279
a::Any
280280
i::Any
281281
BoundsError() = new()
282-
BoundsError(@nospecialize(a)) = (@_noinline_meta; new(a))
283-
BoundsError(@nospecialize(a), i) = (@_noinline_meta; new(a,i))
282+
# For now, copy to avoid escaping a
283+
# Eventually, we want to figure out if the copy is needed to save the performance of copying
284+
#BoundsError(@nospecialize(a)) = (@_noinline_meta; new(a))
285+
BoundsError(@nospecialize(a)) = (@_noinline_meta; new(Array(a)))
286+
#BoundsError(@nospecialize(a), i) = (@_noinline_meta; new(a, i))
287+
BoundsError(@nospecialize(a), i) = (@_noinline_meta; new(Array(a), i))
284288
end
289+
285290
struct DivideError <: Exception end
286291
struct OutOfMemoryError <: Exception end
287292
struct ReadOnlyMemoryError <: Exception end

base/compiler/ssair/passes.jl

Lines changed: 134 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,26 +1256,47 @@ function cfg_simplify!(ir::IRCode)
12561256
return finish(compact)
12571257
end
12581258

1259+
# function is_known_fcall(stmt::Expr, @nospecialize(func))
1260+
# isexpr(stmt, :foreigncall) || return false
1261+
# s = stmt.args[1]
1262+
# isa(s, QuoteNode) && (s = s.value)
1263+
# return s === func
1264+
# end
1265+
1266+
function is_known_fcall(stmt::Expr, funcs::Vector{Symbol})
1267+
isexpr(stmt, :foreigncall) || return false
1268+
s = stmt.args[1]
1269+
isa(s, QuoteNode) && (s = s.value)
1270+
# return any(e -> s === e, funcs)
1271+
return true in map(e -> s === e, funcs)
1272+
end
1273+
12591274
function is_allocation(stmt::Expr)
12601275
isexpr(stmt, :foreigncall) || return false
12611276
s = stmt.args[1]
12621277
isa(s, QuoteNode) && (s = s.value)
1263-
return (s === :jl_alloc_array_1d || s === :jl_alloc_array_2d || s === :jl_alloc_array_3d || s === :jl_new_array)
1278+
return (s === :jl_alloc_array_1d
1279+
|| s === :jl_alloc_array_2d
1280+
|| s === :jl_alloc_array_3d
1281+
|| s === :jl_new_array)
12641282
end
12651283

12661284
function memory_opt!(ir::IRCode)
12671285
compact = IncrementalCompact(ir, false)
12681286
uses = IdDict{Int, Vector{Int}}()
1269-
relevant = IdSet{Int}() # allocations
1270-
revisit = Int[] # potential targets for mutating_arrayfreeze
1287+
relevant = IdSet{Int}() # allocations and their sizes
1288+
revisit = Int[] # potential targets for a mutating_arrayfreeze drop-in
12711289

12721290
function mark_escape(@nospecialize val)
12731291
isa(val, SSAValue) || return
1292+
#println(val.id, " escaped.")
12741293
val.id in relevant && pop!(relevant, val.id)
12751294
end
12761295

12771296
for ((_, idx), stmt) in compact
12781297

1298+
#println("idx: ", idx, " = ", stmt)
1299+
12791300
if isa(stmt, ReturnNode)
12801301
isdefined(stmt, :val) || continue
12811302
val = stmt.val
@@ -1284,6 +1305,26 @@ function memory_opt!(ir::IRCode)
12841305
push!(uses[val.id], idx)
12851306
end
12861307
continue
1308+
1309+
# check for phinodes that are possibly allocations
1310+
elseif isa(stmt, PhiNode)
1311+
1312+
# this loop seems like a waste, but using map here didn't go well
1313+
defined = true
1314+
for i = 1:length(stmt.values)
1315+
if !isassigned(stmt.values, i)
1316+
defined = false
1317+
end
1318+
end
1319+
1320+
defined || continue
1321+
1322+
for val in stmt.values
1323+
if isa(val, SSAValue) && val.id in relevant
1324+
#println("Adding ", idx ," to relevant from PhiNode: " , stmt)
1325+
push!(relevant, idx)
1326+
end
1327+
end
12871328
end
12881329

12891330
(isexpr(stmt, :call) || isexpr(stmt, :foreigncall)) || continue
@@ -1294,54 +1335,101 @@ function memory_opt!(ir::IRCode)
12941335
continue
12951336
end
12961337

1297-
# TODO: Replace this by interprocedural escape analysis
1298-
if is_known_call(stmt, arrayset, compact)
1299-
# arrayset expr.args:
1300-
# :(Base.arrayset)
1301-
# false
1302-
# :(%2) array
1303-
# :(%8) value
1304-
# :(%7) index
1338+
if is_known_call(stmt, arrayset, compact) && length(stmt.args) >= 5
13051339
# The value being set escapes, everything else doesn't
1306-
(length(stmt.args) == 5) || continue # fix boundserror during precompile --- but how do we have arrayset with < 5 args?
13071340
mark_escape(stmt.args[4])
1341+
13081342
arr = stmt.args[3]
1343+
13091344
if isa(arr, SSAValue) && arr.id in relevant
13101345
(haskey(uses, arr.id)) || (uses[arr.id] = Int[])
13111346
push!(uses[arr.id], idx)
13121347
end
13131348

1314-
elseif is_known_call(stmt, Core.arrayfreeze, compact) && isa(stmt.args[2], SSAValue)
1315-
push!(revisit, idx)
1349+
elseif is_known_call(stmt, arrayref, compact) && length(stmt.args) == 4
1350+
arr = stmt.args[3]
13161351

1317-
elseif is_known_call(stmt, arraysize, compact) && isa(stmt.args[2], SSAValue) && isa(stmt.args[3], Number)
1318-
arr = stmt.args[2]
1319-
dim = stmt.args[3]
1320-
typ = types(compact)[arr]
1352+
if isa(arr, SSAValue) && arr.id in relevant
1353+
(haskey(uses, arr.id)) || (uses[arr.id] = Int[])
1354+
push!(uses[arr.id], idx)
1355+
end
13211356

1322-
while !isa(typ, Type)
1323-
typ = typeof(typ)
1357+
elseif is_known_call(stmt, setindex!, compact) && length(stmt.args) == 4
1358+
#println("setindex!: ", stmt.args)
1359+
# handle similarly to arrayset
1360+
# escape the value being set
1361+
val = stmt.args[3]
1362+
mark_escape(val)
1363+
# track usage of arr for dominance analysis
1364+
arr = stmt.args[2]
1365+
if isa(arr, SSAValue) && arr.id in relevant
1366+
(haskey(uses, arr.id)) || (uses[arr.id] = Int[])
1367+
push!(uses[arr.id], idx)
13241368
end
13251369

1326-
if isa(typ, Core.Const)
1327-
typ = typ.val
1370+
# these foreigncalls have similar structure and don't escape our array, so handle them all at once
1371+
elseif is_known_fcall(stmt, [:jl_array_ptr, :jl_array_copy]) && length(stmt.args) == 6
1372+
#println("is_known_fcall: ", stmt)
1373+
arr = stmt.args[6]
1374+
1375+
# just record usage info
1376+
if isa(arr, SSAValue) && arr.id in relevant
1377+
(haskey(uses, arr.id)) || (uses[arr.id] = Int[])
1378+
push!(uses[arr.id], idx)
13281379
end
13291380

1330-
# make sure this call isn't going to throw
1331-
if typ <: AbstractArray && dim >= 1
1332-
# don't escape the array, but mark usage for dom analysis
1333-
if arr.id in relevant
1334-
(haskey(uses, arr.id)) || (uses[arr.id] = Int[])
1335-
push!(uses[arr.id], idx)
1336-
end
1337-
else # if this call throws or we can't tell, the array definitely escapes
1338-
for ur in userefs(stmt)
1339-
mark_escape(ur[])
1340-
end
1381+
# elseif is_known_fcall(stmt, :jl_array_ptr) && length(stmt.args) == 6
1382+
# arr = stmt.args[6]
1383+
1384+
# if isa(arr, SSAValue) && arr.id in relevant
1385+
# (haskey(uses, arr.id)) || (uses[arr.id] = Int[])
1386+
# push!(uses[arr.id], idx)
1387+
# end
1388+
1389+
# elseif is_known_fcall(stmt, :jl_array_copy) && length(stmt.args) == 6
1390+
# arr = stmt.args[6]
1391+
1392+
# if isa(arr, SSAValue) && arr.id in relevant
1393+
# (haskey(uses, arr.id)) || (uses[arr.id] = Int[])
1394+
# push!(uses[arr.id], idx)
1395+
# end
1396+
1397+
elseif is_known_call(stmt, arraysize, compact) && isa(stmt.args[2], SSAValue) #&& isa(stmt.args[3], Number)
1398+
arr = stmt.args[2]
1399+
# dim = stmt.args[3]
1400+
# typ = types(compact)[arr]
1401+
1402+
# if isa(typ, Core.Const)
1403+
# typ = typ.val
1404+
# end
1405+
1406+
# NEW: since exceptions no longer escape arrays, we can just assume no escape
1407+
1408+
if arr.id in relevant
1409+
(haskey(uses, arr.id)) || (uses[arr.id] = Int[])
1410+
push!(uses[arr.id], idx)
13411411
end
1412+
1413+
# # make sure this call isn't going to throw
1414+
# if isa(typ, Type) && typ <: AbstractArray && dim >= 1
1415+
# # don't escape the array, but mark usage for dom analysis
1416+
# if arr.id in relevant
1417+
# (haskey(uses, arr.id)) || (uses[arr.id] = Int[])
1418+
# push!(uses[arr.id], idx)
1419+
# end
1420+
1421+
# else # if this call throws or we can't tell, assume all uses escape
1422+
# for ur in userefs(stmt)
1423+
# mark_escape(ur[])
1424+
# end
1425+
# end
1426+
1427+
elseif is_known_call(stmt, Core.arrayfreeze, compact) && isa(stmt.args[2], SSAValue)
1428+
# mark these for potential replacement with mutating_arrayfreeze
1429+
push!(revisit, idx)
1430+
13421431
else
1343-
# For now we assume everything escapes
1344-
# TODO: We could handle PhiNodes specially and improve this
1432+
# Assume everything else escapes
13451433
for ur in userefs(stmt)
13461434
mark_escape(ur[])
13471435
end
@@ -1357,8 +1445,18 @@ function memory_opt!(ir::IRCode)
13571445
# Make sure that the value we reference didn't escape
13581446
stmt = ir.stmts[idx][:inst]::Expr
13591447
id = (stmt.args[2]::SSAValue).id
1448+
# print("Relevant: ")
1449+
# for id in relevant
1450+
# print(id, " ")
1451+
# end
1452+
# println(" ")
1453+
# print("Revisit: ")
1454+
# Main.Base.show(revisit)
1455+
# println()
13601456
(id in relevant) || continue
13611457

1458+
#println("Revisiting ", stmt)
1459+
13621460
# We're ok to steal the memory if we don't dominate any uses
13631461
ok = true
13641462
if haskey(uses, id)
@@ -1370,6 +1468,7 @@ function memory_opt!(ir::IRCode)
13701468
end
13711469
end
13721470
ok || continue
1471+
#println("Optimization of ", stmt)
13731472
stmt.args[1] = Core.mutating_arrayfreeze
13741473
end
13751474
return ir

src/codegen.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,12 +663,13 @@ static const auto jl_newbits_func = new JuliaFunction{
663663
// `julia.typeof` does read memory, but it is effectively readnone before we lower
664664
// the allocation function. This is OK as long as we lower `julia.typeof` no later than
665665
// `julia.gc_alloc_obj`.
666+
// Updated to argmemonly due to C++ deconstructor style usage in jl_f_arrayfreeze / mutating_arrayfreeze
666667
static const auto jl_typeof_func = new JuliaFunction{
667668
"julia.typeof",
668669
[](LLVMContext &C) { return FunctionType::get(T_prjlvalue,
669670
{T_prjlvalue}, false); },
670671
[](LLVMContext &C) { return AttributeList::get(C,
671-
Attributes(C, {Attribute::ReadNone, Attribute::NoUnwind, Attribute::NoRecurse}),
672+
Attributes(C, {Attribute::ArgMemOnly, Attribute::NoUnwind, Attribute::NoRecurse}),
672673
Attributes(C, {Attribute::NonNull}),
673674
None); },
674675
};

test/compiler/immutablearray.jl

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ using Test
44
function test_allocate1()
55
a = Vector{Float64}(undef, 5)
66
for i = 1:5
7-
a[i] === i
7+
a[i] = i
88
end
9-
ImmutableArray(a)
9+
Core.ImmutableArray(a)
1010
end
1111

1212
function test_allocate2()
@@ -33,15 +33,29 @@ function test_broadcast1()
3333
typeof(a .+ a) <: Core.ImmutableArray
3434
end
3535

36+
function test_allocate5()
37+
a = [1,2,3]
38+
try
39+
getindex(a, 4)
40+
catch end
41+
Core.ImmutableArray(a)
42+
end
43+
3644
let
45+
# warmup for @allocated
46+
a,b,c,d,e = test_allocate1(), test_allocate2(), test_allocate3(), test_allocate4(), test_allocate5()
47+
48+
# these magic values are ~ what the mutable array version would allocate
3749
@test @allocated(test_allocate1()) < 100
3850
@test @allocated(test_allocate2()) < 100
3951
@test @allocated(test_allocate3()) < 150
4052
@test @allocated(test_allocate4()) < 100
53+
@test @allocated(test_allocate5()) < 170
4154
@test test_broadcast1() == true
4255
end
4356

44-
#DiffEq Performance Tests
57+
58+
# DiffEq Performance Tests
4559

4660
# using DifferentialEquations
4761
# using StaticArrays

0 commit comments

Comments
 (0)