From b3dc13bc9147e90781c22f66250a26280b4822bd Mon Sep 17 00:00:00 2001 From: Ian Atol Date: Tue, 30 Aug 2022 13:48:41 -0700 Subject: [PATCH 1/4] Support semi concret eval --- src/Cthulhu.jl | 18 +++++++++++++----- src/callsite.jl | 21 +++++++++++++++++++++ src/interpreter.jl | 7 +++++++ src/reflection.jl | 5 +++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/Cthulhu.jl b/src/Cthulhu.jl index 6becefde..5e91543c 100644 --- a/src/Cthulhu.jl +++ b/src/Cthulhu.jl @@ -281,7 +281,7 @@ end get_effects(result::InferenceResult) = result.ipo_effects @static if VERSION ≥ v"1.9.0-DEV.409" get_effects(result::CC.ConstPropResult) = get_effects(result.result) - get_effects(result::CC.ConcreteResult) = result.effects + get_effects(result::CC.ConstResult) = result.effects else get_effects(result::CC.ConstResult) = result.effects end @@ -382,6 +382,7 @@ end function get_override(@nospecialize(info)) isa(info, ConstPropCallInfo) && return info.result + isa(info, SemiConcreteCallInfo) && return info isa(info, OCCallInfo) && return get_override(info.ci) return nothing end @@ -392,7 +393,7 @@ end # src/ui.jl provides the user facing interface to which _descend responds ## function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::AbstractCursor; - override::Union{Nothing,InferenceResult} = nothing, + override::Union{Nothing,InferenceResult,SemiConcreteCallInfo} = nothing, debuginfo::Union{Symbol,DebugInfo} = CONFIG.debuginfo, # default is compact debuginfo optimize::Bool = CONFIG.optimize, # default is true interruptexc::Bool = CONFIG.interruptexc, @@ -434,10 +435,17 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs """) end while true - if override !== nothing + if override !== nothing && !isa(override, SemiConcreteCallInfo) (; src, rt, infos, slottypes, codeinf, effects) = lookup_constproped(interp, curs, override, optimize) else - if optimize + if isa(override, SemiConcreteCallInfo) + src = CC.copy(override.ir) + rt = get_rt(override) + infos = src.stmts.info + slottypes = src.argtypes + effects = get_effects(override) + (;codeinf) = lookup(interp, mi, optimize) + elseif optimize codeinst = get_optimized_codeinst(interp, curs) if codeinst.inferred === nothing if isdefined(codeinst, :rettype_const) @@ -466,7 +474,7 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs end mi = get_mi(curs) src = preprocess_ci!(src, mi, optimize, CONFIG) - if optimize # optimization might have deleted some statements + if optimize || isa(src, IRCode) # optimization might have deleted some statements infos = src.stmts.info else @assert length(src.code) == length(infos) diff --git a/src/callsite.jl b/src/callsite.jl index 8b772b63..d490b9ea 100644 --- a/src/callsite.jl +++ b/src/callsite.jl @@ -138,6 +138,15 @@ get_mi(ceci::ConcreteCallInfo) = get_mi(ceci.mi) get_rt(ceci::ConcreteCallInfo) = get_rt(ceci.mi) get_effects(ceci::ConcreteCallInfo) = get_effects(ceci.mi) +struct SemiConcreteCallInfo <: CallInfo + mi::CallInfo + argtypes::ArgTypes + ir::IRCode +end +get_mi(scci::SemiConcreteCallInfo) = get_mi(scci.mi) +get_rt(scci::SemiConcreteCallInfo) = get_rt(scci.mi) +get_effects(scci::SemiConcreteCallInfo) = get_effects(scci.mi) + # CUDA callsite struct CuCallInfo <: CallInfo cumi::MICallInfo @@ -309,6 +318,13 @@ function show_callinfo(limiter, ci::ConstPropCallInfo) __show_limited(limiter, name, tt, get_rt(ci), get_effects(ci)) end +function show_callinfo(limiter, ci::SemiConcreteCallInfo) + # XXX: The first argument could be const-overriden too + name = get_mi(ci).def.name + tt = ci.argtypes[2:end] + __show_limited(limiter, name, tt, get_rt(ci)) +end + function show_callinfo(limiter, ci::ConcreteCallInfo) # XXX: The first argument could be const-overriden too name = get_mi(ci).def.name @@ -373,6 +389,11 @@ function print_callsite_info(limiter::IO, info::ConstPropCallInfo) show_callinfo(limiter, info) end +function print_callsite_info(limiter::IO, info::SemiConcreteCallInfo) + print(limiter, " = < semi-consteval > ") + show_callinfo(limiter, info) +end + function print_callsite_info(limiter::IO, info::ConcreteCallInfo) print(limiter, "< concrete eval > ") show_callinfo(limiter, info) diff --git a/src/interpreter.jl b/src/interpreter.jl index d0e53dc3..45c7df09 100644 --- a/src/interpreter.jl +++ b/src/interpreter.jl @@ -192,6 +192,13 @@ function CC.inlining_policy(interp::CthulhuInterpreter) end end # @static if isdefined(CC, :is_stmt_inline) +@static if isdefined(CC, :codeinst_to_ir) +function CC.codeinst_to_ir(interp::CthulhuInterpreter, code::CodeInstance) + isa(code.inferred, Nothing) && return nothing + return CC.copy((code.inferred::OptimizedSource).ir) +end +end # @static if isdefined(CC, :codeinst_to_ir) + function CC.finish!(interp::CthulhuInterpreter, caller::InferenceResult) effects = EFFECTS_ENABLED ? caller.ipo_effects : nothing caller.src = create_cthulhu_source(caller.src, effects) diff --git a/src/reflection.jl b/src/reflection.jl index d5792eb7..2ad72f9e 100644 --- a/src/reflection.jl +++ b/src/reflection.jl @@ -115,6 +115,11 @@ function process_const_info(interp::AbstractInterpreter, @nospecialize(thisinfo) effects = get_effects(result) mici = MICallInfo(linfo, rt, effects) return ConstPropCallInfo(is_cached(optimize ? linfo : result) ? mici : UncachedCallInfo(mici), result) + elseif (@static isdefined(CC, :SemiConcreteResult) && true) && isa(result, CC.SemiConcreteResult) + linfo = result.mi + effects = get_effects(result) + mici = MICallInfo(linfo, rt, effects) + return SemiConcreteCallInfo(mici, argtypes, result.ir) elseif (@static isdefined(CC, :ConstResult) && true) && isa(result, CC.ConstResult) linfo = result.mi effects = get_effects(result) From c9af9c6654da22e04b4419691d58b0c0ff9f73da Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Mon, 5 Sep 2022 21:23:10 +0900 Subject: [PATCH 2/4] fixup --- src/Cthulhu.jl | 26 ++++++++++++++++---------- src/callsite.jl | 2 +- src/interface.jl | 7 +++++++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Cthulhu.jl b/src/Cthulhu.jl index 5e91543c..74506f64 100644 --- a/src/Cthulhu.jl +++ b/src/Cthulhu.jl @@ -281,7 +281,8 @@ end get_effects(result::InferenceResult) = result.ipo_effects @static if VERSION ≥ v"1.9.0-DEV.409" get_effects(result::CC.ConstPropResult) = get_effects(result.result) - get_effects(result::CC.ConstResult) = result.effects + get_effects(result::CC.ConcreteResult) = result.effects + get_effects(result::CC.SemiConcreteResult) = result.effects else get_effects(result::CC.ConstResult) = result.effects end @@ -380,6 +381,16 @@ function lookup_constproped_unoptimized(interp::CthulhuInterpreter, override::In return (; src, rt, infos, slottypes, effects, codeinf) end +function lookup_semiconcrete(interp::CthulhuInterpreter, override::SemiConcreteCallInfo, optimize::Bool) + src = CC.copy(override.ir) + rt = get_rt(override) + infos = src.stmts.info + slottypes = src.argtypes + effects = get_effects(override) + (; codeinf) = lookup(interp, get_mi(override), optimize) + return (; src, rt, infos, slottypes, effects, codeinf) +end + function get_override(@nospecialize(info)) isa(info, ConstPropCallInfo) && return info.result isa(info, SemiConcreteCallInfo) && return info @@ -435,17 +446,12 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs """) end while true - if override !== nothing && !isa(override, SemiConcreteCallInfo) + if isa(override, InferenceResult) (; src, rt, infos, slottypes, codeinf, effects) = lookup_constproped(interp, curs, override, optimize) + elseif isa(override, SemiConcreteCallInfo) + (; src, rt, infos, slottypes, codeinf, effects) = lookup_semiconcrete(interp, curs, override, optimize) else - if isa(override, SemiConcreteCallInfo) - src = CC.copy(override.ir) - rt = get_rt(override) - infos = src.stmts.info - slottypes = src.argtypes - effects = get_effects(override) - (;codeinf) = lookup(interp, mi, optimize) - elseif optimize + if optimize codeinst = get_optimized_codeinst(interp, curs) if codeinst.inferred === nothing if isdefined(codeinst, :rettype_const) diff --git a/src/callsite.jl b/src/callsite.jl index d490b9ea..35dce8ab 100644 --- a/src/callsite.jl +++ b/src/callsite.jl @@ -322,7 +322,7 @@ function show_callinfo(limiter, ci::SemiConcreteCallInfo) # XXX: The first argument could be const-overriden too name = get_mi(ci).def.name tt = ci.argtypes[2:end] - __show_limited(limiter, name, tt, get_rt(ci)) + __show_limited(limiter, name, tt, get_rt(ci), get_effects(ci)) end function show_callinfo(limiter, ci::ConcreteCallInfo) diff --git a/src/interface.jl b/src/interface.jl index 51377323..60f728b4 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -28,6 +28,13 @@ missing `$AbstractCursor` API: lookup_constproped(interp::CthulhuInterpreter, ::CthulhuCursor, override::InferenceResult, optimize::Bool) = lookup_constproped(interp, override, optimize) +lookup_semiconcrete(interp::AbstractInterpreter, curs::AbstractCursor, override::SemiConcreteCallInfo, optimize::Bool) = error(lazy""" +missing `$AbstractCursor` API: +`$(typeof(curs))` is required to implement the `$lookup_semicocnrete(interp::$(typeof(interp)), curs::$(typeof(curs)), override::SemiConcreteCallInfo, optimize::Bool)` interface. +""") +lookup_semiconcrete(interp::CthulhuInterpreter, ::CthulhuCursor, override::SemiConcreteCallInfo, optimize::Bool) = + lookup_semiconcrete(interp, override, optimize) + get_mi(curs::AbstractCursor) = error(lazy""" missing `$AbstractCursor` API: `$(typeof(curs))` is required to implement the `$get_mi(curs::$(typeof(curs))) -> MethodInstance` interface. From 7001ac394921dcb7fcb67e2a792a9e3d46382327 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Mon, 5 Sep 2022 21:32:24 +0900 Subject: [PATCH 3/4] add test case --- src/callsite.jl | 2 +- test/test_Cthulhu.jl | 73 ++++++++++++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/callsite.jl b/src/callsite.jl index 35dce8ab..632830dd 100644 --- a/src/callsite.jl +++ b/src/callsite.jl @@ -390,7 +390,7 @@ function print_callsite_info(limiter::IO, info::ConstPropCallInfo) end function print_callsite_info(limiter::IO, info::SemiConcreteCallInfo) - print(limiter, " = < semi-consteval > ") + print(limiter, " = < semi-concrete eval > ") show_callinfo(limiter, info) end diff --git a/test/test_Cthulhu.jl b/test/test_Cthulhu.jl index 9e9b7891..64a5b917 100644 --- a/test/test_Cthulhu.jl +++ b/test/test_Cthulhu.jl @@ -237,32 +237,59 @@ end @test occursin("= < constprop > getproperty(", String(take!(io))) end end +end - @static isdefined(Core.Compiler, :ConstResult) && @testset "ConstResult" begin - # constant prop' on all the splits - let callsites = (@eval Module() begin - Base.@assume_effects :terminates_locally function issue41694(x) - res = 1 - 1 < x < 20 || throw("bad") - while x > 1 - res *= x - x -= 1 - end - return res - end +Base.@assume_effects :terminates_locally function issue41694(x) + res = 1 + 1 < x < 20 || throw("bad") + while x > 1 + res *= x + x -= 1 + end + return res +end +@static isdefined(Core.Compiler, :ConstResult) && @testset "ConstResult" begin + # constant prop' on all the splits + let callsites = find_callsites_by_ftt(; optimize = false) do + issue41694(12) + end + callinfo = only(callsites).info + @test isa(callinfo, Cthulhu.ConcreteCallInfo) + @test Cthulhu.get_rt(callinfo) == Core.Const(factorial(12)) + @test Cthulhu.get_effects(callinfo) |> Core.Compiler.is_foldable + io = IOBuffer() + print(io, only(callsites)) + @test occursin("= < concrete eval > issue41694(::Core.Const(12))", String(take!(io))) + end +end - $find_callsites_by_ftt(; optimize = false) do - issue41694(12) - end - end) - callinfo = only(callsites).info - @test isa(callinfo, Cthulhu.ConcreteCallInfo) - @test Cthulhu.get_rt(callinfo) == Core.Const(factorial(12)) - @test Cthulhu.get_effects(callinfo) |> Core.Compiler.is_foldable - io = IOBuffer() - print(io, only(callsites)) - @test occursin("= < concrete eval > issue41694(::Core.Const(12))", String(take!(io))) +let # check the performance benefit of semi concrete evaluation + param = 1000 + ex = Expr(:block) + var = gensym() + push!(ex.args, :($var = x)) + for _ = 1:param + newvar = gensym() + push!(ex.args, :($newvar = sin($var))) + var = newvar + end + @eval global Base.@constprop :aggressive Base.@assume_effects :nothrow function semi_concrete_eval(x::Int, _::Int) + out = $ex + out + end +end +@static isdefined(Core.Compiler, :SemiConcreteResult) && @testset "SemiConcreteResult" begin + # constant prop' on all the splits + let callsites = find_callsites_by_ftt((Int,); optimize = false) do x + semi_concrete_eval(42, x) end + callinfo = only(callsites).info + @test isa(callinfo, Cthulhu.SemiConcreteCallInfo) + @test Cthulhu.get_rt(callinfo) == Core.Const(semi_concrete_eval(42, 0)) + # @test Cthulhu.get_effects(callinfo) |> Core.Compiler.is_semiconcrete_eligible + io = IOBuffer() + print(io, only(callsites)) + @test occursin("= < semi-concrete eval > semi_concrete_eval(::Core.Const(42),::$Int)", String(take!(io))) end end From f6d2a9c3ce444ca17acfe18fe8fa545ce4b65dfd Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Mon, 5 Sep 2022 21:46:47 +0900 Subject: [PATCH 4/4] fixup test cases --- src/callsite.jl | 3 +-- src/reflection.jl | 2 +- test/test_Cthulhu.jl | 13 +++++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/callsite.jl b/src/callsite.jl index 632830dd..1524eaf2 100644 --- a/src/callsite.jl +++ b/src/callsite.jl @@ -140,7 +140,6 @@ get_effects(ceci::ConcreteCallInfo) = get_effects(ceci.mi) struct SemiConcreteCallInfo <: CallInfo mi::CallInfo - argtypes::ArgTypes ir::IRCode end get_mi(scci::SemiConcreteCallInfo) = get_mi(scci.mi) @@ -321,7 +320,7 @@ end function show_callinfo(limiter, ci::SemiConcreteCallInfo) # XXX: The first argument could be const-overriden too name = get_mi(ci).def.name - tt = ci.argtypes[2:end] + tt = ci.ir.argtypes[2:end] __show_limited(limiter, name, tt, get_rt(ci), get_effects(ci)) end diff --git a/src/reflection.jl b/src/reflection.jl index 2ad72f9e..964ea855 100644 --- a/src/reflection.jl +++ b/src/reflection.jl @@ -119,7 +119,7 @@ function process_const_info(interp::AbstractInterpreter, @nospecialize(thisinfo) linfo = result.mi effects = get_effects(result) mici = MICallInfo(linfo, rt, effects) - return SemiConcreteCallInfo(mici, argtypes, result.ir) + return SemiConcreteCallInfo(mici, result.ir) elseif (@static isdefined(CC, :ConstResult) && true) && isa(result, CC.ConstResult) linfo = result.mi effects = get_effects(result) diff --git a/test/test_Cthulhu.jl b/test/test_Cthulhu.jl index 64a5b917..10bcccea 100644 --- a/test/test_Cthulhu.jl +++ b/test/test_Cthulhu.jl @@ -429,7 +429,7 @@ invoke_constcall(a::Number, c::Bool) = c ? Number : :number @test info.ci.rt === Core.Compiler.Const(:Int) end - # const prop' callsite + # const prop' / semi-concrete callsite @static hasfield(Core.Compiler.InvokeCallInfo, :result) && let callsites = find_callsites_by_ftt((Any,); optimize=false) do a Base.@invoke invoke_constcall(a::Any, true::Bool) end @@ -438,12 +438,17 @@ invoke_constcall(a::Number, c::Bool) = c ? Number : :number @test isa(info, Cthulhu.InvokeCallInfo) @static Cthulhu.EFFECTS_ENABLED && Cthulhu.get_effects(info) |> Core.Compiler.is_total inner = info.ci - @test isa(inner, Cthulhu.ConstPropCallInfo) rt = Core.Compiler.Const(Any) - @test inner.result.result === rt + @test Cthulhu.get_rt(info) === rt buf = IOBuffer() show(buf, callsite) - @test occursin("= invoke < invoke_constcall(::Any,::$(Core.Compiler.Const(true)))::$rt", String(take!(buf))) + @static if isdefined(Core.Compiler, :SemiConcreteResult) + @test isa(inner, Cthulhu.SemiConcreteCallInfo) + @test occursin("= invoke < invoke_constcall(::Any,::$(Core.Compiler.Const(true)))::$rt", String(take!(buf))) + else + @test isa(inner, Cthulhu.ConstPropCallInfo) + @test occursin("= invoke < invoke_constcall(::Any,::$(Core.Compiler.Const(true)))::$rt", String(take!(buf))) + end end end