diff --git a/base/boot.jl b/base/boot.jl index e041488cdb74c..a02d9d0385ca0 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -64,7 +64,7 @@ # } #end -# struct GenericMemoryRef{kind::Symbol, T, AS::AddrSpace} +#struct GenericMemoryRef{kind::Symbol, T, AS::AddrSpace} # mem::GenericMemory{kind, T, AS} # data::Ptr{Cvoid} # make this GenericPtr{addrspace, Cvoid} #end @@ -125,12 +125,13 @@ # file::Union{Symbol,Nothing} #end -#struct LineInfoNode -# module::Module -# method::Any (Union{Symbol, Method, MethodInstance}) -# file::Symbol -# line::Int32 -# inlined_at::Int32 +#struct LegacyLineInfoNode end # only used internally during lowering + +#struct DebugInfo +# def::Any # (Union{Symbol, Method, MethodInstance}) +# linetable::Any # (Union{Nothing,DebugInfo}) +# edges::SimpleVector # Vector{DebugInfo} +# codelocs::String # compressed Vector{UInt8} #end #struct GotoNode @@ -296,6 +297,9 @@ TypeVar(@nospecialize(n), @nospecialize(ub)) = _typevar(n::Symbol, Union{}, ub) TypeVar(@nospecialize(n), @nospecialize(lb), @nospecialize(ub)) = _typevar(n::Symbol, lb, ub) UnionAll(@nospecialize(v), @nospecialize(t)) = ccall(:jl_type_unionall, Any, (Any, Any), v::TypeVar, t) +const Memory{T} = GenericMemory{:not_atomic, T, CPU} +const MemoryRef{T} = GenericMemoryRef{:not_atomic, T, CPU} + # simple convert for use by constructors of types in Core # note that there is no actual conversion defined here, # so the methods and ccall's in Core aren't permitted to use convert @@ -466,8 +470,10 @@ eval(Core, quote isa(f, String) && (f = Symbol(f)) return $(Expr(:new, :LineNumberNode, :l, :f)) end - LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line::Int32, inlined_at::Int32) = - $(Expr(:new, :LineInfoNode, :mod, :method, :file, :line, :inlined_at)) + DebugInfo(def::Union{Method,MethodInstance,Symbol}, linetable::Union{Nothing,DebugInfo}, edges::SimpleVector, codelocs::String) = + $(Expr(:new, :DebugInfo, :def, :linetable, :edges, :codelocs)) + DebugInfo(def::Union{Method,MethodInstance,Symbol}) = + $(Expr(:new, :DebugInfo, :def, nothing, Core.svec(), "")) SlotNumber(n::Int) = $(Expr(:new, :SlotNumber, :n)) PhiNode(edges::Array{Int32, 1}, values::Array{Any, 1}) = $(Expr(:new, :PhiNode, :edges, :values)) PiNode(@nospecialize(val), @nospecialize(typ)) = $(Expr(:new, :PiNode, :val, :typ)) @@ -482,16 +488,25 @@ eval(Core, quote MethodMatch(@nospecialize(spec_types), sparams::SimpleVector, method::Method, fully_covers::Bool) = $(Expr(:new, :MethodMatch, :spec_types, :sparams, :method, :fully_covers)) end) +struct LineInfoNode # legacy support for aiding Serializer.deserialize of old IR + mod::Module + method + file::Symbol + line::Int32 + inlined_at::Int32 + LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line::Int32, inlined_at::Int32) = new(mod, method, file, line, inlined_at) +end + + function CodeInstance( mi::MethodInstance, owner, @nospecialize(rettype), @nospecialize(exctype), @nospecialize(inferred_const), @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, ipo_effects::UInt32, effects::UInt32, @nospecialize(analysis_results), - relocatability::UInt8) + relocatability::UInt8, edges::DebugInfo) return ccall(:jl_new_codeinst, Ref{CodeInstance}, - (Any, Any, Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), + (Any, Any, Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8, Any), mi, owner, rettype, exctype, inferred_const, inferred, const_flags, min_world, max_world, - ipo_effects, effects, analysis_results, - relocatability) + ipo_effects, effects, analysis_results, relocatability, edges) 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) @@ -629,12 +644,12 @@ module IR export CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, NewvarNode, SSAValue, SlotNumber, Argument, - PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, + PiNode, PhiNode, PhiCNode, UpsilonNode, DebugInfo, Const, PartialStruct, InterConditional, EnterNode using Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, NewvarNode, SSAValue, SlotNumber, Argument, - PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, + PiNode, PhiNode, PhiCNode, UpsilonNode, DebugInfo, Const, PartialStruct, InterConditional, EnterNode end # module IR diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index d4c4d94b6db31..31bc8d4e20105 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -323,7 +323,7 @@ mutable struct InferenceState exc_bestguess = Bottom ipo_effects = EFFECTS_TOTAL - insert_coverage = should_insert_coverage(mod, src) + insert_coverage = should_insert_coverage(mod, src.debuginfo) if insert_coverage ipo_effects = Effects(ipo_effects; effect_free = ALWAYS_FALSE) end @@ -474,25 +474,21 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) end # check if coverage mode is enabled -function should_insert_coverage(mod::Module, src::CodeInfo) +function should_insert_coverage(mod::Module, debuginfo::DebugInfo) coverage_enabled(mod) && return true JLOptions().code_coverage == 3 || return false # path-specific coverage mode: if any line falls in a tracked file enable coverage for all - linetable = src.linetable - if isa(linetable, Vector{Any}) - for line in linetable - line = line::LineInfoNode - if is_file_tracked(line.file) - return true - end - end - elseif isa(linetable, Vector{LineInfoNode}) - for line in linetable - if is_file_tracked(line.file) - return true - end - end - end + return _should_insert_coverage(debuginfo) +end + +_should_insert_coverage(mod::Symbol) = is_file_tracked(mod) +_should_insert_coverage(mod::Method) = _should_insert_coverage(mod.file) +_should_insert_coverage(mod::MethodInstance) = _should_insert_coverage(mod.def) +_should_insert_coverage(mod::Module) = false +function _should_insert_coverage(info::DebugInfo) + linetable = info.linetable + linetable === nothing || (_should_insert_coverage(linetable) && return true) + _should_insert_coverage(info.def) && return true return false end diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index c30f15864892d..81b025e588f7f 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -990,22 +990,79 @@ function run_passes_ipo_safe( if is_asserts() @timeit "verify 3" begin verify_ir(ir, true, false, optimizer_lattice(sv.inlining.interp)) - verify_linetable(ir.linetable) + verify_linetable(ir.debuginfo, length(ir.stmts)) end end @label __done__ # used by @pass return ir end -function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) - linetable = ci.linetable - if !isa(linetable, Vector{LineInfoNode}) - linetable = collect(LineInfoNode, linetable::Vector{Any})::Vector{LineInfoNode} +function strip_trailing_junk!(code::Vector{Any}, ssavaluetypes::Vector{Any}, ssaflags::Vector, debuginfo::DebugInfoStream, cfg::CFG, info::Vector{CallInfo}) + # Remove `nothing`s at the end, we don't handle them well + # (we expect the last instruction to be a terminator) + codelocs = debuginfo.codelocs + for i = length(code):-1:1 + if code[i] !== nothing + resize!(code, i) + resize!(ssavaluetypes, i) + resize!(codelocs, 3i) + resize!(info, i) + resize!(ssaflags, i) + break + end + end + # If the last instruction is not a terminator, add one. This can + # happen for implicit return on dead branches. + term = code[end] + if !isa(term, GotoIfNot) && !isa(term, GotoNode) && !isa(term, ReturnNode) + push!(code, ReturnNode()) + push!(ssavaluetypes, Union{}) + push!(codelocs, 0, 0, 0) + push!(info, NoCallInfo()) + push!(ssaflags, IR_FLAG_NOTHROW) + + # Update CFG to include appended terminator + old_range = cfg.blocks[end].stmts + new_range = StmtRange(first(old_range), last(old_range) + 1) + cfg.blocks[end] = BasicBlock(cfg.blocks[end], new_range) + (length(cfg.index) == length(cfg.blocks)) && (cfg.index[end] += 1) + end + nothing +end + +function changed_lineinfo(di::DebugInfo, codeloc::Int, prevloc::Int) + while true + next = getdebugidx(di, codeloc) + next[1] < 0 && return false # invalid info + next[1] == 0 && next[2] == 0 && return false # no new info + prevloc <= 0 && return true # no old info + prev = getdebugidx(di, prevloc) + next === prev && return false # exactly identical + prev[1] < 0 && return true # previous invalid info, now valid + edge = next[2] + edge === prev[2] || return true # change to this edge + linetable = di.linetable + # check for change to line number here + if linetable === nothing || next[1] == 0 + next[1] == prev[1] || return true + else + changed_lineinfo(linetable, next[1], prev[1]) && return true + end + # check for change to edge here + edge == 0 && return false # no edge here + di = di.edges[Int(edge)]::DebugInfo + codeloc = Int(next[3]) + prevloc = Int(prev[3]) end +end +function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) # Update control-flow to reflect any unreachable branches. ssavaluetypes = ci.ssavaluetypes::Vector{Any} - code = copy_exprargs(ci.code) + ci.code = code = copy_exprargs(ci.code) + di = DebugInfoStream(sv.linfo, ci.debuginfo, length(code)) + codelocs = di.codelocs + ssaflags = ci.ssaflags for i = 1:length(code) expr = code[i] if !(i in sv.unreachable) @@ -1021,11 +1078,11 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) ((block + 1) != destblock) && cfg_delete_edge!(sv.cfg, block, destblock) expr = Expr(:call, Core.typeassert, expr.cond, Bool) elseif i + 1 in sv.unreachable - @assert has_flag(ci.ssaflags[i], IR_FLAG_NOTHROW) + @assert has_flag(ssaflags[i], IR_FLAG_NOTHROW) cfg_delete_edge!(sv.cfg, block, block + 1) expr = GotoNode(expr.dest) elseif expr.dest in sv.unreachable - @assert has_flag(ci.ssaflags[i], IR_FLAG_NOTHROW) + @assert has_flag(ssaflags[i], IR_FLAG_NOTHROW) cfg_delete_edge!(sv.cfg, block, block_for_inst(sv.cfg, expr.dest)) expr = nothing end @@ -1052,20 +1109,17 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) # Go through and add an unreachable node after every # Union{} call. Then reindex labels. stmtinfo = sv.stmt_info - codelocs = ci.codelocs - ssaflags = ci.ssaflags meta = Expr[] idx = 1 oldidx = 1 nstmts = length(code) ssachangemap = labelchangemap = blockchangemap = nothing - prevloc = zero(eltype(ci.codelocs)) + prevloc = 0 while idx <= length(code) - codeloc = codelocs[idx] - if sv.insert_coverage && codeloc != prevloc && codeloc != 0 + if sv.insert_coverage && changed_lineinfo(ci.debuginfo, oldidx, prevloc) # insert a side-effect instruction before the current instruction in the same basic block insert!(code, idx, Expr(:code_coverage_effect)) - insert!(codelocs, idx, codeloc) + splice!(codelocs, 3idx-2:3idx-3, (codelocs[3idx-2], codelocs[3idx-1], codelocs[3idx-0])) insert!(ssavaluetypes, idx, Nothing) insert!(stmtinfo, idx, NoCallInfo()) insert!(ssaflags, idx, IR_FLAG_NULL) @@ -1084,7 +1138,7 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) end blockchangemap[block_for_inst(sv.cfg, oldidx)] += 1 idx += 1 - prevloc = codeloc + prevloc = oldidx end if ssavaluetypes[idx] === Union{} && !(oldidx in sv.unreachable) && !isa(code[idx], PhiNode) # We should have converted any must-throw terminators to an equivalent w/o control-flow edges @@ -1106,7 +1160,7 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) # terminator with an explicit `unreachable` marker. if block_end > idx code[block_end] = ReturnNode() - codelocs[block_end] = codelocs[idx] + codelocs[3block_end-2], codelocs[3block_end-1], codelocs[3block_end-0] = (codelocs[3idx-2], codelocs[3idx-1], codelocs[3idx-0]) ssavaluetypes[block_end] = Union{} stmtinfo[block_end] = NoCallInfo() ssaflags[block_end] = IR_FLAG_NOTHROW @@ -1121,7 +1175,7 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) idx += block_end - idx else insert!(code, idx + 1, ReturnNode()) - insert!(codelocs, idx + 1, codelocs[idx]) + splice!(codelocs, 3idx-2:3idx-3, (codelocs[3idx-2], codelocs[3idx-1], codelocs[3idx-0])) insert!(ssavaluetypes, idx + 1, Union{}) insert!(stmtinfo, idx + 1, NoCallInfo()) insert!(ssaflags, idx + 1, IR_FLAG_NOTHROW) @@ -1158,14 +1212,14 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) for i = 1:length(code) code[i] = process_meta!(meta, code[i]) end - strip_trailing_junk!(ci, sv.cfg, code, stmtinfo) + strip_trailing_junk!(code, ssavaluetypes, ssaflags, di, sv.cfg, stmtinfo) types = Any[] stmts = InstructionStream(code, types, stmtinfo, codelocs, ssaflags) # NOTE this `argtypes` contains types of slots yet: it will be modified to contain the # types of call arguments only once `slot2reg` converts this `IRCode` to the SSA form # and eliminates slots (see below) argtypes = sv.slottypes - return IRCode(stmts, sv.cfg, linetable, argtypes, meta, sv.sptypes) + return IRCode(stmts, sv.cfg, di, argtypes, meta, sv.sptypes) end function process_meta!(meta::Vector{Expr}, @nospecialize stmt) diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 3e57bc8503e06..c49bfe2239972 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -12,14 +12,16 @@ struct InliningTodo mi::MethodInstance # The IR of the inlinee ir::IRCode + # The DebugInfo table for the inlinee + di::DebugInfo # If the function being inlined is a single basic block we can use a # simpler inlining algorithm. This flag determines whether that's allowed linear_inline_eligible::Bool # Effects of the call statement effects::Effects end -function InliningTodo(mi::MethodInstance, ir::IRCode, effects::Effects) - return InliningTodo(mi, ir, linear_inline_eligible(ir), effects) +function InliningTodo(mi::MethodInstance, (ir, di)::Tuple{IRCode, DebugInfo}, effects::Effects) + return InliningTodo(mi, ir, di, linear_inline_eligible(ir), effects) end struct ConstantCase @@ -298,69 +300,30 @@ function finish_cfg_inline!(state::CFGInliningState) end end -# duplicated from IRShow -function normalize_method_name(m) - if m isa Method - return m.name - elseif m isa MethodInstance - return (m.def::Method).name - elseif m isa Symbol - return m - else - return Symbol("") - end -end -@noinline method_name(m::LineInfoNode) = normalize_method_name(m.method) - -inline_node_is_duplicate(topline::LineInfoNode, line::LineInfoNode) = - topline.module === line.module && - method_name(topline) === method_name(line) && - topline.file === line.file && - topline.line === line.line - -function ir_inline_linetable!(linetable::Vector{LineInfoNode}, inlinee_ir::IRCode, - inlinee::MethodInstance, inlined_at::Int32) - inlinee_def = inlinee.def::Method - coverage = coverage_enabled(inlinee_def.module) - linetable_offset::Int32 = length(linetable) - # Append the linetable of the inlined function to our line table - topline::Int32 = linetable_offset + Int32(1) - coverage_by_path = JLOptions().code_coverage == 3 - push!(linetable, LineInfoNode(inlinee_def.module, inlinee_def.name, inlinee_def.file, inlinee_def.line, inlined_at)) - oldlinetable = inlinee_ir.linetable - extra_coverage_line = zero(Int32) - for oldline in eachindex(oldlinetable) - entry = oldlinetable[oldline] - if !coverage && coverage_by_path && is_file_tracked(entry.file) - # include topline coverage entry if in path-specific coverage mode, and any file falls under path - coverage = true - end - newentry = LineInfoNode(entry.module, entry.method, entry.file, entry.line, - (entry.inlined_at > 0 ? entry.inlined_at + linetable_offset + (oldline == 1) : inlined_at)) - if oldline == 1 - # check for a duplicate on the first iteration (likely true) - if inline_node_is_duplicate(linetable[topline], newentry) - continue - else - linetable_offset += 1 - end +function ir_inline_linetable!(debuginfo::DebugInfoStream, inlinee_debuginfo::DebugInfo, inlinee::MethodInstance) + # Append the linetable of the inlined function to our edges table + linetable_offset = 1 + while true + if linetable_offset > length(debuginfo.edges) + push!(debuginfo.edges, inlinee_debuginfo) + break + elseif debuginfo.edges[linetable_offset] === inlinee_debuginfo + break end - push!(linetable, newentry) - end - if coverage && inlinee_ir.stmts[1][:line] + linetable_offset != topline - extra_coverage_line = topline + linetable_offset += 1 end - return linetable_offset, extra_coverage_line + return Int32(linetable_offset) end function ir_prepare_inlining!(insert_node!::Inserter, inline_target::Union{IRCode, IncrementalCompact}, - ir::IRCode, mi::MethodInstance, inlined_at::Int32, argexprs::Vector{Any}) + ir::IRCode, di::DebugInfo, mi::MethodInstance, inlined_at::Int32, argexprs::Vector{Any}) def = mi.def::Method - linetable = inline_target isa IRCode ? inline_target.linetable : inline_target.ir.linetable - topline::Int32 = length(linetable) + Int32(1) - linetable_offset, extra_coverage_line = ir_inline_linetable!(linetable, ir, mi, inlined_at) - if extra_coverage_line != 0 - insert_node!(NewInstruction(Expr(:code_coverage_effect), Nothing, extra_coverage_line)) + debuginfo = inline_target isa IRCode ? inline_target.debuginfo : inline_target.ir.debuginfo + + linetable_offset = ir_inline_linetable!(debuginfo, di, mi) + topline = (inlined_at, linetable_offset, Int32(0)) + if should_insert_coverage(def.module, di) + insert_node!(NewInstruction(Expr(:code_coverage_effect), Nothing, topline)) end spvals_ssa = nothing if !validate_sparams(mi.sparam_vals) @@ -380,7 +343,7 @@ function ir_prepare_inlining!(insert_node!::Inserter, inline_target::Union{IRCod NewInstruction(Expr(:call, GlobalRef(Core, :getfield), argexprs[1], QuoteNode(:captures)), ir.argtypes[1], topline)) end - return SSASubstitute(mi, argexprs, spvals_ssa, linetable_offset) + return SSASubstitute(mi, argexprs, spvals_ssa, (inlined_at, linetable_offset)) end function adjust_boundscheck!(inline_compact::IncrementalCompact, idx′::Int, stmt::Expr, boundscheck::Symbol) @@ -396,13 +359,12 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector item::InliningTodo, boundscheck::Symbol, todo_bbs::Vector{Tuple{Int, Int}}) # Ok, do the inlining here inlined_at = compact.result[idx][:line] - - ssa_substitute = ir_prepare_inlining!(InsertHere(compact), compact, item.ir, item.mi, inlined_at, argexprs) - + @assert inlined_at[2] == 0 "already inlined this instruction" + ssa_substitute = ir_prepare_inlining!(InsertHere(compact), compact, item.ir, item.di, item.mi, inlined_at[1], argexprs) boundscheck = has_flag(compact.result[idx], IR_FLAG_INBOUNDS) ? :off : boundscheck # If the iterator already moved on to the next basic block, - # temporarily re-open in again. + # temporarily re-open it again. local return_value # Special case inlining that maintains the current basic block if there's only one BB in the target new_new_offset = length(compact.new_new_nodes) @@ -410,14 +372,16 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector if item.linear_inline_eligible #compact[idx] = nothing inline_compact = IncrementalCompact(compact, item.ir, compact.result_idx) - for ((_, idx′), stmt′) in inline_compact + @assert isempty(inline_compact.perm) && isempty(inline_compact.pending_perm) "linetable not in canonical form (missing compact call)" + for ((lineidx, idx′), stmt′) in inline_compact # This dance is done to maintain accurate usage counts in the # face of rename_arguments! mutating in place - should figure out # something better eventually. inline_compact[idx′] = nothing + # alter the line number information for InsertBefore to point to the current instruction in the new linetable + inline_compact[SSAValue(idx′)][:line] = (ssa_substitute.inlined_at[1], ssa_substitute.inlined_at[2], Int32(lineidx)) insert_node! = InsertBefore(inline_compact, SSAValue(idx′)) - stmt′ = ssa_substitute!(insert_node!, inline_compact[SSAValue(idx′)], stmt′, - ssa_substitute) + stmt′ = ssa_substitute_op!(insert_node!, inline_compact[SSAValue(idx′)], stmt′, ssa_substitute) if isa(stmt′, ReturnNode) val = stmt′.val return_value = SSAValue(idx′) @@ -444,11 +408,12 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector pn = PhiNode() #compact[idx] = nothing inline_compact = IncrementalCompact(compact, item.ir, compact.result_idx) - for ((_, idx′), stmt′) in inline_compact + @assert isempty(inline_compact.perm) && isempty(inline_compact.pending_perm) "linetable not in canonical form (missing compact call)" + for ((lineidx, idx′), stmt′) in inline_compact inline_compact[idx′] = nothing + inline_compact[SSAValue(idx′)][:line] = (ssa_substitute.inlined_at[1], ssa_substitute.inlined_at[2], Int32(lineidx)) insert_node! = InsertBefore(inline_compact, SSAValue(idx′)) - stmt′ = ssa_substitute!(insert_node!, inline_compact[SSAValue(idx′)], stmt′, - ssa_substitute) + stmt′ = ssa_substitute_op!(insert_node!, inline_compact[SSAValue(idx′)], stmt′, ssa_substitute) if isa(stmt′, ReturnNode) if isdefined(stmt′, :val) val = stmt′.val @@ -484,7 +449,7 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector end function fix_va_argexprs!(insert_node!::Inserter, inline_target::Union{IRCode, IncrementalCompact}, - argexprs::Vector{Any}, nargs_def::Int, line_idx::Int32) + argexprs::Vector{Any}, nargs_def::Int, line_idx::NTuple{3,Int32}) newargexprs = argexprs[1:(nargs_def-1)] tuple_call = Expr(:call, TOP_TUPLE) tuple_typs = Any[] @@ -996,19 +961,20 @@ end function retrieve_ir_for_inlining(cached_result::CodeInstance, src::MaybeCompressed) src = _uncompressed_ir(cached_result, src)::CodeInfo - return inflate_ir!(src, cached_result.def) + return inflate_ir!(src, cached_result.def), src.debuginfo end function retrieve_ir_for_inlining(mi::MethodInstance, src::CodeInfo, preserve_local_sources::Bool) if preserve_local_sources src = copy(src) end - return inflate_ir!(src, mi) + return inflate_ir!(src, mi), src.debuginfo end function retrieve_ir_for_inlining(mi::MethodInstance, ir::IRCode, preserve_local_sources::Bool) if preserve_local_sources ir = copy(ir) end - return ir + ir.debuginfo.def = mi + return ir, DebugInfo(ir.debuginfo, length(ir.stmts)) end function handle_single_case!(todo::Vector{Pair{Int,Any}}, @@ -1829,15 +1795,7 @@ struct SSASubstitute mi::MethodInstance arg_replacements::Vector{Any} spvals_ssa::Union{Nothing,SSAValue} - linetable_offset::Int32 -end - -function ssa_substitute!(insert_node!::Inserter, subst_inst::Instruction, @nospecialize(val), - ssa_substitute::SSASubstitute) - if subst_inst[:line] != 0 - subst_inst[:line] += ssa_substitute.linetable_offset - end - return ssa_substitute_op!(insert_node!, subst_inst, val, ssa_substitute) + inlined_at::NTuple{2,Int32} # TODO: add a map also, so that ssaidx doesn't need to equal inlined_idx? end function insert_spval!(insert_node!::Inserter, spvals_ssa::SSAValue, spidx::Int, do_isdefined::Bool) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index ca658ad101f7f..c23f89e01f664 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -174,6 +174,51 @@ function first_insert_for_bb(code::Vector{Any}, cfg::CFG, block::Int) return lastnonphiidx end +# mutable version of the compressed DebugInfo +mutable struct DebugInfoStream + def::Union{MethodInstance,Symbol,Nothing} + linetable::Union{Nothing,Core.DebugInfo} + edges::Vector{Any} # Vector{Core.DebugInfo} + firstline::Int32 # the starting line for this block (specified by having an index of 0) + codelocs::Vector{Int32} # for each statement: + # index into linetable (if defined), else a line number (in the file represented by def) + # then index into edges + # then index into edges[linetable] + function DebugInfoStream(codelocs::Vector{Int32}) + return new(nothing, nothing, [], 0, codelocs) + end + #DebugInfoStream(def::Union{MethodInstance,Nothing}, di::DebugInfo, nstmts::Int) = + # if debuginfo_file1(di.def) === debuginfo_file1(di.def) + # new(def, di.linetable, Core.svec(di.edges...), getdebugidx(di, 0), + # ccall(:jl_uncompress_codelocs, Any, (Any, Int), di.codelocs, nstmts)::Vector{Int32}) + # else + function DebugInfoStream(def::Union{MethodInstance,Nothing}, di::DebugInfo, nstmts::Int) + codelocs = zeros(Int32, nstmts * 3) + for i = 1:nstmts + codelocs[3i - 2] = i + end + return new(def, di, Vector{Any}(), 0, codelocs) + end + global copy(di::DebugInfoStream) = new(di.def, di.linetable, di.edges, di.firstline, di.codelocs) +end + +Core.DebugInfo(di::DebugInfoStream, nstmts::Int) = + Core.DebugInfo(something(di.def), di.linetable, Core.svec(di.edges...), + ccall(:jl_compress_codelocs, Any, (Int32, Any, Int), di.firstline, di.codelocs, nstmts)::String) + +getdebugidx(debuginfo::Core.DebugInfo, pc::Int) = ccall(:jl_uncompress1_codeloc, NTuple{3,Int32}, (Any, Int), debuginfo.codelocs, pc) + +function getdebugidx(debuginfo::DebugInfoStream, pc::Int) + if 3 <= 3pc <= length(debuginfo.codelocs) + return (debuginfo.codelocs[3pc - 2], debuginfo.codelocs[3pc - 1], debuginfo.codelocs[3pc - 0]) + elseif pc == 0 + return (Int32(debuginfo.firstline), Int32(0), Int32(0)) + else + return (Int32(-1), Int32(0), Int32(0)) + end +end + + # SSA values that need renaming struct OldSSAValue id::Int @@ -203,7 +248,6 @@ end const AnySSAValue = Union{SSAValue, OldSSAValue, NewSSAValue} - # SSA-indexed nodes struct InstructionStream stmt::Vector{Any} @@ -211,13 +255,16 @@ struct InstructionStream info::Vector{CallInfo} line::Vector{Int32} flag::Vector{UInt32} + function InstructionStream(stmts::Vector{Any}, type::Vector{Any}, info::Vector{CallInfo}, line::Vector{Int32}, flag::Vector{UInt32}) + return new(stmts, type, info, line, flag) + end end function InstructionStream(len::Int) stmts = Vector{Any}(undef, len) types = Vector{Any}(undef, len) info = Vector{CallInfo}(undef, len) fill!(info, NoCallInfo()) - lines = fill(Int32(0), len) + lines = fill(Int32(0), 3len) flags = fill(IR_FLAG_NULL, len) return InstructionStream(stmts, types, info, lines, flags) end @@ -242,10 +289,10 @@ function resize!(stmts::InstructionStream, len) resize!(stmts.stmt, len) resize!(stmts.type, len) resize!(stmts.info, len) - resize!(stmts.line, len) + resize!(stmts.line, 3len) resize!(stmts.flag, len) for i in (old_length + 1):len - stmts.line[i] = 0 + stmts.line[3i-2], stmts.line[3i-1], stmts.line[3i] = NoLineUpdate stmts.flag[i] = IR_FLAG_NULL stmts.info[i] = NoCallInfo() end @@ -261,11 +308,20 @@ Instruction(is::InstructionStream) = Instruction(is, add_new_idx!(is)) @inline function getindex(node::Instruction, fld::Symbol) (fld === :inst) && (fld = :stmt) # deprecated isdefined(node, fld) && return getfield(node, fld) - return getfield(getfield(node, :data), fld)[getfield(node, :idx)] + fldarray = getfield(getfield(node, :data), fld) + fldidx = getfield(node, :idx) + (fld === :line) && return (fldarray[3fldidx-2], fldarray[3fldidx-1], fldarray[3fldidx-0]) + return fldarray[fldidx] end @inline function setindex!(node::Instruction, @nospecialize(val), fld::Symbol) (fld === :inst) && (fld = :stmt) # deprecated - getfield(getfield(node, :data), fld)[getfield(node, :idx)] = val + fldarray = getfield(getfield(node, :data), fld) + fldidx = getfield(node, :idx) + if fld === :line + (fldarray[3fldidx-2], fldarray[3fldidx-1], fldarray[3fldidx-0]) = val::NTuple{3,Int32} + else + fldarray[fldidx] = val + end return node end @@ -274,7 +330,7 @@ function setindex!(is::InstructionStream, newval::Instruction, idx::Int) is.stmt[idx] = newval[:stmt] is.type[idx] = newval[:type] is.info[idx] = newval[:info] - is.line[idx] = newval[:line] + (is.line[3idx-2], is.line[3idx-1], is.line[3idx-0]) = newval[:line] is.flag[idx] = newval[:flag] return is end @@ -314,14 +370,15 @@ struct NewInstruction stmt::Any type::Any info::CallInfo - line::Union{Int32,Nothing} # if nothing, copy the line from previous statement in the insertion location + line::Union{NTuple{3,Int32},Nothing} # if nothing, copy the line from previous statement in the insertion location flag::Union{UInt32,Nothing} # if nothing, IR flags will be recomputed on insertion function NewInstruction(@nospecialize(stmt), @nospecialize(type), @nospecialize(info::CallInfo), - line::Union{Int32,Nothing}, flag::Union{UInt32,Nothing}) + line::Union{NTuple{3,Int32},Int32,Nothing}, flag::Union{UInt32,Nothing}) + line isa Int32 && (line = (line, zero(Int32), zero(Int32))) return new(stmt, type, info, line, flag) end end -function NewInstruction(@nospecialize(stmt), @nospecialize(type), line::Union{Int32,Nothing}=nothing) +function NewInstruction(@nospecialize(stmt), @nospecialize(type), line::Union{NTuple{3,Int32},Int32,Nothing}=nothing) return NewInstruction(stmt, type, NoCallInfo(), line, nothing) end @nospecialize @@ -329,7 +386,7 @@ function NewInstruction(newinst::NewInstruction; stmt::Any=newinst.stmt, type::Any=newinst.type, info::CallInfo=newinst.info, - line::Union{Int32,Nothing}=newinst.line, + line::Union{NTuple{3,Int32},Int32,Nothing}=newinst.line, flag::Union{UInt32,Nothing}=newinst.flag) return NewInstruction(stmt, type, info, line, flag) end @@ -337,7 +394,7 @@ function NewInstruction(inst::Instruction; stmt::Any=inst[:stmt], type::Any=inst[:type], info::CallInfo=inst[:info], - line::Union{Int32,Nothing}=inst[:line], + line::Union{NTuple{3,Int32},Int32,Nothing}=inst[:line], flag::Union{UInt32,Nothing}=inst[:flag]) return NewInstruction(stmt, type, info, line, flag) end @@ -366,19 +423,27 @@ struct IRCode stmts::InstructionStream argtypes::Vector{Any} sptypes::Vector{VarState} - linetable::Vector{LineInfoNode} + debuginfo::DebugInfoStream cfg::CFG new_nodes::NewNodeStream meta::Vector{Expr} - function IRCode(stmts::InstructionStream, cfg::CFG, linetable::Vector{LineInfoNode}, argtypes::Vector{Any}, meta::Vector{Expr}, sptypes::Vector{VarState}) - return new(stmts, argtypes, sptypes, linetable, cfg, NewNodeStream(), meta) + function IRCode(stmts::InstructionStream, cfg::CFG, debuginfo::DebugInfoStream, argtypes::Vector{Any}, meta::Vector{Expr}, sptypes::Vector{VarState}) + return new(stmts, argtypes, sptypes, debuginfo, cfg, NewNodeStream(), meta) end function IRCode(ir::IRCode, stmts::InstructionStream, cfg::CFG, new_nodes::NewNodeStream) - return new(stmts, ir.argtypes, ir.sptypes, ir.linetable, cfg, new_nodes, ir.meta) + di = ir.debuginfo + @assert di.codelocs === stmts.line + return new(stmts, ir.argtypes, ir.sptypes, di, cfg, new_nodes, ir.meta) + end + global function copy(ir::IRCode) + di = ir.debuginfo + stmts = copy(ir.stmts) + di = copy(di) + di.edges = copy(di.edges) + di.codelocs = stmts.line + return new(stmts, copy(ir.argtypes), copy(ir.sptypes), di, copy(ir.cfg), copy(ir.new_nodes), copy(ir.meta)) end - global copy(ir::IRCode) = new(copy(ir.stmts), copy(ir.argtypes), copy(ir.sptypes), - copy(ir.linetable), copy(ir.cfg), copy(ir.new_nodes), copy(ir.meta)) end """ @@ -389,11 +454,14 @@ for debugging and unit testing of IRCode APIs. The compiler itself should genera from the frontend or one of the caches. """ function IRCode() - ir = IRCode(InstructionStream(1), CFG([BasicBlock(1:1, Int[], Int[])], Int[1]), LineInfoNode[], Any[], Expr[], VarState[]) + stmts = InstructionStream(1) + debuginfo = DebugInfoStream(stmts.line) + stmts.line[1] = 1 + ir = IRCode(stmts, CFG([BasicBlock(1:1, Int[], Int[])], Int[1]), debuginfo, Any[], Expr[], VarState[]) ir[SSAValue(1)][:stmt] = ReturnNode(nothing) ir[SSAValue(1)][:type] = Nothing ir[SSAValue(1)][:flag] = 0x00 - ir[SSAValue(1)][:line] = Int32(0) + ir[SSAValue(1)][:line] = NoLineUpdate return ir end @@ -690,6 +758,7 @@ mutable struct IncrementalCompact perm = sort!(collect(eachindex(info)); by=i::Int->(2info[i].pos+info[i].attach_after, i)) new_len = length(code.stmts) + length(info) result = InstructionStream(new_len) + code.debuginfo.codelocs = result.line used_ssas = fill(0, new_len) new_new_used_ssas = Vector{Int}() blocks = code.cfg.blocks @@ -870,7 +939,7 @@ function add_pending!(compact::IncrementalCompact, pos::Int, attach_after::Bool) end function inst_from_newinst!(node::Instruction, newinst::NewInstruction, - newline::Int32=newinst.line::Int32, newflag::UInt32=newinst.flag::UInt32) + newline::NTuple{3,Int32}=newinst.line::NTuple{3,Int32}, newflag::UInt32=newinst.flag::UInt32) node[:stmt] = newinst.stmt node[:type] = newinst.type node[:info] = newinst.info @@ -960,7 +1029,7 @@ function maybe_reopen_bb!(compact) end function insert_node_here!(compact::IncrementalCompact, newinst::NewInstruction, reverse_affinity::Bool=false) - newline = newinst.line::Int32 + newline = newinst.line::NTuple{3,Int32} refinish = false result_idx = compact.result_idx result_bbs = compact.cfg_transform.result_bbs @@ -1602,6 +1671,8 @@ function resize!(compact::IncrementalCompact, nnewnodes::Int) return compact end +const NoLineUpdate = (Int32(0), Int32(0), Int32(0)) + function finish_current_bb!(compact::IncrementalCompact, active_bb::Int, old_result_idx::Int=compact.result_idx, unreachable::Bool=false) (;result_bbs, cfg_transforms_enabled, bb_rename_succ) = compact.cfg_transform @@ -1618,9 +1689,9 @@ function finish_current_bb!(compact::IncrementalCompact, active_bb::Int, length(compact.result) < old_result_idx && resize!(compact, old_result_idx) node = compact.result[old_result_idx] if unreachable - node[:stmt], node[:type], node[:line] = ReturnNode(), Union{}, 0 + node[:stmt], node[:type], node[:line] = ReturnNode(), Union{}, NoLineUpdate else - node[:stmt], node[:type], node[:line], node[:flag] = nothing, Nothing, 0, IR_FLAGS_EFFECTS + node[:stmt], node[:type], node[:line], node[:flag] = nothing, Nothing, NoLineUpdate, IR_FLAGS_EFFECTS end compact.result_idx = old_result_idx + 1 elseif cfg_transforms_enabled && compact.result_idx - 1 == first(bb.stmts) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 3b6cfa9019d76..cda590b07d23c 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -163,6 +163,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, inst::Instruction, elseif head === :leave return false else + Core.println(stmt) error("reprocess_instruction!: unhandled expression found") end elseif isa(stmt, PhiNode) diff --git a/base/compiler/ssair/legacy.jl b/base/compiler/ssair/legacy.jl index 3e9a4e2a746dc..6d1c44375a7dd 100644 --- a/base/compiler/ssair/legacy.jl +++ b/base/compiler/ssair/legacy.jl @@ -35,13 +35,10 @@ function inflate_ir!(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{A ssavaluetypes = Any[ Any for i = 1:ssavaluetypes::Int ] end info = CallInfo[NoCallInfo() for i = 1:nstmts] - stmts = InstructionStream(code, ssavaluetypes, info, ci.codelocs, ci.ssaflags) - linetable = ci.linetable - if !isa(linetable, Vector{LineInfoNode}) - linetable = collect(LineInfoNode, linetable::Vector{Any})::Vector{LineInfoNode} - end + di = DebugInfoStream(nothing, ci.debuginfo, nstmts) + stmts = InstructionStream(code, ssavaluetypes, info, di.codelocs, ci.ssaflags) meta = Expr[] - return IRCode(stmts, cfg, linetable, argtypes, meta, sptypes) + return IRCode(stmts, cfg, di, argtypes, meta, sptypes) end """ @@ -73,15 +70,17 @@ function replace_code_newstyle!(ci::CodeInfo, ir::IRCode) stmts = ir.stmts code = ci.code = stmts.stmt ssavaluetypes = ci.ssavaluetypes = stmts.type - codelocs = ci.codelocs = stmts.line + codelocs = stmts.line ssaflags = ci.ssaflags = stmts.flag - linetable = ci.linetable = ir.linetable + debuginfo = ir.debuginfo for metanode in ir.meta push!(code, metanode) - push!(codelocs, 1) + push!(codelocs, 1, 0, 0) push!(ssavaluetypes, Any) push!(ssaflags, IR_FLAG_NULL) end + @assert debuginfo.codelocs === stmts.line "line table not from debuginfo" + ci.debuginfo = DebugInfo(debuginfo, length(code)) # Translate BB Edges to statement edges # (and undo normalization for now) for i = 1:length(code) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 4c34a6d8fe9de..946ba9633d366 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -1507,7 +1507,7 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, end src_inlining_policy(inlining.interp, src, info, IR_FLAG_NULL) || return false - src = retrieve_ir_for_inlining(code, src) + src, di = retrieve_ir_for_inlining(code, src) # For now: Require finalizer to only have one basic block length(src.cfg.blocks) == 1 || return false @@ -1516,8 +1516,8 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, add_inlining_backedge!(et, mi) # TODO: Should there be a special line number node for inlined finalizers? - inlined_at = ir[SSAValue(idx)][:line] - ssa_substitute = ir_prepare_inlining!(InsertBefore(ir, SSAValue(idx)), ir, src, mi, inlined_at, argexprs) + inline_at = ir[SSAValue(idx)][:line][1] + ssa_substitute = ir_prepare_inlining!(InsertBefore(ir, SSAValue(idx)), ir, src, di, mi, inline_at, argexprs) # TODO: Use the actual inliner here rather than open coding this special purpose inliner. ssa_rename = Vector{Any}(undef, length(src.stmts)) @@ -1529,12 +1529,8 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, ssa_rename[ssa.id] end stmt′ = ssa_substitute_op!(InsertBefore(ir, SSAValue(idx)), inst, stmt′, ssa_substitute) - newline = inst[:line] - if newline != 0 - newline += ssa_substitute.linetable_offset - end ssa_rename[idx′] = insert_node!(ir, idx, - NewInstruction(inst; stmt=stmt′, line=newline), + NewInstruction(inst; stmt=stmt′, line=(ssa_substitute.inlined_at[1], ssa_substitute.inlined_at[2], Int32(idx′))), attach_after) end diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index 3936a82a6560e..2c0385a808801 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -145,17 +145,6 @@ function show_unquoted_gotoifnot(io::IO, stmt::GotoIfNot, indent::Int, prefix::S show_unquoted(io, stmt.cond, indent) end -function compute_inlining_depth(linetable::Vector, iline::Int32) - iline == 0 && return 1 - depth = -1 - while iline != 0 - depth += 1 - lineinfo = linetable[iline]::LineInfoNode - iline = lineinfo.inlined_at - end - return depth -end - function should_print_ssa_type(@nospecialize node) if isa(node, Expr) return !(node.head in (:gc_preserve_begin, :gc_preserve_end, :meta, :leave)) @@ -171,32 +160,27 @@ function default_expr_type_printer(io::IO; @nospecialize(type), used::Bool, show return nothing end -function normalize_method_name(m) +function method_name(@nospecialize m) + if m isa LineInfoNode + m = m.method + end + if m isa MethodInstance + m = m.def + end if m isa Method - return m.name - elseif m isa MethodInstance - return (m.def::Method).name - elseif m isa Symbol + m = m.name + end + if m isa Module + return :var"top-level scope" + end + if m isa Symbol return m - else - return Symbol("") end + return :var"" end -@noinline method_name(m::LineInfoNode) = normalize_method_name(m.method) - -# converts the linetable for line numbers -# into a list in the form: -# 1 outer-most-frame -# 2 inlined-frame -# 3 innermost-frame -function compute_loc_stack(linetable::Vector, line::Int32) - stack = Int[] - while line != 0 - entry = linetable[line]::LineInfoNode - pushfirst!(stack, line) - line = entry.inlined_at - end - return stack +@noinline function normalize_method_name(@nospecialize m) + name = method_name(m) + return name === :var"" ? :none : name end """ @@ -265,83 +249,72 @@ function compute_ir_line_annotations(code::IRCode) loc_lineno = String[] cur_group = 1 last_lineno = 0 - last_stack = Int[] + last_stack = LineInfoNode[] # nb. only file, line, and method are populated in this last_printed_depth = 0 - linetable = code.linetable - lines = code.stmts.line - last_line = zero(eltype(lines)) - for idx in 1:length(lines) + debuginfo = code.debuginfo + def = :var"unknown scope" + for idx in 1:length(code.stmts) buf = IOBuffer() - line = lines[idx] print(buf, "│") - depth = compute_inlining_depth(linetable, line) - iline = line - lineno = 0 + stack = buildLineInfoNode(debuginfo, def, idx) + lineno::Int = 0 loc_method = "" - if line != 0 - stack = compute_loc_stack(linetable, line) - lineno = linetable[stack[1]].line + isempty(stack) && (stack = last_stack) + if !isempty(stack) + lineno = stack[1].line x = min(length(last_stack), length(stack)) - if length(stack) != 0 - # Compute the last depth that was in common - first_mismatch = let last_stack=last_stack - findfirst(i->last_stack[i] != stack[i], 1:x) - end - # If the first mismatch is the last stack frame, that might just - # be a line number mismatch in inner most frame. Ignore those - if length(last_stack) == length(stack) && first_mismatch == length(stack) - last_entry, entry = linetable[last_stack[end]], linetable[stack[end]] - if method_name(last_entry) === method_name(entry) && last_entry.file === entry.file - first_mismatch = nothing - end + depth = length(stack) - 1 + # Compute the last depth that was in common + first_mismatch = let last_stack=last_stack + findfirst(i->last_stack[i] != stack[i], 1:x) + end + # If the first mismatch is the last stack frame, that might just + # be a line number mismatch in inner most frame. Ignore those + if length(last_stack) == length(stack) && first_mismatch == length(stack) + last_entry, entry = last_stack[end], stack[end] + if method_name(last_entry) === method_name(entry) && last_entry.file === entry.file + first_mismatch = nothing end - last_depth = something(first_mismatch, x+1)-1 - if min(depth, last_depth) > last_printed_depth - printing_depth = min(depth, last_printed_depth + 1) - last_printed_depth = printing_depth - elseif length(stack) > length(last_stack) || first_mismatch !== nothing - printing_depth = min(depth, last_depth + 1) - last_printed_depth = printing_depth - else - printing_depth = 0 + end + last_depth = something(first_mismatch, x+1)-1 + if min(depth, last_depth) > last_printed_depth + printing_depth = min(depth, last_printed_depth + 1) + last_printed_depth = printing_depth + elseif length(stack) > length(last_stack) || first_mismatch !== nothing + printing_depth = min(depth, last_depth + 1) + last_printed_depth = printing_depth + else + printing_depth = 0 + end + stole_one = false + if printing_depth != 0 + for _ in 1:(printing_depth-1) + print(buf, "│") end - stole_one = false - if printing_depth != 0 - for _ in 1:(printing_depth-1) + if printing_depth <= last_depth-1 && first_mismatch === nothing + print(buf, "┃") + for _ in printing_depth+1:min(depth, last_depth) print(buf, "│") end - if printing_depth <= last_depth-1 && first_mismatch === nothing - print(buf, "┃") - for _ in printing_depth+1:min(depth, last_depth) - print(buf, "│") - end - else - stole_one = true - print(buf, "╻") - end else - for _ in 1:min(depth, last_depth) - print(buf, "│") - end + stole_one = true + print(buf, "╻") end - print(buf, "╷"^max(0, depth - last_depth - stole_one)) - if printing_depth != 0 - if length(stack) == printing_depth - loc_method = line - else - loc_method = stack[printing_depth + 1] - end - loc_method = method_name(linetable[loc_method]) + else + for _ in 1:min(depth, last_depth) + print(buf, "│") end - loc_method = string(" "^printing_depth, loc_method) end + print(buf, "╷"^max(0, depth - last_depth - stole_one)) + if printing_depth != 0 + loc_method = normalize_method_name(stack[printing_depth + 1]) + end + loc_method = string(" "^printing_depth, loc_method) last_stack = stack - entry = linetable[line] end push!(loc_annotations, String(take!(buf))) push!(loc_lineno, (lineno != 0 && lineno != last_lineno) ? string(lineno) : "") push!(loc_methods, loc_method) - last_line = line (lineno != 0) && (last_lineno = lineno) nothing end @@ -350,19 +323,87 @@ end Base.show(io::IO, code::Union{IRCode, IncrementalCompact}) = show_ir(io, code) +# A line_info_preprinter for disabling line info printing lineinfo_disabled(io::IO, linestart::String, idx::Int) = "" -function DILineInfoPrinter(linetable::Vector, showtypes::Bool=false) +# utility function to extract the file name from a DebugInfo object +function debuginfo_file1(debuginfo::Union{Core.DebugInfo,DebugInfoStream}) + def = debuginfo.def + if def isa MethodInstance + def = def.def + end + if def isa Method + def = def.file + end + if def isa Symbol + return def + end + return :var"" +end + +# utility function to extract the first line number and file of a block of code +function debuginfo_firstline(debuginfo::Union{Core.DebugInfo,DebugInfoStream}) + linetable = debuginfo.linetable + while linetable != nothing + debuginfo = linetable + linetable = debuginfo.linetable + end + codeloc = getdebugidx(debuginfo, 0) + return debuginfo_file1(debuginfo), codeloc[1] +end + +struct LineInfoNode + method # ::Union{Method,MethodInstance,Symbol} + file::Symbol + line::Int32 +end + +# utility function for converting a debuginfo object a particular pc to list of LineInfoNodes representing the inlining info at that pc for function `def` +# which is either `nothing` (macro-expand), a module (top-level), a Method (unspecialized code) or a MethodInstance (specialized code) +# Returns `false` if the line info should not be updated with this info because this +# statement has no effect on the line numbers. The `scopes` will still be populated however +# with as much information as was available about the inlining at that statement. +function append_scopes!(scopes::Vector{LineInfoNode}, pc::Int, debuginfo, @nospecialize(def)) + doupdate = true + while true + debuginfo.def isa Symbol || (def = debuginfo.def) + codeloc = getdebugidx(debuginfo, pc) + line::Int = codeloc[1] + inl_to::Int = codeloc[2] + doupdate &= line != 0 || inl_to != 0 # disabled debug info--no update + if debuginfo.linetable === nothing || pc <= 0 || line < 0 + line < 0 && (doupdate = false; line = 0) # broken debug info + push!(scopes, LineInfoNode(def, debuginfo_file1(debuginfo), Int32(line))) + else + doupdate = append_scopes!(scopes, line, debuginfo.linetable::Core.DebugInfo, def) && doupdate + end + inl_to == 0 && return doupdate + def = :var"macro expansion" + debuginfo = debuginfo.edges[inl_to] + pc::Int = codeloc[3] + end +end + +# utility wrapper around `append_scopes!` that returns an empty list instead of false +# when there is no applicable line update +function buildLineInfoNode(debuginfo, @nospecialize(def), pc::Int) + DI = LineInfoNode[] + append_scopes!(DI, pc, debuginfo, def) || empty!(DI) + return DI +end + +# A default line_info_preprinter for printing accurate line number information +function DILineInfoPrinter(debuginfo, def, showtypes::Bool=false) context = LineInfoNode[] context_depth = Ref(0) indent(s::String) = s^(max(context_depth[], 1) - 1) - function emit_lineinfo_update(io::IO, linestart::String, lineidx::Int32) + function emit_lineinfo_update(io::IO, linestart::String, pc::Int) # internal configuration options: linecolor = :yellow collapse = showtypes ? false : true indent_all = true - # convert lineidx to a vector - if lineidx == typemin(Int32) + # convert pc to a vector + if pc == 0 # sentinel value: reset internal (and external) state pops = indent("└") if !isempty(pops) @@ -372,13 +413,10 @@ function DILineInfoPrinter(linetable::Vector, showtypes::Bool=false) end empty!(context) context_depth[] = 0 - elseif lineidx > 0 # just skip over lines with no debug info at all - DI = LineInfoNode[] - while lineidx != 0 - entry = linetable[lineidx]::LineInfoNode - push!(DI, entry) - lineidx = entry.inlined_at - end + return "" + end + DI = reverse!(buildLineInfoNode(debuginfo, def, pc)) + if !isempty(DI) # FOR DEBUGGING, or if you just like very excessive output: # this prints out the context in full for every statement #empty!(context) @@ -508,6 +546,7 @@ function DILineInfoPrinter(linetable::Vector, showtypes::Bool=false) #end end indent_all || return "" + context_depth[] <= 1 && return "" return sprint(io -> printstyled(io, indent("│"), color=linecolor), context=io) end return emit_lineinfo_update @@ -803,18 +842,8 @@ end _strip_color(s::String) = replace(s, r"\e\[\d+m"a => "") -function statementidx_lineinfo_printer(f, code::IRCode) - printer = f(code.linetable) - function (io::IO, indent::String, idx::Int) - printer(io, indent, idx > 0 ? code.stmts[idx][:line] : typemin(Int32)) - end -end -function statementidx_lineinfo_printer(f, code::CodeInfo) - printer = f(code.linetable) - function (io::IO, indent::String, idx::Int) - printer(io, indent, idx > 0 ? code.codelocs[idx] : typemin(Int32)) - end -end +statementidx_lineinfo_printer(f, code::IRCode) = f(code.debuginfo, :var"unknown scope") +statementidx_lineinfo_printer(f, code::CodeInfo) = f(code.debuginfo, :var"unknown scope") statementidx_lineinfo_printer(code) = statementidx_lineinfo_printer(DILineInfoPrinter, code) function stmts_used(io::IO, code::IRCode, warn_unset_entry=true) diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index 7ee07585fae9b..f2bfa0e4c5476 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -161,40 +161,6 @@ function rename_uses!(ir::IRCode, ci::CodeInfo, idx::Int, @nospecialize(stmt), r return fixemup!(stmt::SlotNumber->true, stmt::SlotNumber->renames[slot_id(stmt)], ir, ci, idx, stmt) end -function strip_trailing_junk!(ci::CodeInfo, cfg::CFG, code::Vector{Any}, info::Vector{CallInfo}) - # Remove `nothing`s at the end, we don't handle them well - # (we expect the last instruction to be a terminator) - ssavaluetypes = ci.ssavaluetypes::Vector{Any} - (; codelocs, ssaflags) = ci - for i = length(code):-1:1 - if code[i] !== nothing - resize!(code, i) - resize!(ssavaluetypes, i) - resize!(codelocs, i) - resize!(info, i) - resize!(ssaflags, i) - break - end - end - # If the last instruction is not a terminator, add one. This can - # happen for implicit return on dead branches. - term = code[end] - if !isa(term, GotoIfNot) && !isa(term, GotoNode) && !isa(term, ReturnNode) - push!(code, ReturnNode()) - push!(ssavaluetypes, Union{}) - push!(codelocs, 0) - push!(info, NoCallInfo()) - push!(ssaflags, IR_FLAG_NOTHROW) - - # Update CFG to include appended terminator - old_range = cfg.blocks[end].stmts - new_range = StmtRange(first(old_range), last(old_range) + 1) - cfg.blocks[end] = BasicBlock(cfg.blocks[end], new_range) - (length(cfg.index) == length(cfg.blocks)) && (cfg.index[end] += 1) - end - nothing -end - # maybe use expr_type? function typ_for_val(@nospecialize(x), ci::CodeInfo, ir::IRCode, idx::Int, slottypes::Vector{Any}) if isa(x, Expr) @@ -464,7 +430,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) # Add an explicit goto node in the next basic block (we accounted for this above) nidx = inst_range[end] + 1 node = result[nidx] - node[:stmt], node[:type], node[:line] = GotoNode(bb_rename[bb + 1]), Any, 0 + node[:stmt], node[:type], node[:line] = GotoNode(bb_rename[bb + 1]), Any, NoLineUpdate end result[inst_range[end]][:stmt] = GotoIfNot(terminator.cond, bb_rename[terminator.dest]) elseif !isa(terminator, ReturnNode) @@ -475,7 +441,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) # Add an explicit goto node nidx = inst_range[end] + 1 node = result[nidx] - node[:stmt], node[:type], node[:line] = GotoNode(bb_rename[bb + 1]), Any, 0 + node[:stmt], node[:type], node[:line] = GotoNode(bb_rename[bb + 1]), Any, NoLineUpdate inst_range = first(inst_range):(last(inst_range) + 1) end end @@ -504,6 +470,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) end new_node[:stmt] = renumber_ssa!(new_node_inst, inst_rename, true) end + ir.debuginfo.codelocs = result.line new_ir = IRCode(ir, result, cfg, new_new_nodes) return new_ir end diff --git a/base/compiler/ssair/verify.jl b/base/compiler/ssair/verify.jl index ae294990b40d0..95ee544530810 100644 --- a/base/compiler/ssair/verify.jl +++ b/base/compiler/ssair/verify.jl @@ -111,8 +111,8 @@ function verify_ir(ir::IRCode, print::Bool=true, @verify_error "IR info length is invalid $(length(ir.stmts.info)) / $(length(ir.stmts))" error("") end - if length(ir.stmts.line) != length(ir.stmts) - @verify_error "IR line length is invalid $(length(ir.stmts.line)) / $(length(ir.stmts))" + if length(ir.stmts.line) != length(ir.stmts) * 3 + @verify_error "IR line length is invalid $(length(ir.stmts.line)) / $(length(ir.stmts) * 3)" error("") end if length(ir.stmts.flag) != length(ir.stmts) @@ -329,20 +329,6 @@ function verify_ir(ir::IRCode, print::Bool=true, @verify_error "Terminator $idx in bb $bb is not the last statement in the block" error("") else - if isa(stmt, Expr) || isa(stmt, ReturnNode) # TODO: make sure everything has line info - if (stmt isa ReturnNode) - if isdefined(stmt, :val) - # TODO: Disallow unreachable returns? - # bb_unreachable(domtree, Int64(edge)) - else - #@verify_error "Missing line number information for statement $idx of $ir" - end - end - if !(stmt isa ReturnNode && !isdefined(stmt, :val)) # not actually a return node, but an unreachable marker - if ir.stmts[idx][:line] <= 0 - end - end - end isforeigncall = false if isa(stmt, Expr) if stmt.head === :(=) @@ -397,12 +383,12 @@ function verify_ir(ir::IRCode, print::Bool=true, end end -function verify_linetable(linetable::Vector{LineInfoNode}, print::Bool=true) - for i in 1:length(linetable) - line = linetable[i] - if i <= line.inlined_at - @verify_error "Misordered linetable" - error("") +function verify_linetable(di::DebugInfoStream, nstmts::Int, print::Bool=true) + @assert 3nstmts == length(di.codelocs) + for i in 1:nstmts + edge = di.codelocs[3i-1] + if !(edge == 0 || get(di.edges, edge, nothing) isa DebugInfo) + @verify_error "Malformed debuginfo index into edges" end end end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 3ec9c1a3811ed..7113d3d6697a1 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -317,6 +317,7 @@ function CodeInstance(interp::AbstractInterpreter, result::InferenceResult; else inferred_result = transform_result_for_cache(interp, result.linfo, result.valid_worlds, result, can_discard_trees) if inferred_result isa CodeInfo + edges = inferred_result.debuginfo uncompressed = inferred_result inferred_result = maybe_compress_codeinfo(interp, result.linfo, inferred_result, can_discard_trees) result.is_src_volatile |= uncompressed !== inferred_result @@ -333,12 +334,15 @@ function CodeInstance(interp::AbstractInterpreter, result::InferenceResult; end end # n.b. relocatability = (isa(inferred_result, String) && inferred_result[end]) || inferred_result === nothing + if !@isdefined edges + edges = Core.DebugInfo(result.linfo) + end return CodeInstance(result.linfo, owner, widenconst(result_type), widenconst(result.exc_result), rettype_const, inferred_result, const_flags, first(result.valid_worlds), last(result.valid_worlds), # TODO: Actually do something with non-IPO effects encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.analysis_results, - relocatability) + relocatability, edges) end function transform_result_for_cache(interp::AbstractInterpreter, @@ -926,8 +930,7 @@ function codeinfo_for_const(interp::AbstractInterpreter, mi::MethodInstance, @no tree.slotnames = ccall(:jl_uncompress_argnames, Vector{Symbol}, (Any,), method.slot_syms) tree.slotflags = fill(0x00, nargs) tree.ssavaluetypes = 1 - tree.codelocs = Int32[1] - tree.linetable = LineInfoNode[LineInfoNode(method.module, method.name, method.file, method.line, Int32(0))] + tree.debuginfo = Core.DebugInfo(mi) tree.ssaflags = UInt32[0] set_inlineable!(tree, true) tree.parent = mi @@ -946,7 +949,7 @@ function codeinstance_for_const_with_code(interp::AbstractInterpreter, code::Cod return CodeInstance(code.def, cache_owner(interp), code.rettype, code.exctype, code.rettype_const, src, Int32(0x3), code.min_world, code.max_world, code.ipo_purity_bits, code.purity_bits, code.analysis_results, - code.relocatability) + code.relocatability, src.debuginfo) end result_is_constabi(interp::AbstractInterpreter, run_optimizer::Bool, result::InferenceResult) = @@ -1114,9 +1117,10 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance, source_mod if isa(def, Method) if ccall(:jl_get_module_infer, Cint, (Any,), def.module) == 0 && !generating_output(#=incremental=#false) src = retrieve_code_info(mi, get_inference_world(interp)) + src isa CodeInfo || return nothing return CodeInstance(mi, cache_owner(interp), Any, Any, nothing, src, Int32(0), get_inference_world(interp), get_inference_world(interp), - UInt32(0), UInt32(0), nothing, UInt8(0)) + UInt32(0), UInt32(0), nothing, UInt8(0), src.debuginfo) end end lock_mi_inference(interp, mi) diff --git a/base/expr.jl b/base/expr.jl index 94abe5b2eab67..cbeb16dd8d79b 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -33,9 +33,6 @@ macro gensym(names...) return blk end -## line numbers ## -convert(::Type{LineNumberNode}, lin::Core.LineInfoNode) = LineNumberNode(Int(lin.line), lin.file) - ## expressions ## isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head @@ -81,8 +78,6 @@ function copy(c::CodeInfo) if cnew.slottypes !== nothing cnew.slottypes = copy(cnew.slottypes::Vector{Any}) end - cnew.codelocs = copy(cnew.codelocs) - cnew.linetable = copy(cnew.linetable::Union{Vector{Any},Vector{Core.LineInfoNode}}) cnew.ssaflags = copy(cnew.ssaflags) cnew.edges = cnew.edges === nothing ? nothing : copy(cnew.edges::Vector) ssavaluetypes = cnew.ssavaluetypes @@ -1016,14 +1011,10 @@ function remove_linenums!(@nospecialize ex) for subex in ex.args subex isa Expr && remove_linenums!(subex) end - return ex elseif ex isa CodeInfo - ex.codelocs .= 0 - length(ex.linetable::Vector) > 1 && resize!(ex.linetable::Vector, 1) - return ex - else - return ex + ex.debuginfo = Core.DebugInfo(ex.debuginfo.def) # TODO: filter partially, but keep edges end + return ex end replace_linenums!(ex, ln::LineNumberNode) = ex diff --git a/base/opaque_closure.jl b/base/opaque_closure.jl index 9891db20249fb..0a265e87afb68 100644 --- a/base/opaque_closure.jl +++ b/base/opaque_closure.jl @@ -61,6 +61,8 @@ function Core.OpaqueClosure(ir::IRCode, @nospecialize env...; do_compile::Bool = true) # NOTE: we need ir.argtypes[1] == typeof(env) ir = Core.Compiler.copy(ir) + # if the user didn't specify a definition MethodInstance or filename Symbol to use for the debuginfo, set a filename now + ir.debuginfo.def === nothing && (ir.debuginfo.def = :var"generated IR for OpaqueClosure") nargs = length(ir.argtypes)-1 sig = compute_oc_signature(ir, nargs, isva) rt = compute_ir_rettype(ir) diff --git a/base/show.jl b/base/show.jl index 33832f898d240..9860575ab9d3b 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1353,8 +1353,10 @@ function show_mi(io::IO, l::Core.MethodInstance, from_stackframe::Bool=false) # added by other means. But if it isn't, then we should try # to print a little more identifying information. if !from_stackframe - linetable = l.uninferred.linetable - line = isempty(linetable) ? "unknown" : (lt = linetable[1]::Union{LineNumberNode,Core.LineInfoNode}; string(lt.file, ':', lt.line)) + di = mi.uninferred.debuginfo + file, line = IRShow.debuginfo_firstline(di) + file = string(file) + line = isempty(file) || line < 0 ? "" : "$file:$line" print(io, " from ", def, " starting at ", line) end end @@ -1377,8 +1379,10 @@ function show(io::IO, mi_info::Core.Compiler.Timings.InferenceFrameInfo) show_tuple_as_call(io, def.name, mi.specTypes; argnames, qualified=true) end else - linetable = mi.uninferred.linetable - line = isempty(linetable) ? "" : (lt = linetable[1]; string(lt.file, ':', lt.line)) + di = mi.uninferred.debuginfo + file, line = IRShow.debuginfo_firstline(di) + file = string(file) + line = isempty(file) || line < 0 ? "" : "$file:$line" print(io, "Toplevel InferenceFrameInfo thunk from ", def, " starting at ", line) end end @@ -2820,7 +2824,7 @@ module IRShow import ..Base import .Compiler: IRCode, CFG, scan_ssa_use!, isexpr, compute_basic_blocks, block_for_inst, IncrementalCompact, - Effects, ALWAYS_TRUE, ALWAYS_FALSE + Effects, ALWAYS_TRUE, ALWAYS_FALSE, DebugInfoStream, getdebugidx Base.getindex(r::Compiler.StmtRange, ind::Integer) = Compiler.getindex(r, ind) Base.size(r::Compiler.StmtRange) = Compiler.size(r) Base.first(r::Compiler.StmtRange) = Compiler.first(r) @@ -2833,9 +2837,9 @@ module IRShow include("compiler/ssair/show.jl") const __debuginfo = Dict{Symbol, Any}( - # :full => src -> Base.IRShow.statementidx_lineinfo_printer(src), # and add variable slot information - :source => src -> Base.IRShow.statementidx_lineinfo_printer(src), - # :oneliner => src -> Base.IRShow.statementidx_lineinfo_printer(Base.IRShow.PartialLineInfoPrinter, src), + # :full => src -> statementidx_lineinfo_printer(src), # and add variable slot information + :source => src -> statementidx_lineinfo_printer(src), + # :oneliner => src -> statementidx_lineinfo_printer(PartialLineInfoPrinter, src), :none => src -> Base.IRShow.lineinfo_disabled, ) const default_debuginfo = Ref{Symbol}(:none) @@ -2849,18 +2853,11 @@ function show(io::IO, src::CodeInfo; debuginfo::Symbol=:source) if src.slotnames !== nothing lambda_io = IOContext(lambda_io, :SOURCE_SLOTNAMES => sourceinfo_slotnames(src)) end - if isempty(src.linetable) || src.linetable[1] isa LineInfoNode - println(io) - # TODO: static parameter values? - # only accepts :source or :none, we can't have a fallback for default since - # that would break code_typed(, debuginfo=:source) iff IRShow.default_debuginfo[] = :none - IRShow.show_ir(lambda_io, src, IRShow.IRShowConfig(IRShow.__debuginfo[debuginfo](src))) - else - # this is a CodeInfo that has not been used as a method yet, so its locations are still LineNumberNodes - body = Expr(:block) - body.args = src.code - show(lambda_io, body) - end + println(io) + # TODO: static parameter values? + # only accepts :source or :none, we can't have a fallback for default since + # that would break code_typed(, debuginfo=:source) iff IRShow.default_debuginfo[] = :none + IRShow.show_ir(lambda_io, src, IRShow.IRShowConfig(IRShow.__debuginfo[debuginfo](src))) print(io, ")") end diff --git a/base/stacktraces.jl b/base/stacktraces.jl index e82fd8118b2d7..84d280973c28a 100644 --- a/base/stacktraces.jl +++ b/base/stacktraces.jl @@ -8,6 +8,7 @@ module StackTraces import Base: hash, ==, show import Core: CodeInfo, MethodInstance +using Base.IRShow: normalize_method_name, append_scopes!, LineInfoNode export StackTrace, StackFrame, stacktrace @@ -20,10 +21,10 @@ Stack information representing execution context, with the following fields: The name of the function containing the execution context. -- `linfo::Union{Core.MethodInstance, Method, Module, Core.CodeInfo, Nothing}` +- `linfo::Union{Method, Core.MethodInstance, Core.CodeInfo, Nothing}` - The MethodInstance or CodeInfo containing the execution context (if it could be found), \ - or Module (for macro expansions)" + The Method, MethodInstance, or CodeInfo containing the execution context (if it could be found), \ + or nothing (for example, if the inlining was a result of macro expansion). - `file::Symbol` @@ -54,8 +55,8 @@ struct StackFrame # this type should be kept platform-agnostic so that profiles "the line number in the file containing the execution context" line::Int "the MethodInstance or CodeInfo containing the execution context (if it could be found), \ - or Module (for macro expansions)" - linfo::Union{MethodInstance, Method, Module, CodeInfo, Nothing} + or nothing (for example, if the inlining was a result of macro expansion)." + linfo::Union{MethodInstance, Method, CodeInfo, Nothing} "true if the code is from C" from_c::Bool "true if the code is from an inlined frame" @@ -112,7 +113,11 @@ Base.@constprop :none function lookup(pointer::Ptr{Cvoid}) for i in 1:length(infos) info = infos[i]::Core.SimpleVector @assert(length(info) == 6) - res[i] = StackFrame(info[1]::Symbol, info[2]::Symbol, info[3]::Int, info[4], info[5]::Bool, info[6]::Bool, pointer) + func = info[1]::Symbol + file = info[2]::Symbol + linenum = info[3]::Int + linfo = info[4] + res[i] = StackFrame(func, file, linenum, linfo, info[5]::Bool, info[6]::Bool, pointer) end return res end @@ -136,17 +141,24 @@ function lookup(ip::Union{Base.InterpreterIP,Core.Compiler.InterpreterIP}) file = empty_sym line = Int32(0) end - i = max(ip.stmt+1, 1) # ip.stmt is 0-indexed - if i > length(codeinfo.codelocs) || codeinfo.codelocs[i] == 0 + def = (code isa MethodInstance ? code : StackTraces) # Module just used as a token for top-level code + pc::Int = max(ip.stmt + 1, 0) # n.b. ip.stmt is 0-indexed + scopes = LineInfoNode[] + append_scopes!(scopes, pc, codeinfo.debuginfo, def) + if isempty(scopes) return [StackFrame(func, file, line, code, false, false, 0)] end - lineinfo = codeinfo.linetable[codeinfo.codelocs[i]]::Core.LineInfoNode - scopes = StackFrame[] - while true - inlined = lineinfo.inlined_at != 0 - push!(scopes, StackFrame(Base.IRShow.method_name(lineinfo)::Symbol, lineinfo.file, lineinfo.line, inlined ? nothing : code, false, inlined, 0)) - inlined || break - lineinfo = codeinfo.linetable[lineinfo.inlined_at]::Core.LineInfoNode + inlined = false + scopes = map(scopes) do lno + if inlined + def = lno.method + def isa Union{Method,MethodInstance} || (def = nothing) + else + def = codeinfo + end + sf = StackFrame(normalize_method_name(lno.method), lno.file, lno.line, def, false, inlined, 0) + inlined = true + return sf end return scopes end diff --git a/doc/src/base/reflection.md b/doc/src/base/reflection.md index d44bc474abbd2..0f0af140b605f 100644 --- a/doc/src/base/reflection.md +++ b/doc/src/base/reflection.md @@ -100,7 +100,6 @@ as assignments, branches, and calls: ```jldoctest; setup = (using Base: +, sin) julia> Meta.lower(@__MODULE__, :( [1+2, sin(0.5)] )) :($(Expr(:thunk, CodeInfo( - @ none within `top-level scope` 1 ─ %1 = 1 + 2 │ %2 = sin(0.5) │ %3 = Base.vect(%1, %2) diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index c2cd0e92d58d2..9efcd7974091f 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -767,3 +767,80 @@ Boolean properties: * 0x01 << 4 = the syntactic control flow within this method is guaranteed to terminate (`:terminates_locally`) See the documentation of `Base.@assume_effects` for more details. + + +How to interpret line numbers in a `CodeInfo` object: + +There are 2 common forms for this data: one used internally that compresses the data somewhat and one used in the compiler. +They contain the same basic info, but the compiler version is all mutable while the version used internally is not. + +Many consumers may be able to call `Base.IRShow.buildLineInfoNode`, +`Base.IRShow.append_scopes!`, or `Stacktraces.lookup(::InterpreterIP)` to avoid needing to +(re-)implement these details specifically. + +The definitions of each of these are: + +``` +struct Core.DebugInfo + @noinline + def::Union{Method,MethodInstance,Symbol} + linetable::Union{Nothing,DebugInfo} + edges::SimpleVector{DebugInfo} + codelocs::String # compressed data +end +mutable struct Core.Compiler.DebugInfoStream + def::Union{Method,MethodInstance,Symbol} + linetable::Union{Nothing,DebugInfo} + edges::Vector{DebugInfo} + firstline::Int32 # the starting line for this block (specified by an index of 0) + codelocs::Vector{Int32} # for each statement: + # index into linetable (if defined), else a line number (in the file represented by def) + # then index into edges + # then index into edges[linetable] +end +``` + + + * `def` : where this DebugInfo was defined (the Method, MethodInstance, or file scope, for example) + + * `linetable` + + Another debuginfo that this was derived from, which contains the actual line numbers, + such that this DebugInfo contains only the indexes into it. This avoids making copies, + as well as makes it possible to track how each individual statement transformed from + source to optimized, not just the separate line numbers. If `def` is not a Symbol, then + that object replaces the current function object for the metadata on what function is + conceptually being executed (e.g. think Cassette transforms here). The `codelocs` values + described below also are interpreted as an index into the `codelocs` in this object, + instead of being a line number itself. + + * `edges` : Vector of the unique DebugInfo for every function inlined into this (which + recursively have the edges for everything inlined into them). + + * `firstline` (when uncompressed to DebugInfoStream) + + The line number associated with the `begin` statement (or other keyword such as + `function` or `quote`) that delinated where this code definition "starts". + + * `codelocs` (when uncompressed to DebugInfoStream) + + A vector of indices, with 3 values for each statement in the IR plus one for the + starting point of the block, that describe the stacktrace from that point: + 1. the integer index into the `linetable.codelocs` field, giving the + original location associated with each statement (including its syntactic edges), + or zero indicating no change to the line number from the previously + executed statement (which is not necessarily syntactic or lexical prior). + or + the line number itself if the `linetable` field is `nothing` + 2. the integer index into edges, giving the DebugInfo inlined there (or zero if there + are no edges). + 3. (if entry 2 is non-zero) the integer index into edges[].codelocs, to interpret + recursively for each function in the inlining stack, or zero indicating to use + `edges[].firstline` as the line number. + + Special codes include: + - (zero, zero, *) : no change to the line number or edges from the previous statement + (you may choose to interpret this either syntactically or lexically). The inlining + depth also might have changed, though most callers should ignore that. + - (zero, non-zero, *) : no line number, just edges (usually because of macro-expansion into + top-level code) diff --git a/src/ast.c b/src/ast.c index bd3460cbe7fd2..a7c6ae3bc218c 100644 --- a/src/ast.c +++ b/src/ast.c @@ -576,20 +576,16 @@ static jl_value_t *scm_to_julia_(fl_context_t *fl_ctx, value_t e, jl_module_t *m JL_GC_POP(); return temp; } - else if (sym == jl_lineinfo_sym && n == 5) { - jl_value_t *modu=NULL, *name=NULL, *file=NULL, *linenum=NULL, *inlinedat=NULL; - JL_GC_PUSH5(&modu, &name, &file, &linenum, &inlinedat); + else if (sym == jl_lineinfo_sym && n == 3) { + jl_value_t *file=NULL, *linenum=NULL, *inlinedat=NULL; + JL_GC_PUSH3(&file, &linenum, &inlinedat); value_t lst = e; - modu = scm_to_julia_(fl_ctx, car_(lst), mod); - lst = cdr_(lst); - name = scm_to_julia_(fl_ctx, car_(lst), mod); - lst = cdr_(lst); file = scm_to_julia_(fl_ctx, car_(lst), mod); lst = cdr_(lst); linenum = scm_to_julia_(fl_ctx, car_(lst), mod); lst = cdr_(lst); inlinedat = scm_to_julia_(fl_ctx, car_(lst), mod); - temp = jl_new_struct(jl_lineinfonode_type, modu, name, file, linenum, inlinedat); + temp = jl_new_struct(jl_lineinfonode_type, file, linenum, inlinedat); JL_GC_POP(); return temp; } @@ -938,10 +934,6 @@ JL_DLLEXPORT jl_value_t *jl_copy_ast(jl_value_t *expr) jl_gc_wb(new_ci, new_ci->slotnames); new_ci->slotflags = jl_array_copy(new_ci->slotflags); jl_gc_wb(new_ci, new_ci->slotflags); - new_ci->codelocs = (jl_value_t*)jl_array_copy((jl_array_t*)new_ci->codelocs); - jl_gc_wb(new_ci, new_ci->codelocs); - new_ci->linetable = (jl_value_t*)jl_array_copy((jl_array_t*)new_ci->linetable); - jl_gc_wb(new_ci, new_ci->linetable); new_ci->ssaflags = jl_array_copy(new_ci->ssaflags); jl_gc_wb(new_ci, new_ci->ssaflags); diff --git a/src/builtins.c b/src/builtins.c index 29aec53ae5a40..9dca73a784fc7 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -2510,7 +2510,8 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin("Expr", (jl_value_t*)jl_expr_type); add_builtin("LineNumberNode", (jl_value_t*)jl_linenumbernode_type); - add_builtin("LineInfoNode", (jl_value_t*)jl_lineinfonode_type); + add_builtin("LegacyLineInfoNode", (jl_value_t*)jl_lineinfonode_type); + add_builtin("DebugInfo", (jl_value_t*)jl_debuginfo_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); diff --git a/src/clangsa/GCChecker.cpp b/src/clangsa/GCChecker.cpp index 97def0fac59f2..60471f1909f02 100644 --- a/src/clangsa/GCChecker.cpp +++ b/src/clangsa/GCChecker.cpp @@ -812,6 +812,7 @@ bool GCChecker::isGCTrackedType(QualType QT) { //Name.endswith_lower("jl_genericmemoryref_t") || Name.endswith_lower("jl_method_t") || Name.endswith_lower("jl_method_instance_t") || + Name.endswith_lower("jl_debuginfo_t") || Name.endswith_lower("jl_tupletype_t") || Name.endswith_lower("jl_datatype_t") || Name.endswith_lower("jl_typemap_entry_t") || diff --git a/src/codegen.cpp b/src/codegen.cpp index faa9de2762471..eaa9335659fb1 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -7925,10 +7925,14 @@ static jl_llvm_functions_t toplineno = lam->def.method->line; ctx.file = jl_symbol_name(lam->def.method->file); } - else if (jl_array_nrows(src->linetable) > 0) { - jl_value_t *locinfo = jl_array_ptr_ref(src->linetable, 0); - ctx.file = jl_symbol_name((jl_sym_t*)jl_fieldref_noalloc(locinfo, 2)); - toplineno = jl_unbox_int32(jl_fieldref(locinfo, 3)); + else if ((jl_value_t*)src->debuginfo != jl_nothing) { + // look for the file and line info of the original start of this block, as reported by lowering + jl_debuginfo_t *debuginfo = src->debuginfo; + while ((jl_value_t*)debuginfo->linetable != jl_nothing) + debuginfo = debuginfo->linetable; + ctx.file = jl_debuginfo_file(debuginfo); + struct jl_codeloc_t lineidx = jl_uncompress1_codeloc(debuginfo->codelocs, 0); + toplineno = std::max((int32_t)0, lineidx.line); } if (ctx.file.empty()) ctx.file = ""; @@ -8011,8 +8015,8 @@ static jl_llvm_functions_t //Safe because params holds ctx lock Module *M = TSM.getModuleUnlocked(); M->addModuleFlag(Module::Warning, "julia.debug_level", ctx.emission_context.debug_level); - jl_debugcache_t debuginfo; - debuginfo.initialize(M); + jl_debugcache_t debugcache; + debugcache.initialize(M); jl_returninfo_t returninfo = {}; Function *f = NULL; bool has_sret = false; @@ -8164,11 +8168,11 @@ static jl_llvm_functions_t topfile = dbuilder.createFile(ctx.file, "."); DISubroutineType *subrty; if (ctx.emission_context.debug_level <= 1) - subrty = debuginfo.jl_di_func_null_sig; + subrty = debugcache.jl_di_func_null_sig; else if (!specsig) - subrty = debuginfo.jl_di_func_sig; + subrty = debugcache.jl_di_func_sig; else - subrty = get_specsig_di(ctx, debuginfo, jlrettype, lam->specTypes, dbuilder); + subrty = get_specsig_di(ctx, debugcache, jlrettype, lam->specTypes, dbuilder); SP = dbuilder.createFunction(nullptr ,dbgFuncName // Name ,f->getName() // LinkageName @@ -8199,7 +8203,7 @@ static jl_llvm_functions_t topfile, // File toplineno == -1 ? 0 : toplineno, // Line // Variable type - julia_type_to_di(ctx, debuginfo, varinfo.value.typ, &dbuilder, false), + julia_type_to_di(ctx, debugcache, varinfo.value.typ, &dbuilder, false), AlwaysPreserve, // May be deleted if optimized out DINode::FlagZero); // Flags (TODO: Do we need any) } @@ -8210,7 +8214,7 @@ static jl_llvm_functions_t has_sret + nreq + 1, // Argument number (1-based) topfile, // File toplineno == -1 ? 0 : toplineno, // Line (for now, use lineno of the function) - julia_type_to_di(ctx, debuginfo, ctx.slots[ctx.vaSlot].value.typ, &dbuilder, false), + julia_type_to_di(ctx, debugcache, ctx.slots[ctx.vaSlot].value.typ, &dbuilder, false), AlwaysPreserve, // May be deleted if optimized out DINode::FlagZero); // Flags (TODO: Do we need any) } @@ -8225,7 +8229,7 @@ static jl_llvm_functions_t jl_symbol_name(s), // Variable name topfile, // File toplineno == -1 ? 0 : toplineno, // Line (for now, use lineno of the function) - julia_type_to_di(ctx, debuginfo, varinfo.value.typ, &dbuilder, false), // Variable type + julia_type_to_di(ctx, debugcache, varinfo.value.typ, &dbuilder, false), // Variable type AlwaysPreserve, // May be deleted if optimized out DINode::FlagZero // Flags (TODO: Do we need any) ); @@ -8267,7 +8271,7 @@ static jl_llvm_functions_t // step 7. allocate local variables slots // must be in the first basic block for the llvm mem2reg pass to work - auto allocate_local = [&ctx, &dbuilder, &debuginfo, topdebugloc, va, debug_enabled, M](jl_varinfo_t &varinfo, jl_sym_t *s, int i) { + auto allocate_local = [&ctx, &dbuilder, &debugcache, topdebugloc, va, debug_enabled, M](jl_varinfo_t &varinfo, jl_sym_t *s, int i) { jl_value_t *jt = varinfo.value.typ; assert(!varinfo.boxroot); // variables shouldn't have memory locs already if (varinfo.value.constant) { @@ -8318,7 +8322,7 @@ static jl_llvm_functions_t varinfo.value = mark_julia_slot(lv, jt, NULL, ctx.tbaa().tbaa_stack); alloc_def_flag(ctx, varinfo); if (debug_enabled && varinfo.dinfo) { - assert((Metadata*)varinfo.dinfo->getType() != debuginfo.jl_pvalue_dillvmt); + assert((Metadata*)varinfo.dinfo->getType() != debugcache.jl_pvalue_dillvmt); dbuilder.insertDeclare(lv, varinfo.dinfo, dbuilder.createExpression(), topdebugloc, ctx.builder.GetInsertBlock()); @@ -8334,7 +8338,7 @@ static jl_llvm_functions_t if (debug_enabled && varinfo.dinfo) { SmallVector addr; DIExpression *expr; - if ((Metadata*)varinfo.dinfo->getType() != debuginfo.jl_pvalue_dillvmt) + if ((Metadata*)varinfo.dinfo->getType() != debugcache.jl_pvalue_dillvmt) addr.push_back(llvm::dwarf::DW_OP_deref); expr = dbuilder.createExpression(addr); dbuilder.insertDeclare(av, varinfo.dinfo, expr, @@ -8541,7 +8545,7 @@ static jl_llvm_functions_t addr.push_back(llvm::dwarf::DW_OP_deref); addr.push_back(llvm::dwarf::DW_OP_plus_uconst); addr.push_back((i - 1) * sizeof(void*)); - if ((Metadata*)vi.dinfo->getType() != debuginfo.jl_pvalue_dillvmt) + if ((Metadata*)vi.dinfo->getType() != debugcache.jl_pvalue_dillvmt) addr.push_back(llvm::dwarf::DW_OP_deref); dbuilder.insertDeclare(pargArray, vi.dinfo, dbuilder.createExpression(addr), topdebugloc, @@ -8627,7 +8631,7 @@ static jl_llvm_functions_t return (!jl_is_submodule(mod, jl_base_module) && !jl_is_submodule(mod, jl_core_module)); }; - auto in_tracked_path = [] (StringRef file) { + auto in_tracked_path = [] (StringRef file) { // falls within an explicitly set file or directory return jl_options.tracked_path != NULL && file.startswith(jl_options.tracked_path); }; bool mod_is_user_mod = in_user_mod(ctx.module); @@ -8636,82 +8640,108 @@ static jl_llvm_functions_t DebugLoc loc; StringRef file; ssize_t line; + ssize_t line0; // if this represents pc=1, then also cover the entry to the function (pc=0) bool is_user_code; - bool is_tracked; // falls within an explicitly set file or directory - unsigned inlined_at; - bool operator ==(const DebugLineTable &other) const { - return other.loc == loc && other.file == file && other.line == line && other.is_user_code == is_user_code && other.is_tracked == is_tracked && other.inlined_at == inlined_at; - } + int32_t edgeid; + bool sameframe(const DebugLineTable &other) const { + // detect if the line info for this frame is unchanged (equivalent to loc == other.loc ignoring the inlined_at field) + return other.edgeid == edgeid && other.line == line; + }; }; - SmallVector linetable; - { // populate the linetable data format - assert(jl_is_array(src->linetable)); - size_t nlocs = jl_array_nrows(src->linetable); - std::map, DISubprogram*> subprograms; - linetable.resize(nlocs + 1); - DebugLineTable &topinfo = linetable[0]; - topinfo.file = ctx.file; - topinfo.line = toplineno; - topinfo.is_user_code = mod_is_user_mod; - topinfo.is_tracked = mod_is_tracked; - topinfo.inlined_at = 0; - topinfo.loc = topdebugloc; - for (size_t i = 0; i < nlocs; i++) { - // LineInfoNode(mod::Module, method::Any, file::Symbol, line::Int32, inlined_at::Int32) - jl_value_t *locinfo = jl_array_ptr_ref(src->linetable, i); - DebugLineTable &info = linetable[i + 1]; - assert(jl_typetagis(locinfo, jl_lineinfonode_type)); - jl_module_t *module = (jl_module_t*)jl_fieldref_noalloc(locinfo, 0); - jl_value_t *method = jl_fieldref_noalloc(locinfo, 1); - jl_sym_t *filesym = (jl_sym_t*)jl_fieldref_noalloc(locinfo, 2); - info.line = jl_unbox_int32(jl_fieldref(locinfo, 3)); - info.inlined_at = jl_unbox_int32(jl_fieldref(locinfo, 4)); - assert(info.inlined_at <= i); - info.file = jl_symbol_name(filesym); - if (info.file.empty()) - info.file = ""; - if (module == ctx.module) - info.is_user_code = mod_is_user_mod; - else - info.is_user_code = in_user_mod(module); - info.is_tracked = in_tracked_path(info.file); - if (debug_enabled) { - StringRef fname; - if (jl_is_method_instance(method)) - method = ((jl_method_instance_t*)method)->def.value; - if (jl_is_method(method)) - method = (jl_value_t*)((jl_method_t*)method)->name; - if (jl_is_symbol(method)) - fname = jl_symbol_name((jl_sym_t*)method); - if (fname.empty()) - fname = "macro expansion"; - if (info.inlined_at == 0 && info.file == ctx.file) { // if everything matches, emit a toplevel line number - info.loc = DILocation::get(ctx.builder.getContext(), info.line, 0, SP, NULL); + DebugLineTable topinfo; + topinfo.file = ctx.file; + topinfo.line = toplineno; + topinfo.line0 = 0; + topinfo.is_user_code = mod_is_user_mod; + topinfo.loc = topdebugloc; + topinfo.edgeid = 0; + std::map, DISubprogram*> subprograms; + SmallVector prev_lineinfo, new_lineinfo; + auto update_lineinfo = [&] (size_t pc) { + std::function append_lineinfo = + [&] (jl_debuginfo_t *debuginfo, jl_value_t *func, size_t to, size_t pc) -> bool { + while (1) { + if (!jl_is_symbol(debuginfo->def)) // this is a path + func = debuginfo->def; // this is inlined + struct jl_codeloc_t lineidx = jl_uncompress1_codeloc(debuginfo->codelocs, pc); + size_t i = lineidx.line; + if (i < 0) // pc out of range: broken debuginfo? + return false; + if (i == 0 && lineidx.to == 0) // no update + return false; + if (pc > 0 && (jl_value_t*)debuginfo->linetable != jl_nothing) { + // indirection node + if (!append_lineinfo(debuginfo->linetable, func, to, i)) + return false; // no update } - else { // otherwise, describe this as an inlining frame - DISubprogram *&inl_SP = subprograms[std::make_tuple(fname, info.file)]; - if (inl_SP == NULL) { - DIFile *difile = dbuilder.createFile(info.file, "."); - inl_SP = dbuilder.createFunction(difile - ,std::string(fname) + ";" // Name - ,fname // LinkageName - ,difile // File - ,0 // LineNo - ,debuginfo.jl_di_func_null_sig // Ty - ,0 // ScopeLine - ,DINode::FlagZero // Flags - ,DISubprogram::SPFlagDefinition | DISubprogram::SPFlagOptimized // SPFlags - ,nullptr // Template Parameters - ,nullptr // Template Declaration - ,nullptr // ThrownTypes - ); + else { + // actual node + DebugLineTable info; + info.edgeid = to; + jl_module_t *modu = func ? jl_debuginfo_module1(func) : NULL; + if (modu == NULL) + modu = ctx.module; + info.file = jl_debuginfo_file1(debuginfo); + info.line = i; + info.line0 = 0; + if (pc == 1) { + struct jl_codeloc_t lineidx = jl_uncompress1_codeloc(debuginfo->codelocs, 0); + assert(lineidx.to == 0 && lineidx.pc == 0); + if (lineidx.line > 0 && info.line != lineidx.line) + info.line0 = lineidx.line; + } + if (info.file.empty()) + info.file = ""; + if (modu == ctx.module) + info.is_user_code = mod_is_user_mod; + else + info.is_user_code = in_user_mod(modu); + if (debug_enabled) { + StringRef fname = jl_debuginfo_name(func); + if (new_lineinfo.empty() && info.file == ctx.file) { // if everything matches, emit a toplevel line number + info.loc = DILocation::get(ctx.builder.getContext(), info.line, 0, SP, NULL); + } + else { // otherwise, describe this as an inlining frame + DebugLoc inl_loc = new_lineinfo.empty() ? DebugLoc(DILocation::get(ctx.builder.getContext(), 0, 0, SP, NULL)) : new_lineinfo.back().loc; + DISubprogram *&inl_SP = subprograms[std::make_tuple(fname, info.file)]; + if (inl_SP == NULL) { + DIFile *difile = dbuilder.createFile(info.file, "."); + inl_SP = dbuilder.createFunction(difile + ,std::string(fname) + ";" // Name + ,fname // LinkageName + ,difile // File + ,0 // LineNo + ,debugcache.jl_di_func_null_sig // Ty + ,0 // ScopeLine + ,DINode::FlagZero // Flags + ,DISubprogram::SPFlagDefinition | DISubprogram::SPFlagOptimized // SPFlags + ,nullptr // Template Parameters + ,nullptr // Template Declaration + ,nullptr // ThrownTypes + ); + } + info.loc = DILocation::get(ctx.builder.getContext(), info.line, 0, inl_SP, inl_loc); + } } - DebugLoc inl_loc = (info.inlined_at == 0) ? DebugLoc(DILocation::get(ctx.builder.getContext(), 0, 0, SP, NULL)) : linetable[info.inlined_at].loc; - info.loc = DILocation::get(ctx.builder.getContext(), info.line, 0, inl_SP, inl_loc); + new_lineinfo.push_back(info); } + to = lineidx.to; + if (to == 0) + return true; + pc = lineidx.pc; + debuginfo = (jl_debuginfo_t*)jl_svecref(debuginfo->edges, to - 1); + func = NULL; } - } - } + }; + prev_lineinfo.resize(0); + std::swap(prev_lineinfo, new_lineinfo); + bool updated = append_lineinfo(src->debuginfo, (jl_value_t*)lam, 0, pc + 1); + if (!updated) + std::swap(prev_lineinfo, new_lineinfo); + else + assert(new_lineinfo.size() > 0); + return updated; + }; SmallVector aliasscopes; MDNode* current_aliasscope = nullptr; @@ -8817,44 +8847,67 @@ static jl_llvm_functions_t (in_user_code && malloc_log_mode == JL_LOG_USER) || (is_tracked && malloc_log_mode == JL_LOG_PATH))); }; - SmallVector current_lineinfo, new_lineinfo; - auto coverageVisitStmt = [&] (size_t dbg) { - if (dbg == 0 || dbg >= linetable.size()) - return; - // Compute inlining stack for current line, inner frame first - while (dbg) { - new_lineinfo.push_back(dbg); - dbg = linetable[dbg].inlined_at; - } + auto coverageVisitStmt = [&] () { // Visit frames which differ from previous statement as tracked in - // current_lineinfo (tracked outer frame first). - current_lineinfo.resize(new_lineinfo.size(), 0); + // prev_lineinfo (tracked outer frame first). + size_t dbg; for (dbg = 0; dbg < new_lineinfo.size(); dbg++) { - unsigned newdbg = new_lineinfo[new_lineinfo.size() - dbg - 1]; - if (newdbg != current_lineinfo[dbg]) { - current_lineinfo[dbg] = newdbg; - const auto &info = linetable[newdbg]; - if (do_coverage(info.is_user_code, info.is_tracked)) - coverageVisitLine(ctx, info.file, info.line); + if (dbg >= prev_lineinfo.size() || !new_lineinfo[dbg].sameframe(prev_lineinfo[dbg])) + break; + } + for (; dbg < new_lineinfo.size(); dbg++) { + const auto &newdbg = new_lineinfo[dbg]; + bool is_tracked = in_tracked_path(newdbg.file); + if (do_coverage(newdbg.is_user_code, is_tracked)) { + if (newdbg.line0 != 0 && (dbg >= prev_lineinfo.size() || newdbg.edgeid != prev_lineinfo[dbg].edgeid || newdbg.line0 != prev_lineinfo[dbg].line)) + coverageVisitLine(ctx, newdbg.file, newdbg.line0); + coverageVisitLine(ctx, newdbg.file, newdbg.line); } } - new_lineinfo.clear(); }; - auto mallocVisitStmt = [&] (unsigned dbg, Value *sync) { - if (!do_malloc_log(mod_is_user_mod, mod_is_tracked) || dbg == 0) { + auto mallocVisitStmt = [&] (Value *sync, bool have_dbg_update) { + if (!do_malloc_log(mod_is_user_mod, mod_is_tracked) || !have_dbg_update) { + // TODD: add || new_lineinfo[0].sameframe(prev_lineinfo[0])) above, but currently this breaks the test for it (by making an optimization better) if (do_malloc_log(true, mod_is_tracked) && sync) ctx.builder.CreateCall(prepare_call(sync_gc_total_bytes_func), {sync}); return; } - while (linetable[dbg].inlined_at) - dbg = linetable[dbg].inlined_at; - mallocVisitLine(ctx, ctx.file, linetable[dbg].line, sync); + mallocVisitLine(ctx, new_lineinfo[0].file, new_lineinfo[0].line, sync); }; if (coverage_mode != JL_LOG_NONE) { // record all lines that could be covered - for (const auto &info : linetable) - if (do_coverage(info.is_user_code, info.is_tracked)) - jl_coverage_alloc_line(info.file, info.line); + std::function record_line_exists = [&](jl_debuginfo_t *debuginfo, jl_value_t *func) { + if (!jl_is_symbol(debuginfo->def)) // this is a path + func = debuginfo->def; // this is inlined + for (size_t i = 0; i < jl_svec_len(debuginfo->edges); i++) { + jl_debuginfo_t *edge = (jl_debuginfo_t*)jl_svecref(debuginfo->edges, i); + record_line_exists(edge, NULL); + } + while ((jl_value_t*)debuginfo->linetable != jl_nothing) + debuginfo = debuginfo->linetable; + jl_module_t *modu = func ? jl_debuginfo_module1(func) : NULL; + if (modu == NULL) + modu = ctx.module; + StringRef file = jl_debuginfo_file1(debuginfo); + if (file.empty()) + file = ""; + bool is_user_code; + if (modu == ctx.module) + is_user_code = mod_is_user_mod; + else + is_user_code = in_user_mod(modu); + bool is_tracked = in_tracked_path(file); + if (do_coverage(is_user_code, is_tracked)) { + for (size_t pc = 0; 1; pc++) { + struct jl_codeloc_t lineidx = jl_uncompress1_codeloc(debuginfo->codelocs, pc); + if (lineidx.line == -1) + break; + if (lineidx.line > 0) + jl_coverage_alloc_line(file, lineidx.line); + } + } + }; + record_line_exists(src->debuginfo, (jl_value_t*)lam); } come_from_bb[0] = ctx.builder.GetInsertBlock(); @@ -8906,26 +8959,20 @@ static jl_llvm_functions_t BB[label] = bb; } + new_lineinfo.push_back(topinfo); Value *sync_bytes = nullptr; if (do_malloc_log(true, mod_is_tracked)) sync_bytes = ctx.builder.CreateCall(prepare_call(diff_gc_total_bytes_func), {}); - { // coverage for the function definition line number - const auto &topinfo = linetable[0]; - if (linetable.size() > 1) { - if (topinfo == linetable[1]) - current_lineinfo.push_back(1); - } - if (do_coverage(topinfo.is_user_code, topinfo.is_tracked)) - coverageVisitLine(ctx, topinfo.file, topinfo.line); - } + // coverage for the function definition line number (topinfo) + coverageVisitStmt(); find_next_stmt(0); while (cursor != -1) { - int32_t debuginfoloc = jl_array_data(src->codelocs, int32_t)[cursor]; - if (debuginfoloc > 0) { + bool have_dbg_update = update_lineinfo(cursor); + if (have_dbg_update) { if (debug_enabled) - ctx.builder.SetCurrentDebugLocation(linetable[debuginfoloc].loc); - coverageVisitStmt(debuginfoloc); + ctx.builder.SetCurrentDebugLocation(new_lineinfo.back().loc); + coverageVisitStmt(); } ctx.noalias().aliasscope.current = aliasscopes[cursor]; jl_value_t *stmt = jl_array_ptr_ref(stmts, cursor); @@ -9033,7 +9080,7 @@ static jl_llvm_functions_t } } - mallocVisitStmt(debuginfoloc, sync_bytes); + mallocVisitStmt(sync_bytes, have_dbg_update); if (toplevel || ctx.is_opaque_closure) ctx.builder.CreateStore(last_age, world_age_field); assert(type_is_ghost(retty) || returninfo.cc == jl_returninfo_t::SRet || @@ -9063,7 +9110,7 @@ static jl_llvm_functions_t jl_value_t *cond = jl_gotoifnot_cond(stmt); int lname = jl_gotoifnot_label(stmt); Value *isfalse = emit_condition(ctx, cond, "if"); - mallocVisitStmt(debuginfoloc, nullptr); + mallocVisitStmt(nullptr, have_dbg_update); come_from_bb[cursor+1] = ctx.builder.GetInsertBlock(); workstack.push_back(lname - 1); BasicBlock *ifnot = BB[lname]; @@ -9140,7 +9187,7 @@ static jl_llvm_functions_t } else { emit_stmtpos(ctx, stmt, cursor); - mallocVisitStmt(debuginfoloc, nullptr); + mallocVisitStmt(nullptr, have_dbg_update); } find_next_stmt(cursor + 1); } @@ -9560,6 +9607,7 @@ jl_llvm_functions_t jl_emit_codeinst( return jl_llvm_functions_t(); // failed } } + assert(jl_egal((jl_value_t*)jl_atomic_load_relaxed(&codeinst->debuginfo), (jl_value_t*)src->debuginfo) && "trying to generate code for a codeinst for an incompatible src"); jl_llvm_functions_t decls = jl_emit_code(m, codeinst->def, src, codeinst->rettype, params, jl_atomic_load_relaxed(&codeinst->min_world), jl_atomic_load_relaxed(&codeinst->max_world)); @@ -9593,6 +9641,7 @@ jl_llvm_functions_t jl_emit_codeinst( jl_options.debug_level > 1) { // update the stored code if (inferred != (jl_value_t*)src) { + // TODO: it is somewhat unclear what it means to be mutating this if (jl_is_method(def)) { src = (jl_code_info_t*)jl_compress_ir(def, src); assert(jl_is_string(src)); diff --git a/src/gf.c b/src/gf.c index 88890b7e5f203..db6b5a22d4cbb 100644 --- a/src/gf.c +++ b/src/gf.c @@ -320,7 +320,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_nothing, (jl_value_t*)jl_any_type, (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, NULL); jl_mi_cache_insert(mi, codeinst); jl_atomic_store_relaxed(&codeinst->specptr.fptr1, fptr); jl_atomic_store_relaxed(&codeinst->invoke, jl_fptr_args); @@ -442,7 +442,7 @@ JL_DLLEXPORT jl_value_t *jl_call_in_typeinf_world(jl_value_t **args, int nargs) JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( jl_method_instance_t *mi JL_PROPAGATES_ROOT, jl_value_t *rettype, - size_t min_world, size_t max_world) + size_t min_world, size_t max_world, jl_debuginfo_t *edges) { jl_value_t *owner = jl_nothing; // TODO: owner should be arg jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); @@ -451,25 +451,44 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( jl_atomic_load_relaxed(&codeinst->max_world) == max_world && jl_egal(codeinst->owner, owner) && jl_egal(codeinst->rettype, rettype)) { - return codeinst; + if (edges == NULL) + return codeinst; + jl_debuginfo_t *debuginfo = jl_atomic_load_relaxed(&codeinst->debuginfo); + if (edges == debuginfo) + return codeinst; + if (debuginfo == NULL && jl_atomic_cmpswap_relaxed(&codeinst->debuginfo, &debuginfo, edges)) + return codeinst; + if (debuginfo && jl_egal((jl_value_t*)debuginfo, (jl_value_t*)edges)) + return codeinst; } codeinst = jl_atomic_load_relaxed(&codeinst->next); } codeinst = jl_new_codeinst( mi, owner, rettype, (jl_value_t*)jl_any_type, NULL, NULL, - 0, min_world, max_world, 0, 0, jl_nothing, 0); + 0, min_world, max_world, 0, 0, jl_nothing, 0, edges); jl_mi_cache_insert(mi, codeinst); return codeinst; } +//JL_DLLEXPORT jl_code_instance_t *jl_get_codeinst_for_src( +// jl_method_instance_t *mi JL_PROPAGATES_ROOT, jl_code_info_t *src) +//{ +// // TODO: copy backedges from src to mi +// size_t max_world = src->max_world; +// if (max_world >= jl_atomic_load_acquire(&jl_world_counter)) +// max_world = ~(size_t)0; +// // TODO: this should clone all fields from src that it could to avoid poisoning the IPO cache +// return jl_get_method_inferred(mi, src->rettype, src->min_world, max_world, src->debuginfo); +//} + JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_method_instance_t *mi, jl_value_t *owner, jl_value_t *rettype, jl_value_t *exctype, 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 *analysis_results, - uint8_t relocatability - /*, jl_array_t *edges, int absolute_max*/) + uint8_t relocatability, + jl_debuginfo_t *edges /* , int absolute_max*/) { jl_task_t *ct = jl_current_task; assert(min_world <= max_world && "attempting to set invalid world constraints"); @@ -482,10 +501,10 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( codeinst->rettype = rettype; codeinst->exctype = exctype; jl_atomic_store_release(&codeinst->inferred, inferred); - //codeinst->edges = NULL; if ((const_flags & 2) == 0) inferred_const = NULL; codeinst->rettype_const = inferred_const; + jl_atomic_store_relaxed(&codeinst->debuginfo, edges); jl_atomic_store_relaxed(&codeinst->specptr.fptr, NULL); jl_atomic_store_relaxed(&codeinst->invoke, NULL); if ((const_flags & 1) != 0) { @@ -2297,16 +2316,6 @@ JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, jl_value_t * return ml_matches((jl_methtable_t*)mt, types, lim, include_ambiguous, 1, world, 1, min_valid, max_valid, ambig); } -jl_method_instance_t *jl_get_unspecialized_from_mi(jl_method_instance_t *method JL_PROPAGATES_ROOT) -{ - jl_method_t *def = method->def.method; - jl_method_instance_t *mi = jl_get_unspecialized(def); - if (mi == NULL) { - return method; - } - return mi; -} - jl_method_instance_t *jl_get_unspecialized(jl_method_t *def JL_PROPAGATES_ROOT) { // one unspecialized version of a function can be shared among all cached specializations @@ -2438,9 +2447,11 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t jl_code_instance_t *codeinst2 = jl_compile_method_internal(mi2, world); jl_code_instance_t *codeinst = jl_get_method_inferred( mi, codeinst2->rettype, - jl_atomic_load_relaxed(&codeinst2->min_world), jl_atomic_load_relaxed(&codeinst2->max_world)); + jl_atomic_load_relaxed(&codeinst2->min_world), jl_atomic_load_relaxed(&codeinst2->max_world), + jl_atomic_load_relaxed(&codeinst2->debuginfo)); if (jl_atomic_load_relaxed(&codeinst->invoke) == NULL) { codeinst->rettype_const = codeinst2->rettype_const; + jl_gc_wb(codeinst, codeinst->rettype_const); uint8_t specsigflags = jl_atomic_load_acquire(&codeinst2->specsigflags); jl_callptr_t invoke = jl_atomic_load_acquire(&codeinst2->invoke); void *fptr = jl_atomic_load_relaxed(&codeinst2->specptr.fptr); @@ -2486,7 +2497,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t // if compilation is disabled or source is unavailable, try calling unspecialized version if (compile_option == JL_OPTIONS_COMPILE_OFF || compile_option == JL_OPTIONS_COMPILE_MIN || - def->source == jl_nothing) { + (jl_is_method(def) && def->source == jl_nothing)) { // copy fptr from the template method definition if (jl_is_method(def)) { jl_method_instance_t *unspecmi = jl_atomic_load_relaxed(&def->unspecialized); @@ -2496,7 +2507,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_nothing, (jl_value_t*)jl_any_type, (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, NULL); void *unspec_fptr = jl_atomic_load_relaxed(&unspec->specptr.fptr); if (unspec_fptr) { // wait until invoke and specsigflags are properly set @@ -2523,7 +2534,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_nothing, (jl_value_t*)jl_any_type, (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, NULL); jl_atomic_store_release(&codeinst->invoke, jl_fptr_interpret_call); jl_mi_cache_insert(mi, codeinst); record_precompile_statement(mi); @@ -2575,13 +2586,16 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t JL_GC_POP(); } if (!codeinst) { - jl_method_instance_t *unspec = jl_get_unspecialized_from_mi(mi); - jl_code_instance_t *ucache = jl_get_method_inferred(unspec, (jl_value_t*)jl_any_type, 1, ~(size_t)0); + jl_method_instance_t *unspec = jl_get_unspecialized(def); + if (unspec == NULL) + unspec = mi; + jl_code_instance_t *ucache = jl_get_method_inferred(unspec, (jl_value_t*)jl_any_type, 1, ~(size_t)0, NULL); // ask codegen to make the fptr for unspec jl_callptr_t ucache_invoke = jl_atomic_load_acquire(&ucache->invoke); if (ucache_invoke == NULL) { - if (def->source == jl_nothing && (jl_atomic_load_relaxed(&ucache->def->uninferred) == jl_nothing || - jl_atomic_load_relaxed(&ucache->def->uninferred) == NULL)) { + if ((!jl_is_method(def) || def->source == jl_nothing) && + (jl_atomic_load_relaxed(&ucache->def->uninferred) == jl_nothing || + jl_atomic_load_relaxed(&ucache->def->uninferred) == NULL)) { jl_throw(jl_new_struct(jl_missingcodeerror_type, (jl_value_t*)mi)); } jl_generate_fptr_for_unspecialized(ucache); @@ -2596,7 +2610,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t } codeinst = jl_new_codeinst(mi, jl_nothing, (jl_value_t*)jl_any_type, (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, NULL); void *unspec_fptr = jl_atomic_load_relaxed(&ucache->specptr.fptr); if (unspec_fptr) { // wait until invoke and specsigflags are properly set diff --git a/src/ircode.c b/src/ircode.c index a89d0140e0d88..196f9f7b413ad 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -367,11 +367,6 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) write_uint8(s->s, TAG_UINT8); write_int8(s->s, *(int8_t*)jl_data_ptr(v)); } - else if (jl_typetagis(v, jl_lineinfonode_type)) { - write_uint8(s->s, TAG_LINEINFO); - for (i = 0; i < jl_datatype_nfields(jl_lineinfonode_type); i++) - jl_encode_value(s, jl_get_nth_field(v, i)); - } else if (((jl_datatype_t*)jl_typeof(v))->instance == v) { write_uint8(s->s, TAG_SINGLETON); jl_encode_value(s, jl_typeof(v)); @@ -666,7 +661,7 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED { assert(!ios_eof(s->s)); jl_value_t *v; - size_t i, n; + size_t n; uint64_t key; uint8_t tag = read_uint8(s->s); if (tag > LAST_TAG) @@ -782,13 +777,6 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED v = jl_alloc_string(n); ios_readall(s->s, jl_string_data(v), n); return v; - case TAG_LINEINFO: - v = jl_new_struct_uninit(jl_lineinfonode_type); - for (i = 0; i < jl_datatype_nfields(jl_lineinfonode_type); i++) { - //size_t offs = jl_field_offset(jl_lineinfonode_type, i); - set_nth_field(jl_lineinfonode_type, v, i, jl_decode_value(s), 0); - } - return v; default: assert(tag == TAG_GENERAL || tag == TAG_SHORT_GENERAL); return jl_decode_value_any(s, tag); @@ -799,6 +787,40 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED typedef jl_value_t jl_string_t; // for local expressibility +static size_t codelocs_parseheader(jl_string_t *cl, int *line_offset, int *line_bytes, int *to_bytes) JL_NOTSAFEPOINT +{ + if (jl_string_len(cl) == 0) { + *line_offset = *line_bytes = *to_bytes = 0; + return 0; + } + int32_t header[3]; + memcpy(&header, (char*)jl_string_data(cl), sizeof(header)); + *line_offset = header[0]; + if (header[1] < 255) + *line_bytes = 1; + else if (header[1] < 65535) + *line_bytes = 2; + else + *line_bytes = 4; + if (header[2] == 0) + *to_bytes = 0; + else if (header[2] < 255) + *to_bytes = 1; + else if (header[2] < 65535) + *to_bytes = 2; + else + *to_bytes = 4; + assert(jl_string_len(cl) >= sizeof(header) + *line_bytes); + return (jl_string_len(cl) - sizeof(header) - *line_bytes) / (*line_bytes + *to_bytes * 2); // compute nstmts +} +#ifndef NDEBUG +static int codelocs_nstmts(jl_string_t *cl) JL_NOTSAFEPOINT +{ + int line_offset, line_bytes, to_bytes; + return codelocs_parseheader(cl, &line_offset, &line_bytes, &to_bytes); +} +#endif + #define IR_DATASIZE_FLAGS sizeof(uint8_t) #define IR_DATASIZE_PURITY sizeof(uint16_t) #define IR_DATASIZE_INLINING_COST sizeof(uint16_t) @@ -815,8 +837,12 @@ JL_DLLEXPORT jl_string_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) { JL_TIMING(AST_COMPRESS, AST_COMPRESS); JL_LOCK(&m->writelock); // protect the roots array (Might GC) + int isdef = code == NULL; + if (isdef) + code = (jl_code_info_t*)m->source; assert(jl_is_method(m)); assert(jl_is_code_info(code)); + assert(jl_array_nrows(code->code) == codelocs_nstmts(code->debuginfo->codelocs) || jl_string_len(code->debuginfo->codelocs) == 0); ios_t dest; ios_mem(&dest, 0); int en = jl_gc_enable(0); // Might GC @@ -854,8 +880,8 @@ JL_DLLEXPORT jl_string_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) for (i = 0; i < 5; i++) { int copy = 1; - if (i == 1) { // skip codelocs - assert(jl_field_offset(jl_code_info_type, i) == offsetof(jl_code_info_t, codelocs)); + if (i == 1) { // skip debuginfo + assert(jl_field_offset(jl_code_info_type, i) == offsetof(jl_code_info_t, debuginfo)); continue; } jl_encode_value_(&s, jl_get_nth_field((jl_value_t*)code, i), copy); @@ -872,22 +898,6 @@ JL_DLLEXPORT jl_string_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) else jl_encode_value(&s, jl_nothing); - size_t nstmt = jl_array_nrows(code->code); - assert(nstmt == jl_array_nrows(code->codelocs)); - if (jl_array_nrows(code->linetable) < 256) { - for (i = 0; i < nstmt; i++) { - write_uint8(s.s, jl_array_data(code->codelocs, int32_t)[i]); - } - } - else if (jl_array_nrows(code->linetable) < 65536) { - for (i = 0; i < nstmt; i++) { - write_uint16(s.s, jl_array_data(code->codelocs, int32_t)[i]); - } - } - else { - ios_write(s.s, (char*)jl_array_data(code->codelocs, int32_t), nstmt * sizeof(int32_t)); - } - write_uint8(s.s, s.relocatability); ios_flush(s.s); @@ -941,7 +951,7 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t ios_readall(s.s, jl_array_data(code->slotflags, char), nslots); for (i = 0; i < 5; i++) { - if (i == 1) // skip codelocs + if (i == 1) // skip debuginfo continue; assert(jl_field_isptr(jl_code_info_type, i)); jl_value_t **fld = (jl_value_t**)((char*)jl_data_ptr(code) + jl_field_offset(jl_code_info_type, i)); @@ -955,25 +965,17 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t slotnames = m->slot_syms; code->slotnames = jl_uncompress_argnames(slotnames); - size_t nstmt = jl_array_nrows(code->code); - code->codelocs = (jl_value_t*)jl_alloc_array_1d(jl_array_int32_type, nstmt); - if (jl_array_nrows(code->linetable) < 256) { - for (i = 0; i < nstmt; i++) { - jl_array_data(code->codelocs, int32_t)[i] = read_uint8(s.s); - } - } - else if (jl_array_nrows(code->linetable) < 65536) { - for (i = 0; i < nstmt; i++) { - jl_array_data(code->codelocs, int32_t)[i] = read_uint16(s.s); - } - } - else { - ios_readall(s.s, (char*)jl_array_data(code->codelocs, int32_t), nstmt * sizeof(int32_t)); - } + if (metadata) + code->debuginfo = jl_atomic_load_relaxed(&metadata->debuginfo); + else + code->debuginfo = m->debuginfo; + assert(code->debuginfo); + assert(jl_array_nrows(code->code) == codelocs_nstmts(code->debuginfo->codelocs) || jl_string_len(code->debuginfo->codelocs) == 0); (void) read_uint8(s.s); // relocatability - + assert(!ios_eof(s.s)); assert(ios_getc(s.s) == -1); + ios_close(s.s); JL_GC_PUSH1(&code); jl_gc_enable(en); @@ -1108,6 +1110,245 @@ JL_DLLEXPORT jl_value_t *jl_uncompress_argname_n(jl_value_t *syms, size_t i) return jl_nothing; } +// codelocs are compressed as follows: +// The input vector is a NTuple{3,UInt32} (struct jl_codeloc_t) +// The vector is scanned for min and max of the values for each element +// The output is then allocated to hold (min-line, max-line, max-at) first, then line - min (in the smallest space), then the remainder (in the smallest space) +static inline struct jl_codeloc_t unpack_codeloc(jl_string_t *cl, size_t pc, int line_offset, int line_bytes, int to_bytes) JL_NOTSAFEPOINT +{ + const char *ptr = jl_string_data(cl) + sizeof(int32_t[3]); + if (pc == 0) + to_bytes = 0; + else + ptr += line_bytes + (pc - 1) * (line_bytes + to_bytes * 2); + uint8_t int8; + uint16_t int16; + uint32_t int32; + struct jl_codeloc_t codeloc; + switch (line_bytes) { + case 0: + codeloc.line = 0; + break; + case 1: + memcpy(&int8, ptr, 1); + codeloc.line = int8; + break; + case 2: + memcpy(&int16, ptr, 2); + codeloc.line = int16; + break; + case 4: + memcpy(&int32, ptr, 4); + codeloc.line = int32; + break; + } + if (codeloc.line > 0) + codeloc.line += line_offset - 1; + ptr += line_bytes; + switch (to_bytes) { + case 0: + codeloc.to = 0; + break; + case 1: + memcpy(&int8, ptr, 1); + codeloc.to = int8; + break; + case 2: + memcpy(&int16, ptr, 2); + codeloc.to = int16; + break; + case 4: + memcpy(&int32, ptr, 4); + codeloc.to = int32; + break; + } + ptr += to_bytes; + switch (to_bytes) { + case 0: + codeloc.pc = 0; + break; + case 1: + memcpy(&int8, ptr, 1); + codeloc.pc = int8; + break; + case 2: + memcpy(&int16, ptr, 2); + codeloc.pc = int16; + break; + case 3: + memcpy(&int32, ptr, 4); + codeloc.pc = int32; + break; + } + ptr += to_bytes; + return codeloc; +} + + +static const struct jl_codeloc_t badloc = {-1, 0, 0}; + +JL_DLLEXPORT struct jl_codeloc_t jl_uncompress1_codeloc(jl_string_t *cl, size_t pc) JL_NOTSAFEPOINT +{ + assert(jl_is_string(cl)); + int line_offset, line_bytes, to_bytes; + size_t nstmts = codelocs_parseheader(cl, &line_offset, &line_bytes, &to_bytes); + if (pc > nstmts) + return badloc; + return unpack_codeloc(cl, pc, line_offset, line_bytes, to_bytes); +} + +static int allzero(jl_value_t *codelocs) JL_NOTSAFEPOINT +{ + int32_t *p = jl_array_data(codelocs,int32_t); + int32_t *pend = p + jl_array_nrows(codelocs); + do { + if (*p) + return 0; + } while (++p < pend); + return 1; +} + +JL_DLLEXPORT jl_string_t *jl_compress_codelocs(int32_t firstline, jl_value_t *codelocs, size_t nstmts) // firstline+Vector{Int32} => Memory{UInt8} +{ + assert(jl_typeis(codelocs, jl_array_int32_type)); + if (jl_array_nrows(codelocs) == 0) + nstmts = 0; + assert(nstmts * 3 == jl_array_nrows(codelocs)); + if (allzero(codelocs)) + return jl_an_empty_string; + struct jl_codeloc_t codeloc, min, max; + size_t i; + min.line = min.to = min.pc = firstline <= 0 ? INT32_MAX : firstline; + max.line = max.to = max.pc = 0; + for (i = 0; i < nstmts; i++) { + memcpy(&codeloc, jl_array_data(codelocs,int32_t) + 3 * i, sizeof(codeloc)); +#define SETMIN(x) if (codeloc.x < min.x) min.x = codeloc.x +#define SETMAX(x) if (codeloc.x > max.x) max.x = codeloc.x + if (codeloc.line > 0) + SETMIN(line); + SETMAX(line); + SETMIN(to); + SETMAX(to); + SETMIN(pc); + SETMAX(pc); +#undef SETMIN +#undef SETMAX + } + min.line = min.to = min.pc = firstline <= 0 ? INT32_MAX : firstline; + int32_t header[3]; + header[0] = min.line > max.line ? 0 : min.line; + header[1] = min.line > max.line ? 0 : max.line - min.line; + header[2] = max.to > max.pc ? max.to : max.pc; + size_t line_bytes; + if (header[1] < 255) + line_bytes = 1; + else if (header[1] < 65535) + line_bytes = 2; + else + line_bytes = 4; + size_t to_bytes; + if (header[2] == 0) + to_bytes = 0; + else if (header[2] < 255) + to_bytes = 1; + else if (header[2] < 65535) + to_bytes = 2; + else + to_bytes = 4; + jl_string_t *cl = jl_alloc_string(sizeof(header) + line_bytes + nstmts * (line_bytes + to_bytes * 2)); + // store header structure + memcpy(jl_string_data(cl), &header, sizeof(header)); + // pack bytes + char *ptr = jl_string_data(cl) + sizeof(header); + uint8_t int8; + uint16_t int16; + uint32_t int32; + { // store firstline value + int8 = int16 = int32 = firstline > 0 ? firstline - header[0] + 1 : 0; + switch (line_bytes) { + case 0: + break; + case 1: + memcpy(ptr, &int8, 1); + break; + case 2: + memcpy(ptr, &int16, 2); + break; + case 4: + memcpy(ptr, &int32, 4); + break; + } + ptr += line_bytes; + } + for (i = 0; i < nstmts; i++) { + memcpy(&codeloc, jl_array_data(codelocs,int32_t) + 3 * i, sizeof(codeloc)); + int8 = int16 = int32 = codeloc.line > 0 ? codeloc.line - header[0] + 1 : 0; + switch (line_bytes) { + case 0: + break; + case 1: + memcpy(ptr, &int8, 1); + break; + case 2: + memcpy(ptr, &int16, 2); + break; + case 4: + memcpy(ptr, &int32, 4); + break; + } + ptr += line_bytes; + int8 = int16 = int32 = codeloc.to; + switch (to_bytes) { + case 0: + break; + case 1: + memcpy(ptr, &int8, 1); + break; + case 2: + memcpy(ptr, &int16, 2); + break; + case 4: + memcpy(ptr, &int32, 4); + break; + } + ptr += to_bytes; + int8 = int16 = int32 = codeloc.pc; + switch (to_bytes) { + case 0: + break; + case 1: + memcpy(ptr, &int8, 1); + break; + case 2: + memcpy(ptr, &int16, 2); + break; + case 4: + memcpy(ptr, &int32, 4); + break; + } + ptr += to_bytes; + } + return cl; +} + +JL_DLLEXPORT jl_value_t *jl_uncompress_codelocs(jl_string_t *cl, size_t nstmts) // Memory{UInt8} => Vector{Int32} +{ + assert(jl_is_string(cl)); + int line_offset, line_bytes, to_bytes; + size_t nlocs = codelocs_parseheader(cl, &line_offset, &line_bytes, &to_bytes); + assert(nlocs == 0 || nlocs == nstmts); + jl_value_t *codelocs = (jl_value_t*)jl_alloc_array_1d(jl_array_int32_type, nstmts * 3); + size_t i; + for (i = 0; i < nlocs; i++) { + struct jl_codeloc_t codeloc = unpack_codeloc(cl, i + 1, line_offset, line_bytes, to_bytes);; + memcpy(jl_array_data(codelocs,int32_t) + i * 3, &codeloc, sizeof(codeloc)); + } + if (nlocs == 0) { + memset(jl_array_data(codelocs,int32_t), 0, nstmts * sizeof(struct jl_codeloc_t)); + } + return codelocs; +} + void jl_init_serializer(void) { jl_task_t *ct = jl_current_task; @@ -1186,7 +1427,6 @@ void jl_init_serializer(void) deser_tag[TAG_INT32] = (jl_value_t*)jl_int32_type; deser_tag[TAG_INT64] = (jl_value_t*)jl_int64_type; deser_tag[TAG_UINT8] = (jl_value_t*)jl_uint8_type; - deser_tag[TAG_LINEINFO] = (jl_value_t*)jl_lineinfonode_type; deser_tag[TAG_UNIONALL] = (jl_value_t*)jl_unionall_type; deser_tag[TAG_GOTONODE] = (jl_value_t*)jl_gotonode_type; deser_tag[TAG_QUOTENODE] = (jl_value_t*)jl_quotenode_type; diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index c3ccb5746e37a..b7fb975515eb4 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -507,6 +507,9 @@ void jl_generate_fptr_for_unspecialized_impl(jl_code_instance_t *unspec) if (src) { assert(jl_is_code_info(src)); ++UnspecFPtrCount; + jl_debuginfo_t *debuginfo = src->debuginfo; + jl_atomic_store_release(&unspec->debuginfo, debuginfo); // n.b. this assumes the field was previously NULL, which is not entirely true + jl_gc_wb(unspec, debuginfo); _jl_compile_codeinst(unspec, src, *jl_ExecutionEngine->getContext()); } jl_callptr_t null = nullptr; diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index f40b252180a65..79ff437841879 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -35,6 +35,7 @@ XX(jl_const_type) \ XX(jl_core_module) \ XX(jl_datatype_type) \ + XX(jl_debuginfo_type) \ XX(jl_densearray_type) \ XX(jl_diverror_exception) \ XX(jl_emptysvec) \ diff --git a/src/jltypes.c b/src/jltypes.c index 896e8f7b676c8..c8d4044e97646 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3074,10 +3074,10 @@ void jl_init_types(void) JL_GC_DISABLED jl_emptysvec, 0, 0, 2); jl_lineinfonode_type = - jl_new_datatype(jl_symbol("LineInfoNode"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(5, "module", "method", "file", "line", "inlined_at"), - jl_svec(5, jl_module_type, jl_any_type, jl_symbol_type, jl_int32_type, jl_int32_type), - jl_emptysvec, 0, 0, 5); + jl_new_datatype(jl_symbol("LegacyLineInfoNode"), core, jl_any_type, jl_emptysvec, + jl_perm_symsvec(3, "file", "line", "inlined_at"), + jl_svec(3, jl_symbol_type, jl_int32_type, jl_int32_type), + jl_emptysvec, 0, 0, 3); jl_gotonode_type = jl_new_datatype(jl_symbol("GotoNode"), core, jl_any_type, jl_emptysvec, @@ -3139,15 +3139,30 @@ void jl_init_types(void) JL_GC_DISABLED jl_svec(1, jl_slotnumber_type), jl_emptysvec, 0, 0, 1); + jl_debuginfo_type = + jl_new_datatype(jl_symbol("DebugInfo"), core, + jl_any_type, jl_emptysvec, + jl_perm_symsvec(4, + "def", + "linetable", + "edges", + "codelocs"), + jl_svec(4, + jl_any_type, // union(jl_method_instance_type, jl_method_type, jl_symbol_type), + jl_any_type, // union(jl_nothing, jl_debuginfo_type) + jl_simplevector_type, // memory{debuginfo} + jl_string_type), + jl_emptysvec, 0, 0, 4); + jl_debuginfo_type->name->mayinlinealloc = 0; + jl_code_info_type = jl_new_datatype(jl_symbol("CodeInfo"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(20, + jl_perm_symsvec(19, "code", - "codelocs", + "debuginfo", "ssavaluetypes", "ssaflags", - "linetable", "slotnames", "slotflags", "slottypes", @@ -3163,12 +3178,11 @@ void jl_init_types(void) JL_GC_DISABLED "constprop", "purity", "inlining_cost"), - jl_svec(20, + jl_svec(19, jl_array_any_type, - jl_array_int32_type, + jl_debuginfo_type, jl_any_type, jl_array_uint32_type, - jl_any_type, jl_array_symbol_type, jl_array_uint8_type, jl_any_type, @@ -3185,12 +3199,12 @@ void jl_init_types(void) JL_GC_DISABLED jl_uint16_type, jl_uint16_type), jl_emptysvec, - 0, 1, 20); + 0, 1, 19); 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", @@ -3203,6 +3217,7 @@ void jl_init_types(void) JL_GC_DISABLED "slot_syms", "external_mt", "source", // !const + "debuginfo", // !const "unspecialized", // !const "generator", // !const "roots", // !const @@ -3221,7 +3236,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, @@ -3234,6 +3249,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_string_type, jl_any_type, jl_any_type, + jl_debuginfo_type, jl_any_type, // jl_method_instance_type jl_any_type, jl_array_any_type, @@ -3254,7 +3270,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_uint16_type), jl_emptysvec, 0, 1, 10); - //const static uint32_t method_constfields[1] = { 0x03fc065f }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<6)|(1<<9)|(1<<10)|(1<<18)|(1<<19)|(1<<20)|(1<<21)|(1<<22)|(1<<23)|(1<<24)|(1<<25); + //const static uint32_t method_constfields[1] = { 0b0 }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<6)|(1<<9)|(1<<10)|(1<<17)|(1<<21)|(1<<22)|(1<<23)|(1<<24)|(1<<25)|(1<<26)|(1<<27)|(1<<28)|(1<<29)|(1<<30); //jl_method_type->name->constfields = method_constfields; const static uint32_t method_atomicfields[1] = { 0x00000030 }; // (1<<4)|(1<<5) jl_method_type->name->atomicfields = method_atomicfields; @@ -3278,7 +3294,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_simplevector_type, jl_any_type, jl_array_any_type, - jl_any_type, + jl_any_type/*jl_code_instance_type*/, jl_bool_type, jl_bool_type, jl_bool_type), @@ -3294,7 +3310,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(17, + jl_perm_symsvec(18, "def", "owner", "next", @@ -3304,13 +3320,13 @@ void jl_init_types(void) JL_GC_DISABLED "exctype", "rettype_const", "inferred", - //"edges", + "debuginfo", // TODO: rename to edges? //"absolute_max", "ipo_purity_bits", "purity_bits", "analysis_results", "specsigflags", "precompile", "relocatability", "invoke", "specptr"), // function object decls - jl_svec(17, + jl_svec(18, jl_method_instance_type, jl_any_type, jl_any_type, @@ -3320,7 +3336,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, jl_any_type, jl_any_type, - //jl_any_type, + jl_debuginfo_type, //jl_bool_type, jl_uint32_type, jl_uint32_type, jl_any_type, @@ -3331,10 +3347,11 @@ void jl_init_types(void) JL_GC_DISABLED jl_emptysvec, 0, 1, 1); jl_svecset(jl_code_instance_type->types, 2, jl_code_instance_type); - const static uint32_t code_instance_constfields[1] = { 0b00000101011100011 }; // Set fields 1, 2, 6-8, 10, 12 as const - const static uint32_t code_instance_atomicfields[1] = { 0b11011010100011100 }; // Set fields 3-5, 9, 11, 13-14, 16-17 as atomic + const static uint32_t code_instance_constfields[1] = { 0b000001010011100011 }; // Set fields 1, 2, 6-8, 11, 13 as const + const static uint32_t code_instance_atomicfields[1] = { 0b110110101100011100 }; // Set fields 3-5, 9, 10, 12, 14-15, 17-18 as atomic //Fields 4-5 are only operated on by construction and deserialization, so are const at runtime - //Fields 12 and 16 must be protected by locks, and thus all operations on jl_code_instance_t are threadsafe + //Fields 13 and 17 must be protected by locks, and thus all operations on jl_code_instance_t are threadsafe + //Except for field 9 (inferred), which is volatile unless you know which other places are currently using it jl_code_instance_type->name->constfields = code_instance_constfields; jl_code_instance_type->name->atomicfields = code_instance_atomicfields; @@ -3472,10 +3489,11 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_methtable_type->types, 8, jl_long_type); // uint32_t plus alignment jl_svecset(jl_methtable_type->types, 9, jl_uint8_type); jl_svecset(jl_methtable_type->types, 10, jl_uint8_type); - jl_svecset(jl_method_type->types, 12, jl_method_instance_type); + jl_svecset(jl_method_type->types, 13, jl_method_instance_type); + //jl_svecset(jl_debuginfo_type->types, 0, jl_method_instance_type); // union(jl_method_instance_type, jl_method_type, jl_symbol_type) jl_svecset(jl_method_instance_type->types, 5, jl_code_instance_type); - jl_svecset(jl_code_instance_type->types, 15, jl_voidpointer_type); jl_svecset(jl_code_instance_type->types, 16, jl_voidpointer_type); + jl_svecset(jl_code_instance_type->types, 17, jl_voidpointer_type); jl_svecset(jl_binding_type->types, 1, jl_globalref_type); jl_svecset(jl_binding_type->types, 2, jl_binding_type); @@ -3504,9 +3522,6 @@ void jl_init_types(void) JL_GC_DISABLED // Module object identity is determined by its name and parent name. jl_module_type->isidentityfree = 1; - // override the preferred layout for a couple types - jl_lineinfonode_type->name->mayinlinealloc = 0; // FIXME: assumed to be a pointer by codegen - export_jl_small_typeof(); } diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index d3d4f7dfe3a56..079d4155fa6d0 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -5021,13 +5021,13 @@ f(x) = yt(x) (list ,@(cadr vi)) ,(caddr vi) (list ,@(cadddr vi))) ,@(cdddr lam)))) -(define (make-lineinfo name file line (inlined-at #f)) - `(lineinfo (thismodule) ,(if inlined-at '|macro expansion| name) ,file ,line ,(or inlined-at 0))) +(define (make-lineinfo file line (inlined-at #f)) + `(lineinfo ,file ,line ,(or inlined-at 0))) (define (set-lineno! lineinfo num) - (set-car! (cddddr lineinfo) num)) + (set-car! (cddr lineinfo) num)) -(define (compact-ir body name file line) +(define (compact-ir body file line) (let ((code '(block)) (locs '(list)) (linetable '(list)) @@ -5044,7 +5044,7 @@ f(x) = yt(x) (or e (raise "missing value in IR")) (if (and (null? (cdr linetable)) (not (and (pair? e) (eq? (car e) 'meta)))) - (begin (set! linetable (cons (make-lineinfo name file line) linetable)) + (begin (set! linetable (cons (make-lineinfo file line) linetable)) (set! linetablelen (+ linetablelen 1)) (set! current-loc 1))) (set! code (cons e code)) @@ -5071,8 +5071,8 @@ f(x) = yt(x) (if (pair? (cddr e)) (set! current-file (caddr e))) (set! linetable (cons (if (null? locstack) - (make-lineinfo name current-file current-line) - (make-lineinfo name current-file current-line (caar locstack))) + (make-lineinfo current-file current-line) + (make-lineinfo current-file current-line (caar locstack))) linetable)) (set! linetablelen (+ linetablelen 1)) (set! current-loc linetablelen))))) @@ -5080,7 +5080,7 @@ f(x) = yt(x) (set! locstack (cons (list current-loc current-line current-file) locstack)) (set! current-file (caddr e)) (set! current-line 0) - (set! linetable (cons (make-lineinfo name current-file current-line current-loc) linetable)) + (set! linetable (cons (make-lineinfo current-file current-line current-loc) linetable)) (set! linetablelen (+ linetablelen 1)) (set! current-loc linetablelen)) ((and (length= e 2) (eq? (car e) 'meta) (eq? (cadr e) 'pop_loc)) @@ -5106,7 +5106,6 @@ f(x) = yt(x) (define (renumber-lambda lam file line) (let* ((stuff (compact-ir (lam:body lam) - (if (null? (cadr lam)) '|top-level scope| 'none) file line)) (code (aref stuff 0)) (locs (aref stuff 1)) diff --git a/src/julia.h b/src/julia.h index 5c3a983367470..f43e945da9c23 100644 --- a/src/julia.h +++ b/src/julia.h @@ -245,6 +245,19 @@ typedef struct _jl_line_info_node_t { int32_t inlined_at; } jl_line_info_node_t; +struct jl_codeloc_t { + int32_t line; + int32_t to; + int32_t pc; +}; + +typedef struct _jl_debuginfo_t { + jl_value_t *def; + struct _jl_debuginfo_t *linetable; // or nothing + jl_svec_t *edges; // Memory{DebugInfo} + jl_value_t *codelocs; // String // Memory{UInt8} // compressed info +} jl_debuginfo_t; + // the following mirrors `struct EffectsOverride` in `base/compiler/effects.jl` typedef union __jl_purity_overrides_t { struct { @@ -272,7 +285,7 @@ typedef union __jl_purity_overrides_t { typedef struct _jl_code_info_t { // ssavalue-indexed arrays of properties: jl_array_t *code; // Any array of statements - jl_value_t *codelocs; // Int32 array of indices into the line table + jl_debuginfo_t *debuginfo; // Table of edge data for each statement jl_value_t *ssavaluetypes; // types of ssa values (or count of them) jl_array_t *ssaflags; // 32 bits flags associated with each statement: // 1 << 0 = inbounds region @@ -289,7 +302,6 @@ typedef struct _jl_code_info_t { // 1 << 11 = :inaccessiblemem_or_argmemonly // 1 << 12-19 = callsite effects overrides // miscellaneous data: - jl_value_t *linetable; // Table of locations [TODO: make this volatile like slotnames] jl_array_t *slotnames; // names of local variables jl_array_t *slotflags; // local var bit flags // the following are optional transient properties (not preserved by compression--as they typically get stored elsewhere): @@ -300,7 +312,7 @@ typedef struct _jl_code_info_t { // They are not used by any other part of the system and may be moved elsewhere in the // future. jl_value_t *method_for_inference_limit_heuristics; // optional method used during inference - jl_value_t *edges; // forward edges to method instances that must be invalidated + jl_value_t *edges; // forward edges to method instances that must be invalidated (for copying to debuginfo) size_t min_world; size_t max_world; @@ -337,6 +349,7 @@ typedef struct _jl_method_t { jl_value_t *slot_syms; // compacted list of slot names (String) jl_value_t *external_mt; // reference to the method table this method is part of, null if part of the internal table jl_value_t *source; // original code template (jl_code_info_t, but may be compressed), null for builtins + jl_debuginfo_t *debuginfo; // fixed linetable from the source argument, null if not available _Atomic(jl_method_instance_t*) unspecialized; // unspecialized executable method instance, or null jl_value_t *generator; // executable code-generating function if available jl_array_t *roots; // pointers in generated code (shared to reduce memory), or null @@ -432,7 +445,7 @@ typedef struct _jl_code_instance_t { // deleted to save space. // - null, indicating that inference was not yet completed or did not succeed _Atomic(jl_value_t *) inferred; - //TODO: jl_array_t *edges; // stored information about edges from this object + _Atomic(jl_debuginfo_t *) debuginfo; // stored information about edges from this object (set once, with a happens-before both source and invoke) //TODO: uint8_t absolute_max; // whether true max world is unknown // purity results @@ -822,6 +835,7 @@ extern JL_DLLIMPORT jl_value_t *jl_bottom_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_method_instance_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_code_instance_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_code_info_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_datatype_t *jl_debuginfo_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_method_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_module_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_unionall_t *jl_addrspace_type JL_GLOBALLY_ROOTED; @@ -2145,7 +2159,9 @@ JL_DLLEXPORT uint8_t jl_ir_slotflag(jl_value_t *data, size_t i) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_compress_argnames(jl_array_t *syms); JL_DLLEXPORT jl_array_t *jl_uncompress_argnames(jl_value_t *syms); JL_DLLEXPORT jl_value_t *jl_uncompress_argname_n(jl_value_t *syms, size_t i); - +JL_DLLEXPORT struct jl_codeloc_t jl_uncompress1_codeloc(jl_value_t *cl, size_t pc) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_value_t *jl_compress_codelocs(int32_t firstline, jl_value_t *codelocs, size_t nstmts); +JL_DLLEXPORT jl_value_t *jl_uncompress_codelocs(jl_value_t *cl, size_t nstmts); JL_DLLEXPORT int jl_is_operator(const char *sym); JL_DLLEXPORT int jl_is_unary_operator(const char *sym); diff --git a/src/julia_internal.h b/src/julia_internal.h index 76556e288f387..6fdb65903636b 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -651,17 +651,21 @@ JL_DLLEXPORT jl_code_instance_t *jl_type_infer(jl_method_instance_t *li, size_t JL_DLLEXPORT jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *meth JL_PROPAGATES_ROOT, size_t world); JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( jl_method_instance_t *mi JL_PROPAGATES_ROOT, jl_value_t *rettype, - size_t min_world, size_t max_world); -jl_method_instance_t *jl_get_unspecialized_from_mi(jl_method_instance_t *method JL_PROPAGATES_ROOT); + size_t min_world, size_t max_world, jl_debuginfo_t *edges); jl_method_instance_t *jl_get_unspecialized(jl_method_t *def JL_PROPAGATES_ROOT); -JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( +JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_method_instance_t *mi, jl_value_t *owner, jl_value_t *rettype, jl_value_t *exctype, 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 *analysis_results, - uint8_t relocatability); + uint8_t relocatability, jl_debuginfo_t *edges /* , int absolute_max*/); + +JL_DLLEXPORT const char *jl_debuginfo_file(jl_debuginfo_t *debuginfo) JL_NOTSAFEPOINT; +JL_DLLEXPORT const char *jl_debuginfo_file1(jl_debuginfo_t *debuginfo) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_module_t *jl_debuginfo_module1(jl_value_t *debuginfo_def) JL_NOTSAFEPOINT; +JL_DLLEXPORT const char *jl_debuginfo_name(jl_value_t *func) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tupletype_t *types, size_t world); JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types); diff --git a/src/method.c b/src/method.c index ab9487bbecad4..ee0ac11c368cb 100644 --- a/src/method.c +++ b/src/method.c @@ -290,20 +290,140 @@ jl_value_t *expr_arg1(jl_value_t *expr) { return jl_array_ptr_ref(args, 0); } +static jl_value_t *alloc_edges(arraylist_t *edges_list) +{ + jl_value_t *jledges = (jl_value_t*)jl_alloc_svec(edges_list->len); + jl_value_t *jledges2 = NULL; + jl_value_t *codelocs = NULL; + JL_GC_PUSH3(&jledges, &jledges2, &codelocs); + size_t i; + for (i = 0; i < edges_list->len; i++) { + arraylist_t *edge = (arraylist_t*)edges_list->items[i]; + jl_value_t *file = (jl_value_t*)edge->items[0]; + int32_t line = 0; // not preserved by lowering (and probably lost even before that) + arraylist_t *edges_list2 = (arraylist_t*)edge->items[1]; + size_t j, nlocs = (edge->len - 2) / 3; + codelocs = (jl_value_t*)jl_alloc_array_1d(jl_array_int32_type, nlocs * 3); + for (j = 0; j < nlocs; j++) { + jl_array_data(codelocs,int32_t)[3 * j + 0] = (intptr_t)edge->items[3 * j + 0 + 2]; + jl_array_data(codelocs,int32_t)[3 * j + 1] = (intptr_t)edge->items[3 * j + 1 + 2]; + jl_array_data(codelocs,int32_t)[3 * j + 2] = (intptr_t)edge->items[3 * j + 2 + 2]; + } + codelocs = (jl_value_t*)jl_compress_codelocs(line, codelocs, nlocs); + jledges2 = alloc_edges(edges_list2); + jl_value_t *debuginfo = jl_new_struct(jl_debuginfo_type, file, jl_nothing, jledges2, codelocs); + jledges2 = NULL; + jl_svecset(jledges, i, debuginfo); + free(edges_list2); + free(edge); + } + JL_GC_POP(); + return jledges; +} + +static void add_edge(arraylist_t *edges_list, arraylist_t *inlinestack, int32_t *p_to, int32_t *p_pc) +{ + jl_value_t *locinfo = (jl_value_t*)arraylist_pop(inlinestack); + jl_sym_t *filesym = (jl_sym_t*)jl_fieldref_noalloc(locinfo, 0); + int32_t line = jl_unbox_int32(jl_fieldref(locinfo, 1)); + size_t i; + arraylist_t *edge = NULL; + for (i = 0; i < edges_list->len; i++) { + edge = (arraylist_t*)edges_list->items[i]; + if (edge->items[0] == filesym) + break; + } + if (i == edges_list->len) { + edge = (arraylist_t*)malloc(sizeof(arraylist_t)); + arraylist_t *edge_list2 = (arraylist_t*)malloc(sizeof(arraylist_t)); + arraylist_new(edge, 0); + arraylist_new(edge_list2, 0); + arraylist_push(edge, (void*)filesym); + arraylist_push(edge, (void*)edge_list2); + arraylist_push(edges_list, (void*)edge); + } + *p_to = i + 1; + int32_t to = 0, pc = 0; + if (inlinestack->len) { + arraylist_t *edge_list2 = (arraylist_t*)edge->items[1]; + add_edge(edge_list2, inlinestack, &to, &pc); + } + for (i = 2; i < edge->len; i += 3) { + if ((intptr_t)edge->items[i + 0] == line && + (intptr_t)edge->items[i + 1] == to && + (intptr_t)edge->items[i + 2] == pc) { + break; + } + } + if (i == edge->len) { + arraylist_push(edge, (void*)(intptr_t)line); + arraylist_push(edge, (void*)(intptr_t)to); + arraylist_push(edge, (void*)(intptr_t)pc); + } + *p_pc = (i - 2) / 3 + 1; +} + +jl_debuginfo_t *jl_linetable_to_debuginfo(jl_array_t *codelocs_any, jl_array_t *linetable) +{ + size_t nlocs = jl_array_nrows(codelocs_any); + jl_value_t *toplocinfo = jl_array_ptr_ref(linetable, 0); + jl_sym_t *topfile = (jl_sym_t*)jl_fieldref_noalloc(toplocinfo, 0); + int32_t topline = jl_unbox_int32(jl_fieldref(toplocinfo, 1)); + arraylist_t inlinestack; + arraylist_new(&inlinestack, 0); + arraylist_t edges_list; + arraylist_new(&edges_list, 0); + jl_value_t *jledges = NULL; + jl_value_t *codelocs = (jl_value_t*)jl_alloc_array_1d(jl_array_int32_type, nlocs * 3); + jl_debuginfo_t *debuginfo = NULL; + JL_GC_PUSH3(&jledges, &codelocs, &debuginfo); + int32_t *codelocs32 = jl_array_data(codelocs,int32_t); + size_t j; + for (j = 0; j < nlocs; j++) { + size_t lineidx = jl_unbox_long(jl_array_ptr_ref((jl_array_t*)codelocs_any, j)); // 1 indexed! + while (lineidx != 0) { + jl_value_t *locinfo = jl_array_ptr_ref(linetable, lineidx - 1); + lineidx = jl_unbox_int32(jl_fieldref(locinfo, 2)); + arraylist_push(&inlinestack, locinfo); + } + int32_t line = 0, to = 0, pc = 0; + if (inlinestack.len) { + jl_value_t *locinfo = (jl_value_t*)arraylist_pop(&inlinestack); + jl_sym_t *filesym = (jl_sym_t*)jl_fieldref_noalloc(locinfo, 0); + if (filesym == topfile) + line = jl_unbox_int32(jl_fieldref(locinfo, 1)); + else + arraylist_push(&inlinestack, locinfo); + if (inlinestack.len) { + add_edge(&edges_list, &inlinestack, &to, &pc); + } + } + codelocs32[j * 3 + 0] = line; + codelocs32[j * 3 + 1] = to; + codelocs32[j * 3 + 2] = pc; + } + codelocs = (jl_value_t*)jl_compress_codelocs(topline, codelocs, nlocs); + jledges = alloc_edges(&edges_list); + debuginfo = (jl_debuginfo_t*)jl_new_struct(jl_debuginfo_type, topfile, jl_nothing, jledges, codelocs); + JL_GC_POP(); + return debuginfo; +} + // copy a :lambda Expr into its CodeInfo representation, // including popping of known meta nodes -static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) +jl_code_info_t *jl_new_code_info_from_ir(jl_expr_t *ir) { + jl_code_info_t *li = NULL; + JL_GC_PUSH1(&li); + li = jl_new_code_info_uninit(); assert(jl_is_expr(ir)); jl_expr_t *bodyex = (jl_expr_t*)jl_exprarg(ir, 2); - jl_value_t *codelocs = jl_exprarg(ir, 3); - li->linetable = jl_exprarg(ir, 4); - size_t nlocs = jl_array_nrows(codelocs); - li->codelocs = (jl_value_t*)jl_alloc_array_1d(jl_array_int32_type, nlocs); - size_t j; - for (j = 0; j < nlocs; j++) { - jl_array_uint32_set((jl_array_t*)li->codelocs, j, jl_unbox_long(jl_array_ptr_ref((jl_array_t*)codelocs, j))); - } + + jl_array_t *codelocs_any = (jl_array_t*)jl_exprarg(ir, 3); + jl_array_t *linetable = (jl_array_t*)jl_exprarg(ir, 4); + li->debuginfo = jl_linetable_to_debuginfo(codelocs_any, linetable); + jl_gc_wb(li, li->debuginfo); + assert(jl_is_expr(bodyex)); jl_array_t *body = bodyex->args; li->code = body; @@ -318,6 +438,7 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) // last(inline_flags) == 0: callsite noinline region arraylist_t *inline_flags = arraylist_new((arraylist_t*)malloc_s(sizeof(arraylist_t)), 0); arraylist_t *purity_exprs = arraylist_new((arraylist_t*)malloc_s(sizeof(arraylist_t)), 0); + size_t j; for (j = 0; j < n; j++) { jl_value_t *st = bd[j]; int is_flag_stmt = 0; @@ -472,6 +593,8 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) jl_array_ptr_set(li->slotnames, i, name); jl_array_uint8_set(li->slotflags, i, vinfo_mask & jl_unbox_long(jl_array_ptr_ref(vi, 2))); } + JL_GC_POP(); + return li; } JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void) @@ -499,11 +622,10 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) (jl_code_info_t*)jl_gc_alloc(ct->ptls, sizeof(jl_code_info_t), jl_code_info_type); src->code = NULL; - src->codelocs = NULL; + src->debuginfo = NULL; src->ssavaluetypes = NULL; src->ssaflags = NULL; src->method_for_inference_limit_heuristics = jl_nothing; - src->linetable = jl_nothing; src->slotflags = NULL; src->slotnames = NULL; src->slottypes = jl_nothing; @@ -521,40 +643,6 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) return src; } -jl_code_info_t *jl_new_code_info_from_ir(jl_expr_t *ir) -{ - jl_code_info_t *src = NULL; - JL_GC_PUSH1(&src); - src = jl_new_code_info_uninit(); - jl_code_info_set_ir(src, ir); - JL_GC_POP(); - return src; -} - -void jl_add_function_to_lineinfo(jl_code_info_t *ci, jl_value_t *func) -{ - // func may contain jl_symbol (function name), jl_method_t, or jl_method_instance_t - jl_array_t *li = (jl_array_t*)ci->linetable; - size_t i, n = jl_array_nrows(li); - jl_value_t *rt = NULL, *lno = NULL, *inl = NULL; - JL_GC_PUSH3(&rt, &lno, &inl); - for (i = 0; i < n; i++) { - jl_value_t *ln = jl_array_ptr_ref(li, i); - assert(jl_typetagis(ln, jl_lineinfonode_type)); - jl_value_t *mod = jl_fieldref_noalloc(ln, 0); - jl_value_t *file = jl_fieldref_noalloc(ln, 2); - lno = jl_fieldref(ln, 3); - inl = jl_fieldref(ln, 4); - // respect a given linetable if available - jl_value_t *ln_func = jl_fieldref_noalloc(ln, 1); - if (jl_is_symbol(ln_func) && (jl_sym_t*)ln_func == jl_symbol("none") && jl_is_int32(inl) && jl_unbox_int32(inl) == 0) - ln_func = func; - rt = jl_new_struct(jl_lineinfonode_type, mod, ln_func, file, lno, inl); - jl_array_ptr_set(li, i, rt); - } - JL_GC_POP(); -} - // invoke (compiling if necessary) the jlcall function pointer for a method template static jl_value_t *jl_call_staged(jl_method_t *def, jl_value_t *generator, size_t world, jl_svec_t *sparam_vals, jl_value_t **args, uint32_t nargs) @@ -644,7 +732,6 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo, siz jl_error("The function body AST defined by this @generated function is not pure. This likely means it contains a closure, a comprehension or a generator."); } } - jl_add_function_to_lineinfo(func, (jl_value_t*)def->name); // If this generated function has an opaque closure, cache it for // correctness of method identity @@ -723,7 +810,6 @@ JL_DLLEXPORT void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) m->nospecializeinfer = src->nospecializeinfer; m->constprop = src->constprop; m->purity.bits = src->purity.bits; - jl_add_function_to_lineinfo(src, (jl_value_t*)m->name); jl_array_t *copy = NULL; jl_svec_t *sparam_vars = jl_outer_unionall_vars(m->sig); @@ -811,11 +897,17 @@ JL_DLLEXPORT void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) jl_gc_wb(src, copy); m->slot_syms = jl_compress_argnames(src->slotnames); jl_gc_wb(m, m->slot_syms); - if (gen_only) + if (gen_only) { m->source = NULL; - else - m->source = (jl_value_t*)jl_compress_ir(m, src); - jl_gc_wb(m, m->source); + } + else { + m->debuginfo = src->debuginfo; + jl_gc_wb(m, m->debuginfo); + m->source = (jl_value_t*)src; + jl_gc_wb(m, m->source); + m->source = (jl_value_t*)jl_compress_ir(m, NULL); + jl_gc_wb(m, m->source); + } JL_GC_POP(); } @@ -835,6 +927,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->debuginfo = NULL; 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 2c70a053eb970..656eb91a2f1f3 100644 --- a/src/opaque_closure.c +++ b/src/opaque_closure.c @@ -105,7 +105,7 @@ static jl_opaque_closure_t *new_opaque_closure(jl_tupletype_t *argt, jl_value_t jl_method_instance_t *mi_generic = jl_specializations_get_linfo(jl_opaque_closure_method, sigtype, jl_emptysvec); // OC wrapper methods are not world dependent - ci = jl_get_method_inferred(mi_generic, selected_rt, 1, ~(size_t)0); + ci = jl_get_method_inferred(mi_generic, selected_rt, 1, ~(size_t)0, NULL); if (!jl_atomic_load_acquire(&ci->invoke)) jl_compile_codeinst(ci); specptr = jl_atomic_load_relaxed(&ci->specptr.fptr); @@ -150,7 +150,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, jl_nothing, rt_ub, (jl_value_t*)jl_any_type, NULL, (jl_value_t*)ci, - 0, world, world, 0, 0, jl_nothing, 0); + 0, world, world, 0, 0, jl_nothing, 0, ci->debuginfo); jl_mi_cache_insert(mi, inst); } diff --git a/src/rtutils.c b/src/rtutils.c index 1ab881e613564..9e76f513419c0 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -864,6 +864,17 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } return n; } + if (jl_genericmemory_type && dv->name == jl_genericmemory_typename) { + jl_value_t *isatomic = jl_tparam0(dv); + jl_value_t *el_type = jl_tparam1(dv); + jl_value_t *addrspace = jl_tparam2(dv); + if (isatomic == (jl_value_t*)jl_not_atomic_sym && addrspace && jl_is_addrspacecore(addrspace) && jl_unbox_uint8(addrspace) == 0) { + n += jl_printf(out, "Memory{"); + n += jl_static_show_x(out, el_type, depth, ctx); + n += jl_printf(out, "}"); + return n; + } + } if (ctx.quiet) { return jl_static_show_symbol(out, dv->name->name); } @@ -1104,7 +1115,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } else if (jl_genericmemoryref_type && jl_is_genericmemoryref_type(vt)) { jl_genericmemoryref_t *ref = (jl_genericmemoryref_t*)v; - n += jl_printf(out, "MemoryRef(offset="); + n += jl_printf(out, "GenericMemoryRef(offset="); size_t offset = (size_t)ref->ptr_or_offset; if (ref->mem) { const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typeof(ref->mem))->layout; @@ -1117,30 +1128,19 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } else if (jl_genericmemory_type && jl_is_genericmemory_type(vt)) { jl_genericmemory_t *m = (jl_genericmemory_t*)v; - jl_value_t *isatomic = jl_tparam0(vt); - jl_value_t *addrspace = jl_tparam2(vt); - if (isatomic == (jl_value_t*)jl_not_atomic_sym && jl_is_addrspacecore(addrspace) && jl_unbox_uint8(addrspace) == 0) { - n += jl_printf(out, "Memory{"); - } - else { - n += jl_printf(out, "GenericMemory{"); - n += jl_static_show_x(out, isatomic, depth, ctx); - n += jl_printf(out, ", "); - n += jl_static_show_x(out, addrspace, depth, ctx); - n += jl_printf(out, ", "); - } + //jl_value_t *isatomic = jl_tparam0(vt); jl_value_t *el_type = jl_tparam1(vt); - n += jl_static_show_x(out, el_type, depth, ctx); + jl_value_t *addrspace = jl_tparam2(vt); + n += jl_static_show_x(out, (jl_value_t*)vt, depth, ctx); size_t j, tlen = m->length; - n += jl_printf(out, "}(%" PRIdPTR ", %p)[", tlen, m->ptr); -//#ifdef _P64 -// n += jl_printf(out, "0x%016" PRIx64, tlen); -//#else -// n += jl_printf(out, "0x%08" PRIx32, tlen); -//#endif + n += jl_printf(out, "(%" PRIdPTR ", %p)[", tlen, m->ptr); + if (!(addrspace && jl_is_addrspacecore(addrspace) && jl_unbox_uint8(addrspace) == 0)) { + n += jl_printf(out, "...]"); + return n; + } + const char *typetagdata = NULL; const jl_datatype_layout_t *layout = vt->layout; int nlsep = 0; - const char *typetagdata = NULL; if (layout->flags.arrayelem_isboxed) { // print arrays with newlines, unless the elements are probably small for (j = 0; j < tlen; j++) { diff --git a/src/serialize.h b/src/serialize.h index 2a91189dce739..3d3eb4df5e862 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -52,22 +52,21 @@ extern "C" { #define TAG_SHORT_INT32 44 #define TAG_CALL1 45 #define TAG_CALL2 46 -#define TAG_LINEINFO 47 -#define TAG_SHORT_BACKREF 48 -#define TAG_BACKREF 49 -#define TAG_UNIONALL 50 -#define TAG_GOTONODE 51 -#define TAG_QUOTENODE 52 -#define TAG_GENERAL 53 -#define TAG_GOTOIFNOT 54 -#define TAG_RETURNNODE 55 -#define TAG_ARGUMENT 56 -#define TAG_RELOC_METHODROOT 57 -#define TAG_BINDING 58 -#define TAG_MEMORYT 59 -#define TAG_ENTERNODE 60 - -#define LAST_TAG 60 +#define TAG_SHORT_BACKREF 47 +#define TAG_BACKREF 48 +#define TAG_UNIONALL 49 +#define TAG_GOTONODE 50 +#define TAG_QUOTENODE 51 +#define TAG_GENERAL 52 +#define TAG_GOTOIFNOT 53 +#define TAG_RETURNNODE 54 +#define TAG_ARGUMENT 55 +#define TAG_RELOC_METHODROOT 56 +#define TAG_BINDING 57 +#define TAG_MEMORYT 58 +#define TAG_ENTERNODE 59 + +#define LAST_TAG 59 #define write_uint8(s, n) ios_putc((n), (s)) #define read_uint8(s) ((uint8_t)ios_getc((s))) diff --git a/src/stackwalk.c b/src/stackwalk.c index 37f239609504e..e45af4bb6634e 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -652,6 +652,80 @@ void jl_print_native_codeloc(uintptr_t ip) JL_NOTSAFEPOINT free(frames); } +const char *jl_debuginfo_file1(jl_debuginfo_t *debuginfo) +{ + jl_value_t *def = debuginfo->def; + if (jl_is_method_instance(def)) + def = ((jl_method_instance_t*)def)->def.value; + if (jl_is_method(def)) + def = (jl_value_t*)((jl_method_t*)def)->file; + if (jl_is_symbol(def)) + return jl_symbol_name((jl_sym_t*)def); + return ""; +} + +const char *jl_debuginfo_file(jl_debuginfo_t *debuginfo) +{ + jl_debuginfo_t *linetable = debuginfo->linetable; + while ((jl_value_t*)linetable != jl_nothing) { + debuginfo = linetable; + linetable = debuginfo->linetable; + } + return jl_debuginfo_file1(debuginfo); +} + +jl_module_t *jl_debuginfo_module1(jl_value_t *debuginfo_def) +{ + if (jl_is_method_instance(debuginfo_def)) + debuginfo_def = ((jl_method_instance_t*)debuginfo_def)->def.value; + if (jl_is_method(debuginfo_def)) + debuginfo_def = (jl_value_t*)((jl_method_t*)debuginfo_def)->module; + if (jl_is_module(debuginfo_def)) + return (jl_module_t*)debuginfo_def; + return NULL; +} + +const char *jl_debuginfo_name(jl_value_t *func) +{ + if (func == NULL) + return "macro expansion"; + if (jl_is_method_instance(func)) + func = ((jl_method_instance_t*)func)->def.value; + if (jl_is_method(func)) + func = (jl_value_t*)((jl_method_t*)func)->name; + if (jl_is_symbol(func)) + return jl_symbol_name((jl_sym_t*)func); + if (jl_is_module(func)) + return "top-level scope"; + return ""; +} + +// func == module : top-level +// func == NULL : macro expansion +static void jl_print_debugloc(jl_debuginfo_t *debuginfo, jl_value_t *func, size_t ip, int inlined) JL_NOTSAFEPOINT +{ + if (!jl_is_symbol(debuginfo->def)) // this is a path or + func = debuginfo->def; // this is inlined code + struct jl_codeloc_t stmt = jl_uncompress1_codeloc(debuginfo->codelocs, ip); + intptr_t edges_idx = stmt.to; + if (edges_idx) { + jl_debuginfo_t *edge = (jl_debuginfo_t*)jl_svecref(debuginfo->edges, edges_idx - 1); + assert(jl_typetagis(edge, jl_debuginfo_type)); + jl_print_debugloc(edge, NULL, stmt.pc, 1); + } + intptr_t ip2 = stmt.line; + if (ip2 >= 0 && ip > 0 && (jl_value_t*)debuginfo->linetable != jl_nothing) { + jl_print_debugloc(debuginfo->linetable, func, ip2, 0); + } + else { + if (ip2 < 0) // set broken debug info to ignored + ip2 = 0; + const char *func_name = jl_debuginfo_name(func); + const char *file = jl_debuginfo_file(debuginfo); + jl_safe_print_codeloc(func_name, file, ip2, inlined); + } +} + // Print code location for backtrace buffer entry at *bt_entry void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT { @@ -659,33 +733,18 @@ void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT jl_print_native_codeloc(bt_entry[0].uintptr); } else if (jl_bt_entry_tag(bt_entry) == JL_BT_INTERP_FRAME_TAG) { - size_t ip = jl_bt_entry_header(bt_entry); + size_t ip = jl_bt_entry_header(bt_entry); // zero-indexed jl_value_t *code = jl_bt_entry_jlvalue(bt_entry, 0); + jl_value_t *def = (jl_value_t*)jl_core_module; // just used as a token here that isa Module if (jl_is_method_instance(code)) { + def = code; // When interpreting a method instance, need to unwrap to find the code info code = jl_atomic_load_relaxed(&((jl_method_instance_t*)code)->uninferred); } if (jl_is_code_info(code)) { jl_code_info_t *src = (jl_code_info_t*)code; // See also the debug info handling in codegen.cpp. - // NB: debuginfoloc is 1-based! - intptr_t debuginfoloc = jl_array_data(src->codelocs, int32_t)[ip]; - while (debuginfoloc != 0) { - jl_line_info_node_t *locinfo = (jl_line_info_node_t*) - jl_array_ptr_ref(src->linetable, debuginfoloc - 1); - assert(jl_typetagis(locinfo, jl_lineinfonode_type)); - const char *func_name = "Unknown"; - jl_value_t *method = locinfo->method; - if (jl_is_method_instance(method)) - method = ((jl_method_instance_t*)method)->def.value; - if (jl_is_method(method)) - method = (jl_value_t*)((jl_method_t*)method)->name; - if (jl_is_symbol(method)) - func_name = jl_symbol_name((jl_sym_t*)method); - jl_safe_print_codeloc(func_name, jl_symbol_name(locinfo->file), - locinfo->line, locinfo->inlined_at); - debuginfoloc = locinfo->inlined_at; - } + jl_print_debugloc(src->debuginfo, def, ip + 1, 0); } else { // If we're using this function something bad has already happened; diff --git a/src/staticdata.c b/src/staticdata.c index 261042b775c14..3e2aaf44d8a0d 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -100,7 +100,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 188 +#define NUM_TAGS 189 // 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. @@ -213,6 +213,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_addrspace_type); INSERT_TAG(jl_addrspace_typename); INSERT_TAG(jl_addrspacecore_type); + INSERT_TAG(jl_debuginfo_type); // special typenames INSERT_TAG(jl_tuple_typename); @@ -2337,23 +2338,18 @@ static void jl_prune_type_cache_linear(jl_svec_t *cache) jl_svecset(cache, ins++, jl_nothing); } -static jl_value_t *strip_codeinfo_meta(jl_method_t *m, jl_value_t *ci_, int orig) +static jl_value_t *strip_codeinfo_meta(jl_method_t *m, jl_value_t *ci_, jl_code_instance_t *codeinst, int orig) { jl_code_info_t *ci = NULL; JL_GC_PUSH1(&ci); int compressed = 0; if (!jl_is_code_info(ci_)) { compressed = 1; - ci = jl_uncompress_ir(m, NULL, (jl_value_t*)ci_); + ci = jl_uncompress_ir(m, codeinst, (jl_value_t*)ci_); } else { ci = (jl_code_info_t*)ci_; } - // leave codelocs length the same so the compiler can assume that; just zero it - memset(jl_array_data(ci->codelocs, int32_t), 0, jl_array_len(ci->codelocs)*sizeof(int32_t)); - // empty linetable - if (jl_is_array(ci->linetable)) - jl_array_del_end((jl_array_t*)ci->linetable, jl_array_len(ci->linetable)); // replace slot names with `?`, except unused_sym since the compiler looks at it jl_sym_t *questionsym = jl_symbol("?"); int i, l = jl_array_len(ci->slotnames); @@ -2384,7 +2380,7 @@ static void strip_specializations_(jl_method_instance_t *mi) record_field_change((jl_value_t**)&codeinst->inferred, jl_nothing); } else if (jl_options.strip_metadata) { - jl_value_t *stripped = strip_codeinfo_meta(mi->def.method, inferred, 0); + jl_value_t *stripped = strip_codeinfo_meta(mi->def.method, inferred, codeinst, 0); if (jl_atomic_cmpswap_relaxed(&codeinst->inferred, &inferred, stripped)) { jl_gc_wb(codeinst, stripped); } @@ -2422,7 +2418,7 @@ static int strip_all_codeinfos__(jl_typemap_entry_t *def, void *_env) } } if (jl_options.strip_metadata && !stripped_ir) { - m->source = strip_codeinfo_meta(m, m->source, 1); + m->source = strip_codeinfo_meta(m, m->source, NULL, 1); jl_gc_wb(m, m->source); } } diff --git a/stdlib/InteractiveUtils/test/runtests.jl b/stdlib/InteractiveUtils/test/runtests.jl index 4b69e4c1f615d..ef1cd6e230818 100644 --- a/stdlib/InteractiveUtils/test/runtests.jl +++ b/stdlib/InteractiveUtils/test/runtests.jl @@ -707,6 +707,8 @@ end @testset "code_llvm on opaque_closure" begin let ci = code_typed(+, (Int, Int))[1][1] ir = Core.Compiler.inflate_ir(ci) + @test ir.debuginfo.def === nothing + ir.debuginfo.def = Symbol(@__FILE__) oc = Core.OpaqueClosure(ir) @test (code_llvm(devnull, oc, Tuple{Int, Int}); true) let io = IOBuffer() diff --git a/stdlib/REPL/test/precompilation.jl b/stdlib/REPL/test/precompilation.jl index 2dcf78c114d9a..5021844a9d1a4 100644 --- a/stdlib/REPL/test/precompilation.jl +++ b/stdlib/REPL/test/precompilation.jl @@ -27,7 +27,7 @@ if !Sys.iswindows() tracecompile_out = read(f, String) close(ptm) # close after reading so we don't get precompiles from error shutdown - expected_precompiles = 1 + expected_precompiles = 2 n_precompiles = count(r"precompile\(", tracecompile_out) diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index 29010a52eed91..3f38708e8bb1e 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -1085,6 +1085,7 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) end if makenew meth.module = mod + meth.debuginfo = NullDebugInfo meth.name = name meth.file = file meth.line = line @@ -1097,7 +1098,9 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) meth.purity = purity if template !== nothing # TODO: compress template - meth.source = template::CodeInfo + template = template::CodeInfo + meth.source = template + meth.debuginfo = template.debuginfo if !@isdefined(slot_syms) slot_syms = ccall(:jl_compress_argnames, Ref{String}, (Any,), meth.source.slotnames) end @@ -1151,6 +1154,7 @@ function deserialize(s::AbstractSerializer, ::Type{Core.LineInfoNode}) return Core.LineInfoNode(mod, method, deserialize(s)::Symbol, Int32(deserialize(s)::Union{Int32, Int}), Int32(deserialize(s)::Union{Int32, Int})) end + function deserialize(s::AbstractSerializer, ::Type{PhiNode}) edges = deserialize(s) if edges isa Vector{Any} @@ -1165,6 +1169,7 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) deserialize_cycle(s, ci) code = deserialize(s)::Vector{Any} ci.code = code + ci.debuginfo = NullDebugInfo # allow older-style IR with return and gotoifnot Exprs for i in 1:length(code) stmt = code[i] @@ -1177,17 +1182,27 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) end end end - ci.codelocs = deserialize(s)::Vector{Int32} + _x = deserialize(s) + have_debuginfo = _x isa Core.DebugInfo + if have_debuginfo + ci.debuginfo = _x + else + codelocs = _x::Vector{Int32} + # TODO: convert codelocs to debuginfo format? + end _x = deserialize(s) if _x isa Array || _x isa Int pre_12 = false - ci.ssavaluetypes = _x else pre_12 = true # < v1.2 ci.method_for_inference_limit_heuristics = _x - ci.ssavaluetypes = deserialize(s) - ci.linetable = deserialize(s) + _x = deserialize(s) + end + ci.ssavaluetypes = _x + if pre_12 + linetable = deserialize(s) + # TODO: convert linetable to debuginfo format? end ssaflags = deserialize(s) if length(ssaflags) ≠ length(code) @@ -1204,7 +1219,10 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) if format_version(s) <= 26 ci.method_for_inference_limit_heuristics = deserialize(s) end - ci.linetable = deserialize(s) + if !have_debuginfo # pre v1.11 format + linetable = deserialize(s) + # TODO: convert linetable to debuginfo format? + end end ci.slotnames = deserialize(s) if !pre_12 @@ -1266,9 +1284,12 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) if format_version(s) >= 22 ci.inlining_cost = deserialize(s)::UInt16 end + ci.debuginfo = NullDebugInfo return ci end +const NullDebugInfo = DebugInfo(:none) + if Int === Int64 const OtherInt = Int32 else diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 498254bb26ce0..291b0b0fdad78 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -444,9 +444,7 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` mktempdir() do dir helperdir = joinpath(@__DIR__, "testhelpers") inputfile = joinpath(helperdir, "coverage_file.jl") - expected = replace(read(joinpath(helperdir, "coverage_file.info.bad"), String), - "" => realpath(inputfile)) - expected_good = replace(read(joinpath(helperdir, "coverage_file.info"), String), + expected = replace(read(joinpath(helperdir, "coverage_file.info"), String), "" => realpath(inputfile)) covfile = replace(joinpath(dir, "coverage.info"), "%" => "%%") @test !isfile(covfile) @@ -464,21 +462,18 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) @test readchomp(`$exename -E "Base.JLOptions().code_coverage" -L $inputfile --code-coverage=$covfile --code-coverage=user`) == "1" @test isfile(covfile) got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) @test readchomp(`$exename -E "Base.JLOptions().code_coverage" -L $inputfile --code-coverage=$covfile --code-coverage=all`) == "2" @test isfile(covfile) got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) # Ask for coverage in specific file tfile = realpath(inputfile) @@ -488,7 +483,6 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) # Ask for coverage in directory tdir = dirname(realpath(inputfile)) @@ -498,7 +492,6 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) # Ask for coverage in current directory tdir = dirname(realpath(inputfile)) @@ -511,7 +504,6 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) # Ask for coverage in relative directory tdir = dirname(realpath(inputfile)) @@ -523,7 +515,6 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) # Ask for coverage in relative directory with dot-dot notation tdir = dirname(realpath(inputfile)) @@ -535,7 +526,6 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) # Ask for coverage in a different directory tdir = mktempdir() # a dir that contains no code @@ -570,15 +560,14 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` end do_test() """), """ - DA:1,1 DA:2,1 DA:3,1 DA:5,1 DA:6,0 DA:9,1 DA:10,1 - LH:6 - LF:7 + LH:5 + LF:6 """) @test contains(coverage_info_for(""" function cov_bug() diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl index 0b78d54803f7f..e6691b3373259 100644 --- a/test/compiler/AbstractInterpreter.jl +++ b/test/compiler/AbstractInterpreter.jl @@ -463,7 +463,6 @@ let # generate cache target_mi = CC.specialize_method(only(methods(custom_lookup_target)), Tuple{typeof(custom_lookup_target),Bool,Int}, Core.svec()) target_ci = custom_lookup(target_mi, CONST_INVOKE_INTERP_WORLD, CONST_INVOKE_INTERP_WORLD) @test target_ci.rettype == Tuple{Float64,Nothing} # constprop'ed source - # display(@ccall jl_uncompress_ir(target_ci.def.def::Any, C_NULL::Ptr{Cvoid}, target_ci.inferred::Any)::Any) raw = false lookup = @cfunction(custom_lookup, Any, (Any,Csize_t,Csize_t)) diff --git a/test/compiler/contextual.jl b/test/compiler/contextual.jl index bbcf7b0dfb959..17919c6a9f6ad 100644 --- a/test/compiler/contextual.jl +++ b/test/compiler/contextual.jl @@ -43,14 +43,16 @@ module MiniCassette end end - function transform!(ci::CodeInfo, nargs::Int, sparams::Core.SimpleVector) + function transform!(mi::MethodInstance, ci::CodeInfo, nargs::Int, sparams::Core.SimpleVector) code = ci.code + di = Core.Compiler.DebugInfoStream(mi, ci.debuginfo, length(code)) ci.slotnames = Symbol[Symbol("#self#"), :ctx, :f, :args, ci.slotnames[nargs+1:end]...] ci.slotflags = UInt8[(0x00 for i = 1:4)..., ci.slotflags[nargs+1:end]...] # Insert one SSAValue for every argument statement prepend!(code, Any[Expr(:call, getfield, SlotNumber(4), i) for i = 1:nargs]) - prepend!(ci.codelocs, fill(0, nargs)) + prepend!(di.codelocs, fill(Int32(0), 3nargs)) prepend!(ci.ssaflags, fill(0x00, nargs)) + ci.debuginfo = Core.DebugInfo(di, length(code)) ci.ssavaluetypes += nargs function map_slot_number(slot::Int) if slot == 1 @@ -88,7 +90,7 @@ module MiniCassette code_info = copy(code_info) @assert code_info.edges === nothing code_info.edges = MethodInstance[mi] - transform!(code_info, length(args), match.sparams) + transform!(mi, code_info, length(args), match.sparams) # TODO: this is mandatory: code_info.min_world = max(code_info.min_world, min_world[]) # TODO: this is mandatory: code_info.max_world = min(code_info.max_world, max_world[]) return code_info diff --git a/test/compiler/interpreter_exec.jl b/test/compiler/interpreter_exec.jl index b3aca0cfe2903..f00bc92c7443d 100644 --- a/test/compiler/interpreter_exec.jl +++ b/test/compiler/interpreter_exec.jl @@ -22,7 +22,7 @@ let m = Meta.@lower 1 + 1 nstmts = length(src.code) src.ssavaluetypes = nstmts src.ssaflags = fill(UInt8(0x00), nstmts) - src.codelocs = fill(Int32(1), nstmts) + src.debuginfo = Core.DebugInfo(:none) Core.Compiler.verify_ir(Core.Compiler.inflate_ir(src)) global test29262 = true @test :a === @eval $m @@ -62,7 +62,7 @@ let m = Meta.@lower 1 + 1 nstmts = length(src.code) src.ssavaluetypes = nstmts src.ssaflags = fill(UInt8(0x00), nstmts) - src.codelocs = fill(Int32(1), nstmts) + src.debuginfo = Core.DebugInfo(:none) m.args[1] = copy(src) Core.Compiler.verify_ir(Core.Compiler.inflate_ir(src)) global test29262 = true @@ -81,7 +81,7 @@ let m = Meta.@lower 1 + 1 QuoteNode(:b), GlobalRef(@__MODULE__, :test29262), # block 2 - EnterNode(11), + EnterNode(12), # block 3 UpsilonNode(), UpsilonNode(), @@ -91,19 +91,22 @@ let m = Meta.@lower 1 + 1 UpsilonNode(SSAValue(1)), # block 5 Expr(:throw_undef_if_not, :expected, false), + ReturnNode(), # unreachable # block 6 PhiCNode(Any[SSAValue(5), SSAValue(7), SSAValue(9)]), # NULL, :a, :b PhiCNode(Any[SSAValue(6)]), # NULL + Expr(:pop_exception, SSAValue(4)), # block 7 - ReturnNode(SSAValue(11)), + ReturnNode(SSAValue(12)), ] nstmts = length(src.code) src.ssavaluetypes = nstmts src.ssaflags = fill(UInt8(0x00), nstmts) - src.codelocs = fill(Int32(1), nstmts) + src.debuginfo = Core.DebugInfo(:none) Core.Compiler.verify_ir(Core.Compiler.inflate_ir(src)) global test29262 = true @test :a === @eval $m global test29262 = false @test :b === @eval $m + @test isempty(current_exceptions()) end diff --git a/test/compiler/irpasses.jl b/test/compiler/irpasses.jl index c124832030cbd..4df4c699add09 100644 --- a/test/compiler/irpasses.jl +++ b/test/compiler/irpasses.jl @@ -804,7 +804,7 @@ function each_stmt_a_bb(stmts, preds, succs) append!(ir.stmts.stmt, stmts) empty!(ir.stmts.type); append!(ir.stmts.type, [Nothing for _ = 1:length(stmts)]) empty!(ir.stmts.flag); append!(ir.stmts.flag, [0x0 for _ = 1:length(stmts)]) - empty!(ir.stmts.line); append!(ir.stmts.line, [Int32(0) for _ = 1:length(stmts)]) + empty!(ir.stmts.line); append!(ir.stmts.line, [Int32(0) for _ = 1:3length(stmts)]) empty!(ir.stmts.info); append!(ir.stmts.info, [NoCallInfo() for _ = 1:length(stmts)]) empty!(ir.cfg.blocks); append!(ir.cfg.blocks, [BasicBlock(StmtRange(i, i), preds[i], succs[i]) for i = 1:length(stmts)]) empty!(ir.cfg.index); append!(ir.cfg.index, [i for i = 2:length(stmts)]) diff --git a/test/compiler/irutils.jl b/test/compiler/irutils.jl index 788d7bbc721ee..6a4087fbea958 100644 --- a/test/compiler/irutils.jl +++ b/test/compiler/irutils.jl @@ -71,7 +71,7 @@ let m = Meta.@lower 1 + 1 else src.ssavaluetypes = ssavaluetypes end - src.codelocs = fill(one(Int32), nstmts) + src.debuginfo = Core.DebugInfo(:none) src.ssaflags = fill(zero(UInt32), nstmts) if slottypes !== nothing src.slottypes = slottypes diff --git a/test/compiler/ssair.jl b/test/compiler/ssair.jl index e237ab732ef57..6e0c292c0bd45 100644 --- a/test/compiler/ssair.jl +++ b/test/compiler/ssair.jl @@ -38,7 +38,6 @@ end # false, false, false, false # )) # -# NullLineInfo = Core.LineInfoNode(Main, Symbol(""), Symbol(""), Int32(0), UInt32(0)) # Compiler.run_passes(ci, 1, [NullLineInfo]) # # XXX: missing @test #end @@ -113,8 +112,9 @@ let cfg = CFG(BasicBlock[ make_bb([0, 1, 2] , [5] ), # 0 predecessor should be preserved make_bb([2, 3] , [] ), ], Int[]) - insts = Compiler.InstructionStream([], [], Any[], Int32[], UInt8[]) - ir = Compiler.IRCode(insts, cfg, Core.LineInfoNode[], Any[], Expr[], Compiler.VarState[]) + insts = Compiler.InstructionStream([], [], Core.Compiler.CallInfo[], Int32[], UInt32[]) + di = Compiler.DebugInfoStream(insts.line) + ir = Compiler.IRCode(insts, cfg, di, Any[], Expr[], Compiler.VarState[]) compact = Compiler.IncrementalCompact(ir, true) @test length(compact.cfg_transform.result_bbs) == 4 && 0 in compact.cfg_transform.result_bbs[3].preds end diff --git a/test/core.jl b/test/core.jl index 0eca2251cd146..3a9945f152f5f 100644 --- a/test/core.jl +++ b/test/core.jl @@ -32,7 +32,7 @@ end # sanity tests that our built-in types are marked correctly for atomic fields for (T, c) in ( (Core.CodeInfo, []), - (Core.CodeInstance, [:next, :min_world, :max_world, :inferred, :purity_bits, :invoke, :specptr, :specsigflags, :precompile]), + (Core.CodeInstance, [:next, :min_world, :max_world, :inferred, :debuginfo, :purity_bits, :invoke, :specptr, :specsigflags, :precompile]), (Core.Method, [:primary_world, :deleted_world]), (Core.MethodInstance, [:uninferred, :cache, :precompiled]), (Core.MethodTable, [:defs, :leafcache, :cache, :max_args]), @@ -8063,10 +8063,6 @@ end @test Core.Compiler.is_foldable(Base.infer_effects(length, (Core.SimpleVector,))) @test Core.Compiler.is_foldable(Base.infer_effects(getindex, (Core.SimpleVector,Int))) -let lin = Core.LineInfoNode(Base, first(methods(convert)), :foo, Int32(5), Int32(0)) - @test convert(LineNumberNode, lin) == LineNumberNode(5, :foo) -end - # Test that a nothrow-globalref doesn't get outlined during lowering module WellKnownGlobal global well_known = 1 diff --git a/test/opaque_closure.jl b/test/opaque_closure.jl index e9f1601019a59..8b0d118d98f1b 100644 --- a/test/opaque_closure.jl +++ b/test/opaque_closure.jl @@ -245,6 +245,8 @@ end # constructing an opaque closure from IRCode let src = first(only(code_typed(+, (Int, Int)))) ir = Core.Compiler.inflate_ir(src, Core.Compiler.VarState[], src.slottypes) + @test ir.debuginfo.def === nothing + ir.debuginfo.def = Symbol(@__FILE__) @test OpaqueClosure(src; sig=Tuple{Int, Int}, rettype=Int, nargs=2)(40, 2) == 42 oc = OpaqueClosure(ir) @test oc(40, 2) == 42 @@ -269,6 +271,8 @@ let src = code_typed((Int,Int)) do x, y... @test_throws MethodError oc(1,2,3) end ir = Core.Compiler.inflate_ir(src, Core.Compiler.VarState[], src.slottypes) + @test ir.debuginfo.def === nothing + ir.debuginfo.def = Symbol(@__FILE__) let oc = OpaqueClosure(ir; isva=true) @test oc(1,2) === (1,(2,)) @test_throws MethodError oc(1,2,3) diff --git a/test/precompile.jl b/test/precompile.jl index c06145cba8416..3aa48552b8f66 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1702,7 +1702,8 @@ precompile_test_harness("issue #46296") do load_path mi = first(Base.specializations(first(methods(identity)))) ci = Core.CodeInstance(mi, nothing, Any, Any, nothing, nothing, zero(Int32), typemin(UInt), - typemax(UInt), zero(UInt32), zero(UInt32), nothing, 0x00) + typemax(UInt), zero(UInt32), zero(UInt32), nothing, 0x00, + Core.DebugInfo(mi)) __init__() = @assert ci isa Core.CodeInstance diff --git a/test/show.jl b/test/show.jl index 8d6b32a3fd663..cb442ffc629a3 100644 --- a/test/show.jl +++ b/test/show.jl @@ -2075,7 +2075,7 @@ eval(Meta._parse_string("""function my_fun28173(x) end""", "a"^80, 1, 1, :statement)[1]) # use parse to control the line numbers let src = code_typed(my_fun28173, (Int,), debuginfo=:source)[1][1] ir = Core.Compiler.inflate_ir(src) - fill!(src.codelocs, 0) # IRCode printing is only capable of printing partial line info + src.debuginfo = Core.DebugInfo(src.debuginfo.def) # IRCode printing defaults to incomplete line info printing, so turn it off completely for CodeInfo too let source_slotnames = String["my_fun28173", "x"], repr_ir = split(repr(ir, context = :SOURCE_SLOTNAMES=>source_slotnames), '\n'), repr_ir = "CodeInfo(\n" * join((l[4:end] for l in repr_ir), "\n") * ")" # remove line numbers diff --git a/test/staged.jl b/test/staged.jl index 76d02c1938e4d..6bc3ead35586d 100644 --- a/test/staged.jl +++ b/test/staged.jl @@ -337,13 +337,15 @@ let world = Base.get_world_counter() match = Base._which(Tuple{typeof(sin), Int}; world) mi = Core.Compiler.specialize_method(match) lwr = Core.Compiler.retrieve_code_info(mi, world) - @test all(lin->lin.method === :sin, lwr.linetable) + nstmts = length(lwr.code) + di = Core.DebugInfo(Core.Compiler.DebugInfoStream(mi, lwr.debuginfo, nstmts), nstmts) + lwr.debuginfo = di @eval function sin_generated(a) $(Expr(:meta, :generated, Returns(lwr))) $(Expr(:meta, :generated_only)) end src = only(code_lowered(sin_generated, (Int,))) - @test all(lin->lin.method === :sin, src.linetable) + @test src.debuginfo === di @test sin_generated(42) == sin(42) end @@ -362,7 +364,7 @@ let nstmts = length(src.code) nslots = 1 src.ssavaluetypes = nstmts - src.codelocs = fill(Int32(1), nstmts) + src.debuginfo = Core.DebugInfo(:f_unreachable_generated) src.ssaflags = fill(Int32(0), nstmts) src.slotflags = fill(0, nslots) src.slottypes = Any[Any] diff --git a/test/syntax.jl b/test/syntax.jl index 423d1e3d04bfb..d5338d76adf59 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -713,36 +713,10 @@ m1_exprs = get_expr_list(Meta.lower(@__MODULE__, quote @m1 end)) let low3 = Meta.lower(@__MODULE__, quote @m3 end) m3_exprs = get_expr_list(low3) ci = low3.args[1]::Core.CodeInfo - @test ci.codelocs in ([4, 4, 0], [4, 0]) + #@test ci.codelocs in ([4, 4, 0], [4, 0]) @test is_return_ssavalue(m3_exprs[end]) end -function f1(a) - b = a + 100 - b -end - -@generated function f2(a) - quote - b = a + 100 - b - end -end - -f1_ci = code_typed(f1, (Int,), debuginfo=:source)[1][1] -f2_ci = code_typed(f2, (Int,), debuginfo=:source)[1][1] - -f1_exprs = get_expr_list(f1_ci) -f2_exprs = get_expr_list(f2_ci) - -if Base.JLOptions().can_inline != 0 - @test length(f1_ci.linetable) == 3 - @test length(f2_ci.linetable) >= 3 -else - @test length(f1_ci.linetable) == 2 - @test length(f2_ci.linetable) >= 3 -end - # Check that string and command literals are parsed to the appropriate macros @test :(x"s") == :(@x_str "s") @test :(x"s"flag) == :(@x_str "s" "flag") diff --git a/test/testhelpers/coverage_file.info b/test/testhelpers/coverage_file.info index c83e75dee8060..b03b0e07e6977 100644 --- a/test/testhelpers/coverage_file.info +++ b/test/testhelpers/coverage_file.info @@ -10,9 +10,10 @@ DA:11,1 DA:12,1 DA:14,0 DA:17,1 -DA:19,2 +DA:18,1 +DA:19,1 DA:20,1 DA:22,1 -LH:12 -LF:14 +LH:13 +LF:15 end_of_record diff --git a/test/testhelpers/coverage_file.info.bad b/test/testhelpers/coverage_file.info.bad deleted file mode 100644 index 311f6379381ee..0000000000000 --- a/test/testhelpers/coverage_file.info.bad +++ /dev/null @@ -1,20 +0,0 @@ -SF: -DA:3,1 -DA:4,1 -DA:5,0 -DA:7,1 -DA:8,1 -DA:9,3 -DA:10,5 -DA:11,1 -DA:12,1 -DA:14,0 -DA:17,1 -DA:18,0 -DA:19,2 -DA:20,1 -DA:22,1 -DA:1234,0 -LH:12 -LF:16 -end_of_record diff --git a/test/testhelpers/coverage_file.info.bad2 b/test/testhelpers/coverage_file.info.bad2 deleted file mode 100644 index a766597be4c17..0000000000000 --- a/test/testhelpers/coverage_file.info.bad2 +++ /dev/null @@ -1,20 +0,0 @@ -SF: -DA:3,1 -DA:4,1 -DA:5,0 -DA:7,1 -DA:8,1 -DA:9,3 -DA:10,5 -DA:11,0 -DA:12,1 -DA:14,0 -DA:17,1 -DA:18,0 -DA:19,0 -DA:20,0 -DA:22,1 -DA:1234,0 -LH:9 -LF:16 -end_of_record diff --git a/test/testhelpers/coverage_file.jl b/test/testhelpers/coverage_file.jl index e8e0355952d80..577cc6bb5d2ca 100644 --- a/test/testhelpers/coverage_file.jl +++ b/test/testhelpers/coverage_file.jl @@ -24,6 +24,6 @@ end success = code_coverage_test() == [1, 2, 3] && short_form_func_coverage_test(2) == 4 -exit(success ? 0 : 1) +exit(success ? 0 : 1) # end of file diff --git a/test/worlds.jl b/test/worlds.jl index e9d8895fc90d4..945405cee26a4 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -257,11 +257,10 @@ end # avoid adding this to Base function equal(ci1::Core.CodeInfo, ci2::Core.CodeInfo) return ci1.code == ci2.code && - ci1.codelocs == ci2.codelocs && + ci1.debuginfo == ci2.debuginfo && ci1.ssavaluetypes == ci2.ssavaluetypes && ci1.ssaflags == ci2.ssaflags && ci1.method_for_inference_limit_heuristics == ci2.method_for_inference_limit_heuristics && - ci1.linetable == ci2.linetable && ci1.slotnames == ci2.slotnames && ci1.slotflags == ci2.slotflags && ci1.slottypes == ci2.slottypes