/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
JuliaLowering/src/utils.jl
397 строк
14 KB
Em Chu
[JuliaLowering] Make `SyntaxTree` a standard tree (#62474)
29 июл 2026, 15:34
Не верифицирован
29 июл 2026, 15:34
92cca2d
Код
Авторство
О чём код?
attrsummary(name, _value) = string(name) attrsummary(name, value::Number) = "$name=$value" attrsummary(name, value::LineNumberNode) = "$name=L$(value.line)" attrsummary(name, value::Module) = "$name=$value" function _value_string(ex) k = kind(ex) str = k == K"Identifier" ? syntax_name(ex) : k == K"Placeholder" ? syntax_name(ex) : k == K"SSAValue" ? "%" : k == K"BindingId" ? "#" : k == K"label" ? "label" : k == K"nothing" ? "core.nothing" : k == K"core" ? "core.$(syntax_name(ex))" : k == K"top" ? "top.$(syntax_name(ex))" : k == K"Symbol" ? ":$(syntax_name(ex))" : k == K"globalref" ? "$(ex.mod).$(syntax_name(ex))" : k == K"slot" ? "slot" : k == K"Slots" ? "Slots" : k == K"LambdaBindings" ? "LambdaBindings" : k == K"latestworld" ? "latestworld" : k == K"static_parameter" ? "static_parameter" : k == K"symboliclabel" ? "label:$(syntax_name(ex))" : k == K"symbolicgoto" ? "goto:$(syntax_name(ex))" : k == K"oldsymbolicgoto" ? "goto:$(syntax_name(ex))" : k == K"SourceLocation" ? "SourceLocation:$(JuliaSyntax.filename(ex)):$(join(source_location(ex), ':'))" : k == K"Value" ? (ex.value isa SourceRef ? "SourceRef:$(JuliaSyntax.filename(ex)):$(join(source_location(ex), ':'))" : ex.value isa SyntaxContext ? "SyntaxContext(#=omitted=#)" : repr(ex.value)) : ex.value !== nothing ? repr(ex.value) : "::K\"$(untokenize(k))\"" if kind(ex) in KSet"BindingId slot SSAValue static_parameter label" idstr = subscript_str(syntax_id(ex)) str = "$(str)$idstr" end if k == K"slot" || k == K"BindingId" for p in provenance(ex) if kind(p) == K"Identifier" str = "$(str)/$(syntax_name(p))" break end end end return str end # Within JL, K"Placeholder" is used for never-read identifiers, but this magic # symbol is used in the IR (its write-only properties are enforced in codegen). const UNUSED = "#unused#" function _show_syntax_tree(io, ex, indent, show_kinds, @nospecialize(parent_sc)) nodestr = kind(ex) === K"unknown_head" ? ("unknown_head:"*syntax_name(ex)) : !is_leaf(ex) ? "[$(untokenize(head(ex)))]" : _value_string(ex) treestr = rpad(string(indent, nodestr), 40) if show_kinds && is_leaf(ex) treestr = treestr*" :: "*string(kind(ex)) end std_attrs = Set([:value,:kind,:syntax_flags,:source,:context]) attrstr = join([attrsummary(n, getproperty(ex, n)) for n in fieldnames(typeof(ex)) if n ∉ std_attrs && getproperty(ex, n) !== nothing], ",") print(io, rpad(treestr, 60)) print(io, " | ") sc = ex.context if sc isa SyntaxContext && sc !== parent_sc print(io, sc) print(io, ",") end print(io, attrstr) println(io) if !is_leaf(ex) new_indent = indent*" " for n in children(ex) _show_syntax_tree(io, n, new_indent, show_kinds, sc) end end end function Base.show(io::IO, ::MIME"text/plain", ex::SyntaxTree, show_kinds=true) assert_syntaxtree(ex) _show_syntax_tree(io, ex, "", show_kinds, nothing) end function _show_syntax_tree_sexpr(io, ex) if is_leaf(ex) if JuliaSyntax.is_error(ex) print(io, "(", untokenize(head(ex)), ")") else print(io, _value_string(ex)) end else print(io, "(", untokenize(head(ex))) for n in children(ex) print(io, ' ') _show_syntax_tree_sexpr(io, n) end print(io, ')') end end function Base.show(io::IO, ::MIME"text/x.sexpression", node::SyntaxTree) assert_syntaxtree(node) _show_syntax_tree_sexpr(io, node) end function Base.show(io::IO, node::SyntaxTree) assert_syntaxtree(node) _show_syntax_tree_sexpr(io, node) end #------------------------------------------------------------------------------- # Error handling TODO(msg::AbstractString) = throw(ErrorException("Lowering TODO: $msg")) TODO(ex::SyntaxTree, msg="") = throw(LoweringError(ex, "Lowering TODO: $msg")) """ An error with detailed printing containing one or more SyntaxTrees and one message per tree. If `!internal`, caused by bad user code in `syntax` (flisp: `Expr(:error, msg)`). """ struct LoweringError <: Exception sts::SyntaxList msgs::Vector{String} internal::Bool end @noinline LoweringError(ex::SyntaxTree, msg::String) = LoweringError(SyntaxList(ex), String[msg], false) function Base.showerror(io::IO, exc::LoweringError; show_detail=true) println(io, exc.internal ? "internal lowering bug:" : "LoweringError:") for i in eachindex(exc.sts) st = exc.sts[i] msg = exc.msgs[i] src = sourceref(st) highlight(io, src; note=msg) if exc.internal || src isa LineNumberNode print(io, "\nExpression:\n ") show(io, MIME"text/x.sexpression"(), st) # TODO: no parents available here; need to place them in LoweringError parents = SyntaxList() isempty(parents) || print(io, "\nContaining expressions:") for p in parents print(io, "\n ") show(io, MIME"text/x.sexpression"(), p) end end i !== lastindex(exc.sts) && print(io, "\n\n") end if (show_detail || exc.internal) && !isempty(exc.sts) print(io, "\n\nDetailed provenance:\n ") _show_provtree(io, exc.sts[1], " ") end end function _show_provtree(io::IO, ex::SyntaxTree, indent) print(io, ex) if ex.source !== nothing printstyled(io, " @$(ex.jl_source)", color=:light_black) end prov = provenance(ex) print(io, "\n") src = ex.source msrc = JuliaSyntax.macro_prov(ex) printstyled(io, string( indent, msrc === nothing ? "└─ " : "├─ "); color=:light_black) if src isa SyntaxTree _show_provtree(io, src, string(indent, msrc === nothing ? " " : "│ ")) else @jl_assert ex.source isa Union{LineNumberNode, SourceRef} ex src = sourceref(ex) fn = filename(src) line, _ = source_location(src) printstyled(io, "@ $fn:$line\n", color=:light_black) end if msrc isa SyntaxTree printstyled(io, string(indent, "└─ "); color=:light_black) _show_provtree(io, msrc, indent*" ") end end function showprov(io::IO, exs::AbstractVector; note=nothing, include_location::Bool=true, highlight_kwargs...) for (i,ex) in enumerate(Iterators.reverse(exs)) sr = sourceref(ex) if i > 1 print(io, "\n\n") end k = kind(ex) ex_note = !isnothing(note) ? note : i > 1 && k == K"macrocall" ? "in macro expansion" : i > 1 && k == K"$" ? "interpolated here" : "in source" highlight(io, sr; note=ex_note, highlight_kwargs...) if include_location line, _ = source_location(sr) locstr = "$(filename(sr)):$line" JuliaSyntax._printstyled(io, "\n# @ $locstr", fgcolor=:light_black) end end end function showprov(io::IO, ex::SyntaxTree; showprov_kwargs...) showprov(io, flattened_provenance(ex); showprov_kwargs...) end function subscript_str(i) replace(string(i), "0"=>"₀", "1"=>"₁", "2"=>"₂", "3"=>"₃", "4"=>"₄", "5"=>"₅", "6"=>"₆", "7"=>"₇", "8"=>"₈", "9"=>"₉") end function _deref_ssa(stmts, ex) while kind(ex) == K"SSAValue" ex = stmts[syntax_id(ex)] end ex end function _is_define_method_call(e) kind(e) == K"call" && numchildren(e) >= 1 && kind(e[1]) == K"core" && syntax_name(e[1]) == "define_method" end function _find_method_lambda(ex0, name) ex = kind(ex0) === K"thunk" ? ex0[1] : ex0 @jl_assert kind(ex) == K"code_info" ex # Heuristic search through outer thunk for the method in question. stmts = children(ex[2]) for e in stmts if _is_define_method_call(e) && numchildren(e) == 5 # define_method(module, fname, sig, lam) sig = _deref_ssa(stmts, e[4]) @jl_assert kind(sig) == K"call" ex arg_types = _deref_ssa(stmts, sig[2]) @jl_assert kind(arg_types) == K"call" ex self_type = _deref_ssa(stmts, arg_types[2]) if kind(self_type) == K"globalref" && occursin(name, syntax_name(self_type)) return e[5] end end end end function print_ir(io::IO, ex, method_filter=nothing) @jl_assert kind(ex) == K"code_info" || kind(ex) == K"thunk" ex if !isnothing(method_filter) filtered = _find_method_lambda(ex, method_filter) if isnothing(filtered) @warn "Method not found with method filter $method_filter" else ex = filtered end end _print_ir(io, ex, "") end # TODO: JuliaLowering-the-module should always print the same way, ignoring parent modules function _print_ir(io::IO, ex0, indent) added_indent = " " (ex, is_toplevel_thunk) = kind(ex0) === K"thunk" ? (ex0[1],true) : (ex0,false) @jl_assert ((kind(ex) == K"lambda" || kind(ex) == K"code_info") && kind(ex[2]) == K"block") ex if !is_toplevel_thunk && kind(ex) == K"code_info" slots = ex[1].value print(io, indent, "slots: [") for (i,slot) in enumerate(slots) print(io, "slot$(subscript_str(i))/$(slot.name)") flags = String[] slot.is_nospecialize && push!(flags, "nospecialize") !slot.is_read && push!(flags, "!read") slot.is_single_assign && push!(flags, "single_assign") slot.is_maybe_undef && push!(flags, "maybe_undef") slot.is_called && push!(flags, "called") if !isempty(flags) print(io, "($(join(flags, ",")))") end if i < length(slots) print(io, " ") end end println(io, "]") end stmts = children(ex[2]) for (i, e) in enumerate(stmts) lno = rpad(i, 3) if _is_define_method_call(e) && numchildren(e) == 5 # define_method(module, fname, sig, lam) print(io, indent, lno, " (call core.define_method ", string(e[2]), " ", string(e[3]), " ", string(e[4])) if kind(e[5]) == K"lambda" || kind(e[5]) == K"code_info" println(io) print(io, indent, " --- code_info") println(io) _print_ir(io, e[5], indent*added_indent) else println(io, " ", string(e[5]), ")") end elseif kind(e) == K"opaque_closure_method" @jl_assert numchildren(e) == 5 e print(io, indent, lno, " --- opaque_closure_method ") for i=1:4 print(io, " ", e[i]) end println(io) _print_ir(io, e[5], indent*added_indent) elseif kind(e) == K"code_info" println(io, indent, lno, " --- ", "code_info") _print_ir(io, e, indent*added_indent) else code = string(e) println(io, indent, lno, " ", code) end end end # Wrap a function body in Base.Compiler.@zone for profiling if isdefined(Base.Compiler, Symbol("@zone")) && DEBUG macro fzone(str, f) @assert(f isa Expr && f.head === :function && length(f.args) === 2 && str isa String, "usage: @fzone name_string <function expression>") esc(Expr(:function, f.args[1], # Use source of our caller, not of this macro. Expr(:macrocall, :(Base.Compiler.var"@zone"), __source__, str, f.args[2]))) end else macro fzone(str, f) esc(f) end end function _flatten_blocks(st::SyntaxTree) if kind(st) === K"block" out = SyntaxList() for c in children(st) append!(out, _flatten_blocks(c)) end # special case: an empty final block has value nothing if (length(children(st)) > 0 && kind(st[end]) === K"block" && numchildren(st[end]) == 0) push!(out, @ast _ st[end] (::K"nothing")) end return out elseif is_quoted(st) SyntaxList(st) else SyntaxList(mapchildren(flatten_blocks, st)) end end # Splat the contents of any block in `st` whose parent is also a block function flatten_blocks(st::SyntaxTree) if kind(st) === K"block" @mknode(st; children=_flatten_blocks(st)) elseif is_quoted(st) st else mapchildren(flatten_blocks, st) end end # Hack. Used for assignment to variables with `decl`, since the type may change # between assignments. flisp: renumber-assigned-ssavalues function renumber_assigned_ssavalues(ctx, st) ssamap = Dict{IdTag, IdTag}() _find_assigned_ssavars!(ctx, ssamap, st) isempty(ssamap) && return st _replace_binding_ids(ctx, ssamap, st) end function _find_assigned_ssavars!(ctx, ssamap, st) (is_leaf(st) || is_quoted(st)) && return if kind(st) == K"=" && kind(st[1]) == K"BindingId" b = get_binding(ctx, st[1]) b.is_ssa || return ssamap[b.id] = syntax_id(ssavar(ctx, st[1], b.name)) end foreach(e->_find_assigned_ssavars!(ctx, ssamap, e), children(st)) end function _replace_binding_ids(ctx, ssamap, st) if kind(st) == K"BindingId" id = get(ssamap, syntax_id(st), nothing) isnothing(id) ? st : newleaf(st, K"BindingId", id) elseif is_leaf(st) || is_quoted(st) st else mapchildren(e->_replace_binding_ids(ctx, ssamap, e), st) end end