Skip to content

Commit

Permalink
Merge branch 'master' into ndims_broadcasted
Browse files Browse the repository at this point in the history
  • Loading branch information
nsajko authored Jan 9, 2025
2 parents 4eed3e7 + 3d85309 commit 827581d
Show file tree
Hide file tree
Showing 24 changed files with 466 additions and 111 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, Base, BitVector, Bottom, Callable, DataTypeFieldDesc,
BINDING_KIND_GLOBAL, BINDING_KIND_UNDEF_CONST, Base, BitVector, Bottom, Callable, DataTypeFieldDesc,
EffectsOverride, Filter, Generator, IteratorSize, JLOptions, NUM_EFFECTS_OVERRIDES,
OneTo, Ordering, RefValue, SizeUnknown, _NAMEDTUPLE_NAME,
_array_for, _bits_findnext, _methods_by_ftype, _uniontypes, all, allocatedinline, any,
Expand All @@ -58,7 +58,7 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali
datatype_pointerfree, decode_effects_override, diff_names, fieldindex,
generating_output, get_nospecializeinfer_sig, get_world_counter, has_free_typevars,
hasgenerator, hasintersect, indexed_iterate, isType, is_file_tracked, is_function_def,
is_meta_expr, is_meta_expr_head, is_nospecialized, is_nospecializeinfer,
is_meta_expr, is_meta_expr_head, is_nospecialized, is_nospecializeinfer, is_defined_const_binding,
is_some_const_binding, is_some_guard, is_some_imported, is_valid_intrinsic_elptr,
isbitsunion, isconcretedispatch, isdispatchelem, isexpr, isfieldatomic, isidentityfree,
iskindtype, ismutabletypename, ismutationfree, issingletontype, isvarargtype, isvatuple,
Expand Down
105 changes: 79 additions & 26 deletions Compiler/src/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2637,21 +2637,14 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f),
elseif f === Core.getfield && argtypes_are_actually_getglobal(argtypes)
return Future(abstract_eval_getglobal(interp, sv, si.saw_latestworld, argtypes))
elseif f === Core.isdefined && argtypes_are_actually_getglobal(argtypes)
exct = Bottom
if length(argtypes) == 4
order = argtypes[4]
exct = global_order_exct(order, #=loading=#true, #=storing=#false)
if !(isa(order, Const) && get_atomic_order(order.val, #=loading=#true, #=storing=#false).x >= MEMORY_ORDER_UNORDERED.x)
exct = Union{exct, ConcurrencyViolationError}
end
end
return Future(merge_exct(CallMeta(abstract_eval_isdefined(
interp,
GlobalRef((argtypes[2]::Const).val::Module,
(argtypes[3]::Const).val::Symbol),
si.saw_latestworld,
sv),
NoCallInfo()), exct))
return Future(abstract_eval_isdefinedglobal(interp, argtypes[2], argtypes[3], Const(true),
length(argtypes) == 4 ? argtypes[4] : Const(:unordered),
si.saw_latestworld, sv))
elseif f === Core.isdefinedglobal && 3 <= length(argtypes) <= 5
return Future(abstract_eval_isdefinedglobal(interp, argtypes[2], argtypes[3],
length(argtypes) >= 4 ? argtypes[4] : Const(true),
length(argtypes) >= 5 ? argtypes[5] : Const(:unordered),
si.saw_latestworld, sv))
elseif f === Core.get_binding_type
return Future(abstract_eval_get_binding_type(interp, sv, argtypes))
end
Expand Down Expand Up @@ -3203,21 +3196,73 @@ function abstract_eval_isdefined_expr(interp::AbstractInterpreter, e::Expr, ssta
return abstract_eval_isdefined(interp, sym, sstate.saw_latestworld, sv)
end

function abstract_eval_isdefined(interp::AbstractInterpreter, @nospecialize(sym), saw_latestworld::Bool, sv::AbsIntState)
const generic_isdefinedglobal_effects = Effects(EFFECTS_TOTAL, consistent=ALWAYS_FALSE, nothrow=false)
function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, mod::Module, sym::Symbol, allow_import::Union{Bool, Nothing}, saw_latestworld::Bool, sv::AbsIntState)
rt = Bool
if saw_latestworld
return RTEffects(rt, Union{}, Effects(generic_isdefinedglobal_effects, nothrow=true))
end

effects = EFFECTS_TOTAL
exct = Union{}
isa(sym, Symbol) && (sym = GlobalRef(frame_module(sv), sym))
if isa(sym, GlobalRef)
rte = abstract_eval_globalref(interp, sym, saw_latestworld, sv)
partition = lookup_binding_partition!(interp, GlobalRef(mod, sym), sv)
if allow_import !== true && is_some_imported(binding_kind(partition))
if allow_import === false
rt = Const(false)
else
effects = Effects(generic_isdefinedglobal_effects, nothrow=true)
end
else
partition = walk_binding_partition!(interp, partition, sv)
rte = abstract_eval_partition_load(interp, partition)
if rte.exct == Union{}
rt = Const(true)
elseif rte.rt === Union{} && rte.exct === UndefVarError
rt = Const(false)
else
effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE)
effects = Effects(generic_isdefinedglobal_effects, nothrow=true)
end
end
return RTEffects(rt, Union{}, effects)
end

function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, @nospecialize(M), @nospecialize(s), @nospecialize(allow_import_arg), @nospecialize(order_arg), saw_latestworld::Bool, sv::AbsIntState)
exct = Bottom
allow_import = true
if allow_import_arg !== nothing
if !isa(allow_import_arg, Const)
allow_import = nothing
if widenconst(allow_import_arg) != Bool
exct = Union{exct, TypeError}
end
else
allow_import = allow_import_arg.val
end
end
if order_arg !== nothing
exct = global_order_exct(order_arg, #=loading=#true, #=storing=#false)
if !(isa(order_arg, Const) && get_atomic_order(order_arg.val, #=loading=#true, #=storing=#false).x >= MEMORY_ORDER_UNORDERED.x)
exct = Union{exct, ConcurrencyViolationError}
end
end
if M isa Const && s isa Const
M, s = M.val, s.val
if M isa Module && s isa Symbol
return merge_exct(CallMeta(abstract_eval_isdefinedglobal(interp, M, s, allow_import, saw_latestworld, sv), NoCallInfo()), exct)
end
elseif isexpr(sym, :static_parameter)
return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo())
elseif !hasintersect(widenconst(M), Module) || !hasintersect(widenconst(s), Symbol)
return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo())
elseif M Module && s Symbol
return CallMeta(Bool, Union{exct, UndefVarError}, generic_isdefinedglobal_effects, NoCallInfo())
end
return CallMeta(Bool, Union{exct, TypeError, UndefVarError}, generic_isdefinedglobal_effects, NoCallInfo())
end

function abstract_eval_isdefined(interp::AbstractInterpreter, @nospecialize(sym), saw_latestworld::Bool, sv::AbsIntState)
rt = Bool
effects = EFFECTS_TOTAL
exct = Union{}
if isexpr(sym, :static_parameter)
n = sym.args[1]::Int
if 1 <= n <= length(sv.sptypes)
sp = sv.sptypes[n]
Expand Down Expand Up @@ -3443,22 +3488,31 @@ function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode,
return partition_restriction(partition)
end

function abstract_eval_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
function lookup_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
force_binding_resolution!(g)
partition = lookup_binding_partition(get_inference_world(interp), g)
update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world))
partition
end

