Skip to content

Commit

Permalink
Merge branch 'master' into gb/size-t-offset
Browse files Browse the repository at this point in the history
  • Loading branch information
gbaraldi authored Feb 19, 2025
2 parents d789c23 + b9a8d46 commit 8fa9985
Show file tree
Hide file tree
Showing 103 changed files with 1,730 additions and 1,132 deletions.
7 changes: 4 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ The Julia community uses [GitHub issues](https://github.com/JuliaLang/julia/issu
Issues and pull requests should have self explanatory titles such that they can be understood from the list of PRs and Issues.
i.e. `Add {feature}` and `Fix {bug}` are good, `Fix #12345. Corrects the bug.` is bad.
You can make pull requests for incomplete features to get code review. The convention is to open these a draft PRs and prefix
You can make pull requests for incomplete features to get code review. The convention is to open these as draft PRs and prefix
the pull request title with "WIP:" for Work In Progress, or "RFC:" for Request for Comments when work is completed and ready
for merging. This will prevent accidental merging of work that is in progress.
Expand All @@ -209,6 +209,7 @@ Add new code to Julia's base libraries as follows (this is the "basic" approach;
Build as usual, and do `make clean testall` to test your contribution. If your contribution includes changes to Makefiles or external dependencies, make sure you can build Julia from a clean tree using `git clean -fdx` or equivalent (be careful – this command will delete any files lying around that aren't checked into git).
#### Running specific tests
There are `make` targets for running specific tests:
make test-bitarray
Expand Down Expand Up @@ -346,8 +347,8 @@ please remove the `backport-X.Y` tag from the originating pull request for the c

### Git Recommendations For Pull Requests

- Avoid working from the `master` branch of your fork, creating a new branch will make it easier if Julia's `master` changes and you need to update your pull request.
- Try to [squash](https://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) together small commits that make repeated changes to the same section of code so your pull request is easier to review. A reasonable number of separate well-factored commits is fine, especially for larger changes.
- Avoid working from the `master` branch of your fork. Create a new branch as it will make it easier to update your pull request if Julia's `master` changes.
- Try to [squash](https://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) together small commits that make repeated changes to the same section of code, so your pull request is easier to review. A reasonable number of separate well-factored commits is fine, especially for larger changes.
- If any conflicts arise due to changes in Julia's `master`, prefer updating your pull request branch with `git rebase` versus `git merge` or `git pull`, since the latter will introduce merge commits that clutter the git history with noise that makes your changes more difficult to review.
- Descriptive commit messages are good.
- Using `git add -p` or `git add -i` can be useful to avoid accidentally committing unrelated changes.
Expand Down
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
2 changes: 1 addition & 1 deletion Compiler/src/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3421,7 +3421,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, sstate:
abstract_eval_value(interp, x, sstate, sv)
end
cconv = e.args[5]
if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt16}))
if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt16, Bool}))
override = decode_effects_override(v[2])
effects = override_effects(effects, override)
end
Expand Down
8 changes: 5 additions & 3 deletions Compiler/src/ssair/verify.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

irshow_was_loaded() = invokelatest(isdefined, Compiler.IRShow, :debuginfo_firstline)
function maybe_show_ir(ir::IRCode)
if isdefined(Core, :Main) && isdefined(Core.Main, :Base)
if irshow_was_loaded()
# ensure we use I/O that does not yield, as this gets called during compilation
invokelatest(Core.Main.Base.show, Core.stdout, "text/plain", ir)
else
Expand Down Expand Up @@ -104,15 +105,16 @@ function count_int(val::Int, arr::Vector{Int})
n
end

_debuginfo_firstline(debuginfo::Union{DebugInfo,DebugInfoStream}) = IRShow.debuginfo_firstline(debuginfo)
function verify_ir(ir::IRCode, print::Bool=true,
allow_frontend_forms::Bool=false,
𝕃ₒ::AbstractLattice = SimpleInferenceLattice.instance,
mi::Union{Nothing,MethodInstance}=nothing)
function raise_error()
error_args = Any["IR verification failed."]
if isdefined(Core, :Main) && isdefined(Core.Main, :Base)
if irshow_was_loaded()
# ensure we use I/O that does not yield, as this gets called during compilation
firstline = invokelatest(IRShow.debuginfo_firstline, ir.debuginfo)
firstline = invokelatest(_debuginfo_firstline, ir.debuginfo)
else
firstline = nothing
end
Expand Down
87 changes: 52 additions & 35 deletions Compiler/src/typeinfer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ being used for this purpose alone.
"""
module Timings

using ..Core
using ..Compiler: -, +, :, Vector, length, first, empty!, push!, pop!, @inline,
@inbounds, copy, backtrace

Expand Down Expand Up @@ -142,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 @@ -184,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 @@ -1167,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 @@ -1233,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
10 changes: 2 additions & 8 deletions Compiler/src/typeutils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,8 @@ function isTypeDataType(@nospecialize t)
isType(t) && return false
# Could be Union{} at runtime
t === Core.TypeofBottom && return false
if t.name === Tuple.name
# If we have a Union parameter, could have been redistributed at runtime,
# e.g. `Tuple{Union{Int, Float64}, Int}` is a DataType, but
# `Union{Tuple{Int, Int}, Tuple{Float64, Int}}` is typeequal to it and
# is not.
return all(isTypeDataType, t.parameters)
end
return true
# Return true if `t` is not covariant
return t.name !== Tuple.name
end

has_extended_info(@nospecialize x) = (!isa(x, Type) && !isvarargtype(x)) || isType(x)
Expand Down
2 changes: 1 addition & 1 deletion Compiler/src/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}(
:meta => 0:typemax(Int),
:global => 1:1,
:globaldecl => 1:2,
:foreigncall => 5:typemax(Int), # name, RT, AT, nreq, (cconv, effects), args..., roots...
:foreigncall => 5:typemax(Int), # name, RT, AT, nreq, (cconv, effects, gc_safe), args..., roots...
:cfunction => 5:5,
:isdefined => 1:2,
:code_coverage_effect => 0:0,
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
1 change: 1 addition & 0 deletions Compiler/test/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1769,6 +1769,7 @@ let getfield_tfunc(@nospecialize xs...) =
Compiler.getfield_tfunc(Compiler.fallback_lattice, xs...)
@test getfield_tfunc(Type, Core.Const(:parameters)) !== Union{}
@test !isa(getfield_tfunc(Type{Tuple{Union{Int, Float64}, Int}}, Core.Const(:name)), Core.Const)
@test !isa(getfield_tfunc(Type{Tuple{Any}}, Core.Const(:name)), Core.Const)
end
@test fully_eliminated(Base.ismutable, Tuple{Base.RefValue})

Expand Down
2 changes: 1 addition & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ New library functions
* The new `isfull(c::Channel)` function can be used to check if `put!(c, some_value)` will block ([#53159]).
* `waitany(tasks; throw=false)` and `waitall(tasks; failfast=false, throw=false)` which wait for multiple tasks
at once ([#53341]).
* `uuid7()` creates an RFC 9652 compliant UUID with version 7 ([#54834]).
* `uuid7()` creates an RFC 9562 compliant UUID with version 7 ([#54834]).
* `insertdims(array; dims)` inserts singleton dimensions into an array --- the inverse operation of
`dropdims` ([#45793]).
* A new `Fix` type generalizes `Fix1/Fix2` for fixing a single argument ([#54653]).
Expand Down
Loading

0 comments on commit 8fa9985

Please sign in to comment.