Skip to content

Commit

Permalink
Merge branch 'master' into rationalize_irrational
Browse files Browse the repository at this point in the history
  • Loading branch information
nsajko authored Feb 10, 2025
2 parents 16f6a72 + 8c62f42 commit a5ab4eb
Show file tree
Hide file tree
Showing 111 changed files with 3,281 additions and 1,802 deletions.
4 changes: 2 additions & 2 deletions Compiler/src/Compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ using Core: ABIOverride, Builtin, CodeInstance, IntrinsicFunction, MethodInstanc

using Base
using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospecializeinfer,
BINDING_KIND_GLOBAL, BINDING_KIND_UNDEF_CONST, BINDING_KIND_BACKDATED_CONST,
BINDING_KIND_GLOBAL, BINDING_KIND_UNDEF_CONST, BINDING_KIND_BACKDATED_CONST, BINDING_KIND_DECLARED,
Base, BitVector, Bottom, Callable, DataTypeFieldDesc,
EffectsOverride, Filter, Generator, IteratorSize, JLOptions, NUM_EFFECTS_OVERRIDES,
OneTo, Ordering, RefValue, SizeUnknown, _NAMEDTUPLE_NAME,
Expand All @@ -72,7 +72,7 @@ using Base.Order

import Base: ==, _topmod, append!, convert, copy, copy!, findall, first, get, get!,
getindex, haskey, in, isempty, isready, iterate, iterate, last, length, max_world,
min_world, popfirst!, push!, resize!, setindex!, size
min_world, popfirst!, push!, resize!, setindex!, size, intersect

const getproperty = Core.getfield
const setproperty! = Core.setfield!
Expand Down
52 changes: 27 additions & 25 deletions Compiler/src/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2665,12 +2665,24 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f),
end
pushfirst!(argtypes, ft)
refinements = nothing
if sv isa InferenceState && f === typeassert
# perform very limited back-propagation of invariants after this type assertion
if rt !== Bottom && isa(fargs, Vector{Any})
if sv isa InferenceState
if f === typeassert
# perform very limited back-propagation of invariants after this type assertion
if rt !== Bottom && isa(fargs, Vector{Any})
farg2 = ssa_def_slot(fargs[2], sv)
if farg2 isa SlotNumber
refinements = SlotRefinement(farg2, rt)
end
end
elseif f === setfield! && length(argtypes) == 4 && isa(argtypes[3], Const)
# from there on we know that the struct field will never be undefined,
# so we try to encode that information with a `PartialStruct`
farg2 = ssa_def_slot(fargs[2], sv)
if farg2 isa SlotNumber
refinements = SlotRefinement(farg2, rt)
refined = form_partially_defined_struct(argtypes[2], argtypes[3])
if refined !== nothing
refinements = SlotRefinement(farg2, refined)
end
end
end
end
Expand Down Expand Up @@ -3464,14 +3476,7 @@ world_range(ci::CodeInfo) = WorldRange(ci.min_world, ci.max_world)
world_range(ci::CodeInstance) = WorldRange(ci.min_world, ci.max_world)
world_range(compact::IncrementalCompact) = world_range(compact.ir)

function force_binding_resolution!(g::GlobalRef, world::UInt)
# Force resolution of the binding
# TODO: This will go away once we switch over to fully partitioned semantics
ccall(:jl_force_binding_resolution, Cvoid, (Any, Csize_t), g, world)
return nothing
end

