File 3956-dialyzer-Parallelize-remote-types-processing-in-cont.patch of Package erlang
From 12f0567f701a2ade95b528cc64e69713583e19ba Mon Sep 17 00:00:00 2001
From: Hans Bolinder <hasse@erlang.org>
Date: Sun, 1 Aug 2021 09:09:44 +0200
Subject: [PATCH 6/7] dialyzer: Parallelize remote types processing in
contracts
This has effect if many processes can run i parallel.
---
lib/dialyzer/src/dialyzer_codeserver.erl | 19 ++--
lib/dialyzer/src/dialyzer_contracts.erl | 108 ++++++++++++++++------
lib/dialyzer/src/dialyzer_coordinator.erl | 31 +++++--
lib/dialyzer/src/dialyzer_utils.erl | 2 +-
lib/dialyzer/src/dialyzer_worker.erl | 12 ++-
lib/dialyzer/src/erl_types.erl | 15 +--
6 files changed, 134 insertions(+), 53 deletions(-)
diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl
index 0472e2c128..b25ea791d2 100644
--- a/lib/dialyzer/src/dialyzer_codeserver.erl
+++ b/lib/dialyzer/src/dialyzer_codeserver.erl
@@ -29,7 +29,7 @@
finalize_records/1,
get_contracts/1,
get_callbacks/1,
- get_exported_types/1,
+ get_exported_types_table/1,
extract_exported_types/1,
get_exports/1,
get_records_table/1,
@@ -132,12 +132,15 @@ new() ->
CodeOptions = [compressed, public, {read_concurrency, true}],
Code = ets:new(dialyzer_codeserver_code, CodeOptions),
ReadOptions = [compressed, {read_concurrency, true}],
- [Contracts, Callbacks, Records, ExportedTypes] =
+ [Records, ExportedTypes] =
[ets:new(Name, ReadOptions) ||
- Name <- [dialyzer_codeserver_contracts,
- dialyzer_codeserver_callbacks,
- dialyzer_codeserver_records,
+ Name <- [dialyzer_codeserver_records,
dialyzer_codeserver_exported_types]],
+ ReadWriteOptions = [public | ReadOptions],
+ [Contracts, Callbacks] =
+ [ets:new(Name, ReadWriteOptions) ||
+ Name <- [dialyzer_codeserver_contracts,
+ dialyzer_codeserver_callbacks]],
TempOptions = [public, {write_concurrency, true}],
[Exports, FunMetaInfo, TempExportedTypes, TempRecords, TempContracts,
TempCallbacks] =
@@ -211,10 +214,10 @@ insert_fun_meta_info(List, #codeserver{fun_meta_info = FunMetaInfo} = CS) ->
is_exported(MFA, #codeserver{exports = Exports}) ->
ets_set_is_element(MFA, Exports).
--spec get_exported_types(codeserver()) -> sets:set(mfa()).
+-spec get_exported_types_table(codeserver()) -> map_ets().
-get_exported_types(#codeserver{exported_types = ExpTypes}) ->
- ets_set_to_set(ExpTypes).
+get_exported_types_table(#codeserver{exported_types = ExpTypes}) ->
+ ExpTypes.
-spec extract_exported_types(codeserver()) -> {codeserver(), set_ets()}.
diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl
index 5adcc78a0e..e434c64e4e 100644
--- a/lib/dialyzer/src/dialyzer_contracts.erl
+++ b/lib/dialyzer/src/dialyzer_contracts.erl
@@ -27,12 +27,23 @@
process_contract_remote_types/1,
store_tmp_contract/6]).
--export_type([file_contract/0, plt_contracts/0]).
+%% For dialyzer_worker.
+-export([process_contract_remote_types_module/2]).
+
+-export_type([file_contract/0, plt_contracts/0,
+ contract_remote_types_init_data/0,
+ contract_remote_types_result/0]).
%%-----------------------------------------------------------------------
-include("dialyzer.hrl").
+-type ext_types_message() :: {pid(), 'ext_types',
+ {mfa(), {file:filename(), erl_anno:location()}}}
+ | {'error', io_lib:chars()}.
+-type contract_remote_types_init_data() :: dialyzer_codeserver:codeserver().
+-type contract_remote_types_result() :: [ext_types_message()].
+
%%-----------------------------------------------------------------------
%% Types used in other parts of the system below
%%-----------------------------------------------------------------------
@@ -143,35 +154,76 @@ sequence([H|T], Delimiter) -> H ++ Delimiter ++ sequence(T, Delimiter).
dialyzer_codeserver:codeserver().
process_contract_remote_types(CodeServer) ->
- Mods = dialyzer_codeserver:all_temp_modules(CodeServer),
+ case dialyzer_codeserver:all_temp_modules(CodeServer) of
+ [] ->
+ CodeServer;
+ Mods ->
+ %% CodeServer is updated by each worker, but is still valid
+ %% after updates. Workers call
+ %% process_contract_remote_types_module/2 below.
+ Return =
+ dialyzer_coordinator:parallel_job(contract_remote_types,
+ Mods,
+ _InitData=CodeServer,
+ _Timing=none),
+ %% We need to pass on messages and thrown errors from erl_types:
+ _ = [self() ! {self(), ext_types, ExtType} ||
+ {_, ext_types, ExtType} <- Return],
+ case [Error || {error, _} = Error <- Return] of
+ [] ->
+ dialyzer_codeserver:finalize_contracts(CodeServer);
+ [Error | _] ->
+ throw(Error)
+ end
+ end.
+
+-spec process_contract_remote_types_module(module(),
+ dialyzer_codeserver:codeserver()) -> [ext_types_message()].
+
+process_contract_remote_types_module(ModuleName, CodeServer) ->
RecordTable = dialyzer_codeserver:get_records_table(CodeServer),
- ExpTypes = dialyzer_codeserver:get_exported_types(CodeServer),
- ModuleFun =
- fun(ModuleName) ->
- ContractFun =
- fun({MFA, {File, TmpContract, Xtra}}, C0) ->
- #tmp_contract{contract_funs = CFuns, forms = Forms} = TmpContract,
- {NewCs, C2} = lists:mapfoldl(fun(CFun, C1) ->
- CFun(ExpTypes, RecordTable, C1)
- end, C0, CFuns),
- Args = general_domain(NewCs),
- Contract = #contract{contracts = NewCs, args = Args, forms = Forms},
- {{MFA, {File, Contract, Xtra}}, C2}
- end,
- Cache = erl_types:cache__new(),
- {ContractMap, CallbackMap} =
- dialyzer_codeserver:get_temp_contracts(ModuleName, CodeServer),
- {NewContractList, Cache1} =
- lists:mapfoldl(ContractFun, Cache, maps:to_list(ContractMap)),
- {NewCallbackList, _NewCache} =
- lists:mapfoldl(ContractFun, Cache1, maps:to_list(CallbackMap)),
- dialyzer_codeserver:store_contracts(ModuleName,
- maps:from_list(NewContractList),
- maps:from_list(NewCallbackList),
- CodeServer)
+ ExpTypes = dialyzer_codeserver:get_exported_types_table(CodeServer),
+ ContractFun =
+ fun({MFA, {File, TmpContract, Xtra}}, C0) ->
+ #tmp_contract{contract_funs = CFuns, forms = Forms} = TmpContract,
+ {NewCs, C2} = lists:mapfoldl(fun(CFun, C1) ->
+ CFun(ExpTypes, RecordTable, C1)
+ end, C0, CFuns),
+ Args = general_domain(NewCs),
+ Contract = #contract{contracts = NewCs, args = Args, forms = Forms},
+ {{MFA, {File, Contract, Xtra}}, C2}
end,
- lists:foreach(ModuleFun, Mods),
- dialyzer_codeserver:finalize_contracts(CodeServer).
+ Cache = erl_types:cache__new(),
+ {ContractMap, CallbackMap} =
+ dialyzer_codeserver:get_temp_contracts(ModuleName, CodeServer),
+ try
+ {NewContractList, Cache1} =
+ lists:mapfoldl(ContractFun, Cache, maps:to_list(ContractMap)),
+ {NewCallbackList, _NewCache} =
+ lists:mapfoldl(ContractFun, Cache1, maps:to_list(CallbackMap)),
+ _NewCodeServer =
+ dialyzer_codeserver:store_contracts(ModuleName,
+ maps:from_list(NewContractList),
+ maps:from_list(NewCallbackList),
+ CodeServer),
+ rcv_ext_types()
+ catch
+ throw:{error, _}=Error ->
+ [Error] ++ rcv_ext_types()
+ end.
+
+rcv_ext_types() ->
+ Self = self(),
+ Self ! {Self, done},
+ rcv_ext_types(Self, []).
+
+rcv_ext_types(Self, ExtTypes) ->
+ receive
+ {Self, ext_types, _} = ExtType ->
+ rcv_ext_types(Self, [ExtType | ExtTypes]);
+ {Self, done} ->
+ lists:usort(ExtTypes)
+ end.
-type fun_types() :: orddict:orddict(label(), erl_types:erl_type()).
diff --git a/lib/dialyzer/src/dialyzer_coordinator.erl b/lib/dialyzer/src/dialyzer_coordinator.erl
index 7c1bc1de5a..f8a078364b 100644
--- a/lib/dialyzer/src/dialyzer_coordinator.erl
+++ b/lib/dialyzer/src/dialyzer_coordinator.erl
@@ -43,34 +43,44 @@
-type timing() :: dialyzer_timing:timing_server().
-type scc() :: [mfa_or_funlbl()].
--type mode() :: 'typesig' | 'dataflow' | 'compile' | 'warnings'.
+-type mode() :: 'typesig' | 'dataflow' | 'compile' | 'warnings' |
+ 'contract_remote_types'.
-type compile_job() :: file:filename().
-type typesig_job() :: scc().
-type dataflow_job() :: module().
-type warnings_job() :: module().
+-type contract_remote_types_job() :: module().
--type job() :: compile_job() | typesig_job() | dataflow_job() | warnings_job().
+-type job() :: compile_job() | typesig_job() | dataflow_job() |
+ warnings_job() | contract_remote_types_job().
-type compile_init_data() :: dialyzer_analysis_callgraph:compile_init_data().
-type typesig_init_data() :: dialyzer_succ_typings:typesig_init_data().
-type dataflow_init_data() :: dialyzer_succ_typings:dataflow_init_data().
-type warnings_init_data() :: dialyzer_succ_typings:warnings_init_data().
+-type contract_remote_types_init_data() ::
+ dialyzer_contracts:contract_remote_types_init_data().
-type compile_result() :: dialyzer_analysis_callgraph:compile_result().
-type typesig_result() :: [mfa_or_funlbl()].
-type dataflow_result() :: [mfa_or_funlbl()].
-type warnings_result() :: [dial_warning()].
+-type contract_remote_types_result() ::
+ dialyzer_contracts:contract_remote_types_result().
-type init_data() :: compile_init_data() | typesig_init_data() |
- dataflow_init_data() | warnings_init_data().
+ dataflow_init_data() | warnings_init_data() |
+ contract_remote_types_init_data().
-type result() :: compile_result() | typesig_result() |
- dataflow_result() | warnings_result().
+ dataflow_result() | warnings_result() |
+ contract_remote_types_result().
-type job_result() :: dialyzer_analysis_callgraph:one_file_mid_error() |
dialyzer_analysis_callgraph:one_file_result_ok() |
- typesig_result() | dataflow_result() | warnings_result().
+ typesig_result() | dataflow_result() |
+ warnings_result() | contract_remote_types_result().
-record(state, {mode :: mode(),
active = 0 :: integer(),
@@ -94,7 +104,10 @@
('dataflow', [dataflow_job()], dataflow_init_data(),
timing()) -> dataflow_result();
('warnings', [warnings_job()], warnings_init_data(),
- timing()) -> warnings_result().
+ timing()) -> warnings_result();
+ ('contract_remote_types', [contract_remote_types_job()],
+ contract_remote_types_init_data(), timing()) ->
+ contract_remote_types_result().
parallel_job(Mode, Jobs, InitData, Timing) ->
State = spawn_jobs(Mode, Jobs, InitData, Timing),
@@ -158,7 +171,9 @@ collect_result(#state{mode = Mode, active = Active, result = Result,
ets:delete(SCCtoPID),
NewResult;
'warnings' ->
- NewResult
+ NewResult;
+ 'contract_remote_types' ->
+ NewResult
end;
N ->
case TypesigOrDataflow of
@@ -187,6 +202,8 @@ update_result(Mode, InitData, Job, Data, Result) ->
X when X =:= 'typesig'; X =:= 'dataflow' ->
dialyzer_succ_typings:lookup_names(Data, InitData) ++ Result;
'warnings' ->
+ Data ++ Result;
+ 'contract_remote_types' ->
Data ++ Result
end.
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index c4ac96c9dc..e3a3976366 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -233,7 +233,7 @@ get_record_fields([], _RecDict, Acc) ->
%% The field types are cached. Used during analysis when handling records.
process_record_remote_types(CServer) ->
- ExpTypes = dialyzer_codeserver:get_exported_types(CServer),
+ ExpTypes = dialyzer_codeserver:get_exported_types_table(CServer),
Mods = dialyzer_codeserver:all_temp_modules(CServer),
process_opaque_types0(Mods, CServer, ExpTypes),
VarTable = erl_types:var_table__new(),
diff --git a/lib/dialyzer/src/dialyzer_worker.erl b/lib/dialyzer/src/dialyzer_worker.erl
index af0f2e9e08..64de596da7 100644
--- a/lib/dialyzer/src/dialyzer_worker.erl
+++ b/lib/dialyzer/src/dialyzer_worker.erl
@@ -66,7 +66,8 @@ init(#state{job = SCC, mode = Mode, init_data = InitData,
DependsOn = [{Pid, erlang:monitor(process, Pid)} || Pid <- Pids],
loop(updating, State#state{depends_on = DependsOn});
init(#state{mode = Mode} = State) when
- Mode =:= 'compile'; Mode =:= 'warnings' ->
+ Mode =:= 'compile'; Mode =:= 'warnings';
+ Mode =:= 'contract_remote_types' ->
loop(running, State).
loop(updating, #state{mode = Mode} = State) when
@@ -83,6 +84,11 @@ loop(waiting, #state{mode = Mode} = State) when
?debug("~w: Wait: ~p\n", [self(), State#state.job]),
NewState = wait_for_success_typings(State),
loop(updating, NewState);
+loop(running, #state{mode = 'contract_remote_types'} = State) ->
+ request_activation(State),
+ ?debug("~w: Remote types: ~p\n", [self(), State#state.job]),
+ Result = do_work(State),
+ report_to_coordinator(Result, State);
loop(running, #state{mode = 'compile'} = State) ->
request_activation(State),
?debug("Compile: ~s\n",[State#state.job]),
@@ -127,7 +133,9 @@ request_activation(#state{coordinator = Coordinator}) ->
do_work(#state{mode = Mode, job = Job, init_data = InitData}) ->
case Mode of
typesig -> dialyzer_succ_typings:find_succ_types_for_scc(Job, InitData);
- dataflow -> dialyzer_succ_typings:refine_one_module(Job, InitData)
+ dataflow -> dialyzer_succ_typings:refine_one_module(Job, InitData);
+ contract_remote_types ->
+ dialyzer_contracts:process_contract_remote_types_module(Job, InitData)
end.
report_to_coordinator(Result, #state{job = Job, coordinator = Coordinator}) ->
diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl
index e381bfb64e..e218d2b051 100644
--- a/lib/hipe/cerl/erl_types.erl
+++ b/lib/hipe/cerl/erl_types.erl
@@ -4663,6 +4663,7 @@ mod_name(Mod, Name) ->
[erl_type()], type_names()}.
-type mod_type_table() :: ets:tid().
-type mod_records() :: dict:dict(module(), type_table()).
+-type exported_type_table() :: ets:tid().
-record(cache,
{
types = maps:new() :: #{cache_key() => {erl_type(), expand_limit()}},
@@ -4671,7 +4672,7 @@ mod_name(Mod, Name) ->
-opaque cache() :: #cache{}.
--spec t_from_form(parse_form(), sets:set(mfa()), site(), mod_type_table(),
+-spec t_from_form(parse_form(), exported_type_table(), site(), mod_type_table(),
var_table(), cache()) -> {erl_type(), cache()}.
t_from_form(Form, ExpTypes, Site, RecDict, VarTab, Cache) ->
@@ -4696,12 +4697,12 @@ t_from_form_without_remote(Form, Site, TypeTable) ->
-type expand_depth() :: integer().
-record(from_form, {site :: site() | {'check', mta()},
- xtypes :: sets:set(mfa()) | 'replace_by_none',
+ xtypes :: exported_type_table() | 'replace_by_none',
mrecs :: 'undefined' | mod_type_table(),
vtab :: var_table(),
tnames :: type_names()}).
--spec t_from_form_check_remote(parse_form(), sets:set(mfa()), mta(),
+-spec t_from_form_check_remote(parse_form(), exported_type_table(), mta(),
mod_type_table()) -> 'ok'.
t_from_form_check_remote(Form, ExpTypes, MTA, RecDict) ->
State = #from_form{site = {check, MTA},
@@ -4722,7 +4723,7 @@ t_from_form_check_remote(Form, ExpTypes, Site, RecDict) ->
%% types balanced (unions will otherwise collapse to any()) by limiting
%% the depth the same way as t_limit/2 does.
--spec t_from_form1(parse_form(), sets:set(mfa()) | 'replace_by_none',
+-spec t_from_form1(parse_form(), exported_type_table() | 'replace_by_none',
site(), 'undefined' | mod_type_table(), var_table(),
cache()) -> {erl_type(), cache()}.
@@ -5074,7 +5075,7 @@ remote_from_form(Anno, RemMod, Name, Args, S, D, L, C) ->
self() ! {self(), ext_types, MFA},
{t_any(), L, C};
{RemDict, C1} ->
- case sets:is_element(MFA, ET) of
+ case ets:member(ET, MFA) of
true ->
RemType = {type, MFA},
case can_unfold_more(RemType, TypeNames) of
@@ -5345,7 +5346,7 @@ recur_limit(Fun, D, L, TypeName, TypeNames) ->
Fun(D, L)
end.
--spec t_check_record_fields(parse_form(), sets:set(mfa()), site(),
+-spec t_check_record_fields(parse_form(), exported_type_table(), site(),
mod_type_table(), var_table(), cache()) -> cache().
t_check_record_fields(Form, ExpTypes, Site, RecDict, VarTable, Cache) ->
@@ -5576,7 +5577,7 @@ t_form_to_string({type, _Anno, Name, []} = T) ->
V = var_table__new(),
C = cache__new(),
State = #from_form{site = Site,
- xtypes = sets:new(),
+ xtypes = replace_by_none,
mrecs = 'undefined',
vtab = V,
tnames = []},
--
2.31.1