Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: prettier IR-show for line number and inlining information #28390

Merged
merged 4 commits into from
Sep 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions base/compiler/ssair/ir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ function basic_blocks_starts(stmts::Vector{Any})
push!(jump_dests, idx)
push!(jump_dests, idx+1)
# The catch block is a jump dest
push!(jump_dests, stmt.args[1])
push!(jump_dests, stmt.args[1]::Int)
elseif stmt.head === :gotoifnot
# also tolerate expr form of IR
push!(jump_dests, idx+1)
push!(jump_dests, stmt.args[2])
push!(jump_dests, stmt.args[2]::Int)
elseif stmt.head === :return
# also tolerate expr form of IR
# This is a fake dest to force the next stmt to start a bb
Expand Down Expand Up @@ -123,7 +123,7 @@ function compute_basic_blocks(stmts::Vector{Any})
# Compute successors/predecessors
for (num, b) in enumerate(blocks)
terminator = stmts[last(b.stmts)]
if isa(terminator, ReturnNode)
if isa(terminator, ReturnNode) || isexpr(terminator, :return)
# return never has any successors
continue
end
Expand All @@ -143,17 +143,28 @@ function compute_basic_blocks(stmts::Vector{Any})
push!(blocks[block′].preds, num)
push!(b.succs, block′)
end
elseif isa(terminator, Expr) && terminator.head == :enter
# :enter gets a virtual edge to the exception handler and
# the exception handler gets a virtual edge from outside
# the function.
# See the devdocs on exception handling in SSA form (or
# bug Keno to write them, if you're reading this and they
# don't exist)
block′ = block_for_inst(basic_block_index, terminator.args[1])
push!(blocks[block′].preds, num)
push!(blocks[block′].preds, 0)
push!(b.succs, block′)
elseif isa(terminator, Expr)
if terminator.head == :enter
# :enter gets a virtual edge to the exception handler and
# the exception handler gets a virtual edge from outside
# the function.
# See the devdocs on exception handling in SSA form (or
# bug Keno to write them, if you're reading this and they
# don't exist)
block′ = block_for_inst(basic_block_index, terminator.args[1]::Int)
push!(blocks[block′].preds, num)
push!(blocks[block′].preds, 0)
push!(b.succs, block′)
elseif terminator.head == :gotoifnot
block′ = block_for_inst(basic_block_index, terminator.args[2]::Int)
if block′ == num + 1
# This GotoIfNot acts like a noop - treat it as such.
# We will drop it during SSA renaming
else
push!(blocks[block′].preds, num)
push!(b.succs, block′)
end
end
end
# statement fall-through
if num + 1 <= length(blocks)
Expand Down
226 changes: 160 additions & 66 deletions base/compiler/ssair/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ 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
print(io, "π (")
Expand Down Expand Up @@ -314,6 +316,145 @@ 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:
collapse = true
indent_all = true
# convert lineidx to a vector
lineidx == 0 && return indent_all ? indent("│") : "" # 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
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, indent("│"), "┘"^npops, "\n")
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, 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
print(io, "\n")
end
end
# now print the rest of the new frames
while nctx < nframes
frame = DI[nframes - nctx]
print(io, linestart, 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
print(io, "\n")
end
# FOR DEBUGGING `collapse`:
#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
return indent_all ? indent("│") : ""
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 @@ -465,7 +606,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 @@ -483,14 +624,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 @@ -499,63 +632,24 @@ function show_ir(io::IO, code::CodeInfo, expr_type_printer=default_expr_type_pri
continue
end
stmt = stmts[idx]
# Compute BB guard rail
bbrange = cfg.blocks[bb_idx].stmts
bbrange = bbrange.first:bbrange.last
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 ? "─" : "┄"
bb_start_str = string(bb_idx_str, " ", bb_type, "─"^bb_pad, " ")
bb_guard_rail_cont = string("│ ", " "^max_bb_idx_size)
# 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])
# Compute BB guard rail
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
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
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
if idx == first(bbrange)
print(io, bb_start_str)
elseif idx == last(bbrange)
print(io, "└", "─"^(1 + max_bb_idx_size), " ")
else
print(io, "│ ", " "^max_bb_idx_size)
end
printstyled(io, "│ ", " "^max_bb_idx_size, color=:light_black)
end
print(io, inlining_indent, " ")
if idx == last(bbrange)
bb_idx += 1
end
Expand All @@ -582,7 +676,7 @@ 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)
Expand Down
19 changes: 10 additions & 9 deletions doc/src/devdocs/reflection.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,13 @@ particular interest for understanding how language constructs map to primitive o
as assignments, branches, and calls:

```jldoctest
julia> Meta.lower(@__MODULE__, :([1+2, sin(0.5)]) )
julia> Meta.lower(@__MODULE__, :( [1+2, sin(0.5)] ))
:($(Expr(:thunk, CodeInfo(
1 ─ %1 = 1 + 2
│ %2 = sin(0.5)
│ %3 = (Base.vect)(%1, %2)
└── return %3
@ none within `top-level scope'
1 ─ %1 = 1 + 2
│ %2 = sin(0.5)
│ %3 = (Base.vect)(%1, %2)
└── return %3
))))
```

Expand All @@ -122,11 +123,11 @@ calls and expand argument types automatically:
```julia-repl
julia> @code_llvm +(1,1)

; Function Attrs: sspreq
define i64 @"julia_+_130862"(i64, i64) #0 {
; @ int.jl:53 within `+'
define i64 @"julia_+_130862"(i64, i64) {
top:
%2 = add i64 %1, %0, !dbg !8
ret i64 %2, !dbg !8
%2 = add i64 %1, %0
ret i64 %2
}
```

Expand Down
Loading