@@ -294,6 +294,34 @@ const JET_LOGGER_LEVELS_DESC = let
294294end
295295jet_logger_level (@nospecialize io:: IO ) = get (io, JET_LOGGER_LEVEL, DEFAULT_LOGGER_LEVEL):: Int
296296
297+ # multithreading
298+
299+ """
300+ CASDict{K,V}
301+
302+ A thread-safe dictionary using Compare-And-Swap (CAS) operations for lock-free updates.
303+ Reads are always lock-free via atomic load. Writes use a CAS retry loop, making this
304+ suitable for lightweight, pure update functions that are safe to retry.
305+
306+ Currently implements [`get!`](@ref) only.
307+ """
308+ mutable struct CASDict{K,V}
309+ @atomic data:: Dict{K,V}
310+ CASDict {K,V} () where {K,V} = new {K,V} (Dict {K,V} ())
311+ end
312+
313+ function Base. get! (f:: Base.Callable , d:: CASDict{K,V} , key:: K ) where {K,V}
314+ old = @atomic :acquire d. data
315+ val = get (old, key, nothing )
316+ val != = nothing && return val:: V
317+ while true
318+ new = copy (old)
319+ val = get! (f, new, key):: V
320+ old, success = @atomicreplace :acquire_release :monotonic d. data old => new
321+ success && return val
322+ end
323+ end
324+
297325# analysis core
298326# =============
299327
@@ -327,6 +355,20 @@ Prints a report of the top-level error `report` to the given `io`.
327355"""
328356function print_report end
329357
358+ mutable struct PackageAnalysisProgress
359+ const reports:: Vector{InferenceErrorReport}
360+ const reports_lock:: ReentrantLock
361+ @atomic done:: Int
362+ @atomic analyzed:: Int
363+ @atomic cached:: Int
364+ const interval:: Int
365+ @atomic next_interval:: Int
366+ function PackageAnalysisProgress (n_sigs:: Int )
367+ interval = max (n_sigs ÷ 25 , 1 )
368+ new (InferenceErrorReport[], ReentrantLock (), 0 , 0 , 0 , interval, interval)
369+ end
370+ end
371+
330372include (" toplevel/virtualprocess.jl" )
331373
332374# results
@@ -968,6 +1010,11 @@ struct SigAnalysisResult
9681010 codeinst:: CodeInstance
9691011end
9701012
1013+ struct SigWorkItem
1014+ siginfos:: Vector{Revise.SigInfo}
1015+ index:: Int
1016+ end
1017+
9711018"""
9721019 analyze_and_report_package!(analyzer::AbstractAnalyzer, package::Module; jetconfigs...) -> JETToplevelResult
9731020
@@ -991,7 +1038,6 @@ function analyze_and_report_package!(analyzer::AbstractAnalyzer, pkgmod::Module;
9911038 end
9921039
9931040 start = time ()
994- counter, analyzed, cached = Ref (0 ), Ref (0 ), Ref (0 )
9951041 res = VirtualProcessResult (nothing )
9961042 jetconfigs = set_if_missing (jetconfigs, :toplevel_logger , IOContext (stdout , JET_LOGGER_LEVEL => DEFAULT_LOGGER_LEVEL))
9971043 config = ToplevelConfig (; jetconfigs... )
@@ -1001,66 +1047,89 @@ function analyze_and_report_package!(analyzer::AbstractAnalyzer, pkgmod::Module;
10011047 newstate = AnalyzerState (AnalyzerState (analyzer); world= Base. get_world_counter ())
10021048 analyzer = AbstractAnalyzer (analyzer, newstate)
10031049
1004- n_sigs = 0
1005- for fi in pkgdata. fileinfos, (_, exsigs) in fi. modexsigs, (_, siginfos) in exsigs
1006- isnothing (siginfos) && continue
1007- n_sigs += length (siginfos)
1008- end
1050+ workitems = SigWorkItem[]
10091051 for fi in pkgdata. fileinfos, (_, exsigs) in fi. modexsigs, (_, siginfos) in exsigs
10101052 isnothing (siginfos) && continue
10111053 for (i, siginfo) in enumerate (siginfos)
1012- toplevel_logger (config) do @nospecialize (io:: IO )
1013- clearline (io)
1014- end
1015- counter[] += 1
1016- inf_world = CC. get_inference_world (analyzer)
1054+ push! (workitems, SigWorkItem (siginfos, i))
1055+ end
1056+ end
1057+
1058+ n_sigs = length (workitems)
1059+ progress = PackageAnalysisProgress (n_sigs)
1060+ inf_world = CC. get_inference_world (analyzer)
1061+
1062+ toplevel_logger (config) do @nospecialize (io:: IO )
1063+ print (io, " Analyzing top-level definitions (progress: 0/$n_sigs | interval: $(progress. interval) )" )
1064+ end
1065+
1066+ tasks = map (workitems) do workitem
1067+ (; siginfos, index) = workitem
1068+ siginfo = siginfos[index]
1069+ Threads. @spawn :default try
10171070 ext = Revise. get_extended_data (siginfo, :JET )
1071+ local reports:: Vector{InferenceErrorReport}
10181072 if ext != = nothing && ext. data isa SigAnalysisResult
10191073 prev_result = ext. data:: SigAnalysisResult
10201074 if (CC. cache_owner (analyzer) === prev_result. codeinst. owner &&
10211075 prev_result. codeinst. max_world ≥ inf_world ≥ prev_result. codeinst. min_world)
1022- toplevel_logger (config) do @nospecialize (io:: IO )
1023- (counter[] == n_sigs ? println : print)(io, " Skipped analysis for cached definition ($(counter[]) /$n_sigs )" )
1024- end
1025- cached[] += 1
1076+ @atomic progress. cached += 1
10261077 reports = prev_result. reports
10271078 @goto gotreports
10281079 end
10291080 end
1081+ # Create a new analyzer with fresh local caches (`inf_cache` and `analysis_results`)
1082+ # to avoid data races between concurrent signature analysis tasks
1083+ task_analyzer = AbstractAnalyzer (analyzer,
1084+ AnalyzerState (AnalyzerState (analyzer), #= refresh_local_cache=# true ))
10301085 match = Base. _which (siginfo. sig;
1031- method_table = CC. method_table (analyzer ),
1086+ method_table = CC. method_table (task_analyzer ),
10321087 world = inf_world,
10331088 raise = false )
10341089 if match != = nothing
1035- toplevel_logger (config; pre= clearline) do @nospecialize (io:: IO )
1036- if jet_logger_level (io) ≥ JET_LOGGER_LEVEL_DEBUG
1037- print (io, " Analyzing top-level definition `" )
1038- Base. show_tuple_as_call (io, Symbol (" " ), siginfo. sig)
1039- print (io, " ` (progress: $(counter[]) /$n_sigs )" )
1040- else
1041- print (io, " Analyzing top-level definition (progress: $(counter[]) /$n_sigs )" )
1042- end
1043- end
1044- result = analyze_method_signature! (analyzer,
1090+ result = analyze_method_signature! (task_analyzer,
10451091 match. method, match. spec_types, match. sparams)
1046- analyzed[] += 1
1047- reports = get_reports (analyzer, result)
1048- siginfos[i] = Revise. replace_extended_data (siginfo, :JET , SigAnalysisResult (reports, result. ci))
1049- @label gotreports
1050- append! (res. inference_error_reports, reports)
1092+ @atomic progress. analyzed += 1
1093+ reports = get_reports (task_analyzer, result)
1094+ siginfos[index] = Revise. replace_extended_data (siginfo, :JET , SigAnalysisResult (reports, result. ci))
10511095 else
1052- toplevel_logger (config) do @nospecialize (io:: IO )
1096+ toplevel_logger (config; pre = println ) do @nospecialize (io:: IO )
10531097 print (io, " Couldn't find a single matching method for the signature `" )
10541098 Base. show_tuple_as_call (io, Symbol (" " ), siginfo. sig)
1055- println (io, " ` (progress: $(counter[]) /$n_sigs )" )
1099+ println (io, " `" )
1100+ end
1101+ reports = InferenceErrorReport[]
1102+ end
1103+ @label gotreports
1104+ isempty (reports) || @lock progress. reports_lock append! (progress. reports, reports)
1105+ catch err
1106+ @error " Error analyzing method signature" siginfo. sig
1107+ Base. showerror (stderr , err, catch_backtrace ())
1108+ finally
1109+ done = (@atomic progress. done += 1 )
1110+ current_next = @atomic progress. next_interval
1111+ if done >= current_next
1112+ @atomicreplace progress. next_interval current_next => current_next + progress. interval
1113+ toplevel_logger (config; pre= clearline) do @nospecialize (io:: IO )
1114+ print (io, " Analyzing top-level definitions (progress: $done /$n_sigs )" )
10561115 end
10571116 end
10581117 end
10591118 end
10601119
1120+ waitall (tasks)
1121+
1122+ append! (res. inference_error_reports, progress. reports)
1123+
1124+ toplevel_logger (config; pre= clearline) do @nospecialize (io:: IO )
1125+ done = @atomic progress. done
1126+ print (io, " Analyzing top-level definitions (progress: $done /$n_sigs )" )
1127+ end
10611128 toplevel_logger (config; pre= println) do @nospecialize (io:: IO )
10621129 sec = round (time () - start; digits = 3 )
1063- println (io, " Analyzed all top-level definitions (all: $(counter[]) | analyzed: $(analyzed[]) | cached: $(cached[]) | took: $sec sec)" )
1130+ analyzed = @atomic progress. analyzed
1131+ cached = @atomic progress. cached
1132+ println (io, " Analyzed all top-level definitions (all: $n_sigs | analyzed: $analyzed | cached: $cached | took: $sec sec)" )
10641133 end
10651134
10661135 unique! (aggregation_policy (analyzer), res. inference_error_reports)
0 commit comments