/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
JuliaLowering/src/runtime.jl
427 строк
15 KB
Em Chu
[JuliaLowering] Make `SyntaxTree` a standard tree (#62474)
29 июл 2026, 15:34
Не верифицирован
29 июл 2026, 15:34
92cca2d
Код
Авторство
О чём код?
# Runtime support for # 1. Functions called by the code emitted from lowering # 2. Introspecting Julia's state during lowering # # These should probably all move to `Core` at some point. #------------------------------------------------------------------------------- # Functions/types used by code emitted from lowering, but not called by it directly @inline function _invoke_in_world(w::UInt, f::F, @nospecialize(args...)) where {F} if ccall(:jl_is_in_pure_context, Int8, ()) != 0 # Similar to `Base.invoke_in_world` but also works inside generated-function # expansion (see `jl_code_for_staged`) return Core._call_in_world_total(w, f, args...) end return Base.invoke_in_world(w, f, args...) end # Re-dispatch `f(args...)` at the pinned lowering world (see `jl_lowering_world`) @inline function invoke_in_lowering_world(f::F, @nospecialize(args...)) where {F} @static if VERSION >= v"1.14.0-DEV.2635" w = unsafe_load(cglobal(:jl_lowering_world, Csize_t)) if w == 0 # Fallback when the Base lowering hook is not set up w = Base.tls_world_age() # FIXME: as a side effect, enabling the Base lowering hook now affects # JuliaLowering execution not passing through the hook end return _invoke_in_world(w, f, args...) else f(args...) end end # Return the current exception. In JuliaLowering we use this rather than the # special form `K"the_exception"` to reduce the number of special forms. Base.@assume_effects :removable function current_exception() @ccall jl_current_exception(current_task()::Any)::Any end function __interpolate_expr(@nospecialize(ex), depth, @nospecialize(vals::Tuple), val_i) if ex isa QuoteNode out = __interpolate_expr(Expr(:inert, ex.value), depth, vals, val_i) QuoteNode(only(out.args)) elseif !(ex isa Expr) ex else inner_depth = ex.head == :quote ? depth + 1 : ex.head == :$ ? depth - 1 : depth cs_out = Any[] for e in ex.args if e isa Expr && e.head == :$ && inner_depth == 0 tup = vals[val_i[] += 1]::Tuple for v in tup push!(cs_out, v) end else push!(cs_out, __interpolate_expr(e, inner_depth, vals, val_i)) end end Expr(ex.head, cs_out...) end end function _interpolate_expr(@nospecialize(ex), @nospecialize(values::Tuple)) @jl_assert !Meta.isexpr(ex, :$) (expr_to_est(ex), "expand_quote should handle this") __interpolate_expr(ex, 0, values, Ref(0)) end function interpolate_expr(@nospecialize(ex), @nospecialize(values...)) return invoke_in_lowering_world(_interpolate_expr, ex, values) end function __interpolate_syntax(st::SyntaxTree, depth, @nospecialize(vals), val_i) is_leaf(st) && return st k = kind(st) inner_depth = k == K"syntaxquote" ? depth + 1 : k == K"syntaxunquote" ? depth - 1 : depth cs_out = SyntaxList() for c in children(st) if kind(c) == K"syntaxunquote" && inner_depth == 0 tup = vals[val_i[] += 1]::Tuple @jl_assert numchildren(c) == 1 st @jl_assert kind(c[1]) === K"..." || length(tup) == 1 st for v in tup v2 = !(v isa SyntaxTree) ? expr_to_est(v, c) : v push!(cs_out, v2) end else push!(cs_out, __interpolate_syntax(c, inner_depth, vals, val_i)) end end @mknode(st; children=cs_out) end function _interpolate_syntax(st::SyntaxTree, @nospecialize(vals::Tuple)) # TODO: copy probably not required if immutable st = mktree(st) val_i = Ref(0) out = __interpolate_syntax((@ast _ st [K"None" st]), 0, vals, val_i) @jl_assert val_i[] == length(vals) st @jl_assert numchildren(out) == 1 st out[1] end function interpolate_syntax(st::SyntaxTree, @nospecialize(vals...)) return invoke_in_lowering_world(_interpolate_syntax, st, vals) end #-------------------------------------------------- # Functions called by closure conversion function eval_closure_type(mod::Module, closure_type_name::Symbol, capt_sp, field_names, field_is_box) type_params = Core.TypeVar[] field_types = [] for name in capt_sp push!(type_params, Core.TypeVar(name)) end for (name, isbox) in zip(field_names, field_is_box) if !isbox T = Core.TypeVar(Symbol(name, "_type")) push!(type_params, T) push!(field_types, T) else push!(field_types, Core.Box) end end type = Core._structtype(mod, closure_type_name, Core.svec(type_params...), Core.svec(field_names...), Core.svec(), false, length(field_names)) Core._setsuper!(type, Core.Function) Core.declare_const(mod, closure_type_name, type) Core._typebody!(type, Core.svec(field_types...)) type end # Interpolate captured local variables into the CodeInfo for a global method function replace_captured_locals(ci_in::Core.CodeInfo, locals::Core.SimpleVector) ci = copy(ci_in) for (i, ex) in enumerate(ci.code) ci.code[i] = _replace_captured_locals(ex, locals) end ci end function _replace_captured_locals(@nospecialize(e), locals) if e isa Expr if e.head === :captured_local v = locals[e.args[1]::Int] isa_lowering_ast_node(v) ? QuoteNode(v) : v else # could possibly limit to foreigncall Expr(e.head, map(a->_replace_captured_locals(a, locals), e.args)...) end elseif e isa QuoteNode QuoteNode(_replace_captured_locals(e.value, locals)) else e end end #-------------------------------------------------- # Functions which create modules or mutate their bindings const _Base_has_eval_import = isdefined(Base, :_eval_import) function eval_import(imported::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...) if _Base_has_eval_import Base._eval_import(imported, to, from, paths...) else head = imported ? :import : :using ex = isnothing(from) ? Expr(head, paths...) : Expr(head, Expr(Symbol(":"), from, paths...)) Core.eval(to, ex) end end function eval_using(to::Module, path::Expr) if _Base_has_eval_import Base._eval_using(to, path) else Core.eval(to, Expr(:using, path)) end end function eval_public(mod::Module, is_exported::Bool, identifiers) # symbol jl_module_public is no longer exported as of #57765 Core.eval(mod, Expr((is_exported ? :export : :public), map(Symbol, identifiers)...)) end #-------------------------------------------------- # Docsystem integration function _bind_func_docs!(f, docstr, method_metadata::Core.SimpleVector) mod = parentmodule(f) bind = Base.Docs.Binding(mod, nameof(f)) full_sig = method_metadata[1] arg_sig = Tuple{full_sig[2:end]...} lineno = method_metadata[3] metadata = Dict{Symbol, Any}( :linenumber => lineno.line, :module => mod, ) if !isnothing(lineno.file) push!(metadata, :path => string(lineno.file)) end Docs.doc!(mod, bind, Base.Docs.docstr(docstr, metadata), arg_sig) end function bind_docs!(f::Function, docstr, method_metadata::Core.SimpleVector) _bind_func_docs!(f, docstr, method_metadata) end # Document constructors function bind_docs!(::Type{Type{T}}, docstr, method_metadata::Core.SimpleVector) where T _bind_func_docs!(T, docstr, method_metadata) end function bind_docs!(type::Type, docstr, method_metadata::Core.SimpleVector) _bind_func_docs!(type, docstr, method_metadata) end function bind_docs!(type::Type, docstr, lineno::LineNumberNode; field_docs=Core.svec()) mod = parentmodule(type) bind = Base.Docs.Binding(mod, nameof(type)) metadata = Dict{Symbol, Any}( :linenumber => lineno, :module => mod, ) if !isnothing(lineno.file) push!(metadata, :path => string(lineno.file)) end if !isempty(field_docs) fd = Dict{Symbol, Any}() fns = fieldnames(type) for i = 1:2:length(field_docs) fd[fns[field_docs[i]]] = field_docs[i+1] end metadata[:fields] = fd end Docs.doc!(mod, bind, Base.Docs.docstr(docstr, metadata), Union{}) end """ Called in the unfortunate cases (K"call", K".", K"Identifier") where docstrings change the semantics of the expressions they annotate, no longer requiring the expression to execute. """ function bind_static_docs!(mod::Module, name::Symbol, docstr, lnn::LineNumberNode, sigtypes::Type) metadata = Dict{Symbol, Any}( :linenumber => lnn.line, :module => mod, :path => something(lnn.file, "none"), ) bind = Base.Docs.Binding(mod, name) Docs.doc!(mod, bind, Base.Docs.docstr(docstr, metadata), sigtypes) end #-------------------------------------------------- # Runtime support infrastructure for `@generated` # An alternative to Core.GeneratedFunctionStub which works on SyntaxTree rather # than Expr. struct GeneratedFunctionStub syntax_context::SyntaxContext gen::Function srcref::Union{LineNumberNode,SourceRef} argnames::Core.SimpleVector spnames::Core.SimpleVector end function _gen_args_from_syms(ctx, src, args, sc) out = SyntaxList() for a in args id = newleaf(src, K"Identifier", string(a)) id = _est_to_dst_ident(id) # support placeholders id = @mknode(id; context=sc) push!(out, id) end out end # Call the `@generated` code generator function and wrap the results of the # expression into a CodeInfo. # # `args` passed into stub by the Julia runtime are (parent_func, static_params..., arg_types...) function (g::GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize args...) # Some of the lowering pipeline from lower() and the pass-specific setup is # re-implemented here because generated functions are very much (but not # entirely) like macro expansion. # # TODO: Reduce duplication where possible. __module__ = source.module sc = g.syntax_context # Run code generator - this acts like a macro expander ex0 = g.gen(sc, args...) # Note that we expand in `tls_world_age()` (see Core.GeneratedFunctionStub) world = Base.tls_world_age() # Lower the generated code in the lowering world return invoke_in_lowering_world(_lower_generated_code, g, source, sc, __module__, world, ex0) end function _lower_generated_code(g::GeneratedFunctionStub, source::Method, sc::SyntaxContext, __module__::Module, world::UInt, @nospecialize(ex0)) if ex0 isa Expr ex0 = expr_to_est(ex0, source_location(LineNumberNode, g.srcref)) end # TODO: rebase mistake above? if !(ex0 isa SyntaxTree) ex0 isa Expr && throw(LoweringError( ex0, "implicit expr->syntaxtree: may later be allowed, but is probably a mistake today")) ex0 = expr_to_est(ex0, g.srcref) end @jl_assert base_layer(sc).mod == __module__ ex0 ex0 = JuliaSyntax.fill_context(ex0, sc) ctx1 = MacroExpansionContext(ex0, world, true) ex1 = expand_forms_1(ctx1, ex0) # Desugaring ctx2, ex2 = expand_forms_2(ex1, world) # Wrap expansion in a non-toplevel lambda and run scope resolution ex2 = @ast ctx2 ex0 [K"generated_lambda" [K"block" _gen_args_from_syms(ctx2, ex1, g.argnames, sc)...] [K"block" _gen_args_from_syms(ctx2, ex1, g.spnames, sc)...] ex2 ] ctx3, ex3 = resolve_scopes(ctx2, ex2) # Rest of lowering ctx4, ex4 = convert_closures(ctx3, ex3) _ctx5, ex5 = linearize_ir(ctx4, ex4) ci = to_lowered_expr(ex5) @assert ci isa Core.CodeInfo # See GeneratedFunctionStub code in base/expr.jl ci.isva = source.isva code = ci.code bindings = IdSet{Core.Binding}() for i = 1:length(code) stmt = code[i] if isa(stmt, GlobalRef) push!(bindings, convert(Core.Binding, stmt)) end end if !isempty(bindings) ci.edges = Core.svec(bindings...) end return ci end #------------------------------------------------------------------------------- # The following functions are called directly by lowering to inspect Julia's state. # Get the binding for `name` if one is already resolved in module `mod`. Note # that we cannot use `isdefined(::Module, ::Symbol)` here, because that causes # binding resolution which is a massive side effect we must avoid in lowering. function _get_module_binding(mod, name; create=false) b = @ccall jl_get_module_binding(mod::Module, name::Symbol, create::Cint)::Ptr{Core.Binding} b == C_NULL ? nothing : unsafe_pointer_to_objref(b) end # Return true if a `name` is defined in and *by* the module `mod`. # Has no side effects, unlike isdefined() # # (This should do what fl_defined_julia_global does for flisp lowering) function is_defined_and_owned_global(mod, name, world::UInt=Base.get_world_counter()) return _invoke_in_world(world, Base.binding_kind, mod, name) === Base.PARTITION_KIND_GLOBAL end # "Reserve" a binding: create the binding if it doesn't exist but do not assign # to it. function reserve_module_binding(mod, name) # TODO: Fix the race condition here: We should really hold the Module's # binding lock during this test-and-set type operation. But the binding # lock is only accessible from C. See also the C code in # `fl_module_unique_name`. if _get_module_binding(mod, name; create=false) === nothing return _get_module_binding(mod, name; create=true) !== nothing else return false end end # Reserve a global binding named "$basename#$i" in module `mod` for the # smallest `i` starting at `0`. # # TODO: Remove the use of this where possible. Currently this is used within # lowering to create unique global names for keyword function bodies and # closure types as a more local alternative to current-julia-module-counter. # However, we should ideally defer it to eval-time to make lowering itself # completely non-mutating. function reserve_module_binding_i(mod, basename) i = 0 while true name = "$basename$i" if reserve_module_binding(mod, Symbol(name)) return name end i += 1 end end function lookup_method_instance(func, args, world::Integer) allargs = Vector{Any}(undef, length(args) + 1) allargs[1] = func allargs[2:end] = args mi = @ccall jl_method_lookup(allargs::Ptr{Any}, length(allargs)::Csize_t, world::Csize_t)::Ptr{Cvoid} return mi == C_NULL ? nothing : unsafe_pointer_to_objref(mi) end # Like `Base.methods()` but with world age support function methods_in_world(func, arg_sig, world, err_ex) out = Base._methods(func, arg_sig, -1, world) @jl_assert(out isa Vector{Any}, (err_ex, string( "Base._methods returned non-vector;", " bad world age provided? (", world, ")"))) out::Vector{Any} end