From 72a13edf9bdc96e1c6cfc914fc798d8d255e42ca Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sat, 13 Aug 2016 13:09:41 -0400 Subject: [PATCH] fix effect_free computation over getfield fix #18015 --- base/inference.jl | 183 +++++++++++++++++++++------------------------- test/inference.jl | 18 ++++- 2 files changed, 102 insertions(+), 99 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index 779e99d16efe4..869b30aa0f3e2 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -1106,7 +1106,7 @@ function abstract_eval(e::ANY, vtypes::VarTable, sv::InferenceState) if isa(e,QuoteNode) return abstract_eval_constant((e::QuoteNode).value) elseif isa(e,SSAValue) - return abstract_eval_ssavalue(e::SSAValue, sv) + return abstract_eval_ssavalue(e::SSAValue, sv.linfo) elseif isa(e,Slot) return vtypes[e.id].typ elseif isa(e,Symbol) @@ -1190,8 +1190,8 @@ function abstract_eval_global(M::Module, s::Symbol) return Any end -function abstract_eval_ssavalue(s::SSAValue, sv::InferenceState) - typ = sv.linfo.ssavaluetypes[s.id+1] +function abstract_eval_ssavalue(s::SSAValue, linfo::LambdaInfo) + typ = linfo.ssavaluetypes[s.id + 1] if typ === NF return Bottom end @@ -1921,7 +1921,7 @@ function finish(me::InferenceState) if !ispure && length(me.linfo.code) < 10 ispure = true for stmt in me.linfo.code - if !statement_effect_free(stmt, me) + if !statement_effect_free(stmt, me.linfo) ispure = false; break end end @@ -2177,20 +2177,21 @@ function occurs_more(e::ANY, pred, n) return 0 end -function exprtype(x::ANY, sv::InferenceState) - if isa(x,Expr) +function exprtype(x::ANY, linfo::LambdaInfo) + if isa(x, Expr) return (x::Expr).typ - elseif isa(x,SlotNumber) - return sv.linfo.slottypes[x.id] - elseif isa(x,TypedSlot) + elseif isa(x, SlotNumber) + return linfo.slottypes[x.id] + elseif isa(x, TypedSlot) return (x::Slot).typ - elseif isa(x,SSAValue) - return abstract_eval_ssavalue(x::SSAValue, sv) - elseif isa(x,Symbol) - return abstract_eval_global(sv.mod, x::Symbol) - elseif isa(x,QuoteNode) + elseif isa(x, SSAValue) + return abstract_eval_ssavalue(x::SSAValue, linfo) + elseif isa(x, Symbol) + mod = isdefined(linfo, :def) ? linfo.def.module : current_module() + return abstract_eval_global(mod, x::Symbol) + elseif isa(x, QuoteNode) return abstract_eval_constant((x::QuoteNode).value) - elseif isa(x,GlobalRef) + elseif isa(x, GlobalRef) return abstract_eval_global(x.mod, (x::GlobalRef).name) else return abstract_eval_constant(x) @@ -2221,28 +2222,28 @@ function is_pure_builtin(f::ANY) return false end -function statement_effect_free(e::ANY, sv) - if isa(e,Expr) +function statement_effect_free(e::ANY, linfo::LambdaInfo) + if isa(e, Expr) if e.head === :(=) - return !isa(e.args[1],GlobalRef) && effect_free(e.args[2], sv, false) + return !isa(e.args[1], GlobalRef) && effect_free(e.args[2], linfo, false) elseif e.head === :gotoifnot - return effect_free(e.args[1], sv, false) + return effect_free(e.args[1], linfo, false) end - elseif isa(e,LabelNode) || isa(e,GotoNode) + elseif isa(e, LabelNode) || isa(e, GotoNode) return true end - return effect_free(e, sv, false) + return effect_free(e, linfo, false) end # detect some important side-effect-free calls (allow_volatile=true) # and some affect-free calls (allow_volatile=false) -- affect_free means the call # cannot be affected by previous calls, except assignment nodes -function effect_free(e::ANY, sv, allow_volatile::Bool) - if isa(e,GlobalRef) +function effect_free(e::ANY, linfo::LambdaInfo, allow_volatile::Bool) + if isa(e, GlobalRef) return (isdefined(e.mod, e.name) && (allow_volatile || isconst(e.mod, e.name))) - elseif isa(e,Symbol) + elseif isa(e, Symbol) return allow_volatile - elseif isa(e,Expr) + elseif isa(e, Expr) e = e::Expr head = e.head if head === :static_parameter || head === :meta || head === :line || @@ -2251,32 +2252,17 @@ function effect_free(e::ANY, sv, allow_volatile::Bool) end ea = e.args if head === :call && !isa(e.args[1], SSAValue) && !isa(e.args[1], Slot) - if is_known_call_p(e, is_pure_builtin, sv) + if is_known_call_p(e, is_pure_builtin, linfo) if !allow_volatile - if is_known_call(e, arrayref, sv) || is_known_call(e, arraylen, sv) + if is_known_call(e, arrayref, linfo) || is_known_call(e, arraylen, linfo) return false - elseif is_known_call(e, getfield, sv) - # arguments must be immutable to ensure e is affect_free - first = true - for a in ea - if first # first "arg" is the function name - first = false - continue - end - if isa(a,Symbol) - return false - end - if isa(a,SSAValue) - typ = widenconst(exprtype(a,sv)) - if !isa(typ,DataType) || typ.mutable - return false - end - end - if !effect_free(a,sv,allow_volatile) - return false - end + elseif is_known_call(e, getfield, linfo) + # first argument must be immutable to ensure e is affect_free + a = ea[2] + typ = widenconst(exprtype(a, linfo)) + if !isa(typ, DataType) || typ.mutable || typ.abstract + return false end - return true end end # fall-through @@ -2286,7 +2272,7 @@ function effect_free(e::ANY, sv, allow_volatile::Bool) elseif head === :new if !allow_volatile a = ea[1] - typ = widenconst(exprtype(a,sv)) + typ = widenconst(exprtype(a, linfo)) if !isType(typ) || !isa((typ::Type).parameters[1],DataType) || ((typ::Type).parameters[1]::DataType).mutable return false end @@ -2300,11 +2286,11 @@ function effect_free(e::ANY, sv, allow_volatile::Bool) return false end for a in ea - if !effect_free(a,sv,allow_volatile) + if !effect_free(a, linfo, allow_volatile) return false end end - elseif isa(e,LabelNode) || isa(e,GotoNode) + elseif isa(e, LabelNode) || isa(e, GotoNode) return false end return true @@ -2313,12 +2299,12 @@ end #### post-inference optimizations #### -function inline_as_constant(val::ANY, argexprs, sv) +function inline_as_constant(val::ANY, argexprs, linfo::LambdaInfo) # check if any arguments aren't effect_free and need to be kept around stmts = Any[] for i = 1:length(argexprs) arg = argexprs[i] - if !effect_free(arg, sv, false) + if !effect_free(arg, linfo, false) push!(stmts, arg) end end @@ -2365,7 +2351,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference istopfunction(topmod, f, :typejoin) || istopfunction(topmod, f, :promote_type)) # XXX: compute effect_free for the actual arguments - if length(argexprs) < 2 || effect_free(argexprs[2], sv, true) + if length(argexprs) < 2 || effect_free(argexprs[2], enclosing, true) return (e.typ.parameters[1],()) else return (e.typ.parameters[1], Any[argexprs[2]]) @@ -2373,11 +2359,11 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end end if istopfunction(topmod, f, :isbits) && length(atypes)==2 && isType(atypes[2]) && - effect_free(argexprs[2],sv,true) && isleaftype(atypes[2].parameters[1]) + effect_free(argexprs[2], enclosing, true) && isleaftype(atypes[2].parameters[1]) return (isbits(atypes[2].parameters[1]),()) end if is(f, Core.kwfunc) && length(argexprs) == 2 && isa(e.typ, Const) - if effect_free(argexprs[2], sv, true) + if effect_free(argexprs[2], enclosing, true) return (e.typ.val, ()) else return (e.typ.val, Any[argexprs[2]]) @@ -2407,7 +2393,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference local ti = atypes[i] if arg_hoisted || isa(ti, Union) aei = ex.args[i] - if !effect_free(aei, sv, false) + if !effect_free(aei, enclosing, false) arg_hoisted = true newvar = newvar!(sv, ti) insert!(stmts, 1, Expr(:(=), newvar, aei)) @@ -2516,11 +2502,11 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference (isType(e.typ) || isa(e.typ,Const)) if isType(e.typ) if !has_typevars(e.typ.parameters[1]) - return inline_as_constant(e.typ.parameters[1], argexprs, sv) + return inline_as_constant(e.typ.parameters[1], argexprs, enclosing) end else assert(isa(e.typ,Const)) - return inline_as_constant(e.typ.val, argexprs, sv) + return inline_as_constant(e.typ.val, argexprs, enclosing) end end @@ -2535,7 +2521,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end if linfo !== nothing && linfo.jlcall_api == 2 # in this case function can be inlined to a constant - return inline_as_constant(linfo.constval, argexprs, sv) + return inline_as_constant(linfo.constval, argexprs, enclosing) elseif linfo !== nothing && !linfo.inlineable return invoke_NF() elseif linfo === nothing || linfo.code === nothing @@ -2601,7 +2587,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference for i=na:-1:1 # stmts_free needs to be calculated in reverse-argument order #args_i = args[i] aei = argexprs[i] - aeitype = argtype = widenconst(exprtype(aei,sv)) + aeitype = argtype = widenconst(exprtype(aei, enclosing)) # ok for argument to occur more than once if the actual argument # is a symbol or constant, or is not affected by previous statements @@ -2613,17 +2599,17 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference if occ < 6 occ += occurs_more(b, x->(isa(x,Slot)&&x.id==i), 6) end - # TODO: passing `sv` here is wrong since it refers to the enclosing function - if occ > 0 && affect_free && !effect_free(b, sv, true) #TODO: we could short-circuit this test better by memoizing effect_free(b) in the for loop over i + if occ > 0 && affect_free && !effect_free(b, linfo, true) + #TODO: we might be able to short-circuit this test better by memoizing effect_free(b) in the for loop over i affect_free = false end if occ > 5 && !affect_free break end end - free = effect_free(aei,sv,true) + free = effect_free(aei, enclosing, true) if ((occ==0 && is(aeitype,Bottom)) || (occ > 1 && !inline_worthy(aei, occ*2000)) || - (affect_free && !free) || (!affect_free && !effect_free(aei,sv,false))) + (affect_free && !free) || (!affect_free && !effect_free(aei, enclosing, false))) if occ != 0 vnew = newvar!(sv, aeitype) argexprs[i] = vnew @@ -2825,8 +2811,8 @@ end function mk_tuplecall(args, sv::InferenceState) e = Expr(:call, top_tuple, args...) - e.typ = tuple_tfunc(Tuple{Any[widenconst(exprtype(x,sv)) for x in args]...}) - e + e.typ = tuple_tfunc(Tuple{Any[widenconst(exprtype(x, sv.linfo)) for x in args]...}) + return e end function inlining_pass!(linfo::LambdaInfo, sv::InferenceState) @@ -2865,11 +2851,11 @@ function inlining_pass(e::Expr, sv, linfo) # don't inline first (global) arguments of ccall, as this needs to be evaluated # by the interpreter and inlining might put in something it can't handle, # like another ccall (or try to move the variables out into the function) - if is_known_call(e, Core.Intrinsics.ccall, sv) + if is_known_call(e, Core.Intrinsics.ccall, linfo) # 4 is rewritten to 2 below to handle the callee. i0 = 4 isccall = true - elseif is_known_call(e, Core.Intrinsics.llvmcall, sv) + elseif is_known_call(e, Core.Intrinsics.llvmcall, linfo) i0 = 5 isccall = false else @@ -2895,8 +2881,8 @@ function inlining_pass(e::Expr, sv, linfo) end res = inlining_pass(ei, sv, linfo) res1 = res[1] - if has_stmts && !effect_free(res1, sv, false) - restype = exprtype(res1,sv) + if has_stmts && !effect_free(res1, linfo, false) + restype = exprtype(res1, linfo) vnew = newvar!(sv, restype) argloc[i] = vnew unshift!(stmts, Expr(:(=), vnew, res1)) @@ -2909,7 +2895,7 @@ function inlining_pass(e::Expr, sv, linfo) prepend!(stmts,res2) if !has_stmts for stmt in res2 - if !effect_free(stmt, sv, true) + if !effect_free(stmt, linfo, true) has_stmts = true end end @@ -2930,7 +2916,7 @@ function inlining_pass(e::Expr, sv, linfo) end end - ft = exprtype(arg1, sv) + ft = exprtype(arg1, linfo) if isa(ft, Const) f = ft.val else @@ -2948,7 +2934,7 @@ function inlining_pass(e::Expr, sv, linfo) a2 = e.args[3] if isa(a2, Symbol) || isa(a2, Slot) || isa(a2, SSAValue) - ta2 = exprtype(a2, sv) + ta2 = exprtype(a2, linfo) if isa(ta2, Const) a2 = ta2.val end @@ -2960,7 +2946,7 @@ function inlining_pass(e::Expr, sv, linfo) a1 = e.args[2] basenumtype = Union{corenumtype, Main.Base.Complex64, Main.Base.Complex128, Main.Base.Rational} if isa(a1, basenumtype) || ((isa(a1, Symbol) || isa(a1, Slot) || isa(a1, SSAValue)) && - exprtype(a1, sv) ⊑ basenumtype) + exprtype(a1, linfo) ⊑ basenumtype) if square e.args = Any[GlobalRef(Main.Base,:*), a1, a1] res = inlining_pass(e, sv, linfo) @@ -2984,7 +2970,7 @@ function inlining_pass(e::Expr, sv, linfo) ata = Vector{Any}(length(e.args)) ata[1] = ft for i = 2:length(e.args) - a = exprtype(e.args[i], sv) + a = exprtype(e.args[i], linfo) (a === Bottom || isvarargtype(a)) && return (e, stmts) ata[i] = a end @@ -2999,7 +2985,7 @@ function inlining_pass(e::Expr, sv, linfo) if !is(res,NF) # iteratively inline apply(f, tuple(...), tuple(...), ...) in order # to simplify long vararg lists as in multi-arg + - if isa(res,Expr) && is_known_call(res, _apply, sv) + if isa(res,Expr) && is_known_call(res, _apply, linfo) e = res::Expr f = _apply; ft = abstract_eval_constant(f) else @@ -3007,18 +2993,19 @@ function inlining_pass(e::Expr, sv, linfo) end end - if is(f,_apply) + if is(f, _apply) na = length(e.args) newargs = Vector{Any}(na-2) for i = 3:na aarg = e.args[i] - t = widenconst(exprtype(aarg,sv)) - if isa(aarg,Expr) && (is_known_call(aarg, tuple, sv) || is_known_call(aarg, svec, sv)) + t = widenconst(exprtype(aarg, linfo)) + if isa(aarg,Expr) && (is_known_call(aarg, tuple, linfo) || is_known_call(aarg, svec, linfo)) # apply(f,tuple(x,y,...)) => f(x,y,...) newargs[i-2] = aarg.args[2:end] elseif isa(aarg, Tuple) newargs[i-2] = Any[ QuoteNode(x) for x in aarg ] - elseif isa(t,DataType) && t.name===Tuple.name && !isvatuple(t) && effect_free(aarg,sv,true) && length(t.parameters) <= MAX_TUPLE_SPLAT + elseif isa(t, DataType) && t.name === Tuple.name && !isvatuple(t) && + effect_free(aarg, linfo, true) && length(t.parameters) <= MAX_TUPLE_SPLAT # apply(f,t::(x,y)) => f(t[1],t[2]) tp = t.parameters newargs[i-2] = Any[ mk_getfield(aarg,j,tp[j]) for j=1:length(tp) ] @@ -3030,7 +3017,7 @@ function inlining_pass(e::Expr, sv, linfo) e.args = [Any[e.args[2]]; newargs...] # now try to inline the simplified call - ft = exprtype(e.args[1], sv) + ft = exprtype(e.args[1], linfo) if isa(ft,Const) f = ft.val else @@ -3053,23 +3040,23 @@ function add_slot!(linfo::LambdaInfo, typ, is_sa, name=compiler_temp_sym) push!(linfo.slotnames, name) push!(linfo.slottypes, typ) push!(linfo.slotflags, Slot_Assigned + is_sa * Slot_AssignedOnce) - SlotNumber(id) + return SlotNumber(id) end -function is_known_call(e::Expr, func, sv) +function is_known_call(e::Expr, func::ANY, linfo::LambdaInfo) if e.head !== :call return false end - f = exprtype(e.args[1], sv) - return isa(f,Const) && f.val === func + f = exprtype(e.args[1], linfo) + return isa(f, Const) && f.val === func end -function is_known_call_p(e::Expr, pred, sv) +function is_known_call_p(e::Expr, pred::ANY, linfo::LambdaInfo) if e.head !== :call return false end - f = exprtype(e.args[1], sv) - return isa(f,Const) && pred(f.val) + f = exprtype(e.args[1], linfo) + return isa(f, Const) && pred(f.val) end function delete_var!(linfo, id, T) @@ -3172,7 +3159,7 @@ function occurs_outside_getfield(linfo::LambdaInfo, e::ANY, sym::ANY, end if isa(e,Expr) e = e::Expr - if is_known_call(e, getfield, sv) && symequal(e.args[2],sym) + if is_known_call(e, getfield, linfo) && symequal(e.args[2],sym) idx = e.args[3] if isa(idx,QuoteNode) && (idx.value in field_names) return false @@ -3228,7 +3215,7 @@ function _getfield_elim_pass!(e::Expr, sv) for i = 1:length(e.args) e.args[i] = _getfield_elim_pass!(e.args[i], sv) end - if is_known_call(e, getfield, sv) && length(e.args)==3 && + if is_known_call(e, getfield, sv.linfo) && length(e.args)==3 && (isa(e.args[3],Int) || isa(e.args[3],QuoteNode)) e1 = e.args[2] j = e.args[3] @@ -3243,7 +3230,7 @@ function _getfield_elim_pass!(e::Expr, sv) ok = true for k = 2:length(e1.args) k == j+1 && continue - if !effect_free(e1.args[k], sv, true) + if !effect_free(e1.args[k], sv.linfo, true) ok = false; break end end @@ -3273,10 +3260,10 @@ _getfield_elim_pass!(e::ANY, sv) = e # getfield(..., 1 <= x <= n) or getfield(..., x in f) on the result function is_allocation(e :: ANY, sv::InferenceState) isa(e, Expr) || return false - if is_known_call(e, tuple, sv) + if is_known_call(e, tuple, sv.linfo) return (length(e.args)-1,()) elseif e.head === :new - typ = widenconst(exprtype(e, sv)) + typ = widenconst(exprtype(e, sv.linfo)) if isleaftype(typ) @assert(isa(typ,DataType)) nf = length(e.args)-1 @@ -3305,7 +3292,7 @@ function gotoifnot_elim_pass!(linfo::LambdaInfo, sv::InferenceState) expr = expr::Expr expr.head === :gotoifnot || continue cond = expr.args[1] - condt = exprtype(cond, sv) + condt = exprtype(cond, linfo) isa(condt, Const) || continue val = (condt::Const).val # Codegen should emit an unreachable if val is not a Bool so @@ -3382,7 +3369,7 @@ function alloc_elim_pass!(linfo::LambdaInfo, sv::InferenceState) isa(tupelt,QuoteNode) || isa(tupelt, SSAValue)) vals[j] = tupelt else - elty = exprtype(tupelt,sv) + elty = exprtype(tupelt, linfo) if is_ssa tmpv = newvar!(sv, elty) else @@ -3437,7 +3424,7 @@ end function replace_getfield!(linfo::LambdaInfo, e::Expr, tupname, vals, field_names, sv) for i = 1:length(e.args) a = e.args[i] - if isa(a,Expr) && is_known_call(a, getfield, sv) && + if isa(a,Expr) && is_known_call(a, getfield, linfo) && symequal(a.args[2],tupname) idx = if isa(a.args[3], Int) a.args[3] @@ -3460,7 +3447,7 @@ function replace_getfield!(linfo::LambdaInfo, e::Expr, tupname, vals, field_name end elseif isa(val,SSAValue) val = val::SSAValue - typ = exprtype(val, sv) + typ = exprtype(val, linfo) if a.typ ⊑ typ && !(typ ⊑ a.typ) sv.linfo.ssavaluetypes[val.id+1] = a.typ end diff --git a/test/inference.jl b/test/inference.jl index 1d99d6539bc14..f1f4fbb9b1457 100644 --- a/test/inference.jl +++ b/test/inference.jl @@ -68,7 +68,7 @@ end abstract Outer5906{T} immutable Inner5906{T} - a:: T + a:: T end immutable Empty5906{T} <: Outer5906{T} @@ -306,3 +306,19 @@ let T = Array{Tuple{Vararg{Float64,TypeVar(:dim)}},1}, @test Base.return_types(f16530b, (Symbol,)) == Any[TTlim] end @test f16530a(:d) == Vector + + +# issue #18015 +type Triple18015 + a::Int + b::Int + c::Int +end +a18015(tri) = tri.a +b18015(tri) = tri.b +c18015(tri) = tri.c +setabc18015!(tri, a, b, c) = (tri.a = a; tri.b = b; tri.c = c) +let tri = Triple18015(1, 2, 3) + setabc18015!(tri, b18015(tri), c18015(tri), a18015(tri)) + @test tri.a === 2 && tri.b === 3 && tri.c === 1 +end