diff --git a/NEWS.md b/NEWS.md index 6c3de55f01655..775e486421b2b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/base/compiler/ssair/legacy.jl b/base/compiler/ssair/legacy.jl index a25ed960669a3..45a974ab56721 100644 --- a/base/compiler/ssair/legacy.jl +++ b/base/compiler/ssair/legacy.jl @@ -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 diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index b255b79f1f2c4..d51b086c8de40 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -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, ", ") @@ -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() @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 89f5c0b05fb52..a953e5c0d6021 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -94,17 +94,6 @@ 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) @@ -112,10 +101,11 @@ function retrieve_code_info(linfo::MethodInstance) 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 diff --git a/base/expr.jl b/base/expr.jl index 47e68f85214bc..c27d5bc6b3b05 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -59,6 +59,21 @@ function copy_exprs(x::PhiCNode) end copy_exprargs(x::Array{Any,1}) = Any[copy_exprs(x[i]) for i in 1:length(x)] +# create copies of the CodeInfo definition, and any mutable fields +function copy(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) + cnew.ssaflags = copy(cnew.ssaflags) + ssavaluetypes = cnew.ssavaluetypes + ssavaluetypes isa Vector{Any} && (cnew.ssavaluetypes = copy(ssavaluetypes)) + return cnew +end + + ==(x::Expr, y::Expr) = x.head === y.head && isequal(x.args, y.args) ==(x::QuoteNode, y::QuoteNode) = isequal(x.value, y.value) @@ -352,10 +367,15 @@ function remove_linenums!(ex::Expr) end end for subex in ex.args - remove_linenums!(subex) + subex isa Expr && remove_linenums!(subex) end return ex end +function remove_linenums!(src::CodeInfo) + src.codelocs .= 0 + length(src.linetable) > 1 && resize!(src.linetable, 1) + return src +end macro generated() return Expr(:generated) diff --git a/base/methodshow.jl b/base/methodshow.jl index b8830cd0cab13..32b91c7bfd89a 100644 --- a/base/methodshow.jl +++ b/base/methodshow.jl @@ -79,14 +79,21 @@ function kwarg_decl(m::Method, kwtype::DataType) kwli = ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), kwtype.name.mt, sig, max_world(m)) if kwli !== nothing kwli = kwli::Method - src = uncompressed_ast(kwli) - kws = filter(x -> !('#' in string(x)), src.slotnames[(kwli.nargs + 1):end]) - # ensure the kwarg... is always printed last. The order of the arguments are not - # necessarily the same as defined in the function - i = findfirst(x -> endswith(string(x), "..."), kws) - i === nothing && return kws - push!(kws, kws[i]) - return deleteat!(kws, i) + if isdefined(kwli, :source) + src = kwli.source + nslots = ccall(:jl_ast_nslots, Int, (Any,), src) + slotnames = Vector{Any}(undef, nslots) + ccall(:jl_fill_argnames, Cvoid, (Any, Any), src, slotnames) + kws = filter(x -> !('#' in string(x)), slotnames[(kwli.nargs + 1):end]) + # ensure the kwarg... is always printed last. The order of the arguments are not + # necessarily the same as defined in the function + i = findfirst(x -> endswith(string(x), "..."), kws) + if i !== nothing + push!(kws, kws[i]) + deleteat!(kws, i) + end + return kws + end end return () end diff --git a/base/reflection.jl b/base/reflection.jl index f1597afacf1bf..8d20f9b67a4f7 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -723,7 +723,7 @@ function signature_type(@nospecialize(f), @nospecialize(args)) end """ - code_lowered(f, types; generated = true) + code_lowered(f, types; generated=true, debuginfo=:default) Return an array of the lowered forms (IR) for the methods matching the given generic function and type signature. @@ -733,10 +733,17 @@ implementations. An error is thrown if no fallback implementation exists. If `generated` is `true`, these `CodeInfo` instances will correspond to the method bodies yielded by expanding the generators. +The keyword debuginfo controls the amount of code metadata present in the output. + Note that an error will be thrown if `types` are not leaf types when `generated` is -`true` and the corresponding method is a `@generated` method. +`true` and any of the corresponding methods are an `@generated` method. """ -function code_lowered(@nospecialize(f), @nospecialize(t = Tuple); generated::Bool = true) +function code_lowered(@nospecialize(f), @nospecialize(t=Tuple); generated::Bool=true, debuginfo::Symbol=:default) + if debuginfo == :default + debuginfo = :source + elseif debuginfo != :source && debuginfo != :none + throw(ArgumentError("'debuginfo' must be either :source or :none")) + end return map(method_instances(f, t)) do m if generated && isgenerated(m) if isa(m, Core.MethodInstance) @@ -747,7 +754,9 @@ function code_lowered(@nospecialize(f), @nospecialize(t = Tuple); generated::Boo "not leaf types, but the `generated` argument is `true`.") end end - return uncompressed_ast(m) + code = uncompressed_ast(m) + debuginfo == :none && remove_linenums!(code) + return code end end @@ -865,10 +874,10 @@ function length(mt::Core.MethodTable) end isempty(mt::Core.MethodTable) = (mt.defs === nothing) -uncompressed_ast(m::Method) = isdefined(m,:source) ? uncompressed_ast(m, m.source) : - isdefined(m,:generator) ? error("Method is @generated; try `code_lowered` instead.") : +uncompressed_ast(m::Method) = isdefined(m, :source) ? uncompressed_ast(m, m.source) : + isdefined(m, :generator) ? error("Method is @generated; try `code_lowered` instead.") : error("Code for this Method is not available.") -uncompressed_ast(m::Method, s::CodeInfo) = s +uncompressed_ast(m::Method, s::CodeInfo) = copy(s) uncompressed_ast(m::Method, s::Array{UInt8,1}) = ccall(:jl_uncompress_ast, Any, (Any, Any), m, s)::CodeInfo uncompressed_ast(m::Core.MethodInstance) = uncompressed_ast(m.def) @@ -920,17 +929,23 @@ function func_for_method_checked(m::Method, @nospecialize types) end """ - code_typed(f, types; optimize=true) + code_typed(f, types; optimize=true, debuginfo=:default) Returns an array of type-inferred lowered form (IR) for the methods matching the given generic function and type signature. The keyword argument `optimize` controls whether additional optimizations, such as inlining, are also applied. +The keyword debuginfo controls the amount of code metadata present in the output. """ -function code_typed(@nospecialize(f), @nospecialize(types=Tuple); optimize=true) +function code_typed(@nospecialize(f), @nospecialize(types=Tuple); optimize=true, debuginfo::Symbol=:default) ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") if isa(f, Core.Builtin) throw(ArgumentError("argument is not a generic function")) end + if debuginfo == :default + debuginfo = :source + elseif debuginfo != :source && debuginfo != :none + throw(ArgumentError("'debuginfo' must be either :source or :none")) + end types = to_tuple_type(types) asts = [] world = ccall(:jl_get_world_counter, UInt, ()) @@ -939,6 +954,7 @@ function code_typed(@nospecialize(f), @nospecialize(types=Tuple); optimize=true) meth = func_for_method_checked(x[3], types) (code, ty) = Core.Compiler.typeinf_code(meth, x[1], x[2], optimize, params) code === nothing && error("inference not successful") # inference disabled? + debuginfo == :none && remove_linenums!(code) push!(asts, code => ty) end return asts diff --git a/base/show.jl b/base/show.jl index ae782f1e77bc8..9d16faaa7d7f1 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1562,9 +1562,17 @@ module IRShow Base.first(r::Compiler.StmtRange) = Compiler.first(r) Base.last(r::Compiler.StmtRange) = Compiler.last(r) include("compiler/ssair/show.jl") + + const debuginfo = Dict{Symbol, Any}( + # :full => src -> Base.IRShow.DILineInfoPrinter(src.linetable), # and add variable slot information + :source => src -> Base.IRShow.DILineInfoPrinter(src.linetable), + # :oneliner => src -> Base.IRShow.PartialLineInfoPrinter(src.linetable), + :none => src -> Base.IRShow.lineinfo_disabled, + ) + debuginfo[:default] = debuginfo[:none] end -function show(io::IO, src::CodeInfo) +function show(io::IO, src::CodeInfo; debuginfo::Symbol=:default) # Fix slot names and types in function body print(io, "CodeInfo(") lambda_io::IOContext = io @@ -1575,7 +1583,7 @@ function show(io::IO, src::CodeInfo) if isempty(src.linetable) || src.linetable[1] isa LineInfoNode println(io) # TODO: static parameter values? - IRShow.show_ir(lambda_io, src) + IRShow.show_ir(lambda_io, src, 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) diff --git a/doc/src/devdocs/reflection.md b/doc/src/devdocs/reflection.md index bcf22fd0f5258..3e41b146ad09b 100644 --- a/doc/src/devdocs/reflection.md +++ b/doc/src/devdocs/reflection.md @@ -94,12 +94,12 @@ 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 +1 ─ %1 = 1 + 2 +│ %2 = sin(0.5) +│ %3 = (Base.vect)(%1, %2) +└── return %3 )))) ``` @@ -122,11 +122,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 } ``` diff --git a/src/anticodegen.c b/src/anticodegen.c index 30337f75d82e2..c34de0e82848d 100644 --- a/src/anticodegen.c +++ b/src/anticodegen.c @@ -17,8 +17,8 @@ void jl_write_coverage_data(void) UNAVAILABLE JL_DLLEXPORT void jl_clear_malloc_data(void) UNAVAILABLE JL_DLLEXPORT void jl_extern_c(jl_function_t *f, jl_value_t *rt, jl_value_t *argt, char *name) UNAVAILABLE JL_DLLEXPORT void *jl_function_ptr(jl_function_t *f, jl_value_t *rt, jl_value_t *argt) UNAVAILABLE -JL_DLLEXPORT const jl_value_t *jl_dump_function_asm(void *f, int raw_mc, const char* asm_variant) UNAVAILABLE -JL_DLLEXPORT const jl_value_t *jl_dump_function_ir(void *f, uint8_t strip_ir_metadata, uint8_t dump_module) UNAVAILABLE +JL_DLLEXPORT const jl_value_t *jl_dump_function_asm(void *f, int raw_mc, const char* asm_variant, const char *debuginfo) UNAVAILABLE +JL_DLLEXPORT const jl_value_t *jl_dump_function_ir(void *f, uint8_t strip_ir_metadata, uint8_t dump_module, const char *debuginfo) UNAVAILABLE JL_DLLEXPORT void *jl_LLVMCreateDisasm(const char *TripleName, void *DisInfo, int TagType, void *GetOpInfo, void *SymbolLookUp) UNAVAILABLE JL_DLLEXPORT size_t jl_LLVMDisasmInstruction(void *DC, uint8_t *Bytes, uint64_t BytesSize, uint64_t PC, char *OutString, size_t OutStringSize) UNAVAILABLE diff --git a/src/codegen.cpp b/src/codegen.cpp index fcbfd2deb12c2..3067aea2ee74d 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1594,7 +1594,7 @@ void *jl_get_llvmf_decl(jl_method_instance_t *linfo, size_t world, bool getwrapp // get a native disassembly for f (an LLVM function) // warning: this takes ownership of, and destroys, f extern "C" JL_DLLEXPORT -const jl_value_t *jl_dump_function_asm(void *f, int raw_mc, const char* asm_variant) +const jl_value_t *jl_dump_function_asm(void *f, int raw_mc, const char* asm_variant, const char *debuginfo) { Function *llvmf = dyn_cast_or_null((Function*)f); if (!llvmf) @@ -1604,7 +1604,7 @@ const jl_value_t *jl_dump_function_asm(void *f, int raw_mc, const char* asm_vari if (fptr == 0) fptr = (uintptr_t)jl_ExecutionEngine->getPointerToGlobalIfAvailable(llvmf); delete llvmf; - return jl_dump_fptr_asm(fptr, raw_mc, asm_variant); + return jl_dump_fptr_asm(fptr, raw_mc, asm_variant, debuginfo); } // Logging for code coverage and memory allocation diff --git a/src/disasm.cpp b/src/disasm.cpp index e72000da83a57..418643d56874c 100644 --- a/src/disasm.cpp +++ b/src/disasm.cpp @@ -74,16 +74,49 @@ using namespace llvm; // helper class for tracking inlining context while printing debug info class DILineInfoPrinter { + // internal state: std::vector context; - char LineStart; - bool bracket_outer; + uint32_t inline_depth = 0; + // configuration options: + const char* LineStart = "; "; + bool bracket_outer = false; + bool collapse_recursive = true; + + enum { + output_none = 0, + output_source = 1, + } verbosity = output_source; public: - DILineInfoPrinter(char LineStart, bool bracket_outer) + DILineInfoPrinter(const char *LineStart, bool bracket_outer) : LineStart(LineStart), bracket_outer(bracket_outer) {}; + void SetVerbosity(const char *c) + { + if (StringRef("default") == c) { + verbosity = output_source; + } + else if (StringRef("source") == c) { + verbosity = output_source; + } + else if (StringRef("none") == c) { + verbosity = output_none; + } + } + void emit_finish(raw_ostream &Out); void emit_lineinfo(raw_ostream &Out, std::vector &DI); + struct repeat { + size_t times; + const char *c; + }; + struct repeat inlining_indent(const char *c) + { + return repeat{ + std::max(inline_depth + bracket_outer, (uint32_t)1) - 1, + c }; + } + template void emit_lineinfo(std::string &Out, T &DI) { @@ -115,76 +148,163 @@ class DILineInfoPrinter { } }; +static raw_ostream &operator<<(raw_ostream &Out, struct DILineInfoPrinter::repeat i) +{ + while (i.times-- > 0) + Out << i.c; + return Out; +} + void DILineInfoPrinter::emit_finish(raw_ostream &Out) { - uint32_t npops = context.size(); - if (!bracket_outer && npops > 0) - npops--; - if (npops) { - Out << LineStart; - while (npops--) - Out << '}'; - Out << '\n'; - } + auto pops = inlining_indent("└"); + if (pops.times > 0) + Out << LineStart << pops << '\n'; context.clear(); + this->inline_depth = 0; } void DILineInfoPrinter::emit_lineinfo(raw_ostream &Out, std::vector &DI) { + if (verbosity == output_none) + return; bool update_line_only = false; - uint32_t nctx = context.size(); uint32_t nframes = DI.size(); if (nframes == 0) return; // just skip over lines with no debug info at all - if (nctx > nframes) - context.resize(nframes); - for (uint32_t i = 0; i < nctx && i < nframes; i++) { - const DILineInfo &CtxLine = context.at(i); - const DILineInfo &FrameLine = DI.at(nframes - 1 - i); + // compute the size of the matching prefix in the inlining information stack + uint32_t nctx; + for (nctx = 0; nctx < context.size() && nctx < nframes; nctx++) { + const DILineInfo &CtxLine = context.at(nctx); + const DILineInfo &FrameLine = DI.at(nframes - 1 - nctx); if (CtxLine != FrameLine) { + break; + } + } + if (collapse_recursive && 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 + StringRef method = StringRef(context.at(nctx - 1).FunctionName).rtrim(';'); + if ((nctx < nframes && StringRef(DI.at(nframes - nctx - 1).FunctionName).rtrim(';') == method) || + (nctx < context.size() && StringRef(context.at(nctx).FunctionName).rtrim(';') == method)) { + update_line_only = true; + while (nctx > 0 && StringRef(context.at(nctx - 1).FunctionName).rtrim(';') == method) { + nctx -= 1; + } + } + } + // examine what frames we're returning from + if (nctx < context.size()) { + // compute the new inlining depth + uint32_t npops; + if (collapse_recursive) { + npops = 1; + StringRef Prev = StringRef(context.at(nctx).FunctionName).rtrim(';'); + for (uint32_t i = nctx + 1; i < context.size(); i++) { + StringRef Next = StringRef(context.at(i).FunctionName).rtrim(';'); + if (Prev != Next) + npops += 1; + Prev = Next; + } + } + else { + npops = context.size() - nctx; + } + // look at the first non-matching element to see if we are only changing the line number + if (!update_line_only && nctx < nframes) { + const DILineInfo &CtxLine = context.at(nctx); + const DILineInfo &FrameLine = DI.at(nframes - 1 - nctx); if (CtxLine.FileName == FrameLine.FileName && - CtxLine.FunctionName == FrameLine.FunctionName) { + StringRef(CtxLine.FunctionName).rtrim(';') == StringRef(FrameLine.FunctionName).rtrim(';')) { update_line_only = true; } - context.resize(i); - break; + } + context.resize(nctx); + update_line_only && (npops -= 1); + if (npops > 0) { + this->inline_depth -= npops; + Out << LineStart << inlining_indent("│") << repeat{npops, "└"} << '\n'; } } - uint32_t npops = nctx - context.size() - update_line_only; - if (npops) { - Out << LineStart; - while (npops--) - Out << '}'; - Out << '\n'; - } + // see what change we made to the outermost line number if (update_line_only) { - DILineInfo frame = DI.at(nframes - 1 - context.size()); - if (frame.Line != UINT_MAX && frame.Line != 0) - Out << LineStart << " Location: " << frame.FileName << ":" << frame.Line << '\n'; + const DILineInfo &frame = DI.at(nframes - 1 - nctx); + nctx += 1; context.push_back(frame); + if (frame.Line != UINT_MAX && frame.Line != 0) { + StringRef method = StringRef(frame.FunctionName).rtrim(';'); + Out << LineStart << inlining_indent("│") + << " @ " << frame.FileName + << ":" << frame.Line + << " within `" << method << "'"; + if (collapse_recursive) { + while (nctx < nframes) { + const DILineInfo &frame = DI.at(nframes - 1 - nctx); + if (StringRef(frame.FunctionName).rtrim(';') != method) + break; + nctx += 1; + context.push_back(frame); + Out << " @ " << frame.FileName + << ":" << frame.Line; + } + } + Out << "\n"; + } } - for (uint32_t i = context.size(); i < nframes; i++) { - DILineInfo frame = DI.at(nframes - 1 - i); + // now print the rest of the new frames + while (nctx < nframes) { + const DILineInfo &frame = DI.at(nframes - 1 - nctx); + Out << LineStart << inlining_indent("│"); + nctx += 1; context.push_back(frame); - Out << LineStart << " Function " << frame.FunctionName; - if (bracket_outer || i != 0) - Out << " {"; - Out << "\n" << LineStart << " Location: " << frame.FileName; + this->inline_depth += 1; + if (bracket_outer || nctx != 1) + Out << "┌"; + Out << " @ " << frame.FileName; if (frame.Line != UINT_MAX && frame.Line != 0) Out << ":" << frame.Line; + Out << " within `" << StringRef(frame.FunctionName).rtrim(';') << "'"; + if (collapse_recursive) { + StringRef method = StringRef(frame.FunctionName).rtrim(';'); + while (nctx < nframes) { + const DILineInfo &frame = DI.at(nframes - 1 - nctx); + if (StringRef(frame.FunctionName).rtrim(';') != method) + break; + nctx += 1; + context.push_back(frame); + Out << " @ " << frame.FileName + << ":" << frame.Line; + } + } Out << "\n"; } +#ifndef JL_NDEBUG + StringRef Prev = StringRef(context.at(0).FunctionName).rtrim(';'); + uint32_t depth2 = 1; + for (uint32_t i = 1; i < nctx; i++) { + StringRef Next = StringRef(context.at(i).FunctionName).rtrim(';'); + if (!collapse_recursive || Prev != Next) + depth2 += 1; + Prev = Next; + } + assert(this->inline_depth == depth2); +#endif } // adaptor class for printing line numbers before llvm IR lines class LineNumberAnnotatedWriter : public AssemblyAnnotationWriter { DILocation *InstrLoc = nullptr; - DILineInfoPrinter LinePrinter{';', false}; + DILineInfoPrinter LinePrinter{"; ", false}; DenseMap DebugLoc; DenseMap Subprogram; public: - LineNumberAnnotatedWriter() {} + LineNumberAnnotatedWriter(const char *debuginfo) + { + LinePrinter.SetVerbosity(debuginfo); + } virtual void emitFunctionAnnot(const Function *, formatted_raw_ostream &); virtual void emitInstructionAnnot(const Instruction *, formatted_raw_ostream &); virtual void emitBasicBlockEndAnnot(const BasicBlock *, formatted_raw_ostream &); @@ -211,14 +331,14 @@ void LineNumberAnnotatedWriter::emitFunctionAnnot( if (SP != Subprogram.end()) FuncLoc = SP->second; } - if (!FuncLoc) - return; - std::vector DIvec(1); - DILineInfo &DI = DIvec.back(); - DI.FunctionName = FuncLoc->getName(); - DI.FileName = FuncLoc->getFilename(); - DI.Line = FuncLoc->getLine(); - LinePrinter.emit_lineinfo(Out, DIvec); + if (FuncLoc) { + std::vector DIvec(1); + DILineInfo &DI = DIvec.back(); + DI.FunctionName = FuncLoc->getName(); + DI.FileName = FuncLoc->getFilename(); + DI.Line = FuncLoc->getLine(); + LinePrinter.emit_lineinfo(Out, DIvec); + } } void LineNumberAnnotatedWriter::emitInstructionAnnot( @@ -230,21 +350,22 @@ void LineNumberAnnotatedWriter::emitInstructionAnnot( if (Loc != DebugLoc.end()) NewInstrLoc = Loc->second; } - if (!NewInstrLoc || NewInstrLoc == InstrLoc) - return; - InstrLoc = NewInstrLoc; - std::vector DIvec; - do { - DIvec.emplace_back(); - DILineInfo &DI = DIvec.back(); - DIScope *scope = NewInstrLoc->getScope(); - if (scope) - DI.FunctionName = scope->getName(); - DI.FileName = NewInstrLoc->getFilename(); - DI.Line = NewInstrLoc->getLine(); - NewInstrLoc = NewInstrLoc->getInlinedAt(); - } while (NewInstrLoc); - LinePrinter.emit_lineinfo(Out, DIvec); + if (NewInstrLoc && NewInstrLoc != InstrLoc) { + InstrLoc = NewInstrLoc; + std::vector DIvec; + do { + DIvec.emplace_back(); + DILineInfo &DI = DIvec.back(); + DIScope *scope = NewInstrLoc->getScope(); + if (scope) + DI.FunctionName = scope->getName(); + DI.FileName = NewInstrLoc->getFilename(); + DI.Line = NewInstrLoc->getLine(); + NewInstrLoc = NewInstrLoc->getInlinedAt(); + } while (NewInstrLoc); + LinePrinter.emit_lineinfo(Out, DIvec); + } + Out << LinePrinter.inlining_indent(" "); } void LineNumberAnnotatedWriter::emitBasicBlockEndAnnot( @@ -257,7 +378,7 @@ void LineNumberAnnotatedWriter::emitBasicBlockEndAnnot( // print an llvm IR acquired from jl_get_llvmf // warning: this takes ownership of, and destroys, f->getParent() extern "C" JL_DLLEXPORT -jl_value_t *jl_dump_function_ir(void *f, bool strip_ir_metadata, bool dump_module) +jl_value_t *jl_dump_function_ir(void *f, bool strip_ir_metadata, bool dump_module, const char *debuginfo) { std::string code; llvm::raw_string_ostream stream(code); @@ -267,7 +388,7 @@ jl_value_t *jl_dump_function_ir(void *f, bool strip_ir_metadata, bool dump_modul jl_error("jl_dump_function_ir: Expected Function* in a temporary Module"); JL_LOCK(&codegen_lock); // Might GC - LineNumberAnnotatedWriter AAW; + LineNumberAnnotatedWriter AAW{debuginfo}; if (!llvmf->getParent()) { // print the function declaration as-is llvmf->print(stream, &AAW); @@ -330,7 +451,8 @@ static void jl_dump_asm_internal( const object::ObjectFile *object, DIContext *di_ctx, raw_ostream &rstream, - const char* asm_variant); + const char* asm_variant, + const char* debuginfo); // This isn't particularly fast, but neither is printing assembly, and they're only used for interactive mode static uint64_t compute_obj_symsize(const object::ObjectFile *obj, uint64_t offset) @@ -379,7 +501,7 @@ static uint64_t compute_obj_symsize(const object::ObjectFile *obj, uint64_t offs // print a native disassembly for the function starting at fptr extern "C" JL_DLLEXPORT -jl_value_t *jl_dump_fptr_asm(uint64_t fptr, int raw_mc, const char* asm_variant) +jl_value_t *jl_dump_fptr_asm(uint64_t fptr, int raw_mc, const char* asm_variant, const char *debuginfo) { assert(fptr != 0); jl_ptls_t ptls = jl_get_ptls_states(); @@ -415,7 +537,8 @@ jl_value_t *jl_dump_fptr_asm(uint64_t fptr, int raw_mc, const char* asm_variant) fptr, symsize, slide, object, context, stream, - asm_variant); + asm_variant, + debuginfo); jl_gc_safe_leave(ptls, gc_state); return jl_pchar_to_string(stream.str().data(), stream.str().size()); @@ -619,7 +742,8 @@ static void jl_dump_asm_internal( const object::ObjectFile *object, DIContext *di_ctx, raw_ostream &rstream, - const char* asm_variant) + const char* asm_variant, + const char* debuginfo) { // GC safe // Get the host information @@ -736,7 +860,7 @@ static void jl_dump_asm_internal( uint64_t nextLineAddr = -1; DILineInfoTable::iterator di_lineIter = di_lineinfo.begin(); DILineInfoTable::iterator di_lineEnd = di_lineinfo.end(); - DILineInfoPrinter dbgctx{';', true}; + DILineInfoPrinter dbgctx{"; ", true}; if (pass != 0) { if (di_ctx && di_lineIter != di_lineEnd) { // Set up the line info diff --git a/src/dump.c b/src/dump.c index 197748dd05f44..d29ae8565bdde 100644 --- a/src/dump.c +++ b/src/dump.c @@ -2607,6 +2607,19 @@ JL_DLLEXPORT uint8_t jl_ast_flag_pure(jl_array_t *data) return !!(flags & (1 << 0)); } +JL_DLLEXPORT ssize_t jl_ast_nslots(jl_array_t *data) +{ + if (jl_is_code_info(data)) { + jl_code_info_t *func = (jl_code_info_t*)data; + return jl_array_len(func->slotnames); + } + else { + assert(jl_typeis(data, jl_array_uint8_type)); + int nslots = jl_load_unaligned_i32((char*)data->data + 1); + return nslots; + } +} + JL_DLLEXPORT void jl_fill_argnames(jl_array_t *data, jl_array_t *names) { size_t i, nargs = jl_array_len(names); @@ -2619,16 +2632,12 @@ JL_DLLEXPORT void jl_fill_argnames(jl_array_t *data, jl_array_t *names) } } else { - uint8_t *d = (uint8_t*)data->data; assert(jl_typeis(data, jl_array_uint8_type)); - int b3 = d[1]; - int b2 = d[2]; - int b1 = d[3]; - int b0 = d[4]; - int nslots = b0 | (b1<<8) | (b2<<16) | (b3<<24); + char *d = (char*)data->data; + int nslots = jl_load_unaligned_i32(d + 1); assert(nslots >= nargs); (void)nslots; - char *namestr = (char*)d + 5; + char *namestr = d + 5; for (i = 0; i < nargs; i++) { size_t namelen = strlen(namestr); jl_sym_t *name = jl_symbol_n(namestr, namelen); diff --git a/src/julia_internal.h b/src/julia_internal.h index e8f0b55fa8d37..af463a86fc5f4 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -560,7 +560,7 @@ static inline void jl_set_gc_and_wait(void) } #endif -JL_DLLEXPORT jl_value_t *jl_dump_fptr_asm(uint64_t fptr, int raw_mc, const char* asm_variant); +JL_DLLEXPORT jl_value_t *jl_dump_fptr_asm(uint64_t fptr, int raw_mc, const char *asm_variant, const char *debuginfo); void jl_dump_native(const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *sysimg_data, size_t sysimg_len); int32_t jl_get_llvm_gv(jl_value_t *p) JL_NOTSAFEPOINT; diff --git a/stdlib/InteractiveUtils/src/codeview.jl b/stdlib/InteractiveUtils/src/codeview.jl index 407cb37e900b0..8b3c2568f7165 100644 --- a/stdlib/InteractiveUtils/src/codeview.jl +++ b/stdlib/InteractiveUtils/src/codeview.jl @@ -17,7 +17,7 @@ function warntype_type_printer(io::IO, @nospecialize(ty), used::Bool) end """ - code_warntype([io::IO], f, types; verbose_linetable=false) + code_warntype([io::IO], f, types; debuginfo=:default) Prints lowered and type-inferred ASTs for the methods matching the given generic function and type signature to `io` which defaults to `stdout`. The ASTs are annotated in such a way @@ -26,12 +26,13 @@ This serves as a warning of potential type instability. Not all non-leaf types a problematic for performance, so the results need to be used judiciously. In particular, unions containing either [`missing`](@ref) or [`nothing`](@ref) are displayed in yellow, since these are often intentional. -If the `verbose_linetable` keyword is set, the linetable will be printed -in verbose mode, showing all available information (rather than applying -the usual heuristics). + +Keyword argument `debuginfo` may be one of source or none (default), to specify the verbosity of code comments. + See [`@code_warntype`](@ref man-code-warntype) for more information. """ -function code_warntype(io::IO, @nospecialize(f), @nospecialize(t); verbose_linetable=false) +function code_warntype(io::IO, @nospecialize(f), @nospecialize(t); debuginfo::Symbol=:default) + lineprinter = Base.IRShow.debuginfo[debuginfo] for (src, rettype) in code_typed(f, t) lambda_io::IOContext = io if src.slotnames !== nothing @@ -41,8 +42,7 @@ function code_warntype(io::IO, @nospecialize(f), @nospecialize(t); verbose_linet warntype_type_printer(io, rettype, true) println(io) # TODO: static parameter values - Base.IRShow.show_ir(lambda_io, src, warntype_type_printer; - verbose_linetable = verbose_linetable) + Base.IRShow.show_ir(lambda_io, src, lineprinter(src), warntype_type_printer) end nothing end @@ -53,8 +53,9 @@ import Base.CodegenParams # Printing code representations in IR and assembly function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrapper::Bool, - strip_ir_metadata::Bool, dump_module::Bool, syntax::Symbol=:att, - optimize::Bool=true, params::CodegenParams=CodegenParams()) + strip_ir_metadata::Bool, dump_module::Bool, syntax::Symbol, + optimize::Bool, debuginfo::Symbol, + params::CodegenParams=CodegenParams()) ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") if isa(f, Core.Builtin) throw(ArgumentError("argument is not a generic function")) @@ -68,15 +69,21 @@ function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrappe meth = Base.func_for_method_checked(meth, ti) linfo = ccall(:jl_specializations_get_linfo, Ref{Core.MethodInstance}, (Any, Any, Any, UInt), meth, ti, env, world) # get the code for it - return _dump_function_linfo(linfo, world, native, wrapper, strip_ir_metadata, dump_module, syntax, optimize, params) + return _dump_function_linfo(linfo, world, native, wrapper, strip_ir_metadata, dump_module, syntax, optimize, debuginfo, params) end function _dump_function_linfo(linfo::Core.MethodInstance, world::UInt, native::Bool, wrapper::Bool, - strip_ir_metadata::Bool, dump_module::Bool, syntax::Symbol=:att, - optimize::Bool=true, params::CodegenParams=CodegenParams()) + strip_ir_metadata::Bool, dump_module::Bool, syntax::Symbol, + optimize::Bool, debuginfo::Symbol, + params::CodegenParams=CodegenParams()) if syntax != :att && syntax != :intel throw(ArgumentError("'syntax' must be either :intel or :att")) end + if debuginfo == :default + debuginfo = :source + elseif debuginfo != :source && debuginfo != :none + throw(ArgumentError("'debuginfo' must be either :source or :none")) + end if native llvmf = ccall(:jl_get_llvmf_decl, Ptr{Cvoid}, (Any, UInt, Bool, CodegenParams), linfo, world, wrapper, params) else @@ -88,10 +95,10 @@ function _dump_function_linfo(linfo::Core.MethodInstance, world::UInt, native::B if native str = ccall(:jl_dump_function_asm, Ref{String}, - (Ptr{Cvoid}, Cint, Ptr{UInt8}), llvmf, 0, syntax) + (Ptr{Cvoid}, Cint, Ptr{UInt8}, Ptr{UInt8}), llvmf, 0, syntax, debuginfo) else str = ccall(:jl_dump_function_ir, Ref{String}, - (Ptr{Cvoid}, Bool, Bool), llvmf, strip_ir_metadata, dump_module) + (Ptr{Cvoid}, Bool, Bool, Ptr{UInt8}), llvmf, strip_ir_metadata, dump_module, debuginfo) end # TODO: use jl_is_cacheable_sig instead of isdispatchtuple @@ -100,28 +107,34 @@ function _dump_function_linfo(linfo::Core.MethodInstance, world::UInt, native::B end """ - code_llvm([io=stdout,], f, types) + code_llvm([io=stdout,], f, types; raw=false, dump_module=false, optimize=true, debuginfo=:default) Prints the LLVM bitcodes generated for running the method matching the given generic function and type signature to `io`. If the `optimize` keyword is unset, the code will be shown before LLVM optimizations. -All metadata and dbg.* calls are removed from the printed bitcode. Set the `raw` keyword for the full IR. -To dump the entire module that encapsulates the function, with debug info and metadata, set the `dump_module` keyword. +All metadata and dbg.* calls are removed from the printed bitcode. For the full IR, set the `raw` keyword to true. +To dump the entire module that encapsulates the function (with declarations), set the `dump_module` keyword to true. +Keyword argument `debuginfo` may be one of source (default) or none, to specify the verbosity of code comments. """ -code_llvm(io::IO, @nospecialize(f), @nospecialize(types=Tuple), strip_ir_metadata=true, dump_module=false, optimize=true) = - print(io, _dump_function(f, types, false, false, strip_ir_metadata, dump_module, :att, optimize)) -code_llvm(@nospecialize(f), @nospecialize(types=Tuple); raw=false, dump_module=false, optimize=true) = - code_llvm(stdout, f, types, !raw, dump_module, optimize) +code_llvm(io::IO, @nospecialize(f), @nospecialize(types), raw::Bool, dump_module::Bool=false, optimize::Bool=true, debuginfo::Symbol=:default) = + print(io, _dump_function(f, types, false, false, !raw, dump_module, :att, optimize, debuginfo)) +code_llvm(io::IO, @nospecialize(f), @nospecialize(types=Tuple); raw::Bool=false, dump_module::Bool=false, optimize::Bool=true, debuginfo::Symbol=:default) = + code_llvm(io, f, types, raw, dump_module, optimize, debuginfo) +code_llvm(@nospecialize(f), @nospecialize(types=Tuple); raw=false, dump_module=false, optimize=true, debuginfo::Symbol=:default) = + code_llvm(stdout, f, types; raw=raw, dump_module=dump_module, optimize=optimize, debuginfo=debuginfo) + """ - code_native([io=stdout,], f, types; syntax = :att) + code_native([io=stdout,], f, types; syntax=:att, debuginfo=:default) Prints the native assembly instructions generated for running the method matching the given generic function and type signature to `io`. Switch assembly syntax using `syntax` symbol parameter set to `:att` for AT&T syntax or `:intel` for Intel syntax. +Keyword argument `debuginfo` may be one of source (default) or none, to specify the verbosity of code comments. """ -code_native(io::IO, @nospecialize(f), @nospecialize(types=Tuple); syntax::Symbol = :att) = - print(io, _dump_function(f, types, true, false, false, false, syntax)) -code_native(@nospecialize(f), @nospecialize(types=Tuple); syntax::Symbol = :att) = code_native(stdout, f, types, syntax = syntax) +code_native(io::IO, @nospecialize(f), @nospecialize(types=Tuple); syntax::Symbol=:att, debuginfo::Symbol=:default) = + print(io, _dump_function(f, types, true, false, false, false, syntax, true, debuginfo)) +code_native(@nospecialize(f), @nospecialize(types=Tuple); syntax::Symbol=:att, debuginfo::Symbol=:default) = + code_native(stdout, f, types; syntax=syntax, debuginfo=debuginfo) code_native(::IO, ::Any, ::Symbol) = error("illegal code_native call") # resolve ambiguous call diff --git a/stdlib/InteractiveUtils/src/macros.jl b/stdlib/InteractiveUtils/src/macros.jl index 6415bb68c64af..c1d0b88918977 100644 --- a/stdlib/InteractiveUtils/src/macros.jl +++ b/stdlib/InteractiveUtils/src/macros.jl @@ -116,20 +116,25 @@ function gen_call_with_extracted_types_and_kwargs(__module__, fcn, ex0) return thecall end -for fname in [:which, :less, :edit, :functionloc, :code_warntype, :code_native] +for fname in [:which, :less, :edit, :functionloc] @eval begin macro ($fname)(ex0) gen_call_with_extracted_types(__module__, $(Expr(:quote, fname)), ex0) end end end + macro which(ex0::Symbol) ex0 = QuoteNode(ex0) return :(which($__module__, $ex0)) end -macro code_llvm(ex0...) - gen_call_with_extracted_types_and_kwargs(__module__, :code_llvm, ex0) +for fname in [:code_warntype, :code_llvm, :code_native] + @eval begin + macro ($fname)(ex0...) + gen_call_with_extracted_types_and_kwargs(__module__, $(Expr(:quote, fname)), ex0) + end + end end macro code_typed(ex0...) @@ -140,8 +145,8 @@ macro code_typed(ex0...) end end -macro code_lowered(ex0) - thecall = gen_call_with_extracted_types(__module__, :code_lowered, ex0) +macro code_lowered(ex0...) + thecall = gen_call_with_extracted_types_and_kwargs(__module__, :code_lowered, ex0) quote results = $thecall length(results) == 1 ? results[1] : results @@ -216,15 +221,16 @@ Evaluates the arguments to the function or macro call, determines their types, a Evaluates the arguments to the function or macro call, determines their types, and calls [`code_llvm`](@ref) on the resulting expression. -Set the optional keyword arguments `raw`, `dump_module` and `optimize` by putting them and -their value before the function call, like this: +Set the optional keyword arguments `raw`, `dump_module`, `debuginfo`, `optimize` +by putting them and their value before the function call, like this: - @code_llvm raw=true dump_module=true f(x) + @code_llvm raw=true dump_module=true debuginfo=:default f(x) @code_llvm optimize=false f(x) `optimize` controls whether additional optimizations, such as inlining, are also applied. `raw` makes all metadata and dbg.* calls visible. -`dump_module` prints the entire module that encapsulates the function, with debug info and metadata. +`debuginfo` may be one of full, source (default), none, to specify the verbosity of code comments. +`dump_module` prints the entire module that encapsulates the function. """ :@code_llvm @@ -233,5 +239,11 @@ their value before the function call, like this: Evaluates the arguments to the function or macro call, determines their types, and calls [`code_native`](@ref) on the resulting expression. + +Set the optional keyword argument `debuginfo` by putting it before the function call, like this: + + @code_native debuginfo=:default f(x) + +`debuginfo` may be one of source (default) or none, to specify the verbosity of code comments. """ :@code_native diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 8ecc77dbaf44b..1a35517acbac8 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -1280,10 +1280,10 @@ Int64 julia> @code_warntype f(1, 2, 3) Body::UNION{FLOAT64, INT64} -1 1 ─ %1 = (Base.slt_int)(1, b)::Bool - └── goto #3 if not %1 - 2 ─ return 1 - 3 ─ return 1.0 +1 ─ %1 = (Base.slt_int)(1, b)::Bool +└── goto #3 if not %1 +2 ─ return 1 +3 ─ return 1.0 julia> @inferred f(1, 2, 3) ERROR: return type Int64 does not match inferred return type Union{Float64, Int64} diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index b191ee2f6ad10..473a0452adfeb 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -213,7 +213,7 @@ let exename = `$(Base.julia_cmd()) --sysimage-native-code=yes --startup-file=no` # -g @test readchomp(`$exename -E "Base.JLOptions().debug_level" -g`) == "2" - let code = writereadpipeline("code_llvm(stdout, +, (Int64, Int64), false, true)", `$exename -g0`) + let code = writereadpipeline("code_llvm(stdout, +, (Int64, Int64), raw=true, dump_module=true)", `$exename -g0`) @test code[2] code = code[1] @test occursin("llvm.module.flags", code) @@ -221,7 +221,7 @@ let exename = `$(Base.julia_cmd()) --sysimage-native-code=yes --startup-file=no` @test !occursin("int.jl", code) @test !occursin("Int64", code) end - let code = writereadpipeline("code_llvm(stdout, +, (Int64, Int64), false, true)", `$exename -g1`) + let code = writereadpipeline("code_llvm(stdout, +, (Int64, Int64), raw=true, dump_module=true)", `$exename -g1`) @test code[2] code = code[1] @test occursin("llvm.module.flags", code) @@ -229,7 +229,7 @@ let exename = `$(Base.julia_cmd()) --sysimage-native-code=yes --startup-file=no` @test occursin("int.jl", code) @test !occursin("Int64", code) end - let code = writereadpipeline("code_llvm(stdout, +, (Int64, Int64), false, true)", `$exename -g2`) + let code = writereadpipeline("code_llvm(stdout, +, (Int64, Int64), raw=true, dump_module=true)", `$exename -g2`) @test code[2] code = code[1] @test occursin("llvm.module.flags", code) diff --git a/test/compiler/validation.jl b/test/compiler/validation.jl index 3648885226d80..94731d376ee58 100644 --- a/test/compiler/validation.jl +++ b/test/compiler/validation.jl @@ -28,7 +28,7 @@ c0 = Core.Compiler.retrieve_code_info(mi) @test isempty(Core.Compiler.validate_code(c0)) @testset "INVALID_EXPR_HEAD" begin - c = Core.Compiler.copy_code_info(c0) + c = copy(c0) c.code[1] = Expr(:invalid, 1) errors = Core.Compiler.validate_code(c) @test length(errors) == 1 @@ -36,7 +36,7 @@ c0 = Core.Compiler.retrieve_code_info(mi) end @testset "INVALID_LVALUE" begin - c = Core.Compiler.copy_code_info(c0) + c = copy(c0) c.code[1] = Expr(:(=), GotoNode(1), 1) c.code[2] = Expr(:(=), :x, 1) c.code[3] = Expr(:(=), 3, 1) @@ -46,7 +46,7 @@ end end @testset "INVALID_RVALUE" begin - c = Core.Compiler.copy_code_info(c0) + c = copy(c0) c.code[1] = Expr(:(=), SlotNumber(2), GotoNode(1)) c.code[2] = Expr(:(=), SlotNumber(2), LineNumberNode(2)) i = 2 @@ -59,7 +59,7 @@ end end @testset "INVALID_CALL_ARG" begin - c = Core.Compiler.copy_code_info(c0) + c = copy(c0) c.code[1] = Expr(:(=), SlotNumber(2), Expr(:call, GlobalRef(Base,:+), SlotNumber(2), GotoNode(1))) c.code[2] = Expr(:call, GlobalRef(Base,:-), Expr(:call, GlobalRef(Base,:sin), GotoNode(2)), 3) c.code[3] = Expr(:call, LineNumberNode(2)) @@ -73,7 +73,7 @@ end end @testset "EMPTY_SLOTNAMES" begin - c = Core.Compiler.copy_code_info(c0) + c = copy(c0) empty!(c.slotnames) errors = Core.Compiler.validate_code(c) @test length(errors) == 2 @@ -82,7 +82,7 @@ end end @testset "SLOTFLAGS_MISMATCH" begin - c = Core.Compiler.copy_code_info(c0) + c = copy(c0) push!(c.slotnames, :dummy) errors = Core.Compiler.validate_code(c) @test length(errors) == 1 @@ -98,7 +98,7 @@ end end @testset "SSAVALUETYPES_MISMATCH_UNINFERRED" begin - c = Core.Compiler.copy_code_info(c0) + c = copy(c0) c.ssavaluetypes -= 1 errors = Core.Compiler.validate_code(c) @test length(errors) == 1 @@ -115,7 +115,7 @@ end end @testset "NON_TOP_LEVEL_METHOD" begin - c = Core.Compiler.copy_code_info(c0) + c = copy(c0) c.code[1] = Expr(:method, :dummy) errors = Core.Compiler.validate_code(c) @test length(errors) == 1 diff --git a/test/show.jl b/test/show.jl index 341cc0792136a..a480812f4618e 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1343,9 +1343,12 @@ eval(Meta.parse("""function my_fun28173(x) end""")) # use parse to control the line numbers let src = code_typed(my_fun28173, (Int,))[1][1] ir = Core.Compiler.inflate_ir(src) - source_slotnames = String["my_fun28173", "x"] - irshow = sprint(show, ir, context = :SOURCE_SLOTNAMES=>source_slotnames) - @test repr(src) == "CodeInfo(\n" * irshow * ")" + fill!(src.codelocs, 0) # IRCode printing is only capable of printing partial line info + 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 + @test repr(src) == repr_ir + end lines1 = split(repr(ir), '\n') @test isempty(pop!(lines1)) Core.Compiler.insert_node!(ir, 1, Val{1}, QuoteNode(1), false)