aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dialyzer/src/dialyzer_coordinator.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dialyzer/src/dialyzer_coordinator.erl')
-rw-r--r--lib/dialyzer/src/dialyzer_coordinator.erl459
1 files changed, 165 insertions, 294 deletions
diff --git a/lib/dialyzer/src/dialyzer_coordinator.erl b/lib/dialyzer/src/dialyzer_coordinator.erl
index c417b45717..490cfb8d49 100644
--- a/lib/dialyzer/src/dialyzer_coordinator.erl
+++ b/lib/dialyzer/src/dialyzer_coordinator.erl
@@ -21,61 +21,23 @@
%%%-------------------------------------------------------------------
%%% File : dialyzer_coordinator.erl
%%% Authors : Stavros Aronis <[email protected]>
-%%%
-%%% Description:
-%%%
-%%% The parallel version of Dialyzer's typesig analysis is spread over 4 modules
-%%% with the intention to both minimize the changes on the original code and use
-%%% a separate module for every kind of Erlang process that will be running.
-%%%
-%%% There are therefore 3 kinds of processes:
-%%%
-%%% - The original Dialyzer backend (in succ_typings module)
-%%% - The worker process for the typesig analysis (in typesig and
-%%% worker)
-%%% - A coordinator of the worker processes (in coordinator)
-%%%
-%%% Operation guidelines:
-%%%
-%%% - The backend requests from the coordinator to spawn a worker for each SCC
-%%% - The backend notifies the coordinator when all SCC have been spawned and
-%%% waits for the server to report that the PLT has been updated
-%%% - Each worker is responsible to notify all those who wait for it.
-%%%
%%%-------------------------------------------------------------------
-module(dialyzer_coordinator).
-%%% Exports for all possible uses of coordinator from main process
--export([start/2, all_spawned/1]).
+%%% Export for dialyzer main process
+-export([parallel_job/3]).
%%% Exports for all possible workers
-export([wait_activation/0, job_done/3]).
-%%% Export for the typesig, dataflow and warnings main process
--export([scc_spawn/2]).
-
-%%% Export for the typesig and dataflow analysis main process
--export([receive_not_fixpoint/0]).
-
-%%% Export for warning main process
--export([receive_warnings/0]).
-
%%% Exports for the typesig and dataflow analysis workers
--export([request_activation/1, sccs_to_pids/1]).
-
-%%% Exports for the compilation main process
--export([compiler_spawn/2, receive_compilation_data/0]).
+-export([sccs_to_pids/1]).
%%% Exports for the compilation workers
-export([get_next_label/2, compilation_done/3]).
--export_type([coordinator/0, mode/0]).
-
--behaviour(gen_server).
-
--export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
- code_change/3]).
+-export_type([coordinator/0, mode/0, init_data/0]).
%%--------------------------------------------------------------------
@@ -83,41 +45,169 @@
-type coordinator() :: pid(). %%opaque
--type scc() :: [mfa_or_funlbl()] | module().
+-type scc() :: [mfa_or_funlbl()].
-type mode() :: 'typesig' | 'dataflow' | 'compile' | 'warnings'.
--type servers() :: dialyzer_succ_typings:servers() |
- dialyzer_analysis_callgraph:servers() |
- dialyzer_succ_typings:warning_servers().
-
--record(state, {parent :: pid(),
- mode :: mode(),
- spawn_count = 0 :: integer(),
- all_spawned = false :: boolean(),
- next_label :: integer(),
- result :: [mfa_or_funlbl()] |
- dialyzer_analysis_callgraph:result() |
- [dial_warning()],
- init_job_data :: servers(),
- tickets :: integer(),
- queue :: queue()
+
+-type compile_jobs() :: [file:filename()].
+-type typesig_jobs() :: [scc()].
+-type dataflow_jobs() :: [module()].
+-type warnings_jobs() :: [module()].
+
+-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 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 init_data() :: compile_init_data() | typesig_init_data() |
+ dataflow_init_data() | warnings_init_data().
+
+-type result() :: compile_result() | typesig_result() |
+ dataflow_result() | warnings_result().
+
+-record(state, {active = 0 :: integer(),
+ result :: result(),
+ next_label = 0 :: integer(),
+ tickets = 0 :: integer(),
+ queue = queue:new() :: queue(),
+ init_data :: init_data()
}).
-include("dialyzer.hrl").
%%--------------------------------------------------------------------
--spec start(mode(), servers()) -> coordinator().
+-spec parallel_job('compile', compile_jobs(), compile_init_data()) ->
+ {compile_result(), integer()};
+ ('typesig', typesig_jobs(), typesig_init_data()) ->
+ typesig_result();
+ ('dataflow', dataflow_jobs(), dataflow_init_data()) ->
+ dataflow_result();
+ ('warnings', warnings_jobs(), warnings_init_data()) ->
+ warnings_result().
-start(Mode, Servers) ->
- {ok, Pid} = gen_server:start_link(?MODULE, {self(), Mode, Servers}, []),
- Pid.
+parallel_job(Mode, Jobs, InitData) ->
+ State = spawn_jobs(Mode, Jobs, InitData),
+ collect_result(Mode, State).
--spec scc_spawn(scc() | module(), coordinator()) -> ok.
+spawn_jobs(Mode, Jobs, InitData) when
+ Mode =:= 'typesig'; Mode =:= 'dataflow' ->
+ Coordinator = self(),
+ ?MAP = ets:new(?MAP, [named_table, {read_concurrency, true}]),
+ Fold =
+ fun(Job, Count) ->
+ Pid = dialyzer_worker:launch(Mode, Job, InitData, Coordinator),
+ true = ets:insert(?MAP, {Job, Pid}),
+ Count + 1
+ end,
+ JobCount = lists:foldl(Fold, 0, Jobs),
+ #state{active = JobCount, result = [], init_data = InitData};
+spawn_jobs(Mode, Jobs, InitData) when
+ Mode =:= 'compile'; Mode =:= 'warnings' ->
+ Coordinator = self(),
+ Fold =
+ fun(Job, {InTickets, InQueue, Count}) ->
+ Pid = dialyzer_worker:launch(Mode, Job, InitData, Coordinator),
+ NewCount = Count + 1,
+ case InTickets of
+ 0 -> {InTickets, queue:in(Pid, InQueue), NewCount};
+ N -> activate_pid(Pid), {N-1, InQueue, NewCount}
+ end
+ end,
+ CPUs = erlang:system_info(logical_processors_available),
+ InitTickets = 4*CPUs,
+ {Tickets, Queue, JobCount} =
+ lists:foldl(Fold, {InitTickets, queue:new(), 0}, Jobs),
+ InitResult =
+ case Mode of
+ 'warnings' -> [];
+ 'compile' -> dialyzer_analysis_callgraph:compile_init_result()
+ end,
+ #state{active = JobCount, result = InitResult, next_label = 0,
+ tickets = Tickets, queue = Queue, init_data = InitData}.
+
+collect_result(Mode, State) ->
+ case Mode of
+ 'compile' -> compile_loop(State);
+ 'typesig' -> not_fixpoint_loop(State);
+ 'dataflow' -> not_fixpoint_loop(State);
+ 'warnings' -> warnings_loop(State)
+ end.
-scc_spawn(SCC, Coordinator) ->
- cast({scc_spawn, SCC}, Coordinator).
+compile_loop(#state{active = Active, result = Result,
+ next_label = NextLabel, tickets = Tickets,
+ queue = Queue, init_data = InitData} = State) ->
+ receive
+ {next_label_request, Estimation, Pid} ->
+ Pid ! {next_label_reply, NextLabel},
+ compile_loop(State#state{next_label = NextLabel + Estimation});
+ {done, Job, Data} ->
+ NewResult =
+ dialyzer_analysis_callgraph:add_to_result(Job, Data, Result, InitData),
+ case Active of
+ 1 ->
+ {NewResult, NextLabel};
+ _ ->
+ NewActive = Active - 1,
+ {NewQueue, NewTickets} = manage_waiting(Queue, Tickets),
+ NewState =
+ State#state{result = NewResult, active = NewActive,
+ queue = NewQueue, tickets = NewTickets},
+ compile_loop(NewState)
+ end
+ end.
--spec sccs_to_pids([scc()]) -> {[dialyzer_worker:worker()], [scc()]}.
+not_fixpoint_loop(#state{active = Active, result = Result,
+ init_data = InitData} = State) ->
+ receive
+ {done, _Job, Data} ->
+ FinalData = dialyzer_succ_typings:lookup_names(Data, InitData),
+ NewResult = FinalData ++ Result,
+ case Active of
+ 1 ->
+ ets:delete(?MAP),
+ NewResult;
+ _ ->
+ NewActive = Active - 1,
+ NewState = State#state{active = NewActive, result = NewResult},
+ not_fixpoint_loop(NewState)
+ end
+ end.
+
+warnings_loop(#state{active = Active, result = Result, tickets = Tickets,
+ queue = Queue} = State) ->
+ receive
+ {done, _Job, Data} ->
+ NewResult = Data ++ Result,
+ case Active of
+ 1 -> NewResult;
+ _ ->
+ NewActive = Active - 1,
+ {NewQueue, NewTickets} = manage_waiting(Queue, Tickets),
+ NewState =
+ State#state{result = NewResult, active = NewActive,
+ queue = NewQueue, tickets = NewTickets},
+ warnings_loop(NewState)
+ end
+ end.
+
+manage_waiting(Queue, Tickets) ->
+ {Waiting, NewQueue} = queue:out(Queue),
+ NewTickets =
+ case Waiting of
+ empty -> Tickets + 1;
+ {value, Pid} ->
+ activate_pid(Pid),
+ Tickets
+ end,
+ {NewQueue, NewTickets}.
+
+-spec sccs_to_pids([scc() | module()]) ->
+ {[dialyzer_worker:worker()], [scc() | module()]}.
sccs_to_pids(SCCs) ->
lists:foldl(fun pid_partition/2, {[], []}, SCCs).
@@ -129,70 +219,27 @@ pid_partition(SCC, {Pids, Unknown}) ->
_:_ -> {Pids, [SCC|Unknown]}
end.
--spec job_done(scc() | file:filename(), term(), coordinator()) -> ok.
+-spec job_done(scc() | module() | file:filename(), term(), coordinator()) -> ok.
job_done(Job, Result, Coordinator) ->
- cast({done, Job, Result}, Coordinator).
+ Coordinator ! {done, Job, Result},
+ ok.
-spec compilation_done(file:filename(),
- dialyzer_analysis_callgraph:compilation_data(),
+ dialyzer_analysis_callgraph:compile_result(),
coordinator()) -> ok.
compilation_done(Filename, CompilationData, Coordinator) ->
- cast({done, Filename, CompilationData}, Coordinator).
-
--spec all_spawned(coordinator()) -> ok.
-
-all_spawned(Coordinator) ->
- cast(all_spawned, Coordinator).
-
-send_done_to_parent(#state{mode = Mode,
- parent = Parent,
- result = Result,
- next_label = NextLabel}) ->
- Msg =
- case Mode of
- X when X =:= 'typesig'; X =:= 'dataflow' ->
- ets:delete(?MAP),
- {not_fixpoint, Result};
- 'compile' -> {compilation_data, Result, NextLabel};
- 'warnings' -> {warnings, Result}
- end,
- Parent ! Msg,
+ Coordinator ! {done, Filename, CompilationData},
ok.
--spec receive_not_fixpoint() -> [mfa_or_funlbl()].
-
-receive_not_fixpoint() ->
- receive {not_fixpoint, NotFixpoint} -> NotFixpoint end.
-
--spec receive_compilation_data() ->
- {dialyzer_analysis_callgraph:result(), integer()}.
-
-receive_compilation_data() ->
- receive {compilation_data, CompilationData, NextLabel} ->
- {CompilationData, NextLabel}
- end.
-
--spec receive_warnings() -> [dial_warning()].
-
-receive_warnings() ->
- receive {warnings, Warnings} -> Warnings end.
-
--spec compiler_spawn(file:filename(), coordinator()) -> ok.
-
-compiler_spawn(Filename, Coordinator) ->
- cast({compiler_spawn, Filename}, Coordinator).
-
-spec get_next_label(integer(), coordinator()) -> integer().
get_next_label(EstimatedSize, Coordinator) ->
- call({get_next_label, EstimatedSize}, Coordinator).
-
--spec request_activation(coordinator()) -> ok.
-
-request_activation(Coordinator) ->
- cast({request_activation, self()}, Coordinator).
+ Coordinator ! {next_label_request, EstimatedSize, self()},
+ receive
+ {next_label_reply, NextLabel} -> NextLabel
+ end.
-spec wait_activation() -> ok.
@@ -201,179 +248,3 @@ wait_activation() ->
activate_pid(Pid) ->
Pid ! activate.
-
-%%--------------------------------------------------------------------
-
--spec init({pid(), mode(), servers()}) -> {ok, #state{}}.
-
-init({Parent, Mode, InitJobData}) ->
- BaseTickets = erlang:system_info(logical_processors_available),
- Tickets =
- case Mode of
- 'compile' -> 4*BaseTickets;
- 'warnings' -> 4*BaseTickets;
- _ -> non_regulated
- end,
- InitState =
- #state{parent = Parent, mode = Mode, init_job_data = InitJobData,
- tickets = Tickets, queue = queue:new()},
- State =
- case Mode of
- X when X =:= 'typesig'; X =:= 'dataflow' ->
- ?MAP = ets:new(?MAP, [named_table, {read_concurrency, true}]),
- InitState#state{result = []};
- 'warnings' ->
- InitState#state{result = []};
- 'compile' ->
- InitResult = dialyzer_analysis_callgraph:compile_coordinator_init(),
- InitState#state{result = InitResult, next_label = 0}
- end,
- {ok, State}.
-
--spec handle_call(Query::term(), From::term(), #state{}) ->
- {reply, Reply::term(), #state{}}.
-
-handle_call({get_next_label, EstimatedSize}, _From,
- #state{next_label = NextLabel} = State) ->
- {reply, NextLabel, State#state{next_label = NextLabel + EstimatedSize}}.
-
--spec handle_cast(Msg::term(), #state{}) ->
- {noreply, #state{}} | {stop, normal, #state{}}.
-
-handle_cast({done, _Job, NewData},
- #state{mode = Mode, result = OldResult,
- init_job_data = Servers} = State) when
- Mode =:= 'typesig'; Mode =:= 'dataflow' ->
- FinalData = dialyzer_succ_typings:lookup_names(NewData, Servers),
- UpdatedState = State#state{result = FinalData ++ OldResult},
- reduce_or_stop(UpdatedState);
-handle_cast({done, Job, NewData},
- #state{mode = Mode,
- result = OldResult,
- tickets = Tickets,
- queue = Queue} = State) when
- Mode =:= 'compile'; Mode =:= 'warnings' ->
- {Waiting, NewQueue} = queue:out(Queue),
- NewTickets =
- case Waiting of
- empty -> Tickets+1;
- {value, Pid} ->
- activate_pid(Pid),
- Tickets
- end,
- NewResult =
- case Mode of
- 'compile' ->
- dialyzer_analysis_callgraph:add_to_result(Job, NewData, OldResult);
- 'warnings' ->
- NewData ++ OldResult
- end,
- UpdatedState =
- State#state{result = NewResult, tickets = NewTickets, queue = NewQueue},
- reduce_or_stop(UpdatedState);
-handle_cast(all_spawned, #state{spawn_count = SpawnCount} = State) ->
- case SpawnCount of
- 0 ->
- send_done_to_parent(State),
- {stop, normal, State};
- _ ->
- NewState = State#state{all_spawned = true},
- {noreply, NewState}
- end;
-handle_cast({request_activation, Pid},
- #state{tickets = Tickets, queue = Queue} = State) ->
- {NewTickets, NewQueue} =
- case Tickets of
- 0 -> {Tickets, queue:in(Pid, Queue)};
- N ->
- activate_pid(Pid),
- {N-1, Queue}
- end,
- {noreply, State#state{tickets = NewTickets, queue = NewQueue}};
-handle_cast({scc_spawn, SCC},
- #state{mode = Mode,
- init_job_data = Servers,
- spawn_count = SpawnCount} = State) when
- Mode =:= 'typesig'; Mode =:= 'dataflow' ->
- Pid = dialyzer_worker:launch(Mode, SCC, Servers, self()),
- true = ets:insert(?MAP, {SCC, Pid}),
- {noreply, State#state{spawn_count = SpawnCount + 1}};
-handle_cast({scc_spawn, SCC},
- #state{mode = 'warnings',
- init_job_data = Servers,
- spawn_count = SpawnCount,
- tickets = Tickets,
- queue = Queue} = State) ->
- Pid = dialyzer_worker:launch('warnings', SCC, Servers, self()),
- {NewTickets, NewQueue} =
- case Tickets of
- 0 -> {Tickets, queue:in(Pid, Queue)};
- N ->
- activate_pid(Pid),
- {N-1, Queue}
- end,
- {noreply, State#state{spawn_count = SpawnCount + 1,
- tickets = NewTickets,
- queue = NewQueue}};
-handle_cast({compiler_spawn, Filename},
- #state{mode = Mode,
- init_job_data = Servers,
- spawn_count = SpawnCount,
- tickets = Tickets,
- queue = Queue
- } = State) ->
- Pid = dialyzer_worker:launch(Mode, Filename, Servers, self()),
- NewSpawnCount = SpawnCount + 1,
- {NewTickets, NewQueue} =
- case Tickets of
- 0 -> {Tickets, queue:in(Pid, Queue)};
- N ->
- activate_pid(Pid),
- {N-1, Queue}
- end,
- {noreply, State#state{spawn_count = NewSpawnCount,
- tickets = NewTickets,
- queue = NewQueue}}.
-
--spec handle_info(term(), #state{}) -> {noreply, #state{}}.
-
-handle_info(_Info, State) ->
- {noreply, State}.
-
--spec terminate(term(), #state{}) -> ok.
-
-terminate(_Reason, _State) ->
- ok.
-
--spec code_change(term(), #state{}, term()) -> {ok, #state{}}.
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-%%--------------------------------------------------------------------
-
-cast(Message, Coordinator) ->
- gen_server:cast(Coordinator, Message).
-
-call(Message, Coordinator) ->
- gen_server:call(Coordinator, Message, infinity).
-
-reduce_or_stop(#state{all_spawned = AllSpawned,
- spawn_count = SpawnCount} = State) ->
- Action =
- case AllSpawned of
- false -> reduce;
- true ->
- case SpawnCount of
- 1 -> finish;
- _ -> reduce
- end
- end,
- case Action of
- reduce ->
- NewState = State#state{spawn_count = SpawnCount - 1},
- {noreply, NewState};
- finish ->
- send_done_to_parent(State),
- {stop, normal, State}
- end.