Skip to content

Commit

Permalink
Merge pull request #28390 from JuliaLang/jn/irshow3
Browse files Browse the repository at this point in the history
prettier IR-show for line number and inlining information
  • Loading branch information
vtjnash authored Sep 10, 2018
2 parents 97864d3 + 1f80e44 commit 999bcc5
Show file tree
Hide file tree
Showing 7 changed files with 371 additions and 163 deletions.
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 @@ -130,7 +130,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 @@ -150,17 +150,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

0 comments on commit 999bcc5

Please sign in to comment.