function walk_binding_partition!(interp::AbstractInterpreter, partition::Core.BindingPartition, sv::AbsIntState)
while is_some_imported(binding_kind(partition))
imported_binding = partition_restriction(partition)::Core.Binding
partition = lookup_binding_partition(get_inference_world(interp), imported_binding)
update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world))
end
return partition
end

function abstract_eval_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
partition = lookup_binding_partition!(interp, g, sv)
partition = walk_binding_partition!(interp, partition, sv)
return partition
end

function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Core.BindingPartition)
if is_some_guard(binding_kind(partition))
kind = binding_kind(partition)
if is_some_guard(kind) || kind == BINDING_KIND_UNDEF_CONST
if InferenceParams(interp).assume_bindings_static
return RTEffects(Union{}, UndefVarError, EFFECTS_THROWS)
else
Expand All @@ -3468,13 +3522,12 @@ function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Co
end
end

if is_some_const_binding(binding_kind(partition))
if is_defined_const_binding(kind)
rt = Const(partition_restriction(partition))
return RTEffects(rt, Union{}, Effects(EFFECTS_TOTAL, inaccessiblememonly=is_mutation_free_argtype(rt) ? ALWAYS_TRUE : ALWAYS_FALSE))
end

rt = partition_restriction(partition)

return RTEffects(rt, UndefVarError, generic_getglobal_effects)
end

