%---------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %---------------------------------------------------------------------------% % Copyright (C) 2005-2007, 2010-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 rbmm.live_variable_analysis.m. % Main author: Quan Phan. % % This module implements the live variable analysis. % %---------------------------------------------------------------------------% :- module transform_hlds.rbmm.live_variable_analysis. :- interface. :- import_module hlds. :- import_module hlds.hlds_module. :- import_module transform_hlds.rbmm.region_liveness_info. % Collects live variable sets. % :- pred live_variable_analysis(module_info::in, execution_path_table::in, proc_pp_varset_table::out, proc_pp_varset_table::out, proc_pp_varset_table::out) is det. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. :- import_module hlds.hlds_goal. :- import_module hlds.hlds_pred. :- import_module parse_tree. :- import_module parse_tree.prog_data. :- import_module parse_tree.var_table. :- import_module transform_hlds.smm_common. :- import_module assoc_list. :- import_module bool. :- import_module list. :- import_module map. :- import_module pair. :- import_module require. :- import_module set. :- import_module string. :- import_module term. %---------------------------------------------------------------------------% % % Live variable analysis % % For each procedure, compute the sets of live variables before and after % each program point. % Currently, it also calculates set of void variables (i.e., ones whose names % start with "_") after each program point. Those variables are considered % dead at that point. % live_variable_analysis(ModuleInfo, ExecPathTable, LVBeforeTable, LVAfterTable, VoidVarTable) :- module_info_get_valid_pred_ids(ModuleInfo, PredIds), map.init(LVBeforeTable0), map.init(LVAfterTable0), map.init(VoidVarTable0), list.foldl3(live_variable_analysis_pred(ModuleInfo, ExecPathTable), PredIds, LVBeforeTable0, LVBeforeTable, LVAfterTable0, LVAfterTable, VoidVarTable0, VoidVarTable). :- pred live_variable_analysis_pred(module_info::in, execution_path_table::in, pred_id::in, proc_pp_varset_table::in, proc_pp_varset_table::out, proc_pp_varset_table::in, proc_pp_varset_table::out, proc_pp_varset_table::in, proc_pp_varset_table::out) is det. live_variable_analysis_pred(ModuleInfo, ExecPathTable, PredId, !LVBeforeTable, !LVAfterTable, !VoidVarTable) :- module_info_pred_info(ModuleInfo, PredId, PredInfo), ProcIds = pred_info_valid_non_imported_procids(PredInfo), list.foldl3( live_variable_analysis_proc(ModuleInfo, ExecPathTable, PredId), ProcIds, !LVBeforeTable, !LVAfterTable, !VoidVarTable). :- pred live_variable_analysis_proc(module_info::in, execution_path_table::in, pred_id::in, proc_id::in, proc_pp_varset_table::in, proc_pp_varset_table::out, proc_pp_varset_table::in, proc_pp_varset_table::out, proc_pp_varset_table::in, proc_pp_varset_table::out) is det. live_variable_analysis_proc(ModuleInfo, ExecPathTable, PredId, ProcId, !LVBeforeTable, !LVAfterTable, !VoidVarTable) :- PPId = proc(PredId, ProcId), ( if some_are_special_preds([PPId], ModuleInfo) then true else module_info_proc_info(ModuleInfo, PPId, ProcInfo), find_input_output_args(ModuleInfo, ProcInfo, Inputs, Outputs), map.lookup(ExecPathTable, PPId, ExecPaths), live_variable_analysis_exec_paths(ExecPaths, Inputs, Outputs, ModuleInfo, ProcInfo, map.init, ProcLVBefore, map.init, ProcLVAfter, map.init, ProcVoidVar), map.set(PPId, ProcLVBefore, !LVBeforeTable), map.set(PPId, ProcLVAfter, !LVAfterTable), map.set(PPId, ProcVoidVar, !VoidVarTable) ). :- pred live_variable_analysis_exec_paths(list(execution_path)::in, list(prog_var)::in, list(prog_var)::in, module_info::in, proc_info::in, pp_varset_table::in, pp_varset_table::out, pp_varset_table::in, pp_varset_table::out, pp_varset_table::in, pp_varset_table::out) is det. % Live variable analysis is backward, so we reverse the execution path % before starting. We have specific treatment for execution paths with % only one program point, which means the last program point is also the % first one. % live_variable_analysis_exec_paths([], _, _, _, _, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar). live_variable_analysis_exec_paths([ExecPath0 | ExecPaths], Inputs, Outputs, ModuleInfo, ProcInfo, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar) :- list.reverse(ExecPath0, ExecPath), ( if list.length(ExecPath) = 1 then live_variable_analysis_singleton_exec_path(ExecPath, Inputs, Outputs, ModuleInfo, ProcInfo, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar) else % Start with the last program point. live_variable_analysis_exec_path(ExecPath, Inputs, Outputs, ModuleInfo, ProcInfo, yes, set.init, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar) ), live_variable_analysis_exec_paths(ExecPaths, Inputs, Outputs, ModuleInfo, ProcInfo, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar). :- pred live_variable_analysis_exec_path(execution_path::in, list(prog_var)::in, list(prog_var)::in, module_info::in, proc_info::in, bool::in, set(prog_var)::in, pp_varset_table::in, pp_varset_table::out, pp_varset_table::in, pp_varset_table::out, pp_varset_table::in, pp_varset_table::out) is det. live_variable_analysis_exec_path([], _, _, _, _,_, _, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar). % XXX Exactly what piece of code does this comment apply to? % Process the last program point in an execution path. The live variable % set of the last program point is always the set of output variables % of the procedure. % live_variable_analysis_exec_path([(LastProgPoint - Goal) | ProgPointGoals], Inputs, Outputs, ModuleInfo, ProcInfo, yes, _LVBeforeNext, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar) :- ( if map.search(!.ProcLVAfter, LastProgPoint, LVAfterLast0) then LVAfterLast = LVAfterLast0 else LVAfterLast = set.list_to_set(Outputs), map.set(LastProgPoint, LVAfterLast, !ProcLVAfter) ), % Compute live variable before this last program point. compute_useds_produceds(ModuleInfo, Goal, UsedSet, ProducedSet), set.union(set.difference(LVAfterLast, ProducedSet), UsedSet, LVBeforeLastInThisExecPath), record_live_vars_at_prog_point(LastProgPoint, LVBeforeLastInThisExecPath, !ProcLVBefore), % Collect void variables after this program point. collect_void_vars(LastProgPoint, ProducedSet, ProcInfo, !ProcVoidVar), live_variable_analysis_exec_path(ProgPointGoals, Inputs, Outputs, ModuleInfo, ProcInfo, no, LVBeforeLastInThisExecPath, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar). % Process a middle program point. % live_variable_analysis_exec_path( [(ProgPoint - Goal), ProgPointGoal | ProgPointGoals], Inputs, Outputs, ModuleInfo, ProcInfo, no, LVBeforeNext, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar) :- % The live variable set after this program point is the union of the % live variable sets after it in all execution paths to which it belongs. record_live_vars_at_prog_point(ProgPoint, LVBeforeNext, !ProcLVAfter), % Compute LV before this program point. compute_useds_produceds(ModuleInfo, Goal, UsedSet, ProducedSet), set.union(set.difference(LVBeforeNext, ProducedSet), UsedSet, LVBeforeInThisExecPath), record_live_vars_at_prog_point(ProgPoint, LVBeforeInThisExecPath, !ProcLVBefore), % Collect void variables after this program point. collect_void_vars(ProgPoint, ProducedSet, ProcInfo, !ProcVoidVar), live_variable_analysis_exec_path([ProgPointGoal | ProgPointGoals], Inputs, Outputs, ModuleInfo, ProcInfo, no, LVBeforeInThisExecPath, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar). % The live variable set before the first program point is ALWAYS % Inputs. % live_variable_analysis_exec_path([FirstProgPoint - Goal], Inputs, _Outputs, ModuleInfo, ProcInfo, no, LVBeforeNext, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar) :- ( if map.search(!.ProcLVBefore, FirstProgPoint, _LVBeforeFirst) then true else LVBeforeFirst = set.list_to_set(Inputs), map.set(FirstProgPoint, LVBeforeFirst, !ProcLVBefore) ), % Live variable set after the first program point. record_live_vars_at_prog_point(FirstProgPoint, LVBeforeNext, !ProcLVAfter), % Collect void vars after this program point. compute_useds_produceds(ModuleInfo, Goal, _UsedSet, ProducedSet), collect_void_vars(FirstProgPoint, ProducedSet, ProcInfo, !ProcVoidVar). % This predicate analyses execution paths with only one program point. % So it must be called in a context that matches that condition. % :- pred live_variable_analysis_singleton_exec_path(execution_path::in, list(prog_var)::in, list(prog_var)::in, module_info::in, proc_info::in, pp_varset_table::in, pp_varset_table::out, pp_varset_table::in, pp_varset_table::out, pp_varset_table::in, pp_varset_table::out) is det. live_variable_analysis_singleton_exec_path([ProgPoint - Goal | _], Inputs, Outputs, ModuleInfo, ProcInfo, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar) :- LVBefore = set.list_to_set(Inputs), map.set(ProgPoint, LVBefore, !ProcLVBefore), LVAfter = set.list_to_set(Outputs), map.set(ProgPoint, LVAfter, !ProcLVAfter), % Collect void vars after this program point. compute_useds_produceds(ModuleInfo, Goal, _UsedSet, ProducedSet), collect_void_vars(ProgPoint, ProducedSet, ProcInfo, !ProcVoidVar). live_variable_analysis_singleton_exec_path([], _, _, _, _, !ProcLVBefore, !ProcLVAfter, !ProcVoidVar) :- unexpected($pred, "empty list"). % A variable is live at a program point if it is live in one of % the execution paths that covers the program point. % Therefore we need to union the existing live variable set at a program % point with the newly found. % :- pred record_live_vars_at_prog_point(program_point::in, variable_set::in, pp_varset_table::in, pp_varset_table::out) is det. record_live_vars_at_prog_point(ProgPoint, LV, !ProcLV) :- ( if map.search(!.ProcLV, ProgPoint, ExistingLV) then map.set(ProgPoint, set.union(ExistingLV, LV), !ProcLV) else map.set(ProgPoint, LV, !ProcLV) ). % Compute used and produced variables in an atomic goal, which % has been recorded alongside a program point in an execution_path. % A variable is used in an atomic goal if it is input to the goal. % It is produced in the atomic goal if it is output of the goal. % :- pred compute_useds_produceds(module_info::in, hlds_goal::in, variable_set::out, variable_set::out) is det. compute_useds_produceds(ModuleInfo, Goal, UsedSet, ProducedSet) :- ( if % a removed switch Goal = hlds_goal(switch(Var, _, _), _SwitchInfo) then Useds = [Var], Produceds = [] else Goal = hlds_goal(Expr, _Info), ( if Expr = plain_call(CalleePredId, CalleeProcId, Args, _BuiltIn, _Context, _Name) then get_inputs_outputs_proc_call(Args, proc(CalleePredId, CalleeProcId), ModuleInfo, Useds, Produceds) else if Expr = unify(_, _, _, Unification, _) then get_inputs_outputs_unification(Unification, Useds, Produceds) else if ( Expr = conj(_, []) ; Expr = disj([]) ) then Useds = [], Produceds = [] else unexpected($pred, "the expression must be either call, unify, true, or fail") ) ), set.list_to_set(Useds, UsedSet), set.list_to_set(Produceds, ProducedSet). % Divide the variables appearing in a unification into lists of input % variables and output variables. % :- pred get_inputs_outputs_unification(unification::in, list(prog_var)::out, list(prog_var)::out) is det. get_inputs_outputs_unification(construct(LVar, _, Args, _, _, _, _), Args, [LVar]). get_inputs_outputs_unification(deconstruct(LVar, _, Args, _, _, _), [LVar], Args). get_inputs_outputs_unification(assign(LVar, RVar), [RVar], [LVar]). get_inputs_outputs_unification(simple_test(LVar, RVar), [LVar, RVar], []). get_inputs_outputs_unification(complicated_unify(_, _, _), [], []). % Divide the arguments in a procedure call into lists of input % variables and output variables. % :- pred get_inputs_outputs_proc_call(list(prog_var)::in, pred_proc_id::in, module_info::in, list(prog_var)::out, list(prog_var)::out) is det. get_inputs_outputs_proc_call(ActualArgs, CalleeId, ModuleInfo, ActualInputs, ActualOutputs) :- module_info_pred_proc_info(ModuleInfo, CalleeId, _PredInfo, CalleeInfo), find_input_output_args(ModuleInfo, CalleeInfo, Inputs, Outputs), proc_info_get_headvars(CalleeInfo, FormalArgs), get_inputs_outputs_proc_call_2(FormalArgs, ActualArgs, Inputs, Outputs, [], ActualInputs, [], ActualOutputs). :- pred get_inputs_outputs_proc_call_2(list(prog_var)::in, list(prog_var)::in, list(prog_var)::in, list(prog_var)::in, list(prog_var)::in, list(prog_var)::out, list(prog_var)::in, list(prog_var)::out) is det. get_inputs_outputs_proc_call_2([], [], _, _, !ActualInputs, !ActualOutputs). get_inputs_outputs_proc_call_2([], [_ | _], _, _, !ActualInputs, !ActualOutputs) :- unexpected($pred, "mismatched lists"). get_inputs_outputs_proc_call_2([_ | _], [], _, _, !ActualInputs, !ActualOutputs) :- unexpected($pred, "mismatched lists"). get_inputs_outputs_proc_call_2([FormalArg | FormalArgs], [ActualArg | ActualArgs], Inputs, Outputs, !ActualInputs, !ActualOutputs) :- ( if list.member(FormalArg, Inputs) then % This formal argument is an input, so the correspondig argument % is an actual input argument. ActualInputs1 = [ActualArg | !.ActualInputs], ActualOutputs1 = !.ActualOutputs else if list.member(FormalArg, Outputs) then % This formal argument is an output, so the corresponding argument % is an actual output argument. ActualOutputs1 = [ActualArg | !.ActualOutputs], ActualInputs1 = !.ActualInputs else % This formal param is neither an output nor an input, so ignore % the corresponding arg. ActualInputs1 = !.ActualInputs, ActualOutputs1 = !.ActualOutputs ), get_inputs_outputs_proc_call_2(FormalArgs, ActualArgs, Inputs, Outputs, ActualInputs1, !:ActualInputs, ActualOutputs1, !:ActualOutputs). % Collect variables whose names start with _, i.e., void variables. % I am considering those variables dead right after created in the live % variable and region analyses. % :- pred collect_void_vars(program_point::in, variable_set::in, proc_info::in, pp_varset_table::in, pp_varset_table::out) is det. collect_void_vars(ProgPoint, ProducedSet, ProcInfo, !ProcVoidVar) :- ( if map.search(!.ProcVoidVar, ProgPoint, _DeadVars) then true else proc_info_get_var_table(ProcInfo, VarTable), set.fold(void_var(VarTable), ProducedSet, set.init, VoidVars), map.set(ProgPoint, VoidVars, !ProcVoidVar) ). % To be used with the fold above: if Var is a void variable, % add it to VoidVars set. % :- pred void_var(var_table::in, prog_var::in, variable_set::in, variable_set::out) is det. void_var(VarTable, Var, !VoidVars) :- lookup_var_entry(VarTable, Var, VarEntry), ( if string.index(VarEntry ^ vte_name, 0, '_') then set.insert(Var, !VoidVars) else true ). %---------------------------------------------------------------------------% :- end_module transform_hlds.rbmm.live_variable_analysis. %---------------------------------------------------------------------------%