From 003472759c0f8e7c9211887525e7e9a61cd8095a Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Mon, 28 Aug 2023 16:33:36 +0900 Subject: [PATCH] effects: allow override of `:nonoverlayed` effect bit Certain external `AbstractInterpreters`, such as GPUCompiler.jl, have long sought the ability to allow concrete evaluation for specific overlay-ed methods to achieve optimal inference accuracy. This is currently not permitted, although it should be safe when an overlay-ed method has the same semantics as the original method, and its result can be safely replaced with the result of the original method. Refer to JuliaGPU/GPUCompiler.jl#384 for more examples. To address this issue, this commit introduces the capability to override the `:nonoverlayed` effect bit using `@assume_effects`. With the enhancements in PR #51078, this override behaves similarly to other effect bits. Consequently, external `AbstractInterpreters` can utilize this feature to permit concrete evaluation for annotated overlay-ed methods, e.g. ```julia @overlay OVERLAY_MT Base.@assume_effects :nonoverlayed f(x) = [...] ``` However, it now seems awkward to annotate a method with `Base.@assume_effects :nonoverlayed` when it is actually marked with `@overlay`. A more intuitive terminology, like `native_executable`, might be more appropriate for renaming the `:nonoverlayed` effect bit. --- base/boot.jl | 3 +- base/c.jl | 2 +- base/compiler/abstractinterpretation.jl | 2 +- base/compiler/effects.jl | 39 +++++++++-------- base/compiler/typeinfer.jl | 3 ++ base/essentials.jl | 15 ++++--- base/expr.jl | 16 ++++--- base/strings/string.jl | 6 ++- src/jltypes.c | 4 +- src/julia.h | 27 ++++++------ src/method.c | 5 ++- stdlib/Serialization/src/Serialization.jl | 9 ++-- test/compiler/AbstractInterpreter.jl | 51 ++++++++++++++++------- 13 files changed, 116 insertions(+), 66 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index e24a6f4ffc0e0..4e795d24b850f 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -464,7 +464,8 @@ macro _foldable_meta() #=:terminates_locally=#false, #=:notaskstate=#false, #=:inaccessiblememonly=#false, - #=:noub=#true)) + #=:noub=#true, + #=:nonoverlayed=#false)) end const NTuple{N,T} = Tuple{Vararg{T,N}} diff --git a/base/c.jl b/base/c.jl index 662986501d59d..84c9bbf9d5523 100644 --- a/base/c.jl +++ b/base/c.jl @@ -715,6 +715,6 @@ macro ccall(expr) return ccall_macro_lower(:ccall, ccall_macro_parse(expr)...) end -macro ccall_effects(effects::UInt8, expr) +macro ccall_effects(effects::UInt32, expr) return ccall_macro_lower((:ccall, effects), ccall_macro_parse(expr)...) end diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index f8626b0b82ae7..14485af60bfff 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2557,7 +2557,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes: abstract_eval_value(interp, x, vtypes, sv) end cconv = e.args[5] - if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt8})) + if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt32})) override = decode_effects_override(v[2]) effects = Effects(effects; consistent = override.consistent ? ALWAYS_TRUE : effects.consistent, diff --git a/base/compiler/effects.jl b/base/compiler/effects.jl index a8f5596a0af9e..de6c72b961ad3 100644 --- a/base/compiler/effects.jl +++ b/base/compiler/effects.jl @@ -258,29 +258,32 @@ struct EffectsOverride notaskstate::Bool inaccessiblememonly::Bool noub::Bool + nonoverlayed::Bool end function encode_effects_override(eo::EffectsOverride) - e = 0x00 - eo.consistent && (e |= (0x01 << 0)) - eo.effect_free && (e |= (0x01 << 1)) - eo.nothrow && (e |= (0x01 << 2)) - eo.terminates_globally && (e |= (0x01 << 3)) - eo.terminates_locally && (e |= (0x01 << 4)) - eo.notaskstate && (e |= (0x01 << 5)) - eo.inaccessiblememonly && (e |= (0x01 << 6)) - eo.noub && (e |= (0x01 << 7)) + e = zero(UInt32) + eo.consistent && (e |= (1 << 0) % UInt32) + eo.effect_free && (e |= (1 << 1) % UInt32) + eo.nothrow && (e |= (1 << 2) % UInt32) + eo.terminates_globally && (e |= (1 << 3) % UInt32) + eo.terminates_locally && (e |= (1 << 4) % UInt32) + eo.notaskstate && (e |= (1 << 5) % UInt32) + eo.inaccessiblememonly && (e |= (1 << 6) % UInt32) + eo.noub && (e |= (1 << 7) % UInt32) + eo.nonoverlayed && (e |= (1 << 8) % UInt32) return e end -function decode_effects_override(e::UInt8) +function decode_effects_override(e::UInt32) return EffectsOverride( - (e & (0x01 << 0)) != 0x00, - (e & (0x01 << 1)) != 0x00, - (e & (0x01 << 2)) != 0x00, - (e & (0x01 << 3)) != 0x00, - (e & (0x01 << 4)) != 0x00, - (e & (0x01 << 5)) != 0x00, - (e & (0x01 << 6)) != 0x00, - (e & (0x01 << 7)) != 0x00) + !iszero(e & (1 << 0)), + !iszero(e & (1 << 1)), + !iszero(e & (1 << 2)), + !iszero(e & (1 << 3)), + !iszero(e & (1 << 4)), + !iszero(e & (1 << 5)), + !iszero(e & (1 << 6)), + !iszero(e & (1 << 7)), + !iszero(e & (1 << 8))) end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 641ff7962a266..8d09ee074f52d 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -500,6 +500,9 @@ function adjust_effects(sv::InferenceState) if is_effect_overridden(override, :noub) ipo_effects = Effects(ipo_effects; noub=true) end + if is_effect_overridden(override, :nonoverlayed) + ipo_effects = Effects(ipo_effects; nonoverlayed=true) + end end return ipo_effects diff --git a/base/essentials.jl b/base/essentials.jl index 0d32116514052..c275178d5cf7b 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -209,7 +209,8 @@ macro _total_meta() #=:terminates_locally=#false, #=:notaskstate=#true, #=:inaccessiblememonly=#true, - #=:noub=#true)) + #=:noub=#true, + #=:nonoverlayed=#false)) end # can be used in place of `@assume_effects :foldable` (supposed to be used for bootstrapping) macro _foldable_meta() @@ -221,7 +222,8 @@ macro _foldable_meta() #=:terminates_locally=#false, #=:notaskstate=#false, #=:inaccessiblememonly=#true, - #=:noub=#true)) + #=:noub=#true, + #=:nonoverlayed=#false)) end # can be used in place of `@assume_effects :nothrow` (supposed to be used for bootstrapping) macro _nothrow_meta() @@ -233,7 +235,8 @@ macro _nothrow_meta() #=:terminates_locally=#false, #=:notaskstate=#false, #=:inaccessiblememonly=#false, - #=:noub=#false)) + #=:noub=#false, + #=:nonoverlayed=#false)) end # can be used in place of `@assume_effects :terminates_locally` (supposed to be used for bootstrapping) macro _terminates_locally_meta() @@ -245,7 +248,8 @@ macro _terminates_locally_meta() #=:terminates_locally=#true, #=:notaskstate=#false, #=:inaccessiblememonly=#false, - #=:noub=#false)) + #=:noub=#false, + #=:nonoverlayed=#false)) end # can be used in place of `@assume_effects :effect_free :terminates_locally` (supposed to be used for bootstrapping) macro _effect_free_terminates_locally_meta() @@ -257,7 +261,8 @@ macro _effect_free_terminates_locally_meta() #=:terminates_locally=#true, #=:notaskstate=#false, #=:inaccessiblememonly=#false, - #=:noub=#false)) + #=:noub=#false, + #=:nonoverlayed=#false)) end # another version of inlining that propagates an inbounds context diff --git a/base/expr.jl b/base/expr.jl index 9d6ed3278fe8a..ade34fb627633 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -714,8 +714,9 @@ macro assume_effects(args...) ex = nothing idx = length(args) end - (consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub) = - (false, false, false, false, false, false, false, false, false) + (consistent, effect_free, nothrow, terminates_globally, terminates_locally, + notaskstate, inaccessiblememonly, noub, nonoverlayed) = + (false, false, false, false, false, false, false, false, false, false) for org_setting in args[1:idx] (setting, val) = compute_assumed_setting(org_setting) if setting === :consistent @@ -734,6 +735,8 @@ macro assume_effects(args...) inaccessiblememonly = val elseif setting === :noub noub = val + elseif setting === :nonoverlayed + nonoverlayed = val elseif setting === :foldable consistent = effect_free = terminates_globally = noub = val elseif setting === :removable @@ -746,15 +749,18 @@ macro assume_effects(args...) end if is_function_def(inner) return esc(pushmeta!(ex, :purity, - consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub)) + consistent, effect_free, nothrow, terminates_globally, terminates_locally, + notaskstate, inaccessiblememonly, noub, nonoverlayed)) elseif isexpr(ex, :macrocall) && ex.args[1] === Symbol("@ccall") ex.args[1] = GlobalRef(Base, Symbol("@ccall_effects")) insert!(ex.args, 3, Core.Compiler.encode_effects_override(Core.Compiler.EffectsOverride( - consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub))) + consistent, effect_free, nothrow, terminates_globally, terminates_locally, + notaskstate, inaccessiblememonly, noub, nonoverlayed))) return esc(ex) else # anonymous function case return Expr(:meta, Expr(:purity, - consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub)) + consistent, effect_free, nothrow, terminates_globally, terminates_locally, + notaskstate, inaccessiblememonly, noub, nonoverlayed)) end end diff --git a/base/strings/string.jl b/base/strings/string.jl index fefaf5d79ea3a..f69d7809c7749 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -87,7 +87,11 @@ end # This is @assume_effects :effect_free :nothrow :terminates_globally @ccall jl_alloc_string(n::Csize_t)::Ref{String}, # but the macro is not available at this time in bootstrap, so we write it manually. -@eval _string_n(n::Integer) = $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String}, Expr(:call, Expr(:core, :svec), :Csize_t), 1, QuoteNode((:ccall,0xe)), :(convert(Csize_t, n)))) +@eval function _string_n(n::Integer) + return $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String}, + Expr(:call, Expr(:core, :svec), :Csize_t), 1, QuoteNode((:ccall, 0x0000000e)), + :(convert(Csize_t, n)))) +end """ String(s::AbstractString) diff --git a/src/jltypes.c b/src/jltypes.c index f3273ae936db3..2cc96e53273b2 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3005,7 +3005,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_uint8_type, jl_uint8_type, - jl_uint8_type, + jl_uint32_type, jl_uint16_type), jl_emptysvec, 0, 1, 22); @@ -3074,7 +3074,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_uint8_type, jl_uint8_type, - jl_uint8_type), + jl_uint32_type), jl_emptysvec, 0, 1, 10); //const static uint32_t method_constfields[1] = { 0x03fc065f }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<6)|(1<<9)|(1<<10)|(1<<18)|(1<<19)|(1<<20)|(1<<21)|(1<<22)|(1<<23)|(1<<24)|(1<<25); diff --git a/src/julia.h b/src/julia.h index a96b4a1f5e562..ce324e3b52bc9 100644 --- a/src/julia.h +++ b/src/julia.h @@ -269,8 +269,9 @@ typedef union __jl_purity_overrides_t { uint8_t ipo_notaskstate : 1; uint8_t ipo_inaccessiblememonly : 1; uint8_t ipo_noub : 1; + uint8_t ipo_nonoverlayed : 1; } overrides; - uint8_t bits; + uint32_t bits; } _jl_purity_overrides_t; // This type describes a single function body @@ -428,22 +429,24 @@ typedef struct _jl_code_instance_t { // see also encode_effects() and decode_effects() in `base/compiler/effects.jl`, uint32_t ipo_purity_bits; // ipo_purity_flags: - // uint8_t ipo_consistent : 2; + // uint8_t ipo_consistent : 3; // uint8_t ipo_effect_free : 2; - // uint8_t ipo_nothrow : 2; - // uint8_t ipo_terminates : 2; - // uint8_t ipo_nonoverlayed : 1; + // uint8_t ipo_nothrow : 1; + // uint8_t ipo_terminates : 1; // uint8_t ipo_notaskstate : 2; // uint8_t ipo_inaccessiblememonly : 2; + // uint8_t ipo_nonoverlayed : 1; + // uint8_t ipo_noinbounds : 1; _Atomic(uint32_t) purity_bits; // purity_flags: - // uint8_t consistent : 2; - // uint8_t effect_free : 2; - // uint8_t nothrow : 2; - // uint8_t terminates : 2; - // uint8_t nonoverlayed : 1; - // uint8_t notaskstate : 2; - // uint8_t inaccessiblememonly : 2; + // uint8_t ipo_consistent : 3; + // uint8_t ipo_effect_free : 2; + // uint8_t ipo_nothrow : 1; + // uint8_t ipo_terminates : 1; + // uint8_t ipo_notaskstate : 2; + // uint8_t ipo_inaccessiblememonly : 2; + // uint8_t ipo_nonoverlayed : 1; + // uint8_t ipo_noinbounds : 1; jl_value_t *argescapes; // escape information of call arguments // compilation state cache diff --git a/src/method.c b/src/method.c index 00eae940f9f88..d04c41b2e84da 100644 --- a/src/method.c +++ b/src/method.c @@ -192,7 +192,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve jl_error("In ccall calling convention, expected two argument tuple or symbol."); } JL_TYPECHK(ccall method definition, symbol, jl_get_nth_field(cc, 0)); - JL_TYPECHK(ccall method definition, uint8, jl_get_nth_field(cc, 1)); + JL_TYPECHK(ccall method definition, uint32, jl_get_nth_field(cc, 1)); } jl_exprargset(e, 0, resolve_globals(jl_exprarg(e, 0), module, sparam_vals, binding_effects, 1)); i++; @@ -328,7 +328,7 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) else if (ma == (jl_value_t*)jl_no_constprop_sym) li->constprop = 2; else if (jl_is_expr(ma) && ((jl_expr_t*)ma)->head == jl_purity_sym) { - if (jl_expr_nargs(ma) == 8) { + if (jl_expr_nargs(ma) == 9) { li->purity.overrides.ipo_consistent = jl_unbox_bool(jl_exprarg(ma, 0)); li->purity.overrides.ipo_effect_free = jl_unbox_bool(jl_exprarg(ma, 1)); li->purity.overrides.ipo_nothrow = jl_unbox_bool(jl_exprarg(ma, 2)); @@ -337,6 +337,7 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) li->purity.overrides.ipo_notaskstate = jl_unbox_bool(jl_exprarg(ma, 5)); li->purity.overrides.ipo_inaccessiblememonly = jl_unbox_bool(jl_exprarg(ma, 6)); li->purity.overrides.ipo_noub = jl_unbox_bool(jl_exprarg(ma, 7)); + li->purity.overrides.ipo_nonoverlayed = jl_unbox_bool(jl_exprarg(ma, 8)); } } else diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index 7c1043f33bdfe..680f0024e858b 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -80,7 +80,7 @@ const TAGS = Any[ const NTAGS = length(TAGS) @assert NTAGS == 255 -const ser_version = 24 # do not make changes without bumping the version #! +const ser_version = 25 # do not make changes without bumping the version #! format_version(::AbstractSerializer) = ser_version format_version(s::Serializer) = s.version @@ -1028,7 +1028,8 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) isva = deserialize(s)::Bool is_for_opaque_closure = false nospecializeinfer = false - constprop = purity = 0x00 + constprop = 0x00 + purity = format_version(s) >= 25 ? zero(UInt32) : 0x00 template_or_is_opaque = deserialize(s) if isa(template_or_is_opaque, Bool) is_for_opaque_closure = template_or_is_opaque @@ -1039,7 +1040,7 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) constprop = deserialize(s)::UInt8 end if format_version(s) >= 17 - purity = deserialize(s)::UInt8 + purity = deserialize(s)::(format_version(s) >= 25 ? UInt32 : UInt8) end template = deserialize(s) else @@ -1211,7 +1212,7 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) ci.constprop = deserialize(s)::UInt8 end if format_version(s) >= 17 - ci.purity = deserialize(s)::UInt8 + ci.purity = deserialize(s)::(format_version(s) >= 25 ? UInt32 : UInt8) end if format_version(s) >= 22 ci.inlining_cost = deserialize(s)::UInt16 diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl index b46f53ffd576c..e2c1f144ccc9b 100644 --- a/test/compiler/AbstractInterpreter.jl +++ b/test/compiler/AbstractInterpreter.jl @@ -20,8 +20,8 @@ using Base.Experimental: @MethodTable, @overlay end @newinterp MTOverlayInterp -@MethodTable OverlayedMT -CC.method_table(interp::MTOverlayInterp) = CC.OverlayMethodTable(CC.get_world_counter(interp), OverlayedMT) +@MethodTable OVERLAY_MT +CC.method_table(interp::MTOverlayInterp) = CC.OverlayMethodTable(CC.get_world_counter(interp), OVERLAY_MT) function CC.add_remark!(interp::MTOverlayInterp, ::CC.InferenceState, remark) if interp.meta !== nothing @@ -32,7 +32,7 @@ function CC.add_remark!(interp::MTOverlayInterp, ::CC.InferenceState, remark) end strangesin(x) = sin(x) -@overlay OverlayedMT strangesin(x::Float64) = iszero(x) ? nothing : cos(x) +@overlay OVERLAY_MT strangesin(x::Float64) = iszero(x) ? nothing : cos(x) # inference should use the overlayed method table @test Base.return_types((Float64,); interp=MTOverlayInterp()) do x @@ -84,7 +84,7 @@ end |> only === Float64 # not fully covered overlay method match overlay_match(::Any) = nothing -@overlay OverlayedMT overlay_match(::Int) = missing +@overlay OVERLAY_MT overlay_match(::Int) = missing @test Base.return_types((Any,); interp=MTOverlayInterp()) do x overlay_match(x) end |> only === Union{Nothing,Missing} @@ -116,11 +116,34 @@ Base.@assume_effects :total totalcall(f, args...) = f(args...) end end |> only === Nothing +# override `:nonoverlayed` to allow concrete-eval for overlay-ed methods +Base.@assume_effects :foldable function gpucompiler384(x::Int) + 1 < x < 20 || error("x is too big") + return factorial(x) +end +@overlay OVERLAY_MT Base.@assume_effects :foldable :nonoverlayed function gpucompiler384(x::Int) + 1 < x < 20 || raise_on_gpu("x is too big") + return factorial(x) +end +raise_on_gpu(x) = #=do something with GPU=# error(x) + +call_gpucompiler384(x) = gpucompiler384(x) + +@test Base.infer_effects(gpucompiler384, (Int,); interp=MTOverlayInterp()) |> Core.Compiler.is_nonoverlayed +@test Base.infer_effects(call_gpucompiler384, (Int,); interp=MTOverlayInterp()) |> Core.Compiler.is_nonoverlayed + +@test Base.return_types(; interp=MTOverlayInterp()) do + Val(gpucompiler384(3) == 6) +end |> only == Val{true} +@test Base.return_types(; interp=MTOverlayInterp()) do + Val(call_gpucompiler384(3) == 6) +end |> only == Val{true} + # GPUCompiler needs accurate inference through kwfunc with the overlay of `Core.throw_inexacterror` # https://github.com/JuliaLang/julia/issues/48097 @newinterp Issue48097Interp -@MethodTable Issue48097MT -CC.method_table(interp::Issue48097Interp) = CC.OverlayMethodTable(CC.get_world_counter(interp), Issue48097MT) +@MethodTable ISSUE48097_MT +CC.method_table(interp::Issue48097Interp) = CC.OverlayMethodTable(CC.get_world_counter(interp), ISSUE48097_MT) CC.InferenceParams(::Issue48097Interp) = CC.InferenceParams(; unoptimize_throw_blocks=false) function CC.concrete_eval_eligible(interp::Issue48097Interp, @nospecialize(f), result::CC.MethodCallResult, arginfo::CC.ArgInfo, sv::CC.AbsIntState) @@ -132,7 +155,7 @@ function CC.concrete_eval_eligible(interp::Issue48097Interp, end return ret end -@overlay Issue48097MT @noinline Core.throw_inexacterror(f::Symbol, ::Type{T}, val) where {T} = return +@overlay ISSUE48097_MT @noinline Core.throw_inexacterror(f::Symbol, ::Type{T}, val) where {T} = return issue48097(; kwargs...) = return 42 @test fully_eliminated(; interp=Issue48097Interp(), retval=42) do issue48097(; a=1f0, b=1.0) @@ -140,26 +163,26 @@ end # Should not concrete-eval overlayed methods in semi-concrete interpretation @newinterp OverlaySinInterp -@MethodTable OverlaySinMT -CC.method_table(interp::OverlaySinInterp) = CC.OverlayMethodTable(CC.get_world_counter(interp), OverlaySinMT) +@MethodTable OVERLAY_SIN_MT +CC.method_table(interp::OverlaySinInterp) = CC.OverlayMethodTable(CC.get_world_counter(interp), OVERLAY_SIN_MT) overlay_sin1(x) = error("Not supposed to be called.") -@overlay OverlaySinMT overlay_sin1(x) = cos(x) -@overlay OverlaySinMT Base.sin(x::Union{Float32,Float64}) = overlay_sin1(x) +@overlay OVERLAY_SIN_MT overlay_sin1(x) = cos(x) +@overlay OVERLAY_SIN_MT Base.sin(x::Union{Float32,Float64}) = overlay_sin1(x) let oc = Base.code_ircode(; interp=OverlaySinInterp()) do sin(0.) end |> only |> first |> Core.OpaqueClosure @test oc() == cos(0.) end -@overlay OverlaySinMT Base.sin(x::Union{Float32,Float64}) = @noinline overlay_sin1(x) +@overlay OVERLAY_SIN_MT Base.sin(x::Union{Float32,Float64}) = @noinline overlay_sin1(x) let oc = Base.code_ircode(; interp=OverlaySinInterp()) do sin(0.) end |> only |> first |> Core.OpaqueClosure @test oc() == cos(0.) end _overlay_sin2(x) = error("Not supposed to be called.") -@overlay OverlaySinMT _overlay_sin2(x) = cos(x) +@overlay OVERLAY_SIN_MT _overlay_sin2(x) = cos(x) overlay_sin2(x) = _overlay_sin2(x) -@overlay OverlaySinMT Base.sin(x::Union{Float32,Float64}) = @noinline overlay_sin2(x) +@overlay OVERLAY_SIN_MT Base.sin(x::Union{Float32,Float64}) = @noinline overlay_sin2(x) let oc = Base.code_ircode(; interp=OverlaySinInterp()) do sin(0.) end |> only |> first |> Core.OpaqueClosure