mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-16 14:25:56 +00:00
Estimated hours taken: 20 Branches: main Support implicit parallelism in the compiler. The compiler now uses the deep profiler feedback information to build a parallel version of a program. Changes have also been made to the feedback format for candidate parallel conjunctions and the analysis that recommends opportunities for parallelism to the compiler. compiler/implicit_parallelism.m: Mark Tannier's implementation as deprecated (it also crashes the compiler). Introduce new implicit parallelism transformation. apply_implicit_parallelism_transformation now returns maybe_error rather than maybe so that errors can be described. compiler/goal_util.m: Add a predicate to transform a goal referenced by a goal path within a larger goal structure and rebuild that structure. compiler/mercury_compile.m: Conform to changes in implicit_parallelism.m deep_profiler/mdprof_feedback.m: Return a cord of warnings from many predicates, these warnings are used to describe cases where parallelism might be profitable but it is not (yet) possible to transform the code into parallel code. Fix a bug whereby the wrong deep profiling statistic was used to calculate the cost of a call. Do not attempt to parallelise calls with other goals between them. mdbcomp/feedback.m: Remove the intermediate goals information from the candidate parallel conjunctions feedback data. mdbcomp/program_representation.m: Provide a in-order alternative to the goal_path type so that operations on the start of the goal path occur in constant time and goal_path itself remains usable as a key in arrays because it doesn't use the cord type internally. library/cord.m: Added a di/uo mode to cord.foldl_pred. library/list.m: Added list.find_index_of_match/4 to return the index of the first item in a list that satisfies the predicate given in the first argument. library/pqueue.m: Added pqueue.length/1 NEWS: Announce standard library changes.
1269 lines
50 KiB
Mathematica
1269 lines
50 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2006-2009 The University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% File : implicit_parallelism.m.
|
|
% Author: tannier, pbone.
|
|
%
|
|
% This module uses deep profiling feedback information generated by
|
|
% mdprof_feedback to introduce parallel conjunctions where it could be
|
|
% worthwhile (implicit parallelism). It deals with both independent and
|
|
% dependent parallelism.
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module transform_hlds.implicit_parallelism.
|
|
:- interface.
|
|
|
|
:- import_module hlds.hlds_module.
|
|
|
|
:- import_module maybe.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% apply_implicit_parallelism_transformation(!ModuleInfo, FeedbackFile, !IO)
|
|
%
|
|
% Apply the implicit parallelism transformation using the specified
|
|
% feedback file.
|
|
%
|
|
:- pred apply_implicit_parallelism_transformation(
|
|
module_info::in, maybe_error(module_info)::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module check_hlds.inst_match.
|
|
:- import_module check_hlds.mode_util.
|
|
:- import_module hlds.goal_util.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module hlds.instmap.
|
|
:- import_module hlds.pred_table.
|
|
:- import_module hlds.quantification.
|
|
:- import_module libs.compiler_util.
|
|
:- import_module libs.globals.
|
|
:- import_module libs.options.
|
|
:- import_module mdbcomp.feedback.
|
|
:- import_module mdbcomp.prim_data.
|
|
:- import_module mdbcomp.program_representation.
|
|
:- import_module parse_tree.error_util.
|
|
:- import_module parse_tree.prog_data.
|
|
:- import_module transform_hlds.dep_par_conj.
|
|
|
|
:- import_module assoc_list.
|
|
:- import_module bool.
|
|
:- import_module char.
|
|
:- import_module counter.
|
|
:- import_module int.
|
|
:- import_module list.
|
|
:- import_module map.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module set.
|
|
:- import_module string.
|
|
:- import_module svmap.
|
|
:- import_module varset.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
apply_implicit_parallelism_transformation(ModuleInfo0, MaybeModuleInfo) :-
|
|
module_info_get_globals(ModuleInfo0, Globals),
|
|
lookup_bool_option(Globals, old_implicit_parallelism,
|
|
UseOldImplicitParallelism),
|
|
(
|
|
UseOldImplicitParallelism = yes,
|
|
apply_old_implicit_parallelism_transformation(ModuleInfo0,
|
|
MaybeModuleInfo)
|
|
;
|
|
UseOldImplicitParallelism = no,
|
|
apply_new_implicit_parallelism_transformation(ModuleInfo0,
|
|
MaybeModuleInfo)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% This type is used to track whether parallelism has been introduced by a
|
|
% predicate.
|
|
%
|
|
:- type introduced_parallelism
|
|
---> have_not_introduced_parallelism
|
|
; introduced_parallelism.
|
|
|
|
:- pred apply_new_implicit_parallelism_transformation(module_info::in,
|
|
maybe_error(module_info)::out) is det.
|
|
|
|
apply_new_implicit_parallelism_transformation(ModuleInfo0, MaybeModuleInfo) :-
|
|
module_info_get_globals(ModuleInfo0, Globals0),
|
|
globals.get_feedback_info(Globals0, FeedbackInfo),
|
|
module_info_get_name(ModuleInfo0, ModuleName),
|
|
(
|
|
get_implicit_parallelism_feedback(ModuleName, FeedbackInfo,
|
|
ParallelismInfo)
|
|
->
|
|
some [!ModuleInfo]
|
|
(
|
|
!:ModuleInfo = ModuleInfo0,
|
|
|
|
% Retrieve and process predicates.
|
|
module_info_predids(PredIds, !ModuleInfo),
|
|
module_info_get_predicate_table(!.ModuleInfo, PredTable0),
|
|
predicate_table_get_preds(PredTable0, PredMap0),
|
|
list.foldl2(maybe_parallelise_pred(!.ModuleInfo, ParallelismInfo),
|
|
PredIds, PredMap0, PredMap,
|
|
have_not_introduced_parallelism, IntroducedParallelism),
|
|
(
|
|
IntroducedParallelism = have_not_introduced_parallelism
|
|
;
|
|
IntroducedParallelism = introduced_parallelism,
|
|
predicate_table_set_preds(PredMap, PredTable0, PredTable),
|
|
module_info_set_predicate_table(PredTable, !ModuleInfo),
|
|
module_info_set_contains_par_conj(!ModuleInfo)
|
|
),
|
|
MaybeModuleInfo = ok(!.ModuleInfo)
|
|
)
|
|
;
|
|
MaybeModuleInfo =
|
|
error("Insufficient feedback information for implicit parallelism")
|
|
).
|
|
|
|
% Information retrieved from the feedback system to be used for
|
|
% parallelising this module.
|
|
%
|
|
:- type parallelism_info
|
|
---> parallelism_info(
|
|
pi_desired_parallelism :: float,
|
|
% The number of desired busy sparks.
|
|
|
|
pi_sparking_cost :: int,
|
|
% The cost of creating a spark in call sequence counts.
|
|
|
|
pi_locking_cost :: int,
|
|
% The cost of maintaining a lock on a single dependant
|
|
% variable in call sequence counts.
|
|
|
|
pi_cpc_map :: module_candidate_par_conjs_map
|
|
% A map of candidate parallel conjunctions in this module
|
|
% indexed by their procedure.
|
|
).
|
|
|
|
:- type intra_module_proc_label
|
|
---> intra_module_proc_label(
|
|
im_pred_name :: string,
|
|
im_arity :: int,
|
|
im_pred_or_func :: pred_or_func,
|
|
im_mode :: int
|
|
).
|
|
|
|
% A map of the candidate parallel conjunctions indexed by the procedure
|
|
% label for a given module.
|
|
%
|
|
:- type module_candidate_par_conjs_map
|
|
== map(intra_module_proc_label, candidate_par_conjunction).
|
|
|
|
:- pred get_implicit_parallelism_feedback(module_name::in, feedback_info::in,
|
|
parallelism_info::out) is semidet.
|
|
|
|
get_implicit_parallelism_feedback(ModuleName, FeedbackInfo, ParallelismInfo) :-
|
|
FeedbackData =
|
|
feedback_data_candidate_parallel_conjunctions(_, _, _, _),
|
|
get_feedback_data(FeedbackInfo, FeedbackData),
|
|
FeedbackData = feedback_data_candidate_parallel_conjunctions(
|
|
DesiredParallelism, SparkingCost, LockingCost, AssocList),
|
|
make_module_candidate_par_conjs_map(ModuleName, AssocList,
|
|
CandidateParConjsMap),
|
|
ParallelismInfo = parallelism_info(DesiredParallelism, SparkingCost,
|
|
LockingCost, CandidateParConjsMap).
|
|
|
|
:- pred make_module_candidate_par_conjs_map(module_name::in,
|
|
assoc_list(string_proc_label, candidate_par_conjunction)::in,
|
|
module_candidate_par_conjs_map::out) is det.
|
|
|
|
make_module_candidate_par_conjs_map(ModuleName,
|
|
CandidateParConjsAssocList0, CandidateParConjsMap) :-
|
|
ModuleNameStr = sym_name_to_string(ModuleName),
|
|
filter_map(cpc_proc_is_in_module(ModuleNameStr),
|
|
CandidateParConjsAssocList0, CandidateParConjsAssocList),
|
|
CandidateParConjsMap = map.from_assoc_list(CandidateParConjsAssocList).
|
|
|
|
:- pred cpc_proc_is_in_module(string::in,
|
|
pair(string_proc_label, candidate_par_conjunction)::in,
|
|
pair(intra_module_proc_label, candidate_par_conjunction)::out) is semidet.
|
|
|
|
cpc_proc_is_in_module(ModuleName, ProcLabel - CPC, IMProcLabel - CPC) :-
|
|
(
|
|
ProcLabel = str_ordinary_proc_label(PredOrFunc, _, DefModule, Name,
|
|
Arity, Mode)
|
|
;
|
|
ProcLabel = str_special_proc_label(_, _, DefModule, Name, Arity, Mode),
|
|
PredOrFunc = pf_predicate
|
|
),
|
|
ModuleName = DefModule,
|
|
IMProcLabel = intra_module_proc_label(Name, Arity, PredOrFunc, Mode).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred maybe_parallelise_pred(module_info::in, parallelism_info::in,
|
|
pred_id::in, pred_table::in, pred_table::out,
|
|
introduced_parallelism::in, introduced_parallelism::out) is det.
|
|
|
|
maybe_parallelise_pred(ModuleInfo, ParallelismInfo, PredId, !PredTable,
|
|
!IntroducedParallelism) :-
|
|
map.lookup(!.PredTable, PredId, PredInfo0),
|
|
ProcIds = pred_info_non_imported_procids(PredInfo0),
|
|
pred_info_get_procedures(PredInfo0, ProcTable0),
|
|
list.foldl2(maybe_parallelise_proc(ModuleInfo, ParallelismInfo, PredId),
|
|
ProcIds, ProcTable0, ProcTable, have_not_introduced_parallelism,
|
|
ProcIntroducedParallelism),
|
|
(
|
|
ProcIntroducedParallelism = have_not_introduced_parallelism
|
|
;
|
|
ProcIntroducedParallelism = introduced_parallelism,
|
|
!:IntroducedParallelism = introduced_parallelism,
|
|
pred_info_set_procedures(ProcTable, PredInfo0, PredInfo),
|
|
svmap.det_update(PredId, PredInfo, !PredTable)
|
|
).
|
|
|
|
:- pred maybe_parallelise_proc(module_info::in, parallelism_info::in,
|
|
pred_id::in, proc_id::in, proc_table::in, proc_table::out,
|
|
introduced_parallelism::in, introduced_parallelism::out) is det.
|
|
|
|
maybe_parallelise_proc(ModuleInfo, ParallelismInfo, PredId, ProcId, !ProcTable,
|
|
!IntroducedParallelism) :-
|
|
module_info_pred_proc_info(ModuleInfo, PredId, ProcId,
|
|
PredInfo, ProcInfo0),
|
|
|
|
% Lookup the Candidate Parallel Conjunction (CPC) Map for this procedure.
|
|
Name = pred_info_name(PredInfo),
|
|
Arity = pred_info_orig_arity(PredInfo),
|
|
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
|
|
Mode = proc_id_to_int(ProcId),
|
|
IMProcLabel = intra_module_proc_label(Name, Arity, PredOrFunc, Mode),
|
|
CPCMap = ParallelismInfo ^ pi_cpc_map,
|
|
( map.search(CPCMap, IMProcLabel, CPC) ->
|
|
proc_info_get_goal(ProcInfo0, Goal0),
|
|
TargetGoalPathString = CPC ^ goal_path,
|
|
( goal_path_from_string(TargetGoalPathString, TargetGoalPathPrime) ->
|
|
TargetGoalPath = TargetGoalPathPrime
|
|
;
|
|
unexpected(this_file,
|
|
"Invalid goal path in CPC Feedback Information")
|
|
),
|
|
maybe_parallelise_goal(ProcInfo0, CPC, TargetGoalPath,
|
|
Goal0, MaybeGoal),
|
|
(
|
|
MaybeGoal = yes(Goal),
|
|
% In the future we'll specialise the procedure for parallelism, We
|
|
% don't do that now so simply replace the procedure's body.
|
|
proc_info_set_goal(Goal, ProcInfo0, ProcInfo1),
|
|
proc_info_set_has_parallel_conj(yes, ProcInfo1, ProcInfo),
|
|
svmap.det_update(ProcId, ProcInfo, !ProcTable),
|
|
!:IntroducedParallelism = introduced_parallelism
|
|
;
|
|
MaybeGoal = no
|
|
)
|
|
;
|
|
true
|
|
).
|
|
|
|
% maybe_parallelise_goal(ModuleInfo, CPC, GoalPath, Goal, MaybeGoal).
|
|
%
|
|
% Parallelise a goal addressed by GoalPath within Goal producing MaybeGoal
|
|
% if found. The goal to parallelise must be a conjunction with conjuncts
|
|
% matching those described in CPC.
|
|
%
|
|
% As this predicate recurses deeper into the goal tree GoalPath becomes
|
|
% smaller as goal path steps are popped off the end and followed.
|
|
%
|
|
% Try to parallelise the given conjunction within this goal.
|
|
%
|
|
:- pred maybe_parallelise_goal(proc_info::in, candidate_par_conjunction::in,
|
|
goal_path::in, hlds_goal::in, maybe(hlds_goal)::out) is det.
|
|
|
|
maybe_parallelise_goal(ProcInfo, CPC, TargetGoalPath,
|
|
Goal0, MaybeGoal) :-
|
|
goal_path_consable(TargetGoalPath, TargetGoalPathC),
|
|
maybe_transform_goal_at_goal_path(maybe_parallelise_conj(ProcInfo, CPC),
|
|
TargetGoalPathC, Goal0, MaybeGoal).
|
|
|
|
:- pred maybe_parallelise_conj(proc_info::in, candidate_par_conjunction::in,
|
|
hlds_goal::in, maybe(hlds_goal)::out) is det.
|
|
|
|
maybe_parallelise_conj(ProcInfo, CPC, Goal0, MaybeGoal) :-
|
|
GoalExpr0 = Goal0 ^ hlds_goal_expr,
|
|
% We've reached the point indicated by the goal path, Find the
|
|
% conjuncts that we wish to parallelise.
|
|
(
|
|
GoalExpr0 = conj(plain_conj, Conjs0),
|
|
index1_of_candidate_par_conjunct(ProcInfo, CPC ^ par_conjunct_a,
|
|
Conjs0, AIdx),
|
|
index1_of_candidate_par_conjunct(ProcInfo, CPC ^ par_conjunct_b,
|
|
Conjs0, BIdx),
|
|
AIdx \= BIdx,
|
|
% In the future there may be some goals between the two calls to
|
|
% parallelise. The analysis will say how many of these goals should be
|
|
% run with each of the two calls, but it may be incorrect in cases
|
|
% where the code has changed. Thus we need to ensure that the code is
|
|
% still mode-correct.
|
|
GoalA = list.det_index1(Conjs0, AIdx),
|
|
GoalB = list.det_index1(Conjs0, BIdx),
|
|
model_det_and_at_least_semipure(GoalA),
|
|
model_det_and_at_least_semipure(GoalB),
|
|
( BIdx - AIdx = 1 ->
|
|
% The conjuncts are adjacent with GoalA occuring first.
|
|
FirstParGoal = GoalA,
|
|
SecondParGoal = GoalB,
|
|
MaxIdx = BIdx,
|
|
MinIdx = AIdx
|
|
; AIdx - BIdx = 1 ->
|
|
FirstParGoal = GoalB,
|
|
SecondParGoal = GoalA,
|
|
MaxIdx = AIdx,
|
|
MinIdx = BIdx
|
|
;
|
|
fail
|
|
)
|
|
->
|
|
ParConjExprList = [FirstParGoal, SecondParGoal],
|
|
ParConjExpr = conj(parallel_conj, ParConjExprList),
|
|
goal_list_nonlocals(ParConjExprList, ParConjNonLocals),
|
|
goal_list_instmap_delta(ParConjExprList, ParConjInstmapDelta),
|
|
goal_list_determinism(ParConjExprList, ParConjDetism),
|
|
goal_list_purity(ParConjExprList, ParConjPurity),
|
|
goal_info_init(ParConjNonLocals, ParConjInstmapDelta, ParConjDetism,
|
|
ParConjPurity, ParConjInfo),
|
|
ParConj = hlds_goal(ParConjExpr, ParConjInfo),
|
|
(
|
|
take(MinIdx - 1, Conjs0, GoalsBeforeParPrime),
|
|
drop(MaxIdx, Conjs0, GoalsAfterParPrime)
|
|
->
|
|
GoalsBeforePar = GoalsBeforeParPrime,
|
|
GoalsAfterPar = GoalsAfterParPrime
|
|
;
|
|
unexpected(this_file, "Miscalculated conjunct list operations.")
|
|
),
|
|
Conjs = GoalsBeforePar ++ [ ParConj | GoalsAfterPar ],
|
|
GoalExpr = conj(plain_conj, Conjs),
|
|
MaybeGoal = yes(hlds_goal(GoalExpr, Goal0 ^ hlds_goal_info))
|
|
;
|
|
MaybeGoal = no
|
|
).
|
|
|
|
:- pred index1_of_candidate_par_conjunct(proc_info::in,
|
|
candidate_par_conjunct::in, list(hlds_goal)::in, int::out) is semidet.
|
|
|
|
index1_of_candidate_par_conjunct(ProcInfo, CPC, Goals, Index) :-
|
|
MaybeCallee = CPC ^ callee,
|
|
(
|
|
MaybeCallee = yes(Callee),
|
|
NamePt1 - NamePt2 = Callee,
|
|
MaybeName = yes(string_to_sym_name(NamePt1 ++ "." ++ NamePt2))
|
|
;
|
|
MaybeCallee = no,
|
|
MaybeName = no
|
|
),
|
|
CPC ^ vars = CPCArgs,
|
|
proc_info_get_varset(ProcInfo, VarSet),
|
|
|
|
find_index_of_match((pred(Goal::in) is semidet :-
|
|
Goal = hlds_goal(CallGoal, _),
|
|
% Some calls know the name of the callee, others dont. match the
|
|
% if the name is know and what it is if known against the candidate
|
|
% parallel conjunct.
|
|
% Note: we don't (yet) allow parallelisation of foreign code,
|
|
(
|
|
CallGoal = plain_call(_, _, Args, _, _, Name),
|
|
MaybeName = yes(Name)
|
|
;
|
|
CallGoal = generic_call(_, Args, _, _),
|
|
MaybeName = no
|
|
),
|
|
% Match arguments which have user defined names in the profiled
|
|
% program against the names in the current program.
|
|
list.map(args_match(VarSet), Args, CPCArgs)
|
|
), Goals, 1, Index).
|
|
|
|
:- pred args_match(prog_varset::in, prog_var::in, maybe(string)::in)
|
|
is semidet.
|
|
|
|
args_match(_, _, no).
|
|
args_match(VarSet, Var, yes(Name)) :-
|
|
varset.search_name(VarSet, Var, Name).
|
|
|
|
:- pred model_det_and_at_least_semipure(hlds_goal::in) is semidet.
|
|
|
|
model_det_and_at_least_semipure(Goal) :-
|
|
GoalInfo = Goal ^ hlds_goal_info,
|
|
Determinism = goal_info_get_determinism(GoalInfo),
|
|
( Determinism = detism_det
|
|
; Determinism = detism_cc_multi
|
|
),
|
|
Purity = goal_info_get_purity(GoalInfo),
|
|
( Purity = purity_pure
|
|
; Purity = purity_semipure
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% The following code is deprecated, it is the older implicit parallelisation
|
|
% transformation developed by Jerömé.
|
|
%
|
|
% TODO
|
|
% - Once a call which is a candidate for implicit parallelism is found,
|
|
% search forward AND backward for the closest goal which is also a
|
|
% candidate for implicit parallelism/parallel conjunction and determine
|
|
% which side is the best (on the basis of the number of shared variables).
|
|
%
|
|
% XXX Several predicates in this module repeatedly add goals to the ends of
|
|
% lists of goals, yielding quadratic behavior. This should be fixed.
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Represent a call site static which is a candidate for introducing
|
|
% implicit parallelism.
|
|
%
|
|
:- type candidate_call_site
|
|
---> candidate_call_site(
|
|
caller :: string, % The caller of the call.
|
|
slot_number :: int, % The slot number of the call.
|
|
kind :: call_site_kind, % The kind of the call.
|
|
callee :: string % The callee of the call.
|
|
).
|
|
|
|
% Represent the kind of a call site.
|
|
%
|
|
:- type call_site_kind
|
|
---> csk_normal
|
|
; csk_special
|
|
; csk_higher_order
|
|
; csk_method
|
|
; csk_callback.
|
|
|
|
% Construct a call_site_kind from its string representation.
|
|
%
|
|
:- pred construct_call_site_kind(string::in, call_site_kind::out) is semidet.
|
|
|
|
construct_call_site_kind("normal_call", csk_normal).
|
|
construct_call_site_kind("special_call", csk_special).
|
|
construct_call_site_kind("higher_order_call", csk_higher_order).
|
|
construct_call_site_kind("method_call", csk_method).
|
|
construct_call_site_kind("callback", csk_callback).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred apply_old_implicit_parallelism_transformation(
|
|
module_info::in, maybe_error(module_info)::out) is det.
|
|
|
|
apply_old_implicit_parallelism_transformation(ModuleInfo0, MaybeModuleInfo) :-
|
|
module_info_get_globals(ModuleInfo0, Globals),
|
|
globals.get_feedback_info(Globals, FeedbackInfo),
|
|
(
|
|
FeedbackData = feedback_data_calls_above_threshold_sorted(_, _, _),
|
|
get_feedback_data(FeedbackInfo, FeedbackData)
|
|
->
|
|
some [!ModuleInfo]
|
|
(
|
|
!:ModuleInfo = ModuleInfo0,
|
|
module_info_predids(PredIds, !ModuleInfo),
|
|
FeedbackData =
|
|
feedback_data_calls_above_threshold_sorted(_, _, Calls),
|
|
list.map(call_site_convert, Calls, CandidateCallSites),
|
|
process_preds_for_implicit_parallelism(PredIds, CandidateCallSites,
|
|
!ModuleInfo),
|
|
MaybeModuleInfo = ok(!.ModuleInfo)
|
|
)
|
|
;
|
|
MaybeModuleInfo =
|
|
error("Insufficient feedback information for implicit parallelism")
|
|
).
|
|
|
|
% This predicate isn't really necessary as this entire module should use
|
|
% the call_site structure defined in mdbcomp.program_representation.
|
|
% However it's expected that the rest of this module will be replaced in
|
|
% the near future.
|
|
%
|
|
:- pred call_site_convert(call_site::in, candidate_call_site::out) is det.
|
|
|
|
call_site_convert(Call, CallSite) :-
|
|
Call = call_site(Caller0, Slot, CallTypeAndCallee),
|
|
string_proc_label_to_string(Caller0, Caller),
|
|
(
|
|
CallTypeAndCallee = plain_call(Callee0),
|
|
string_proc_label_to_string(Callee0, Callee),
|
|
CallSiteKind = csk_normal
|
|
;
|
|
(
|
|
CallTypeAndCallee = callback_call,
|
|
CallSiteKind = csk_callback
|
|
;
|
|
CallTypeAndCallee = higher_order_call,
|
|
CallSiteKind = csk_higher_order
|
|
;
|
|
CallTypeAndCallee = method_call,
|
|
CallSiteKind = csk_method
|
|
;
|
|
CallTypeAndCallee = special_call,
|
|
CallSiteKind = csk_special
|
|
),
|
|
Callee = ""
|
|
),
|
|
CallSite = candidate_call_site(Caller, Slot, CallSiteKind, Callee).
|
|
|
|
:- pred string_proc_label_to_string(string_proc_label::in, string::out) is det.
|
|
|
|
string_proc_label_to_string(ProcLabel, String) :-
|
|
(
|
|
ProcLabel = str_ordinary_proc_label(_, Module, _, Name, Arity, Mode)
|
|
;
|
|
ProcLabel = str_special_proc_label(_, _, Module, Name, Arity, Mode)
|
|
),
|
|
string.format("%s.%s/%d-%d", [s(Module), s(Name), i(Arity), i(Mode)],
|
|
String).
|
|
|
|
% Process predicates for implicit parallelism.
|
|
%
|
|
:- pred process_preds_for_implicit_parallelism(list(pred_id)::in,
|
|
list(candidate_call_site)::in, module_info::in, module_info::out) is det.
|
|
|
|
process_preds_for_implicit_parallelism([],
|
|
_CandidateCallSites, !ModuleInfo).
|
|
process_preds_for_implicit_parallelism([PredId | PredIds],
|
|
CandidateCallSites, !ModuleInfo) :-
|
|
module_info_pred_info(!.ModuleInfo, PredId, PredInfo),
|
|
ProcIds = pred_info_non_imported_procids(PredInfo),
|
|
process_procs_for_implicit_parallelism(PredId, ProcIds,
|
|
CandidateCallSites, !ModuleInfo),
|
|
process_preds_for_implicit_parallelism(PredIds,
|
|
CandidateCallSites, !ModuleInfo).
|
|
|
|
% Process procedures for implicit parallelism.
|
|
%
|
|
:- pred process_procs_for_implicit_parallelism(pred_id::in,
|
|
list(proc_id)::in, list(candidate_call_site)::in,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
process_procs_for_implicit_parallelism(_PredId, [],
|
|
_CandidateCallSites, !ModuleInfo).
|
|
process_procs_for_implicit_parallelism(PredId, [ProcId | ProcIds],
|
|
CandidateCallSites, !ModuleInfo) :-
|
|
module_info_pred_proc_info(!.ModuleInfo, PredId, ProcId,
|
|
PredInfo0, ProcInfo0),
|
|
% Initialize the counter for the slot number.
|
|
SiteNumCounter = counter.init(0),
|
|
pred_proc_id_to_raw_id(PredInfo0, ProcId, CallerRawId),
|
|
get_callees_feedback(CallerRawId, CandidateCallSites, [], CallSites),
|
|
list.length(CallSites, NumCallSites),
|
|
( NumCallSites = 0 ->
|
|
% No candidate calls for implicit parallelism in this procedure.
|
|
process_procs_for_implicit_parallelism(PredId, ProcIds,
|
|
CandidateCallSites, !ModuleInfo)
|
|
;
|
|
proc_info_get_goal(ProcInfo0, Body0),
|
|
process_goal_for_implicit_parallelism(Body0, Body, ProcInfo0,
|
|
!ModuleInfo, no, _, 0, _, CallSites, _, SiteNumCounter, _),
|
|
proc_info_set_goal(Body, ProcInfo0, ProcInfo1),
|
|
proc_info_set_has_parallel_conj(yes, ProcInfo1, ProcInfo2),
|
|
requantify_proc(ProcInfo2, ProcInfo3),
|
|
recompute_instmap_delta_proc(do_not_recompute_atomic_instmap_deltas,
|
|
ProcInfo3, ProcInfo, !ModuleInfo),
|
|
pred_info_set_proc_info(ProcId, ProcInfo, PredInfo0, PredInfo),
|
|
module_info_set_pred_info(PredId, PredInfo, !ModuleInfo),
|
|
process_procs_for_implicit_parallelism(PredId, ProcIds,
|
|
CandidateCallSites, !ModuleInfo)
|
|
).
|
|
|
|
% Filter the list of call site information from the feedback file so that
|
|
% the resulting list only contains those call sites that belong to the first
|
|
% argument, e.g. the caller.
|
|
%
|
|
:- pred get_callees_feedback(string::in, list(candidate_call_site)::in,
|
|
list(candidate_call_site)::in, list(candidate_call_site)::out) is det.
|
|
|
|
get_callees_feedback(_Caller, [], !ResultAcc).
|
|
get_callees_feedback(Caller, [CandidateCallSite | CandidateCallSites],
|
|
!ResultAcc) :-
|
|
CandidateCallSite = candidate_call_site(CSSCaller, _, _, _),
|
|
( Caller = CSSCaller ->
|
|
!:ResultAcc = [CandidateCallSite | !.ResultAcc]
|
|
;
|
|
true
|
|
),
|
|
get_callees_feedback(Caller, CandidateCallSites, !ResultAcc).
|
|
|
|
% Process a goal for implicit parallelism.
|
|
% MaybeConj is the conjunction which contains Goal.
|
|
%
|
|
:- pred process_goal_for_implicit_parallelism(hlds_goal::in, hlds_goal::out,
|
|
proc_info::in, module_info::in, module_info::out,
|
|
maybe(hlds_goal_expr)::in, maybe(hlds_goal_expr)::out, int ::in, int::out,
|
|
list(candidate_call_site)::in, list(candidate_call_site)::out,
|
|
counter::in, counter::out) is det.
|
|
|
|
process_goal_for_implicit_parallelism(!Goal, ProcInfo, !ModuleInfo,
|
|
!MaybeConj, !IndexInConj, !CalleesToBeParallelized, !SiteNumCounter) :-
|
|
!.Goal = hlds_goal(GoalExpr0, GoalInfo),
|
|
(
|
|
GoalExpr0 = unify(_, _, _, _, _),
|
|
increment_index_if_in_conj(!.MaybeConj, !IndexInConj)
|
|
;
|
|
GoalExpr0 = plain_call(_, _, _, _, _, _),
|
|
process_call_for_implicit_parallelism(!.Goal, ProcInfo, !ModuleInfo,
|
|
!IndexInConj, !MaybeConj, !CalleesToBeParallelized,
|
|
!SiteNumCounter)
|
|
% We deal with the index in the conjunction in
|
|
% process_call_for_implicit_parallelism.
|
|
;
|
|
GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _),
|
|
process_call_for_implicit_parallelism(!.Goal, ProcInfo, !ModuleInfo,
|
|
!IndexInConj, !MaybeConj, !CalleesToBeParallelized,
|
|
!SiteNumCounter)
|
|
;
|
|
GoalExpr0 = generic_call(Details, _, _, _),
|
|
(
|
|
Details = higher_order(_, _, _, _),
|
|
process_call_for_implicit_parallelism(!.Goal, ProcInfo,
|
|
!ModuleInfo, !IndexInConj, !MaybeConj,
|
|
!CalleesToBeParallelized, !SiteNumCounter)
|
|
;
|
|
Details = class_method(_, _, _, _),
|
|
process_call_for_implicit_parallelism(!.Goal, ProcInfo,
|
|
!ModuleInfo, !IndexInConj, !MaybeConj,
|
|
!CalleesToBeParallelized, !SiteNumCounter)
|
|
;
|
|
Details = event_call(_),
|
|
increment_index_if_in_conj(!.MaybeConj, !IndexInConj)
|
|
;
|
|
Details = cast(_),
|
|
increment_index_if_in_conj(!.MaybeConj, !IndexInConj)
|
|
)
|
|
;
|
|
% No distinction is made between plain conjunctions and parallel
|
|
% conjunctions. We have to process the parallel conjunction for the
|
|
% slot number.
|
|
GoalExpr0 = conj(_, _),
|
|
process_conj_for_implicit_parallelism(GoalExpr0, GoalExpr, 1,
|
|
ProcInfo, !ModuleInfo, !CalleesToBeParallelized, !SiteNumCounter),
|
|
% A plain conjunction will never be contained in an other plain
|
|
% conjunction. As for parallel conjunctions, they will not be modified.
|
|
% Therefore, incrementing the index suffices (no need to call
|
|
% update_conj_and_index).
|
|
!:Goal = hlds_goal(GoalExpr, GoalInfo),
|
|
increment_index_if_in_conj(!.MaybeConj, !IndexInConj)
|
|
;
|
|
GoalExpr0 = disj(Goals0),
|
|
process_disj_for_implicit_parallelism(Goals0, [], Goals,
|
|
ProcInfo, !ModuleInfo, !CalleesToBeParallelized,
|
|
!SiteNumCounter),
|
|
GoalExpr = disj(Goals),
|
|
% If we are not in a conjunction, then we need to return the modified
|
|
% value of Goal. If we are in a conjunction, that information is not
|
|
% read (see process_conj_for_implicit_parallelism).
|
|
!:Goal = hlds_goal(GoalExpr, GoalInfo),
|
|
update_conj_and_index(!MaybeConj, !.Goal, !IndexInConj)
|
|
;
|
|
GoalExpr0 = switch(Var, CanFail, Cases0),
|
|
process_switch_cases_for_implicit_parallelism(Cases0, [], Cases,
|
|
ProcInfo, !ModuleInfo, !CalleesToBeParallelized, !SiteNumCounter),
|
|
GoalExpr = switch(Var, CanFail, Cases),
|
|
!:Goal = hlds_goal(GoalExpr, GoalInfo),
|
|
update_conj_and_index(!MaybeConj, !.Goal, !IndexInConj)
|
|
;
|
|
GoalExpr0 = negation(SubGoal0),
|
|
process_goal_for_implicit_parallelism(SubGoal0, SubGoal, ProcInfo,
|
|
!ModuleInfo, !MaybeConj, !IndexInConj, !CalleesToBeParallelized,
|
|
!SiteNumCounter),
|
|
GoalExpr = negation(SubGoal),
|
|
!:Goal = hlds_goal(GoalExpr, GoalInfo),
|
|
update_conj_and_index(!MaybeConj, !.Goal, !IndexInConj)
|
|
;
|
|
GoalExpr0 = scope(Reason, Goal0),
|
|
( Reason = from_ground_term(_, from_ground_term_construct) ->
|
|
% Treat the scope as if it were a single unification, since
|
|
% that is effectively what happens at runtime.
|
|
increment_index_if_in_conj(!.MaybeConj, !IndexInConj)
|
|
;
|
|
% 0 is the default value when we are not in a conjunction
|
|
% (in this case a scope).
|
|
process_goal_for_implicit_parallelism(Goal0, Goal, ProcInfo,
|
|
!ModuleInfo, no, _, 0, _, !CalleesToBeParallelized,
|
|
!SiteNumCounter),
|
|
GoalExpr = scope(Reason, Goal),
|
|
!:Goal = hlds_goal(GoalExpr, GoalInfo),
|
|
update_conj_and_index(!MaybeConj, !.Goal, !IndexInConj)
|
|
)
|
|
;
|
|
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
|
|
process_goal_for_implicit_parallelism(Cond0, Cond, ProcInfo,
|
|
!ModuleInfo, no, _, 0, _, !CalleesToBeParallelized,
|
|
!SiteNumCounter),
|
|
process_goal_for_implicit_parallelism(Then0, Then, ProcInfo,
|
|
!ModuleInfo, no, _, 0, _, !CalleesToBeParallelized,
|
|
!SiteNumCounter),
|
|
process_goal_for_implicit_parallelism(Else0, Else, ProcInfo,
|
|
!ModuleInfo, no, _, 0, _, !CalleesToBeParallelized,
|
|
!SiteNumCounter),
|
|
GoalExpr = if_then_else(Vars, Cond, Then, Else),
|
|
!:Goal = hlds_goal(GoalExpr, GoalInfo),
|
|
update_conj_and_index(!MaybeConj, !.Goal, !IndexInConj)
|
|
;
|
|
GoalExpr0 = shorthand(_),
|
|
% These should have been expanded out by now.
|
|
unexpected(this_file,
|
|
"process_goal_for_implicit_parallelism: shorthand")
|
|
).
|
|
|
|
% Increment the index if we are in a conjunction.
|
|
%
|
|
:- pred increment_index_if_in_conj(maybe(hlds_goal_expr)::in, int::in, int::out)
|
|
is det.
|
|
|
|
increment_index_if_in_conj(MaybeConj, !IndexInConj) :-
|
|
(
|
|
MaybeConj = yes(_),
|
|
!:IndexInConj = !.IndexInConj + 1
|
|
;
|
|
MaybeConj = no
|
|
).
|
|
|
|
% Process a call for implicit parallelism.
|
|
%
|
|
:- pred process_call_for_implicit_parallelism(hlds_goal::in, proc_info::in,
|
|
module_info::in, module_info::out, int::in, int::out,
|
|
maybe(hlds_goal_expr)::in, maybe(hlds_goal_expr)::out,
|
|
list(candidate_call_site)::in, list(candidate_call_site)::out,
|
|
counter::in, counter::out) is det.
|
|
|
|
process_call_for_implicit_parallelism(Call, ProcInfo, !ModuleInfo,
|
|
!IndexInConj, !MaybeConj, !CalleesToBeParallelized, !SiteNumCounter) :-
|
|
counter.allocate(SlotNumber, !SiteNumCounter),
|
|
get_call_kind_and_callee(!.ModuleInfo, Call, Kind, CalleeRawId),
|
|
(
|
|
!.MaybeConj = yes(Conj0),
|
|
Conj0 = conj(plain_conj, ConjGoals0)
|
|
->
|
|
(
|
|
is_in_css_list_to_be_parallelized(Kind, SlotNumber, CalleeRawId,
|
|
!.CalleesToBeParallelized, [], !:CalleesToBeParallelized)
|
|
->
|
|
(
|
|
build_goals_surrounded_by_calls_to_be_parallelized(ConjGoals0,
|
|
!.ModuleInfo, [Call], Goals, !.IndexInConj + 1, End,
|
|
!SiteNumCounter, !CalleesToBeParallelized)
|
|
->
|
|
parallelize_calls(Goals, !.IndexInConj, End, Conj0, Conj,
|
|
ProcInfo, !ModuleInfo),
|
|
!:IndexInConj = End,
|
|
!:MaybeConj = yes(Conj)
|
|
;
|
|
% The next call is not in the feedback file or we've hit a
|
|
% plain conjunction/disjunction/switch/if_then_else.
|
|
!:IndexInConj = !.IndexInConj + 1
|
|
)
|
|
;
|
|
% Not to be parallelized.
|
|
!:IndexInConj = !.IndexInConj + 1
|
|
)
|
|
;
|
|
% Call is not in a conjunction or the call is already in a parallel
|
|
% conjunction.
|
|
true
|
|
).
|
|
|
|
% Give the raw id (the same as in the deep profiler) of a callee contained
|
|
% in a call.
|
|
%
|
|
:- pred get_call_kind_and_callee(module_info::in, hlds_goal::in,
|
|
call_site_kind::out, string::out) is det.
|
|
|
|
get_call_kind_and_callee(ModuleInfo, Call, Kind, CalleeRawId) :-
|
|
GoalExpr = Call ^ hlds_goal_expr,
|
|
(
|
|
GoalExpr = plain_call(PredId, ProcId, _, _, _, _),
|
|
module_info_pred_proc_info(ModuleInfo, PredId, ProcId, PredInfo, _),
|
|
pred_proc_id_to_raw_id(PredInfo, ProcId, CalleeRawId),
|
|
Kind = csk_normal
|
|
;
|
|
GoalExpr = call_foreign_proc(_, PredId, ProcId, _, _, _, _),
|
|
module_info_pred_proc_info(ModuleInfo, PredId, ProcId, PredInfo, _),
|
|
pred_proc_id_to_raw_id(PredInfo, ProcId, CalleeRawId),
|
|
Kind = csk_special
|
|
;
|
|
GoalExpr = generic_call(Details, _, _, _),
|
|
(
|
|
Details = higher_order(_, _, _, _),
|
|
CalleeRawId = "",
|
|
Kind = csk_higher_order
|
|
;
|
|
Details = class_method(_, _, _, _),
|
|
CalleeRawId = "",
|
|
Kind = csk_method
|
|
;
|
|
Details = event_call(_),
|
|
unexpected(this_file, "get_call_kind_and_callee: event_call")
|
|
;
|
|
Details = cast(_),
|
|
unexpected(this_file, "get_call_kind_and_callee: cast")
|
|
)
|
|
;
|
|
% XXX Some of our callers can call us with these kinds of goals.
|
|
( GoalExpr = unify(_, _, _, _, _)
|
|
; GoalExpr = conj(_, _)
|
|
; GoalExpr = disj(_)
|
|
; GoalExpr = switch(_, _, _)
|
|
; GoalExpr = if_then_else(_, _, _, _)
|
|
; GoalExpr = negation(_)
|
|
; GoalExpr = scope(_, _)
|
|
; GoalExpr = shorthand(_)
|
|
),
|
|
unexpected(this_file, "get_call_kind_and_callee")
|
|
).
|
|
|
|
% Convert a pred_info and a proc_id to the raw procedure id (the same used
|
|
% in the deep profiler).
|
|
%
|
|
:- pred pred_proc_id_to_raw_id(pred_info::in, proc_id::in, string::out) is det.
|
|
|
|
pred_proc_id_to_raw_id(PredInfo, ProcId, RawId) :-
|
|
ModuleName = pred_info_module(PredInfo),
|
|
Name = pred_info_name(PredInfo),
|
|
OrigArity = pred_info_orig_arity(PredInfo),
|
|
IsPredOrFunc = pred_info_is_pred_or_func(PredInfo),
|
|
ModuleString = sym_name_to_string(ModuleName),
|
|
ProcIdInt = proc_id_to_int(ProcId),
|
|
RawId = string.append_list([ModuleString, ".", Name, "/",
|
|
string.int_to_string(OrigArity),
|
|
( IsPredOrFunc = pf_function -> "+1" ; ""), "-",
|
|
string.from_int(ProcIdInt)]).
|
|
|
|
% Succeeds if the caller, slot number and callee correspond to a
|
|
% candidate_call_site in the list given as a parameter.
|
|
% Fail otherwise.
|
|
%
|
|
:- pred is_in_css_list_to_be_parallelized(call_site_kind::in, int::in,
|
|
string::in, list(candidate_call_site)::in,
|
|
list(candidate_call_site)::in, list(candidate_call_site)::out) is semidet.
|
|
|
|
is_in_css_list_to_be_parallelized(Kind, SlotNumber, CalleeRawId,
|
|
CandidateCallSites, !ResultAcc) :-
|
|
(
|
|
CandidateCallSites = [],
|
|
fail
|
|
;
|
|
CandidateCallSites = [HeadCandidateCallSite | TailCandidateCallSites],
|
|
HeadCandidateCallSite = candidate_call_site(_, CSSSlotNumber, CSSKind,
|
|
CSSCallee),
|
|
% =< because there is not a one to one correspondance with the source
|
|
% code. New calls might have been added by the previous passes of the
|
|
% compiler.
|
|
(
|
|
CSSSlotNumber =< SlotNumber,
|
|
CSSKind = Kind,
|
|
CSSCallee = CalleeRawId
|
|
->
|
|
!:ResultAcc = !.ResultAcc ++ TailCandidateCallSites
|
|
;
|
|
!:ResultAcc = !.ResultAcc ++ [HeadCandidateCallSite],
|
|
is_in_css_list_to_be_parallelized(Kind, SlotNumber, CalleeRawId,
|
|
TailCandidateCallSites, !ResultAcc)
|
|
)
|
|
).
|
|
|
|
% Build a list of goals surrounded by two calls which are in the feedback
|
|
% file or by a call which is in the feedback file and a parallel
|
|
% conjunction.
|
|
%
|
|
% Succeed if we can build that list of goals.
|
|
% Fail otherwise.
|
|
%
|
|
:- pred build_goals_surrounded_by_calls_to_be_parallelized(list(hlds_goal)::in,
|
|
module_info::in, list(hlds_goal)::in, list(hlds_goal)::out,
|
|
int::in, int::out, counter::in, counter::out,
|
|
list(candidate_call_site)::in, list(candidate_call_site)::out)
|
|
is semidet.
|
|
|
|
build_goals_surrounded_by_calls_to_be_parallelized(ConjGoals, ModuleInfo,
|
|
!ResultAcc, !Index, !SiteNumCounter, !CalleesToBeParallelized) :-
|
|
list.length(ConjGoals, Length),
|
|
( !.Index > Length ->
|
|
fail
|
|
;
|
|
list.index1_det(ConjGoals, !.Index, Goal),
|
|
GoalExpr = Goal ^ hlds_goal_expr,
|
|
(
|
|
( GoalExpr = disj(_)
|
|
; GoalExpr = switch(_, _, _)
|
|
; GoalExpr = if_then_else(_, _, _, _)
|
|
; GoalExpr = conj(plain_conj, _)
|
|
)
|
|
->
|
|
fail
|
|
;
|
|
!:ResultAcc = !.ResultAcc ++ [Goal],
|
|
( goal_is_conjunction(Goal, parallel_conj) ->
|
|
true
|
|
;
|
|
( goal_is_call_or_negated_call(Goal) ->
|
|
counter.allocate(SlotNumber, !SiteNumCounter),
|
|
get_call_kind_and_callee(ModuleInfo, Goal, Kind,
|
|
CalleeRawId),
|
|
(
|
|
is_in_css_list_to_be_parallelized(Kind, SlotNumber,
|
|
CalleeRawId, !.CalleesToBeParallelized,
|
|
[], !:CalleesToBeParallelized)
|
|
->
|
|
true
|
|
;
|
|
!:Index = !.Index + 1,
|
|
build_goals_surrounded_by_calls_to_be_parallelized(
|
|
ConjGoals, ModuleInfo, !ResultAcc, !Index,
|
|
!SiteNumCounter, !CalleesToBeParallelized)
|
|
)
|
|
;
|
|
!:Index = !.Index + 1,
|
|
build_goals_surrounded_by_calls_to_be_parallelized(
|
|
ConjGoals, ModuleInfo, !ResultAcc, !Index,
|
|
!SiteNumCounter, !CalleesToBeParallelized)
|
|
)
|
|
)
|
|
)
|
|
).
|
|
|
|
% Succeeds if Goal is a conjunction and return the type of the
|
|
% conjunction. Fail otherwise.
|
|
%
|
|
:- pred goal_is_conjunction(hlds_goal::in, conj_type::out) is semidet.
|
|
|
|
goal_is_conjunction(Goal, Type) :-
|
|
GoalExpr = Goal ^ hlds_goal_expr,
|
|
GoalExpr = conj(Type, _).
|
|
|
|
% Succeed if Goal is a call or a negated call.
|
|
% Call here includes higher-order and class method calls.
|
|
% Fail otherwise.
|
|
%
|
|
% XXX Should be a function returning a bool or something similar.
|
|
%
|
|
:- pred goal_is_call_or_negated_call(hlds_goal::in) is semidet.
|
|
|
|
goal_is_call_or_negated_call(Goal) :-
|
|
GoalExpr = Goal ^ hlds_goal_expr,
|
|
(
|
|
GoalExpr = plain_call(_, _, _, _, _, _)
|
|
;
|
|
GoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
|
|
;
|
|
GoalExpr = generic_call(Details, _, _, _),
|
|
(
|
|
Details = class_method(_, _, _, _)
|
|
;
|
|
Details = higher_order(_, _, _, _)
|
|
)
|
|
;
|
|
GoalExpr = negation(GoalNeg),
|
|
GoalNegExpr = GoalNeg ^ hlds_goal_expr,
|
|
(
|
|
GoalNegExpr = plain_call(_, _, _, _, _, _)
|
|
;
|
|
GoalNegExpr = call_foreign_proc(_, _, _, _, _, _, _)
|
|
;
|
|
GoalNegExpr = generic_call(Details, _, _, _),
|
|
(
|
|
Details = class_method(_, _, _, _)
|
|
;
|
|
Details = higher_order(_, _, _, _)
|
|
)
|
|
)
|
|
).
|
|
|
|
% Parallelize two calls/a call and a parallel conjunction which might have
|
|
% goals between them. If these have no dependencies with the first call
|
|
% then we move them before the first call and parallelize the two
|
|
% calls/call and parallel conjunction.
|
|
%
|
|
% Goals is contained in Conj.
|
|
%
|
|
:- pred parallelize_calls(list(hlds_goal)::in, int::in, int::in,
|
|
hlds_goal_expr::in, hlds_goal_expr::out, proc_info::in,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
parallelize_calls(Goals, Start, End, !Conj, ProcInfo, !ModuleInfo) :-
|
|
( !.Conj = conj(plain_conj, ConjGoals0) ->
|
|
( ConjGoals0 = [FirstGoal, LastGoal] ->
|
|
(
|
|
is_worth_parallelizing(FirstGoal, LastGoal, ProcInfo,
|
|
!.ModuleInfo)
|
|
->
|
|
( goal_is_conjunction(LastGoal, parallel_conj) ->
|
|
% The parallel conjunction has to remain flatened.
|
|
add_call_to_parallel_conjunction(FirstGoal, LastGoal,
|
|
ParallelGoal),
|
|
!:Conj = ParallelGoal ^ hlds_goal_expr
|
|
;
|
|
!:Conj = conj(parallel_conj, ConjGoals0)
|
|
)
|
|
;
|
|
% Not worth parallelizing.
|
|
true
|
|
)
|
|
;
|
|
% There are more than two goals in the conjunction.
|
|
list.length(Goals, Length),
|
|
list.index1_det(Goals, 1, FirstGoal),
|
|
list.index1_det(Goals, Length, LastGoal),
|
|
(
|
|
is_worth_parallelizing(FirstGoal, LastGoal, ProcInfo,
|
|
!.ModuleInfo)
|
|
->
|
|
GoalsInBetweenAndLast = list.det_tail(Goals),
|
|
list.delete_all(GoalsInBetweenAndLast, LastGoal,
|
|
GoalsInBetween),
|
|
% Check the dependencies of GoalsInBetween with FirstGoal.
|
|
list.filter(goal_depends_on_goal(FirstGoal),
|
|
GoalsInBetween, GoalsFiltered),
|
|
( list.is_empty(GoalsFiltered) ->
|
|
( goal_is_conjunction(LastGoal, parallel_conj) ->
|
|
add_call_to_parallel_conjunction(FirstGoal, LastGoal,
|
|
ParallelGoal)
|
|
;
|
|
create_conj(FirstGoal, LastGoal, parallel_conj,
|
|
ParallelGoal)
|
|
),
|
|
( Start = 1 ->
|
|
GoalsFront = []
|
|
;
|
|
list.det_split_list(Start - 1, ConjGoals0,
|
|
GoalsFront, _)
|
|
),
|
|
list.length(ConjGoals0, ConjLength),
|
|
( End = ConjLength ->
|
|
GoalsBack = []
|
|
;
|
|
list.det_split_list(End, ConjGoals0, _, GoalsBack)
|
|
),
|
|
ConjGoals = GoalsFront ++ GoalsInBetween ++
|
|
[ParallelGoal] ++ GoalsBack,
|
|
!:Conj = conj(plain_conj, ConjGoals)
|
|
;
|
|
% The goals between the two calls/call and parallel
|
|
% conjunction can't be moved before the first call.
|
|
true
|
|
)
|
|
;
|
|
% Not worth parallelizing.
|
|
true
|
|
)
|
|
)
|
|
;
|
|
% Conj is not a conjunction.
|
|
unexpected(this_file, "parallelize_calls")
|
|
).
|
|
|
|
% Two calls are worth parallelizing if the number of shared variables is
|
|
% smaller than the number of argument variables of at least one of the two
|
|
% calls.
|
|
%
|
|
% A call and a parallel conjunction are worth parallelizing if the number of
|
|
% shared variables is smaller than the number of argument variables of the
|
|
% call.
|
|
%
|
|
% Succeed if it is worth parallelizing the two goals.
|
|
% Fail otherwise.
|
|
%
|
|
:- pred is_worth_parallelizing(hlds_goal::in, hlds_goal::in, proc_info::in,
|
|
module_info::in) is semidet.
|
|
|
|
is_worth_parallelizing(GoalA, GoalB, ProcInfo, ModuleInfo) :-
|
|
proc_info_get_initial_instmap(ProcInfo, ModuleInfo, InstMap),
|
|
SharedVars = find_shared_variables(ModuleInfo, InstMap, [GoalA, GoalB]),
|
|
set.to_sorted_list(SharedVars, SharedVarsList),
|
|
list.length(SharedVarsList, NbSharedVars),
|
|
( NbSharedVars = 0 ->
|
|
% No shared vars between the goals.
|
|
true
|
|
;
|
|
( goal_is_conjunction(GoalB, parallel_conj) ->
|
|
get_number_args(GoalA, NbArgsA),
|
|
NbSharedVars < NbArgsA
|
|
;
|
|
(
|
|
get_number_args(GoalA, NbArgsA),
|
|
get_number_args(GoalB, NbArgsB)
|
|
->
|
|
( NbSharedVars < NbArgsA, NbSharedVars < NbArgsB
|
|
; NbSharedVars = NbArgsA, NbSharedVars < NbArgsB
|
|
; NbSharedVars < NbArgsA, NbSharedVars = NbArgsB
|
|
)
|
|
;
|
|
unexpected(this_file, "is_worth_parallelizing")
|
|
)
|
|
)
|
|
).
|
|
|
|
% Give the number of argument variables of a call.
|
|
%
|
|
:- pred get_number_args(hlds_goal::in, int::out) is semidet.
|
|
|
|
get_number_args(Call, NbArgs) :-
|
|
CallExpr = Call ^ hlds_goal_expr,
|
|
(
|
|
CallExpr = plain_call(_, _, Args, _, _, _),
|
|
list.length(Args, NbArgs)
|
|
;
|
|
CallExpr = generic_call(Details, Args, _, _),
|
|
(
|
|
Details = higher_order(_, _, _, _),
|
|
list.length(Args, NbArgs)
|
|
;
|
|
Details = class_method(_, _, _, _),
|
|
list.length(Args, NbArgs)
|
|
)
|
|
;
|
|
CallExpr = call_foreign_proc(_, _, _, Args, _, _, _),
|
|
list.length(Args, NbArgs)
|
|
).
|
|
|
|
% Add a call to an existing parallel conjunction.
|
|
%
|
|
:- pred add_call_to_parallel_conjunction(hlds_goal::in, hlds_goal::in,
|
|
hlds_goal::out) is det.
|
|
|
|
add_call_to_parallel_conjunction(Call, ParallelGoal0, ParallelGoal) :-
|
|
ParallelGoalExpr0 = ParallelGoal0 ^ hlds_goal_expr,
|
|
ParallelGoalInfo0 = ParallelGoal0 ^ hlds_goal_info,
|
|
( ParallelGoalExpr0 = conj(parallel_conj, GoalList0) ->
|
|
GoalList = [Call | GoalList0],
|
|
goal_list_nonlocals(GoalList, NonLocals),
|
|
goal_list_instmap_delta(GoalList, InstMapDelta),
|
|
goal_list_determinism(GoalList, Detism),
|
|
goal_list_purity(GoalList, Purity),
|
|
goal_info_set_nonlocals(NonLocals, ParallelGoalInfo0,
|
|
ParallelGoalInfo1),
|
|
goal_info_set_instmap_delta(InstMapDelta, ParallelGoalInfo1,
|
|
ParallelGoalInfo2),
|
|
goal_info_set_determinism(Detism,
|
|
ParallelGoalInfo2, ParallelGoalInfo3),
|
|
goal_info_set_purity(Purity, ParallelGoalInfo3, ParallelGoalInfo),
|
|
ParallelGoalExpr = conj(parallel_conj, GoalList),
|
|
ParallelGoal = hlds_goal(ParallelGoalExpr, ParallelGoalInfo)
|
|
;
|
|
unexpected(this_file, "add_call_to_parallel_conjunction")
|
|
).
|
|
|
|
% Succeed if the first goal depends on the second one.
|
|
% Fail otherwise.
|
|
%
|
|
:- pred goal_depends_on_goal(hlds_goal::in, hlds_goal::in) is semidet.
|
|
|
|
goal_depends_on_goal(Goal1, Goal2) :-
|
|
Goal1 = hlds_goal(_, GoalInfo1),
|
|
Goal2 = hlds_goal(_, GoalInfo2),
|
|
InstmapDelta1 = goal_info_get_instmap_delta(GoalInfo1),
|
|
instmap_delta_changed_vars(InstmapDelta1, ChangedVars1),
|
|
NonLocals2 = goal_info_get_nonlocals(GoalInfo2),
|
|
set.intersect(ChangedVars1, NonLocals2, Intersection),
|
|
\+ set.empty(Intersection).
|
|
|
|
% Process a conjunction for implicit parallelism.
|
|
%
|
|
:- pred process_conj_for_implicit_parallelism(
|
|
hlds_goal_expr::in, hlds_goal_expr::out, int::in,
|
|
proc_info::in, module_info::in, module_info::out,
|
|
list(candidate_call_site)::in, list(candidate_call_site)::out,
|
|
counter::in, counter::out) is det.
|
|
|
|
process_conj_for_implicit_parallelism(!GoalExpr, IndexInConj, ProcInfo,
|
|
!ModuleInfo, !CalleesToBeParallelized, !SiteNumCounter) :-
|
|
( !.GoalExpr = conj(_, GoalsConj) ->
|
|
list.length(GoalsConj, Length),
|
|
( IndexInConj > Length ->
|
|
true
|
|
;
|
|
MaybeConj0 = yes(!.GoalExpr),
|
|
list.index1_det(GoalsConj, IndexInConj, GoalInConj),
|
|
% We are not interested in the return value of GoalInConj, only
|
|
% MaybeConj matters.
|
|
process_goal_for_implicit_parallelism(GoalInConj, _, ProcInfo,
|
|
!ModuleInfo, MaybeConj0, MaybeConj, IndexInConj, IndexInConj0,
|
|
!CalleesToBeParallelized, !SiteNumCounter),
|
|
( MaybeConj = yes(GoalExprProcessed) ->
|
|
!:GoalExpr = GoalExprProcessed
|
|
;
|
|
unexpected(this_file, "process_conj_for_implicit_parallelism")
|
|
),
|
|
process_conj_for_implicit_parallelism(!GoalExpr, IndexInConj0,
|
|
ProcInfo, !ModuleInfo, !CalleesToBeParallelized,
|
|
!SiteNumCounter)
|
|
)
|
|
;
|
|
unexpected(this_file, "process_conj_for_implicit_parallelism")
|
|
).
|
|
|
|
% Process a disjunction for implicit parallelism.
|
|
%
|
|
:- pred process_disj_for_implicit_parallelism(
|
|
list(hlds_goal)::in, list(hlds_goal)::in, list(hlds_goal)::out,
|
|
proc_info::in, module_info::in, module_info::out,
|
|
list(candidate_call_site)::in, list(candidate_call_site)::out,
|
|
counter::in, counter::out) is det.
|
|
|
|
process_disj_for_implicit_parallelism([], !GoalsAcc, _ProcInfo,
|
|
!ModuleInfo, !CalleesToBeParallelized, !SiteNumCounter).
|
|
process_disj_for_implicit_parallelism([Goal0 | Goals], !GoalsAcc,
|
|
ProcInfo, !ModuleInfo, !CalleesToBeParallelized, !SiteNumCounter) :-
|
|
process_goal_for_implicit_parallelism(Goal0, Goal, ProcInfo,
|
|
!ModuleInfo, no, _, 0, _, !CalleesToBeParallelized, !SiteNumCounter),
|
|
!:GoalsAcc = !.GoalsAcc ++ [Goal],
|
|
process_disj_for_implicit_parallelism(Goals, !GoalsAcc, ProcInfo,
|
|
!ModuleInfo, !CalleesToBeParallelized, !SiteNumCounter).
|
|
|
|
% If we are in a conjunction, update it by replacing the goal at index by
|
|
% Goal and increment the index.
|
|
%
|
|
:- pred update_conj_and_index(
|
|
maybe(hlds_goal_expr)::in, maybe(hlds_goal_expr)::out,
|
|
hlds_goal::in, int::in, int::out) is det.
|
|
|
|
update_conj_and_index(!MaybeConj, Goal, !IndexInConj) :-
|
|
( !.MaybeConj = yes(conj(Type, Goals0)) ->
|
|
list.replace_nth_det(Goals0, !.IndexInConj, Goal, Goals),
|
|
!:IndexInConj = !.IndexInConj + 1,
|
|
!:MaybeConj = yes(conj(Type, Goals))
|
|
;
|
|
true
|
|
).
|
|
|
|
% Process a switch for implicit parallelism.
|
|
%
|
|
:- pred process_switch_cases_for_implicit_parallelism(
|
|
list(case)::in, list(case)::in, list(case)::out, proc_info::in,
|
|
module_info::in, module_info::out,
|
|
list(candidate_call_site)::in, list(candidate_call_site)::out,
|
|
counter::in, counter::out) is det.
|
|
|
|
process_switch_cases_for_implicit_parallelism([], !CasesAcc, _ProcInfo,
|
|
!ModuleInfo, !CalleesToBeParallelized, !SiteNumCounter).
|
|
process_switch_cases_for_implicit_parallelism([Case0 | Cases], !CasesAcc,
|
|
ProcInfo, !ModuleInfo, !CalleesToBeParallelized, !SiteNumCounter) :-
|
|
Case0 = case(MainConsId, OtherConsIds, Goal0),
|
|
process_goal_for_implicit_parallelism(Goal0, Goal, ProcInfo,
|
|
!ModuleInfo, no, _, 0, _, !CalleesToBeParallelized, !SiteNumCounter),
|
|
Case = case(MainConsId, OtherConsIds, Goal),
|
|
!:CasesAcc = !.CasesAcc ++ [Case],
|
|
process_switch_cases_for_implicit_parallelism(Cases, !CasesAcc,
|
|
ProcInfo, !ModuleInfo, !CalleesToBeParallelized, !SiteNumCounter).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func this_file = string.
|
|
|
|
this_file = "implicit_parallelism.m".
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module transform_hlds.implicit_parallelism.
|
|
%-----------------------------------------------------------------------------%
|