Skip to content

Commit

Permalink
improved inlining
Browse files Browse the repository at this point in the history
we can now handle cases that require inserting new temp variables
fixes #1145
helps complex performance a lot (#323)
  • Loading branch information
JeffBezanson committed Aug 12, 2012
1 parent 6ca43d9 commit be9a37f
Showing 1 changed file with 92 additions and 41 deletions.
133 changes: 92 additions & 41 deletions base/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1086,7 +1086,7 @@ function typeinf(linfo::LambdaStaticData,atypes::Tuple,sparams::Tuple, def, cop)
fulltree = type_annotate(ast, s, sv, frame.result, vars, args)

if !rec
fulltree.args[3] = inlining_pass(fulltree.args[3], vars)
fulltree.args[3] = inlining_pass(fulltree.args[3], vars, fulltree)[1]
tuple_elim_pass(fulltree)
linfo.inferred = true
end
Expand Down Expand Up @@ -1325,12 +1325,25 @@ function without_linenums(a::Array{Any,1})
l
end

# detect some important side-effect-free calls
function effect_free(e)
if isa(e,Expr) && (e.head === :call || e.head === :call1)
ea = e.args
if isa(ea[1],TopNode) && ea[1].name === :getfield
if length(ea) > 1 && (isa(ea[2],Symbol) || isa(ea[2],SymbolNode))
return true
end
end
end
return false
end

# for now, only inline functions whose bodies are of the form "return <expr>"
# where <expr> doesn't contain any argument more than once.
# functions with closure environments or varargs are also excluded.
# static parameters are ok if all the static parameter values are leaf types,
# meaning they are fully known.
function inlineable(f, e::Expr, vars)
function inlineable(f, e::Expr, vars, enclosing_ast)
if !(isa(f,Function)||isa(f,CompositeKind)||isa(f,IntrinsicFunction))
return NF
end
Expand All @@ -1342,15 +1355,15 @@ function inlineable(f, e::Expr, vars)
if isType(atypes[1]) && isleaftype(atypes[1]) &&
subtype(atypes[2],atypes[1].parameters[1])
# todo: if T expression has side effects??!
return e.args[3]
return (e.args[3],())
end
end
if (is(f,apply_type) || is(f,fieldtype)) &&
isType(e.typ) && isleaftype(e.typ.parameters[1])
return e.typ.parameters[1]
return (e.typ.parameters[1],())
end
if length(atypes)==2 && is(f,unbox) && isa(atypes[2],BitsKind)
return e.args[3]
return (e.args[3],())
end
if isa(f,IntrinsicFunction)
return NF
Expand Down Expand Up @@ -1404,42 +1417,45 @@ function inlineable(f, e::Expr, vars)
if na>0 && is_rest_arg(ast.args[1][na])
return NF
end
# see if each argument occurs only once in the body expression
# TODO: make sure side effects aren't skipped if argument doesn't occur
expr = body[1].args[1]

# avoid capture if the function has free variables with the same name
# as our vars
if occurs_more(expr, x->(contains_is(vars,x)&&!contains_is(args,x)), 0) > 0
return NF
end

stmts = {}
# see if each argument occurs only once in the body expression
for i=1:length(args)
a = args[i]
occ = occurs_more(expr, x->is(x,a), 1)
if occ > 1
aei = argexprs[i]
if occ != 1
aei = argexprs[i]; aeitype = exprtype(aei)
# ok for argument to occur more than once if the actual argument
# is a symbol or constant
if !isa(aei,Symbol) && !isa(aei,Number) && !isa(aei,SymbolNode)
return NF
end
elseif occ == 0
aei = argexprs[i]
if is(exprtype(aei),None)
# if an argument does not occur in the function and its
# actual argument is an error, make sure the error is not
# skipped.
return NF
if (!isa(aei,Symbol) && !isa(aei,Number) && !isa(aei,SymbolNode) && !isa(aei,String)) || (occ==0 && is(aeitype,None))
# introduce variable for this argument
if occ > 1
vnew = unique_name(enclosing_ast)
add_variable(enclosing_ast, vnew, aeitype)
push(stmts, Expr(:(=), {vnew, aei}, Any))
argexprs[i] = SymbolNode(vnew,aeitype)
elseif !isType(aeitype) && !effect_free(aei)
push(stmts, aei)
end
end
end
end
# avoid capture if the function has free variables with the same name
# as our vars
if occurs_more(expr, x->(contains_is(vars,x)&&!contains_is(args,x)), 0) > 0
return NF
end

# ok, substitute argument expressions for argument names in the body
spnames = { sp[i].name for i=1:2:length(sp) }
expr = astcopy(expr)
mfrom = meth[3].module; mto = (inference_stack::CallStack).mod
if !is(mfrom, mto)
expr = resolve_globals(expr, mfrom, mto, args, spnames)
end
return sym_replace(expr, args, spnames, argexprs, spvals)
return (sym_replace(expr, args, spnames, argexprs, spvals), stmts)
end

_jl_tn(sym::Symbol) =
Expand All @@ -1453,13 +1469,13 @@ function _jl_mk_tupleref(texpr, i)
e
end

function inlining_pass(e::Expr, vars)
function inlining_pass(e::Expr, vars, ast)
# don't inline first argument of ccall, as this needs to be evaluated
# by the interpreter and inlining might put in something it can't handle,
# like another ccall.
eargs = e.args
if length(eargs)<1
return e
return (e,())
end
arg1 = eargs[1]
if is(e.head,:call1) && (is(arg1, :ccall) ||
Expand All @@ -1471,10 +1487,34 @@ function inlining_pass(e::Expr, vars)
i0 = 1
isccall = false
end
for i=i0:length(eargs)
ei = eargs[i]
if isa(ei,Expr)
eargs[i] = inlining_pass(ei, vars)
stmts = {}
if e.head === :body
i = i0
while i <= length(eargs)
ei = eargs[i]
if isa(ei,Expr)
res = inlining_pass(ei, vars, ast)
eargs[i] = res[1]
if isa(res[2],Array)
sts = res[2]::Array{Any,1}
for j = 1:length(sts)
insert(eargs, i, sts[j])
i += 1
end
end
end
i += 1
end
else
for i=i0:length(eargs)
ei = eargs[i]
if isa(ei,Expr)
res = inlining_pass(ei, vars, ast)
eargs[i] = res[1]
if isa(res[2],Array)
append!(stmts,res[2]::Array{Any,1})
end
end
end
end
if isccall
Expand Down Expand Up @@ -1509,11 +1549,16 @@ function inlining_pass(e::Expr, vars)
end
end

body = inlineable(f, e, vars)
if !is(body,NF)
#print("inlining ", e, " => ", body, "\n")
return body
res = inlineable(f, e, vars, ast)
if isa(res,Tuple)
if isa(res[2],Array)
append!(stmts,res[2])
end
return (res[1],stmts)
elseif !is(res,NF)
return (res,stmts)
end

if is(f,apply)
na = length(e.args)
newargs = cell(na-2)
Expand All @@ -1528,20 +1573,26 @@ function inlining_pass(e::Expr, vars)
newargs[i-2] = { _jl_mk_tupleref(aarg,j) for j=1:length(t) }
else
# not all args expandable
return e
return (e,stmts)
end
end
e.args = [{e.args[2]}, newargs...]

# now try to inline the simplified call
body = inlineable(_ieval(e.args[1]), e, vars)
if !is(body,NF)
return body
res = inlineable(_ieval(e.args[1]), e, vars, ast)
if isa(res,Tuple)
if isa(res[2],Array)
append!(stmts,res[2])
end
return (res[1],stmts)
elseif !is(res,NF)
return (res,stmts)
end
return e

return (e,stmts)
end
end
e
return (e,stmts)
end

function add_variable(ast, name, typ)
Expand Down

2 comments on commit be9a37f

@ViralBShah
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really exciting! Any idea how this improves us on our mandel benchmark?

@timholy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!

Please sign in to comment.