Skip to content

Commit

Permalink
Merge branch 'master' into hash_tuple_fix
Browse files Browse the repository at this point in the history
  • Loading branch information
nsajko authored Feb 25, 2025
2 parents 2648cb1 + 58399e2 commit 672cfa1
Show file tree
Hide file tree
Showing 164 changed files with 2,816 additions and 2,120 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
3 changes: 2 additions & 1 deletion Compiler/src/Compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali
partition_restriction, quoted, rename_unionall, rewrap_unionall, specialize_method,
structdiff, tls_world_age, unconstrain_vararg_length, unionlen, uniontype_layout,
uniontypes, unsafe_convert, unwrap_unionall, unwrapva, vect, widen_diagonal,
_uncompressed_ir, maybe_add_binding_backedge!
_uncompressed_ir, maybe_add_binding_backedge!, datatype_min_ninitialized,
partialstruct_undef_length, partialstruct_init_undef
using Base.Order

import Base: ==, _topmod, append!, convert, copy, copy!, findall, first, get, get!,
Expand Down
29 changes: 9 additions & 20 deletions Compiler/src/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2148,23 +2148,13 @@ function form_partially_defined_struct(@nospecialize(obj), @nospecialize(name))
isabstracttype(objt) && return nothing
fldidx = try_compute_fieldidx(objt, name.val)
fldidx === nothing && return nothing
isa(obj, PartialStruct) && return define_field(obj, fldidx)
nminfld = datatype_min_ninitialized(objt)
if ismutabletype(objt)
# A mutable struct can have non-contiguous undefined fields, but `PartialStruct` cannot
# model such a state. So here `PartialStruct` can be used to represent only the
# objects where the field following the minimum initialized fields is also defined.
if fldidx nminfld+1
# if it is already represented as a `PartialStruct`, we can add one more
# `isdefined`-field information on top of those implied by its `fields`
if !(obj isa PartialStruct && fldidx == length(obj.fields)+1)
return nothing
end
end
else
fldidx > nminfld || return nothing
end
return PartialStruct(fallback_lattice, objt0, Any[obj isa PartialStruct && ilength(obj.fields) ?
obj.fields[i] : fieldtype(objt0,i) for i = 1:fldidx])
fldidx > nminfld || return nothing
undef = partialstruct_init_undef(objt, fldidx; all_defined = false)
undef[fldidx] = false
fields = Any[fieldtype(objt0, i) for i = 1:fldidx]
return PartialStruct(fallback_lattice, objt0, undef, fields)
end

function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{Any}, call::CallMeta)
Expand Down Expand Up @@ -3421,7 +3411,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 Expand Up @@ -3725,8 +3715,7 @@ end
@nospecializeinfer function widenreturn_partials(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info::BestguessInfo)
if isa(rt, PartialStruct)
fields = copy(rt.fields)
anyrefine = !isvarargtype(rt.fields[end]) &&
length(rt.fields) > datatype_min_ninitialized(rt.typ)
anyrefine = refines_definedness_information(rt)
𝕃 = typeinf_lattice(info.interp)
= strictpartialorder(𝕃)
for i in 1:length(fields)
Expand All @@ -3738,7 +3727,7 @@ end
end
fields[i] = a
end
anyrefine && return PartialStruct(𝕃ᵢ, rt.typ, fields)
anyrefine && return PartialStruct(𝕃ᵢ, rt.typ, rt.undef, fields)
end
if isa(rt, PartialOpaque)
return rt # XXX: this case was missed in #39512
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
6 changes: 3 additions & 3 deletions Compiler/src/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ end
end
elseif isa(arg1, PartialStruct)
if !isvarargtype(arg1.fields[end])
if 1 idx length(arg1.fields)
if !is_field_maybe_undef(arg1, idx)
return Const(true)
end
end
Expand Down Expand Up @@ -1141,8 +1141,8 @@ end
sty = unwrap_unionall(s)::DataType
if isa(name, Const)
nv = _getfield_fieldindex(sty, name)
if isa(nv, Int) && 1 <= nv <= length(s00.fields)
return unwrapva(s00.fields[nv])
if isa(nv, Int) && !is_field_maybe_undef(s00, nv)
return unwrapva(partialstruct_getfield(s00, nv))
end
end
s00 = s
Expand Down
94 changes: 56 additions & 38 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 @@ -512,7 +515,7 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter)
rettype_const = result_type.parameters[1]
const_flags = 0x2
elseif isa(result_type, PartialStruct)
rettype_const = result_type.fields
rettype_const = (result_type.undef, result_type.fields)
const_flags = 0x2
elseif isa(result_type, InterConditional)
rettype_const = result_type
Expand Down Expand Up @@ -956,8 +959,9 @@ function cached_return_type(code::CodeInstance)
rettype_const = code.rettype_const
# the second subtyping/egal conditions are necessary to distinguish usual cases
# from rare cases when `Const` wrapped those extended lattice type objects
if isa(rettype_const, Vector{Any}) && !(Vector{Any} <: rettype)
return PartialStruct(fallback_lattice, rettype, rettype_const)
if isa(rettype_const, Tuple{BitVector, Vector{Any}}) && !(Tuple{BitVector, Vector{Any}} <: rettype)
undef, fields = rettype_const
return PartialStruct(fallback_lattice, rettype, undef, fields)
elseif isa(rettype_const, PartialOpaque) && rettype <: Core.OpaqueClosure
return rettype_const
elseif isa(rettype_const, InterConditional) && rettype !== InterConditional
Expand Down Expand Up @@ -1167,7 +1171,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 +1240,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
Loading

0 comments on commit 672cfa1

Please sign in to comment.