Skip to content

Commit 6ef3947

Browse files
committed
Ellide immutable allocation in some simple cases
Extend tuple_elim_pass and getfield_elim_pass to handle immutable object allocations.
1 parent 2ac9bc9 commit 6ef3947

File tree

4 files changed

+97
-36
lines changed

4 files changed

+97
-36
lines changed

base/inference.jl

+77-36
Original file line numberDiff line numberDiff line change
@@ -1436,7 +1436,7 @@ function typeinf(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, def, cop
14361436
end
14371437
end
14381438
# TODO: typeinf currently gets stuck without this
1439-
if linfo.name === :abstract_interpret || linfo.name === :tuple_elim_pass || linfo.name === :abstract_call_gf
1439+
if linfo.name === :abstract_interpret || linfo.name === :alloc_elim_pass || linfo.name === :abstract_call_gf
14401440
return (linfo.ast, Any)
14411441
end
14421442

@@ -1861,7 +1861,7 @@ function typeinf_uncached(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector,
18611861
sv.vars = append_any(f_argnames(fulltree), map(vi->vi[1], fulltree.args[2][1]))
18621862
inbounds_meta_elim_pass(fulltree.args[3])
18631863
end
1864-
tuple_elim_pass(fulltree, sv)
1864+
alloc_elim_pass(fulltree, sv)
18651865
getfield_elim_pass(fulltree.args[3], sv)
18661866
end
18671867
linfo.inferred = true
@@ -2429,15 +2429,15 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atype::ANY, sv::VarInfo, enclosing
24292429
vaname = args[na]
24302430
len_argexprs = length(argexprs)
24312431
valen = len_argexprs-na+1
2432-
if valen>0 && !occurs_outside_getfield(body, vaname, sv, valen)
2432+
if valen>0 && !occurs_outside_getfield(body, vaname, sv, valen, ())
24332433
# argument tuple is not used as a whole, so convert function body
24342434
# to one accepting the exact number of arguments we have.
24352435
newnames = unique_names(ast,valen)
24362436
if needcopy
24372437
body = astcopy(body)
24382438
needcopy = false
24392439
end
2440-
replace_getfield!(ast, body, vaname, newnames, sv, 1)
2440+
replace_getfield!(ast, body, vaname, newnames, (), sv, 1)
24412441
args = vcat(args[1:na-1], newnames)
24422442
na = length(args)
24432443

@@ -3190,28 +3190,27 @@ symequal(x::Symbol , y::SymbolNode) = is(x,y.name)
31903190
symequal(x::GenSym , y::GenSym) = is(x.id,y.id)
31913191
symequal(x::ANY , y::ANY) = is(x,y)
31923192

3193-
function occurs_outside_getfield(e::ANY, sym::ANY, sv::VarInfo, tuplen::Int)
3193+
function occurs_outside_getfield(e::ANY, sym::ANY, sv::VarInfo, field_count, field_names)
31943194
if is(e, sym) || (isa(e, SymbolNode) && is(e.name, sym))
31953195
return true
31963196
end
31973197
if isa(e,Expr)
31983198
e = e::Expr
31993199
if is_known_call(e, getfield, sv) && symequal(e.args[2],sym)
3200-
targ = e.args[2]
3201-
if !(exprtype(targ,sv) <: Tuple)
3202-
return true
3203-
end
32043200
idx = e.args[3]
3205-
if !isa(idx,Int) || !(1 <= idx <= tuplen)
3206-
return true
3201+
if isa(idx,QuoteNode) && (idx.value in field_names)
3202+
return false
32073203
end
3208-
return false
3204+
if isa(idx,Int) && (1 <= idx <= field_count)
3205+
return false
3206+
end
3207+
return true
32093208
end
32103209
if is(e.head,:(=))
3211-
return occurs_outside_getfield(e.args[2], sym, sv, tuplen)
3210+
return occurs_outside_getfield(e.args[2], sym, sv, field_count, field_names)
32123211
else
32133212
for a in e.args
3214-
if occurs_outside_getfield(a, sym, sv, tuplen)
3213+
if occurs_outside_getfield(a, sym, sv, field_count, field_names)
32153214
return true
32163215
end
32173216
end
@@ -3230,46 +3229,80 @@ function inbounds_meta_elim_pass(e::Expr)
32303229
end
32313230
end
32323231

3233-
# replace getfield(tuple(exprs...), i) with exprs[i]
3232+
# does the same job as alloc_elim_pass for allocations inline in getfields
3233+
# TODO can probably be removed when we switch to a linear IR
32343234
function getfield_elim_pass(e::Expr, sv)
32353235
for i = 1:length(e.args)
32363236
ei = e.args[i]
32373237
if isa(ei,Expr)
32383238
getfield_elim_pass(ei, sv)
32393239
if is_known_call(ei, getfield, sv) && length(ei.args)==3 &&
3240-
isa(ei.args[3],Int)
3240+
(isa(ei.args[3],Int) || isa(ei.args[3],QuoteNode))
32413241
e1 = ei.args[2]
32423242
j = ei.args[3]
32433243
if isa(e1,Expr)
3244-
if is_known_call(e1, tuple, sv) && (1 <= j < length(e1.args))
3245-
ok = true
3246-
for k = 2:length(e1.args)
3247-
k == j+1 && continue
3248-
if !effect_free(e1.args[k], sv, true)
3249-
ok = false; break
3250-
end
3244+
alloc = is_immutable_allocation(e1, sv)
3245+
if !is(alloc, false)
3246+
flen, fnames = alloc
3247+
if isa(j,QuoteNode)
3248+
j = findfirst(fnames, j.value)
32513249
end
3252-
if ok
3253-
e.args[i] = e1.args[j+1]
3250+
if 1 <= j <= flen
3251+
ok = true
3252+
for k = 2:length(e1.args)
3253+
k == j+1 && continue
3254+
if !effect_free(e1.args[k], sv, true)
3255+
ok = false; break
3256+
end
3257+
end
3258+
if ok
3259+
e.args[i] = e1.args[j+1]
3260+
end
32543261
end
32553262
end
3256-
elseif isa(e1,Tuple) && (1 <= j <= length(e1))
3263+
elseif isa(e1,Tuple) && isa(j,Int) && (1 <= j <= length(e1))
32573264
e1j = e1[j]
32583265
if !(isa(e1j,Number) || isa(e1j,AbstractString) || isa(e1j,Tuple) ||
32593266
isa(e1j,Type))
32603267
e1j = QuoteNode(e1j)
32613268
end
32623269
e.args[i] = e1j
3263-
elseif isa(e1,QuoteNode) && isa(e1.value,Tuple) && (1 <= j <= length(e1.value))
3270+
elseif isa(e1,QuoteNode) && isa(e1.value,Tuple) && isa(j,Int) && (1 <= j <= length(e1.value))
32643271
e.args[i] = QuoteNode(e1.value[j])
32653272
end
32663273
end
32673274
end
32683275
end
32693276
end
32703277

3271-
# eliminate allocation of unnecessary tuples
3272-
function tuple_elim_pass(ast::Expr, sv::VarInfo)
3278+
# check if e is a successful allocation of an immutable struct
3279+
# if it is, returns (n,f) such that it is always valid to call
3280+
# getfield(..., 1 <= x <= n) or getfield(..., x in f) on the result
3281+
function is_immutable_allocation(e :: ANY, sv::VarInfo)
3282+
isa(e, Expr) || return false
3283+
if is_known_call(e, tuple, sv)
3284+
return (length(e.args)-1,())
3285+
elseif e.head === :new
3286+
typ = exprtype(e, sv)
3287+
if isleaftype(typ) && !typ.mutable
3288+
@assert(isa(typ,DataType))
3289+
nf = length(e.args)-1
3290+
names = fieldnames(typ)
3291+
@assert(nf <= nfields(typ))
3292+
if nf < nfields(typ)
3293+
# some fields were left undef
3294+
# we could potentially propagate Bottom
3295+
# for pointer fields
3296+
names = names[1:nf]
3297+
end
3298+
return (nf, names)
3299+
end
3300+
end
3301+
false
3302+
end
3303+
# eliminate allocation of unnecessary immutables
3304+
# that are only used as arguments to safe getfield calls
3305+
function alloc_elim_pass(ast::Expr, sv::VarInfo)
32733306
bexpr = ast.args[3]::Expr
32743307
body = (ast.args[3].args)::Array{Any,1}
32753308
vs = find_sa_vars(ast)
@@ -3283,10 +3316,11 @@ function tuple_elim_pass(ast::Expr, sv::VarInfo)
32833316
end
32843317
var = e.args[1]
32853318
rhs = e.args[2]
3286-
if isa(rhs,Expr) && is_known_call(rhs, tuple, sv)
3319+
alloc = is_immutable_allocation(rhs, sv)
3320+
if !is(alloc,false)
3321+
nv, field_names = alloc
32873322
tup = rhs.args
3288-
nv = length(tup)-1
3289-
if occurs_outside_getfield(bexpr, var, sv, nv) || !is_local(sv, var)
3323+
if occurs_outside_getfield(bexpr, var, sv, nv, field_names) || !is_local(sv, var)
32903324
i += 1
32913325
continue
32923326
end
@@ -3309,22 +3343,29 @@ function tuple_elim_pass(ast::Expr, sv::VarInfo)
33093343
end
33103344
end
33113345
i += n_ins
3312-
replace_getfield!(ast, bexpr, var, vals, sv, i)
3346+
replace_getfield!(ast, bexpr, var, vals, field_names, sv, i)
33133347
else
33143348
i += 1
33153349
end
33163350
end
33173351
end
33183352

3319-
function replace_getfield!(ast, e::ANY, tupname, vals, sv, i0)
3353+
function replace_getfield!(ast, e::ANY, tupname, vals, field_names, sv, i0)
33203354
if !isa(e,Expr)
33213355
return
33223356
end
33233357
for i = i0:length(e.args)
33243358
a = e.args[i]
33253359
if isa(a,Expr) && is_known_call(a, getfield, sv) &&
33263360
symequal(a.args[2],tupname)
3327-
val = vals[a.args[3]]
3361+
idx = if isa(a.args[3], Int)
3362+
a.args[3]
3363+
else
3364+
@assert isa(a.args[3], QuoteNode)
3365+
findfirst(field_names, a.args[3].value)
3366+
end
3367+
@assert(idx > 0) # clients should check that all getfields are valid
3368+
val = vals[idx]
33283369
# original expression might have better type info than
33293370
# the tuple element expression that's replacing it.
33303371
if isa(val,SymbolNode)
@@ -3347,7 +3388,7 @@ function replace_getfield!(ast, e::ANY, tupname, vals, sv, i0)
33473388
end
33483389
e.args[i] = val
33493390
else
3350-
replace_getfield!(ast, a, tupname, vals, sv, 1)
3391+
replace_getfield!(ast, a, tupname, vals, field_names, sv, 1)
33513392
end
33523393
end
33533394
end

src/codegen.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -3488,6 +3488,7 @@ static jl_cgval_t emit_expr(jl_value_t *expr, jl_codectx_t *ctx)
34883488
if (jl_is_type_type(ty) &&
34893489
jl_is_datatype(jl_tparam0(ty)) &&
34903490
jl_is_leaf_type(jl_tparam0(ty))) {
3491+
assert(nargs <= jl_datatype_nfields(jl_tparam0(ty))+1);
34913492
return emit_new_struct(jl_tparam0(ty),nargs,args,ctx);
34923493
}
34933494
Value *typ = boxed(emit_expr(args[0], ctx), ctx);

src/julia.h

+2
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ STATIC_INLINE char *jl_symbol_name_(jl_sym_t *s)
692692
#define DEFINE_FIELD_ACCESSORS(f) \
693693
static inline uint32_t jl_field_##f(jl_datatype_t *st, int i) \
694694
{ \
695+
assert(i >= 0 && (size_t)i < jl_datatype_nfields(st)); \
695696
if (st->fielddesc_type == 0) { \
696697
return ((jl_fielddesc8_t*)jl_datatype_fields(st))[i].f; \
697698
} \
@@ -705,6 +706,7 @@ STATIC_INLINE char *jl_symbol_name_(jl_sym_t *s)
705706
static inline void jl_field_set##f(jl_datatype_t *st, int i, \
706707
uint32_t val) \
707708
{ \
709+
assert(i >= 0 && (size_t)i < jl_datatype_nfields(st)); \
708710
if (st->fielddesc_type == 0) { \
709711
((jl_fielddesc8_t*)jl_datatype_fields(st))[i].f = val; \
710712
} \

test/core.jl

+17
Original file line numberDiff line numberDiff line change
@@ -3845,3 +3845,20 @@ let ary = Vector{Any}(10)
38453845
check_undef_and_fill(ary, 1:(2n + 4))
38463846
end
38473847
end
3848+
3849+
# pr #15259
3850+
immutable A15259
3851+
x
3852+
y
3853+
end
3854+
# check that allocation was ellided
3855+
@eval f15259(x,y) = (a = $(Expr(:new, :A15259, :x, :y)); (a.x, a.y, getfield(a,1), getfield(a, 2)))
3856+
@test isempty(filter(x -> isa(x,Expr) && x.head === :(=) &&
3857+
isa(x.args[2], Expr) && x.args[2].head === :new,
3858+
code_typed(f15259, (Any,Int))[1].args[3].args))
3859+
@test f15259(1,2) == (1,2,1,2)
3860+
# check that error cases are still correct
3861+
@eval g15259(x,y) = (a = $(Expr(:new, :A15259, :x, :y)); a.z)
3862+
@test_throws ErrorException g15259(1,1)
3863+
@eval h15259(x,y) = (a = $(Expr(:new, :A15259, :x, :y)); getfield(a, 3))
3864+
@test_throws BoundsError h15259(1,1)

0 commit comments

Comments
 (0)