diff --git a/base/boot.jl b/base/boot.jl index 2dc19528e178e..b84f9b76838e0 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -459,6 +459,7 @@ eval(Core, quote ReturnNode(@nospecialize val) = $(Expr(:new, :ReturnNode, :val)) ReturnNode() = $(Expr(:new, :ReturnNode)) # unassigned val indicates unreachable GotoIfNot(@nospecialize(cond), dest::Int) = $(Expr(:new, :GotoIfNot, :cond, :dest)) + EnterNode(dest::Int) = $(Expr(:new, :EnterNode, :dest)) LineNumberNode(l::Int) = $(Expr(:new, :LineNumberNode, :l, nothing)) function LineNumberNode(l::Int, @nospecialize(f)) isa(f, String) && (f = Symbol(f)) @@ -626,12 +627,12 @@ module IR export CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, NewvarNode, SSAValue, SlotNumber, Argument, PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, - Const, PartialStruct, InterConditional + Const, PartialStruct, InterConditional, EnterNode using Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, NewvarNode, SSAValue, SlotNumber, Argument, PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, - Const, PartialStruct, InterConditional + Const, PartialStruct, InterConditional, EnterNode end # module IR @@ -965,4 +966,7 @@ arraysize(a::Array) = a.size arraysize(a::Array, i::Int) = sle_int(i, nfields(a.size)) ? getfield(a.size, i) : 1 export arrayref, arrayset, arraysize, const_arrayref +# For convenience +EnterNode(old::EnterNode, new_dest::Int) = EnterNode(new_dest) + ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Core, true) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 1b860bb8aee6a..f284928b17135 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -3102,8 +3102,8 @@ function update_exc_bestguess!(@nospecialize(exct), frame::InferenceState, š•ƒ handler_frame = frame.handlers[cur_hand] if !āŠ‘(š•ƒā‚š, exct, handler_frame.exct) handler_frame.exct = tmerge(š•ƒā‚š, handler_frame.exct, exct) - enter = frame.src.code[handler_frame.enter_idx]::Expr - exceptbb = block_for_inst(frame.cfg, enter.args[1]::Int) + enter = frame.src.code[handler_frame.enter_idx]::EnterNode + exceptbb = block_for_inst(frame.cfg, enter.catch_dest) push!(frame.ip, exceptbb) end end @@ -3114,8 +3114,8 @@ function propagate_to_error_handler!(currstate::VarTable, frame::InferenceState, # exception handler, BEFORE applying any state changes. cur_hand = frame.handler_at[frame.currpc][1] if cur_hand != 0 - enter = frame.src.code[frame.handlers[cur_hand].enter_idx]::Expr - exceptbb = block_for_inst(frame.cfg, enter.args[1]::Int) + enter = frame.src.code[frame.handlers[cur_hand].enter_idx]::EnterNode + exceptbb = block_for_inst(frame.cfg, enter.catch_dest) if update_bbstate!(š•ƒįµ¢, frame, exceptbb, currstate) push!(frame.ip, exceptbb) end @@ -3256,8 +3256,9 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) end ssavaluetypes[frame.currpc] = Any @goto find_next_bb - elseif isexpr(stmt, :enter) + elseif isa(stmt, EnterNode) ssavaluetypes[currpc] = Any + add_curr_ssaflag!(frame, IR_FLAG_NOTHROW) @goto fallthrough elseif isexpr(stmt, :leave) ssavaluetypes[currpc] = Any diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index dd3b10405fccf..885a6dbab65b7 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -362,8 +362,8 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) # start from all :enter statements and record the location of the try for pc = 1:n stmt = code[pc] - if isexpr(stmt, :enter) - l = stmt.args[1]::Int + if isa(stmt, EnterNode) + l = stmt.catch_dest push!(handlers, TryCatchFrame(Bottom, pc)) handler_id = length(handlers) handler_at[pc + 1] = (handler_id, 0) @@ -396,15 +396,15 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) elseif isa(stmt, ReturnNode) @assert !isdefined(stmt, :val) || cur_stacks[1] == 0 "unbalanced try/catch" break + elseif isa(stmt, EnterNode) + l = stmt.catch_dest + # We assigned a handler number above. Here we just merge that + # with out current handler information. + handler_at[l] = (cur_stacks[1], handler_at[l][2]) + cur_stacks = (handler_at[pcĀ“][1], cur_stacks[2]) elseif isa(stmt, Expr) head = stmt.head - if head === :enter - l = stmt.args[1]::Int - # We assigned a handler number above. Here we just merge that - # with out current handler information. - handler_at[l] = (cur_stacks[1], handler_at[l][2]) - cur_stacks = (handler_at[pcĀ“][1], cur_stacks[2]) - elseif head === :leave + if head === :leave l = 0 for j = 1:length(stmt.args) arg = stmt.args[j] @@ -415,7 +415,7 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) if enter_stmt === nothing continue end - @assert isexpr(enter_stmt, :enter) "malformed :leave" + @assert isa(enter_stmt, EnterNode) "malformed :leave" end l += 1 end diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index b0a4dfd1ced8d..54a3823cd2f26 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -270,6 +270,7 @@ function stmt_effect_flags(š•ƒā‚’::AbstractLattice, @nospecialize(stmt), @nospe isa(stmt, PiNode) && return (true, true, true) isa(stmt, PhiNode) && return (true, true, true) isa(stmt, ReturnNode) && return (true, false, true) + isa(stmt, EnterNode) && return (true, false, true) isa(stmt, GotoNode) && return (true, false, true) isa(stmt, GotoIfNot) && return (true, false, āŠ‘(š•ƒā‚’, argextype(stmt.cond, src), Bool)) if isa(stmt, GlobalRef) @@ -761,7 +762,7 @@ end function ((; sv)::ScanStmt)(inst::Instruction, lstmt::Int, bb::Int) stmt = inst[:stmt] - if isexpr(stmt, :enter) + if isa(stmt, EnterNode) # try/catch not yet modeled give_up_refinements!(sv) return nothing @@ -971,8 +972,8 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) expr = nothing end code[i] = expr - elseif isexpr(expr, :enter) - catchdest = expr.args[1]::Int + elseif isa(expr, EnterNode) + catchdest = expr.catch_dest if catchdest in sv.unreachable cfg_delete_edge!(sv.cfg, block_for_inst(sv.cfg, i), block_for_inst(sv.cfg, catchdest)) code[i] = nothing @@ -1239,12 +1240,6 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp return cost elseif head === :copyast return 100 - elseif head === :enter - # try/catch is a couple function calls, - # but don't inline functions with try/catch - # since these aren't usually performance-sensitive functions, - # and llvm is more likely to miscompile them when these functions get large - return typemax(Int) end return 0 end @@ -1263,6 +1258,12 @@ function statement_or_branch_cost(@nospecialize(stmt), line::Int, src::Union{Cod thiscost = dst(stmt.label) < line ? 40 : 0 elseif stmt isa GotoIfNot thiscost = dst(stmt.dest) < line ? 40 : 0 + elseif stmt isa EnterNode + # try/catch is a couple function calls, + # but don't inline functions with try/catch + # since these aren't usually performance-sensitive functions, + # and llvm is more likely to miscompile them when these functions get large + thiscost = typemax(Int) end return thiscost end @@ -1359,19 +1360,19 @@ function renumber_ir_elements!(body::Vector{Any}, ssachangemap::Vector{Int}, lab i += 1 end end + elseif isa(el, EnterNode) + tgt = el.catch_dest + was_deleted = labelchangemap[tgt] == typemin(Int) + if was_deleted + body[i] = nothing + else + body[i] = EnterNode(el, tgt + labelchangemap[tgt]) + end elseif isa(el, Expr) if el.head === :(=) && el.args[2] isa Expr el = el.args[2]::Expr end - if el.head === :enter - tgt = el.args[1]::Int - was_deleted = labelchangemap[tgt] == typemin(Int) - if was_deleted - body[i] = nothing - else - el.args[1] = tgt + labelchangemap[tgt] - end - elseif !is_meta_expr_head(el.head) + if !is_meta_expr_head(el.head) args = el.args for i = 1:length(args) el = args[i] diff --git a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl index c05f3f53837a7..e4ea159ac8824 100644 --- a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl +++ b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl @@ -652,7 +652,7 @@ function analyze_escapes(ir::IRCode, nargs::Int, š•ƒā‚’::AbstractLattice, get_e elseif is_meta_expr_head(head) # meta expressions doesn't account for any usages continue - elseif head === :enter || head === :leave || head === :the_exception || head === :pop_exception + elseif head === :leave || head === :the_exception || head === :pop_exception # ignore these expressions since escapes via exceptions are handled by `escape_exception!` # `escape_exception!` conservatively propagates `AllEscape` anyway, # and so escape information imposed on `:the_exception` isn't computed @@ -666,6 +666,9 @@ function analyze_escapes(ir::IRCode, nargs::Int, š•ƒā‚’::AbstractLattice, get_e else add_conservative_changes!(astate, pc, stmt.args) end + elseif isa(stmt, EnterNode) + # Handled via escape_exception! + continue elseif isa(stmt, ReturnNode) if isdefined(stmt, :val) add_escape_change!(astate, stmt.val, ReturnEscape(pc)) @@ -728,10 +731,10 @@ function compute_frameinfo(ir::IRCode) for idx in 1:nstmts+nnewnodes inst = ir[SSAValue(idx)] stmt = inst[:stmt] - if isexpr(stmt, :enter) + if isa(stmt, EnterNode) @assert idx ā‰¤ nstmts "try/catch inside new_nodes unsupported" tryregions === nothing && (tryregions = UnitRange{Int}[]) - leave_block = stmt.args[1]::Int + leave_block = stmt.catch_dest leave_pc = first(ir.cfg.blocks[leave_block].stmts) push!(tryregions, idx:leave_pc) elseif arrayinfo !== nothing diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 2e250bade533c..326a4094e4134 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -459,8 +459,8 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector end elseif isa(stmtā€², GotoNode) stmtā€² = GotoNode(stmtā€².label + bb_offset) - elseif isa(stmtā€², Expr) && stmtā€².head === :enter - stmtā€² = Expr(:enter, stmtā€².args[1]::Int + bb_offset) + elseif isa(stmtā€², EnterNode) + stmtā€² = EnterNode(stmtā€², stmtā€².catch_dest + bb_offset) elseif isa(stmtā€², GotoIfNot) stmtā€² = GotoIfNot(stmtā€².cond, stmtā€².dest + bb_offset) elseif isa(stmtā€², PhiNode) @@ -710,8 +710,8 @@ function batch_inline!(ir::IRCode, todo::Vector{Pair{Int,Any}}, propagate_inboun end elseif isa(stmt, GotoNode) compact[idx] = GotoNode(state.bb_rename[stmt.label]) - elseif isa(stmt, Expr) && stmt.head === :enter - compact[idx] = Expr(:enter, state.bb_rename[stmt.args[1]::Int]) + elseif isa(stmt, EnterNode) + compact[idx] = EnterNode(stmt, state.bb_rename[stmt.catch_dest]) elseif isa(stmt, GotoIfNot) compact[idx] = GotoIfNot(stmt.cond, state.bb_rename[stmt.dest]) elseif isa(stmt, PhiNode) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 9b89b30854cdf..fb1219803b1fe 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -2,7 +2,7 @@ Core.PhiNode() = Core.PhiNode(Int32[], Any[]) -isterminator(@nospecialize(stmt)) = isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isa(stmt, ReturnNode) +isterminator(@nospecialize(stmt)) = isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isa(stmt, ReturnNode) || isa(stmt, EnterNode) struct CFG blocks::Vector{BasicBlock} @@ -60,16 +60,16 @@ block_for_inst(cfg::CFG, inst::Int) = block_for_inst(cfg.index, inst) # This is a fake dest to force the next stmt to start a bb idx < length(stmts) && push!(jump_dests, idx+1) push!(jump_dests, stmt.label) + elseif isa(stmt, EnterNode) + # :enter starts/ends a BB + push!(jump_dests, idx) + push!(jump_dests, idx+1) + # The catch block is a jump dest + push!(jump_dests, stmt.catch_dest) elseif isa(stmt, Expr) if stmt.head === :leave # :leave terminates a BB push!(jump_dests, idx+1) - elseif stmt.head === :enter - # :enter starts/ends a BB - push!(jump_dests, idx) - push!(jump_dests, idx+1) - # The catch block is a jump dest - push!(jump_dests, stmt.args[1]::Int) end end if isa(stmt, PhiNode) @@ -125,11 +125,11 @@ function compute_basic_blocks(stmts::Vector{Any}) push!(blocks[blockā€²].preds, num) push!(b.succs, blockā€²) end - elseif isexpr(terminator, :enter) + elseif isa(terminator, EnterNode) # :enter gets a virtual edge to the exception handler and # the exception handler gets a virtual edge from outside # the function. - blockā€² = block_for_inst(basic_block_index, terminator.args[1]::Int) + blockā€² = block_for_inst(basic_block_index, terminator.catch_dest) push!(blocks[blockā€²].preds, num) push!(blocks[blockā€²].preds, 0) push!(b.succs, blockā€²) @@ -456,6 +456,10 @@ struct UndefToken end; const UNDEF_TOKEN = UndefToken() isdefined(stmt, :val) || return OOB_TOKEN op == 1 || return OOB_TOKEN return stmt.val + elseif isa(stmt, EnterNode) + isdefined(stmt, :scope) || return OOB_TOKEN + op == 1 || return OOB_TOKEN + return stmt.scope elseif isa(stmt, PiNode) isdefined(stmt, :val) || return OOB_TOKEN op == 1 || return OOB_TOKEN @@ -510,6 +514,9 @@ end elseif isa(stmt, GotoIfNot) op == 1 || throw(BoundsError()) stmt = GotoIfNot(v, stmt.dest) + elseif isa(stmt, EnterNode) + op == 1 || throw(BoundsError()) + stmt = EnterNode(stmt.catch_dest, v) elseif isa(stmt, ReturnNode) op == 1 || throw(BoundsError()) stmt = typeof(stmt)(v) @@ -544,7 +551,7 @@ end function userefs(@nospecialize(x)) relevant = (isa(x, Expr) && is_relevant_expr(x)) || isa(x, GotoIfNot) || isa(x, ReturnNode) || isa(x, SSAValue) || isa(x, NewSSAValue) || - isa(x, PiNode) || isa(x, PhiNode) || isa(x, PhiCNode) || isa(x, UpsilonNode) + isa(x, PiNode) || isa(x, PhiNode) || isa(x, PhiCNode) || isa(x, UpsilonNode) || isa(x, EnterNode) return UseRefIterator(x, relevant) end @@ -1379,13 +1386,15 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr result[result_idx][:stmt] = GotoIfNot(cond, label) result_idx += 1 end + elseif cfg_transforms_enabled && isa(stmt, EnterNode) + label = bb_rename_succ[stmt.catch_dest] + @assert label > 0 + ssa_rename[idx] = SSAValue(result_idx) + result[result_idx][:stmt] = EnterNode(stmt, label) + result_idx += 1 elseif isa(stmt, Expr) stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, new_new_used_ssas, late_fixup, result_idx, do_rename_ssa, mark_refined!)::Expr - if cfg_transforms_enabled && isexpr(stmt, :enter) - label = bb_rename_succ[stmt.args[1]::Int] - @assert label > 0 - stmt.args[1] = label - elseif isexpr(stmt, :throw_undef_if_not) + if isexpr(stmt, :throw_undef_if_not) cond = stmt.args[2] if isa(cond, Bool) && cond === true # cond was folded to true - this statement @@ -1445,7 +1454,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr ssa_rename[idx] = SSAValue(result_idx) result[result_idx][:stmt] = stmt result_idx += 1 - elseif isa(stmt, ReturnNode) || isa(stmt, UpsilonNode) || isa(stmt, GotoIfNot) + elseif isa(stmt, ReturnNode) || isa(stmt, UpsilonNode) || isa(stmt, GotoIfNot) || isa(stmt, EnterNode) ssa_rename[idx] = SSAValue(result_idx) result[result_idx][:stmt] = renumber_ssa2!(stmt, ssa_rename, used_ssas, new_new_used_ssas, late_fixup, result_idx, do_rename_ssa, mark_refined!) result_idx += 1 diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index be072ca64e1c4..dd9a133f36802 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -96,7 +96,7 @@ function kill_terminator_edges!(irsv::IRInterpretationState, term_idx::Int, bb:: elseif isa(stmt, ReturnNode) # Nothing to do else - @assert !isexpr(stmt, :enter) + @assert !isa(stmt, EnterNode) kill_edge!(irsv, bb, bb+1) end end @@ -222,8 +222,8 @@ function process_terminator!(@nospecialize(stmt), bb::Int, bb_ip::BitSetBoundedM backedge || push!(bb_ip, stmt.dest) push!(bb_ip, bb+1) return backedge - elseif isexpr(stmt, :enter) - dest = stmt.args[1]::Int + elseif isa(stmt, EnterNode) + dest = stmt.catch_dest @assert dest > bb push!(bb_ip, dest) push!(bb_ip, bb+1) @@ -329,8 +329,7 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR delete!(ssa_refined, idx) end check_ret!(stmt, idx) - is_terminator_or_phi = (isa(stmt, PhiNode) || isa(stmt, GotoNode) || - isa(stmt, GotoIfNot) || isa(stmt, ReturnNode) || isexpr(stmt, :enter)) + is_terminator_or_phi = (isa(stmt, PhiNode) || isterminator(stmt)) if typ === Bottom && !(idx == lstmt && is_terminator_or_phi) return true end diff --git a/base/compiler/ssair/legacy.jl b/base/compiler/ssair/legacy.jl index 8f557f4fd2241..e8ed197ba5ce6 100644 --- a/base/compiler/ssair/legacy.jl +++ b/base/compiler/ssair/legacy.jl @@ -25,9 +25,8 @@ function inflate_ir!(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{A code[i] = GotoIfNot(stmt.cond, block_for_inst(cfg, stmt.dest)) elseif isa(stmt, PhiNode) code[i] = PhiNode(Int32[block_for_inst(cfg, Int(edge)) for edge in stmt.edges], stmt.values) - elseif isexpr(stmt, :enter) - stmt.args[1] = block_for_inst(cfg, stmt.args[1]::Int) - code[i] = stmt + elseif isa(stmt, EnterNode) + code[i] = EnterNode(stmt, block_for_inst(cfg, stmt.catch_dest)) end end nstmts = length(code) @@ -93,9 +92,8 @@ function replace_code_newstyle!(ci::CodeInfo, ir::IRCode) code[i] = GotoIfNot(stmt.cond, first(ir.cfg.blocks[stmt.dest].stmts)) elseif isa(stmt, PhiNode) code[i] = PhiNode(Int32[edge == 0 ? 0 : last(ir.cfg.blocks[edge].stmts) for edge in stmt.edges], stmt.values) - elseif isexpr(stmt, :enter) - stmt.args[1] = first(ir.cfg.blocks[stmt.args[1]::Int].stmts) - code[i] = stmt + elseif isa(stmt, EnterNode) + code[i] = EnterNode(stmt, first(ir.cfg.blocks[stmt.catch_dest].stmts)) end end end diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 9c6ed30dbbce8..8b3ae55fe7429 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -1911,7 +1911,7 @@ function legalize_bb_drop_pred!(ir::IRCode, bb::BasicBlock, bbidx::Int, bbs::Vec end ir[last_fallthrough_term_ssa] = nothing kill_edge!(bbs, last_fallthrough, terminator.dest) - elseif isexpr(terminator, :enter) + elseif isa(terminator, EnterNode) return false elseif isa(terminator, GotoNode) return true @@ -1923,8 +1923,6 @@ function legalize_bb_drop_pred!(ir::IRCode, bb::BasicBlock, bbidx::Int, bbs::Vec return true end -is_terminator(@nospecialize(stmt)) = isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isexpr(stmt, :enter) - function follow_map(map::Vector{Int}, idx::Int) while map[idx] ā‰  0 idx = map[idx] @@ -1992,7 +1990,7 @@ function cfg_simplify!(ir::IRCode) if length(bbs[succ].preds) == 1 && succ != 1 # Can't merge blocks with :enter terminator even if they # only have one successor. - if isexpr(ir[SSAValue(last(bb.stmts))][:stmt], :enter) + if isa(ir[SSAValue(last(bb.stmts))][:stmt], EnterNode) continue end # Prevent cycles by making sure we don't end up back at `idx` @@ -2068,7 +2066,7 @@ function cfg_simplify!(ir::IRCode) if bb_rename_succ[terminator.dest] == 0 push!(worklist, terminator.dest) end - elseif isexpr(terminator, :enter) + elseif isa(terminator, EnterNode) enteridx = terminator.args[1]::Int if bb_rename_succ[enteridx] == 0 push!(worklist, enteridx) diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index 67698fbc89aaa..7323c21ae56b4 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -67,8 +67,8 @@ function print_stmt(io::IO, idx::Int, @nospecialize(stmt), used::BitSet, maxleng join(io, (print_arg(i) for i = 3:length(stmt.args)), ", ") print(io, ")") # given control flow information, we prefer to print these with the basic block #, instead of the ssa % - elseif isexpr(stmt, :enter) && length((stmt::Expr).args) == 1 && (stmt::Expr).args[1] isa Int - print(io, "\$(Expr(:enter, #", (stmt::Expr).args[1]::Int, "))") + elseif isa(stmt, EnterNode) + print(io, "enter #", stmt.catch_dest, "") elseif stmt isa GotoNode print(io, "goto #", stmt.label) elseif stmt isa PhiNode @@ -154,11 +154,11 @@ end function should_print_ssa_type(@nospecialize node) if isa(node, Expr) - return !(node.head in (:gc_preserve_begin, :gc_preserve_end, :meta, :enter, :leave)) + return !(node.head in (:gc_preserve_begin, :gc_preserve_end, :meta, :leave)) end return !isa(node, PiNode) && !isa(node, GotoIfNot) && !isa(node, GotoNode) && !isa(node, ReturnNode) && - !isa(node, QuoteNode) + !isa(node, QuoteNode) && !isa(node, EnterNode) end function default_expr_type_printer(io::IO; @nospecialize(type), used::Bool, show_type::Bool=true, _...) @@ -565,10 +565,8 @@ end function statement_indices_to_labels(stmt, cfg::CFG) # convert statement index to labels, as expected by print_stmt - if stmt isa Expr - if stmt.head === :enter && length(stmt.args) == 1 && stmt.args[1] isa Int - stmt = Expr(:enter, block_for_inst(cfg, stmt.args[1]::Int)) - end + if stmt isa EnterNode + stmt = EnterNode(stmt, block_for_inst(cfg, stmt.catch_dest)) elseif isa(stmt, GotoIfNot) stmt = GotoIfNot(stmt.cond, block_for_inst(cfg, stmt.dest)) elseif stmt isa GotoNode diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index cbe167926014c..52e809f1c445b 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -468,10 +468,8 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) end result[inst_range[end]][:stmt] = GotoIfNot(terminator.cond, bb_rename[terminator.dest]) elseif !isa(terminator, ReturnNode) - if isa(terminator, Expr) - if terminator.head === :enter - terminator.args[1] = bb_rename[terminator.args[1]] - end + if isa(terminator, EnterNode) + result[inst_range[end]][:stmt] = EnterNode(terminator, bb_rename[terminator.catch_dest]) end if bb_rename[bb + 1] != new_bb + 1 # Add an explicit goto node @@ -576,10 +574,10 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, catch_entry_blocks = TryCatchRegion[] for idx in 1:length(code) stmt = code[idx] - if isexpr(stmt, :enter) + if isa(stmt, EnterNode) push!(catch_entry_blocks, TryCatchRegion( block_for_inst(cfg, idx), - block_for_inst(cfg, stmt.args[1]::Int))) + block_for_inst(cfg, stmt.catch_dest))) end end @@ -817,7 +815,7 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, enter_idx = idx while handler_at[enter_idx][1] != 0 (; enter_idx) = handlers[handler_at[enter_idx][1]] - leave_block = block_for_inst(cfg, code[enter_idx].args[1]::Int) + leave_block = block_for_inst(cfg, (code[enter_idx]::EnterNode).catch_dest) cidx = findfirst((; slot)::NewPhiCNode2->slot_id(slot)==id, new_phic_nodes[leave_block]) if cidx !== nothing node = thisdef ? UpsilonNode(thisval) : UpsilonNode() @@ -879,9 +877,9 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, else new_code[idx] = GotoIfNot(stmt.cond, new_dest) end - elseif isexpr(stmt, :enter) - except_bb = block_for_inst(cfg, stmt.args[1]::Int) - new_code[idx] = Expr(:enter, except_bb) + elseif isa(stmt, EnterNode) + except_bb = block_for_inst(cfg, stmt.catch_dest) + new_code[idx] = EnterNode(stmt, except_bb) ssavalmap[idx] = SSAValue(idx) # Slot to store token for pop_exception elseif isexpr(stmt, :leave) || isexpr(stmt, :(=)) || isa(stmt, ReturnNode) || isexpr(stmt, :meta) || isa(stmt, NewvarNode) diff --git a/base/compiler/ssair/verify.jl b/base/compiler/ssair/verify.jl index 801836c485e31..03e3ab0b0f03a 100644 --- a/base/compiler/ssair/verify.jl +++ b/base/compiler/ssair/verify.jl @@ -198,9 +198,9 @@ function verify_ir(ir::IRCode, print::Bool=true, @verify_error "Block $idx successors ($(block.succs)), does not match GotoIfNot terminator" error("") end - elseif isexpr(terminator, :enter) + elseif isa(terminator, EnterNode) @label enter_check - if length(block.succs) != 2 || (block.succs != Int[terminator.args[1], idx+1] && block.succs != Int[idx+1, terminator.args[1]]) + if length(block.succs) != 2 || (block.succs != Int[terminator.catch_dest, idx+1] && block.succs != Int[idx+1, terminator.catch_dest]) @verify_error "Block $idx successors ($(block.succs)), does not match :enter terminator" error("") end @@ -210,7 +210,7 @@ function verify_ir(ir::IRCode, print::Bool=true, # statement, until we can do proper CFG manipulations during compaction. for stmt_idx in first(block.stmts):last(block.stmts) stmt = ir[SSAValue(stmt_idx)][:stmt] - if isexpr(stmt, :enter) + if isa(stmt, EnterNode) terminator = stmt @goto enter_check end @@ -319,7 +319,7 @@ function verify_ir(ir::IRCode, print::Bool=true, error("") end end - elseif (isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isexpr(stmt, :enter)) && idx != last(ir.cfg.blocks[bb].stmts) + elseif (isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isa(stmt, EnterNode)) && idx != last(ir.cfg.blocks[bb].stmts) @verify_error "Terminator $idx in bb $bb is not the last statement in the block" error("") else @@ -373,7 +373,7 @@ function verify_ir(ir::IRCode, print::Bool=true, error() elseif isa(arg, SSAValue) enter_stmt = ir[arg::SSAValue][:stmt] - if !isa(enter_stmt, Nothing) && !isexpr(enter_stmt, :enter) + if !isa(enter_stmt, Nothing) && !isa(enter_stmt, EnterNode) @verify_error "Malformed :leave - argument ssavalue should point to `nothing` or :enter" error() end diff --git a/base/meta.jl b/base/meta.jl index 31fef1b9697e3..545b1dbf5243b 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -377,6 +377,9 @@ function _partially_inline!(@nospecialize(x), slot_replacements::Vector{Any}, x.dest + statement_offset, ) end + if isa(x, Core.EnterNode) + return Core.EnterNode(x, x.catch_dest + statement_offset) + end if isa(x, Expr) head = x.head if head === :static_parameter @@ -424,8 +427,6 @@ function _partially_inline!(@nospecialize(x), slot_replacements::Vector{Any}, static_param_values, slot_offset, statement_offset, boundscheck) x.args[2] += statement_offset - elseif head === :enter - x.args[1] += statement_offset elseif head === :isdefined arg = x.args[1] # inlining a QuoteNode or literal into `Expr(:isdefined, x)` is invalid, replace with true diff --git a/src/ast.c b/src/ast.c index e6adbdc4d5aac..8257309a07b2b 100644 --- a/src/ast.c +++ b/src/ast.c @@ -603,6 +603,11 @@ static jl_value_t *scm_to_julia_(fl_context_t *fl_ctx, value_t e, jl_module_t *m temp = scm_to_julia(fl_ctx, car_(cdr_(e)), mod); temp = jl_new_struct(jl_gotoifnot_type, ex, temp); } + else if (sym == jl_enter_sym) { + ex = scm_to_julia_(fl_ctx, car_(e), mod); + temp = jl_new_struct_uninit(jl_enternode_type); + jl_enternode_catch_dest(temp) = jl_unbox_long(ex); + } else if (sym == jl_newvar_sym) { ex = scm_to_julia_(fl_ctx, car_(e), mod); temp = jl_new_struct(jl_newvarnode_type, ex); diff --git a/src/builtins.c b/src/builtins.c index 88b91baa19279..78e66ef190437 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -2214,6 +2214,7 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin("LineInfoNode", (jl_value_t*)jl_lineinfonode_type); add_builtin("GotoNode", (jl_value_t*)jl_gotonode_type); add_builtin("GotoIfNot", (jl_value_t*)jl_gotoifnot_type); + add_builtin("EnterNode", (jl_value_t*)jl_enternode_type); add_builtin("ReturnNode", (jl_value_t*)jl_returnnode_type); add_builtin("PiNode", (jl_value_t*)jl_pinode_type); add_builtin("PhiNode", (jl_value_t*)jl_phinode_type); diff --git a/src/codegen.cpp b/src/codegen.cpp index bd5572ce7be16..bc7f30f49626d 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2943,18 +2943,16 @@ static void mark_volatile_vars(jl_array_t *stmts, SmallVectorImpl size_t slength = jl_array_dim0(stmts); for (int i = 0; i < (int)slength; i++) { jl_value_t *st = jl_array_ptr_ref(stmts, i); - if (jl_is_expr(st)) { - if (((jl_expr_t*)st)->head == jl_enter_sym) { - int last = jl_unbox_long(jl_exprarg(st, 0)); - std::set as = assigned_in_try(stmts, i + 1, last - 1); - for (int j = 0; j < (int)slength; j++) { - if (j < i || j > last) { - std::set::iterator it = as.begin(); - for (; it != as.end(); it++) { - if (local_var_occurs(jl_array_ptr_ref(stmts, j), *it)) { - jl_varinfo_t &vi = slots[*it]; - vi.isVolatile = true; - } + if (jl_is_enternode(st)) { + int last = jl_enternode_catch_dest(st); + std::set as = assigned_in_try(stmts, i + 1, last - 1); + for (int j = 0; j < (int)slength; j++) { + if (j < i || j > last) { + std::set::iterator it = as.begin(); + for (; it != as.end(); it++) { + if (local_var_occurs(jl_array_ptr_ref(stmts, j), *it)) { + jl_varinfo_t &vi = slots[*it]; + vi.isVolatile = true; } } } @@ -8522,14 +8520,11 @@ static jl_llvm_functions_t // targets. if (i + 2 <= stmtslen) branch_targets.insert(i + 2); - } else if (jl_is_expr(stmt)) { - if (((jl_expr_t*)stmt)->head == jl_enter_sym) { - branch_targets.insert(i + 1); - if (i + 2 <= stmtslen) - branch_targets.insert(i + 2); - int dest = jl_unbox_long(jl_array_ptr_ref(((jl_expr_t*)stmt)->args, 0)); - branch_targets.insert(dest); - } + } else if (jl_is_enternode(stmt)) { + branch_targets.insert(i + 1); + if (i + 2 <= stmtslen) + branch_targets.insert(i + 2); + branch_targets.insert(jl_enternode_catch_dest(stmt)); } else if (jl_is_gotonode(stmt)) { int dest = jl_gotonode_label(stmt); branch_targets.insert(dest); @@ -8575,7 +8570,6 @@ static jl_llvm_functions_t } ctx.noalias().aliasscope.current = aliasscopes[cursor]; jl_value_t *stmt = jl_array_ptr_ref(stmts, cursor); - jl_expr_t *expr = jl_is_expr(stmt) ? (jl_expr_t*)stmt : nullptr; if (jl_is_returnnode(stmt)) { jl_value_t *retexpr = jl_returnnode_value(stmt); if (retexpr == NULL) { @@ -8729,11 +8723,8 @@ static jl_llvm_functions_t find_next_stmt(cursor + 1); continue; } - else if (expr && expr->head == jl_enter_sym) { - jl_value_t **args = jl_array_data(expr->args, jl_value_t*); - - assert(jl_is_long(args[0])); - int lname = jl_unbox_long(args[0]); + else if (jl_is_enternode(stmt)) { + int lname = jl_enternode_catch_dest(stmt); // Save exception stack depth at enter for use in pop_exception Value *excstack_state = ctx.builder.CreateCall(prepare_call(jl_excstack_state_func)); diff --git a/src/interpreter.c b/src/interpreter.c index 7f8bd236819d5..313f5d9423fcc 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -489,6 +489,50 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, ssize_t id = ((jl_ssavalue_t*)phic)->id - 1; s->locals[jl_source_nslots(s->src) + id] = val; } + else if (jl_is_enternode(stmt)) { + jl_enter_handler(&__eh); + // This is a bit tricky, but supports the implementation of PhiC nodes. + // They are conceptually slots, but the slot to store to doesn't get explicitly + // mentioned in the store (aka the "UpsilonNode") (this makes them integrate more + // nicely with the rest of the SSA representation). In a compiler, we would figure + // out which slot to store to at compile time when we encounter the statement. We + // can't quite do that here, but we do something similar: We scan the catch entry + // block (the only place where PhiC nodes may occur) to find all the Upsilons we + // can possibly encounter. Then, we remember which slot they store to (we abuse the + // SSA value result array for this purpose). TODO: We could do this only the first + // time we encounter a given enter. + size_t catch_ip = jl_enternode_catch_dest(stmt) - 1; + while (catch_ip < ns) { + jl_value_t *phicnode = jl_array_ptr_ref(stmts, catch_ip); + if (!jl_is_phicnode(phicnode)) + break; + jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(phicnode, 0); + for (size_t i = 0; i < jl_array_nrows(values); ++i) { + jl_value_t *val = jl_array_ptr_ref(values, i); + assert(jl_is_ssavalue(val)); + size_t upsilon = ((jl_ssavalue_t*)val)->id - 1; + assert(jl_is_upsilonnode(jl_array_ptr_ref(stmts, upsilon))); + s->locals[jl_source_nslots(s->src) + upsilon] = jl_box_ssavalue(catch_ip + 1); + } + s->locals[jl_source_nslots(s->src) + catch_ip] = NULL; + catch_ip += 1; + } + // store current top of exception stack for restore in pop_exception. + s->locals[jl_source_nslots(s->src) + ip] = jl_box_ulong(jl_excstack_state()); + if (!jl_setjmp(__eh.eh_ctx, 1)) { + return eval_body(stmts, s, next_ip, toplevel); + } + jl_eh_restore_state(&__eh); + if (s->continue_at) { // means we reached a :leave expression + ip = s->continue_at; + s->continue_at = 0; + continue; + } + else { // a real exception + ip = catch_ip; + continue; + } + } else if (jl_is_expr(stmt)) { // Most exprs are allowed to end a BB by fall through jl_sym_t *head = ((jl_expr_t*)stmt)->head; @@ -518,51 +562,6 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, JL_GC_POP(); } } - else if (head == jl_enter_sym) { - jl_enter_handler(&__eh); - // This is a bit tricky, but supports the implementation of PhiC nodes. - // They are conceptually slots, but the slot to store to doesn't get explicitly - // mentioned in the store (aka the "UpsilonNode") (this makes them integrate more - // nicely with the rest of the SSA representation). In a compiler, we would figure - // out which slot to store to at compile time when we encounter the statement. We - // can't quite do that here, but we do something similar: We scan the catch entry - // block (the only place where PhiC nodes may occur) to find all the Upsilons we - // can possibly encounter. Then, we remember which slot they store to (we abuse the - // SSA value result array for this purpose). TODO: We could do this only the first - // time we encounter a given enter. - size_t catch_ip = jl_unbox_long(jl_exprarg(stmt, 0)) - 1; - while (catch_ip < ns) { - jl_value_t *phicnode = jl_array_ptr_ref(stmts, catch_ip); - if (!jl_is_phicnode(phicnode)) - break; - jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(phicnode, 0); - for (size_t i = 0; i < jl_array_nrows(values); ++i) { - jl_value_t *val = jl_array_ptr_ref(values, i); - assert(jl_is_ssavalue(val)); - size_t upsilon = ((jl_ssavalue_t*)val)->id - 1; - assert(jl_is_upsilonnode(jl_array_ptr_ref(stmts, upsilon))); - s->locals[jl_source_nslots(s->src) + upsilon] = jl_box_ssavalue(catch_ip + 1); - } - s->locals[jl_source_nslots(s->src) + catch_ip] = NULL; - catch_ip += 1; - } - // store current top of exception stack for restore in pop_exception. - s->locals[jl_source_nslots(s->src) + ip] = jl_box_ulong(jl_excstack_state()); - if (!jl_setjmp(__eh.eh_ctx, 1)) { - return eval_body(stmts, s, next_ip, toplevel); - } else { - jl_eh_restore_state(&__eh); - if (s->continue_at) { // means we reached a :leave expression - ip = s->continue_at; - s->continue_at = 0; - continue; - } - else { // a real exception - ip = catch_ip; - continue; - } - } - } else if (head == jl_leave_sym) { int hand_n_leave = 0; for (int i = 0; i < jl_expr_nargs(stmt); ++i) { diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index e840d2e8a40dc..f40b252180a65 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -52,6 +52,7 @@ XX(jl_binding_type) \ XX(jl_globalref_type) \ XX(jl_gotoifnot_type) \ + XX(jl_enternode_type) \ XX(jl_gotonode_type) \ XX(jl_initerror_type) \ XX(jl_int16_type) \ diff --git a/src/jltypes.c b/src/jltypes.c index 5666319860f6a..dddf88aececac 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3046,6 +3046,12 @@ void jl_init_types(void) JL_GC_DISABLED jl_svec(2, jl_any_type, jl_long_type), jl_emptysvec, 0, 0, 2); + jl_enternode_type = + jl_new_datatype(jl_symbol("EnterNode"), core, jl_any_type, jl_emptysvec, + jl_perm_symsvec(1, "catch_dest"), + jl_svec(1, jl_long_type), + jl_emptysvec, 0, 0, 1); + jl_returnnode_type = jl_new_datatype(jl_symbol("ReturnNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "val"), diff --git a/src/julia.h b/src/julia.h index 409da556ed957..9b69dd54dabc8 100644 --- a/src/julia.h +++ b/src/julia.h @@ -893,6 +893,7 @@ extern JL_DLLIMPORT jl_datatype_t *jl_globalref_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_linenumbernode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_gotonode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_gotoifnot_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_datatype_t *jl_enternode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_returnnode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_phinode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_pinode_type JL_GLOBALLY_ROOTED; @@ -1272,6 +1273,7 @@ STATIC_INLINE void jl_array_uint32_set(void *a, size_t i, uint8_t x) JL_NOTSAFEP #define jl_gotonode_label(x) (((intptr_t*)(x))[0]) #define jl_gotoifnot_cond(x) (((jl_value_t**)(x))[0]) #define jl_gotoifnot_label(x) (((intptr_t*)(x))[1]) +#define jl_enternode_catch_dest(x) (((intptr_t*)(x))[0]) #define jl_globalref_mod(s) (*(jl_module_t**)(s)) #define jl_globalref_name(s) (((jl_sym_t**)(s))[1]) #define jl_quotenode_value(x) (((jl_value_t**)x)[0]) @@ -1455,6 +1457,7 @@ static inline int jl_field_isconst(jl_datatype_t *st, int i) JL_NOTSAFEPOINT #define jl_is_gotonode(v) jl_typetagis(v,jl_gotonode_type) #define jl_is_gotoifnot(v) jl_typetagis(v,jl_gotoifnot_type) #define jl_is_returnnode(v) jl_typetagis(v,jl_returnnode_type) +#define jl_is_enternode(v) jl_typetagis(v,jl_enternode_type) #define jl_is_argument(v) jl_typetagis(v,jl_argument_type) #define jl_is_pinode(v) jl_typetagis(v,jl_pinode_type) #define jl_is_phinode(v) jl_typetagis(v,jl_phinode_type) diff --git a/src/staticdata.c b/src/staticdata.c index 6f21d0aa4aa2c..a687406dbe73a 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -98,7 +98,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 178 +#define NUM_TAGS 179 // An array of references that need to be restored from the sysimg // This is a manually constructed dual of the gvars array, which would be produced by codegen for Julia code, for C. @@ -132,6 +132,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_gotonode_type); INSERT_TAG(jl_quotenode_type); INSERT_TAG(jl_gotoifnot_type); + INSERT_TAG(jl_enternode_type); INSERT_TAG(jl_argument_type); INSERT_TAG(jl_returnnode_type); INSERT_TAG(jl_const_type); diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index f0a01eba61405..c71fd3ca9b265 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4416,9 +4416,9 @@ g41908() = f41908(Any[1][1]) # issue #42022 let x = Tuple{Int,Any}[ #= 1=# (0, Expr(:(=), Core.SlotNumber(3), 1)) - #= 2=# (0, Expr(:enter, 17)) + #= 2=# (0, EnterNode(17)) #= 3=# (2, Expr(:(=), Core.SlotNumber(3), 2.0)) - #= 4=# (2, Expr(:enter, 12)) + #= 4=# (2, EnterNode(12)) #= 5=# (4, Expr(:(=), Core.SlotNumber(3), '3')) #= 6=# (4, Core.GotoIfNot(Core.SlotNumber(2), 9)) #= 7=# (4, Expr(:leave, Core.SSAValue(4), Core.SSAValue(2))) diff --git a/test/compiler/interpreter_exec.jl b/test/compiler/interpreter_exec.jl index 47dc3778402bd..17d0e4f7e7eac 100644 --- a/test/compiler/interpreter_exec.jl +++ b/test/compiler/interpreter_exec.jl @@ -81,7 +81,7 @@ let m = Meta.@lower 1 + 1 QuoteNode(:b), GlobalRef(@__MODULE__, :test29262), # block 2 - Expr(:enter, 11), + Core.EnterNode(11), # block 3 Core.UpsilonNode(), Core.UpsilonNode(),