Skip to content

Commit

Permalink
Merge branch 'master' into test_compiler_codegen_ci
Browse files Browse the repository at this point in the history
  • Loading branch information
nsajko authored Feb 19, 2025
2 parents d21ade9 + b9a8d46 commit ce81f20
Show file tree
Hide file tree
Showing 13 changed files with 126 additions and 240 deletions.
6 changes: 4 additions & 2 deletions Compiler/extras/CompilerDevTools/src/CompilerDevTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ struct SplitCacheInterp <: Compiler.AbstractInterpreter
inf_params::Compiler.InferenceParams
opt_params::Compiler.OptimizationParams
inf_cache::Vector{Compiler.InferenceResult}
codegen_cache::IdDict{CodeInstance,CodeInfo}
function SplitCacheInterp(;
world::UInt = Base.get_world_counter(),
inf_params::Compiler.InferenceParams = Compiler.InferenceParams(),
opt_params::Compiler.OptimizationParams = Compiler.OptimizationParams(),
inf_cache::Vector{Compiler.InferenceResult} = Compiler.InferenceResult[])
new(world, inf_params, opt_params, inf_cache)
new(world, inf_params, opt_params, inf_cache, IdDict{CodeInstance,CodeInfo}())
end
end

Expand All @@ -23,10 +24,11 @@ Compiler.OptimizationParams(interp::SplitCacheInterp) = interp.opt_params
Compiler.get_inference_world(interp::SplitCacheInterp) = interp.world
Compiler.get_inference_cache(interp::SplitCacheInterp) = interp.inf_cache
Compiler.cache_owner(::SplitCacheInterp) = SplitCacheOwner()
Compiler.codegen_cache(interp::SplitCacheInterp) = interp.codegen_cache

import Core.OptimizedGenerics.CompilerPlugins: typeinf, typeinf_edge
@eval @noinline typeinf(::SplitCacheOwner, mi::MethodInstance, source_mode::UInt8) =
Base.invoke_in_world(which(typeinf, Tuple{SplitCacheOwner, MethodInstance, UInt8}).primary_world, Compiler.typeinf_ext, SplitCacheInterp(; world=Base.tls_world_age()), mi, source_mode)
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)