function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode, IncrementalCompact}, retry_after_resolve::Bool=true)
function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode, IncrementalCompact})
worlds = world_range(src)
partition = lookup_binding_partition(min_world(worlds), g)
partition.max_world < max_world(worlds) && return Any
Expand All @@ -3480,25 +3485,18 @@ function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode,
partition = lookup_binding_partition(min_world(worlds), imported_binding)
partition.max_world < max_world(worlds) && return Any
end
if is_some_guard(binding_kind(partition))
if retry_after_resolve
# This method is surprisingly hot. For performance, don't ask the runtime to resolve
# the binding unless necessary - doing so triggers an additional lookup, which though
# not super expensive is hot enough to show up in benchmarks.
force_binding_resolution!(g, min_world(worlds))
return abstract_eval_globalref_type(g, src, false)
end
kind = binding_kind(partition)
if is_some_guard(kind)
# return Union{}
return Any
end
if is_some_const_binding(binding_kind(partition))
if is_some_const_binding(kind)
return Const(partition_restriction(partition))
end
return partition_restriction(partition)
return kind == BINDING_KIND_DECLARED ? Any : partition_restriction(partition)
end

function lookup_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
force_binding_resolution!(g, get_inference_world(interp))
partition = lookup_binding_partition(get_inference_world(interp), g)
update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world))
partition
Expand Down Expand Up @@ -3541,7 +3539,11 @@ function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Co
return RTEffects(rt, Union{}, Effects(EFFECTS_TOTAL, inaccessiblememonly=is_mutation_free_argtype(rt) ? ALWAYS_TRUE : ALWAYS_FALSE))
end

rt = partition_restriction(partition)
if kind == BINDING_KIND_DECLARED
rt = Any
else
rt = partition_restriction(partition)
end
return RTEffects(rt, UndefVarError, generic_getglobal_effects)
end

Expand Down Expand Up @@ -3580,7 +3582,7 @@ function global_assignment_binding_rt_exct(interp::AbstractInterpreter, partitio
elseif is_some_const_binding(kind)
return Pair{Any,Any}(Bottom, ErrorException)
end
ty = partition_restriction(partition)
ty = kind == BINDING_KIND_DECLARED ? Any : partition_restriction(partition)
wnewty = widenconst(newty)
if !hasintersect(wnewty, ty)
return Pair{Any,Any}(Bottom, TypeError)
Expand Down
2 changes: 1 addition & 1 deletion Compiler/src/ssair/ir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ function is_relevant_expr(e::Expr)
:foreigncall, :isdefined, :copyast,
:throw_undef_if_not,
:cfunction, :method, :pop_exception,
:leave,
:leave, :const, :globaldecl,
:new_opaque_closure)
end

Expand Down
1 change: 0 additions & 1 deletion Compiler/src/ssair/verify.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int,
raise_error()
end
elseif isa(op, GlobalRef)
force_binding_resolution!(op, min_world(ir.valid_worlds))
bpart = lookup_binding_partition(min_world(ir.valid_worlds), op)
while is_some_imported(binding_kind(bpart)) && max_world(ir.valid_worlds) <= bpart.max_world
imported_binding = partition_restriction(bpart)::Core.Binding
Expand Down
41 changes: 27 additions & 14 deletions Compiler/src/typeinfer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ If set to `true`, record per-method-instance timings within type inference in th
__set_measure_typeinf(onoff::Bool) = __measure_typeinf__[] = onoff
const __measure_typeinf__ = RefValue{Bool}(false)

