diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index dd6a55bfc9f1c7..94c21f78f19100 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -544,7 +544,7 @@ function store_backedges(caller::CodeInstance, edges::SimpleVector) # ignore `Method`-edges (from e.g. failed `abstract_call_method`) i += 1 continue - elseif isa(item, Core.BindingPartition) + elseif isa(item, Core.BindingPartition) || isa(item, Core.Binding) i += 1 continue end diff --git a/base/Base_compiler.jl b/base/Base_compiler.jl index abeec81f0c0289..e5a97b86ab0e6a 100644 --- a/base/Base_compiler.jl +++ b/base/Base_compiler.jl @@ -236,13 +236,13 @@ include("views.jl") include("baseext.jl") include("c.jl") -include("ntuple.jl") include("abstractset.jl") include("bitarray.jl") include("bitset.jl") include("abstractdict.jl") include("iddict.jl") include("idset.jl") +include("ntuple.jl") include("iterators.jl") using .Iterators: zip, enumerate, only using .Iterators: Flatten, Filter, product # for generators diff --git a/base/boot.jl b/base/boot.jl index 9b386f90d4abef..e50d74659d399f 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -777,27 +777,6 @@ struct GeneratedFunctionStub spnames::SimpleVector end -# invoke and wrap the results of @generated expression -function (g::GeneratedFunctionStub)(world::UInt, source::LineNumberNode, @nospecialize args...) - # args is (spvals..., argtypes...) - body = g.gen(args...) - file = source.file - file isa Symbol || (file = :none) - lam = Expr(:lambda, Expr(:argnames, g.argnames...).args, - Expr(:var"scope-block", - Expr(:block, - source, - Expr(:meta, :push_loc, file, :var"@generated body"), - Expr(:return, body), - Expr(:meta, :pop_loc)))) - spnames = g.spnames - if spnames === svec() - return lam - else - return Expr(Symbol("with-static-parameters"), lam, spnames...) - end -end - # If the generator is a subtype of this trait, inference caches the generated unoptimized # code, sacrificing memory space to improve the performance of subsequent inferences. # This tradeoff is not appropriate in general cases (e.g., for `GeneratedFunctionStub`s diff --git a/base/expr.jl b/base/expr.jl index 84078829f77ed2..a90fd649201417 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -1654,3 +1654,48 @@ end function quoted(@nospecialize(x)) return is_self_quoting(x) ? x : QuoteNode(x) end + +# Implementation of generated functions +function unique end +function generated_body_to_codeinfo(ex::Expr, defmod::Module, isva::Bool) + ci = ccall(:jl_expand, Any, (Any, Any), ex, defmod) + if !isa(ci, CodeInfo) + if isa(ci, Expr) && ci.head === :error + error("syntax: $(ci.args[1])") + end + error("The function body AST defined by this @generated function is not pure. This likely means it contains a closure, a comprehension or a generator.") + end + ci.isva = isva + edges = Core.Binding[] + 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 + +# invoke and wrap the results of @generated expression +function (g::Core.GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize args...) + # args is (spvals..., argtypes...) + body = g.gen(args...) + file = source.file + file isa Symbol || (file = :none) + lam = Expr(:lambda, Expr(:argnames, g.argnames...).args, + Expr(:var"scope-block", + Expr(:block, + LineNumberNode(Int(source.line), source.file), + Expr(:meta, :push_loc, file, :var"@generated body"), + Expr(:return, body), + Expr(:meta, :pop_loc)))) + spnames = g.spnames + return generated_body_to_codeinfo(spnames === Core.svec() ? lam : Expr(Symbol("with-static-parameters"), lam, spnames...), + typename(typeof(g.gen)).module, + source.isva) +end diff --git a/base/invalidation.jl b/base/invalidation.jl index 5abb0b74ad884a..46eea082e17702 100644 --- a/base/invalidation.jl +++ b/base/invalidation.jl @@ -85,12 +85,12 @@ function should_invalidate_code_for_globalref(gr::GlobalRef, src::CodeInfo) return found_any end -function scan_edge_list(ci::Core.CodeInstance, bpart::Core.BindingPartition) +function scan_edge_list(ci::Core.CodeInstance, b::Core.Binding, bpart::Core.BindingPartition) isdefined(ci, :edges) || return false edges = ci.edges i = 1 while i <= length(edges) - if isassigned(edges, i) && edges[i] === bpart + if isassigned(edges, i) && (edges[i] === bpart || edges[i] === b) return true end i += 1 @@ -99,24 +99,26 @@ function scan_edge_list(ci::Core.CodeInstance, bpart::Core.BindingPartition) end function invalidate_code_for_globalref!(gr::GlobalRef, invalidated_bpart::Core.BindingPartition, new_max_world::UInt) + b = convert(Core.Binding, gr) try valid_in_valuepos = false foreach_reachable_mtable(new_max_world) do mt::Core.MethodTable for method in MethodList(mt) + invalidate_all = false if isdefined(method, :source) src = _uncompressed_ir(method) old_stmts = src.code invalidate_all = should_invalidate_code_for_globalref(gr, src) - for mi in specializations(method) - isdefined(mi, :cache) || continue - ci = mi.cache - while true - if ci.max_world > new_max_world && (invalidate_all || scan_edge_list(ci, invalidated_bpart)) - ccall(:jl_invalidate_code_instance, Cvoid, (Any, UInt), ci, new_max_world) - end - isdefined(ci, :next) || break - ci = ci.next + end + for mi in specializations(method) + isdefined(mi, :cache) || continue + ci = mi.cache + while true + if ci.max_world > new_max_world && (invalidate_all || scan_edge_list(ci, b, invalidated_bpart)) + ccall(:jl_invalidate_code_instance, Cvoid, (Any, UInt), ci, new_max_world) end + isdefined(ci, :next) || break + ci = ci.next end end end diff --git a/base/staticdata.jl b/base/staticdata.jl index 345769e4793809..da0f0a939547fe 100644 --- a/base/staticdata.jl +++ b/base/staticdata.jl @@ -96,7 +96,7 @@ function verify_method(codeinst::CodeInstance, stack::Vector{CodeInstance}, visi local min_valid2::UInt, max_valid2::UInt edge = callees[j] @assert !(edge isa Method) # `Method`-edge isn't allowed for the optimized one-edge format - if edge isa Core.BindingPartition + if edge isa Union{Core.BindingPartition, Core.Binding} j += 1 continue end diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 9e221420aa9f41..4d1ab94644e39d 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -127,7 +127,6 @@ XX(jl_exit_on_sigint) \ XX(jl_exit_threaded_region) \ XX(jl_expand) \ - XX(jl_expand_and_resolve) \ XX(jl_expand_stmt) \ XX(jl_expand_stmt_with_loc) \ XX(jl_expand_with_loc) \ diff --git a/src/method.c b/src/method.c index 4b39de9aa67e1d..82e7527fb7517e 100644 --- a/src/method.c +++ b/src/method.c @@ -600,8 +600,7 @@ static jl_value_t *jl_call_staged(jl_method_t *def, jl_value_t *generator, size_t totargs = 2 + n_sparams + def->nargs; JL_GC_PUSHARGS(gargs, totargs); gargs[0] = jl_box_ulong(world); - gargs[1] = jl_box_long(def->line); - gargs[1] = jl_new_struct(jl_linenumbernode_type, gargs[1], def->file); + gargs[1] = (jl_value_t*)def; memcpy(&gargs[2], jl_svec_data(sparam_vals), n_sparams * sizeof(void*)); memcpy(&gargs[2 + n_sparams], args, (def->nargs - def->isva) * sizeof(void*)); if (def->isva) @@ -611,23 +610,6 @@ static jl_value_t *jl_call_staged(jl_method_t *def, jl_value_t *generator, return code; } -// Lower `ex` into Julia IR, and (if it expands into a CodeInfo) resolve global-variable -// references in light of the provided type parameters. -// Like `jl_expand`, if there is an error expanding the provided expression, the return value -// will be an error expression (an `Expr` with `error_sym` as its head), which should be eval'd -// in the caller's context. -JL_DLLEXPORT jl_code_info_t *jl_expand_and_resolve(jl_value_t *ex, jl_module_t *module, - jl_svec_t *sparam_vals) { - jl_code_info_t *func = (jl_code_info_t*)jl_expand((jl_value_t*)ex, module); - JL_GC_PUSH1(&func); - if (jl_is_code_info(func)) { - jl_array_t *stmts = (jl_array_t*)func->code; - jl_resolve_definition_effects_in_ir(stmts, module, sparam_vals, 1); - } - JL_GC_POP(); - return func; -} - JL_DLLEXPORT jl_code_instance_t *jl_cached_uninferred(jl_code_instance_t *codeinst, size_t world) { for (; codeinst; codeinst = jl_atomic_load_relaxed(&codeinst->next)) { @@ -699,25 +681,12 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *mi, size_t ex = jl_call_staged(def, generator, world, mi->sparam_vals, jl_svec_data(ttdt->parameters), jl_nparams(ttdt)); // do some post-processing - if (jl_is_code_info(ex)) { - func = (jl_code_info_t*)ex; - jl_array_t *stmts = (jl_array_t*)func->code; - jl_resolve_definition_effects_in_ir(stmts, def->module, mi->sparam_vals, 1); - } - else { - // Lower the user's expression and resolve references to the type parameters - func = jl_expand_and_resolve(ex, def->module, mi->sparam_vals); - if (!jl_is_code_info(func)) { - if (jl_is_expr(func) && ((jl_expr_t*)func)->head == jl_error_sym) { - ct->ptls->in_pure_callback = 0; - jl_toplevel_eval(def->module, (jl_value_t*)func); - } - jl_error("The function body AST defined by this @generated function is not pure. This likely means it contains a closure, a comprehension or a generator."); - } - // TODO: This should ideally be in the lambda expression, - // but currently our isva determination is non-syntactic - func->isva = def->isva; + if (!jl_is_code_info(ex)) { + jl_error("As of Julia 1.12, generated functions must return `CodeInfo`. See `Base.generated_body_to_codeinfo`."); } + func = (jl_code_info_t*)ex; + jl_array_t *stmts = (jl_array_t*)func->code; + jl_resolve_definition_effects_in_ir(stmts, def->module, mi->sparam_vals, 1); ex = NULL; // If this generated function has an opaque closure, cache it for diff --git a/test/rebinding.jl b/test/rebinding.jl index 10da27ce3ad8f5..7dc8d7926ebd90 100644 --- a/test/rebinding.jl +++ b/test/rebinding.jl @@ -47,4 +47,11 @@ module Rebinding @test f_return_delete_me_indirect() == 2 Base.delete_binding(@__MODULE__, :delete_me) @test_throws UndefVarError f_return_delete_me_indirect() + + # + via generated function + const delete_me = 4 + @generated f_generated_return_delete_me() = return :(delete_me) + @test f_generated_return_delete_me() == 4 + Base.delete_binding(@__MODULE__, :delete_me) + @test_throws UndefVarError f_generated_return_delete_me() end diff --git a/test/staged.jl b/test/staged.jl index 6cb99950a7bb28..f3dbdcd73d8115 100644 --- a/test/staged.jl +++ b/test/staged.jl @@ -381,7 +381,7 @@ let @test length(ir.cfg.blocks) == 1 end -function generate_lambda_ex(world::UInt, source::LineNumberNode, +function generate_lambda_ex(world::UInt, source::Method, argnames, spnames, @nospecialize body) stub = Core.GeneratedFunctionStub(identity, Core.svec(argnames...), Core.svec(spnames...)) return stub(world, source, body) @@ -389,7 +389,7 @@ end # Test that `Core.CachedGenerator` works as expected struct Generator54916 <: Core.CachedGenerator end -function (::Generator54916)(world::UInt, source::LineNumberNode, args...) +function (::Generator54916)(world::UInt, source::Method, args...) return generate_lambda_ex(world, source, (:doit54916, :func, :arg), (), :(func(arg))) end @@ -432,7 +432,7 @@ function overdubbee54341(a, b) a + b end const overdubee_codeinfo54341 = code_lowered(overdubbee54341, Tuple{Any, Any})[1] -function overdub_generator54341(world::UInt, source::LineNumberNode, selftype, fargtypes) +function overdub_generator54341(world::UInt, source::Method, selftype, fargtypes) if length(fargtypes) != 2 return generate_lambda_ex(world, source, (:overdub54341, :args), (), :(error("Wrong number of arguments")))