Skip to content

Commit 36e1839

Browse files
authored
Only compute effect_free once (#40066)
This is a revival of #33753. While this is useful in itself, my real goal here is to be able to make use of the computed effect-freeness earlier to allow removal of calls that the compiler has shown to be effect-free, but are too large to inline. This moves in that direction, by making effect-freeness an SSA flag that an appropriate pass can then set and that it propagated appropriately by inlining (since unlike effect-freeness on builtins which is easy to compute, effect-freeness on generic function calls requires interprocedural information that we throw away after inlining into the outer function). While I'm at it, also refactor the instruction insertion interface, to make them all use a common type, rather than random assortments of subsets of the possible fields.
1 parent 51591ff commit 36e1839

File tree

10 files changed

+184
-82
lines changed

10 files changed

+184
-82
lines changed

base/compiler/compiler.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ using .Order
9999
include("sort.jl")
100100
using .Sort
101101

102+
# We don't include some.jl, but this definition is still useful.
103+
something(x::Nothing, y...) = something(y...)
104+
something(x::Any, y...) = x
105+
102106
############
103107
# compiler #
104108
############

base/compiler/optimize.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,12 @@ const SLOT_ASSIGNEDONCE = 16 # slot is assigned to only once
132132
const SLOT_USEDUNDEF = 32 # slot has uses that might raise UndefVarError
133133
# const SLOT_CALLED = 64
134134

135-
const IR_FLAG_INBOUNDS = 0x01
135+
# This statement was marked as @inbounds by the user. If replaced by inlining,
136+
# any contained boundschecks may be removed
137+
const IR_FLAG_INBOUNDS = 0x01
138+
# This statement may be removed if its result is unused. In particular it must
139+
# thus be both pure and effect free.
140+
const IR_FLAG_EFFECT_FREE = 0x01 << 4
136141

137142
# known to be always effect-free (in particular nothrow)
138143
const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields]

