%------------------------------------------------------------------------------% % vim: ft=mercury ff=unix ts=4 sw=4 et %------------------------------------------------------------------------------% % Copyright (C) 2006-2011 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: structure_reuse.versions.m. % Main authors: nancy. % % Provide the functionality to create optimised versions of those procedures % for which reuse was detected. % %------------------------------------------------------------------------------% :- module structure_reuse.versions. :- interface. :- import_module hlds.hlds_module. :- import_module hlds.hlds_pred. :- import_module transform_hlds.ctgc.structure_reuse.domain. %------------------------------------------------------------------------------% % For each of the entries in the reuse table: % * if the listed reuse is conditional, then duplicate the % pred-info/proc-info of the original procedure, changing all potential % reuse annotation to real reuses; % * if the listed reuse is unconditional, then no duplication is needed, % yet the goal needs to be traversed to correctly replace all % procedure calls to calls to reuse versions whenever needed. % * if the listed reuse is "no reuse", then obviously, nothing needs to % be done. % % This process updates the module information by adding the new predicates, % and recording the pred-proc-id to the reuse pred-proc-id mappings in % module_info. % :- pred create_reuse_procedures(reuse_as_table::in, reuse_as_table::out, module_info::in, module_info::out) is det. % Create a copy of the predicate/procedure information specified by the % given pred_proc_id, and return the pred_proc_id of that copy. The copy % is not altered w.r.t. structure reuse. It is a plain copy, nothing more % than that. % :- pred create_fresh_pred_proc_info_copy(pred_proc_id::in, no_clobber_args::in, pred_proc_id::out, module_info::in, module_info::out) is det. % Create a fake reuse procedure that simply calls the non-reuse procedure. % :- pred create_fake_reuse_procedure(pred_proc_id::in, no_clobber_args::in, module_info::in, module_info::out) is det. %------------------------------------------------------------------------------% %------------------------------------------------------------------------------% :- implementation. :- import_module check_hlds. :- import_module check_hlds.mode_util. :- import_module hlds.hlds_goal. :- import_module hlds.passes_aux. :- import_module hlds.pred_table. :- import_module hlds.quantification. :- import_module libs.globals. :- import_module libs.options. :- import_module mdbcomp. :- import_module mdbcomp.prim_data. :- import_module parse_tree.prog_util. :- import_module transform_hlds.ctgc.structure_reuse.analysis. :- import_module bimap. :- import_module bool. :- import_module list. :- import_module map. :- import_module maybe. :- import_module require. :- import_module set. %------------------------------------------------------------------------------% :- type reuse_name == sym_name. :- func generate_reuse_name(module_info, pred_proc_id, list(int)) = reuse_name. generate_reuse_name(ModuleInfo, PPId, NoClobbers) = ReuseName :- PPId = proc(_, ProcId), module_info_pred_proc_info(ModuleInfo, PPId, PredInfo, _ProcInfo), PredModule = pred_info_module(PredInfo), PredOrFunc = pred_info_is_pred_or_func(PredInfo), PredName = pred_info_name(PredInfo), proc_id_to_int(ProcId, ProcInt), make_pred_name(PredModule, "ctgc", yes(PredOrFunc), PredName, newpred_structure_reuse(ProcInt, NoClobbers), ReuseName). %------------------------------------------------------------------------------% % This process can be split into separate steps: % - determine all the pred-proc-ids of procedure with conditional reuse; % - create duplicates of these procedures; % - traverse all these procedures + the procedures with unconditional reuse % to correctly update the reuse annotations. % create_reuse_procedures(!ReuseTable, !ModuleInfo) :- % Get the list of conditional reuse procedures already created. ExistingReusePPIds = bimap.coordinates(!.ReuseTable ^ reuse_version_map), ExistingReusePPIdsSet = set.from_list(ExistingReusePPIds), map.foldl2(divide_reuse_procs(ExistingReusePPIdsSet), !.ReuseTable ^ reuse_info_map, [], CondOrigPPIds, [], UncondOrigPPIds), % Create duplicates of the procedures which have conditional reuse. The % "intermediate" reuse procedures will already have been created during the % analysis, so this creates just the reuse versions where all possible % arguments are potentially reusable. list.map_foldl2(maybe_create_full_reuse_proc_copy, CondOrigPPIds, ReuseCondPPIds, !ModuleInfo, !ReuseTable), % Process all the goals to update the reuse annotations. In the reuse % versions of procedures we can take advantage of potential reuse % opportunities. list.foldl(check_cond_process_proc(convert_potential_reuse, !.ReuseTable), ReuseCondPPIds, !ModuleInfo), list.foldl(check_cond_process_proc(convert_potential_reuse, !.ReuseTable), ExistingReusePPIds, !ModuleInfo), % In the original procedures, only the unconditional reuse opportunities % can be taken. list.foldl(process_proc(leave_potential_reuse, !.ReuseTable), CondOrigPPIds, !ModuleInfo), list.foldl(process_proc(leave_potential_reuse, !.ReuseTable), UncondOrigPPIds, !ModuleInfo). % Separate procedures in the reuse table into those with some conditional % reuse opportunities, and those with only unconditional reuse. % Skip any procedure which is already a reuse copy of another procedure. % :- pred divide_reuse_procs(set(pred_proc_id)::in, pred_proc_id::in, reuse_as_and_status::in, list(pred_proc_id)::in, list(pred_proc_id)::out, list(pred_proc_id)::in, list(pred_proc_id)::out) is det. divide_reuse_procs(ExistingReusePPIdsSet, PPId, ReuseAs_Status, !CondPPIds, !UncondPPIds) :- ReuseAs_Status = reuse_as_and_status(ReuseAs, _), ( set.contains(ExistingReusePPIdsSet, PPId) -> true ; reuse_as_conditional_reuses(ReuseAs) -> !:CondPPIds = [PPId | !.CondPPIds] ; reuse_as_all_unconditional_reuses(ReuseAs) -> !:UncondPPIds = [PPId | !.UncondPPIds] ; reuse_as_no_reuses(ReuseAs) -> true ; unexpected(this_file, "divide_reuse_procs") ). :- pred maybe_create_full_reuse_proc_copy(pred_proc_id::in, pred_proc_id::out, module_info::in, module_info::out, reuse_as_table::in, reuse_as_table::out) is det. maybe_create_full_reuse_proc_copy(PPId, NewPPId, !ModuleInfo, !ReuseTable) :- NoClobbers = [], ( reuse_as_table_search_reuse_version_proc(!.ReuseTable, PPId, NoClobbers, _) -> unexpected(this_file, "maybe_create_full_reuse_proc_copy: procedure already exists") ; true ), create_fresh_pred_proc_info_copy(PPId, NoClobbers, NewPPId, !ModuleInfo), ( reuse_as_table_search(!.ReuseTable, PPId, ReuseAs_Status) -> reuse_as_table_set(NewPPId, ReuseAs_Status, !ReuseTable), reuse_as_table_insert_reuse_version_proc(PPId, NoClobbers, NewPPId, !ReuseTable) ; unexpected(this_file, "maybe_create_full_reuse_proc_copy: no reuse information") ). %------------------------------------------------------------------------------% create_fresh_pred_proc_info_copy(PPId, NoClobbers, NewPPId, !ModuleInfo) :- module_info_pred_proc_info(!.ModuleInfo, PPId, PredInfo0, ProcInfo0), ReusePredName = generate_reuse_name(!.ModuleInfo, PPId, NoClobbers), PPId = proc(PredId, _), create_fresh_pred_proc_info_copy_2(PredId, PredInfo0, ProcInfo0, ReusePredName, ReusePredInfo, ReuseProcId), NewPPId = proc(ReusePredId, ReuseProcId), module_info_get_predicate_table(!.ModuleInfo, PredTable0), predicate_table_insert(ReusePredInfo, ReusePredId, PredTable0, PredTable), module_info_set_predicate_table(PredTable, !ModuleInfo), module_info_get_structure_reuse_preds(!.ModuleInfo, ReusePreds0), set.insert(ReusePredId, ReusePreds0, ReusePreds), module_info_set_structure_reuse_preds(ReusePreds, !ModuleInfo). :- pred create_fresh_pred_proc_info_copy_2(pred_id::in, pred_info::in, proc_info::in, reuse_name::in, pred_info::out, proc_id::out) is det. create_fresh_pred_proc_info_copy_2(PredId, PredInfo, ProcInfo, ReusePredName, ReusePredInfo, ReuseProcId) :- ModuleName = pred_info_module(PredInfo), PredOrFunc = pred_info_is_pred_or_func(PredInfo), pred_info_get_context(PredInfo, ProgContext), pred_info_get_origin(PredInfo, PredOrigin), pred_info_get_import_status(PredInfo, ImportStatus0), % If the predicate was opt_imported then the specialised copy should be % local otherwise it will be eliminated by dead proc elimination. ( ImportStatus0 = status_opt_imported -> ImportStatus = status_local ; ImportStatus = ImportStatus0 ), pred_info_get_markers(PredInfo, PredMarkers), pred_info_get_arg_types(PredInfo, MerTypes), pred_info_get_typevarset(PredInfo, TVarset), pred_info_get_exist_quant_tvars(PredInfo, ExistQTVars), pred_info_get_class_context(PredInfo, ProgConstraints), pred_info_get_assertions(PredInfo, AssertIds), pred_info_get_var_name_remap(PredInfo, VarNameRemap), NewPredOrigin = origin_transformed(transform_structure_reuse, PredOrigin, PredId), pred_info_create(ModuleName, ReusePredName, PredOrFunc, ProgContext, NewPredOrigin, ImportStatus, PredMarkers, MerTypes, TVarset, ExistQTVars, ProgConstraints, AssertIds, VarNameRemap, ProcInfo, ReuseProcId, ReusePredInfo). %------------------------------------------------------------------------------% :- type convert_potential_reuse ---> convert_potential_reuse ; leave_potential_reuse. % When generating target code, we may find a set of reuse conditions on a % procedure which are *harsher* than the reuse conditions that we found % during the `--make-analysis-registry' step. This can happen due extra % analysis information gathered for other modules in the meantime. In that % case, we may have external callers to the procedure which have verified % only against the *laxer* reuse conditions. % % Hence we need to be careful that we don't generate any code which % violates the weaker reuse conditions. One (conservative) way to do that % is to ignore all the potential reuse annotations and only use the % unconditional reuse annotations. % % XXX the same problem occurs with `--intermodule-optimisation' % :- pred check_cond_process_proc(convert_potential_reuse::in, reuse_as_table::in, pred_proc_id::in, module_info::in, module_info::out) is det. check_cond_process_proc(ConvertPotentialReuse, ReuseTable, ReusePPId, !ModuleInfo) :- module_info_get_globals(!.ModuleInfo, Globals), globals.lookup_bool_option(Globals, intermodule_analysis, IntermodAnalysis), globals.lookup_bool_option(Globals, make_analysis_registry, MakeAnalysisReg), ( IntermodAnalysis = yes, MakeAnalysisReg = no -> structure_reuse_answer_harsher_than_in_analysis_registry(!.ModuleInfo, ReuseTable, ReusePPId, IsHarsher) ; IsHarsher = no ), ( IsHarsher = yes, % Ignoring potential reuse is equivalent to having only unconditional % structure reuse. process_proc(leave_potential_reuse, ReuseTable, ReusePPId, !ModuleInfo) ; IsHarsher = no, process_proc(ConvertPotentialReuse, ReuseTable, ReusePPId, !ModuleInfo) ). % Process the goal of the procedure with the given pred_proc_id so that % all potential reuses are replaced by real reuses, and all calls to % procedures that have a reuse version are replaced by calls to their % reuse version (if of course, that is in accordance with the reuse % annotations). % :- pred process_proc(convert_potential_reuse::in, reuse_as_table::in, pred_proc_id::in, module_info::in, module_info::out) is det. process_proc(ConvertPotentialReuse, ReuseTable, PPId, !ModuleInfo) :- trace [io(!IO)] ( write_proc_progress_message("(reuse version) ", PPId, !.ModuleInfo, !IO) ), some [!ProcInfo] ( module_info_pred_proc_info(!.ModuleInfo, PPId, PredInfo0, !:ProcInfo), pred_info_get_import_status(PredInfo0, ImportStatus), ( ImportStatus = status_imported(_) -> % The bodies may contain junk, so don't try to process. true ; proc_info_get_goal(!.ProcInfo, Goal0), process_goal(ConvertPotentialReuse, ReuseTable, !.ModuleInfo, Goal0, Goal), proc_info_set_goal(Goal, !ProcInfo), % A dead variable needs to appear in the non-local set of the % construction unification in which its space is reused, so we % requantify. Then we recompute instmap deltas with the updated % non-local sets. requantify_proc_general(ordinary_nonlocals_no_lambda, !ProcInfo), recompute_instmap_delta_proc( do_not_recompute_atomic_instmap_deltas, !ProcInfo, !ModuleInfo), module_info_set_pred_proc_info(PPId, PredInfo0, !.ProcInfo, !ModuleInfo) ) ). :- pred process_goal(convert_potential_reuse::in, reuse_as_table::in, module_info::in, hlds_goal::in, hlds_goal::out) is det. process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo, !Goal) :- !.Goal = hlds_goal(GoalExpr0, GoalInfo0), ( GoalExpr0 = conj(ConjType, Goals0), list.map(process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo), Goals0, Goals), GoalExpr = conj(ConjType, Goals), !:Goal = hlds_goal(GoalExpr, GoalInfo0) ; GoalExpr0 = plain_call(CalleePredId, CalleeProcId, Args, BI, UC, CalleePredName), ReuseDescription0 = goal_info_get_reuse(GoalInfo0), ( % If the reuse description already says "reuse", then this is % a call to a procedure which might have specified conditions, yet % whose conditions are always met, hence do not imply conditions on % the procedure in which this call appears. We must therefore % make sure to call the appropriate version of the called % procedure. ReuseDescription0 = reuse(reuse_call(_CondDescr, NoClobbers)) -> determine_reuse_version(ReuseTable, ModuleInfo, CalleePredId, CalleeProcId, CalleePredName, NoClobbers, ReuseCalleePredId, ReuseCalleeProcId, ReuseCalleePredName), GoalExpr = plain_call(ReuseCalleePredId, ReuseCalleeProcId, Args, BI, UC, ReuseCalleePredName), !:Goal = hlds_goal(GoalExpr, GoalInfo0) ; ReuseDescription0 = potential_reuse(reuse_call(CondDescr, NoClobbers)), ConvertPotentialReuse = convert_potential_reuse -> ConvertPotentialReuse = convert_potential_reuse, % Replace the call to the reuse version, and change the % potential reuse annotation to a real annotation. determine_reuse_version(ReuseTable, ModuleInfo, CalleePredId, CalleeProcId, CalleePredName, NoClobbers, ReuseCalleePredId, ReuseCalleeProcId, ReuseCalleePredName), GoalExpr = plain_call(ReuseCalleePredId, ReuseCalleeProcId, Args, BI, UC, ReuseCalleePredName), ReuseDescription = reuse(reuse_call(CondDescr, NoClobbers)), goal_info_set_reuse(ReuseDescription, GoalInfo0, GoalInfo), !:Goal = hlds_goal(GoalExpr, GoalInfo) ; true ) ; GoalExpr0 = generic_call(_, _, _, _) ; GoalExpr0 = unify(_, _, _, Unification0, _), ReuseDescription0 = goal_info_get_reuse(GoalInfo0), ( ( ReuseDescription0 = reuse(Descr) ; ReuseDescription0 = potential_reuse(Descr), ConvertPotentialReuse = convert_potential_reuse ), ReuseDescription = reuse(Descr), unification_set_reuse(Descr, Unification0, Unification), GoalExpr = GoalExpr0 ^ unify_kind := Unification, goal_info_set_reuse(ReuseDescription, GoalInfo0, GoalInfo), !:Goal = hlds_goal(GoalExpr, GoalInfo) ; ReuseDescription0 = potential_reuse(_), ConvertPotentialReuse = leave_potential_reuse ; ReuseDescription0 = no_reuse_info ; ReuseDescription0 = no_possible_reuse ; ReuseDescription0 = missed_reuse(_) ) ; GoalExpr0 = disj(Goals0), list.map(process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo), Goals0, Goals), GoalExpr = disj(Goals), !:Goal = hlds_goal(GoalExpr, GoalInfo0) ; GoalExpr0 = switch(A, B, Cases0), list.map(process_case(ConvertPotentialReuse, ReuseTable, ModuleInfo), Cases0, Cases), GoalExpr = switch(A, B, Cases), !:Goal = hlds_goal(GoalExpr, GoalInfo0) ; % XXX To check and compare with the theory. GoalExpr0 = negation(_Goal) ; GoalExpr0 = scope(Reason, SubGoal0), ( Reason = from_ground_term(_, from_ground_term_construct) -> true ; process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo, SubGoal0, SubGoal), GoalExpr = scope(Reason, SubGoal), !:Goal = hlds_goal(GoalExpr, GoalInfo0) ) ; GoalExpr0 = if_then_else(Vars, IfGoal0, ThenGoal0, ElseGoal0), process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo, IfGoal0, IfGoal), process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo, ThenGoal0, ThenGoal), process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo, ElseGoal0, ElseGoal), GoalExpr = if_then_else(Vars, IfGoal, ThenGoal, ElseGoal), !:Goal = hlds_goal(GoalExpr, GoalInfo0) ; GoalExpr0 = call_foreign_proc(_Attrs, _ForeignPredId, _ForeignProcId, _Args, _ExtraArgs, _MaybeTraceRuntimeCond, _Impl) ; GoalExpr0 = shorthand(_), % These should have been expanded out by now. unexpected(this_file, "process_goal: shorthand goal.") ). :- pred unification_set_reuse(short_reuse_description::in, unification::in, unification::out) is det. unification_set_reuse(ShortReuseDescription, !Unification) :- ( !.Unification = construct(A, B, C, D, HowToConstruct0, F, G), ShortReuseDescription = cell_reused(DeadVar, _, PossibleConsIds, CellsToUpdate) -> ( HowToConstruct0 = construct_statically, unexpected(this_file, "unification_set_reuse: construct_statically") ; HowToConstruct0 = construct_dynamically ; HowToConstruct0 = construct_in_region(_) ; HowToConstruct0 = reuse_cell(_) ), CellToReuse = cell_to_reuse(DeadVar, PossibleConsIds, CellsToUpdate), HowToConstruct = reuse_cell(CellToReuse), !:Unification = construct(A, B, C, D, HowToConstruct, F, G) ; true ). :- pred determine_reuse_version(reuse_as_table::in, module_info::in, pred_id::in, proc_id::in, sym_name::in, list(int)::in, pred_id::out, proc_id::out, reuse_name::out) is det. determine_reuse_version(ReuseTable, ModuleInfo, PredId, ProcId, PredName, NoClobbers, ReusePredId, ReuseProcId, ReusePredName) :- ( reuse_as_table_search_reuse_version_proc(ReuseTable, proc(PredId, ProcId), NoClobbers, Result) -> Result = proc(ReusePredId, ReuseProcId), module_info_pred_info(ModuleInfo, ReusePredId, ReusePredInfo), ModuleName = pred_info_module(ReusePredInfo), Name = pred_info_name(ReusePredInfo), ReusePredName = qualified(ModuleName, Name) ; ReusePredId = PredId, ReuseProcId = ProcId, ReusePredName = PredName ). :- pred process_case(convert_potential_reuse::in, reuse_as_table::in, module_info::in, case::in, case::out) is det. process_case(ConvertPotentialReuse, ReuseMap, ModuleInfo, Case0, Case) :- Case0 = case(MainConsId, OtherConsIds, Goal0), process_goal(ConvertPotentialReuse, ReuseMap, ModuleInfo, Goal0, Goal), Case = case(MainConsId, OtherConsIds, Goal). %------------------------------------------------------------------------------% create_fake_reuse_procedure(PPId, NoClobbers, !ModuleInfo) :- PPId = proc(PredId, ProcId), module_info_pred_proc_info(!.ModuleInfo, PPId, OldPredInfo, OldProcInfo), OldPredModule = pred_info_module(OldPredInfo), OldPredName = pred_info_name(OldPredInfo), proc_info_interface_determinism(OldProcInfo, Determinism), create_fresh_pred_proc_info_copy(PPId, NoClobbers, NewPPId, !ModuleInfo), some [!PredInfo, !ProcInfo] ( module_info_pred_proc_info(!.ModuleInfo, NewPPId, !:PredInfo, !:ProcInfo), proc_info_get_goal(!.ProcInfo, Body), Body = hlds_goal(_, GoalInfo0), proc_info_get_headvars(!.ProcInfo, HeadVars), GoalExpr = plain_call(PredId, ProcId, HeadVars, not_builtin, no, qualified(OldPredModule, OldPredName)), goal_info_set_determinism(Determinism, GoalInfo0, GoalInfo), Goal = hlds_goal(GoalExpr, GoalInfo), proc_info_set_goal(Goal, !ProcInfo), module_info_set_pred_proc_info(NewPPId, !.PredInfo, !.ProcInfo, !ModuleInfo) ). %------------------------------------------------------------------------------% :- func this_file = string. this_file = "structure_reuse.versions.m". %------------------------------------------------------------------------------% :- end_module structure_reuse.versions. %------------------------------------------------------------------------------%