@eval @noinline function typeinf_edge(::SplitCacheOwner, mi::MethodInstance, parent_frame::Compiler.InferenceState, world::UInt, source_mode::UInt8)
# TODO: This isn't quite right, we're just sketching things for now
Expand Down
86 changes: 51 additions & 35 deletions Compiler/src/typeinfer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,10 @@ function finish!(interp::AbstractInterpreter, caller::InferenceState, validation
ci, inferred_result, const_flag, first(result.valid_worlds), last(result.valid_worlds), encode_effects(result.ipo_effects),
result.analysis_results, di, edges)
engine_reject(interp, ci)
if !discard_src && isdefined(interp, :codegen) && uncompressed isa CodeInfo
codegen = codegen_cache(interp)
if !discard_src && codegen !== nothing && uncompressed isa CodeInfo
# record that the caller could use this result to generate code when required, if desired, to avoid repeating n^2 work
interp.codegen[ci] = uncompressed
codegen[ci] = uncompressed
if bootstrapping_compiler && inferred_result == nothing
# This is necessary to get decent bootstrapping performance
# when compiling the compiler to inject everything eagerly
Expand Down Expand Up @@ -185,8 +186,9 @@ function finish!(interp::AbstractInterpreter, mi::MethodInstance, ci::CodeInstan
ccall(:jl_update_codeinst, Cvoid, (Any, Any, Int32, UInt, UInt, UInt32, Any, Any, Any),
ci, nothing, const_flag, min_world, max_world, ipo_effects, nothing, di, edges)
code_cache(interp)[mi] = ci
if isdefined(interp, :codegen)
interp.codegen[ci] = src
codegen = codegen_cache(interp)
if codegen !== nothing
codegen[ci] = src
end
engine_reject(interp, ci)
return nothing
Expand Down Expand Up @@ -1168,7 +1170,10 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance, source_mod

ci = result.ci # reload from result in case it changed
@assert frame.cache_mode != CACHE_MODE_NULL
@assert is_result_constabi_eligible(result) || (!isdefined(interp, :codegen) || haskey(interp.codegen, ci))
@assert is_result_constabi_eligible(result) || begin
codegen = codegen_cache(interp)
codegen === nothing || haskey(codegen, ci)
end
@assert is_result_constabi_eligible(result) == use_const_api(ci)
@assert isdefined(ci, :inferred) "interpreter did not fulfill our expectations"
if !is_cached(frame) && source_mode == SOURCE_MODE_ABI
Expand Down Expand Up @@ -1234,44 +1239,55 @@ function collectinvokes!(wq::Vector{CodeInstance}, ci::CodeInfo)
end
end

# This is a bridge for the C code calling `jl_typeinf_func()` on a single Method match
function typeinf_ext_toplevel(mi::MethodInstance, world::UInt, source_mode::UInt8)
interp = NativeInterpreter(world)
ci = typeinf_ext(interp, mi, source_mode)
if source_mode == SOURCE_MODE_ABI && ci isa CodeInstance && !ci_has_invoke(ci)
inspected = IdSet{CodeInstance}()
tocompile = Vector{CodeInstance}()
push!(tocompile, ci)
while !isempty(tocompile)
# ci_has_real_invoke(ci) && return ci # optimization: cease looping if ci happens to get compiled (not just jl_fptr_wait_for_compiled, but fully jl_is_compiled_codeinst)
callee = pop!(tocompile)
ci_has_invoke(callee) && continue
callee in inspected && continue
src = get(interp.codegen, callee, nothing)
function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UInt8)
source_mode == SOURCE_MODE_ABI || return ci
ci isa CodeInstance && !ci_has_invoke(ci) || return ci
codegen = codegen_cache(interp)
codegen === nothing && return ci
inspected = IdSet{CodeInstance}()
tocompile = Vector{CodeInstance}()
push!(tocompile, ci)
while !isempty(tocompile)
# ci_has_real_invoke(ci) && return ci # optimization: cease looping if ci happens to get compiled (not just jl_fptr_wait_for_compiled, but fully jl_is_compiled_codeinst)
callee = pop!(tocompile)
ci_has_invoke(callee) && continue
callee in inspected && continue
src = get(codegen, callee, nothing)
if !isa(src, CodeInfo)
src = @atomic :monotonic callee.inferred
if isa(src, String)
src = _uncompressed_ir(callee, src)
end
if !isa(src, CodeInfo)
src = @atomic :monotonic callee.inferred
if isa(src, String)
src = _uncompressed_ir(callee, src)
end
if !isa(src, CodeInfo)
newcallee = typeinf_ext(interp, callee.def, source_mode)
if newcallee isa CodeInstance
callee === ci && (ci = newcallee) # ci stopped meeting the requirements after typeinf_ext last checked, try again with newcallee
push!(tocompile, newcallee)
#else
# println("warning: could not get source code for ", callee.def)
end
continue
newcallee = typeinf_ext(interp, callee.def, source_mode)
if newcallee isa CodeInstance
callee === ci && (ci = newcallee) # ci stopped meeting the requirements after typeinf_ext last checked, try again with newcallee
push!(tocompile, newcallee)
#else
# println("warning: could not get source code for ", callee.def)
end
continue
end
push!(inspected, callee)
collectinvokes!(tocompile, src)
ccall(:jl_add_codeinst_to_jit, Cvoid, (Any, Any), callee, src)
end
push!(inspected, callee)
collectinvokes!(tocompile, src)
ccall(:jl_add_codeinst_to_jit, Cvoid, (Any, Any), callee, src)
end
return ci
end

