mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-14 05:12:33 +00:00
Estimated hours taken: 0 Branches: main Fix some formatting. compiler/distance_granularity.m: compiler/exception_analysis.m: compiler/implicit_parallelism.m: compiler/inst_graph.m: compiler/interval.m: compiler/layout_out.m: compiler/lp_rational.m: compiler/make.program_target.m: compiler/modules.m: compiler/prog_data.m: compiler/purity.m: compiler/recompilation.check.m: compiler/term_constr_data.m: compiler/term_util.m: compiler/xml_documentation.m: deep_profiler/mdprof_cgi.m: library/pqueue.m: profiler/output.m: Fix the positioning of commas. s/[_|_]/[_ | _]/ in a spot.
965 lines
38 KiB
Mathematica
965 lines
38 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2006-2007 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.
|
|
%
|
|
% 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.
|
|
%
|
|
% 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).
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module transform_hlds.implicit_parallelism.
|
|
:- interface.
|
|
|
|
:- import_module hlds.hlds_module.
|
|
|
|
:- import_module io.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% 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, module_info::out, string::in, io::di, io::uo) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module check_hlds.inst_match.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module check_hlds.mode_util.
|
|
:- import_module hlds.goal_util.
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module hlds.instmap.
|
|
:- import_module hlds.quantification.
|
|
:- import_module libs.compiler_util.
|
|
:- import_module mdbcomp.prim_data.
|
|
:- import_module parse_tree.error_util.
|
|
:- import_module parse_tree.prog_data.
|
|
:- import_module transform_hlds.dep_par_conj.
|
|
|
|
:- import_module bool.
|
|
:- import_module char.
|
|
:- import_module counter.
|
|
:- import_module int.
|
|
:- import_module list.
|
|
:- import_module maybe.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module set.
|
|
:- import_module string.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% 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).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
apply_implicit_parallelism_transformation(!ModuleInfo, FeedbackFile, !IO) :-
|
|
parse_feedback_file(FeedbackFile, MaybeCandidateCallSites, !IO),
|
|
(
|
|
MaybeCandidateCallSites = error(Error),
|
|
io.stderr_stream(Stderr, !IO),
|
|
io.write_string(Stderr, Error ++ "\n", !IO)
|
|
;
|
|
MaybeCandidateCallSites = ok(CandidateCallSites),
|
|
module_info_predids(PredIds, !ModuleInfo),
|
|
process_preds_for_implicit_parallelism(PredIds, CandidateCallSites,
|
|
!ModuleInfo)
|
|
).
|
|
|
|
% 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([], _ListCandidateCallSite,
|
|
!ModuleInfo).
|
|
process_preds_for_implicit_parallelism([ PredId | PredIdList ],
|
|
ListCandidateCallSite, !ModuleInfo) :-
|
|
module_info_pred_info(!.ModuleInfo, PredId, PredInfo),
|
|
ProcIds = pred_info_non_imported_procids(PredInfo),
|
|
process_procs_for_implicit_parallelism(PredId, ProcIds,
|
|
ListCandidateCallSite, !ModuleInfo),
|
|
process_preds_for_implicit_parallelism(PredIdList,
|
|
ListCandidateCallSite, !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, [],
|
|
_ListCandidateCallSite, !ModuleInfo).
|
|
process_procs_for_implicit_parallelism(PredId, [ ProcId | ProcIds ],
|
|
ListCandidateCallSite, !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, ListCandidateCallSite, [],
|
|
CallSites),
|
|
list.length(CallSites, NumCallSites),
|
|
( NumCallSites = 0 ->
|
|
% No candidate calls for implicit parallelism in this procedure.
|
|
process_procs_for_implicit_parallelism(PredId, ProcIds,
|
|
ListCandidateCallSite, !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),
|
|
RecomputeAtomic = no,
|
|
recompute_instmap_delta_proc(RecomputeAtomic, 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,
|
|
ListCandidateCallSite, !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 | ListCandidateCallSite ],
|
|
!ResultAcc) :-
|
|
CandidateCallSite = candidate_call_site(CSSCaller, _, _, _),
|
|
( Caller = CSSCaller ->
|
|
!:ResultAcc = [ CandidateCallSite | !.ResultAcc ],
|
|
get_callees_feedback(Caller, ListCandidateCallSite, !ResultAcc)
|
|
;
|
|
get_callees_feedback(Caller, ListCandidateCallSite, !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, !CalleeListToBeParallelized, !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, !CalleeListToBeParallelized,
|
|
!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, !CalleeListToBeParallelized,
|
|
!SiteNumCounter)
|
|
;
|
|
GoalExpr0 = generic_call(Details, _, _, _),
|
|
(
|
|
Details = higher_order(_, _, _, _),
|
|
process_call_for_implicit_parallelism(!.Goal, ProcInfo,
|
|
!ModuleInfo, !IndexInConj, !MaybeConj,
|
|
!CalleeListToBeParallelized, !SiteNumCounter)
|
|
;
|
|
Details = class_method(_, _, _, _),
|
|
process_call_for_implicit_parallelism(!.Goal, ProcInfo,
|
|
!ModuleInfo, !IndexInConj, !MaybeConj,
|
|
!CalleeListToBeParallelized, !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, !CalleeListToBeParallelized,
|
|
!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, !CalleeListToBeParallelized,
|
|
!SiteNumCounter),
|
|
GoalProcessed = hlds_goal(disj(Goals), GoalInfo),
|
|
update_conj_and_index(!MaybeConj, GoalProcessed, !IndexInConj),
|
|
% If we are not in a conjunction, then we need to return the modified
|
|
% value of Goal. In we are in a conjunction, that information is not
|
|
% read (see process_conj_for_implicit_parallelism).
|
|
!:Goal = GoalProcessed
|
|
;
|
|
GoalExpr0 = switch(Var, CanFail, Cases0),
|
|
process_switch_cases_for_implicit_parallelism(Cases0, [], Cases,
|
|
ProcInfo, !ModuleInfo, !CalleeListToBeParallelized,
|
|
!SiteNumCounter),
|
|
GoalProcessed = hlds_goal(switch(Var, CanFail, Cases), GoalInfo),
|
|
update_conj_and_index(!MaybeConj, GoalProcessed, !IndexInConj),
|
|
!:Goal = GoalProcessed
|
|
;
|
|
GoalExpr0 = negation(Goal0),
|
|
process_goal_for_implicit_parallelism(Goal0, Goal, ProcInfo,
|
|
!ModuleInfo, !MaybeConj, !IndexInConj, !CalleeListToBeParallelized,
|
|
!SiteNumCounter),
|
|
GoalProcessed = hlds_goal(negation(Goal), GoalInfo),
|
|
update_conj_and_index(!MaybeConj, GoalProcessed, !IndexInConj),
|
|
!:Goal = GoalProcessed
|
|
;
|
|
GoalExpr0 = scope(Reason, Goal0),
|
|
% 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, _, !CalleeListToBeParallelized,
|
|
!SiteNumCounter),
|
|
GoalProcessed = hlds_goal(scope(Reason, Goal), GoalInfo),
|
|
update_conj_and_index(!MaybeConj, GoalProcessed, !IndexInConj),
|
|
!:Goal = GoalProcessed
|
|
;
|
|
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
|
|
process_goal_for_implicit_parallelism(Cond0, Cond, ProcInfo,
|
|
!ModuleInfo, no, _, 0, _, !CalleeListToBeParallelized,
|
|
!SiteNumCounter),
|
|
process_goal_for_implicit_parallelism(Then0, Then, ProcInfo,
|
|
!ModuleInfo, no, _, 0, _, !CalleeListToBeParallelized,
|
|
!SiteNumCounter),
|
|
process_goal_for_implicit_parallelism(Else0, Else, ProcInfo,
|
|
!ModuleInfo, no, _, 0, _, !CalleeListToBeParallelized,
|
|
!SiteNumCounter),
|
|
GoalProcessed = hlds_goal(if_then_else(Vars, Cond, Then, Else),
|
|
GoalInfo),
|
|
update_conj_and_index(!MaybeConj, GoalProcessed, !IndexInConj),
|
|
!:Goal = GoalProcessed
|
|
;
|
|
GoalExpr0 = shorthand(_),
|
|
increment_index_if_in_conj(!.MaybeConj, !IndexInConj)
|
|
).
|
|
|
|
% 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, !CalleeListToBeParallelized, !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,
|
|
!.CalleeListToBeParallelized, [], !:CalleeListToBeParallelized)
|
|
->
|
|
(
|
|
build_goals_surrounded_by_calls_to_be_parallelized(ConjGoals0,
|
|
!.ModuleInfo, [ Call ], Goals, !.IndexInConj + 1, End,
|
|
!SiteNumCounter, !CalleeListToBeParallelized)
|
|
->
|
|
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")
|
|
;
|
|
Details = cast(_),
|
|
unexpected(this_file, "get_call_kind_and_callee")
|
|
)
|
|
;
|
|
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,
|
|
ListCandidateCallSite, !ResultAcc) :-
|
|
(
|
|
ListCandidateCallSite = [],
|
|
fail
|
|
;
|
|
ListCandidateCallSite = [ CandidateCallSite |
|
|
ListCandidateCallSite0 ],
|
|
CandidateCallSite = 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
|
|
->
|
|
list.append(!.ResultAcc, ListCandidateCallSite0, !:ResultAcc)
|
|
;
|
|
list.append(!.ResultAcc, [ CandidateCallSite ], !:ResultAcc),
|
|
is_in_css_list_to_be_parallelized(Kind, SlotNumber, CalleeRawId,
|
|
ListCandidateCallSite0, !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, !CalleeListToBeParallelized) :-
|
|
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
|
|
;
|
|
( goal_is_conjunction(Goal, parallel_conj) ->
|
|
list.append(!.ResultAcc, [ Goal ], !:ResultAcc)
|
|
;
|
|
( 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, !.CalleeListToBeParallelized, [],
|
|
!:CalleeListToBeParallelized)
|
|
->
|
|
list.append(!.ResultAcc, [ Goal ], !:ResultAcc)
|
|
;
|
|
list.append(!.ResultAcc, [ Goal ], !:ResultAcc),
|
|
!:Index = !.Index + 1,
|
|
build_goals_surrounded_by_calls_to_be_parallelized(
|
|
ConjGoals, ModuleInfo, !ResultAcc, !Index,
|
|
!SiteNumCounter, !CalleeListToBeParallelized)
|
|
)
|
|
;
|
|
list.append(!.ResultAcc, [ Goal ], !:ResultAcc),
|
|
!:Index = !.Index + 1,
|
|
build_goals_surrounded_by_calls_to_be_parallelized(
|
|
ConjGoals, ModuleInfo, !ResultAcc, !Index,
|
|
!SiteNumCounter, !CalleeListToBeParallelized)
|
|
)
|
|
)
|
|
)
|
|
).
|
|
|
|
% 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.
|
|
%
|
|
:- 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)
|
|
),
|
|
list.append(GoalsFront, GoalsInBetween,
|
|
GoalsFrontWithBetween),
|
|
list.append(GoalsFrontWithBetween, [ ParallelGoal ],
|
|
GoalsWithoutBack),
|
|
list.append(GoalsWithoutBack, GoalsBack, ConjGoals),
|
|
!: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(hlds_goal(_, GoalInfo1), hlds_goal(_, GoalInfo2)) :-
|
|
goal_info_get_instmap_delta(GoalInfo1, InstmapDelta1),
|
|
instmap_delta_changed_vars(InstmapDelta1, ChangedVars1),
|
|
goal_info_get_nonlocals(GoalInfo2, NonLocals2),
|
|
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, !CalleeListToBeParallelized, !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,
|
|
!CalleeListToBeParallelized, !SiteNumCounter),
|
|
( MaybeConj = yes(GoalExprProcessed) ->
|
|
!:GoalExpr = GoalExprProcessed
|
|
;
|
|
unexpected(this_file, "process_conj_for_implicit_parallelism")
|
|
),
|
|
process_conj_for_implicit_parallelism(!GoalExpr, IndexInConj0,
|
|
ProcInfo, !ModuleInfo, !CalleeListToBeParallelized,
|
|
!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, !CalleeListToBeParallelized, !SiteNumCounter).
|
|
process_disj_for_implicit_parallelism([ Goal0 | Goals ], !GoalsAcc,
|
|
ProcInfo, !ModuleInfo, !CalleeListToBeParallelized, !SiteNumCounter) :-
|
|
process_goal_for_implicit_parallelism(Goal0, Goal, ProcInfo,
|
|
!ModuleInfo, no, _, 0, _, !CalleeListToBeParallelized, !SiteNumCounter),
|
|
list.append(!.GoalsAcc, [ Goal ], !:GoalsAcc),
|
|
process_disj_for_implicit_parallelism(Goals, !GoalsAcc, ProcInfo,
|
|
!ModuleInfo, !CalleeListToBeParallelized, !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, !CalleeListToBeParallelized, !SiteNumCounter).
|
|
process_switch_cases_for_implicit_parallelism([ Case0 | Cases ], !CasesAcc,
|
|
ProcInfo, !ModuleInfo, !CalleeListToBeParallelized, !SiteNumCounter) :-
|
|
Case0 = case(Functor, Goal0),
|
|
process_goal_for_implicit_parallelism(Goal0, Goal, ProcInfo,
|
|
!ModuleInfo, no, _, 0, _, !CalleeListToBeParallelized, !SiteNumCounter),
|
|
list.append(!.CasesAcc, [ case(Functor, Goal) ], !:CasesAcc),
|
|
process_switch_cases_for_implicit_parallelism(Cases, !CasesAcc,
|
|
ProcInfo, !ModuleInfo, !CalleeListToBeParallelized, !SiteNumCounter).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Parse the feedback file (header and body).
|
|
%
|
|
:- pred parse_feedback_file(string::in,
|
|
maybe_error(list(candidate_call_site))::out, io::di, io::uo) is det.
|
|
|
|
parse_feedback_file(InputFile, MaybeListCandidateCallSite, !IO) :-
|
|
io.open_input(InputFile, Result, !IO),
|
|
(
|
|
Result = io.error(ErrInput),
|
|
MaybeListCandidateCallSite = error(io.error_message(ErrInput))
|
|
;
|
|
Result = ok(Stream),
|
|
io.read_file_as_string(Stream, MaybeFileAsString, !IO),
|
|
(
|
|
MaybeFileAsString = ok(FileAsString),
|
|
LineList = string.words_separator(is_carriage_return,
|
|
FileAsString),
|
|
process_header(LineList, MaybeBodyFileAsListString, !IO),
|
|
(
|
|
MaybeBodyFileAsListString = error(ErrProcessHeader),
|
|
MaybeListCandidateCallSite = error(ErrProcessHeader)
|
|
;
|
|
MaybeBodyFileAsListString = ok(BodyFileAsListString),
|
|
process_body(BodyFileAsListString, MaybeListCandidateCallSite)
|
|
)
|
|
;
|
|
MaybeFileAsString = error(_, ErrReadFileAsString),
|
|
MaybeListCandidateCallSite =
|
|
error(io.error_message(ErrReadFileAsString))
|
|
),
|
|
io.close_input(Stream, !IO)
|
|
).
|
|
|
|
:- pred is_carriage_return(char::in) is semidet.
|
|
|
|
is_carriage_return(Char) :-
|
|
Char = '\n'.
|
|
|
|
% Process the header of the feedback file.
|
|
%
|
|
:- pred process_header(list(string)::in, maybe_error(list(string))::out,
|
|
io::di, io::uo) is det.
|
|
|
|
process_header(FileAsListString, MaybeFileAsListStringWithoutHeader, !IO) :-
|
|
( list.index0(FileAsListString, 0, Type) ->
|
|
( Type = "Profiling feedback file" ->
|
|
(list.index0(FileAsListString, 1, Version) ->
|
|
( Version = "Version = 1.0" ->
|
|
list.det_split_list(4, FileAsListString, _,
|
|
FileAsListStringWithoutHeader),
|
|
MaybeFileAsListStringWithoutHeader =
|
|
ok(FileAsListStringWithoutHeader)
|
|
;
|
|
MaybeFileAsListStringWithoutHeader = error("Profiling" ++
|
|
" feedback file version incorrect")
|
|
)
|
|
;
|
|
MaybeFileAsListStringWithoutHeader = error("Not a profiling"
|
|
++ " feedback file")
|
|
)
|
|
;
|
|
MaybeFileAsListStringWithoutHeader = error("Not a profiling" ++
|
|
" feedback file")
|
|
)
|
|
;
|
|
MaybeFileAsListStringWithoutHeader = error("Not a profiling feedback"
|
|
++ " file")
|
|
).
|
|
|
|
% Process the body of the feedback file.
|
|
%
|
|
:- pred process_body(list(string)::in,
|
|
maybe_error(list(candidate_call_site))::out) is det.
|
|
|
|
process_body(CoreFileAsListString, MaybeListCandidateCallSite) :-
|
|
( process_body2(CoreFileAsListString, [], ListCandidateCallSite) ->
|
|
MaybeListCandidateCallSite = ok(ListCandidateCallSite)
|
|
;
|
|
MaybeListCandidateCallSite = error("Profiling feedback file is not"
|
|
++ " well-formed")
|
|
).
|
|
|
|
:- pred process_body2(list(string)::in, list(candidate_call_site)::in,
|
|
list(candidate_call_site)::out) is semidet.
|
|
|
|
process_body2([], !ListCandidateCallSiteAcc).
|
|
process_body2([ Line | Lines ], !ListCandidateCallSiteAcc) :-
|
|
Words = string.words_separator(is_whitespace, Line),
|
|
list.index0_det(Words, 0, Caller),
|
|
( Caller = "Mercury" ->
|
|
process_body2(Lines, !ListCandidateCallSiteAcc)
|
|
;
|
|
list.index0_det(Words, 1, SlotNumber),
|
|
string.to_int(SlotNumber, IntSlotNumber),
|
|
list.index0_det(Words, 2, KindAsString),
|
|
( construct_call_site_kind(KindAsString, Kind) ->
|
|
( Kind = csk_normal ->
|
|
list.index0_det(Words, 3, Callee),
|
|
CandidateCallSite = candidate_call_site(Caller, IntSlotNumber,
|
|
Kind, Callee)
|
|
;
|
|
CandidateCallSite = candidate_call_site(Caller, IntSlotNumber,
|
|
Kind, "")
|
|
)
|
|
;
|
|
% Unexpected call site kind.
|
|
unexpected(this_file, "process_body2")
|
|
),
|
|
!:ListCandidateCallSiteAcc = [ CandidateCallSite |
|
|
!.ListCandidateCallSiteAcc ],
|
|
process_body2(Lines, !ListCandidateCallSiteAcc)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func this_file = string.
|
|
|
|
this_file = "implicit_parallelism.m".
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module transform_hlds.implicit_parallelism.
|
|
%-----------------------------------------------------------------------------%
|