From 95b89738b09cb79cf013295b4b52a5a385623b3a Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 5 Mar 2024 20:03:29 +0000 Subject: [PATCH] inverted linetable representation Previously, our linetables (similar to LLVM) represented line information as a linked list from callee via inlined_at up to the original information. This requires many copies of this information to be created. Instead we can take advantage of the necessary existence of the line table from the child to flip this chain of information and instead make each statement be a table describing (for each IR instruction): `(current line number, (index into edges, index into edges statements))` plus a table of all edges, plus a table with the original line numbers from the parser, plus the file name. This is all packed into the struct struct DebugInfo def::Union{Method,MethodInstance,Symbol} linetable::Union{Nothing,DebugInfo} edges::SimpleVector{DebugInfo} codelocs::String end Which is described in doc/src/devdocs/ast.md for what each field means and look at stacktraces.jl or compiler/ssair/show.jl to look at how to decode and interpret this information. For the sysimage, this saves several megabytes (about 113 MB -> 110 MB) and about 5% of the stdlib pkgimages (294 MB -> 279 MB). It also now happens to have the full type information for the inlined functions. Now if you create an `IRShow.DILineInfoPrinter` with `showtypes=true`, it can print that information when printing IR. --- base/boot.jl | 45 ++- base/compiler/inferencestate.jl | 30 +- base/compiler/optimize.jl | 92 ++++-- base/compiler/ssair/inlining.jl | 124 +++----- base/compiler/ssair/ir.jl | 119 ++++++-- base/compiler/ssair/irinterp.jl | 1 + base/compiler/ssair/legacy.jl | 17 +- base/compiler/ssair/passes.jl | 12 +- base/compiler/ssair/show.jl | 259 +++++++++-------- base/compiler/ssair/slot2ssa.jl | 39 +-- base/compiler/ssair/verify.jl | 30 +- base/compiler/typeinfer.jl | 14 +- base/expr.jl | 13 +- base/opaque_closure.jl | 2 + base/show.jl | 37 ++- base/stacktraces.jl | 42 ++- doc/src/base/reflection.md | 1 - doc/src/devdocs/ast.md | 77 +++++ src/ast.c | 16 +- src/builtins.c | 3 +- src/clangsa/GCChecker.cpp | 1 + src/codegen.cpp | 307 +++++++++++--------- src/gf.c | 66 +++-- src/ircode.c | 338 ++++++++++++++++++---- src/jitlayers.cpp | 3 + src/jl_exported_data.inc | 1 + src/jltypes.c | 69 +++-- src/julia-syntax.scm | 17 +- src/julia.h | 26 +- src/julia_internal.h | 12 +- src/method.c | 195 +++++++++---- src/opaque_closure.c | 4 +- src/rtutils.c | 42 +-- src/serialize.h | 31 +- src/stackwalk.c | 97 +++++-- src/staticdata.c | 16 +- stdlib/InteractiveUtils/test/runtests.jl | 2 + stdlib/REPL/test/precompilation.jl | 2 +- stdlib/Serialization/src/Serialization.jl | 33 ++- test/cmdlineargs.jl | 17 +- test/compiler/AbstractInterpreter.jl | 1 - test/compiler/contextual.jl | 8 +- test/compiler/interpreter_exec.jl | 13 +- test/compiler/irpasses.jl | 2 +- test/compiler/irutils.jl | 2 +- test/compiler/ssair.jl | 6 +- test/core.jl | 6 +- test/opaque_closure.jl | 4 + test/precompile.jl | 3 +- test/show.jl | 2 +- test/staged.jl | 8 +- test/syntax.jl | 28 +- test/testhelpers/coverage_file.info | 7 +- test/testhelpers/coverage_file.info.bad | 20 -- test/testhelpers/coverage_file.info.bad2 | 20 -- test/testhelpers/coverage_file.jl | 2 +- test/worlds.jl | 3 +- 57 files changed, 1488 insertions(+), 899 deletions(-) delete mode 100644 test/testhelpers/coverage_file.info.bad delete mode 100644 test/testhelpers/coverage_file.info.bad2 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