/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
JuliaLowering/src/eval.jl
847 строк
31 KB
Em Chu
[JuliaLowering] Make `SyntaxTree` a standard tree (#62474)
29 июл 2026, 15:34
Не верифицирован
29 июл 2026, 15:34
92cca2d
Код
Авторство
О чём код?
# Non-incremental lowering API for non-toplevel non-module expressions. # May be removed? function lower(mod::Module, ex_in::SyntaxTree; expr_compat_mode::Bool=false, soft_scope::Union{Nothing,Bool}=nothing) ver = expr_compat_mode ? JL_OLD_SYNTAX_VERSION : JL_NEW_SYNTAX_VERSION ex0 = rebase_layers(ex_in, mod, ver) world = Base.get_world_counter() ex1 = expand_forms_1(ex0, world, true) ctx2, ex2 = expand_forms_2(ex1, world) ctx3, ex3 = resolve_scopes(ctx2, ex2; soft_scope) ctx4, ex4 = convert_closures(ctx3, ex3) _ctx5, ex5 = linearize_ir(ctx4, ex4) ex5 end function macroexpand(mod::Module, ex_in::SyntaxTree; expr_compat_mode::Bool=false, ver::VersionNumber=expr_compat_mode ? JL_OLD_SYNTAX_VERSION : JL_NEW_SYNTAX_VERSION, recursive::Bool=true) ex0 = rebase_layers(ex_in, mod, ver) expand_forms_1(ex0, Base.get_world_counter(), recursive) end "May be used in macros or from any module" function macroexpand(st::SyntaxTree) DEBUG && assert_expandable(st) ctx = MacroExpansionContext(st, Base.get_world_counter(), true) expand_forms_1(ctx, st) end # If a top-level thunk has existing context, we can assume all syntax has the # same base layer: either it was produced by a macro expansion and went through # `apply_expansion_layer`, or it was produced by parsing (which we assume either # adds zero or uniform context to the tree). # We ignore old the base layer's module, which should usually be the same as the # current lowering module. (counterexample: macroexpand in mod A producing # escaped :toplevel st, then eval st in mod B, but flisp does the same thing by # spamming globalrefs to mod A throughout st). function rebase_layers(st, mod::Module, ver::VersionNumber) out = if st.context === nothing # assert zero context sc = SyntaxContext(mod, ver) fill_context!(st, sc) else base = base_layer(st.context::SyntaxContext) newbase = ScopeLayer(mod, nothing) _rebase_layers( st, Dict{ScopeLayer, ScopeLayer}(base=>newbase), Dict{SyntaxContext, SyntaxContext}()) end DEBUG && assert_expandable(out) out end function _rebase_layers(st, slmap, scmap) sc = st.context::SyntaxContext sc2 = get(scmap, sc, nothing) if isnothing(sc2) sl2 = _get_sl!(slmap, sc.layer) sc2 = scmap[sc] = SyntaxContext(sl2, sc.unexpanded, sc.version, sc.internal) end if is_leaf(st) || numchildren(st) == 0 @mknode(st; context=sc2) else cs = mapsyntax(c->_rebase_layers(c, slmap, scmap), children(st)) @mknode(st; context=sc2, children=cs) end end function _get_sl!(slmap, sl::ScopeLayer) out = get(slmap, sl, nothing) out isa ScopeLayer && return out slmap[sl] = ScopeLayer( sl.mod, isnothing(sl.escaped) ? nothing : _get_sl!(slmap, sl.escaped)) end # Incremental lowering API which can manage toplevel and module expressions. # # This iteration API is oddly bespoke and arguably somewhat non-Julian for two # reasons: # # * Lowering knows when new modules are required, and may request them with # `:begin_module`. However `eval()` generates those modules so they need to # be passed back into lowering. So we can't just use `Base.iterate()`. (Put a # different way, we have a situation which is suited to coroutines but we # don't want to use full Julia `Task`s for this.) # * We might want to implement this `eval()` in Julia's C runtime code or early # in bootstrap. Hence using SimpleVector and Symbol as the return values of # `lower_step()` # # We might consider changing at least the second of these choices, depending on # how we end up putting this into Base. struct LoweringIterator ver::VersionNumber # later stored in module? todo::Vector{Tuple{SyntaxTree, Bool, Int}} end function lower_init(ex::SyntaxTree, ver) LoweringIterator(ver, [(ex, false, 0)]) end function lower_step(iter::LoweringIterator, mod::Module, world::UInt; soft_scope::Union{Nothing,Bool}=nothing) if isempty(iter.todo) return Core.svec(:done) end top_ex, is_module_body, child_idx = pop!(iter.todo) if child_idx > 0 if child_idx <= numchildren(top_ex) push!(iter.todo, (top_ex, is_module_body, child_idx + 1)) ex = top_ex[child_idx] elseif is_module_body return Core.svec(:end_module) else return lower_step(iter, mod, world; soft_scope) end else ex = top_ex end k = kind(ex) if !(k in KSet"toplevel module") ex = rebase_layers(ex, mod, iter.ver) ex = expand_forms_1(ex, world, true) k = kind(ex) end if k == K"toplevel" push!(iter.todo, (ex, false, 1)) return lower_step(iter, mod, world; soft_scope) elseif k == K"module" (version, notbare, mname, body) = @stm ex begin [K"module" version nb_st mname body] -> (version.value, nb_st.value, mname, body) [K"module" nb_st mname body] -> (nothing, nb_st.value, mname, body) end if kind(mname) != K"Identifier" throw(LoweringError(mname, "Expected module name")) end newmod_name = Symbol(syntax_name(mname)) loc = source_location(LineNumberNode, ex) push!(iter.todo, (body, true, 1)) return Core.svec(:begin_module, version, newmod_name, notbare, loc) else ctx2, ex2 = expand_forms_2(ex, world) ctx3, ex3 = resolve_scopes(ctx2, ex2; soft_scope) ctx4, ex4 = convert_closures(ctx3, ex3) _ctx5, ex5 = linearize_ir(ctx4, ex4) thunk = to_lowered_expr(ex5) return Core.svec(:thunk, thunk) end end #------------------------------------------------------------------------------- function codeinfo_has_image_globalref(@nospecialize(e)) if e isa GlobalRef return 0x00 !== @ccall jl_object_in_image(e.mod::Any)::UInt8 elseif e isa Core.CodeInfo return any(codeinfo_has_image_globalref, e.code) else return false end end function codeinfo_has_fcall(@nospecialize(e)) if e isa Expr if e.head === :(=) return codeinfo_has_fcall(e.args[2]) end return e.head === :foreigncall || e.head === :foreignglobal || e.head === :cfunction end return false end const _CodeInfo_need_ver = v"1.12.0-DEV.512" @static if VERSION < _CodeInfo_need_ver function _CodeInfo(args...) error("Constructing a CodeInfo using JuliaLowering currently requires Julia version $_CodeInfo_need_ver or greater") end else # debuginfo changed completely as of https://github.com/JuliaLang/julia/pull/52415 # nargs / isva was added as of https://github.com/JuliaLang/julia/pull/54341 # field rettype added in https://github.com/JuliaLang/julia/pull/54655 # field has_image_globalref added in https://github.com/JuliaLang/julia/pull/57433 # CodeInfo constructor. TODO: Should be in Core let fns = fieldnames(Core.CodeInfo) fts = fieldtypes(Core.CodeInfo) conversions = [:(convert($t, $n)) for (t,n) in zip(fts, fns)] expected_fns = (:code, :debuginfo, :ssavaluetypes, :ssaflags, :slotnames, :slotflags, :slottypes, :rettype, :parent, :edges, :min_world, :max_world, :method_for_inference_limit_heuristics, :nargs, :propagate_inbounds, :has_fcall, :has_image_globalref, :nospecializeinfer, :isva, :inlining, :constprop, :purity, :inlining_cost) expected_fts = (Vector{Any}, Core.DebugInfo, Any, Vector{UInt32}, Vector{Symbol}, Vector{UInt8}, Any, Any, Any, Any, UInt, UInt, Any, UInt, Bool, Bool, Bool, Bool, Bool, UInt8, UInt8, UInt16, UInt16) code = if fns != expected_fns || fts != expected_fts :(function _CodeInfo(args...) error(string( "JuliaLowering didn't recognize Core.CodeInfo's fields; ", "it may need updating to match Core.CodeInfo.\n", "expected field names: $($expected_fns)\n", "expected field types: $($expected_fts)\n")) end) else :(function _CodeInfo($(fns...)) $(Expr(:new, :(Core.CodeInfo), conversions...)) end) end Core.eval(@__MODULE__, code) end end """ Uncompressed form of DebugInfo's linetable::String. When compressing, some conveniences are erased: - `file` is not present - `line_offset` is identical - `spans` pairs (s1, s2) are stored `(s1-byte_offset, s2-s1+1)` - `line_starts` are stored `x-byte_offset` """ struct SourceByteTable file::Symbol line_offset::Int32 spans::Vector{Tuple{Int32,Int32}} line_starts::Vector{Int32} function SourceByteTable(file, line_offset, spans, line_starts) @assert issorted(spans) @assert allunique(spans) @assert issorted(line_starts) @assert allunique(line_starts) @assert length(line_starts) > 0 for s in spans @assert 0 < s[2] "linenode provenance; expected SourceFile" @assert 0 < s[1] <= s[2]+1 end if !isempty(spans) @assert !isempty(line_starts) min_byte = spans[begin][begin] max_byte = maximum(last, spans) @assert line_starts[begin] <= min_byte for ls in line_starts[begin+1:end] @assert min_byte < ls @assert ls <= max_byte end else # Not used for now @assert false end new(file, line_offset, spans, line_starts) end end function SourceByteTable(sf::SourceFile, spans::Vector{Tuple{Int32, Int32}}) # Trim all newlines outside SBT's range line_starts = map(ls->Int32(ls+sf.byte_offset), sf.line_starts) b0, _ = JuliaSyntax.source_line_range(sf, spans[1][1]) first_line = sf.first_line while length(line_starts) >= 2 && line_starts[2] <= b0 popfirst!(line_starts) first_line += 1 end max_byte = maximum(last, spans) while !isempty(line_starts) && max_byte < line_starts[end] pop!(line_starts) end SourceByteTable(Symbol(sf.filename), first_line, spans, line_starts) end function _take32(io::IOBuffer, n::Integer) n in (0, 1, 2, 4) || throw(ArgumentError("Unsupported byte count")) v = Int32(0) n >= 1 && (v |= Int32(read(io, UInt8))) n >= 2 && (v |= Int32(read(io, UInt8))<<8) n >= 4 && (v |= Int32(read(io, UInt8))<<16) n >= 4 && (v |= Int32(read(io, UInt8))<<24) return v end function _push32(io::IOBuffer, v::Int32, n) n in (0, 1, 2, 4) || throw(ArgumentError("Unsupported byte count")) n >= 1 && write(io, v % UInt8) n >= 2 && write(io, (v>>>8) % UInt8) n >= 4 && write(io, (v>>>16) % UInt8) n >= 4 && write(io, (v>>>24) % UInt8) nothing end _encoded_len(max::Int32) = Int32(max == 0 ? 0 : max < typemax(UInt8) ? 1 : max < typemax(UInt16) ? 2 : 4) function compress_sbt(sbt::SourceByteTable) min_byte = sbt.line_starts[1] max_byte = Int32(0) max_span = Int32(0) for (b1,b2) in sbt.spans max_span = max(max_span, (b2+Int32(1))-b1) max_byte = max(max_byte, b2) end max_byte_rel = Int32(min_byte >= max_byte ? 1 : (max_byte - min_byte)) nlocs::Int32 = length(sbt.spans) encl_span = _encoded_len(max_span) encl_byte = _encoded_len(max_byte_rel) final_len = 14 + # header (encl_byte + encl_span) * nlocs + (encl_byte * length(sbt.line_starts)) io = IOBuffer(;sizehint=final_len) _push32(io, min_byte, 4) _push32(io, sbt.line_offset, 4) _push32(io, nlocs, 4) _push32(io, encl_byte, 1) _push32(io, encl_span, 1) for (b1, b2) in sbt.spans _push32(io, b1 - min_byte, encl_byte) _push32(io, b2 - b1 + Int32(1), encl_span) end for n in sbt.line_starts _push32(io, n - min_byte, encl_byte) end out = take!(io) let l = length(out) @assert l == final_len "wrong final length $l" end return String(out) end function uncompress_sbt(di::Core.DebugInfo) di.linetable isa String || throw(ArgumentError("linetable: expected string")) io = IOBuffer(di.linetable) byte_offset = _take32(io, 4) line_offset = _take32(io, 4) nlocs = _take32(io, 4) byte_encl = _take32(io, 1) span_encl = _take32(io, 1) let newlines_offset = (byte_encl + span_encl) * nlocs @assert bytesavailable(io) >= newlines_offset "compressed string too short" @assert byte_encl == 0 || (bytesavailable(io) - newlines_offset) % byte_encl == 0 "bad newlines" end out_spans = Tuple{Int32,Int32}[] for i in 1:nlocs s1 = _take32(io, byte_encl) s2 = _take32(io, span_encl) push!(out_spans, (s1+byte_offset, s1+byte_offset+s2-1)) end out_newlines = Int32[] while bytesavailable(io) > 0 push!(out_newlines, _take32(io, byte_encl) + byte_offset) end return SourceByteTable(di.def, line_offset, out_spans, out_newlines) end const LINENODE_SPAN_END = Int32(-5) # Byte-precise `DebugInfo` requires `Core.DebugInfo` to accept a `String` linetable, # which is only available on recent Julia. On older versions (e.g. v1.12) we degrade to # line-based `DebugInfo` so that lowering still produces a valid `CodeInfo`, at the cost of # byte-precise source attribution. const _has_byte_precise_debuginfo = hasmethod(Core.DebugInfo, Tuple{Symbol, String, Core.SimpleVector, String}) function _di_pos(st::SyntaxTree) src = JuliaSyntax.unexpanded_sourceref(st) pos = if src isa SourceRef (Int32(first_byte(src)), Int32(last_byte(src))) elseif src isa LineNumberNode (Int32(src.line), LINENODE_SPAN_END) else @jl_assert false st end end # TODO sourcefile(::LNN) should return Symbol, not LNN function _di_sourcefile(st) x = JuliaSyntax.unexpanded_sourceref(st) x isa LineNumberNode ? x.file : x.file[]::SourceFile end # A single pass over all IR to collect unique byte/line positions and CodeInfos function collect_locs!(node_sources, codeinfos, top_sf, st) if kind(st) === K"code_info" push!(codeinfos, st) # TODO: macro_source is ignored for now get!(node_sources, st, _di_pos(st)) for c in children(st[2]) node_sources[c] = if _di_sourcefile(c) !== top_sf top_sf isa SourceFile && @warn "inconsistent provenance for child" c st node_sources[st] else _di_pos(c) end collect_locs!(node_sources, codeinfos, top_sf, c) end elseif !is_leaf(st) # Non-toplevel codeinfo can contain nested codeinfo (opaque closures) for c in children(st) collect_locs!(node_sources, codeinfos, top_sf, c) end end nothing end function add_ci_debuginfo!(st::SyntaxTree, file::Symbol, top_sbt::Union{String, Nothing}, node_sources::Dict{SyntaxTree, Tuple{Int32, Int32}}, spans::Vector{Tuple{Int32, Int32}}) @jl_assert kind(st) === K"code_info" st locs = let a = sizehint!(Vector{Int32}(), 3*numchildren(st[2])) for c in children(st[2]) if top_sbt isa String # precise provenance push!(a, Int32(searchsortedfirst(spans, node_sources[c]))) else i = searchsortedfirst(spans, node_sources[c]) @jl_assert spans[i][2] == LINENODE_SPAN_END (c, "lno with span end?") push!(a, spans[i][1]) end push!(a, Int32(0)) push!(a, Int32(0)) end a end setmeta!(st, :debuginfo, Core.DebugInfo( file, top_sbt, Core.svec(), @ccall(jl_compress_codelocs((-1)::Int32, locs::Any, numchildren(st[2])::Csize_t)::String))) end # Populate `.debuginfo` on all K"code_info" in `st` function add_debuginfo!(st::SyntaxTree) @jl_assert kind(st) === K"code_info" st node_sources = Dict{SyntaxTree, Tuple{Int32, Int32}}() codeinfos = SyntaxList() top_sf = _di_sourcefile(st) collect_locs!(node_sources, codeinfos, top_sf, st) byte_precise = _has_byte_precise_debuginfo && top_sf isa SourceFile if !byte_precise && top_sf isa SourceFile # Without byte-precise support, degrade each byte span to its line number # so the line-based path below emits valid `DebugInfo` (same shape as the # `LineNumberNode` case). for id in collect(keys(node_sources)) line = Int32(JuliaSyntax.source_line(top_sf, node_sources[id][1])) node_sources[id] = (line, LINENODE_SPAN_END) end end spans = sort!(unique(values(node_sources))) if byte_precise top_sbt = compress_sbt(SourceByteTable(top_sf, spans)) file = Symbol(top_sf.filename) else top_sbt = nothing file = top_sf isa SourceFile ? Symbol(top_sf.filename) : Symbol(top_sf) end for ci in codeinfos add_ci_debuginfo!(ci, file, top_sbt, node_sources, spans) end end # flisp: jl_new_code_info_from_ir (method.c) function compute_ssaflags(st::SyntaxTree) @jl_assert kind(st) == K"block" st stmts = children(st) out = zeros(UInt32, length(stmts)) inline_flags = Vector{Bool}() inbounds_depth = 0 purity_flags = Vector{UInt32}() # Note this should probably go in validation or be a user-facing # loweringerror, but method.c only checks this in asserts builds, so we may # need to allow these to be unbalanced function checked_pop!(stk) @jl_assert(!isempty(stk), (st, "ssaflags pop without push")) pop!(stk) end for (i, stmt) in enumerate(stmts) is_flag_stmt = true @stm stmt begin [K"inbounds" [K"Value"]] -> stmt[1].value::Bool ? (inbounds_depth += 1) : # push (inbounds_depth = 0) # clear [K"inbounds_pop"] -> (inbounds_depth = max(0, inbounds_depth-1)) [K"boundscheck" _...] -> nothing [K"inline" [K"Value"]] -> stmt[1].value::Bool ? push!(inline_flags, true) : checked_pop!(inline_flags) [K"noinline" [K"Value"]] -> stmt[1].value::Bool ? push!(inline_flags, false) : checked_pop!(inline_flags) [K"purity"] -> checked_pop!(purity_flags) [K"purity" _ _...] -> push!( purity_flags, UInt32(purity_expr_to_flags(stmt)) << Core.Compiler.NUM_IR_FLAGS) _ -> is_flag_stmt = false end flag = UInt32(0) if !isempty(inline_flags) flag |= (inline_flags[end] ? Core.Compiler.IR_FLAG_INLINE : Core.Compiler.IR_FLAG_NOINLINE) end if inbounds_depth != 0 flag |= Core.Compiler.IR_FLAG_INBOUNDS end if !isempty(purity_flags) for pf in purity_flags flag |= pf end end out[i] = is_flag_stmt ? UInt32(0) : flag end @jl_assert length(out) == length(stmts) st @jl_assert length(inline_flags) == 0 st @jl_assert length(purity_flags) == 0 st out end # Convert SyntaxTree to the CodeInfo+Expr data structures understood by the # Julia runtime function to_code_info(ex::SyntaxTree) slots = ex[1].value::Vector{Slot} meta = ex.meta nargs = sum((s.kind==:argument for s in slots), init=0) slotnames = Vector{Symbol}(undef, length(slots)) slot_rename_inds = Dict{String,Int}() slotflags = Vector{UInt8}(undef, length(slots)) for (i, slot) in enumerate(slots) name = slot.name # TODO: Do we actually want unique names here? The C code in # `jl_new_code_info_from_ir` has logic to simplify gensym'd names and # use the empty string for compiler-generated bindings. if name !== UNUSED ni = get(slot_rename_inds, name, 0) slot_rename_inds[name] = ni + 1 if ni > 0 name = "$name@$ni" end end sname = Symbol(name) slotnames[i] = sname slotflags[i] = # Inference | Codegen slot.is_read << 3 | # SLOT_USED | jl_vinfo_sa slot.is_single_assign << 4 | # SLOT_ASSIGNEDONCE | - slot.is_maybe_undef << 5 | # SLOT_USEDUNDEF | jl_vinfo_usedundef slot.is_called << 6 # SLOT_CALLED | - end stmts = map(_to_lowered_expr, children(ex[2])) has_image_globalref = any(codeinfo_has_image_globalref, stmts) ssaflags = compute_ssaflags(ex[2]) propagate_inbounds = get(meta, :propagate_inbounds, false) has_fcall = any(codeinfo_has_fcall, stmts) nospecializeinfer = get(meta, :nospecializeinfer, false) inlining = get(meta, :inline, false) ? 0x01 : get(meta, :noinline, false) ? 0x02 : 0x00 constprop = get(meta, :aggressive_constprop, false) ? 0x01 : get(meta, :no_constprop, false) ? 0x02 : 0x00 purity = let eo = get(meta, :purity, nothing) isnothing(eo) ? 0x0000 : eo::UInt16 end # The following CodeInfo fields always get their default values for # uninferred code. ssavaluetypes = length(stmts) # Why does the runtime code do this? slottypes = nothing parent = nothing method_for_inference_limit_heuristics = nothing edges = nothing min_world = Csize_t(1) max_world = typemax(Csize_t) isva = false inlining_cost = 0xffff rettype = Any @jl_assert(length(stmts) == numchildren(ex[2]), ex) _CodeInfo( stmts, getmeta(ex, :debuginfo, nothing), ssavaluetypes, ssaflags, slotnames, slotflags, slottypes, rettype, parent, edges, min_world, max_world, method_for_inference_limit_heuristics, nargs, propagate_inbounds, has_fcall, has_image_globalref, nospecializeinfer, isva, inlining, constprop, purity, inlining_cost ) end @fzone "JL: to_lowered_expr" function to_lowered_expr(ex::SyntaxTree) @jl_assert kind(ex) in KSet"thunk code_info" ex add_debuginfo!(kind(ex) === K"thunk" ? ex[1] : ex) _to_lowered_expr(ex) end function _to_lowered_expr(ex::SyntaxTree) k = kind(ex) if is_literal(k) ex.value elseif k == K"nothing" nothing elseif k == K"core" GlobalRef(Core, Symbol(syntax_name(ex))) elseif k == K"top" GlobalRef(Base, Symbol(syntax_name(ex))) elseif k == K"globalref" GlobalRef(ex.mod::Module, Symbol(syntax_name(ex))) elseif k == K"Identifier" # TODO: assert false (only reachable from simdloop?) Symbol(syntax_name(ex)) elseif k == K"SourceLocation" QuoteNode(source_location(LineNumberNode, ex)) elseif k == K"Symbol" QuoteNode(Symbol(syntax_name(ex))) elseif k == K"slot" Core.SlotNumber(syntax_id(ex)) elseif k == K"static_parameter" Expr(:static_parameter, syntax_id(ex)) elseif k == K"SSAValue" Core.SSAValue(syntax_id(ex)) elseif k == K"return" v = _to_lowered_expr(ex[1]) @jl_assert Base.Compiler.is_valid_return(v) ex Core.ReturnNode(v) elseif k == K"inert" est_to_expr(ex) elseif k == K"syntaxinert" ex[1] elseif k == K"code_info" to_code_info(ex) elseif k == K"Value" @jl_assert !isa_lowering_ast_node(ex.value) ( ex, string("smuggling AST through Value is asking for trouble; ", "find a SyntaxTree representation")) ex.value isa LineNumberNode ? QuoteNode(ex.value) : ex.value elseif k == K"goto" Core.GotoNode(syntax_id(ex[1])) elseif k == K"gotoifnot" Core.GotoIfNot(_to_lowered_expr(ex[1]), syntax_id(ex[2])) elseif k == K"enter" catch_idx = syntax_id(ex[1]) numchildren(ex) == 1 ? Core.EnterNode(catch_idx) : Core.EnterNode(catch_idx, _to_lowered_expr(ex[2])) elseif k == K"newvar" Core.NewvarNode(_to_lowered_expr(ex[1])) elseif k == K"opaque_closure_method" args = map(_to_lowered_expr, children(ex)) # opaque_closure_method has special non-evaluated semantics for the # `functionloc` line number node so we need to undo a level of quoting arg4 = args[4] @jl_assert arg4 isa QuoteNode ex args[4] = arg4.value Expr(:opaque_closure_method, args...) elseif k == K"meta" args = Any[_to_lowered_expr(e) for e in children(ex)] # Unpack K"Symbol" QuoteNode as `Expr(:meta)` requires an identifier here. arg1 = args[1] @jl_assert (arg1 isa QuoteNode) ex args[1] = arg1.value Expr(:meta, args...) elseif k == K"foreignsymbol" @jl_assert kind(ex[1]) == K"tuple" ex _foreignsymbol_expr(ex[1]) elseif k == K"static_eval" @jl_assert numchildren(ex) == 1 ex _to_lowered_expr(ex[1]) elseif k == K"cfunction" # For a scope-resolved callable (`K"static_eval"`), drop the module tag # and emit a bare Symbol so `method.c` resolves it in the method's # module at eval time, matching Base `@cfunction`'s runtime semantics. ret = Expr(:cfunction) for (i, e) in enumerate(children(ex)) if i == 2 && kind(e) == K"static_eval" && kind(e[1]) == K"globalref" push!(ret.args, QuoteNode(Symbol(syntax_name(e[1])))) else push!(ret.args, _to_lowered_expr(e)) end end return ret elseif k in KSet"inline noinline inbounds inbounds_pop purity" # only used in compute_ssaflags (see method.c) nothing else # Allowed forms according to https://docs.julialang.org/en/v1/devdocs/ast/ # # call invoke static_parameter `=` method struct_type abstract_type # primitive_type global const new splatnew isdefined # enter leave pop_exception inbounds boundscheck loopinfo copyast meta # lambda head = k == K"call" ? :call : k == K"new" ? :new : k == K"splatnew" ? :splatnew : k == K"=" ? :(=) : k == K"leave" ? :leave : k == K"isdefined" ? :isdefined : k == K"loopinfo" ? :loopinfo : k == K"thunk" ? :thunk : k == K"boundscheck" ? :boundscheck : k == K"latestworld" ? :latestworld : k == K"pop_exception" ? :pop_exception : k == K"captured_local" ? :captured_local : k == K"gc_preserve_begin" ? :gc_preserve_begin : k == K"gc_preserve_end" ? :gc_preserve_end : k == K"foreigncall" ? :foreigncall : k == K"foreignglobal" ? :foreignglobal : k == K"cfunction" ? :cfunction : k == K"aliasscope" ? :aliasscope : k == K"popaliasscope" ? :popaliasscope : k == K"new_opaque_closure" ? :new_opaque_closure : nothing if isnothing(head) throw(LoweringError(ex, "Unhandled form for kind $k")) end ret = Expr(head) for e in children(ex) push!(ret.args, _to_lowered_expr(e)) end return ret end end # ultra-permissive conversion allowing unlowered structure, but lowered leaves function _foreignsymbol_expr(ex) if is_leaf(ex) || kind(ex) == K"inert" _to_lowered_expr(ex) else k = kind(ex) Expr(Symbol((k === K"unknown_head" ? syntax_name(ex) : untokenize(k))::String), map(_foreignsymbol_expr, children(ex))...) end end #------------------------------------------------------------------------------- # Our version of eval - should be upstreamed though? @fzone "JL: eval" function eval(mod::Module, @nospecialize(ex); soft_scope::Union{Nothing,Bool}=nothing, expr_compat_mode::Bool=false) # Run the `eval` driver in the lowering world. Any internal operations # are required to `invokelatest` before executing any code that dispatches # on user code / types. ver = expr_compat_mode ? JL_OLD_SYNTAX_VERSION : JL_NEW_SYNTAX_VERSION return invoke_in_lowering_world(_lower_and_eval, mod, ex, ver, soft_scope) end # `ex` may be a `SyntaxTree` or an `Expr` (or `Expr` tree leaves of any type). function _lower_and_eval(mod::Module, @nospecialize(ex), ver::VersionNumber, soft_scope::Union{Nothing,Bool}) st = ex isa SyntaxTree ? ex : expr_to_est(ex) iter = lower_init(st, ver) return _eval(mod, iter; soft_scope) end function _eval(mod::Module, iter::LoweringIterator; soft_scope::Union{Nothing,Bool}=nothing) modules = Module[mod] result = nothing while true thunk = lower_step(iter, modules[end], Base.get_world_counter(); soft_scope)::Core.SimpleVector type = thunk[1]::Symbol if type == :done break elseif type == :begin_module filename = something(thunk[5].file, :none) mod = @ccall jl_begin_new_module( modules[end]::Any, thunk[3]::Symbol, thunk[2]::Any, thunk[4]::Cint, filename::Cstring, thunk[5].line::Cint)::Module push!(modules, mod) elseif type == :end_module @ccall jl_end_new_module(modules[end]::Module)::Cvoid result = pop!(modules) else @assert type == :thunk result = Base.invokelatest(Core.eval, modules[end], thunk[2]) end end @assert length(modules) === 1 return result end """ include(mod::Module, path::AbstractString) Evaluate the contents of the input source file in the global scope of module `mod`. Every module (except those defined with baremodule) has its own definition of `include()` omitting the `mod` argument, which evaluates the file in that module. Returns the result of the last evaluated expression of the input file. During including, a task-local include path is set to the directory containing the file. Nested calls to include will search relative to that path. This function is typically used to load source interactively, or to combine files in packages that are broken into multiple source files. """ function include(mod::Module, path::AbstractString) path, prev = Base._include_dependency(mod, path) code = read(path, String) tls = task_local_storage() tls[:SOURCE_PATH] = path try return include_string(mod, code, path) finally if prev === nothing delete!(tls, :SOURCE_PATH) else tls[:SOURCE_PATH] = prev end end end """ include_string(mod::Module, code::AbstractString, filename::AbstractString="string") Like `include`, except reads code from the given string rather than from a file. """ function include_string(mod::Module, code::AbstractString, filename::AbstractString="string"; expr_compat_mode=false, version::VersionNumber=VERSION) eval(mod, parseall(SyntaxTree, code; filename, version); expr_compat_mode) end include(path::AbstractString) = include(JuliaLowering, path)