Expand Down
4 changes: 2 additions & 2 deletions Compiler/src/ssair/verify.jl
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ function verify_ir(ir::IRCode, print::Bool=true,
@verify_error "Assignment should have been removed during SSA conversion"
raise_error()
elseif stmt.head === :isdefined
if length(stmt.args) > 2 || (length(stmt.args) == 2 && !isa(stmt.args[2], Bool))
if length(stmt.args) > 2
@verify_error "malformed isdefined"
raise_error()
end
Expand All @@ -382,7 +382,7 @@ function verify_ir(ir::IRCode, print::Bool=true,
elseif stmt.head === :foreigncall
isforeigncall = true
elseif stmt.head === :isdefined && length(stmt.args) == 1 &&
(stmt.args[1] isa GlobalRef || isexpr(stmt.args[1], :static_parameter))
isexpr(stmt.args[1], :static_parameter)
# a GlobalRef or static_parameter isdefined check does not evaluate its argument
continue
elseif stmt.head === :call
Expand Down
1 change: 1 addition & 0 deletions base/Base_compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ include("ordering.jl")
using .Order

include("coreir.jl")
include("invalidation.jl")

# For OS specific stuff
# We need to strcat things here, before strings are really defined
Expand Down
2 changes: 1 addition & 1 deletion base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export
fieldtype, getfield, setfield!, swapfield!, modifyfield!, replacefield!, setfieldonce!,
nfields, throw, tuple, ===, isdefined, eval,
# access to globals
getglobal, setglobal!, swapglobal!, modifyglobal!, replaceglobal!, setglobalonce!,
getglobal, setglobal!, swapglobal!, modifyglobal!, replaceglobal!, setglobalonce!, isdefinedglobal,
# ifelse, sizeof # not exported, to avoid conflicting with Base
# type reflection
<:, typeof, isa, typeassert,
Expand Down
7 changes: 5 additions & 2 deletions base/deepcopy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,14 @@ function _deepcopy_memory_t(@nospecialize(x::Memory), T, stackdict::IdDict)
end
return dest
end
@eval function deepcopy_internal(x::Array{T, N}, stackdict::IdDict) where {T, N}
function deepcopy_internal(x::Array{T, N}, stackdict::IdDict) where {T, N}
if haskey(stackdict, x)
return stackdict[x]::typeof(x)
end
stackdict[x] = $(Expr(:new, :(Array{T, N}), :(deepcopy_internal(x.ref, stackdict)), :(x.size)))
y = stackdict[x] = Array{T, N}(undef, ntuple(Returns(0), Val{N}()))
setfield!(y, :ref, deepcopy_internal(x.ref, stackdict))
setfield!(y, :size, x.size)
y
end
function deepcopy_internal(x::GenericMemoryRef, stackdict::IdDict)
if haskey(stackdict, x)
Expand Down
34 changes: 34 additions & 0 deletions base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2754,6 +2754,9 @@ compatible with the stores to that location. Otherwise, if not declared as
To test whether an array element is defined, use [`isassigned`](@ref) instead.
The global variable variant is supported for compatibility with older julia
releases. For new code, prefer [`isdefinedglobal`](@ref).
See also [`@isdefined`](@ref).
# Examples
Expand Down Expand Up @@ -2781,6 +2784,37 @@ false
"""
isdefined


"""
isdefinedglobal(m::Module, s::Symbol, [allow_import::Bool=true, [order::Symbol=:unordered]])
Tests whether a global variable `s` is defined in module `m` (in the current world age).
A variable is considered defined if and only if a value may be read from this global variable
and an access will not throw. This includes both constants and global variables that have
a value set.
If `allow_import` is `false`, the global variable must be defined inside `m`
and may not be imported from another module.
See also [`@isdefined`](@ref).
# Examples
```jldoctest
julia> isdefinedglobal(Base, :sum)
true
julia> isdefinedglobal(Base, :NonExistentMethod)
false
julia> isdefinedglobal(Base, :sum, false)
true
julia> isdefinedglobal(Main, :sum, false)
false
```
"""
isdefinedglobal

"""
Memory{T}(undef, n)
Expand Down
111 changes: 111 additions & 0 deletions base/invalidation.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

struct GlobalRefIterator
mod::Module
end
IteratorSize(::Type{GlobalRefIterator}) = SizeUnknown()
globalrefs(mod::Module) = GlobalRefIterator(mod)

function iterate(gri::GlobalRefIterator, i = 1)
m = gri.mod
table = ccall(:jl_module_get_bindings, Ref{SimpleVector}, (Any,), m)
i == length(table) && return nothing
b = table[i]
b === nothing && return iterate(gri, i+1)
return ((b::Core.Binding).globalref, i+1)
end

const TYPE_TYPE_MT = Type.body.name.mt
const NONFUNCTION_MT = Core.MethodTable.name.mt
function foreach_module_mtable(visit, m::Module, world::UInt)
for gb in globalrefs(m)
binding = gb.binding
bpart = lookup_binding_partition(world, binding)
if is_defined_const_binding(binding_kind(bpart))
v = partition_restriction(bpart)
uw = unwrap_unionall(v)
name = gb.name
if isa(uw, DataType)
tn = uw.name
if tn.module === m && tn.name === name && tn.wrapper === v && isdefined(tn, :mt)
# this is the original/primary binding for the type (name/wrapper)
mt = tn.mt
if mt !== nothing && mt !== TYPE_TYPE_MT && mt !== NONFUNCTION_MT
@assert mt.module === m
visit(mt) || return false
end
end
elseif isa(v, Module) && v !== m && parentmodule(v) === m && _nameof(v) === name
# this is the original/primary binding for the submodule
foreach_module_mtable(visit, v, world) || return false
elseif isa(v, Core.MethodTable) && v.module === m && v.name === name
# this is probably an external method table here, so let's
# assume so as there is no way to precisely distinguish them
visit(v) || return false
end
end
end
return true
end

function foreach_reachable_mtable(visit, world::UInt)
visit(TYPE_TYPE_MT) || return
visit(NONFUNCTION_MT) || return
for mod in loaded_modules_array()
foreach_module_mtable(visit, mod, world)
end
end

function should_invalidate_code_for_globalref(gr::GlobalRef, src::CodeInfo)
found_any = false
labelchangemap = nothing
stmts = src.code
isgr(g::GlobalRef) = gr.mod == g.mod && gr.name === g.name
isgr(g) = false
for i = 1:length(stmts)
stmt = stmts[i]
if isgr(stmt)
found_any = true
continue
end
for ur in Compiler.userefs(stmt)
arg = ur[]
# If any of the GlobalRefs in this stmt match the one that
# we are about, we need to move out all GlobalRefs to preserve
# effect order, in case we later invalidate a different GR
if isa(arg, GlobalRef)
if isgr(arg)
@assert !isa(stmt, PhiNode)
found_any = true
break
end
end
end
end
return found_any
end

function invalidate_code_for_globalref!(gr::GlobalRef, new_max_world::UInt)
valid_in_valuepos = false
foreach_reachable_mtable(new_max_world) do mt::Core.MethodTable
for method in MethodList(mt)
if isdefined(method, :source)
src = _uncompressed_ir(method)
old_stmts = src.code
if should_invalidate_code_for_globalref(gr, src)
for mi in specializations(method)
ci = mi.cache
while true
if ci.max_world > new_max_world
ccall(:jl_invalidate_code_instance, Cvoid, (Any, UInt), ci, new_max_world)
end
isdefined(ci, :next) || break
ci = ci.next
end
end
end
end
end
return true
end
end
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ Core.replacefield!
Core.swapfield!
Core.setfieldonce!
Core.isdefined
Core.isdefinedglobal
Base.@isdefined
Base.convert
Base.promote
Expand Down
Loading

0 comments on commit 827581d

Please sign in to comment.