diff --git a/src/abstractinterpret/abstractanalyzer.jl b/src/abstractinterpret/abstractanalyzer.jl index 696117a2c..aa115cbf6 100644 --- a/src/abstractinterpret/abstractanalyzer.jl +++ b/src/abstractinterpret/abstractanalyzer.jl @@ -84,7 +84,25 @@ struct AbstractBindingState AbstractBindingState(isconst::Bool, maybeundef::Bool, @nospecialize typ) = new(isconst, maybeundef, typ) AbstractBindingState(isconst::Bool, maybeundef::Bool) = new(isconst, maybeundef) end -const AbstractBindings = IdDict{Core.BindingPartition,AbstractBindingState} + +# `report_package` analyzes signatures concurrently while all task analyzers share a single +# `binding_states::AbstractBindings` (see `AnalyzerState`). Guard the underlying `IdDict` with +# a lock so concurrent `setindex!`/rehash cannot corrupt it; the accesses are infrequent +# (only on global binding assignments and binding-partition lookups), so the uncontended lock +# cost is negligible. +struct AbstractBindings + bindings::IdDict{Core.BindingPartition,AbstractBindingState} + lock::ReentrantLock + AbstractBindings() = new(IdDict{Core.BindingPartition,AbstractBindingState}(), ReentrantLock()) +end +@inline Base.getindex(b::AbstractBindings, partition::Core.BindingPartition) = + @lock b.lock b.bindings[partition] +@inline Base.setindex!(b::AbstractBindings, binding_state::AbstractBindingState, partition::Core.BindingPartition) = + (@lock b.lock b.bindings[partition] = binding_state; return b) +@inline Base.haskey(b::AbstractBindings, partition::Core.BindingPartition) = + @lock b.lock haskey(b.bindings, partition) +@inline Base.get(b::AbstractBindings, partition::Core.BindingPartition, @nospecialize(default)) = + @lock b.lock get(b.bindings, partition, default) """ mutable struct AnalyzerState diff --git a/src/abstractinterpret/typeinfer.jl b/src/abstractinterpret/typeinfer.jl index 032ffdf1e..44bcfbc65 100644 --- a/src/abstractinterpret/typeinfer.jl +++ b/src/abstractinterpret/typeinfer.jl @@ -558,18 +558,21 @@ function const_assignment_rt_exct(analyzer::ToplevelAbstractAnalyzer, sv::Infere if rt !== Union{} # `:const` assignment destructively overrides the binding type binding_states = get_binding_states(analyzer) - if !isconditional - binding_state = AbstractBindingState(true, false, new_binding_typ′[]) - elseif haskey(binding_states, partition) - old_binding_state = binding_states[partition] - @assert old_binding_state.isconst && isdefined(old_binding_state, :typ) - newmaybeundef = old_binding_state.maybeundef & isconditional - newtyp = old_binding_state.typ ⊔ new_binding_typ′[] - binding_state = AbstractBindingState(true, newmaybeundef, newtyp) - else - binding_state = AbstractBindingState(true, true, new_binding_typ′[]) + binding_state = @lock binding_states.lock begin + if !isconditional + new_state = AbstractBindingState(true, false, new_binding_typ′[]) + elseif haskey(binding_states, partition) + old_binding_state = binding_states[partition] + @assert old_binding_state.isconst && isdefined(old_binding_state, :typ) + newmaybeundef = old_binding_state.maybeundef & isconditional + newtyp = old_binding_state.typ ⊔ new_binding_typ′[] + new_state = AbstractBindingState(true, newmaybeundef, newtyp) + else + new_state = AbstractBindingState(true, true, new_binding_typ′[]) + end + binding_states[partition] = new_state + new_state end - binding_states[partition] = binding_state # HACK/FIXME Concretize `AbstractBindingState` # For top-level analysis implementation reasons, we actually define this # `AbstractBindingState` in the analyzed module’s namespace.