function typeinf_ext_toplevel(interp::AbstractInterpreter, mi::MethodInstance, source_mode::UInt8)
ci = typeinf_ext(interp, mi, source_mode)
ci = add_codeinsts_to_jit!(interp, ci, source_mode)
return ci
end

# This is a bridge for the C code calling `jl_typeinf_func()` on a single Method match
function typeinf_ext_toplevel(mi::MethodInstance, world::UInt, source_mode::UInt8)
interp = NativeInterpreter(world)
return typeinf_ext_toplevel(interp, mi, source_mode)
end

# This is a bridge for the C code calling `jl_typeinf_func()` on set of Method matches
function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim::Bool)
inspected = IdSet{CodeInstance}()
Expand Down
17 changes: 17 additions & 0 deletions Compiler/src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ the following methods to satisfy the `AbstractInterpreter` API requirement:
- `get_inference_world(interp::NewInterpreter)` - return the world age for this interpreter
- `get_inference_cache(interp::NewInterpreter)` - return the local inference cache
- `cache_owner(interp::NewInterpreter)` - return the owner of any new cache entries
If `CodeInstance`s compiled using `interp::NewInterpreter` are meant to be executed with `invoke`,
a method `codegen_cache(interp::NewInterpreter) -> IdDict{CodeInstance, CodeInfo}` must be defined,
and inference must be triggered via `typeinf_ext_toplevel` with source mode `SOURCE_MODE_ABI`.
"""
abstract type AbstractInterpreter end

Expand Down Expand Up @@ -430,6 +434,19 @@ to incorporate customized dispatches for the overridden methods.
method_table(interp::AbstractInterpreter) = InternalMethodTable(get_inference_world(interp))
method_table(interp::NativeInterpreter) = interp.method_table

"""
codegen_cache(interp::AbstractInterpreter) -> Union{Nothing, IdDict{CodeInstance, CodeInfo}}
Optionally return a cache associating a `CodeInfo` to a `CodeInstance` that should be added to the JIT
for future execution via `invoke(f, ::CodeInstance, args...)`. This cache is used during `typeinf_ext_toplevel`,
and may be safely discarded between calls to this function.
By default, a value of `nothing` is returned indicating that `CodeInstance`s should not be added to the JIT.
Attempting to execute them via `invoke` will result in an error.
"""
codegen_cache(interp::AbstractInterpreter) = nothing
codegen_cache(interp::NativeInterpreter) = interp.codegen

"""
By default `AbstractInterpreter` implements the following inference bail out logic:
- `bail_out_toplevel_call(::AbstractInterpreter, sig, ::InferenceState)`: bail out from
Expand Down
14 changes: 14 additions & 0 deletions Compiler/test/AbstractInterpreter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -534,3 +534,17 @@ let interp = DebugInterp()
end
@test found
end

@newinterp InvokeInterp
struct InvokeOwner end
codegen = IdDict{CodeInstance, CodeInfo}()
Compiler.cache_owner(::InvokeInterp) = InvokeOwner()
Compiler.codegen_cache(::InvokeInterp) = codegen
let interp = InvokeInterp()
source_mode = Compiler.SOURCE_MODE_ABI
f = (+)
args = (1, 1)
mi = @ccall jl_method_lookup(Any[f, args...]::Ptr{Any}, (1+length(args))::Csize_t, Base.tls_world_age()::Csize_t)::Ref{Core.MethodInstance}
ci = Compiler.typeinf_ext_toplevel(interp, mi, source_mode)
@test invoke(f, ci, args...) == 2
end
51 changes: 0 additions & 51 deletions Compiler/test/codegen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -889,57 +889,6 @@ ex54166 = Union{Missing, Int64}[missing -2; missing -2];
dims54166 = (1,2)
@test (minimum(ex54166; dims=dims54166)[1] === missing)

# #54109 - Excessive LLVM time for egal
struct DefaultOr54109{T}
x::T
default::Bool
end

