Skip to content

Commit edf1cd0

Browse files
authored
CompilerDevTools: add proof of concept for caching runtime calls (#57193)
This PR leverages new capabilities that have been implemented in #56660 to also support caching methods for runtime calls under a custom `AbstractInterpreter`. In my understanding, the idea is that when executing code compiled with a custom `AbstractInterpreter`, only methods that can be compiled ahead of execution will be cached, because dynamic calls requiring runtime compilation will not have enough context to know which interpreter and cache to use. They will instead use the native interpreter and cache. This sample package demonstrates a way to ensure that runtime calls go through an entry point (`with_new_compiler`) that provides the context required to use the right cache and interpreter for subsequent compilation. I'd be happy to get feedback on the logic used to redirect runtime calls to `with_new_compiler`. Overloading `optimize(::SplitCacheInterp)` seemed the most convenient (right after that, the `IRCode` gets converted to a `CodeInfo`), but perhaps there might be a better place for it. --------- Co-authored-by: Cédric Belmant <cedric.belmant@juliahub.com>
1 parent 1a3cbb1 commit edf1cd0

File tree

4 files changed

+70
-12
lines changed

4 files changed

+70
-12
lines changed

Compiler/extras/CompilerDevTools/src/CompilerDevTools.jl

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,74 @@
11
module CompilerDevTools
22

33
using Compiler
4+
using Compiler: argextype, widenconst
45
using Core.IR
6+
using Base: isexpr
7+
8+
mutable struct SplitCacheOwner end
59

6-
struct SplitCacheOwner; end
710
struct SplitCacheInterp <: Compiler.AbstractInterpreter
811
world::UInt
12+
owner::SplitCacheOwner
913
inf_params::Compiler.InferenceParams
1014
opt_params::Compiler.OptimizationParams
1115
inf_cache::Vector{Compiler.InferenceResult}
1216
codegen_cache::IdDict{CodeInstance,CodeInfo}
1317
function SplitCacheInterp(;
1418
world::UInt = Base.get_world_counter(),
19+
owner::SplitCacheOwner = SplitCacheOwner(),
1520
inf_params::Compiler.InferenceParams = Compiler.InferenceParams(),
1621
opt_params::Compiler.OptimizationParams = Compiler.OptimizationParams(),
1722
inf_cache::Vector{Compiler.InferenceResult} = Compiler.InferenceResult[])
18-
new(world, inf_params, opt_params, inf_cache, IdDict{CodeInstance,CodeInfo}())
23+
new(world, owner, inf_params, opt_params, inf_cache, IdDict{CodeInstance,CodeInfo}())
1924
end
2025
end
2126

2227
Compiler.InferenceParams(interp::SplitCacheInterp) = interp.inf_params
2328
Compiler.OptimizationParams(interp::SplitCacheInterp) = interp.opt_params
2429
Compiler.get_inference_world(interp::SplitCacheInterp) = interp.world
2530
Compiler.get_inference_cache(interp::SplitCacheInterp) = interp.inf_cache
26-
Compiler.cache_owner(::SplitCacheInterp) = SplitCacheOwner()
31+
Compiler.cache_owner(interp::SplitCacheInterp) = interp.owner
2732
Compiler.codegen_cache(interp::SplitCacheInterp) = interp.codegen_cache
2833

2934
import Core.OptimizedGenerics.CompilerPlugins: typeinf, typeinf_edge
30-
@eval @noinline typeinf(::SplitCacheOwner, mi::MethodInstance, source_mode::UInt8) =
31-
Base.invoke_in_world(which(typeinf, Tuple{SplitCacheOwner, MethodInstance, UInt8}).primary_world, Compiler.typeinf_ext_toplevel, SplitCacheInterp(; world=Base.tls_world_age()), mi, source_mode)
35+
@eval @noinline typeinf(owner::SplitCacheOwner, mi::MethodInstance, source_mode::UInt8) =
36+
Base.invoke_in_world(which(typeinf, Tuple{SplitCacheOwner, MethodInstance, UInt8}).primary_world, Compiler.typeinf_ext_toplevel, SplitCacheInterp(; world=Base.tls_world_age(), owner), mi, source_mode)
3237

33-
@eval @noinline function typeinf_edge(::SplitCacheOwner, mi::MethodInstance, parent_frame::Compiler.InferenceState, world::UInt, source_mode::UInt8)
38+
@eval @noinline function typeinf_edge(owner::SplitCacheOwner, mi::MethodInstance, parent_frame::Compiler.InferenceState, world::UInt, source_mode::UInt8)
3439
# TODO: This isn't quite right, we're just sketching things for now
35-
interp = SplitCacheInterp(; world)
40+
interp = SplitCacheInterp(; world, owner)
3641
Compiler.typeinf_edge(interp, mi.def, mi.specTypes, Core.svec(), parent_frame, false, false)
3742
end
3843

39-
function with_new_compiler(f, args...)
40-
mi = @ccall jl_method_lookup(Any[f, args...]::Ptr{Any}, (1+length(args))::Csize_t, Base.tls_world_age()::Csize_t)::Ref{Core.MethodInstance}
41-
world = Base.tls_world_age()
44+
function lookup_method_instance(f, args...)
45+
@ccall jl_method_lookup(Any[f, args...]::Ptr{Any}, (1+length(args))::Csize_t, Base.tls_world_age()::Csize_t)::Ref{Core.MethodInstance}
46+
end
47+
48+
function Compiler.optimize(interp::SplitCacheInterp, opt::Compiler.OptimizationState, caller::Compiler.InferenceResult)
49+
@invoke Compiler.optimize(interp::Compiler.AbstractInterpreter, opt::Compiler.OptimizationState, caller::Compiler.InferenceResult)
50+
ir = opt.ir::Compiler.IRCode
51+
override = GlobalRef(@__MODULE__(), :with_new_compiler)
52+
for inst in ir.stmts
53+
stmt = inst[:stmt]
54+
isexpr(stmt, :call) || continue
55+
f = stmt.args[1]
56+
f === override && continue
57+
if isa(f, GlobalRef)
58+
T = widenconst(argextype(f, ir))
59+
T <: Core.Builtin && continue
60+
end
61+
insert!(stmt.args, 1, override)
62+
insert!(stmt.args, 3, interp.owner)
63+
end
64+
end
65+
66+
with_new_compiler(f, args...; owner::SplitCacheOwner = SplitCacheOwner()) = with_new_compiler(f, owner, args...)
67+
68+
function with_new_compiler(f, owner::SplitCacheOwner, args...)
69+
mi = lookup_method_instance(f, args...)
4270
new_compiler_ci = Core.OptimizedGenerics.CompilerPlugins.typeinf(
43-
SplitCacheOwner(), mi, Compiler.SOURCE_MODE_ABI
71+
owner, mi, Compiler.SOURCE_MODE_ABI
4472
)
4573
invoke(f, new_compiler_ci, args...)
4674
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Test
2+
using Compiler: code_cache
3+
using Base: inferencebarrier
4+
using CompilerDevTools
5+
using CompilerDevTools: lookup_method_instance, SplitCacheInterp
6+
7+
@testset "CompilerDevTools" begin
8+
do_work(x, y) = x + y
9+
f1() = do_work(inferencebarrier(1), inferencebarrier(2))
10+
interp = SplitCacheInterp()
11+
cache = code_cache(interp)
12+
mi = lookup_method_instance(f1)
13+
@test !haskey(cache, mi)
14+
@test with_new_compiler(f1, interp.owner) === 3
15+
@test haskey(cache, mi)
16+
# Here `do_work` is compiled at runtime, and so must have
17+
# required extra work to be cached under the same cache owner.
18+
mi = lookup_method_instance(do_work, 1, 2)
19+
@test haskey(cache, mi)
20+
end;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using Pkg
2+
3+
Pkg.activate(dirname(@__DIR__)) do
4+
Pkg.instantiate()
5+
include("runtests.jl")
6+
end

test/choosetests.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const STDLIB_DIR = Sys.STDLIB
66
const STDLIBS = filter!(x -> isfile(joinpath(STDLIB_DIR, x, "src", "$(x).jl")), readdir(STDLIB_DIR))
77

88
const TESTNAMES = [
9-
"subarray", "core", "compiler", "worlds", "atomics",
9+
"subarray", "core", "compiler", "compiler_extras", "worlds", "atomics",
1010
"keywordargs", "numbers", "subtype",
1111
"char", "strings", "triplequote", "unicode", "intrinsics",
1212
"dict", "hashing", "iobuffer", "staged", "offsetarray",
@@ -54,6 +54,9 @@ function test_path(test)
5454
else
5555
return joinpath(pkgdir, "test", "runtests")
5656
end
57+
elseif t[1] == "Compiler" && length(t) 3 && t[2] == "extras"
58+
testpath = length(t) >= 4 ? t[4:end] : ("runtests",)
59+
return joinpath(@__DIR__, "..", t[1], t[2], t[3], "test", testpath...)
5760
elseif t[1] == "Compiler"
5861
testpath = length(t) >= 2 ? t[2:end] : ("runtests",)
5962
return joinpath(@__DIR__, "..", t[1], "test", testpath...)
@@ -172,6 +175,7 @@ function choosetests(choices = [])
172175
# do subarray before sparse but after linalg
173176
filtertests!(tests, "subarray")
174177
filtertests!(tests, "compiler", ["Compiler"])
178+
filtertests!(tests, "compiler_extras", ["Compiler/extras/CompilerDevTools/testpkg"])
175179
filtertests!(tests, "stdlib", STDLIBS)
176180
filtertests!(tests, "internet_required", INTERNET_REQUIRED_LIST)
177181
# do ambiguous first to avoid failing if ambiguities are introduced by other tests

0 commit comments

Comments
 (0)