Skip to content

Commit

Permalink
reliable line numbers in code info printing (JuliaLang#29893)
Browse files Browse the repository at this point in the history
* code_llvm annotations: format prettier

Use box-drawing characters and indentation to make the output readable more rapidly.

* code_typed/code_lowered: format line-info similarly to code_llvm/code_native

For consistency of user experience, reduce the variance in our IR printing across formats.
This also now shows inlining and line number information even if the output device might not support color,
which was previously impossible (a regression since v0.6).

* code formatting: reduce inlining indentation

Here we make the observation that it's somewhat common to have chains
of methods of one function that recursively handle arguments in some
fashion (for example, map-tuple or +). However, since they all have the
same method name, it's possible to represent these on a single line.

* code show: thread debuginfo verbosity parameter throughout APIs

These levels are intended to roughly correspond approximately to compiler options -g0 (none) through -g2 (including local variables).
Currently we do not implement the ability to represent local variable information, so the default is to only show line number information
suitable for identifying the source line of a given statement (but not the types).

* code formatting: enable color usage

Only for Julia IR, for now. Later, this should be threaded into code_llvm and enabled there also.
I chose green for new nodes (like new life), and used yellow for metadata
(because the strong contrast helps to hide it visually, and similar to sticky notes),
and used grey for basic blocks (because they are minor information),
and used cyan/gray/Base.warn_color() for type information
(because that's what we've always used).
  • Loading branch information
vtjnash authored and vchuravy committed Nov 30, 2018
1 parent ef496b0 commit b3cf152
Show file tree
Hide file tree
Showing 20 changed files with 580 additions and 251 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ Standard library changes
* `edit` can now be called on a module to edit the file that defines it ([#29636]).
* `diff` now supports arrays of arbitrary dimensionality and can operate over any dimension ([#29827]).
* `sprandn` now supports result types like `ComplexF64` or `Float32` ([#30083]).
* All compiler-reflection tools (i.e. the `code_` class of functions and macros) now print accurate
line number and inlining information in a common style, and take an optional parameter (debuginfo=:default)
to control the verbosity of the metadata shown ([#29893]).
* The constructor `BigFloat(::BigFloat)` now respects the global precision setting and always
returns a `BigFloat` with precision equal to `precision(BigFloat)` ([#29127]). The optional
`precision` argument to override the global setting is now a keyword instead of positional
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/ssair/legacy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function inflate_ir(ci::CodeInfo, spvals::SimpleVector, argtypes::Vector{Any})
code[i] = stmt
end
end
ssavaluetypes = ci.ssavaluetypes isa Vector{Any} ? copy(ci.ssavaluetypes) : Any[ Any for i = 1:ci.ssavaluetypes ]
ssavaluetypes = ci.ssavaluetypes isa Vector{Any} ? copy(ci.ssavaluetypes) : Any[ Any for i = 1:(ci.ssavaluetypes::Int) ]
ir = IRCode(code, ssavaluetypes, copy(ci.codelocs), copy(ci.ssaflags), cfg, collect(LineInfoNode, ci.linetable),
argtypes, Any[], spvals)
return ir
Expand Down
272 changes: 198 additions & 74 deletions base/compiler/ssair/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ function Base.show(io::IO, cfg::CFG)
end

function print_stmt(io::IO, idx::Int, @nospecialize(stmt), used::BitSet, maxlength_idx::Int, color::Bool, show_type::Bool)
indent = maxlength_idx + 4
if idx in used
pad = " "^(maxlength_idx - length(string(idx)) + 1)
print(io, "%", idx, pad, "= ")
idx_s = string(idx)
pad = " "^(maxlength_idx - length(idx_s) + 1)
print(io, "%", idx_s, pad, "= ")
else
print(io, " "^indent)
print(io, " "^(maxlength_idx + 4))
end
# TODO: `indent` is supposed to be the full width of the leader for correct alignment
indent = 16
if !color && stmt isa PiNode
# when the outer context is already colored (yellow, for pending nodes), don't use the usual coloring printer
# when the outer context is already colored (green, for pending nodes), don't use the usual coloring printer
print(io, "π (")
show_unquoted(io, stmt.val, indent)
print(io, ", ")
Expand Down Expand Up @@ -321,6 +323,171 @@ end

Base.show(io::IO, code::IRCode) = show_ir(io, code)


lineinfo_disabled(io::IO, linestart::String, lineidx::Int32) = ""

function DILineInfoPrinter(linetable::Vector)
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)
# internal configuration options:
linecolor = :yellow
collapse = true
indent_all = true
# convert lineidx to a vector
if lineidx < 0
# sentinel value: reset internal (and external) state
pops = indent("")
if !isempty(pops)
print(io, linestart)
printstyled(io, pops; color=linecolor)
println(io)
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
# FOR DEBUGGING, or if you just like very excessive output:
# this prints out the context in full for every statement
#empty!(context)
#context_depth[] = 0
nframes = length(DI)
nctx = 0
pop_skips = 0
# compute the size of the matching prefix in the inlining information stack
for i = 1:min(length(context), nframes)
CtxLine = context[i]
FrameLine = DI[nframes - i + 1]
CtxLine === FrameLine || break
nctx = i
end
update_line_only = false
if collapse && 0 < nctx
# check if we're adding more frames with the same method name,
# if so, drop all existing calls to it from the top of the context
# AND check if instead the context was previously printed that way
# but now has removed the recursive frames
let method = context[nctx].method
if (nctx < nframes && DI[nframes - nctx].method === method) ||
(nctx < length(context) && context[nctx + 1].method === method)
update_line_only = true
while nctx > 0 && context[nctx].method === method
nctx -= 1
end
end
end
end
# examine what frames we're returning from
if nctx < length(context)
# compute the new inlining depth
if collapse
npops = 1
let Prev = context[nctx + 1].method
for i = (nctx + 2):length(context)
Next = context[i].method
Prev === Next || (npops += 1)
Prev = Next
end
end
else
npops = length(context) - nctx
end
# look at the first non-matching element to see if we are only changing the line number
if !update_line_only && nctx < nframes
let CtxLine = context[nctx + 1],
FrameLine = DI[nframes - nctx]
if CtxLine.file == FrameLine.file &&
CtxLine.method == FrameLine.method &&
CtxLine.mod == FrameLine.mod
update_line_only = true
end
end
end
resize!(context, nctx)
update_line_only && (npops -= 1)
if npops > 0
context_depth[] -= npops
print(io, linestart)
printstyled(io, indent(""), ""^npops; color=linecolor)
println(io)
end
end
# see what change we made to the outermost line number
if update_line_only
frame = DI[nframes - nctx]
nctx += 1
push!(context, frame)
if frame.line != typemax(frame.line) && frame.line != 0
print(io, linestart)
Base.with_output_color(linecolor, io) do io
print(io, indent(""), " @ ", frame.file, ":", frame.line, " within `", frame.method, "'")
if collapse
method = frame.method
while nctx < nframes
frame = DI[nframes - nctx]
frame.method === method || break
nctx += 1
push!(context, frame)
print(io, " @ ", frame.file, ":", frame.line)
end
end
end
println(io)
end
end
# now print the rest of the new frames
while nctx < nframes
frame = DI[nframes - nctx]
print(io, linestart)
Base.with_output_color(linecolor, io) do io
print(io, indent(""))
nctx += 1
push!(context, frame)
context_depth[] += 1
nctx != 1 && print(io, "")
print(io, " @ ", frame.file)
if frame.line != typemax(frame.line) && frame.line != 0
print(io, ":", frame.line)
end
print(io, " within `", frame.method, "'")
if collapse
method = frame.method
while nctx < nframes
frame = DI[nframes - nctx]
frame.method === method || break
nctx += 1
push!(context, frame)
print(io, " @ ", frame.file, ":", frame.line)
end
end
end
println(io)
end
# FOR DEBUGGING `collapse`:
# this double-checks the computation of context_depth
#let Prev = context[1].method,
# depth2 = 1
# for i = 2:nctx
# Next = context[i].method
# (collapse && Prev === Next) || (depth2 += 1)
# Prev = Next
# end
# @assert context_depth[] == depth2
#end
end
indent_all || return ""
return sprint(io -> printstyled(io, indent(""), color=linecolor), context=io)
end
return emit_lineinfo_update
end


function show_ir(io::IO, code::IRCode, expr_type_printer=default_expr_type_printer; verbose_linetable=false)
cols = displaysize(io)[2]
used = BitSet()
Expand Down Expand Up @@ -443,7 +610,7 @@ function show_ir(io::IO, code::IRCode, expr_type_printer=default_expr_type_print
print_sep = true
floop = false
show_type = should_print_ssa_type(new_node.node)
with_output_color(:yellow, io) do io′
with_output_color(:green, io) do io′
print_stmt(io′, node_idx, new_node.node, used, maxlength_idx, false, show_type)
end
if show_type
Expand Down Expand Up @@ -479,7 +646,7 @@ function show_ir(io::IO, code::IRCode, expr_type_printer=default_expr_type_print
end
end

function show_ir(io::IO, code::CodeInfo, expr_type_printer=default_expr_type_printer; verbose_linetable=false)
function show_ir(io::IO, code::CodeInfo, line_info_preprinter=DILineInfoPrinter(code.linetable), line_info_postprinter=default_expr_type_printer)
cols = displaysize(io)[2]
used = BitSet()
stmts = code.code
Expand All @@ -497,14 +664,6 @@ function show_ir(io::IO, code::CodeInfo, expr_type_printer=default_expr_type_pri
maxused = maximum(used)
maxlength_idx = length(string(maxused))
end
if !verbose_linetable
(loc_annotations, loc_methods, loc_lineno) = compute_ir_line_annotations(code)
max_loc_width = maximum(length(str) for str in loc_annotations)
max_lineno_width = maximum(length(str) for str in loc_lineno)
max_method_width = maximum(length(str) for str in loc_methods)
end
max_depth = maximum(compute_inlining_depth(code.linetable, line) for line in code.codelocs)
last_stack = []
for idx in eachindex(stmts)
if !isassigned(stmts, idx)
# This is invalid, but do something useful rather
Expand All @@ -515,71 +674,32 @@ function show_ir(io::IO, code::CodeInfo, expr_type_printer=default_expr_type_pri
stmt = stmts[idx]
# Compute BB guard rail
if bb_idx > length(cfg.blocks)
# Even if invariants are violated, try out best to still print
bb_range = last(cfg.blocks[end].stmts):typemax(Int)
bb_idx_str = "!!!"
bb_type = ""
# If invariants are violated, print a special leader
linestart = " "^(max_bb_idx_size + 2) # not inside a basic block bracket
inlining_indent = line_info_preprinter(io, linestart, code.codelocs[idx])
printstyled(io, "!!! ", ""^max_bb_idx_size, color=:light_black)
else
bbrange = cfg.blocks[bb_idx].stmts
bbrange = bbrange.start:bbrange.stop
bb_idx_str = string(bb_idx)
bb_type = length(cfg.blocks[bb_idx].preds) <= 1 ? "" : ""
end
bb_pad = max_bb_idx_size - length(bb_idx_str)
bb_start_str = string(bb_idx_str, " ", bb_type, ""^bb_pad, " ")
bb_guard_rail_cont = string("", " "^max_bb_idx_size)
if idx == first(bbrange)
bb_guard_rail = bb_start_str
else
bb_guard_rail = bb_guard_rail_cont
end
# Print linetable information
if verbose_linetable
stack = compute_loc_stack(code.linetable, code.codelocs[idx])
# We need to print any stack frames that did not exist in the last stack
ndepth = max(1, length(stack))
rail = string(" "^(max_depth+1-ndepth), ""^ndepth)
start_column = cols - max_depth - 10
for (i, x) in enumerate(stack)
if i > length(last_stack) || last_stack[i] != x
entry = code.linetable[x]
printstyled(io, "\e[$(start_column)G$(rail)\e[1G", color = :light_black)
print(io, bb_guard_rail)
ssa_guard = " "^(maxlength_idx + 4 + (i - 1))
entry_label = "$(ssa_guard)$(entry.method) at $(entry.file):$(entry.line) "
hline = string(""^(start_column-length(entry_label)-length(bb_guard_rail)+max_depth-i), "")
printstyled(io, string(entry_label, hline), "\n"; color=:light_black)
bb_guard_rail = bb_guard_rail_cont
end
end
printstyled(io, "\e[$(start_column)G$(rail)\e[1G", color = :light_black)
last_stack = stack
else
annotation = loc_annotations[idx]
loc_method = loc_methods[idx]
lineno = loc_lineno[idx]
# Print location information right aligned. If the line below is too long, it'll overwrite this,
# but that's what we want.
if get(io, :color, false)
method_start_column = cols - max_method_width - max_loc_width - 2
filler = " "^(max_loc_width-length(annotation))
printstyled(io, "\e[$(method_start_column)G$(annotation)$(filler)$(loc_method)\e[1G", color = :light_black)
end
printstyled(io, lineno, " "^(max_lineno_width-length(lineno)+1); color = :light_black)
end
idx != last(bbrange) && print(io, bb_guard_rail)
if idx == last(bbrange) # print separator
# Print line info update
linestart = idx == first(bbrange) ? " " : sprint(io -> printstyled(io, "", color=:light_black), context=io)
linestart *= " "^max_bb_idx_size
inlining_indent = line_info_preprinter(io, linestart, code.codelocs[idx])
if idx == first(bbrange)
print(io, bb_start_str)
elseif idx == last(bbrange)
print(io, "", ""^(1 + max_bb_idx_size), " ")
bb_idx_str = string(bb_idx)
bb_pad = max_bb_idx_size - length(bb_idx_str)
bb_type = length(cfg.blocks[bb_idx].preds) <= 1 ? "" : ""
printstyled(io, bb_idx_str, " ", bb_type, ""^bb_pad, color=:light_black)
elseif idx == last(bbrange) # print separator
printstyled(io, "", ""^(1 + max_bb_idx_size), color=:light_black)
else
print(io, "", " "^max_bb_idx_size)
printstyled(io, "", " "^max_bb_idx_size, color=:light_black)
end
if idx == last(bbrange)
bb_idx += 1
end
end
if idx == last(bbrange)
bb_idx += 1
end
print(io, inlining_indent, " ")
# convert statement index to labels, as expected by print_stmt
if stmt isa Expr
if stmt.head === :gotoifnot && length(stmt.args) == 2 && stmt.args[2] isa Int
Expand All @@ -603,11 +723,15 @@ function show_ir(io::IO, code::CodeInfo, expr_type_printer=default_expr_type_pri
printstyled(io, "::#UNDEF", color=:red)
elseif show_type
typ = types[idx]
expr_type_printer(io, typ, idx in used)
line_info_postprinter(io, typ, idx in used)
end
end
println(io)
end
let linestart = " "^(max_bb_idx_size + 2)
line_info_preprinter(io, linestart, typemin(Int32))
end
nothing
end

@specialize
18 changes: 4 additions & 14 deletions base/compiler/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,28 +94,18 @@ function get_staged(li::MethodInstance)
end
end

# create copies of the CodeInfo definition, and any fields that type-inference might modify
function copy_code_info(c::CodeInfo)
cnew = ccall(:jl_copy_code_info, Ref{CodeInfo}, (Any,), c)
cnew.code = copy_exprargs(cnew.code)
cnew.slotnames = copy(cnew.slotnames)
cnew.slotflags = copy(cnew.slotflags)
cnew.codelocs = copy(cnew.codelocs)
cnew.linetable = copy(cnew.linetable)
return cnew
end

function retrieve_code_info(linfo::MethodInstance)
m = linfo.def::Method
if isdefined(m, :generator)
# user code might throw errors – ignore them
return get_staged(linfo)
else
# TODO: post-inference see if we can swap back to the original arrays?
if isa(m.source, Array{UInt8,1})
c = ccall(:jl_uncompress_ast, Any, (Any, Any), m, m.source)
src = m.source
if isa(src, Array{UInt8,1})
c = ccall(:jl_uncompress_ast, Any, (Any, Any), m, src)
else
c = copy_code_info(m.source)
c = copy(src::CodeInfo)
end
end
return c::CodeInfo
Expand Down
Loading

0 comments on commit b3cf152

Please sign in to comment.