Setup.jl/src/about.jl

257 lines
9.5 KiB
Julia

using InteractiveUtils
const colorcycle = [:light_blue, :light_green, :light_yellow, :light_magenta]
function humansize(bytes::Integer)
units = ("B", "kB", "MB", "GB")
magnitude = floor(Int, log(1024, 1 + bytes))
if 10 < bytes < 10*1024^magnitude
round(bytes / 1024^magnitude, digits=1)
else
round(Int, bytes / 1024^magnitude)
end, units[1+magnitude]
end
function about(obj::Any)
print(Base.summary(obj))
size, units = humansize(Base.summarysize(obj))
if ismutable(obj)
print(" (mutable)")
end
print(", ", size, units, "\n\n")
printstyled("Fields:\n", bold=true)
memorylayout(obj)
end
function about(type::Type)
if isprimitivetype(type)
print("Primitive ")
elseif isconcretetype(type)
print("Concrete ")
if Base.datatype_haspadding(type)
printstyled("(padded) ", color=:light_black)
end
elseif isabstracttype(type)
print("Abstract ")
end
if Base.issingletontype(type)
print("singleton ")
end
print(Base.summary(type))
print(" defined in ")
printstyled(type.name.module, color=:light_red)
size, units = humansize(Base.summarysize(type))
selfsize, selfunits = try
st = humansize(sizeof(type))
print(", ", st...)
st
catch _; (0, "") end
if (size, units) != (selfsize, selfunits)
print(" referencing ", size, units)
end
println("\n ", join(string.(supertypes(type)), " \e[31m<:\e[m "))
if isstructtype(type) && fieldcount(type) > 0
println("\nStruct with \e[1m", fieldcount(type), "\e[m fields:")
if type isa DataType
for (i, (fname, ftype)) in enumerate(zip(fieldnames(type), fieldtypes(type)))
color = colorcycle[i % length(colorcycle) + 1]
print("")
printstyled(fname; color)
printstyled("::", string(ftype), color=:light_black)
print('\n')
end
print('\n')
memorylayout(type)
else
println("",
join(string.(fieldnames(type), "\e[90m::",
fieldtypes(type), "\e[m"),
"\n"))
end
end
end
function structinfo(T::DataType)
map(1:fieldcount(T)) do i
size = Int(if i < fieldcount(T) fieldoffset(T, i+1)
else sizeof(T) end - fieldoffset(T, i))
contentsize=try sizeof(fieldtype(T, i)) catch _ 0 end
(; i, offset=fieldoffset(T, i) |> Int,
size, contentsize,
pointer=contentsize == 0,
name=fieldname(T, i),
type=fieldtype(T, i))
end
end
function memorylayout(type::DataType)
cpad(s, n::Integer, p=" ") = rpad(lpad(s,div(n+textwidth(s),2),p),n,p)
si = structinfo(type)
!isempty(si) || return
memstep = memstep = gcd((getfield.(si, :size), getfield.(si, :contentsize)) |>
Iterators.flatten |> collect)
memscale = max(1, floor(Int, 70/(sizeof(type)/memstep)))
print(' ')
for (; i, size, contentsize, pointer) in si
color = colorcycle[i % length(colorcycle) + 1]
contentwidth = memscale * contentsize÷memstep
printstyled('■'^contentwidth; color)
if contentsize < size
paddwidth = memscale * (size - contentsize)÷memstep
printstyled('■'^paddwidth;
color=if pointer; :cyan else :light_black end)
end
end
print("\n ")
for (; i, size, contentsize, pointer) in si
width = memscale * size÷memstep
color = colorcycle[i % length(colorcycle) + 1]
fsize, funits = humansize(size)
if pointer
printstyled(cpad("*", width); color)
elseif contentsize < size
csize, cunits = humansize(contentsize)
psize, punits = humansize(size - contentsize)
csizestr, psizestr = string(csize, cunits), string(psize, punits)
cwidth = div(width + textwidth(csizestr) - textwidth(psizestr) - 1, 2)
pwidth = width - cwidth
printstyled(lpad(csizestr, cwidth); color)
printstyled(rpad('+' * psizestr, pwidth); color=:light_black)
else
printstyled(cpad(string(fsize, funits), width); color)
end
end
if any(getfield.(si, :pointer))
printstyled("\n\n * = Pointer (8B)", color=:cyan)
end
print('\n')
end
function memorylayout(val::T) where {T}
si = structinfo(T)
!isempty(si) || return memorylayout(T)
rows = Vector{NamedTuple}()
for (; name, size, contentsize, pointer, type) in si
fsize, funits = humansize(size)
fbits = if isprimitivetype(type)
bitstring(getfield(val, name)) * '-'^(8*(size-contentsize))
elseif pointer
"Pointer"
else
""
end
fshow = sprint(show, getfield(val, name), context=:compact => true)
push!(rows, (; fname=string(name), fsize=string(fsize), funits,
fbits, fshow, ftype=string(type)))
end
widths = Dict(c => maximum(length.(getfield.(rows, c)))
for c in fieldnames(typeof(first(rows)))) |>
NamedTuple
for (i, (; fname, fsize, funits, fbits, fshow, ftype)) in enumerate(rows)
color = colorcycle[i % length(colorcycle) + 1]
printstyled(' ', lpad(fname, widths.fname), "::",
rpad(ftype, widths.ftype); color)
printstyled(" ", lpad(fsize, widths.fsize),
rpad(funits, widths.funits), ' '; color)
print(replace(rpad(fbits, widths.fbits), r"0|\-" => s"\e[90m\0\e[m"), ' ')
printstyled(rpad(fshow, widths.fshow); color)
print('\n')
end
print('\n')
memorylayout(T)
end
function about(fn::Function)
source = Main.InteractiveUtils.which(parentmodule(fn), Symbol(fn))
methodmodules = getproperty.(methods(fn).ms, :module)
others = setdiff(methodmodules, [source])
print(Base.summary(fn))
print("\n\nDefined in ")
printstyled(source, color=:light_red)
if length(others) > 0
printstyled("(", sum(Ref(source) .=== methodmodules), ")",
color=:light_black)
print(" extended in ")
for (i, oth) in enumerate(others)
printstyled(oth, color=:light_red)
printstyled("(", sum(Ref(oth) .=== methodmodules), ")",
color=:light_black)
if length(others) == 2 && i == 1
print(" and ")
elseif length(others) > 2 && i < length(others)-1
print(", ")
elseif length(others) > 2 && i == length(others)-1
print(", and ")
end
end
end
print(".\n")
end
function about(fn::Union{Function, Type}, typesig::Type{<:Tuple})
about(Base.unwrap_unionall(fn))
print("\n")
matchedmethods = methods(fn, typesig)
printstyled("Matching methods ($(length(matchedmethods.ms))):\n", bold=true)
for method in matchedmethods.ms
println(" ", sprint(show, method, context=IOContext(stdout)))
end
print('\n')
printstyled("Effects:\n", bold=true)
effects = Base.infer_effects(fn, typesig)
trichar(t::UInt8) =
Dict(Core.Compiler.ALWAYS_TRUE => '✔',
# Core.Compiler.TRISTATE_UNKNOWN => '?',
Core.Compiler.ALWAYS_FALSE => '✗')[t]
trichar(b::Bool) = ifelse(b, '✔', '✗')
tricolor(t::UInt8) =
Dict(Core.Compiler.ALWAYS_TRUE => :green,
# Core.Compiler.TRISTATE_UNKNOWN => :yellow,
Core.Compiler.ALWAYS_FALSE => :red)[t]
tricolor(b::Bool) = ifelse(b, :green, :red)
hedge(t::UInt8) =
Dict(Core.Compiler.ALWAYS_TRUE => "guaranteed to",
# Core.Compiler.TRISTATE_UNKNOWN => "might",
Core.Compiler.ALWAYS_FALSE => "not guaranteed to")[t]
hedge(b::Bool) = ifelse(b, "guaranteed to", "not guaranteed to")
for (effect, description) in
[(:consistent, "return or terminate consistently"),
(:effect_free, "be free from externally semantically visible side effects"),
(:nothrow, "never throw an exception"),
(:terminates, "terminate")]
e = getfield(effects, effect)
print(" "); printstyled(trichar(e), ' ', string(effect), color=tricolor(e))
print(" "); printstyled(hedge(e), ' ', description, color=:light_black);
print('\n')
end
if !effects.nonoverlayed
print(" "); printstyled(trichar(Core.Compiler.ALWAYS_FALSE), " nonoverlayed",
color=tricolor(Core.Compiler.ALWAYS_FALSE))
print(" "); printstyled("methods within this method are not defined in an overlayed method table",
color=:light_black);
print('\n')
end
end
macro about(obj::Symbol, types::Union{Symbol, Expr}...)
if Core.eval(Main, obj) isa Function && length(types) == 1 &&
types[1] == :(())
:(about($obj, Tuple{}))
elseif Core.eval(Main, obj) isa Function && length(types) > 0
:(about($obj, $(Tuple{Core.eval.(Ref(Main), types)...})))
else
:(about($obj))
end |> esc
end
macro about(qobj::Union{QuoteNode, Expr}, types::Union{Symbol, Expr}...)
if Core.eval(Main, Core.eval(Main, qobj)) isa Function && length(types) == 1 &&
types[1] == :(())
:(about($(Core.eval(Main, qobj)), Tuple{}))
elseif length(types) > 0
:(about($(Core.eval(Main, qobj)), $(Tuple{Core.eval.(Ref(Main), types)...})))
else
:(about($(Core.eval(Main, qobj))))
end |> esc
end