diff --git a/base/abstractarraymath.jl b/base/abstractarraymath.jl index 70c304d9060c1..0fd99e9b8a51f 100644 --- a/base/abstractarraymath.jl +++ b/base/abstractarraymath.jl @@ -468,7 +468,7 @@ function repeat_outer(a::AbstractMatrix, (m,n)::NTuple{2, Any}) R = d:d+p-1 for i=1:m c = (i-1)*o+1 - @inbounds b[c:c+o-1, R] = a + b[c:c+o-1, R] = a end end return b diff --git a/base/boot.jl b/base/boot.jl index 78b7daaf47d64..ef9b6f30ce5ac 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -199,7 +199,7 @@ export # type reflection <:, typeof, isa, typeassert, # method reflection - applicable, invoke, + applicable, invoke, invoke_split_effects, # constants nothing, Main @@ -439,12 +439,12 @@ function CodeInstance( mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, ipo_effects::UInt32, effects::UInt32, @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#), - relocatability::UInt8) + relocatability::UInt8, effect_assumptions::UInt32) return ccall(:jl_new_codeinst, Ref{CodeInstance}, - (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), + (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8, UInt32), mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, ipo_effects, effects, argescapes, - relocatability) + relocatability, effect_assumptions) end GlobalRef(m::Module, s::Symbol) = ccall(:jl_module_globalref, Ref{GlobalRef}, (Any, Any), m, s) Module(name::Symbol=:anonymous, std_imports::Bool=true, default_names::Bool=true) = ccall(:jl_f_new_module, Ref{Module}, (Any, Bool, Bool), name, std_imports, default_names) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index ed41b43ff95d9..12cb53f338806 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1969,6 +1969,43 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), return abstract_apply(interp, argtypes, si, sv, max_methods) elseif f === invoke return abstract_invoke(interp, arginfo, si, sv) + elseif f === invoke_split_effects + # First perform inference as usual. We may not need to use the precondition logic + # at all, (e.g. if we get a constant). + new_arginfo = ArgInfo(arginfo.fargs === nothing ? nothing : arginfo.fargs[3:end], + arginfo.argtypes[3:end]) + r = abstract_call(interp, new_arginfo, si, sv, max_methods) + # Find applicable preconditions that we may be able to use + which = arginfo.argtypes[2] + if isa(which, Const) + if isa(r.info, MethodMatchInfo) && length(r.info.results) == 1 && which.val === :nothrow + # Check for user-defined preconditions + match = r.info.results[1] + m = match.method + + for i = 1:2:length(m.preconditions) + i + 1 <= length(m.preconditions) || break + + cond = m.preconditions[1] + check = m.preconditions[2] + + isa(cond, EffectsOverride) || continue + + # TODO: Should depend on `which` + cond.nothrow || continue + + check_arginfo = ArgInfo(nothing, + Any[Const(check), arginfo.argtypes[3:end]...]) + rcheck = abstract_call(interp, check_arginfo, StmtInfo(true), sv, max_methods) + rcheck.rt === Bool || continue + is_foldable_nothrow(rcheck.effects) || continue + + # TODO: What to do if multiple preconditions match + return CallMeta(r.rt, r.effects, InvokeSplitEffectsInfo(r.info, check, cond, rcheck.info)) + end + end + end + return r elseif f === modifyfield! return abstract_modifyfield!(interp, argtypes, si, sv) elseif f === Core.finalizer diff --git a/base/compiler/cicache.jl b/base/compiler/cicache.jl index 8332777e6d5bc..69d54b44f0317 100644 --- a/base/compiler/cicache.jl +++ b/base/compiler/cicache.jl @@ -49,11 +49,11 @@ WorldView(wvc::WorldView, wr::WorldRange) = WorldView(wvc.cache, wr) WorldView(wvc::WorldView, args...) = WorldView(wvc.cache, args...) function haskey(wvc::WorldView{InternalCodeCache}, mi::MethodInstance) - return ccall(:jl_rettype_inferred, Any, (Any, UInt, UInt), mi, first(wvc.worlds), last(wvc.worlds)) !== nothing + return ccall(:jl_rettype_inferred, Any, (Any, UInt, UInt, UInt32), mi, first(wvc.worlds), last(wvc.worlds), 0) !== nothing end function get(wvc::WorldView{InternalCodeCache}, mi::MethodInstance, default) - r = ccall(:jl_rettype_inferred, Any, (Any, UInt, UInt), mi, first(wvc.worlds), last(wvc.worlds)) + r = ccall(:jl_rettype_inferred, Any, (Any, UInt, UInt, UInt32), mi, first(wvc.worlds), last(wvc.worlds), 0) if r === nothing return default end diff --git a/base/compiler/effects.jl b/base/compiler/effects.jl index 7d09769e5b31b..9caf0bd2e6036 100644 --- a/base/compiler/effects.jl +++ b/base/compiler/effects.jl @@ -240,6 +240,7 @@ struct EffectsOverride notaskstate::Bool inaccessiblememonly::Bool end +EffectsOverride() = decode_effects_override(0x00) function encode_effects_override(eo::EffectsOverride) e = 0x00 diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 338016e53517e..b550ee106efdf 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -21,11 +21,13 @@ end function InliningTodo(mi::MethodInstance, ir::IRCode, effects::Effects) return InliningTodo(mi, ir, linear_inline_eligible(ir), effects) end +copy(todo::InliningTodo) = InliningTodo(todo.mi, copy(todo.ir), todo.linear_inline_eligible, todo.effects) struct ConstantCase val::Any ConstantCase(@nospecialize val) = new(val) end +copy(cc::ConstantCase) = cc struct SomeCase val::Any @@ -37,6 +39,7 @@ struct InvokeCase effects::Effects info::CallInfo end +copy(ic::InvokeCase) = ic struct InliningCase sig # Type @@ -56,6 +59,14 @@ struct UnionSplit new(fully_covered, atype, cases, Int[]) end +struct EffectSplit + assume_case # Union{InliningTodo, InvokeCase, ConstantCase} + else_case # Union{InliningTodo, InvokeCase, ConstantCase} + check_case # Union{InliningTodo, InvokeCase, ConstantCase} + bbs::Vector{Int} + EffectSplit(@nospecialize(assume_case), @nospecialize(else_case), @nospecialize(check_case)) = new(assume_case, else_case, check_case, Int[]) +end + struct InliningEdgeTracker edges::Vector{Any} invokesig::Union{Nothing,Vector{Any}} @@ -211,41 +222,16 @@ function cfg_inline_item!(ir::IRCode, idx::Int, todo::InliningTodo, state::CFGIn return nothing end -function cfg_inline_unionsplit!(ir::IRCode, idx::Int, - (; fully_covered, #=atype,=# cases, bbs)::UnionSplit, - state::CFGInliningState, - params::OptimizationParams) +function ifelsesplit!(make_cases!, ir::IRCode, idx::Int, state::CFGInliningState, bbs::Vector{Int}) inline_into_block!(state, block_for_inst(ir, idx)) - from_bbs = Int[] + + # Split the current block - we're adding an if/else nest delete!(state.split_targets, length(state.new_cfg_blocks)) orig_succs = copy(state.new_cfg_blocks[end].succs) empty!(state.new_cfg_blocks[end].succs) - for i in 1:length(cases) - # The condition gets sunk into the previous block - # Add a block for the union-split body - push!(state.new_cfg_blocks, BasicBlock(StmtRange(idx, idx))) - cond_bb = length(state.new_cfg_blocks)-1 - push!(state.new_cfg_blocks[end].preds, cond_bb) - push!(state.new_cfg_blocks[cond_bb].succs, cond_bb+1) - case = cases[i].item - if isa(case, InliningTodo) - if !case.linear_inline_eligible - cfg_inline_item!(ir, idx, case, state, true) - end - end - push!(from_bbs, length(state.new_cfg_blocks)) - # TODO: Right now we unconditionally generate a fallback block - # in case of subtyping errors - This is probably unnecessary. - if i != length(cases) || (!fully_covered) - # This block will have the next condition or the final else case - push!(state.new_cfg_blocks, BasicBlock(StmtRange(idx, idx))) - push!(state.new_cfg_blocks[cond_bb].succs, length(state.new_cfg_blocks)) - push!(state.new_cfg_blocks[end].preds, cond_bb) - push!(bbs, length(state.new_cfg_blocks)) - end - end - # The edge from the fallback block. - fully_covered || push!(from_bbs, length(state.new_cfg_blocks)) + + from_bbs = make_cases!() + # This block will be the block everyone returns to push!(state.new_cfg_blocks, BasicBlock(StmtRange(idx, idx), from_bbs, orig_succs)) join_bb = length(state.new_cfg_blocks) @@ -256,6 +242,69 @@ function cfg_inline_unionsplit!(ir::IRCode, idx::Int, end end +function make_split_case!(ir::IRCode, idx::Int, state::CFGInliningState, @nospecialize(case), cond_bb) + # The condition gets sunk into the previous block + # Add a block for the union-split body + push!(state.new_cfg_blocks, BasicBlock(StmtRange(idx, idx))) + push!(state.new_cfg_blocks[end].preds, cond_bb) + push!(state.new_cfg_blocks[cond_bb].succs, cond_bb+1) + if isa(case, InliningTodo) + if !case.linear_inline_eligible + cfg_inline_item!(ir, idx, case, state, true) + end + end + return length(state.new_cfg_blocks) +end + + +function cfg_inline_unionsplit!(ir::IRCode, idx::Int, + (; fully_covered, #=atype,=# cases, bbs)::UnionSplit, + state::CFGInliningState, + params::OptimizationParams) + ifelsesplit!(ir, idx, state, bbs) do + from_bbs = Int[] + for i in 1:length(cases) + case = cases[i].item + next_bb = make_split_case!(ir, idx, state, case, length(state.new_cfg_blocks)) + cond_bb = next_bb - 1 + push!(from_bbs, next_bb) + + # TODO: Right now we unconditionally generate a fallback block + # in case of subtyping errors - This is probably unnecessary. + if i != length(cases) || (!fully_covered) + # This block will have the next condition or the final else case + push!(state.new_cfg_blocks, BasicBlock(StmtRange(idx, idx))) + push!(state.new_cfg_blocks[cond_bb].succs, length(state.new_cfg_blocks)) + push!(state.new_cfg_blocks[end].preds, cond_bb) + push!(bbs, length(state.new_cfg_blocks)) + end + end + # The edge from the fallback block. + fully_covered || push!(from_bbs, length(state.new_cfg_blocks)) + return from_bbs + end +end + +function cfg_inline_effectsplit!(ir::IRCode, idx::Int, (; check_case, assume_case, else_case, bbs)::EffectSplit, state::CFGInliningState, params::OptimizationParams) + inline_into_block!(state, block_for_inst(ir, idx)) + + # Handle check + if isa(check_case, InliningTodo) && !check_case.linear_inline_eligible + cfg_inline_item!(ir, idx, check_case, state, false) + end + + # Split into true and fale calses + ifelsesplit!(ir, idx, state, bbs) do + from_bbs = Int[] + cond_bb = length(state.new_cfg_blocks) + from_bb = make_split_case!(ir, idx, state, assume_case, cond_bb) + push!(from_bbs, from_bb) + push!(bbs, from_bb+1) + push!(from_bbs, make_split_case!(ir, idx, state, else_case, cond_bb)-1) + return from_bbs + end +end + function finish_cfg_inline!(state::CFGInliningState) new_range = (state.first_bb + 1):length(state.cfg.blocks) state.bb_rename[new_range] = let @@ -492,6 +541,21 @@ function fix_va_argexprs!(insert_node!::Inserter, inline_target::Union{IRCode, I return newargexprs end +function ir_inline_case_here!(@nospecialize(case), compact::IncrementalCompact, idx::Int, + argexprs::Vector{Any}, boundscheck::Symbol, todo_bbs::Vector{Tuple{Int, Int}}) + typ, line = compact.result[idx][:type], compact.result[idx][:line] + if isa(case, InliningTodo) + return ir_inline_item!(compact, idx, argexprs, case, boundscheck, todo_bbs) + elseif isa(case, InvokeCase) + inst = Expr(:invoke, case.invoke, argexprs...) + flag = flags_for_effects(case.effects) + return insert_node_here!(compact, NewInstruction(inst, typ, case.info, line, flag)) + else + case = case::ConstantCase + return case.val + end +end + """ ir_inline_unionsplit! @@ -602,16 +666,7 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int, argexprs:: end end end - if isa(case, InliningTodo) - val = ir_inline_item!(compact, idx, argexprsβ€², case, boundscheck, todo_bbs) - elseif isa(case, InvokeCase) - inst = Expr(:invoke, case.invoke, argexprsβ€²...) - flag = flags_for_effects(case.effects) - val = insert_node_here!(compact, NewInstruction(inst, typ, case.info, line, flag)) - else - case = case::ConstantCase - val = case.val - end + val = ir_inline_case_here!(case, compact, idx, argexprs, boundscheck, todo_bbs) if !isempty(compact.cfg_transform.result_bbs[bb].preds) push!(pn.edges, bb) push!(pn.values, val) @@ -637,12 +692,47 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int, argexprs:: return insert_node_here!(compact, NewInstruction(pn, typ, line)) end +function ir_inline_effectsplit!(compact::IncrementalCompact, idx::Int, + argexprs::Vector{Any}, + (; check_case, assume_case, else_case, bbs)::EffectSplit, + boundscheck::Symbol, todo_bbs::Vector{Tuple{Int, Int}}, + params::OptimizationParams) + stmt, typ, line = compact.result[idx][:inst], compact.result[idx][:type], compact.result[idx][:line] + next_cond_bb, join_bb = bbs + + # Inline the check + check_argexprs = [nothing, argexprs[3:end]...] + val = ir_inline_case_here!(check_case, compact, idx, check_argexprs, boundscheck, todo_bbs) + insert_node_here!(compact, NewInstruction(GotoIfNot(val, next_cond_bb), Union{}, line)) + finish_current_bb!(compact, 0) + + # Allocate Phi node for join block + pn = PhiNode() + + case_argexprs = argexprs[3:end] + val = ir_inline_case_here!(assume_case, compact, idx, case_argexprs, boundscheck, todo_bbs) + insert_node_here!(compact, NewInstruction(GotoNode(join_bb), Union{}, line)) + push!(pn.edges, next_cond_bb - 1) + push!(pn.values, val) + finish_current_bb!(compact, 0) + + val = ir_inline_case_here!(else_case, compact, idx, case_argexprs, boundscheck, todo_bbs) + push!(pn.edges, join_bb - 1) + push!(pn.values, val) + finish_current_bb!(compact, 0) + + # We're now in the join block. + return insert_node_here!(compact, NewInstruction(pn, typ, line)) +end + function batch_inline!(ir::IRCode, todo::Vector{Pair{Int,Any}}, propagate_inbounds::Bool, params::OptimizationParams) # Compute the new CFG first (modulo statement ranges, which will be computed below) state = CFGInliningState(ir) for (idx, item) in todo if isa(item, UnionSplit) cfg_inline_unionsplit!(ir, idx, item, state, params) + elseif isa(item, EffectSplit) + cfg_inline_effectsplit!(ir, idx, item, state, params) else item = item::InliningTodo # A linear inline does not modify the CFG @@ -698,6 +788,8 @@ function batch_inline!(ir::IRCode, todo::Vector{Pair{Int,Any}}, propagate_inboun compact.ssa_rename[old_idx] = ir_inline_item!(compact, idx, argexprs, item, boundscheck, state.todo_bbs) elseif isa(item, UnionSplit) compact.ssa_rename[old_idx] = ir_inline_unionsplit!(compact, idx, argexprs, item, boundscheck, state.todo_bbs, params) + elseif isa(item, EffectSplit) + compact.ssa_rename[old_idx] = ir_inline_effectsplit!(compact, idx, argexprs, item, boundscheck, state.todo_bbs, params) end compact[idx] = nothing refinish && finish_current_bb!(compact, 0) @@ -1084,70 +1176,67 @@ end function inline_apply!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stmt::Expr, sig::Signature, state::InliningState) - while sig.f === Core._apply_iterate - info = ir.stmts[idx][:info] - if isa(info, UnionSplitApplyCallInfo) - if length(info.infos) != 1 - # TODO: Handle union split applies? - new_info = info = NoCallInfo() - else - info = info.infos[1] - new_info = info.call - end - else - @assert info === NoCallInfo() + info = ir.stmts[idx][:info] + if isa(info, UnionSplitApplyCallInfo) + if length(info.infos) != 1 + # TODO: Handle union split applies? new_info = info = NoCallInfo() + else + info = info.infos[1] + new_info = info.call end - arg_start = 3 - argtypes = sig.argtypes - if arg_start > length(argtypes) + else + @assert info === NoCallInfo() + new_info = info = NoCallInfo() + end + arg_start = 3 + argtypes = sig.argtypes + if arg_start > length(argtypes) + return nothing + end + ft = argtypes[arg_start] + if ft isa Const && ft.val === Core.tuple + # if one argument is a tuple already, and the rest are empty, we can just return it + # e.g. rewrite `((t::Tuple)...,)` to `t` + nonempty_idx = 0 + 𝕃ₒ = optimizer_lattice(state.interp) + for i = (arg_start + 1):length(argtypes) + ti = argtypes[i] + βŠ‘(𝕃ₒ, ti, Tuple{}) && continue + if βŠ‘(𝕃ₒ, ti, Tuple) && nonempty_idx == 0 + nonempty_idx = i + continue + end + nonempty_idx = 0 + break + end + if nonempty_idx != 0 + ir.stmts[idx][:inst] = stmt.args[nonempty_idx] return nothing end - ft = argtypes[arg_start] - if ft isa Const && ft.val === Core.tuple - # if one argument is a tuple already, and the rest are empty, we can just return it - # e.g. rewrite `((t::Tuple)...,)` to `t` - nonempty_idx = 0 - 𝕃ₒ = optimizer_lattice(state.interp) - for i = (arg_start + 1):length(argtypes) - ti = argtypes[i] - βŠ‘(𝕃ₒ, ti, Tuple{}) && continue - if βŠ‘(𝕃ₒ, ti, Tuple) && nonempty_idx == 0 - nonempty_idx = i - continue - end - nonempty_idx = 0 - break - end - if nonempty_idx != 0 - ir.stmts[idx][:inst] = stmt.args[nonempty_idx] + end + # Try to figure out the signature of the function being called + # and if rewrite_apply_exprargs can deal with this form + arginfos = MaybeAbstractIterationInfo[] + for i = (arg_start + 1):length(argtypes) + thisarginfo = nothing + if !is_valid_type_for_apply_rewrite(argtypes[i], OptimizationParams(state.interp)) + isa(info, ApplyCallInfo) || return nothing + thisarginfo = info.arginfo[i-arg_start] + if thisarginfo === nothing || !thisarginfo.complete return nothing end end - # Try to figure out the signature of the function being called - # and if rewrite_apply_exprargs can deal with this form - arginfos = MaybeAbstractIterationInfo[] - for i = (arg_start + 1):length(argtypes) - thisarginfo = nothing - if !is_valid_type_for_apply_rewrite(argtypes[i], OptimizationParams(state.interp)) - isa(info, ApplyCallInfo) || return nothing - thisarginfo = info.arginfo[i-arg_start] - if thisarginfo === nothing || !thisarginfo.complete - return nothing - end - end - push!(arginfos, thisarginfo) - end - # Independent of whether we can inline, the above analysis allows us to rewrite - # this apply call to a regular call - argtypes = rewrite_apply_exprargs!(todo, - ir, idx, stmt, argtypes, arginfos, arg_start, state) - ir.stmts[idx][:info] = new_info - has_free_typevars(ft) && return nothing - f = singleton_type(ft) - sig = Signature(f, ft, argtypes) + push!(arginfos, thisarginfo) end - sig + # Independent of whether we can inline, the above analysis allows us to rewrite + # this apply call to a regular call + argtypes = rewrite_apply_exprargs!(todo, + ir, idx, stmt, argtypes, arginfos, arg_start, state) + ir.stmts[idx][:info] = new_info + has_free_typevars(ft) && return nothing + f = singleton_type(ft) + sig = Signature(f, ft, argtypes) end # TODO: this test is wrong if we start to handle Unions of function types later @@ -1260,9 +1349,15 @@ function process_simple!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stat sig = call_sig(ir, stmt) sig === nothing && return nothing - # Handle _apply_iterate - sig = inline_apply!(todo, ir, idx, stmt, sig, state) - sig === nothing && return nothing + while true + # Handle _apply_iterate + if sig.f === _apply_iterate + sig = inline_apply!(todo, ir, idx, stmt, sig, state) + sig === nothing && return nothing + continue + end + break + end # Check if we match any of the early inliners earlyres = early_inline_special_case(ir, stmt, rt, sig, state) @@ -1279,7 +1374,7 @@ function process_simple!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stat end end - if (sig.f !== Core.invoke && sig.f !== Core.finalizer && sig.f !== modifyfield!) && + if (sig.f !== Core.invoke && sig.f !== Core.finalizer && sig.f !== modifyfield! && sig.f !== Core.invoke_split_effects) && is_builtin(optimizer_lattice(state.interp), sig) # No inlining for builtins (other invoke/apply/typeassert/finalizer) return nothing @@ -1441,6 +1536,38 @@ function handle_call!(todo::Vector{Pair{Int,Any}}, all_covered, joint_effects) end +function handle_invoke_split_effects!(todo::Vector{Pair{Int,Any}}, + ir::IRCode, idx::Int, stmt::Expr, info::InvokeSplitEffectsInfo, flag::UInt8, sig::Signature, + state::InliningState) + + base_sig = Signature(singleton_type(sig.argtypes[3]), sig.argtypes[3], sig.argtypes[3:end]) + base_cases = compute_inlining_cases(info.info, flag, base_sig, state) + base_cases === nothing && return false + + check_sig = Signature(info.precond, Const(info.precond), Any[Const(info.precond), sig.argtypes[3:end]...]) + check_cases = compute_inlining_cases(info.check_info, flag, check_sig, state) + if check_cases === nothing + error("TODO: Fall back to regular inlining") + end + + base_cases, base_fully_covered, base_joint_effects = base_cases + check_cases, check_fully_covered, check_joint_effects = check_cases + + if !(base_fully_covered && check_fully_covered && length(base_cases) == 1 && length(check_cases) == 1) + error("TODO: Fall back to regular inlining") + end + + else_case = base_cases[1].item + if isa(else_case, InvokeCase) + assume_case = InvokeCase(else_case.invoke, apply_effects_overrides(else_case.effects, info.cond_effects), else_case.info) + else + assume_case = copy(else_case) + end + + push!(todo, idx=>EffectSplit(assume_case, else_case, check_cases[1].item)) + return true +end + function handle_match!(cases::Vector{InliningCase}, match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; @@ -1672,7 +1799,9 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState) end # handle special cased builtins - if isa(info, OpaqueClosureCallInfo) + if isa(info, InvokeSplitEffectsInfo) + handle_invoke_split_effects!(todo, ir, idx, stmt, info, flag, sig, state) + elseif isa(info, OpaqueClosureCallInfo) handle_opaque_closure_call!(todo, ir, idx, stmt, info, flag, sig, state) elseif isa(info, ModifyFieldInfo) handle_modifyfield!_call!(ir, idx, stmt, info, state) diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index 9f55d56181838..876ce02fc4dc8 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -186,6 +186,16 @@ struct OpaqueClosureCreateInfo <: CallInfo end end +""" + info::InvokeSplitEffectsInfo <: CallInfo +""" +struct InvokeSplitEffectsInfo <: CallInfo + info::CallInfo + precond::Any + cond_effects::EffectsOverride + check_info::CallInfo +end + # Stmt infos that are used by external consumers, but not by optimization. # These are not produced by default and must be explicitly opted into by # the AbstractInterpreter. diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 1198aa9fa6b35..70a2a43a54dbf 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -333,7 +333,7 @@ function CodeInstance(interp::AbstractInterpreter, result::InferenceResult, const_flags, first(valid_worlds), last(valid_worlds), # TODO: Actually do something with non-IPO effects encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.argescapes, - relocatability) + relocatability, UInt32(encode_effects_override(result.effect_assumptions))) end function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInstance, ci::CodeInfo) @@ -432,6 +432,28 @@ function cycle_fix_limited(@nospecialize(typ), sv::InferenceState) return typ end +function apply_effects_overrides(ipo_effects::Effects, override::EffectsOverride) + if is_effect_overridden(override, :consistent) + ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE) + end + if is_effect_overridden(override, :effect_free) + ipo_effects = Effects(ipo_effects; effect_free=ALWAYS_TRUE) + end + if is_effect_overridden(override, :nothrow) + ipo_effects = Effects(ipo_effects; nothrow=true) + end + if is_effect_overridden(override, :terminates_globally) + ipo_effects = Effects(ipo_effects; terminates=true) + end + if is_effect_overridden(override, :notaskstate) + ipo_effects = Effects(ipo_effects; notaskstate=true) + end + if is_effect_overridden(override, :inaccessiblememonly) + ipo_effects = Effects(ipo_effects; inaccessiblememonly=ALWAYS_TRUE) + end + return ipo_effects +end + function adjust_effects(sv::InferenceState) ipo_effects = sv.ipo_effects @@ -479,24 +501,7 @@ function adjust_effects(sv::InferenceState) def = sv.linfo.def if isa(def, Method) override = decode_effects_override(def.purity) - if is_effect_overridden(override, :consistent) - ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE) - end - if is_effect_overridden(override, :effect_free) - ipo_effects = Effects(ipo_effects; effect_free=ALWAYS_TRUE) - end - if is_effect_overridden(override, :nothrow) - ipo_effects = Effects(ipo_effects; nothrow=true) - end - if is_effect_overridden(override, :terminates_globally) - ipo_effects = Effects(ipo_effects; terminates=true) - end - if is_effect_overridden(override, :notaskstate) - ipo_effects = Effects(ipo_effects; notaskstate=true) - end - if is_effect_overridden(override, :inaccessiblememonly) - ipo_effects = Effects(ipo_effects; inaccessiblememonly=ALWAYS_TRUE) - end + ipo_effects = apply_effects_overrides(ipo_effects, override) end return ipo_effects diff --git a/base/compiler/types.jl b/base/compiler/types.jl index c53256c61ace9..9f5b7a39ebaea 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -70,6 +70,7 @@ mutable struct InferenceResult const linfo::MethodInstance const argtypes::Vector{Any} const overridden_by_const::BitVector + const effect_assumptions::EffectsOverride result # extended lattice element if inferred, nothing otherwise src # ::Union{CodeInfo, IRCode, OptimizationState} if inferred copy is available, nothing otherwise valid_worlds::WorldRange # if inference and optimization is finished @@ -77,18 +78,18 @@ mutable struct InferenceResult effects::Effects # if optimization is finished argescapes # ::ArgEscapeCache if optimized, nothing otherwise must_be_codeinf::Bool # if this must come out as CodeInfo or leaving it as IRCode is ok - function InferenceResult(linfo::MethodInstance, cache_argtypes::Vector{Any}, overridden_by_const::BitVector) + function InferenceResult(linfo::MethodInstance, cache_argtypes::Vector{Any}, overridden_by_const::BitVector, effect_assumptions::EffectsOverride=EffectsOverride()) # def = linfo.def # nargs = def isa Method ? Int(def.nargs) : 0 # @assert length(cache_argtypes) == nargs - return new(linfo, cache_argtypes, overridden_by_const, nothing, nothing, + return new(linfo, cache_argtypes, overridden_by_const, effect_assumptions, nothing, nothing, WorldRange(), Effects(), Effects(), nothing, true) end end -InferenceResult(linfo::MethodInstance, 𝕃::AbstractLattice=fallback_lattice) = - InferenceResult(linfo, matching_cache_argtypes(𝕃, linfo)...) -InferenceResult(linfo::MethodInstance, argtypes::ForwardableArgtypes, 𝕃::AbstractLattice=fallback_lattice) = - InferenceResult(linfo, matching_cache_argtypes(𝕃, linfo, argtypes)...) +InferenceResult(linfo::MethodInstance, 𝕃::AbstractLattice=fallback_lattice, effect_assumptions::EffectsOverride=EffectsOverride()) = + InferenceResult(linfo, matching_cache_argtypes(𝕃, linfo)..., effect_assumptions) +InferenceResult(linfo::MethodInstance, argtypes::ForwardableArgtypes, 𝕃::AbstractLattice=fallback_lattice, effect_assumptions::EffectsOverride=EffectsOverride()) = + InferenceResult(linfo, matching_cache_argtypes(𝕃, linfo, argtypes)..., effect_assumptions) """ inf_params::InferenceParams diff --git a/base/expr.jl b/base/expr.jl index 234085d9c9774..a67344b731c2c 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -711,7 +711,23 @@ macro assume_effects(args...) end (consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly) = (false, false, false, false, false, false, false, false) + preconditions = nothing + any_regular = false for org_setting in args[1:idx] + # Conditional effect assumptions + if isa(org_setting, Expr) && org_setting.head == :(&&) + any_conditional = true + setting = org_setting.args[2] + if setting !== QuoteNode(:nothrow) + error("Currently only :nothrow is supported in conditional effects") + end + preconditions === nothing && (preconditions = Expr(:call, Core.svec)) + push!(preconditions.args, Core.Compiler.EffectsOverride(false, false, true, false, false, false, false)) + push!(preconditions.args, org_setting.args[1]) + continue + end + # Regular effect assumptions + any_regular = true (setting, val) = compute_assumed_setting(org_setting) if setting === :consistent consistent = val @@ -738,15 +754,37 @@ macro assume_effects(args...) end end if is_function_def(inner) - return esc(pushmeta!(ex, :purity, - consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly)) + if any_regular + ex = pushmeta!(ex, :purity, consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly) + end + ex = esc(ex) + if preconditions !== nothing + argnames = collect_argnames(inner) + for i = 3:2:length(preconditions.args) + preconditions.args[i] = :(($(argnames...),)->$(preconditions.args[i])) + end + ex = quote + f = $(ex) + # HACK, there needs to be some builtin way to do this, this is just for demonstration + m = first(methods(f)) + m.preconditions = $preconditions + f + end + end + return ex elseif isexpr(ex, :macrocall) && ex.args[1] === Symbol("@ccall") + if preconditions !== nothing + throw(ArgumentError("preconditions current unsupported on @assume_effects @ccall")) + end ex.args[1] = GlobalRef(Base, Symbol("@ccall_effects")) insert!(ex.args, 3, Core.Compiler.encode_effects_override(Core.Compiler.EffectsOverride( consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, ))) return esc(ex) else # anonymous function case + if preconditions !== nothing + throw(ArgumentError("preconditions current unsupported on anonymous functions")) + end return Expr(:meta, Expr(:purity, consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly)) end @@ -917,6 +955,21 @@ end is_function_def(@nospecialize(ex)) = return isexpr(ex, :function) || is_short_function_def(ex) || isexpr(ex, :->) +function collect_argnames(@nospecialize(ex)) + if isexpr(ex, :function) + return map(ex.args[1].args) do arg + isa(arg, Symbol) && return arg + return (arg::Expr).args[1] + end + elseif is_short_function_def(ex) + error("TODO") + elseif isexpr(ex, :->) + error("TODO") + else + error("Not a function") + end +end + function findmeta(ex::Expr) if is_function_def(ex) body = ex.args[2]::Expr diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 63358f0798411..a2c6fa44a18cb 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -226,7 +226,7 @@ static void makeSafeName(GlobalObject &G) static void jl_ci_cache_lookup(const jl_cgparams_t &cgparams, jl_method_instance_t *mi, size_t world, jl_code_instance_t **ci_out, jl_code_info_t **src_out) { ++CICacheLookups; - jl_value_t *ci = cgparams.lookup(mi, world, world); + jl_value_t *ci = cgparams.lookup(mi, world, world, 0); JL_GC_PROMISE_ROOTED(ci); jl_code_instance_t *codeinst = NULL; if (ci != jl_nothing) { @@ -342,7 +342,7 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm params.tsctx, params.imaging, clone.getModuleUnlocked()->getDataLayout(), Triple(clone.getModuleUnlocked()->getTargetTriple())); - jl_llvm_functions_t decls = jl_emit_code(result_m, mi, src, codeinst->rettype, params); + jl_llvm_functions_t decls = jl_emit_code(result_m, mi, src, codeinst->rettype, codeinst->purity_assumptions, params); if (result_m) emitted[codeinst] = {std::move(result_m), std::move(decls)}; } @@ -2050,7 +2050,7 @@ void jl_add_optimization_passes_impl(LLVMPassManagerRef PM, int opt_level, int l // this is paired with jl_dump_function_ir, jl_dump_function_asm, jl_dump_method_asm in particular ways: // misuse will leak memory or cause read-after-free extern "C" JL_DLLEXPORT_CODEGEN -void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, size_t world, char getwrapper, char optimize, const jl_cgparams_t params) +void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, size_t world, uint32_t purity_assumptions, char getwrapper, char optimize, const jl_cgparams_t params) { if (jl_is_method(mi->def.method) && mi->def.method->source == NULL && mi->def.method->generator == NULL) { @@ -2070,7 +2070,7 @@ void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, siz src = jl_uncompress_ir(mi->def.method, NULL, (jl_value_t*)src); } else { - jl_value_t *ci = jl_rettype_inferred_addr(mi, world, world); + jl_value_t *ci = jl_rettype_inferred_addr(mi, world, world, purity_assumptions); if (ci != jl_nothing) { codeinst = (jl_code_instance_t*)ci; src = (jl_code_info_t*)jl_atomic_load_relaxed(&codeinst->inferred); @@ -2117,7 +2117,7 @@ void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, siz // No debug info = no variable names, // max debug info = llvm.dbg.declare/value intrinsics which clutter IR output output.debug_level = std::max(2, static_cast(jl_options.debug_level)); - auto decls = jl_emit_code(m, mi, src, jlrettype, output); + auto decls = jl_emit_code(m, mi, src, jlrettype, purity_assumptions, output); JL_UNLOCK(&jl_codegen_lock); // Might GC Function *F = NULL; diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 64e3fbd1af366..98f01f83f29eb 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -38,6 +38,7 @@ DECLARE_BUILTIN(fieldtype); DECLARE_BUILTIN(getfield); DECLARE_BUILTIN(ifelse); DECLARE_BUILTIN(invoke); +DECLARE_BUILTIN(invoke_split_effects); DECLARE_BUILTIN(is); DECLARE_BUILTIN(isa); DECLARE_BUILTIN(isdefined); diff --git a/src/builtins.c b/src/builtins.c index 4f75fd79eadcc..b51c9b065c884 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1408,6 +1408,12 @@ JL_CALLABLE(jl_f_invoke) return res; } +JL_CALLABLE(jl_f_invoke_split_effects) +{ + JL_NARGSV(invoke_split_effecst, 2); + return jl_apply_generic(args[1], &args[2], nargs - 2); +} + // Expr constructor for internal use ------------------------------------------ jl_expr_t *jl_exprn(jl_sym_t *head, size_t n) @@ -2022,6 +2028,9 @@ void jl_init_primitives(void) JL_GC_DISABLED jl_builtin_applicable = add_builtin_func("applicable", jl_f_applicable); jl_builtin_invoke = add_builtin_func("invoke", jl_f_invoke); + // effect splitting + jl_builtin_invoke_split_effects = add_builtin_func("invoke_split_effects", jl_f_invoke_split_effects); + // internal functions jl_builtin_apply_type = add_builtin_func("apply_type", jl_f_apply_type); jl_builtin__apply_iterate = add_builtin_func("_apply_iterate", jl_f__apply_iterate); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 2551bd02c36c0..388469be87acd 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -1955,7 +1955,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, const jl_cgval_t argv[3] = { cmp, lhs, rhs }; jl_cgval_t ret; if (modifyop) { - ret = emit_invoke(ctx, *modifyop, argv, 3, (jl_value_t*)jl_any_type); + ret = emit_invoke(ctx, *modifyop, argv, 3, (jl_value_t*)jl_any_type, 0); } else { Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, 3, julia_call); @@ -3844,7 +3844,7 @@ static jl_cgval_t emit_setfield(jl_codectx_t &ctx, emit_lockstate_value(ctx, strct, false); const jl_cgval_t argv[3] = { cmp, oldval, rhs }; if (modifyop) { - rhs = emit_invoke(ctx, *modifyop, argv, 3, (jl_value_t*)jl_any_type); + rhs = emit_invoke(ctx, *modifyop, argv, 3, (jl_value_t*)jl_any_type, 0); } else { Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, 3, julia_call); diff --git a/src/codegen-stubs.c b/src/codegen-stubs.c index de5f2a2770c04..4aa2d6530fad2 100644 --- a/src/codegen-stubs.c +++ b/src/codegen-stubs.c @@ -20,7 +20,7 @@ JL_DLLEXPORT void jl_extern_c_fallback(jl_function_t *f, jl_value_t *rt, jl_valu JL_DLLEXPORT jl_value_t *jl_dump_method_asm_fallback(jl_method_instance_t *linfo, size_t world, char emit_mc, char getwrapper, const char* asm_variant, const char *debuginfo, char binary) UNAVAILABLE JL_DLLEXPORT jl_value_t *jl_dump_function_ir_fallback(jl_llvmf_dump_t *dump, char strip_ir_metadata, char dump_module, const char *debuginfo) UNAVAILABLE -JL_DLLEXPORT void jl_get_llvmf_defn_fallback(jl_llvmf_dump_t *dump, jl_method_instance_t *linfo, size_t world, char getwrapper, char optimize, const jl_cgparams_t params) UNAVAILABLE +JL_DLLEXPORT void jl_get_llvmf_defn_fallback(jl_llvmf_dump_t *dump, jl_method_instance_t *linfo, size_t world, uint32_t purity_assumptions, char getwrapper, char optimize, const jl_cgparams_t params) UNAVAILABLE JL_DLLEXPORT void *jl_LLVMCreateDisasm_fallback(const char *TripleName, void *DisInfo, int TagType, void *GetOpInfo, void *SymbolLookUp) UNAVAILABLE JL_DLLEXPORT size_t jl_LLVMDisasmInstruction_fallback(void *DC, uint8_t *Bytes, uint64_t BytesSize, uint64_t PC, char *OutString, size_t OutStringSize) UNAVAILABLE diff --git a/src/codegen.cpp b/src/codegen.cpp index 9ec32746850f2..ed3d71867b3cd 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1636,6 +1636,8 @@ class jl_codectx_t { bool external_linkage = false; const jl_cgparams_t *params = NULL; + uint32_t purity_assumptions = 0; + std::vector> llvmcall_modules; jl_codectx_t(LLVMContext &llvmctx, jl_codegen_params_t ¶ms) @@ -1765,7 +1767,7 @@ static CallInst *emit_jlcall(jl_codectx_t &ctx, JuliaFunction<> *theFptr, Value static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2, Value *nullcheck1 = nullptr, Value *nullcheck2 = nullptr); static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t nargs, const jl_cgval_t *argv, bool is_promotable=false); -static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const jl_cgval_t *argv, size_t nargs, jl_value_t *rt); +static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const jl_cgval_t *argv, size_t nargs, jl_value_t *rt, uint32_t purity_assumptions); static Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p); static GlobalVariable *prepare_global_in(Module *M, GlobalVariable *G); @@ -3334,6 +3336,7 @@ static jl_llvm_functions_t jl_method_instance_t *lam, jl_code_info_t *src, jl_value_t *jlrettype, + uint32_t purity_assumptions, jl_codegen_params_t ¶ms); static void emit_hasnofield_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *type, jl_cgval_t name); @@ -4382,7 +4385,7 @@ static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, jl_value_t *jlretty return update_julia_type(ctx, mark_julia_type(ctx, ret, true, jlretty), inferred_retty); } -static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) +static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt, uint32_t purity_assumptions) { jl_value_t **args = (jl_value_t**)jl_array_data(ex->args); size_t arglen = jl_array_dim0(ex->args); @@ -4396,10 +4399,10 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) if (argv[i].typ == jl_bottom_type) return jl_cgval_t(); } - return emit_invoke(ctx, lival, argv.data(), nargs, rt); + return emit_invoke(ctx, lival, argv.data(), nargs, rt, purity_assumptions); } -static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const jl_cgval_t *argv, size_t nargs, jl_value_t *rt) +static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const jl_cgval_t *argv, size_t nargs, jl_value_t *rt, uint32_t purity_assumptions) { ++EmittedInvokes; bool handled = false; @@ -4423,9 +4426,19 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const } } else { - jl_value_t *ci = ctx.params->lookup(mi, ctx.world, ctx.world); // TODO: need to use the right pair world here + jl_value_t *ci = ctx.params->lookup(mi, ctx.world, ctx.world, purity_assumptions); // TODO: need to use the right pair world here jl_code_instance_t *codeinst = (jl_code_instance_t*)ci; if (ci != jl_nothing) { + if (// Nothrow assumption + (ctx.purity_assumptions & 0x04) != 0 && + // But callee throws + codeinst->rettype == jl_bottom_type && + // and terminates + (codeinst->ipo_purity_bits & (1 << 6))) { + // TODO: This probably should have been done on the julia side + ctx.builder.CreateUnreachable(); + return jl_cgval_t(); + } auto invoke = jl_atomic_load_acquire(&codeinst->invoke); // check if we know how to handle this specptr if (invoke == jl_fptr_const_return_addr) { @@ -5365,7 +5378,7 @@ static std::pair get_oc_function(jl_codectx_t &ctx, jl_met sigtype = jl_apply_tuple_type_v(jl_svec_data(sig_args), nsig); jl_method_instance_t *mi = jl_specializations_get_linfo(closure_method, sigtype, jl_emptysvec); - jl_code_instance_t *ci = (jl_code_instance_t*)jl_rettype_inferred_addr(mi, ctx.world, ctx.world); + jl_code_instance_t *ci = (jl_code_instance_t*)jl_rettype_inferred_addr(mi, ctx.world, ctx.world, 0); if (ci == NULL || (jl_value_t*)ci == jl_nothing) { JL_GC_POP(); @@ -5385,7 +5398,7 @@ static std::pair get_oc_function(jl_codectx_t &ctx, jl_met name_from_method_instance(mi), ctx.emission_context.tsctx, ctx.emission_context.imaging, jl_Module->getDataLayout(), Triple(jl_Module->getTargetTriple())); - jl_llvm_functions_t closure_decls = emit_function(closure_m, mi, ir, rettype, ctx.emission_context); + jl_llvm_functions_t closure_decls = emit_function(closure_m, mi, ir, rettype, 0, ctx.emission_context); assert(closure_decls.functionObject != "jl_fptr_sparam"); bool isspecsig = closure_decls.functionObject != "jl_fptr_args"; @@ -5496,7 +5509,10 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ assert(ssaidx_0based >= 0); jl_value_t *expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based); - return emit_invoke(ctx, ex, expr_t); + uint8_t stmt_flag = jl_array_uint8_ref(ctx.source->ssaflags, ssaidx_0based); + uint8_t IR_FLAG_NOTHROW = 0x01 << 5; + uint32_t overrides = (stmt_flag & IR_FLAG_NOTHROW) ? 0x04 : 0x00; + return emit_invoke(ctx, ex, expr_t, overrides); } else if (head == jl_invoke_modify_sym) { assert(ssaidx_0based >= 0); @@ -7205,6 +7221,7 @@ static jl_llvm_functions_t jl_method_instance_t *lam, jl_code_info_t *src, jl_value_t *jlrettype, + uint32_t purity_assumptions, jl_codegen_params_t ¶ms) { ++EmittedFunctions; @@ -7215,6 +7232,7 @@ static jl_llvm_functions_t JL_GC_PUSH2(&ctx.code, &vatyp); ctx.code = src->code; ctx.source = src; + ctx.purity_assumptions = purity_assumptions; std::map labels; bool toplevel = false; @@ -8260,7 +8278,9 @@ static jl_llvm_functions_t if (jl_is_returnnode(stmt)) { jl_value_t *retexpr = jl_returnnode_value(stmt); if (retexpr == NULL) { - CreateTrap(ctx.builder, false); + if (ctx.builder.GetInsertBlock() && !ctx.builder.GetInsertBlock()->getTerminator()) { + CreateTrap(ctx.builder, false); + } find_next_stmt(-1); continue; } @@ -8760,6 +8780,7 @@ jl_llvm_functions_t jl_emit_code( jl_method_instance_t *li, jl_code_info_t *src, jl_value_t *jlrettype, + uint32_t purity_assumptions, jl_codegen_params_t ¶ms) { JL_TIMING(CODEGEN, CODEGEN_LLVM); @@ -8770,7 +8791,7 @@ jl_llvm_functions_t jl_emit_code( compare_cgparams(params.params, &jl_default_cgparams)) && "functions compiled with custom codegen params must not be cached"); JL_TRY { - decls = emit_function(m, li, src, jlrettype, params); + decls = emit_function(m, li, src, jlrettype, purity_assumptions, params); auto stream = *jl_ExecutionEngine->get_dump_emitted_mi_name_stream(); if (stream) { jl_printf(stream, "%s\t", decls.specFunctionObject.c_str()); @@ -8844,7 +8865,7 @@ jl_llvm_functions_t jl_emit_codeinst( return jl_llvm_functions_t(); // failed } } - jl_llvm_functions_t decls = jl_emit_code(m, codeinst->def, src, codeinst->rettype, params); + jl_llvm_functions_t decls = jl_emit_code(m, codeinst->def, src, codeinst->rettype, codeinst->purity_assumptions, params); const std::string &specf = decls.specFunctionObject; const std::string &f = decls.functionObject; @@ -8966,7 +8987,7 @@ void jl_compile_workqueue( jl_create_ts_module(name_from_method_instance(codeinst->def), params.tsctx, params.imaging, original.getDataLayout(), Triple(original.getTargetTriple())); - result.second = jl_emit_code(result_m, codeinst->def, src, src->rettype, params); + result.second = jl_emit_code(result_m, codeinst->def, src, src->rettype, codeinst->purity_assumptions, params); result.first = std::move(result_m); } } diff --git a/src/gf.c b/src/gf.c index 935fd1e60db78..36be47231c63e 100644 --- a/src/gf.c +++ b/src/gf.c @@ -289,7 +289,7 @@ JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( jl_value_t *inferred_const, jl_value_t *inferred, int32_t const_flags, size_t min_world, size_t max_world, uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, - uint8_t relocatability); + uint8_t relocatability, uint32_t effect_assumptions); jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_args_t fptr) JL_GC_DISABLED { @@ -324,7 +324,7 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0, 0); jl_mi_cache_insert(mi, codeinst); jl_atomic_store_relaxed(&codeinst->specptr.fptr1, fptr); jl_atomic_store_relaxed(&codeinst->invoke, jl_fptr_args); @@ -436,11 +436,12 @@ JL_DLLEXPORT jl_value_t *jl_call_in_typeinf_world(jl_value_t **args, int nargs) return ret; } -JL_DLLEXPORT jl_value_t *jl_rettype_inferred(jl_method_instance_t *mi, size_t min_world, size_t max_world) JL_NOTSAFEPOINT +JL_DLLEXPORT jl_value_t *jl_rettype_inferred(jl_method_instance_t *mi, size_t min_world, size_t max_world, uint32_t purity_assumptions) JL_NOTSAFEPOINT { jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); while (codeinst) { - if (codeinst->min_world <= min_world && max_world <= codeinst->max_world) { + if (codeinst->min_world <= min_world && max_world <= codeinst->max_world && + codeinst->purity_assumptions == purity_assumptions) { jl_value_t *code = jl_atomic_load_relaxed(&codeinst->inferred); if (code && (code == jl_nothing || jl_ir_flag_inferred(code))) return (jl_value_t*)codeinst; @@ -449,7 +450,7 @@ JL_DLLEXPORT jl_value_t *jl_rettype_inferred(jl_method_instance_t *mi, size_t mi } return (jl_value_t*)jl_nothing; } -JL_DLLEXPORT jl_value_t *(*const jl_rettype_inferred_addr)(jl_method_instance_t *mi, size_t min_world, size_t max_world) JL_NOTSAFEPOINT = jl_rettype_inferred; +JL_DLLEXPORT jl_value_t *(*const jl_rettype_inferred_addr)(jl_method_instance_t *mi, size_t min_world, size_t max_world, uint32_t purity_assumptions) JL_NOTSAFEPOINT = jl_rettype_inferred; JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( @@ -467,7 +468,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( } codeinst = jl_new_codeinst( mi, rettype, NULL, NULL, - 0, min_world, max_world, 0, 0, jl_nothing, 0); + 0, min_world, max_world, 0, 0, jl_nothing, 0, 0); jl_mi_cache_insert(mi, codeinst); return codeinst; } @@ -477,7 +478,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_value_t *inferred_const, jl_value_t *inferred, int32_t const_flags, size_t min_world, size_t max_world, uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, - uint8_t relocatability + uint8_t relocatability, uint32_t effect_assumptions /*, jl_array_t *edges, int absolute_max*/) { jl_task_t *ct = jl_current_task; @@ -503,6 +504,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_atomic_store_relaxed(&codeinst->precompile, 0); jl_atomic_store_relaxed(&codeinst->next, NULL); codeinst->ipo_purity_bits = ipo_effects; + codeinst->purity_assumptions = effect_assumptions; jl_atomic_store_relaxed(&codeinst->purity_bits, effects); codeinst->argescapes = argescapes; codeinst->relocatability = relocatability; @@ -536,7 +538,7 @@ static int get_method_unspec_list(jl_typemap_entry_t *def, void *closure) if (!jl_is_svec(specializations)) { jl_method_instance_t *mi = (jl_method_instance_t*)specializations; assert(jl_is_method_instance(mi)); - if (jl_rettype_inferred(mi, world, world) == jl_nothing) + if (jl_rettype_inferred(mi, world, world, 0) == jl_nothing) jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); return 1; } @@ -546,7 +548,7 @@ static int get_method_unspec_list(jl_typemap_entry_t *def, void *closure) jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, i); if ((jl_value_t*)mi != jl_nothing) { assert(jl_is_method_instance(mi)); - if (jl_rettype_inferred(mi, world, world) == jl_nothing) + if (jl_rettype_inferred(mi, world, world, 0) == jl_nothing) jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); } } @@ -666,7 +668,7 @@ JL_DLLEXPORT void jl_set_typeinf_func(jl_value_t *f) size_t i, l; for (i = 0, l = jl_array_len(unspec); i < l; i++) { jl_method_instance_t *mi = (jl_method_instance_t*)jl_array_ptr_ref(unspec, i); - if (jl_rettype_inferred(mi, world, world) == jl_nothing) + if (jl_rettype_inferred(mi, world, world, 0) == jl_nothing) jl_type_infer(mi, world, 1); } JL_GC_POP(); @@ -2315,7 +2317,7 @@ jl_code_instance_t *jl_method_compiled(jl_method_instance_t *mi, size_t world) { jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); while (codeinst) { - if (codeinst->min_world <= world && world <= codeinst->max_world) { + if (codeinst->min_world <= world && world <= codeinst->max_world && codeinst->purity_assumptions == 0) { if (jl_atomic_load_relaxed(&codeinst->invoke) != NULL) return codeinst; } @@ -2432,7 +2434,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (unspec && (unspec_invoke = jl_atomic_load_acquire(&unspec->invoke))) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0, 0); void *unspec_fptr = jl_atomic_load_relaxed(&unspec->specptr.fptr); if (unspec_fptr) { // wait until invoke and specsigflags are properly set @@ -2459,7 +2461,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (!jl_code_requires_compiler(src, 0)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0, 0); jl_atomic_store_release(&codeinst->invoke, jl_fptr_interpret_call); jl_mi_cache_insert(mi, codeinst); record_precompile_statement(mi); @@ -2496,7 +2498,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t return ucache; } codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0, 0); void *unspec_fptr = jl_atomic_load_relaxed(&ucache->specptr.fptr); if (unspec_fptr) { // wait until invoke and specsigflags are properly set @@ -2745,10 +2747,10 @@ static jl_method_instance_t *jl_get_compile_hint_specialization(jl_tupletype_t * static void _generate_from_hint(jl_method_instance_t *mi, size_t world) { - jl_value_t *codeinst = jl_rettype_inferred(mi, world, world); + jl_value_t *codeinst = jl_rettype_inferred(mi, world, world, 0); if (codeinst == jl_nothing) { (void)jl_type_infer(mi, world, 1); - codeinst = jl_rettype_inferred(mi, world, world); + codeinst = jl_rettype_inferred(mi, world, world, 0); } if (codeinst != jl_nothing) { if (jl_atomic_load_relaxed(&((jl_code_instance_t*)codeinst)->invoke) == jl_fptr_const_return) @@ -2787,10 +2789,10 @@ JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tuplet jl_method_instance_t *mi2 = jl_specializations_get_linfo(mi->def.method, (jl_value_t*)types2, tpenv2); JL_GC_POP(); jl_atomic_store_relaxed(&mi2->precompiled, 1); - if (jl_rettype_inferred(mi2, world, world) == jl_nothing) + if (jl_rettype_inferred(mi2, world, world, 0) == jl_nothing) (void)jl_type_infer(mi2, world, 1); if (jl_typeinf_func && mi->def.method->primary_world <= tworld) { - if (jl_rettype_inferred(mi2, tworld, tworld) == jl_nothing) + if (jl_rettype_inferred(mi2, tworld, tworld, 0) == jl_nothing) (void)jl_type_infer(mi2, tworld, 1); } } @@ -2864,7 +2866,7 @@ STATIC_INLINE jl_value_t *_jl_invoke(jl_value_t *F, jl_value_t **args, uint32_t // manually inlined copy of jl_method_compiled jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mfunc->cache); while (codeinst) { - if (codeinst->min_world <= world && world <= codeinst->max_world) { + if (codeinst->min_world <= world && world <= codeinst->max_world && codeinst->purity_assumptions == 0) { jl_callptr_t invoke = jl_atomic_load_acquire(&codeinst->invoke); if (invoke != NULL) { jl_value_t *res = invoke(F, args, nargs, codeinst); diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 986f2c713e03d..bb02ef5b01471 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -455,7 +455,7 @@ jl_code_instance_t *jl_generate_fptr_impl(jl_method_instance_t *mi JL_PROPAGATES jl_code_instance_t *codeinst = NULL; JL_GC_PUSH2(&src, &codeinst); JL_LOCK(&jl_codegen_lock); // also disables finalizers, to prevent any unexpected recursion - jl_value_t *ci = jl_rettype_inferred_addr(mi, world, world); + jl_value_t *ci = jl_rettype_inferred_addr(mi, world, world, 0); if (ci != jl_nothing) codeinst = (jl_code_instance_t*)ci; if (codeinst) { @@ -638,7 +638,7 @@ jl_value_t *jl_dump_method_asm_impl(jl_method_instance_t *mi, size_t world, // whatever, that didn't work - use the assembler output instead jl_llvmf_dump_t llvmf_dump; - jl_get_llvmf_defn(&llvmf_dump, mi, world, getwrapper, true, jl_default_cgparams); + jl_get_llvmf_defn(&llvmf_dump, mi, world, 0, getwrapper, true, jl_default_cgparams); if (!llvmf_dump.F) return jl_an_empty_string; return jl_dump_function_asm(&llvmf_dump, emit_mc, asm_variant, debuginfo, binary, false); diff --git a/src/jitlayers.h b/src/jitlayers.h index c2332eaeb9bd6..958e1cae4ffe4 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -248,6 +248,7 @@ jl_llvm_functions_t jl_emit_code( jl_method_instance_t *mi, jl_code_info_t *src, jl_value_t *jlrettype, + uint32_t purity_assumptions, jl_codegen_params_t ¶ms); jl_llvm_functions_t jl_emit_codeinst( diff --git a/src/jltypes.c b/src/jltypes.c index f6c1febc7cf5c..741570248d41d 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3012,7 +3012,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_method_type = jl_new_datatype(jl_symbol("Method"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(30, + jl_perm_symsvec(31, "name", "module", "file", @@ -3033,6 +3033,7 @@ void jl_init_types(void) JL_GC_DISABLED "ccallable", // !const "invokes", // !const "recursion_relation", // !const + "preconditions", // !const "nargs", "called", "nospecialize", @@ -3043,7 +3044,7 @@ void jl_init_types(void) JL_GC_DISABLED "constprop", "max_varargs", "purity"), - jl_svec(30, + jl_svec(31, jl_symbol_type, jl_module_type, jl_symbol_type, @@ -3064,6 +3065,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_simplevector_type, jl_any_type, jl_any_type, + jl_simplevector_type, jl_int32_type, jl_int32_type, jl_int32_type, @@ -3082,7 +3084,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_method_instance_type = jl_new_datatype(jl_symbol("MethodInstance"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(10, + jl_perm_symsvec(11, "def", "specTypes", "sparam_vals", @@ -3090,10 +3092,11 @@ void jl_init_types(void) JL_GC_DISABLED "backedges", "callbacks", "cache", + "preconditions", "inInference", "cache_with_orig", "precompiled"), - jl_svec(10, + jl_svec(11, jl_new_struct(jl_uniontype_type, jl_method_type, jl_module_type), jl_any_type, jl_simplevector_type, @@ -3101,6 +3104,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_array_any_type, jl_any_type, jl_any_type, + jl_simplevector_type, jl_bool_type, jl_bool_type, jl_bool_type), @@ -3108,7 +3112,7 @@ void jl_init_types(void) JL_GC_DISABLED 0, 1, 3); // These fields should be constant, but Serialization wants to mutate them in initialization //const static uint32_t method_instance_constfields[1] = { 0x00000007 }; // (1<<0)|(1<<1)|(1<<2); - const static uint32_t method_instance_atomicfields[1] = { 0x00000248 }; // (1<<3)|(1<<6)|(1<<9); + const static uint32_t method_instance_atomicfields[1] = { 0x00000448 }; // (1<<3)|(1<<6)|(1<<10); //Fields 4 and 5 must be protected by method->write_lock, and thus all operations on jl_method_instance_t are threadsafe. TODO: except inInference //jl_method_instance_type->name->constfields = method_instance_constfields; jl_method_instance_type->name->atomicfields = method_instance_atomicfields; @@ -3116,7 +3120,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_code_instance_type = jl_new_datatype(jl_symbol("CodeInstance"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(15, + jl_perm_symsvec(16, "def", "next", "min_world", @@ -3129,8 +3133,10 @@ void jl_init_types(void) JL_GC_DISABLED "ipo_purity_bits", "purity_bits", "argescapes", "isspecsig", "precompile", "relocatability", - "invoke", "specptr"), // function object decls - jl_svec(15, + "invoke", "specptr", + "purity_assumptions" + ), // function object decls + jl_svec(16, jl_method_instance_type, jl_any_type, jl_ulong_type, @@ -3145,12 +3151,13 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_bool_type, jl_uint8_type, - jl_any_type, jl_any_type), // fptrs + jl_any_type, jl_any_type, + jl_uint32_type), // fptrs jl_emptysvec, 0, 1, 1); jl_svecset(jl_code_instance_type->types, 1, jl_code_instance_type); - const static uint32_t code_instance_constfields[1] = { 0b000001010110001 }; // Set fields 1, 5-6, 8, 10 as const - const static uint32_t code_instance_atomicfields[1] = { 0b110100101000010 }; // Set fields 2, 7, 9, 12, 14-15 as atomic + const static uint32_t code_instance_constfields[1] = { 0b0000001010110001 }; // Set fields 1, 5-6, 9, 11 as const + const static uint32_t code_instance_atomicfields[1] = { 0b0110100101000010 }; // Set fields 2, 7, 10, 13, 15-16 as atomic //Fields 3-4 are only operated on by construction and deserialization, so are const at runtime //Fields 11 and 15 must be protected by locks, and thus all operations on jl_code_instance_t are threadsafe jl_code_instance_type->name->constfields = code_instance_constfields; diff --git a/src/julia.h b/src/julia.h index 9c5f0d519de0f..c0dffdc424221 100644 --- a/src/julia.h +++ b/src/julia.h @@ -352,6 +352,8 @@ typedef struct _jl_method_t { // the default recursion relation. jl_value_t *recursion_relation; + jl_svec_t *preconditions; + uint32_t nargs; uint32_t called; // bit flags: whether each of the first 8 arguments is called uint32_t nospecialize; // bit flags: which arguments should not be specialized @@ -391,6 +393,7 @@ struct _jl_method_instance_t { jl_array_t *backedges; // list of method-instances which call this method-instance; `invoke` records (invokesig, caller) pairs jl_array_t *callbacks; // list of callback functions to inform external caches about invalidations _Atomic(struct _jl_code_instance_t*) cache; + jl_svec_t *preconditions; uint8_t inInference; // flags to tell if inference is running on this object uint8_t cache_with_orig; // !cache_with_specTypes _Atomic(uint8_t) precompiled; // true if this instance was generated by an explicit `precompile(...)` call @@ -459,6 +462,8 @@ typedef struct _jl_code_instance_t { _Atomic(jl_fptr_sparam_t) fptr3; // 4 interpreter } specptr; // private data for `jlcall entry point + + uint32_t purity_assumptions; } jl_code_instance_t; // all values are callable as Functions @@ -2336,7 +2341,7 @@ JL_DLLEXPORT void jl_set_safe_restore(jl_jmp_buf *) JL_NOTSAFEPOINT; // The root propagation here doesn't have to be literal, but callers should // ensure that the return value outlives the MethodInstance typedef jl_value_t *(*jl_codeinstance_lookup_t)(jl_method_instance_t *mi JL_PROPAGATES_ROOT, - size_t min_world, size_t max_world); + size_t min_world, size_t max_world, uint32_t purity_assumptions); typedef struct { int track_allocations; // can we track allocations? int code_coverage; // can we measure coverage? diff --git a/src/julia_internal.h b/src/julia_internal.h index cf65521770681..80c08c305bbf2 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -967,7 +967,7 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t*); JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *module); JL_DLLEXPORT jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types, size_t world, size_t *min_valid, size_t *max_valid, int mt_cache); jl_method_instance_t *jl_get_specialized(jl_method_t *m, jl_value_t *types, jl_svec_t *sp); -JL_DLLEXPORT jl_value_t *jl_rettype_inferred(jl_method_instance_t *li JL_PROPAGATES_ROOT, size_t min_world, size_t max_world); +JL_DLLEXPORT jl_value_t *jl_rettype_inferred(jl_method_instance_t *li JL_PROPAGATES_ROOT, size_t min_world, size_t max_world, uint32_t purity_assumptions); JL_DLLEXPORT jl_code_instance_t *jl_method_compiled(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt JL_PROPAGATES_ROOT, jl_value_t *type, size_t world); JL_DLLEXPORT jl_method_instance_t *jl_specializations_get_linfo( @@ -977,7 +977,7 @@ JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, JL_DLLEXPORT void jl_method_table_add_backedge(jl_methtable_t *mt, jl_value_t *typ, jl_value_t *caller); JL_DLLEXPORT void jl_mi_cache_insert(jl_method_instance_t *mi JL_ROOTING_ARGUMENT, jl_code_instance_t *ci JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED); -JL_DLLEXPORT extern jl_value_t *(*const jl_rettype_inferred_addr)(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t min_world, size_t max_world) JL_NOTSAFEPOINT; +JL_DLLEXPORT extern jl_value_t *(*const jl_rettype_inferred_addr)(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t min_world, size_t max_world, uint32_t purity_assumptions) JL_NOTSAFEPOINT; uint32_t jl_module_next_counter(jl_module_t *m) JL_NOTSAFEPOINT; jl_tupletype_t *arg_type_tuple(jl_value_t *arg1, jl_value_t **args, size_t nargs); @@ -1659,7 +1659,7 @@ typedef struct { JL_DLLIMPORT jl_value_t *jl_dump_method_asm(jl_method_instance_t *linfo, size_t world, char emit_mc, char getwrapper, const char* asm_variant, const char *debuginfo, char binary); -JL_DLLIMPORT void jl_get_llvmf_defn(jl_llvmf_dump_t* dump, jl_method_instance_t *linfo, size_t world, char getwrapper, char optimize, const jl_cgparams_t params); +JL_DLLIMPORT void jl_get_llvmf_defn(jl_llvmf_dump_t* dump, jl_method_instance_t *linfo, size_t world, uint32_t purity_assumptions, char getwrapper, char optimize, const jl_cgparams_t params); JL_DLLIMPORT jl_value_t *jl_dump_fptr_asm(uint64_t fptr, char emit_mc, const char* asm_variant, const char *debuginfo, char binary); JL_DLLIMPORT jl_value_t *jl_dump_function_ir(jl_llvmf_dump_t *dump, char strip_ir_metadata, char dump_module, const char *debuginfo); JL_DLLIMPORT jl_value_t *jl_dump_function_asm(jl_llvmf_dump_t *dump, char emit_mc, const char* asm_variant, const char *debuginfo, char binary, char raw); @@ -1681,6 +1681,13 @@ JL_DLLIMPORT int jl_getFunctionInfo(jl_frame_t **frames, uintptr_t pointer, int // n.b. this might be called from unmanaged thread: JL_DLLIMPORT uint64_t jl_getUnwindInfo(uint64_t dwBase); +JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( + jl_method_instance_t *mi, jl_value_t *rettype, + jl_value_t *inferred_const, jl_value_t *inferred, + int32_t const_flags, size_t min_world, size_t max_world, + uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, + uint8_t relocatability, uint32_t effect_assumptions); + #ifdef __cplusplus } #endif diff --git a/src/method.c b/src/method.c index 06a05361a927d..48b90d4fa4e3e 100644 --- a/src/method.c +++ b/src/method.c @@ -447,6 +447,7 @@ JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void) mi->def.value = NULL; mi->specTypes = NULL; mi->sparam_vals = jl_emptysvec; + mi->preconditions = jl_emptysvec; jl_atomic_store_relaxed(&mi->uninferred, NULL); mi->backedges = NULL; mi->callbacks = NULL; @@ -800,6 +801,7 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t *module) m->module = module; m->external_mt = NULL; m->source = NULL; + m->preconditions = jl_emptysvec; jl_atomic_store_relaxed(&m->unspecialized, NULL); m->generator = NULL; m->name = NULL; diff --git a/src/opaque_closure.c b/src/opaque_closure.c index d73beff0f8587..f8a484af7ef17 100644 --- a/src/opaque_closure.c +++ b/src/opaque_closure.c @@ -134,13 +134,6 @@ jl_opaque_closure_t *jl_new_opaque_closure(jl_tupletype_t *argt, jl_value_t *rt_ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name, int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva); -JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( - jl_method_instance_t *mi, jl_value_t *rettype, - jl_value_t *inferred_const, jl_value_t *inferred, - int32_t const_flags, size_t min_world, size_t max_world, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, - uint8_t relocatability); - JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tupletype_t *argt, jl_value_t *rt_lb, jl_value_t *rt_ub, jl_module_t *mod, jl_code_info_t *ci, int lineno, jl_value_t *file, int nargs, int isva, jl_value_t *env, int do_compile) { @@ -158,7 +151,7 @@ JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tuplet sigtype = jl_argtype_with_function(env, (jl_value_t*)argt); jl_method_instance_t *mi = jl_specializations_get_linfo((jl_method_t*)root, sigtype, jl_emptysvec); inst = jl_new_codeinst(mi, rt_ub, NULL, (jl_value_t*)ci, - 0, meth->primary_world, -1, 0, 0, jl_nothing, 0); + 0, meth->primary_world, -1, 0, 0, jl_nothing, 0, 0); jl_mi_cache_insert(mi, inst); jl_opaque_closure_t *oc = new_opaque_closure(argt, rt_lb, rt_ub, root, env, do_compile); diff --git a/src/staticdata.c b/src/staticdata.c index c05422fd10969..d5dd64fe69c91 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -99,7 +99,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 158 +#define NUM_TAGS 159 // 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. @@ -270,6 +270,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_builtin_apply_type); INSERT_TAG(jl_builtin_applicable); INSERT_TAG(jl_builtin_invoke); + INSERT_TAG(jl_builtin_invoke_split_effects); INSERT_TAG(jl_builtin__expr); INSERT_TAG(jl_builtin_ifelse); INSERT_TAG(jl_builtin__typebody); @@ -445,7 +446,7 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f_getfield, &jl_f_setfield, &jl_f_swapfield, &jl_f_modifyfield, &jl_f_replacefield, &jl_f_fieldtype, &jl_f_nfields, &jl_f_arrayref, &jl_f_const_arrayref, &jl_f_arrayset, &jl_f_arraysize, &jl_f_apply_type, - &jl_f_applicable, &jl_f_invoke, &jl_f_sizeof, &jl_f__expr, &jl_f__typevar, + &jl_f_applicable, &jl_f_invoke, &jl_f_invoke_split_effects, &jl_f_sizeof, &jl_f__expr, &jl_f__typevar, &jl_f_ifelse, &jl_f__structtype, &jl_f__abstracttype, &jl_f__primitivetype, &jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f_get_binding_type, &jl_f_set_binding_type, &jl_f_opaque_closure_call, &jl_f_donotdelete, &jl_f_compilerbarrier, diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index bf1a830b608de..900b9b22eb036 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -1095,7 +1095,7 @@ static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_a else { assert(ci->max_world == ~(size_t)0); jl_method_instance_t *caller = ci->def; - if (ci->inferred && jl_rettype_inferred(caller, minworld, ~(size_t)0) == jl_nothing) { + if (ci->inferred && jl_rettype_inferred(caller, minworld, ~(size_t)0, 0) == jl_nothing) { jl_mi_cache_insert(caller, ci); } //jl_static_show((jl_stream*)ios_stderr, (jl_value_t*)caller); @@ -1140,7 +1140,7 @@ static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_a jl_code_instance_t *codeinst = (jl_code_instance_t*)ci; assert(codeinst->min_world == minworld && codeinst->inferred); codeinst->max_world = maxvalid; - if (jl_rettype_inferred(caller, minworld, maxvalid) == jl_nothing) { + if (jl_rettype_inferred(caller, minworld, maxvalid, 0) == jl_nothing) { jl_mi_cache_insert(caller, codeinst); } } diff --git a/stdlib/InteractiveUtils/src/codeview.jl b/stdlib/InteractiveUtils/src/codeview.jl index 646028575d052..4c65cacee2688 100644 --- a/stdlib/InteractiveUtils/src/codeview.jl +++ b/stdlib/InteractiveUtils/src/codeview.jl @@ -170,15 +170,17 @@ const OC_MISMATCH_WARNING = function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrapper::Bool, raw::Bool, dump_module::Bool, syntax::Symbol, - optimize::Bool, debuginfo::Symbol, binary::Bool) + optimize::Bool, debuginfo::Symbol, binary::Bool, + purity_assumptions::UInt32 = UInt32(0)) params = CodegenParams(debug_info_kind=Cint(0), safepoint_on_entry=raw, gcstack_arg=raw) _dump_function(f, t, native, wrapper, raw, dump_module, syntax, - optimize, debuginfo, binary, params) + optimize, debuginfo, binary, params, purity_assumptions) end function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrapper::Bool, raw::Bool, dump_module::Bool, syntax::Symbol, - optimize::Bool, debuginfo::Symbol, binary::Bool, params::CodegenParams) + optimize::Bool, debuginfo::Symbol, binary::Bool, params::CodegenParams, + purity_assumptions::UInt32 = UInt32(0)) ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") if isa(f, Core.Builtin) throw(ArgumentError("argument is not a generic function")) @@ -223,7 +225,7 @@ function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrappe str = _dump_function_native_disassembly(mi, world, wrapper, syntax, debuginfo, binary) end else - str = _dump_function_llvm(mi, world, wrapper, !raw, dump_module, optimize, debuginfo, params) + str = _dump_function_llvm(mi, world, purity_assumptions, wrapper, !raw, dump_module, optimize, debuginfo, params) end str = warning * str return str @@ -247,7 +249,7 @@ function _dump_function_native_assembly(mi::Core.MethodInstance, world::UInt, wrapper::Bool, syntax::Symbol, debuginfo::Symbol, binary::Bool, raw::Bool, params::CodegenParams) llvmf_dump = Ref{LLVMFDump}() - @ccall jl_get_llvmf_defn(llvmf_dump::Ptr{LLVMFDump},mi::Any, world::UInt, wrapper::Bool, + @ccall jl_get_llvmf_defn(llvmf_dump::Ptr{LLVMFDump},mi::Any, world::UInt, 0::UInt32, wrapper::Bool, true::Bool, params::CodegenParams)::Cvoid llvmf_dump[].f == C_NULL && error("could not compile the specified method") str = @ccall jl_dump_function_asm(llvmf_dump::Ptr{LLVMFDump}, false::Bool, @@ -257,12 +259,12 @@ function _dump_function_native_assembly(mi::Core.MethodInstance, world::UInt, end function _dump_function_llvm( - mi::Core.MethodInstance, world::UInt, wrapper::Bool, + mi::Core.MethodInstance, world::UInt, purity_assumptions::UInt32, wrapper::Bool, strip_ir_metadata::Bool, dump_module::Bool, optimize::Bool, debuginfo::Symbol, params::CodegenParams) llvmf_dump = Ref{LLVMFDump}() - @ccall jl_get_llvmf_defn(llvmf_dump::Ptr{LLVMFDump}, mi::Any, world::UInt, + @ccall jl_get_llvmf_defn(llvmf_dump::Ptr{LLVMFDump}, mi::Any, world::UInt, purity_assumptions::UInt32, wrapper::Bool, optimize::Bool, params::CodegenParams)::Cvoid llvmf_dump[].f == C_NULL && error("could not compile the specified method") str = @ccall jl_dump_function_ir(llvmf_dump::Ptr{LLVMFDump}, strip_ir_metadata::Bool, @@ -282,18 +284,19 @@ To dump the entire module that encapsulates the function (with declarations), se Keyword argument `debuginfo` may be one of source (default) or none, to specify the verbosity of code comments. """ function code_llvm(io::IO, @nospecialize(f), @nospecialize(types), raw::Bool, - dump_module::Bool=false, optimize::Bool=true, debuginfo::Symbol=:default) - d = _dump_function(f, types, false, false, raw, dump_module, :intel, optimize, debuginfo, false) + dump_module::Bool=false, optimize::Bool=true, debuginfo::Symbol=:default, + purity_assumptions::UInt32 = UInt32(0)) + d = _dump_function(f, types, false, false, raw, dump_module, :intel, optimize, debuginfo, false, purity_assumptions) if highlighting[:llvm] && get(io, :color, false)::Bool print_llvm(io, d) else print(io, d) end end -code_llvm(io::IO, @nospecialize(f), @nospecialize(types=Base.default_tt(f)); raw::Bool=false, dump_module::Bool=false, optimize::Bool=true, debuginfo::Symbol=:default) = - code_llvm(io, f, types, raw, dump_module, optimize, debuginfo) -code_llvm(@nospecialize(f), @nospecialize(types=Base.default_tt(f)); raw=false, dump_module=false, optimize=true, debuginfo::Symbol=:default) = - code_llvm(stdout, f, types; raw, dump_module, optimize, debuginfo) +code_llvm(io::IO, @nospecialize(f), @nospecialize(types=Base.default_tt(f)); raw::Bool=false, dump_module::Bool=false, optimize::Bool=true, debuginfo::Symbol=:default, purity_assumptions=UInt32(0)) = + code_llvm(io, f, types, raw, dump_module, optimize, debuginfo, purity_assumptions) +code_llvm(@nospecialize(f), @nospecialize(types=Base.default_tt(f)); raw=false, dump_module=false, optimize=true, debuginfo::Symbol=:default, purity_assumptions=UInt32(0)) = + code_llvm(stdout, f, types; raw, dump_module, optimize, debuginfo, purity_assumptions) """ code_native([io=stdout,], f, types; syntax=:intel, debuginfo=:default, binary=false, dump_module=true)