@eval struct Torture1_54109
$((Expr(:(::), Symbol("x$i"), DefaultOr54109{Float64}) for i = 1:897)...)
end
Torture1_54109() = Torture1_54109((DefaultOr54109(1.0, false) for i = 1:897)...)

@eval struct Torture2_54109
$((Expr(:(::), Symbol("x$i"), DefaultOr54109{Float64}) for i = 1:400)...)
$((Expr(:(::), Symbol("x$(i+400)"), DefaultOr54109{Int16}) for i = 1:400)...)
end
Torture2_54109() = Torture2_54109((DefaultOr54109(1.0, false) for i = 1:400)..., (DefaultOr54109(Int16(1), false) for i = 1:400)...)

@noinline egal_any54109(x, @nospecialize(y::Any)) = x === Base.compilerbarrier(:type, y)

let ir1 = get_llvm(egal_any54109, Tuple{Torture1_54109, Any}),
ir2 = get_llvm(egal_any54109, Tuple{Torture2_54109, Any})

# We can't really do timing on CI, so instead, let's look at the length of
# the optimized IR. The original version had tens of thousands of lines and
# was slower, so just check here that we only have < 500 lines. If somebody,
# implements a better comparison that's larger than that, just re-benchmark
# this and adjust the threshold.

@test count(==('\n'), ir1) < 500
@test count(==('\n'), ir2) < 500
end

## Regression test for egal of a struct of this size without padding, but with
## non-bitsegal, to make sure that it doesn't accidentally go down the accelerated
## path.
@eval struct BigStructAnyInt
$((Expr(:(::), Symbol("x$i"), Pair{Any, Int}) for i = 1:33)...)
end
BigStructAnyInt() = BigStructAnyInt((Union{Base.inferencebarrier(Float64), Int}=>i for i = 1:33)...)
@test egal_any54109(BigStructAnyInt(), BigStructAnyInt())

## For completeness, also test correctness, since we don't have a lot of
## large-struct tests.

# The two allocations of the same struct will likely have different padding,
# we want to make sure we find them egal anyway - a naive memcmp would
# accidentally look at it.
@test egal_any54109(Torture1_54109(), Torture1_54109())
@test egal_any54109(Torture2_54109(), Torture2_54109())
@test !egal_any54109(Torture1_54109(), Torture1_54109((DefaultOr54109(2.0, false) for i = 1:897)...))

bar54599() = Base.inferencebarrier(true) ? (Base.PkgId(Main),1) : nothing

function foo54599()
Expand Down
2 changes: 1 addition & 1 deletion base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3288,7 +3288,7 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in
if p.exitcode == 125
return PrecompilableError()
else
error("Failed to precompile $(repr("text/plain", pkg)) to $(repr(tmppath)).")
error("Failed to precompile $(repr("text/plain", pkg)) to $(repr(tmppath)) ($(Base.process_status(p))).")
end
end

Expand Down
7 changes: 3 additions & 4 deletions src/cgutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4710,10 +4710,9 @@ static jl_cgval_t emit_memoryref(jl_codectx_t &ctx, const jl_cgval_t &ref, jl_cg
setName(ctx.emission_context, ovflw, "memoryref_ovflw");
}
#endif
boffset = ctx.builder.CreateMul(offset, elsz);
setName(ctx.emission_context, boffset, "memoryref_byteoffset");
newdata = ctx.builder.CreateGEP(getInt8Ty(ctx.builder.getContext()), data, boffset);
setName(ctx.emission_context, newdata, "memoryref_data_byteoffset");
Type *elty = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, jl_tparam1(ref.typ));
newdata = ctx.builder.CreateGEP(elty, data, offset);
setName(ctx.emission_context, newdata, "memoryref_data_offset");
(void)boffset; // LLVM is very bad at handling GEP with types different from the load
if (bc) {
BasicBlock *failBB, *endBB;
Expand Down
Loading

0 comments on commit ce81f20

Please sign in to comment.