function finish!(interp::AbstractInterpreter, caller::InferenceState)
function finish!(interp::AbstractInterpreter, caller::InferenceState, validation_world::UInt)
result = caller.result
opt = result.src
if opt isa OptimizationState
Expand All @@ -108,12 +108,7 @@ function finish!(interp::AbstractInterpreter, caller::InferenceState)
ci = result.ci
# if we aren't cached, we don't need this edge
# but our caller might, so let's just make it anyways
if last(result.valid_worlds) >= get_world_counter()
# TODO: this should probably come after all store_backedges (after optimizations) for the entire graph in finish_cycle
# since we should be requiring that all edges first get their backedges set, as a batch
result.valid_worlds = WorldRange(first(result.valid_worlds), typemax(UInt))
end
if last(result.valid_worlds) == typemax(UInt)
if last(result.valid_worlds) >= validation_world
# if we can record all of the backedges in the global reverse-cache,
# we can now widen our applicability in the global cache too
store_backedges(ci, edges)
Expand Down Expand Up @@ -202,7 +197,14 @@ function finish_nocycle(::AbstractInterpreter, frame::InferenceState)
if opt isa OptimizationState # implies `may_optimize(caller.interp) === true`
optimize(frame.interp, opt, frame.result)
end
finish!(frame.interp, frame)
validation_world = get_world_counter()
finish!(frame.interp, frame, validation_world)
if isdefined(frame.result, :ci)
# After validation, under the world_counter_lock, set max_world to typemax(UInt) for all dependencies
# (recursively). From that point onward the ordinary backedge mechanism is responsible for maintaining
# validity.
ccall(:jl_promote_ci_to_current, Cvoid, (Any, UInt), frame.result.ci, validation_world)
end
if frame.cycleid != 0
frames = frame.callstack::Vector{AbsIntState}
@assert frames[end] === frame
Expand Down Expand Up @@ -236,10 +238,19 @@ function finish_cycle(::AbstractInterpreter, frames::Vector{AbsIntState}, cyclei
optimize(caller.interp, opt, caller.result)
end
end
validation_world = get_world_counter()
cis = CodeInstance[]
for frameid = cycleid:length(frames)
caller = frames[frameid]::InferenceState
finish!(caller.interp, caller)
finish!(caller.interp, caller, validation_world)
if isdefined(caller.result, :ci)
push!(cis, caller.result.ci)
end
end
# After validation, under the world_counter_lock, set max_world to typemax(UInt) for all dependencies
# (recursively). From that point onward the ordinary backedge mechanism is responsible for maintaining
# validity.
ccall(:jl_promote_cis_to_current, Cvoid, (Ptr{CodeInstance}, Csize_t, UInt), cis, length(cis), validation_world)
resize!(frames, cycleid - 1)
return nothing
end
Expand Down Expand Up @@ -1266,6 +1277,7 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim::
tocompile = Vector{CodeInstance}()
codeinfos = []
# first compute the ABIs of everything
latest = true # whether this_world == world_counter()
for this_world in reverse(sort!(worlds))
interp = NativeInterpreter(this_world)
for i = 1:length(methods)
Expand All @@ -1278,18 +1290,18 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim::
# then we want to compile and emit this
if item.def.primary_world <= this_world <= item.def.deleted_world
ci = typeinf_ext(interp, item, SOURCE_MODE_NOT_REQUIRED)
ci isa CodeInstance && !use_const_api(ci) && push!(tocompile, ci)
ci isa CodeInstance && push!(tocompile, ci)
end
elseif item isa SimpleVector
elseif item isa SimpleVector && latest
(rt::Type, sig::Type) = item
# make a best-effort attempt to enqueue the relevant code for the ccallable
ptr = ccall(:jl_get_specialization1,
#= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint),
sig, this_world, #= mt_cache =# 0)
if ptr !== C_NULL
mi = unsafe_pointer_to_objref(ptr)
mi = unsafe_pointer_to_objref(ptr)::MethodInstance
ci = typeinf_ext(interp, mi, SOURCE_MODE_NOT_REQUIRED)
ci isa CodeInstance && !use_const_api(ci) && push!(tocompile, ci)
ci isa CodeInstance && push!(tocompile, ci)
end
# additionally enqueue the ccallable entrypoint / adapter, which implicitly
# invokes the above ci
Expand All @@ -1305,7 +1317,7 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim::
mi = get_ci_mi(callee)
def = mi.def
if use_const_api(callee)
src = codeinfo_for_const(interp, mi, code.rettype_const)
src = codeinfo_for_const(interp, mi, callee.rettype_const)
elseif haskey(interp.codegen, callee)
src = interp.codegen[callee]
elseif isa(def, Method) && ccall(:jl_get_module_infer, Cint, (Any,), def.module) == 0 && !trim
Expand All @@ -1327,6 +1339,7 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim::
println("warning: failed to get code for ", mi)
end
end
latest = false
end
return codeinfos
end
Expand Down
2 changes: 1 addition & 1 deletion Compiler/src/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}(
:copyast => 1:1,
:meta => 0:typemax(Int),
:global => 1:1,
:globaldecl => 2:2,
:globaldecl => 1:2,
:foreigncall => 5:typemax(Int), # name, RT, AT, nreq, (cconv, effects), args..., roots...
:cfunction => 5:5,
:isdefined => 1:2,
Expand Down
31 changes: 13 additions & 18 deletions Compiler/test/effects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -378,32 +378,27 @@ let effects = Base.infer_effects(; optimize=false) do
end

# we should taint `nothrow` if the binding doesn't exist and isn't fixed yet,
# as the cached effects can be easily wrong otherwise
# since the inference currently doesn't track "world-age" of global variables
@eval global_assignment_undefinedyet() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET)) = 42
setglobal!_nothrow_undefinedyet() = setglobal!(@__MODULE__, :UNDEFINEDYET, 42)
let effects = Base.infer_effects() do
global_assignment_undefinedyet()
end
let effects = Base.infer_effects(setglobal!_nothrow_undefinedyet)
@test !Compiler.is_nothrow(effects)
end
let effects = Base.infer_effects() do
setglobal!_nothrow_undefinedyet()
end
@test !Compiler.is_nothrow(effects)
@test_throws ErrorException setglobal!_nothrow_undefinedyet()
# This declares the binding as ::Any
@eval global_assignment_undefinedyet() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET)) = 42
let effects = Base.infer_effects(global_assignment_undefinedyet)
@test Compiler.is_nothrow(effects)
end
global UNDEFINEDYET::String = "0"
let effects = Base.infer_effects() do
global_assignment_undefinedyet()
end
# Again with type mismatch
global UNDEFINEDYET2::String = "0"
setglobal!_nothrow_undefinedyet2() = setglobal!(@__MODULE__, :UNDEFINEDYET2, 42)
@eval global_assignment_undefinedyet2() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET2)) = 42
let effects = Base.infer_effects(global_assignment_undefinedyet2)
@test !Compiler.is_nothrow(effects)
end
let effects = Base.infer_effects() do
setglobal!_nothrow_undefinedyet()
end
let effects = Base.infer_effects(setglobal!_nothrow_undefinedyet2)
@test !Compiler.is_nothrow(effects)
end
@test_throws Union{ErrorException,TypeError} setglobal!_nothrow_undefinedyet() # TODO: what kind of error should this be?
@test_throws TypeError setglobal!_nothrow_undefinedyet2()

# Nothrow for setfield!
mutable struct SetfieldNothrow
Expand Down
2 changes: 1 addition & 1 deletion Compiler/test/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6160,7 +6160,7 @@ end === Int
swapglobal!(@__MODULE__, :swapglobal!_xxx, x)
end === Union{}

global swapglobal!_must_throw
eval(Expr(:const, :swapglobal!_must_throw))
@newinterp SwapGlobalInterp
Compiler.InferenceParams(::SwapGlobalInterp) = Compiler.InferenceParams(; assume_bindings_static=true)
function func_swapglobal!_must_throw(x)
Expand Down
11 changes: 11 additions & 0 deletions Compiler/test/irpasses.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2042,3 +2042,14 @@ let src = code_typed1(()) do
end
@test count(iscall((src, setfield!)), src.code) == 1
end

# optimize `isdefined` away in the presence of a dominating `setfield!`
let src = code_typed1(()) do
a = Ref{Any}()
setfield!(a, :x, 2)
invokelatest(identity, a)
isdefined(a, :x) && return 1.0
a[]
end
@test count(iscall((src, isdefined)), src.code) == 0
end
Loading

0 comments on commit a5ab4eb

Please sign in to comment.