base/compiler/ssair/inlining.jl

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,8 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector
324324
if is_opaque
325325
# Replace the first argument by a load of the capture environment
326326
argexprs[1] = insert_node_here!(compact,
327-
Expr(:call, GlobalRef(Core, :getfield), argexprs[1], :captures),
328-
spec.ir.argtypes[1], compact.result[idx][:line])
327+
NewInstruction(Expr(:call, GlobalRef(Core, :getfield), argexprs[1], QuoteNode(:captures)),
328+
spec.ir.argtypes[1], compact.result[idx][:line]))
329329
end
330330
flag = compact.result[idx][:flag]
331331
boundscheck_idx = boundscheck
@@ -386,8 +386,8 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector
386386
inline_compact.result[idx′][:type] = (isa(val, Argument) || isa(val, Expr)) ?
387387
compact_exprtype(compact, val) :
388388
compact_exprtype(inline_compact, val)
389-
insert_node_here!(inline_compact, GotoNode(post_bb_id),
390-
Any, compact.result[idx′][:line],
389+
insert_node_here!(inline_compact, NewInstruction(GotoNode(post_bb_id),
390+
Any, compact.result[idx′][:line]),
391391
true)
392392
push!(pn.values, SSAValue(idx′))
393393
else
@@ -419,7 +419,8 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector
419419
if length(pn.edges) == 1
420420
return_value = pn.values[1]
421421
else
422-
return_value = insert_node_here!(compact, pn, compact_exprtype(compact, SSAValue(idx)), compact.result[idx][:line])
422+
return_value = insert_node_here!(compact,
423+
NewInstruction(pn, compact_exprtype(compact, SSAValue(idx)), compact.result[idx][:line]))
423424
end
424425
end
425426
return_value
@@ -448,15 +449,15 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int,
448449
a <: m && continue
449450
# Generate isa check
450451
isa_expr = Expr(:call, isa, argexprs[i], m)
451-
ssa = insert_node_here!(compact, isa_expr, Bool, line)
452+
ssa = insert_node_here!(compact, NewInstruction(isa_expr, Bool, line))
452453
if cond === true
453454
cond = ssa
454455
else
455456
and_expr = Expr(:call, and_int, cond, ssa)
456-
cond = insert_node_here!(compact, and_expr, Bool, line)
457+
cond = insert_node_here!(compact, NewInstruction(and_expr, Bool, line))
457458
end
458459
end
459-
insert_node_here!(compact, GotoIfNot(cond, next_cond_bb), Union{}, line)
460+
insert_node_here!(compact, NewInstruction(GotoIfNot(cond, next_cond_bb), Union{}, line))
460461
bb = next_cond_bb - 1
461462
finish_current_bb!(compact, 0)
462463
argexprs′ = argexprs
@@ -466,45 +467,49 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int,
466467
a, m = atype.parameters[i], metharg.parameters[i]
467468
(isa(argexprs[i], SSAValue) || isa(argexprs[i], Argument)) || continue
468469
if !(a <: m)
469-
argexprs′[i] = insert_node_here!(compact, PiNode(argexprs′[i], m),
470-
m, line)
470+
argexprs′[i] = insert_node_here!(compact,
471+
NewInstruction(PiNode(argexprs′[i], m), m, line))
471472
end
472473
end
473474
end
474475
if isa(case, InliningTodo)
475476
val = ir_inline_item!(compact, idx, argexprs′, linetable, case, boundscheck, todo_bbs)
476477
elseif isa(case, MethodInstance)
477-
val = insert_node_here!(compact, Expr(:invoke, case, argexprs′...), typ, line)
478+
val = insert_node_here!(compact,
479+
NewInstruction(Expr(:invoke, case, argexprs′...), typ, line))
478480
else
479481
case = case::ConstantCase
480482
val = case.val
481483
end
482484
if !isempty(compact.result_bbs[bb].preds)
483485
push!(pn.edges, bb)
484486
push!(pn.values, val)
485-
insert_node_here!(compact, GotoNode(join_bb), Union{}, line)
487+
insert_node_here!(compact,
488+
NewInstruction(GotoNode(join_bb), Union{}, line))
486489
else
487-
insert_node_here!(compact, ReturnNode(), Union{}, line)
490+
insert_node_here!(compact,
491+
NewInstruction(ReturnNode(), Union{}, line))
488492
end
489493
finish_current_bb!(compact, 0)
490494
end
491495
bb += 1
492496
# We're now in the fall through block, decide what to do
493497
if item.fully_covered
494498
e = Expr(:call, GlobalRef(Core, :throw), fatal_type_bound_error)
495-
insert_node_here!(compact, e, Union{}, line)
496-
insert_node_here!(compact, ReturnNode(), Union{}, line)
499+
insert_node_here!(compact, NewInstruction(e, Union{}, line))
500+
insert_node_here!(compact, NewInstruction(ReturnNode(), Union{}, line))
497501
finish_current_bb!(compact, 0)
498502
else
499-
ssa = insert_node_here!(compact, stmt, typ, line)
503+
ssa = insert_node_here!(compact, NewInstruction(stmt, typ, line))
500504
push!(pn.edges, bb)
501505
push!(pn.values, ssa)
502-
insert_node_here!(compact, GotoNode(join_bb), Union{}, line)
506+
insert_node_here!(compact, NewInstruction(GotoNode(join_bb), Union{}, line))
503507
finish_current_bb!(compact, 0)
504508
end
505509

506510
# We're now in the join block.
507-
compact.ssa_rename[compact.idx-1] = insert_node_here!(compact, pn, typ, line)
511+
compact.ssa_rename[compact.idx-1] = insert_node_here!(compact,
512+
NewInstruction(pn, typ, line))
508513
nothing
509514
end
510515

@@ -553,8 +558,9 @@ function batch_inline!(todo::Vector{Pair{Int, Any}}, ir::IRCode, linetable::Vect
553558
# At the moment we will allow globalrefs in argument position, turn those into ssa values
554559
for aidx in 1:length(argexprs)
555560
aexpr = argexprs[aidx]
556-
if isa(aexpr, GlobalRef) || isa(aexpr, Expr)
557-
argexprs[aidx] = insert_node_here!(compact, aexpr, compact_exprtype(compact, aexpr), compact.result[idx][:line])
561+
if isa(aexpr, Expr)
562+
argexprs[aidx] = insert_node_here!(compact,
563+
NewInstruction(aexpr, compact_exprtype(compact, aexpr), compact.result[idx][:line]))
558564
end
559565
end
560566
if isa(item, InliningTodo)
@@ -630,7 +636,7 @@ function rewrite_apply_exprargs!(ir::IRCode, todo::Vector{Pair{Int, Any}}, idx::
630636
new_argexpr = quoted(def_atype.val)
631637
else
632638
new_call = Expr(:call, GlobalRef(Core, :getfield), def, j)
633-
new_argexpr = insert_node!(ir, idx, def_atype, new_call)
639+
new_argexpr = insert_node!(ir, idx, NewInstruction(new_call, def_atype))
634640
end
635641
push!(new_argexprs, new_argexpr)
636642
push!(new_atypes, def_atype)
@@ -640,7 +646,7 @@ function rewrite_apply_exprargs!(ir::IRCode, todo::Vector{Pair{Int, Any}}, idx::
640646
for i = 1:length(thisarginfo.each)
641647
call = thisarginfo.each[i]
642648
new_stmt = Expr(:call, argexprs[2], def, state...)
643-
state1 = insert_node!(ir, idx, call.rt, new_stmt)
649+
state1 = insert_node!(ir, idx, NewInstruction(new_stmt, call.rt))
644650
new_sig = with_atype(call_sig(ir, new_stmt)::Signature)
645651
info = call.info
646652
handled = false
@@ -661,12 +667,14 @@ function rewrite_apply_exprargs!(ir::IRCode, todo::Vector{Pair{Int, Any}}, idx::
661667
end
662668
if i != length(thisarginfo.each)
663669
valT = getfield_tfunc(call.rt, Const(1))
664-
val_extracted = insert_node!(ir, idx, valT,
665-
Expr(:call, GlobalRef(Core, :getfield), state1, 1))
670+
val_extracted = insert_node!(ir, idx, NewInstruction(
671+
Expr(:call, GlobalRef(Core, :getfield), state1, 1),
672+
valT))
666673
push!(new_argexprs, val_extracted)
667674
push!(new_atypes, valT)
668-
state_extracted = insert_node!(ir, idx, getfield_tfunc(call.rt, Const(2)),
669-
Expr(:call, GlobalRef(Core, :getfield), state1, 2))
675+
state_extracted = insert_node!(ir, idx, NewInstruction(
676+
Expr(:call, GlobalRef(Core, :getfield), state1, 2),
677+
getfield_tfunc(call.rt, Const(2))))
670678
state = Core.svec(state_extracted)
671679
end
672680
end
@@ -933,7 +941,7 @@ function inline_splatnew!(ir::IRCode, idx::Int)
933941
for j = 1:n
934942
atype = getfield_tfunc(tt, Const(j))
935943
new_call = Expr(:call, Core.getfield, tup, j)
936-
new_argexpr = insert_node!(ir, idx, atype, new_call)
944+
new_argexpr = insert_node!(ir, idx, NewInstruction(new_call, atype))
937945
push!(new_argexprs, new_argexpr)
938946
end
939947
stmt.head = :new
@@ -1081,17 +1089,32 @@ function narrow_opaque_closure!(ir::IRCode, stmt::Expr, @nospecialize(info), sta
10811089
end
10821090
end
10831091

1092+
# As a matter of convenience, this pass also computes effect-freenes.
1093+
# For primitives, we do that right here. For proper calls, we will
1094+
# discover this when we consult the caches.
1095+
function check_effect_free!(ir::IRCode, @nospecialize(stmt), @nospecialize(calltype), idx::Int)
1096+
if stmt_effect_free(stmt, calltype, ir, ir.sptypes)
1097+
ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE
1098+
end
1099+
end
1100+
10841101
# Handles all analysis and inlining of intrinsics and builtins. In particular,
10851102
# this method does not access the method table or otherwise process generic
10861103
# functions.
10871104
function process_simple!(ir::IRCode, todo::Vector{Pair{Int, Any}}, idx::Int, state::InliningState)
10881105
stmt = ir.stmts[idx][:inst]
1089-
stmt isa Expr || return nothing
1090-
if stmt.head === :splatnew
1091-
inline_splatnew!(ir, idx)
1106+
calltype = ir.stmts[idx][:type]
1107+
if !(stmt isa Expr)
1108+
check_effect_free!(ir, stmt, calltype, idx)
10921109
return nothing
1093-
elseif stmt.head === :new_opaque_closure
1094-
narrow_opaque_closure!(ir, stmt, ir.stmts[idx][:info], state)
1110+
end
1111+
if stmt.head !== :call
1112+
if stmt.head === :splatnew
1113+
inline_splatnew!(ir, idx)
1114+
elseif stmt.head === :new_opaque_closure
1115+
narrow_opaque_closure!(ir, stmt, ir.stmts[idx][:info], state)
1116+
end
1117+
check_effect_free!(ir, stmt, calltype, idx)
10951118
return nothing
10961119
end
10971120

@@ -1105,13 +1128,14 @@ function process_simple!(ir::IRCode, todo::Vector{Pair{Int, Any}}, idx::Int, sta
11051128
sig === nothing && return nothing
11061129

11071130
# Check if we match any of the early inliners
1108-
calltype = ir.stmts[idx][:type]
11091131
res = early_inline_special_case(ir, sig, stmt, state.params, calltype)
11101132
if res !== nothing
11111133
ir.stmts[idx][:inst] = res
11121134
return nothing
11131135
end
11141136

1137+
check_effect_free!(ir, stmt, calltype, idx)
1138+
11151139
if sig.f !== Core.invoke && is_builtin(sig)
11161140
# No inlining for builtins (other invoke/apply)
11171141
return nothing
@@ -1121,8 +1145,10 @@ function process_simple!(ir::IRCode, todo::Vector{Pair{Int, Any}}, idx::Int, sta
11211145

11221146
# Special case inliners for regular functions
11231147
if late_inline_special_case!(ir, sig, idx, stmt, state.params) || is_return_type(sig.f)
1148+
check_effect_free!(ir, ir.stmts[idx][:inst], calltype, idx)
11241149
return nothing
11251150
end
1151+
11261152
return sig
11271153
end
11281154

@@ -1256,6 +1282,7 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState)
12561282
ir.stmts[idx][:inst] = quoted(calltype.val)
12571283
continue
12581284
end
1285+
ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE
12591286
info = info.info
12601287
end
12611288

@@ -1306,7 +1333,7 @@ end
13061333
function mk_tuplecall!(compact::IncrementalCompact, args::Vector{Any}, line_idx::Int32)
13071334
e = Expr(:call, TOP_TUPLE, args...)
13081335
etyp = tuple_tfunc(Any[compact_exprtype(compact, args[i]) for i in 1:length(args)])
1309-
return insert_node_here!(compact, e, etyp, line_idx)
1336+
return insert_node_here!(compact, NewInstruction(e, etyp, line_idx))
13101337
end
13111338

13121339
function linear_inline_eligible(ir::IRCode)
@@ -1371,7 +1398,7 @@ function late_inline_special_case!(ir::IRCode, sig::Signature, idx::Int, stmt::E
13711398
return true
13721399
end
13731400
cmp_call = Expr(:call, GlobalRef(Core, :(===)), stmt.args[2], stmt.args[3])
1374-
cmp_call_ssa = insert_node!(ir, idx, Bool, cmp_call)
1401+
cmp_call_ssa = insert_node!(ir, idx, effect_free(NewInstruction(cmp_call, Bool)))
13751402
not_call = Expr(:call, GlobalRef(Core.Intrinsics, :not_int), cmp_call_ssa)
13761403
ir[SSAValue(idx)] = not_call
13771404
return true

0 commit comments

Comments
 (0)