Skip to content

Commit

Permalink
Merge branch 'master' into gb/sched-refact
Browse files Browse the repository at this point in the history
  • Loading branch information
gbaraldi authored Jan 27, 2025
2 parents f5d2743 + 0625b3a commit ca21e01
Show file tree
Hide file tree
Showing 41 changed files with 404 additions and 220 deletions.
3 changes: 2 additions & 1 deletion Compiler/src/Compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ 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, Base, BitVector, Bottom, Callable, DataTypeFieldDesc,
BINDING_KIND_GLOBAL, BINDING_KIND_UNDEF_CONST, BINDING_KIND_BACKDATED_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 Down
5 changes: 5 additions & 0 deletions Compiler/src/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3524,6 +3524,11 @@ function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Co
end

if is_defined_const_binding(kind)
if kind == BINDING_KIND_BACKDATED_CONST
# Infer this as guard. We do not want a later const definition to retroactively improve
# inference results in an earlier world.
return RTEffects(Any, UndefVarError, generic_getglobal_effects)
end
rt = Const(partition_restriction(partition))
return RTEffects(rt, Union{}, Effects(EFFECTS_TOTAL, inaccessiblememonly=is_mutation_free_argtype(rt) ? ALWAYS_TRUE : ALWAYS_FALSE))
end
Expand Down
4 changes: 0 additions & 4 deletions Compiler/src/ssair/slot2ssa.jl
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,6 @@ function fixemup!(@specialize(slot_filter), @specialize(rename_slot), ir::IRCode
return nothing
end
op[] = x
elseif isa(val, GlobalRef) && !(isdefined(val.mod, val.name) && isconst(val.mod, val.name))
typ = typ_for_val(val, ci, ir, idx, Any[])
new_inst = NewInstruction(val, typ)
op[] = NewSSAValue(insert_node!(ir, idx, new_inst).id - length(ir.stmts))
elseif isexpr(val, :static_parameter)
ty = typ_for_val(val, ci, ir, idx, Any[])
if isa(ty, Const)
Expand Down
11 changes: 10 additions & 1 deletion Compiler/src/ssair/verify.jl
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ function verify_ir(ir::IRCode, print::Bool=true,
if mi !== nothing
push!(error_args, "\n", " Method instance: ", mi)
end
error(error_args...)
invokelatest(error, error_args...)
end
# Verify CFG graph. Must be well formed to construct domtree
if !(length(ir.cfg.blocks) - 1 <= length(ir.cfg.index) <= length(ir.cfg.blocks))
Expand Down Expand Up @@ -380,6 +380,15 @@ function verify_ir(ir::IRCode, print::Bool=true,
# undefined GlobalRef is OK in isdefined
continue
end
elseif stmt.head === :throw_undef_if_not
if length(stmt.args) > 3
@verify_error "malformed throw_undef_if_not"
raise_error()
end
if stmt.args[1] isa GlobalRef
# undefined GlobalRef is OK in throw_undef_if_not
continue
end
elseif stmt.head === :gc_preserve_end
# We allow gc_preserve_end tokens to span across try/catch
# blocks, which isn't allowed for regular SSA values, so
Expand Down
2 changes: 1 addition & 1 deletion Compiler/test/invalidation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ let mi = Base.method_instance(basic_caller, (Float64,))
end

# this redefinition below should invalidate the cache
const BASIC_CALLER_WORLD = Base.get_world_counter()
const BASIC_CALLER_WORLD = Base.get_world_counter()+1
basic_callee(x) = x, x
@test !isdefined(Base.method_instance(basic_callee, (Float64,)), :cache)
let mi = Base.method_instance(basic_caller, (Float64,))
Expand Down
2 changes: 2 additions & 0 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ include("sysinfo.jl")
include("libc.jl")
using .Libc: getpid, gethostname, time, memcpy, memset, memmove, memcmp

const USING_STOCK_GC = occursin("stock", GC.gc_active_impl())

# These used to be in build_h.jl and are retained for backwards compatibility.
# NOTE: keep in sync with `libblastrampoline_jll.libblastrampoline`.
const libblas_name = "libblastrampoline" * (Sys.iswindows() ? "-5" : "")
Expand Down
4 changes: 4 additions & 0 deletions base/Base_compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ using .Order
include("coreir.jl")
include("invalidation.jl")

# Because lowering inserts direct references, it is mandatory for this binding
# to exist before we start inferring code.
function string end

# For OS specific stuff
# We need to strcat things here, before strings are really defined
function strcat(x::String, y::String)
Expand Down
8 changes: 5 additions & 3 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,10 @@ struct StackOverflowError <: Exception end
struct UndefRefError <: Exception end
struct UndefVarError <: Exception
var::Symbol
world::UInt
scope # a Module or Symbol or other object describing the context where this variable was looked for (e.g. Main or :local or :static_parameter)
UndefVarError(var::Symbol) = new(var)
UndefVarError(var::Symbol, @nospecialize scope) = new(var, scope)
UndefVarError(var::Symbol) = new(var, ccall(:jl_get_tls_world_age, UInt, ()))
UndefVarError(var::Symbol, @nospecialize scope) = new(var, ccall(:jl_get_tls_world_age, UInt, ()), scope)
end
struct ConcurrencyViolationError <: Exception
msg::AbstractString
Expand Down Expand Up @@ -717,7 +718,8 @@ macro __doc__(x)
end

isbasicdoc(@nospecialize x) = (isa(x, Expr) && x.head === :.) || isa(x, Union{QuoteNode, Symbol})
iscallexpr(ex::Expr) = (isa(ex, Expr) && ex.head === :where) ? iscallexpr(ex.args[1]) : (isa(ex, Expr) && ex.head === :call)
firstarg(arg1, args...) = arg1
iscallexpr(ex::Expr) = (isa(ex, Expr) && ex.head === :where) ? iscallexpr(firstarg(ex.args...)) : (isa(ex, Expr) && ex.head === :call)
iscallexpr(ex) = false
function ignoredoc(source, mod, str, expr)
(isbasicdoc(expr) || iscallexpr(expr)) && return Expr(:escape, nothing)
Expand Down
90 changes: 3 additions & 87 deletions base/condition.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,104 +125,20 @@ proceeding.
"""
function wait end

# wait with timeout
#
# The behavior of wait changes if a timeout is specified. There are
# three concurrent entities that can interact:
# 1. Task W: the task that calls wait w/timeout.
# 2. Task T: the task created to handle a timeout.
# 3. Task N: the task that notifies the Condition being waited on.
#
# Typical flow:
# - W enters the Condition's wait queue.
# - W creates T and stops running (calls wait()).
# - T, when scheduled, waits on a Timer.
# - Two common outcomes:
# - N notifies the Condition.
# - W starts running, closes the Timer, sets waiter_left and returns
# the notify'ed value.
# - The closed Timer throws an EOFError to T which simply ends.
# - The Timer expires.
# - T starts running and locks the Condition.
# - T confirms that waiter_left is unset and that W is still in the
# Condition's wait queue; it then removes W from the wait queue,
# sets dosched to true and unlocks the Condition.
# - If dosched is true, T schedules W with the special :timed_out
# value.
# - T ends.
# - W runs and returns :timed_out.
#
# Some possible interleavings:
# - N notifies the Condition but the Timer expires and T starts running
# before W:
# - W closing the expired Timer is benign.
# - T will find that W is no longer in the Condition's wait queue
# (which is protected by a lock) and will not schedule W.
# - N notifies the Condition; W runs and calls wait on the Condition
# again before the Timer expires:
# - W sets waiter_left before leaving. When T runs, it will find that
# waiter_left is set and will not schedule W.
#
# The lock on the Condition's wait queue and waiter_left together
# ensure proper synchronization and behavior of the tasks involved.

"""
wait(c::GenericCondition; first::Bool=false, timeout::Real=0.0)
wait(c::GenericCondition; first::Bool=false)
Wait for [`notify`](@ref) on `c` and return the `val` parameter passed to `notify`.
If the keyword `first` is set to `true`, the waiter will be put _first_
in line to wake up on `notify`. Otherwise, `wait` has first-in-first-out (FIFO) behavior.
If `timeout` is specified, cancel the `wait` when it expires and return
`:timed_out`. The minimum value for `timeout` is 0.001 seconds, i.e. 1
millisecond.
"""
function wait(c::GenericCondition; first::Bool=false, timeout::Real=0.0)
timeout == 0.0 || timeout 1e-3 || throw(ArgumentError("timeout must be ≥ 1 millisecond"))

function wait(c::GenericCondition; first::Bool=false)
ct = current_task()
_wait2(c, ct, first)
token = unlockall(c.lock)

timer::Union{Timer, Nothing} = nothing
waiter_left::Union{Threads.Atomic{Bool}, Nothing} = nothing
if timeout > 0.0
timer = Timer(timeout)
waiter_left = Threads.Atomic{Bool}(false)
# start a task to wait on the timer
t = Task() do
try
wait(timer)
catch e
# if the timer was closed, the waiting task has been scheduled; do nothing
e isa EOFError && return
end
dosched = false
lock(c.lock)
# Confirm that the waiting task is still in the wait queue and remove it. If
# the task is not in the wait queue, it must have been notified already so we
# don't do anything here.
if !waiter_left[] && ct.queue == c.waitq
dosched = true
Base.list_deletefirst!(c.waitq, ct)
end
unlock(c.lock)
# send the waiting task a timeout
dosched && schedule(ct, :timed_out)
end
t.sticky = false
Threads._spawn_set_thrpool(t, :interactive)
schedule(t)
end

try
res = wait()
if timer !== nothing
close(timer)
waiter_left[] = true
end
return res
return wait()
catch
q = ct.queue; q === nothing || Base.list_deletefirst!(q::IntrusiveLinkedList{Task}, ct)
rethrow()
Expand Down
4 changes: 2 additions & 2 deletions base/docs/bindings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ end

bindingexpr(x) = Expr(:call, Binding, splitexpr(x)...)

defined(b::Binding) = isdefined(b.mod, b.var)
resolve(b::Binding) = getfield(b.mod, b.var)
defined(b::Binding) = invokelatest(isdefined, b.mod, b.var)
resolve(b::Binding) = invokelatest(getfield, b.mod, b.var)

function splitexpr(x::Expr)
isexpr(x, :macrocall) ? splitexpr(x.args[1]) :
Expand Down
109 changes: 109 additions & 0 deletions base/experimental.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
module Experimental

using Base: Threads, sync_varname, is_function_def, @propagate_inbounds
using Base: GenericCondition
using Base.Meta

"""
Expand Down Expand Up @@ -577,4 +578,112 @@ function task_wall_time_ns(t::Task=current_task())
return end_at - start_at
end

# wait_with_timeout
#
# A version of `wait(c::Condition)` that additionally allows the
# specification of a timeout. This is experimental as it will likely
# be dropped when a cancellation framework is added.
#
# The parallel behavior of wait_with_timeout is specified here. There
# are three concurrent entities that can interact:
# 1. Task W: the task that calls wait_with_timeout.
# 2. Task T: the task created to handle a timeout.
# 3. Task N: the task that notifies the Condition being waited on.
#
# Typical flow:
# - W enters the Condition's wait queue.
# - W creates T and stops running (calls wait()).
# - T, when scheduled, waits on a Timer.
# - Two common outcomes:
# - N notifies the Condition.
# - W starts running, closes the Timer, sets waiter_left and returns
# the notify'ed value.
# - The closed Timer throws an EOFError to T which simply ends.
# - The Timer expires.
# - T starts running and locks the Condition.
# - T confirms that waiter_left is unset and that W is still in the
# Condition's wait queue; it then removes W from the wait queue,
# sets dosched to true and unlocks the Condition.
# - If dosched is true, T schedules W with the special :timed_out
# value.
# - T ends.
# - W runs and returns :timed_out.
#
# Some possible interleavings:
# - N notifies the Condition but the Timer expires and T starts running
# before W:
# - W closing the expired Timer is benign.
# - T will find that W is no longer in the Condition's wait queue
# (which is protected by a lock) and will not schedule W.
# - N notifies the Condition; W runs and calls wait on the Condition
# again before the Timer expires:
# - W sets waiter_left before leaving. When T runs, it will find that
# waiter_left is set and will not schedule W.
#
# The lock on the Condition's wait queue and waiter_left together
# ensure proper synchronization and behavior of the tasks involved.

"""
wait_with_timeout(c::GenericCondition; first::Bool=false, timeout::Real=0.0)
Wait for [`notify`](@ref) on `c` and return the `val` parameter passed to `notify`.
If the keyword `first` is set to `true`, the waiter will be put _first_
in line to wake up on `notify`. Otherwise, `wait` has first-in-first-out (FIFO) behavior.
If `timeout` is specified, cancel the `wait` when it expires and return
`:timed_out`. The minimum value for `timeout` is 0.001 seconds, i.e. 1
millisecond.
"""
function wait_with_timeout(c::GenericCondition; first::Bool=false, timeout::Real=0.0)
ct = current_task()
Base._wait2(c, ct, first)
token = Base.unlockall(c.lock)

timer::Union{Timer, Nothing} = nothing
waiter_left::Union{Threads.Atomic{Bool}, Nothing} = nothing
if timeout > 0.0
timer = Timer(timeout)
waiter_left = Threads.Atomic{Bool}(false)
# start a task to wait on the timer
t = Task() do
try
wait(timer)
catch e
# if the timer was closed, the waiting task has been scheduled; do nothing
e isa EOFError && return
end
dosched = false
lock(c.lock)
# Confirm that the waiting task is still in the wait queue and remove it. If
# the task is not in the wait queue, it must have been notified already so we
# don't do anything here.
if !waiter_left[] && ct.queue == c.waitq
dosched = true
Base.list_deletefirst!(c.waitq, ct)
end
unlock(c.lock)
# send the waiting task a timeout
dosched && schedule(ct, :timed_out)
end
t.sticky = false
Threads._spawn_set_thrpool(t, :interactive)
schedule(t)
end

try
res = wait()
if timer !== nothing
close(timer)
waiter_left[] = true
end
return res
catch
q = ct.queue; q === nothing || Base.list_deletefirst!(q::IntrusiveLinkedList{Task}, ct)
rethrow()
finally
Base.relockall(c.lock, token)
end
end

end # module
11 changes: 11 additions & 0 deletions base/gcutils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,15 @@ function logging_enabled()
ccall(:jl_is_gc_logging_enabled, Cint, ()) != 0
end

"""
GC.gc_active_impl()
Return a string stating which GC implementation is being used and possibly
its version according to the list of supported GCs
"""
function gc_active_impl()
unsafe_string(ccall(:jl_gc_active_impl, Ptr{UInt8}, ()))
end


end # module GC
3 changes: 2 additions & 1 deletion base/runtime_internals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,9 @@ const BINDING_KIND_FAILED = 0x6
const BINDING_KIND_DECLARED = 0x7
const BINDING_KIND_GUARD = 0x8
const BINDING_KIND_UNDEF_CONST = 0x9
const BINDING_KIND_BACKDATED_CONST = 0xa

is_defined_const_binding(kind::UInt8) = (kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT)
is_defined_const_binding(kind::UInt8) = (kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT || kind == BINDING_KIND_BACKDATED_CONST)
is_some_const_binding(kind::UInt8) = (is_defined_const_binding(kind) || kind == BINDING_KIND_UNDEF_CONST)
is_some_imported(kind::UInt8) = (kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_EXPLICIT || kind == BINDING_KIND_IMPORTED)
is_some_guard(kind::UInt8) = (kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED || kind == BINDING_KIND_FAILED || kind == BINDING_KIND_UNDEF_CONST)
Expand Down
Loading

0 comments on commit ca21e01

Please sign in to comment.