mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 01:13:30 +00:00
2562 lines
105 KiB
Mathematica
2562 lines
105 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 1996-2012 The University of Melbourne.
|
|
% Copyright (C) 2014-2026 The Mercury team.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% File: unused_args.m.
|
|
% Main author: stayl.
|
|
%
|
|
% Detects and removes unused input arguments in procedures, especially
|
|
% type_infos.
|
|
%
|
|
% To enable the warnings use `--warn-unused-args'.
|
|
% To enable the optimisation use `--optimize-unused-args'.
|
|
%
|
|
% An argument is considered used if it (or any of its aliases) are
|
|
% - used in a call to a predicate external to the current module
|
|
% - used in a higher-order call
|
|
% - used to instantiate an output variable
|
|
% - involved in a simple test, switch or a semidet deconstruction
|
|
% - used as an argument to another predicate in this module which is used.
|
|
%
|
|
% When using typeinfo liveness, the following variables are also
|
|
% considered used:
|
|
% - a type-info (or part of a type-info) of a type parameter of the
|
|
% type of a variable that is used (for example, if a variable
|
|
% of type list(T) is used, then the type_info for T is used)
|
|
%
|
|
% The first step is to determine which arguments of which predicates are used
|
|
% locally to their predicate. For each unused argument, a set of other
|
|
% arguments that it depends on is built up.
|
|
%
|
|
% The next step is to iterate over the this map, checking for each unused
|
|
% argument whether any of the arguments it depends on has become used in the
|
|
% last iteration. Iterations are repeated until a fixpoint is reached.
|
|
%
|
|
% Warnings are then output. The warning message indicates which arguments are
|
|
% used in none of the modes of a predicate.
|
|
%
|
|
% The predicates are then fixed up. Unused variables and unifications are
|
|
% removed.
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module transform_hlds.unused_args.
|
|
:- interface.
|
|
|
|
:- import_module analysis.
|
|
:- import_module analysis.framework.
|
|
:- import_module hlds.
|
|
:- import_module hlds.hlds_module.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.error_spec.
|
|
:- import_module parse_tree.prog_item.
|
|
|
|
:- import_module list.
|
|
:- import_module set.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type maybe_gather_pragma_unused_args
|
|
---> do_not_gather_pragma_unused_args
|
|
; do_gather_pragma_unused_args.
|
|
|
|
:- type maybe_record_analysis_unused_args
|
|
---> do_not_record_analysis_unused_args
|
|
; do_record_analysis_unused_args.
|
|
|
|
:- pred unused_args_process_module(maybe_gather_pragma_unused_args::in,
|
|
maybe_record_analysis_unused_args::in,
|
|
list(error_spec)::out, set(gen_pragma_unused_args_info)::out,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Instances used by mmc_analysis.m.
|
|
%
|
|
|
|
:- type unused_args_func_info.
|
|
:- type unused_args_call.
|
|
:- type unused_args_answer.
|
|
|
|
:- instance analysis(unused_args_func_info, unused_args_call,
|
|
unused_args_answer).
|
|
|
|
:- instance partial_order(unused_args_func_info, unused_args_call).
|
|
:- instance call_pattern(unused_args_func_info, unused_args_call).
|
|
:- instance to_term(unused_args_call).
|
|
|
|
:- instance partial_order(unused_args_func_info, unused_args_answer).
|
|
:- instance answer_pattern(unused_args_func_info, unused_args_answer).
|
|
:- instance to_term(unused_args_answer).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module analysis.operations.
|
|
:- import_module check_hlds.
|
|
:- import_module check_hlds.inst_match.
|
|
:- import_module check_hlds.mode_test.
|
|
:- import_module hlds.goal_refs.
|
|
:- import_module hlds.goal_vars.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module hlds.hlds_markers.
|
|
:- import_module hlds.hlds_out.
|
|
:- import_module hlds.hlds_out.hlds_out_util.
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module hlds.hlds_proc_util.
|
|
:- import_module hlds.hlds_rtti.
|
|
:- import_module hlds.instmap.
|
|
:- import_module hlds.make_goal.
|
|
:- import_module hlds.passes_aux.
|
|
:- import_module hlds.pred_name.
|
|
:- import_module hlds.pred_table.
|
|
:- import_module hlds.quantification.
|
|
:- import_module hlds.status.
|
|
:- import_module hlds.type_util.
|
|
:- import_module libs.
|
|
:- import_module libs.globals.
|
|
:- import_module libs.maybe_util.
|
|
:- import_module libs.optimization_options.
|
|
:- import_module libs.options.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.prim_data.
|
|
:- import_module mdbcomp.sym_name.
|
|
:- import_module parse_tree.parse_tree_out_term.
|
|
:- import_module parse_tree.prog_data.
|
|
:- import_module parse_tree.prog_data_pragma.
|
|
:- import_module parse_tree.prog_parse_tree.
|
|
:- import_module parse_tree.prog_rename.
|
|
:- import_module parse_tree.prog_type_scan.
|
|
:- import_module parse_tree.prog_util.
|
|
:- import_module parse_tree.set_of_var.
|
|
:- import_module parse_tree.var_table.
|
|
:- import_module transform_hlds.mmc_analysis.
|
|
|
|
:- import_module bool.
|
|
:- import_module int.
|
|
:- import_module io.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module string.
|
|
:- import_module term.
|
|
:- import_module term_context.
|
|
:- import_module term_conversion.
|
|
:- import_module varset.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
unused_args_process_module(GatherPragmas, RecordAnalysis,
|
|
Specs, PragmaUnusedArgInfos, !ModuleInfo) :-
|
|
module_info_get_globals(!.ModuleInfo, Globals),
|
|
globals.lookup_bool_option(Globals, very_verbose, VeryVerbose),
|
|
init_global_var_usage_map(GlobalVarUsageMap0, FixpointPredProcIds,
|
|
NewProcMap0, !ModuleInfo),
|
|
% maybe_write_string(VeryVerbose, "% Finished initialisation.\n", !IO),
|
|
|
|
record_required_vars_as_used_to_fixpoint(0, !.ModuleInfo,
|
|
FixpointPredProcIds, GlobalVarUsageMap0, GlobalVarUsageMap),
|
|
% maybe_write_string(VeryVerbose, "% Finished analysis.\n", !IO),
|
|
|
|
map.init(UnusedArgInfo0),
|
|
get_unused_arg_info(!.ModuleInfo, GlobalVarUsageMap, FixpointPredProcIds,
|
|
UnusedArgInfo0, UnusedArgInfo),
|
|
|
|
map.keys(UnusedArgInfo, PredProcIdsToFix),
|
|
globals.lookup_bool_option(Globals, warn_unused_args, DoWarnBool),
|
|
( DoWarnBool = no, DoWarn = do_not_warn_unused_args
|
|
; DoWarnBool = yes, DoWarn = do_warn_unused_args
|
|
),
|
|
( if
|
|
( DoWarn = do_warn_unused_args
|
|
; GatherPragmas = do_gather_pragma_unused_args
|
|
)
|
|
then
|
|
set.init(WarnedPredIds0),
|
|
gather_warnings_and_pragmas(!.ModuleInfo, UnusedArgInfo,
|
|
DoWarn, GatherPragmas, PredProcIdsToFix, WarnedPredIds0,
|
|
[], Specs, set.init, PragmaUnusedArgInfos)
|
|
else
|
|
Specs = [],
|
|
set.init(PragmaUnusedArgInfos)
|
|
),
|
|
(
|
|
RecordAnalysis = do_record_analysis_unused_args,
|
|
module_info_get_analysis_info(!.ModuleInfo, AnalysisInfo0),
|
|
module_info_get_valid_pred_ids(!.ModuleInfo, PredIds),
|
|
list.foldl(
|
|
maybe_record_intermod_unused_args(!.ModuleInfo, UnusedArgInfo),
|
|
PredIds, AnalysisInfo0, AnalysisInfo1),
|
|
list.foldl(record_intermod_dependencies(!.ModuleInfo),
|
|
FixpointPredProcIds, AnalysisInfo1, AnalysisInfo),
|
|
module_info_set_analysis_info(AnalysisInfo, !ModuleInfo)
|
|
;
|
|
RecordAnalysis = do_not_record_analysis_unused_args
|
|
),
|
|
globals.get_opt_tuple(Globals, OptTuple),
|
|
OptUnusedArgs = OptTuple ^ ot_opt_unused_args,
|
|
(
|
|
OptUnusedArgs = opt_unused_args,
|
|
list.foldl2(unused_args_create_new_pred(UnusedArgInfo),
|
|
PredProcIdsToFix, NewProcMap0, NewProcMap, !ModuleInfo),
|
|
% maybe_write_string(VeryVerbose, "% Finished new preds.\n", !IO),
|
|
delete_unused_args_in_module(VeryVerbose, GlobalVarUsageMap,
|
|
FixpointPredProcIds, NewProcMap, !ModuleInfo),
|
|
% maybe_write_string(VeryVerbose, "% Fixed up goals.\n", !IO),
|
|
( if map.is_empty(NewProcMap) then
|
|
true
|
|
else
|
|
% The dependencies have changed, so any old dependency graph
|
|
% is now invalid.
|
|
module_info_clobber_dependency_info(!ModuleInfo)
|
|
)
|
|
;
|
|
OptUnusedArgs = do_not_opt_unused_args
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Initialisation section.
|
|
%
|
|
|
|
% Set initial status of all args of local procs by examining the
|
|
% module_info. PredProcList is the list of procedures to do the fixpoint
|
|
% iteration over.
|
|
%
|
|
% The reason why we take the module_info as a read/write argument
|
|
% instead of as a read-only argument is that we may need to update it
|
|
% to record analysis results.
|
|
%
|
|
:- pred init_global_var_usage_map(global_var_usage_map::out,
|
|
list(pred_proc_id)::out, new_proc_map::out,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
init_global_var_usage_map(GlobalVarUsageMap, FixpointPredProcIds, NewProcMap,
|
|
!ModuleInfo) :-
|
|
module_info_get_valid_pred_ids(!.ModuleInfo, PredIds),
|
|
init_global_var_usage_map_for_preds(PredIds, map.init, GlobalVarUsageMap,
|
|
[], FixpointPredProcIds, map.init, NewProcMap, !ModuleInfo).
|
|
|
|
% Setup args for the whole module.
|
|
%
|
|
:- pred init_global_var_usage_map_for_preds(list(pred_id)::in,
|
|
global_var_usage_map::in, global_var_usage_map::out,
|
|
list(pred_proc_id)::in, list(pred_proc_id)::out,
|
|
new_proc_map::in, new_proc_map::out,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
init_global_var_usage_map_for_preds([], !GlobalVarUsageMap,
|
|
!FixpointPredProcIds, !OptProcs, !ModuleInfo).
|
|
init_global_var_usage_map_for_preds([PredId | PredIds], !GlobalVarUsageMap,
|
|
!FixpointPredProcIds, !OptProcs, !ModuleInfo) :-
|
|
maybe_init_global_var_usage_map_for_pred(PredId, !GlobalVarUsageMap,
|
|
!FixpointPredProcIds, !OptProcs, !ModuleInfo),
|
|
init_global_var_usage_map_for_preds(PredIds, !GlobalVarUsageMap,
|
|
!FixpointPredProcIds, !OptProcs, !ModuleInfo).
|
|
|
|
% Setup args for the given predicate if required.
|
|
%
|
|
:- pred maybe_init_global_var_usage_map_for_pred(pred_id::in,
|
|
global_var_usage_map::in, global_var_usage_map::out,
|
|
list(pred_proc_id)::in, list(pred_proc_id)::out,
|
|
new_proc_map::in, new_proc_map::out,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
maybe_init_global_var_usage_map_for_pred(PredId, !GlobalVarUsageMap,
|
|
!FixpointPredProcIds, !OptProcs, !ModuleInfo) :-
|
|
module_info_pred_info(!.ModuleInfo, PredId, PredInfo),
|
|
( if
|
|
% Check whether we must (or should) consider all arguments
|
|
% of the predicate to be used.
|
|
(
|
|
% Builtins *do* use all their arguments.
|
|
pred_info_is_builtin(PredInfo)
|
|
;
|
|
% To avoid spurious warnings in their callers, we want to treat
|
|
% stub procedures (those which originally had no clauses)
|
|
% as if they use all of their arguments,
|
|
pred_info_get_markers(PredInfo, Markers),
|
|
marker_is_present(Markers, marker_stub)
|
|
;
|
|
% The method of a class instance must have the exact same set
|
|
% or arguments as the class method itself. We cannot delete
|
|
% an argument, even an unused argument, from an instance method
|
|
% without deleting that same argument from the class method,
|
|
% which we cannot do unless that argument is unused in *all*
|
|
% instances of the class, a condition we cannot check here.
|
|
%
|
|
% The above is why we cannot *replace* a class or instance method
|
|
% procedure with their unused-arg-optimized clone. We *could* make
|
|
% a class or instance method *forward* its job to such a clone,
|
|
% but since class and instance methods cannot be recursive,
|
|
% there would be no point.
|
|
pred_info_get_origin(PredInfo, Origin),
|
|
Origin = origin_user(OriginUser),
|
|
( OriginUser = user_made_class_method(_, _)
|
|
; OriginUser = user_made_instance_method(_, _)
|
|
)
|
|
)
|
|
then
|
|
true
|
|
else
|
|
pred_info_get_proc_table(PredInfo, ProcMap),
|
|
map.foldl4(
|
|
init_global_var_usage_map_entry_for_proc(PredId, PredInfo),
|
|
ProcMap,
|
|
!GlobalVarUsageMap, !FixpointPredProcIds, !OptProcs, !ModuleInfo)
|
|
).
|
|
|
|
% Setup args for the procedure.
|
|
%
|
|
% XXX Document the meaning of the arguments.
|
|
%
|
|
:- pred init_global_var_usage_map_entry_for_proc(pred_id::in, pred_info::in,
|
|
proc_id::in, proc_info::in,
|
|
global_var_usage_map::in, global_var_usage_map::out,
|
|
list(pred_proc_id)::in, list(pred_proc_id)::out,
|
|
new_proc_map::in, new_proc_map::out,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
init_global_var_usage_map_entry_for_proc(PredId, PredInfo, ProcId, ProcInfo,
|
|
!GlobalVarUsageMap, !FixpointPredProcIds, !OptProcs, !ModuleInfo) :-
|
|
module_info_get_globals(!.ModuleInfo, Globals),
|
|
globals.lookup_bool_option(Globals, intermodule_analysis, Intermod),
|
|
( if
|
|
% Don't use the intermodule analysis info when we have the clauses
|
|
% (opt_imported preds) since we may be able to do better with the
|
|
% information in this module.
|
|
Intermod = yes,
|
|
pred_info_is_imported_not_external(PredInfo),
|
|
not is_unify_index_or_compare_pred(PredInfo)
|
|
then
|
|
try_to_look_up_global_var_usage_map_entry_for_proc(PredId, PredInfo,
|
|
ProcId, ProcInfo, !GlobalVarUsageMap, !OptProcs, !ModuleInfo)
|
|
else if
|
|
should_ignore_proc_unused_args(PredInfo, ProcId, ProcInfo)
|
|
then
|
|
true
|
|
else
|
|
proc_info_get_var_table(ProcInfo, VarTable),
|
|
var_table_vars(VarTable, Vars),
|
|
some [!LocalVarUsageMap] (
|
|
map.init(!:LocalVarUsageMap),
|
|
InitRequiredBy = required_by(set.init, set.init),
|
|
init_requiring_vars_for_var(InitRequiredBy, Vars,
|
|
!LocalVarUsageMap),
|
|
record_output_args_as_used(!.ModuleInfo, ProcInfo,
|
|
!LocalVarUsageMap),
|
|
|
|
proc_interface_should_use_typeinfo_liveness(PredInfo, ProcId,
|
|
Globals, TypeInfoLiveness),
|
|
PredProcId = proc(PredId, ProcId),
|
|
(
|
|
TypeInfoLiveness = yes,
|
|
proc_info_get_rtti_varmaps(ProcInfo, RttiVarMaps),
|
|
require_typeinfo_liveness_for_vars(PredProcId, VarTable,
|
|
RttiVarMaps, Vars, !LocalVarUsageMap)
|
|
;
|
|
TypeInfoLiveness = no
|
|
),
|
|
|
|
Info = unused_args_info(!.ModuleInfo, VarTable),
|
|
proc_info_get_goal(ProcInfo, Goal),
|
|
unused_args_traverse_goal(Info, Goal, !LocalVarUsageMap),
|
|
map.det_insert(PredProcId, !.LocalVarUsageMap, !GlobalVarUsageMap)
|
|
),
|
|
|
|
!:FixpointPredProcIds = [PredProcId | !.FixpointPredProcIds]
|
|
).
|
|
|
|
:- pred try_to_look_up_global_var_usage_map_entry_for_proc(
|
|
pred_id::in, pred_info::in, proc_id::in, proc_info::in,
|
|
global_var_usage_map::in, global_var_usage_map::out,
|
|
new_proc_map::in, new_proc_map::out,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
try_to_look_up_global_var_usage_map_entry_for_proc(PredId, PredInfo,
|
|
ProcId, ProcInfo, !GlobalVarUsageMap, !OptProcs, !ModuleInfo) :-
|
|
PredModuleName = pred_info_module(PredInfo),
|
|
pred_info_get_orig_arity(PredInfo, PredFormArity),
|
|
FuncInfo = unused_args_func_info(PredFormArity),
|
|
module_info_get_analysis_info(!.ModuleInfo, AnalysisInfo0),
|
|
pred_info_proc_id_to_module_name_func_id(PredInfo, ProcId,
|
|
ModuleId, FuncId),
|
|
lookup_best_result(AnalysisInfo0, ModuleId, FuncId,
|
|
FuncInfo, unused_args_call, MaybeBestResult),
|
|
(
|
|
MaybeBestResult = yes(analysis_result(_, BestAnswer, _)),
|
|
BestAnswer = unused_args_answer(UnusedArgs),
|
|
(
|
|
UnusedArgs = [_ | _],
|
|
InitRequiredBy = required_by(set.init, set.init),
|
|
proc_info_get_headvars(ProcInfo, HeadVars),
|
|
list.map(list.det_index1(HeadVars), UnusedArgs, UnusedVars),
|
|
init_requiring_vars_for_var(InitRequiredBy, UnusedVars,
|
|
map.init, LocalVarUsageMap),
|
|
PredProcId = proc(PredId, ProcId),
|
|
map.det_insert(PredProcId, LocalVarUsageMap, !GlobalVarUsageMap),
|
|
module_info_get_globals(!.ModuleInfo, Globals),
|
|
globals.get_opt_tuple(Globals, OptTuple),
|
|
OptUnusedArgs = OptTuple ^ ot_opt_unused_args,
|
|
(
|
|
OptUnusedArgs = opt_unused_args,
|
|
make_imported_unused_args_pred_info(PredProcId, UnusedArgs,
|
|
!OptProcs, !ModuleInfo)
|
|
;
|
|
OptUnusedArgs = do_not_opt_unused_args
|
|
)
|
|
;
|
|
UnusedArgs = []
|
|
),
|
|
AnalysisInfo = AnalysisInfo0
|
|
;
|
|
MaybeBestResult = no,
|
|
record_request(analysis_name, PredModuleName, FuncId,
|
|
unused_args_call, AnalysisInfo0, AnalysisInfo)
|
|
),
|
|
module_info_set_analysis_info(AnalysisInfo, !ModuleInfo).
|
|
|
|
:- pred should_ignore_proc_unused_args(pred_info::in,
|
|
proc_id::in, proc_info::in) is semidet.
|
|
|
|
should_ignore_proc_unused_args(PredInfo, ProcId, ProcInfo) :-
|
|
(
|
|
pred_info_is_imported(PredInfo)
|
|
;
|
|
pred_info_is_pseudo_imported(PredInfo),
|
|
hlds_pred.in_in_unification_proc_id(ProcId)
|
|
;
|
|
% Unused argument optimization and tabling are
|
|
% not compatible with each other.
|
|
proc_info_get_eval_method(ProcInfo, EvalMethod),
|
|
EvalMethod \= eval_normal
|
|
;
|
|
proc_info_get_declared_determinism(ProcInfo,
|
|
MaybeDeclaredDetism),
|
|
proc_info_get_goal(ProcInfo, Goal),
|
|
Goal = hlds_goal(_, GoalInfo),
|
|
ActualDetism = goal_info_get_determinism(GoalInfo),
|
|
% If there is a declared detism but the actual detism differs from it,
|
|
% then replacing the Goal in ProcInfo with a forwarding call
|
|
% to the clone of this procedure in which the unused args
|
|
% have been eliminated can cause simplification to screw up.
|
|
%
|
|
% The scenario, as shown by Mantis bug #541, is the following.
|
|
%
|
|
% - A predicate is declared to be det (usually because it has to
|
|
% conform to an interface) but its body goal always throws
|
|
% an exception. Its actual determinism is therefore erroneous,
|
|
% and the body goal as a whole has "unreachable" as instmap delta.
|
|
%
|
|
% - The clone of this procedure which has the unused args deleted
|
|
% has the same declared determinism as the original, i.e. det.
|
|
%
|
|
% - Replacing the body with a call to the clone replaces
|
|
% an erroneous goal whose instmap_delta is unreachable
|
|
% with a det goal whose instmap_delta is unreachable.
|
|
%
|
|
% It is this step that is at fault, for violating the invariant
|
|
% which says that a goal whose instmap_delta is "unreachable"
|
|
% should have determinism whose soln_count component is
|
|
% "at_most_zero", and vice versa.
|
|
%
|
|
% - The simplification pass we invoke just before code generation
|
|
% sees the contradiction. To make the program point as unreachable
|
|
% as the instmap_delta says it should be, it adds a "fail" goal
|
|
% after the call to the clone.
|
|
%
|
|
% - The code generator aborts when it finds "fail" in a det context.
|
|
%
|
|
% We could change the simplification pass to make the
|
|
% supposed-to-be-unreachable end of the procedure body actually
|
|
% unreachable by adding not "fail", but code that throws an exception.
|
|
% However, there is no point in having unused args replacing code
|
|
% that throws an exception with code that does a det call (whose body
|
|
% throws an exception which we don't see because it is beyond
|
|
% the predicate boundary), and then having simplification add code
|
|
% to throw an exception afterward. It is much simpler not to allow
|
|
% the unused arg transformation to replace the original exception
|
|
% throwing code in the first place.
|
|
(
|
|
MaybeDeclaredDetism = yes(DeclaredDetism),
|
|
DeclaredDetism \= ActualDetism
|
|
;
|
|
determinism_components(ActualDetism, _CanFail, SolnCount),
|
|
SolnCount = at_most_zero
|
|
)
|
|
).
|
|
|
|
%---------------------%
|
|
|
|
:- pred init_requiring_vars_for_var(required_by::in, list(prog_var)::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
init_requiring_vars_for_var(_, [], !LocalVarUsageMap).
|
|
init_requiring_vars_for_var(RequiredBy, [Var | Vars], !LocalVarUsageMap) :-
|
|
map.det_insert(Var, RequiredBy, !LocalVarUsageMap),
|
|
init_requiring_vars_for_var(RequiredBy, Vars, !LocalVarUsageMap).
|
|
|
|
% Record that all output arguments for a procedure as used.
|
|
%
|
|
:- pred record_output_args_as_used(module_info::in, proc_info::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
record_output_args_as_used(ModuleInfo, ProcInfo, !LocalVarUsageMap) :-
|
|
proc_info_instantiated_head_vars(ModuleInfo, ProcInfo,
|
|
ChangedInstHeadVars),
|
|
record_vars_as_used(ChangedInstHeadVars, !LocalVarUsageMap).
|
|
|
|
% For each variable, ensure the typeinfos describing the type parameters
|
|
% of the type of the variable depend on the head variable. For example,
|
|
% if HeadVar1 has type list(T), then the type_info for T is used
|
|
% if HeadVar1 is used.
|
|
%
|
|
:- pred require_typeinfo_liveness_for_vars(pred_proc_id::in, var_table::in,
|
|
rtti_varmaps::in, list(prog_var)::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
require_typeinfo_liveness_for_vars(_, _, _, [], !LocalVarUsageMap).
|
|
require_typeinfo_liveness_for_vars(PredProcId, VarTable, RttiVarMaps,
|
|
[Var | Vars], !LocalVarUsageMap) :-
|
|
require_typeinfo_liveness_for_var(PredProcId, VarTable, RttiVarMaps,
|
|
Var, !LocalVarUsageMap),
|
|
require_typeinfo_liveness_for_vars(PredProcId, VarTable, RttiVarMaps,
|
|
Vars, !LocalVarUsageMap).
|
|
|
|
:- pred require_typeinfo_liveness_for_var(pred_proc_id::in, var_table::in,
|
|
rtti_varmaps::in, prog_var::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
require_typeinfo_liveness_for_var(PredProcId, VarTable, RttiVarMaps, Var,
|
|
!LocalVarUsageMap) :-
|
|
lookup_var_type(VarTable, Var, Type),
|
|
type_vars_in_type(Type, TVars),
|
|
list.map(tvar_to_type_info_var(RttiVarMaps), TVars, TypeInfoVars),
|
|
ArgVarInProc = arg_var_in_proc(PredProcId, Var),
|
|
local_vars_are_required_by_proc_arg(TypeInfoVars, ArgVarInProc,
|
|
!LocalVarUsageMap).
|
|
|
|
:- pred tvar_to_type_info_var(rtti_varmaps::in, tvar::in, prog_var::out)
|
|
is det.
|
|
|
|
tvar_to_type_info_var(RttiVarMaps, TVar, TypeInfoVar) :-
|
|
rtti_lookup_type_info_locn(RttiVarMaps, TVar, Locn),
|
|
type_info_locn_var(Locn, TypeInfoVar).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Traverse the procedure body, building up alias records as we go.
|
|
%
|
|
|
|
:- type unused_args_info
|
|
---> unused_args_info(
|
|
unarg_module_info :: module_info,
|
|
unarg_var_table :: var_table
|
|
).
|
|
|
|
:- pred unused_args_traverse_goal(unused_args_info::in, hlds_goal::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
unused_args_traverse_goal(Info, Goal, !LocalVarUsageMap) :-
|
|
Goal = hlds_goal(GoalExpr, _GoalInfo),
|
|
(
|
|
GoalExpr = unify(LHS, RHS, _, Unify, _),
|
|
unused_args_traverse_unify(Info, LHS, RHS, Unify, !LocalVarUsageMap)
|
|
;
|
|
GoalExpr = plain_call(PredId, ProcId, CallArgVars, _, _, _),
|
|
ModuleInfo = Info ^ unarg_module_info,
|
|
module_info_pred_proc_info(ModuleInfo, PredId, ProcId, _, ProcInfo),
|
|
proc_info_get_headvars(ProcInfo, CalleeHeadVars),
|
|
CalleePredProcId = proc(PredId, ProcId),
|
|
add_plain_call_arg_deps(CalleePredProcId, CallArgVars, CalleeHeadVars,
|
|
!LocalVarUsageMap)
|
|
;
|
|
GoalExpr = generic_call(GenericCall, CallArgVars, _, _, _),
|
|
vars_in_generic_call(GenericCall, GenericCallArgVars),
|
|
record_vars_as_used(GenericCallArgVars, !LocalVarUsageMap),
|
|
record_vars_as_used(CallArgVars, !LocalVarUsageMap)
|
|
;
|
|
GoalExpr = call_foreign_proc(_, _, _,
|
|
ForeignArgs, ForeignExtraArgs, _, _),
|
|
% Only arguments with names can be used in the foreign code.
|
|
% The code in here should be kept in sync with the treatment
|
|
% of foreign_procs in delete_unused_args_in_goal_expr:
|
|
% any variable considered unused here should be renamed apart there.
|
|
ArgIsUsed =
|
|
( pred(ForeignArg::in, Var::out) is semidet :-
|
|
ForeignArg = foreign_arg(Var, MaybeNameAndMode, _, _),
|
|
MaybeNameAndMode = yes(_)
|
|
),
|
|
list.filter_map(ArgIsUsed, ForeignArgs ++ ForeignExtraArgs, UsedVars),
|
|
record_vars_as_used(UsedVars, !LocalVarUsageMap)
|
|
;
|
|
GoalExpr = conj(_ConjType, Goals),
|
|
unused_args_traverse_goals(Info, Goals, !LocalVarUsageMap)
|
|
;
|
|
GoalExpr = disj(Goals),
|
|
unused_args_traverse_goals(Info, Goals, !LocalVarUsageMap)
|
|
;
|
|
GoalExpr = switch(Var, _, Cases),
|
|
record_var_as_used(Var, !LocalVarUsageMap),
|
|
unused_args_traverse_cases(Info, Cases, !LocalVarUsageMap)
|
|
;
|
|
GoalExpr = if_then_else(_, Cond, Then, Else),
|
|
unused_args_traverse_goal(Info, Cond, !LocalVarUsageMap),
|
|
unused_args_traverse_goal(Info, Then, !LocalVarUsageMap),
|
|
unused_args_traverse_goal(Info, Else, !LocalVarUsageMap)
|
|
;
|
|
GoalExpr = negation(SubGoal),
|
|
unused_args_traverse_goal(Info, SubGoal, !LocalVarUsageMap)
|
|
;
|
|
GoalExpr = scope(Reason, SubGoal),
|
|
( if
|
|
Reason = from_ground_term(_TermVar, from_ground_term_construct)
|
|
then
|
|
% What we do here is what we would do for a construction
|
|
% unification that binds TermVar to a constant, i.e. nothing.
|
|
true
|
|
else
|
|
% XXX We could treat from_ground_term_deconstruct specially
|
|
% as well.
|
|
unused_args_traverse_goal(Info, SubGoal, !LocalVarUsageMap)
|
|
)
|
|
;
|
|
GoalExpr = shorthand(_),
|
|
% These should have been expanded out by now.
|
|
unexpected($pred, "shorthand")
|
|
).
|
|
|
|
:- pred unused_args_traverse_unify(unused_args_info::in,
|
|
prog_var::in, unify_rhs::in, unification::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
unused_args_traverse_unify(Info, LHSVar, RHS, Unify, !LocalVarUsageMap) :-
|
|
(
|
|
Unify = simple_test(Var1, Var2),
|
|
record_var_as_used(Var1, !LocalVarUsageMap),
|
|
record_var_as_used(Var2, !LocalVarUsageMap)
|
|
;
|
|
Unify = assign(Target, Source),
|
|
( if local_var_is_used(!.LocalVarUsageMap, Target) then
|
|
% If Target is used to instantiate an output argument,
|
|
% Source is used.
|
|
record_var_as_used(Source, !LocalVarUsageMap)
|
|
else
|
|
local_var_is_required_by_local_vars(Source, [Target],
|
|
!LocalVarUsageMap)
|
|
)
|
|
;
|
|
Unify = construct(CellVar, _, ArgVars, _, _, _, _),
|
|
expect(unify(CellVar, LHSVar), $pred, "LHSVar != CellVar"),
|
|
( if local_var_is_used(!.LocalVarUsageMap, CellVar) then
|
|
record_vars_as_used(ArgVars, !LocalVarUsageMap)
|
|
else
|
|
local_vars_are_required_by_local_var(ArgVars, CellVar,
|
|
!LocalVarUsageMap)
|
|
)
|
|
;
|
|
Unify = deconstruct(CellVar, _, ArgVars, ArgModes, CanFail, _),
|
|
expect(unify(CellVar, LHSVar), $pred, "LHSVar != CellVar"),
|
|
partition_deconstruct_args(Info, ArgVars, ArgModes,
|
|
InputVars, OutputVars),
|
|
% The deconstructed variable is used if any of the variables that
|
|
% the deconstruction binds are used.
|
|
local_var_is_required_by_local_vars(CellVar, OutputVars,
|
|
!LocalVarUsageMap),
|
|
% Treat a deconstruction that further instantiates its left arg
|
|
% as a partial construction.
|
|
local_vars_are_required_by_local_var(InputVars, CellVar,
|
|
!LocalVarUsageMap),
|
|
(
|
|
CanFail = can_fail,
|
|
% A deconstruction that can_fail uses its left arg.
|
|
record_var_as_used(CellVar, !LocalVarUsageMap)
|
|
;
|
|
CanFail = cannot_fail
|
|
)
|
|
;
|
|
Unify = complicated_unify(_, _, _),
|
|
% These should be transformed into calls by polymorphism.m.
|
|
% This is here to cover the case where unused arguments is called
|
|
% with --error-check-only and polymorphism has not been run.
|
|
(
|
|
RHS = rhs_var(RHSVar),
|
|
record_var_as_used(RHSVar, !LocalVarUsageMap),
|
|
record_var_as_used(LHSVar, !LocalVarUsageMap)
|
|
;
|
|
( RHS = rhs_functor(_, _, _)
|
|
; RHS = rhs_lambda_goal(_, _, _, _, _, _, _)
|
|
),
|
|
unexpected($pred,
|
|
"complicated unifications should only be var-var")
|
|
)
|
|
).
|
|
|
|
% Add PredProcId - CalleeArgVar as an alias for the corresponding
|
|
% CallArgVar.
|
|
%
|
|
:- pred add_plain_call_arg_deps(pred_proc_id::in,
|
|
list(prog_var)::in, list(prog_var)::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
add_plain_call_arg_deps(PredProcId, CallArgVars, CalleeArgVars,
|
|
!LocalVarUsageMap) :-
|
|
(
|
|
CallArgVars = [],
|
|
CalleeArgVars = []
|
|
;
|
|
CallArgVars = [],
|
|
CalleeArgVars = [_ | _],
|
|
unexpected($pred, "invalid call")
|
|
;
|
|
CallArgVars = [_ | _],
|
|
CalleeArgVars = [],
|
|
unexpected($pred, "invalid call")
|
|
;
|
|
CallArgVars = [HeadCallArgVar | TailCallArgVars],
|
|
CalleeArgVars = [HeadCalleeArgVar | TailCalleeArgVars],
|
|
ArgVarInProc = arg_var_in_proc(PredProcId, HeadCalleeArgVar),
|
|
local_var_is_required_by_proc_arg(HeadCallArgVar, ArgVarInProc,
|
|
!LocalVarUsageMap),
|
|
add_plain_call_arg_deps(PredProcId, TailCallArgVars, TailCalleeArgVars,
|
|
!LocalVarUsageMap)
|
|
).
|
|
|
|
% Partition the arguments to a deconstruction into inputs
|
|
% and outputs.
|
|
%
|
|
:- pred partition_deconstruct_args(unused_args_info::in, list(prog_var)::in,
|
|
list(unify_mode)::in, list(prog_var)::out, list(prog_var)::out) is det.
|
|
|
|
partition_deconstruct_args(Info, Vars, ArgModes, InputVars, OutputVars) :-
|
|
(
|
|
Vars = [],
|
|
ArgModes = [],
|
|
InputVars = [],
|
|
OutputVars = []
|
|
;
|
|
Vars = [],
|
|
ArgModes = [_ | _],
|
|
unexpected($pred, "mismatched lists")
|
|
;
|
|
Vars = [_ | _],
|
|
ArgModes = [],
|
|
unexpected($pred, "mismatched lists")
|
|
;
|
|
Vars = [HeadVar | TailVars],
|
|
ArgModes = [HeadArgMode | TailArgModes],
|
|
partition_deconstruct_args(Info, TailVars, TailArgModes,
|
|
InputVarsTail, OutputVarsTail),
|
|
|
|
HeadArgMode = unify_modes_li_lf_ri_rf(InitX, FinalX, InitY, FinalY),
|
|
lookup_var_type(Info ^ unarg_var_table, HeadVar, HeadType),
|
|
ModuleInfo = Info ^ unarg_module_info,
|
|
|
|
% If the inst of the argument of the LHS is changed,
|
|
% the argument is input.
|
|
( if inst_matches_binding(ModuleInfo, HeadType, InitX, FinalX) then
|
|
InputVars = InputVarsTail
|
|
else
|
|
InputVars = [HeadVar | InputVarsTail]
|
|
),
|
|
|
|
% If the inst of the argument of the RHS is changed,
|
|
% the argument is output.
|
|
( if inst_matches_binding(ModuleInfo, HeadType, InitY, FinalY) then
|
|
OutputVars = OutputVarsTail
|
|
else
|
|
OutputVars = [HeadVar | OutputVarsTail]
|
|
)
|
|
).
|
|
|
|
:- pred unused_args_traverse_goals(unused_args_info::in, list(hlds_goal)::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
unused_args_traverse_goals(_, [], !LocalVarUsageMap).
|
|
unused_args_traverse_goals(Info, [Goal | Goals], !LocalVarUsageMap) :-
|
|
unused_args_traverse_goal(Info, Goal, !LocalVarUsageMap),
|
|
unused_args_traverse_goals(Info, Goals, !LocalVarUsageMap).
|
|
|
|
:- pred unused_args_traverse_cases(unused_args_info::in, list(case)::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
unused_args_traverse_cases(_, [], !LocalVarUsageMap).
|
|
unused_args_traverse_cases(Info, [Case | Cases], !LocalVarUsageMap) :-
|
|
Case = case(_, _, Goal),
|
|
unused_args_traverse_goal(Info, Goal, !LocalVarUsageMap),
|
|
unused_args_traverse_cases(Info, Cases, !LocalVarUsageMap).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Interprocedural analysis section.
|
|
%
|
|
|
|
% Start by assuming that the only input arguments a procedure needs
|
|
% are the ones used in its own procedure body. This denotes the
|
|
% ideal situation, in the sense it has the most "unused" arguments
|
|
% that can be optimized. However, this assumption is false in most cases,
|
|
% because it does not account for discrepancies such as local variables
|
|
% in procedure P1 that are needed in the body of procedure P2, to which
|
|
% they are passed.
|
|
%
|
|
% This predicate does a top-down iteration to find the greatest fixpoint
|
|
% of the operation that fixes such discrepancies. Each iteration
|
|
% fixes the discrepancies implicit in its initial value of
|
|
% !.GlobalVarUsageMap by marking more local variables is definitely used.
|
|
% When we get to a fixpoint, by definition there will be no discrepancies
|
|
% left, and the final GlobalVarUsageMap will record a variable as unused
|
|
% if it can be truly deleted from the program without any ill effects.
|
|
%
|
|
% The reason why we compute the greatest fixpoint is because it allows us
|
|
% to warn about and to optimize not just unused variables that are
|
|
% local to their defining procedure, but also input arguments that
|
|
% are seemingly used by being passed to recursive calls, possibly even
|
|
% mutually recursive calls, but which are not used for anything else.
|
|
%
|
|
% One thing that we do NOT do, but we could consider doing,
|
|
% is finding input arguments that are "used" not just by passing them
|
|
% around as input to (possibly mutually) recursive calls, but also
|
|
% to return them unchanged as output arguments. Such input arguments
|
|
% should not be deleted on their own. They should always be deleted
|
|
% together with the output argument they are returned as, and calls
|
|
% from outside the clique of recursive procedures to inside must be
|
|
% extended with code that copies the input argument to the unchanged
|
|
% output argument. (This cannot be done if this caller is in another
|
|
% module, so the compiler would need to create and export a shim
|
|
% procedure to do this copying.)
|
|
%
|
|
:- pred record_required_vars_as_used_to_fixpoint(int::in, module_info::in,
|
|
list(pred_proc_id)::in,
|
|
global_var_usage_map::in, global_var_usage_map::out) is det.
|
|
|
|
record_required_vars_as_used_to_fixpoint(PassNum, ModuleInfo,
|
|
LocalPredProcIds, !GlobalVarUsageMap) :-
|
|
% If we find any local variables in any procedures that
|
|
%
|
|
% - were not known to be definitely used, but
|
|
% - were known to be required for the computation of some variable
|
|
% or procedure argument that is NOW known to be definitely used,
|
|
%
|
|
% then mark them as definitely used, and set !:Changed accordingly.
|
|
record_required_vars_as_used_in_procs(LocalPredProcIds,
|
|
unchanged, Changed, !GlobalVarUsageMap),
|
|
(
|
|
Changed = changed,
|
|
% There are some new variables that were not known to be definitely
|
|
% used BEFORE the call to record_required_vars_as_used_in_procs,
|
|
% which are known to be definitely used NOW. This means that
|
|
% any variable in any procedure that the computation of the values
|
|
% of these require must *also* be marked as definitely used.
|
|
%
|
|
% Technically, we don't always need to reprocess *all* procedures
|
|
% in LocalPredProcIds; the only ones we need to process are the
|
|
% callers of procedures for which record_required_vars_as_used_in_proc
|
|
% set !:Changed to "yes".
|
|
%
|
|
% However, keeping track of this set of procedures is likely to take
|
|
% a nontrivial amount of time. Revisiting every procedure, even
|
|
% the ones that we do not NEED to revisit, will likely be
|
|
% just as fast, other than in cases of pathologically-slow convergence.
|
|
trace [compile_time(flag("unused_args_var_usage")), io(!IO)] (
|
|
get_debug_output_stream(ModuleInfo, DebugStream, !IO),
|
|
io.format(DebugStream,
|
|
"\nVARIABLE USAGE MAP AFTER PASS %d\n", [i(PassNum)], !IO),
|
|
write_global_var_usage_map(DebugStream, ModuleInfo,
|
|
!.GlobalVarUsageMap, !IO)
|
|
),
|
|
record_required_vars_as_used_to_fixpoint(PassNum + 1, ModuleInfo,
|
|
LocalPredProcIds, !GlobalVarUsageMap)
|
|
;
|
|
Changed = unchanged
|
|
).
|
|
|
|
:- pred record_required_vars_as_used_in_procs(list(pred_proc_id)::in,
|
|
maybe_changed::in, maybe_changed::out,
|
|
global_var_usage_map::in, global_var_usage_map::out) is det.
|
|
|
|
record_required_vars_as_used_in_procs([], !Changed, !GlobalVarUsageMap).
|
|
record_required_vars_as_used_in_procs([PredProcId | PredProcIds],
|
|
!Changed, !GlobalVarUsageMap) :-
|
|
record_required_vars_as_used_in_proc(PredProcId,
|
|
!Changed, !GlobalVarUsageMap),
|
|
record_required_vars_as_used_in_procs(PredProcIds,
|
|
!Changed, !GlobalVarUsageMap).
|
|
|
|
% If we find any local variables that
|
|
%
|
|
% - were not known to be definitely used, but
|
|
% - were known to be required for the computation of some variable
|
|
% of procedure argument that is NOW known to be definitely used,
|
|
%
|
|
% then mark them as definitely used, and set !:Changed accordingly.
|
|
%
|
|
:- pred record_required_vars_as_used_in_proc(pred_proc_id::in,
|
|
maybe_changed::in, maybe_changed::out,
|
|
global_var_usage_map::in, global_var_usage_map::out) is det.
|
|
|
|
record_required_vars_as_used_in_proc(PredProcId,
|
|
!Changed, !GlobalVarUsageMap) :-
|
|
map.lookup(!.GlobalVarUsageMap, PredProcId, LocalVarUsageMap0),
|
|
% NOTE: It would be nice to use map.map_foldl here, but that works
|
|
% when the processing of each key-value pair involves *updating*
|
|
% the value, whereas the job of unused_args_check_all_vars is to
|
|
% *delete* whole pairs.
|
|
%
|
|
% Technically, we *could* fold over the initial version of
|
|
% LocalVarUsageMap0 while producing the updated LocalVarUsageMap,
|
|
% but this would be harder to maintain, due to the abstraction barrier
|
|
% involved in calls to record_var_as_used.
|
|
map.keys(LocalVarUsageMap0, Vars),
|
|
record_required_vars_as_used(!.GlobalVarUsageMap, Vars,
|
|
unchanged, LocalChanged, LocalVarUsageMap0, LocalVarUsageMap),
|
|
(
|
|
LocalChanged = changed,
|
|
map.det_update(PredProcId, LocalVarUsageMap, !GlobalVarUsageMap),
|
|
!:Changed = changed
|
|
;
|
|
LocalChanged = unchanged
|
|
).
|
|
|
|
% Check each var of a procedure in turn.
|
|
%
|
|
:- pred record_required_vars_as_used(global_var_usage_map::in,
|
|
list(prog_var)::in, maybe_changed::in, maybe_changed::out,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
record_required_vars_as_used(_, [], !Changed, !LocalVarUsageMap).
|
|
record_required_vars_as_used(GlobalVarUsageMap, [Var | Vars],
|
|
!Changed, !LocalVarUsageMap) :-
|
|
map.lookup(!.LocalVarUsageMap, Var, RequiredBy),
|
|
RequiredBy = required_by(RequiringLocalVars0, RequiringProcArgs0),
|
|
( if
|
|
(
|
|
% Are there any used procedure arguments that Var depends on?
|
|
some [Argument] (
|
|
set.member(Argument, RequiringProcArgs0),
|
|
Argument = arg_var_in_proc(PredProcId, ArgVar),
|
|
proc_arg_var_is_used(GlobalVarUsageMap, PredProcId, ArgVar)
|
|
)
|
|
;
|
|
% Are there any used local variables that Var depends on?
|
|
some [X] (
|
|
set.member(X, RequiringLocalVars0),
|
|
local_var_is_used(!.LocalVarUsageMap, X)
|
|
)
|
|
)
|
|
then
|
|
% Mark the current variable as used. Note that we update the same
|
|
% data structure (!LocalVarUsageMap) as we test in the condition
|
|
% above. This is OK because the order in which we mark variables
|
|
% in !.LocalVarUsageMap as used does not matter; the iteration
|
|
% performed by record_required_vars_as_used_to_fixpoint
|
|
% is guaranteed to reach the same final result.
|
|
record_var_as_used(Var, !LocalVarUsageMap),
|
|
!:Changed = changed
|
|
else
|
|
true
|
|
),
|
|
record_required_vars_as_used(GlobalVarUsageMap, Vars,
|
|
!Changed, !LocalVarUsageMap).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred get_unused_arg_info(module_info::in, global_var_usage_map::in,
|
|
list(pred_proc_id)::in, unused_arg_info::in, unused_arg_info::out) is det.
|
|
|
|
get_unused_arg_info(_, _, [], !UnusedArgInfo).
|
|
get_unused_arg_info(ModuleInfo, GlobalVarUsageMap, [PredProcId | PredProcIds],
|
|
!UnusedArgInfo) :-
|
|
PredProcId = proc(PredId, ProcId),
|
|
map.lookup(GlobalVarUsageMap, PredProcId, LocalVarUsageMap),
|
|
module_info_pred_proc_info(ModuleInfo, PredId, ProcId, _, ProcInfo),
|
|
proc_info_get_headvars(ProcInfo, HeadVars),
|
|
get_unused_arg_nums(LocalVarUsageMap, HeadVars, 1, UnusedArgs),
|
|
map.det_insert(PredProcId, UnusedArgs, !UnusedArgInfo),
|
|
get_unused_arg_info(ModuleInfo, GlobalVarUsageMap, PredProcIds,
|
|
!UnusedArgInfo).
|
|
|
|
:- pred get_unused_arg_nums(local_var_usage_map::in, list(prog_var)::in,
|
|
int::in, list(int)::out) is det.
|
|
|
|
get_unused_arg_nums(_, [], _, []).
|
|
get_unused_arg_nums(LocalVarUsageMap, [HeadVar | HeadVars], ArgNum,
|
|
UnusedArgs) :-
|
|
get_unused_arg_nums(LocalVarUsageMap, HeadVars, ArgNum + 1,
|
|
UnusedArgsTail),
|
|
( if map.contains(LocalVarUsageMap, HeadVar) then
|
|
UnusedArgs = [ArgNum | UnusedArgsTail]
|
|
else
|
|
UnusedArgs = UnusedArgsTail
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Fix up the module.
|
|
%
|
|
|
|
% Information about predicates which have new predicates created for the
|
|
% optimized version.
|
|
%
|
|
:- type new_proc_map == map(pred_proc_id, new_proc_info).
|
|
|
|
% New pred_id, proc_id, name, and the indices in the argument
|
|
% vector of the arguments that have been removed.
|
|
:- type new_proc_info
|
|
---> new_proc_info(pred_id, proc_id, sym_name, list(int)).
|
|
|
|
% Create a new predicate for each procedure which has unused arguments.
|
|
% There are two reasons why we can't throw away the old procedure for
|
|
% non-exported predicates. One is higher-order terms - we can't remove
|
|
% arguments from them without changing their type, so they need the old
|
|
% calling interface. The other is that the next proc_id for a predicate is
|
|
% chosen based on the length of the list of proc_ids.
|
|
%
|
|
:- pred unused_args_create_new_pred(unused_arg_info::in, pred_proc_id::in,
|
|
new_proc_map::in, new_proc_map::out,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
unused_args_create_new_pred(UnusedArgInfo, OrigPredProcId,
|
|
!NewProcMap, !ModuleInfo) :-
|
|
map.lookup(UnusedArgInfo, OrigPredProcId, UnusedArgs),
|
|
module_info_pred_proc_info(!.ModuleInfo, OrigPredProcId,
|
|
OrigPredInfo, OrigProcInfo),
|
|
PredModuleName = pred_info_module(OrigPredInfo),
|
|
|
|
OrigPredProcId = proc(OrigPredId, ProcId),
|
|
module_info_get_globals(!.ModuleInfo, Globals),
|
|
globals.lookup_bool_option(Globals, intermodule_analysis, Intermod),
|
|
(
|
|
Intermod = yes,
|
|
module_info_get_analysis_info(!.ModuleInfo, AnalysisInfo0),
|
|
|
|
pred_info_proc_id_to_module_name_func_id(OrigPredInfo, ProcId,
|
|
ModuleId, FuncId),
|
|
analysis.operations.lookup_results(AnalysisInfo0, ModuleId, FuncId,
|
|
IntermodResultsTriples : list(analysis_result(unused_args_call,
|
|
unused_args_answer))),
|
|
IntermodOldAnswers = list.map((func(R) = R ^ ar_answer),
|
|
IntermodResultsTriples),
|
|
|
|
pred_info_get_orig_arity(OrigPredInfo, PredFormArity),
|
|
FuncInfo = unused_args_func_info(PredFormArity),
|
|
Answer = unused_args_answer(UnusedArgs),
|
|
|
|
FilterUnused =
|
|
( pred(VersionAnswer::in) is semidet :-
|
|
VersionAnswer \= Answer,
|
|
VersionAnswer \= unused_args_answer([]),
|
|
more_precise_than(FuncInfo, Answer, VersionAnswer)
|
|
),
|
|
IntermodOldArgLists = list.map(get_unused_args,
|
|
list.filter(FilterUnused, IntermodOldAnswers))
|
|
;
|
|
Intermod = no,
|
|
IntermodResultsTriples = [],
|
|
IntermodOldArgLists = []
|
|
),
|
|
|
|
(
|
|
UnusedArgs = []
|
|
;
|
|
UnusedArgs = [_ | _],
|
|
pred_info_get_status(OrigPredInfo, PredStatus0),
|
|
( if
|
|
PredStatus0 = pred_status(status_opt_imported),
|
|
IntermodResultsTriples = [_ | _],
|
|
IntermodOldArgLists = []
|
|
then
|
|
% If this predicate is from a .opt file, and no more arguments
|
|
% have been removed than in the original module, then leave the
|
|
% import status as opt_imported so that dead_proc_elim will remove
|
|
% it if no other optimization is performed on it.
|
|
PredStatus = pred_status(status_opt_imported)
|
|
else if
|
|
pred_status_is_exported(PredStatus0) = yes
|
|
then
|
|
% This specialized version of the predicate will be declared
|
|
% in the analysis file for this module so it must be exported.
|
|
PredStatus = PredStatus0
|
|
else
|
|
PredStatus = pred_status(status_local)
|
|
),
|
|
make_new_pred_info(!.ModuleInfo, UnusedArgs, PredStatus,
|
|
OrigPredProcId, OrigPredInfo, NewPredInfo0),
|
|
NewPredName = pred_info_name(NewPredInfo0),
|
|
pred_info_get_proc_table(NewPredInfo0, NewProcs0),
|
|
|
|
% Assign the old procedure to a new predicate, which will be fixed up
|
|
% in delete_unused_args_in_module.
|
|
% XXX Fixed up in what sense? And where within the call tree
|
|
% of delete_unused_args_in_module?
|
|
map.set(ProcId, OrigProcInfo, NewProcs0, NewProcs),
|
|
pred_info_set_proc_table(NewProcs, NewPredInfo0, NewPredInfo),
|
|
|
|
% Add the new proc to the pred table.
|
|
module_info_get_predicate_table(!.ModuleInfo, PredTable0),
|
|
predicate_table_insert(NewPredInfo, NewPredId, PredTable0, PredTable),
|
|
module_info_set_predicate_table(PredTable, !ModuleInfo),
|
|
|
|
% Add the new proc to the new_proc_map.
|
|
PredSymName = qualified(PredModuleName, NewPredName),
|
|
OrigToNew = new_proc_info(NewPredId, ProcId, PredSymName, UnusedArgs),
|
|
map.det_insert(OrigPredProcId, OrigToNew, !NewProcMap),
|
|
|
|
% Add a forwarding predicate with the original interface.
|
|
create_call_goal(UnusedArgs, NewPredId, ProcId,
|
|
PredModuleName, NewPredName, OrigProcInfo, ForwardingProcInfo),
|
|
module_info_set_pred_proc_info(OrigPredId, ProcId, OrigPredInfo,
|
|
ForwardingProcInfo, !ModuleInfo),
|
|
|
|
% Add forwarding predicates for results produced in previous
|
|
% compilations.
|
|
% XXX This only works "once" due to the analysis framework now
|
|
% discarding all but the best answer. If we compile this module again
|
|
% without changing anything else, we won't remember to produce
|
|
% the same forwarding predicates. If some callers refer to those
|
|
% forwarding predicates, then linking will fail.
|
|
list.foldl(
|
|
make_intermod_proc(OrigPredId, NewPredId, ProcId, NewPredName,
|
|
OrigPredInfo, OrigProcInfo, UnusedArgs),
|
|
IntermodOldArgLists, !ModuleInfo)
|
|
).
|
|
|
|
:- pred make_intermod_proc(pred_id::in, pred_id::in, proc_id::in, string::in,
|
|
pred_info::in, proc_info::in, list(int)::in, list(int)::in,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
make_intermod_proc(PredId, NewPredId, ProcId, NewPredName,
|
|
OrigPredInfo, OrigProcInfo, UnusedArgs, UnusedArgs2, !ModuleInfo) :-
|
|
% Add an exported predicate with the number of removed arguments promised
|
|
% in the analysis file, which just calls the new predicate.
|
|
make_new_pred_info(!.ModuleInfo, UnusedArgs2, pred_status(status_exported),
|
|
proc(PredId, ProcId), OrigPredInfo, ExtraPredInfo0),
|
|
PredModuleName = pred_info_module(OrigPredInfo),
|
|
create_call_goal(UnusedArgs, NewPredId, ProcId,
|
|
PredModuleName, NewPredName, OrigProcInfo, ExtraProc0),
|
|
|
|
proc_info_get_headvars(OrigProcInfo, HeadVars0),
|
|
proc_info_get_argmodes(OrigProcInfo, ArgModes0),
|
|
remove_specified_positions(UnusedArgs2, HeadVars0, IntermodHeadVars),
|
|
remove_specified_positions(UnusedArgs2, ArgModes0, IntermodArgModes),
|
|
proc_info_set_headvars(IntermodHeadVars, ExtraProc0, ExtraProc1),
|
|
proc_info_set_argmodes(IntermodArgModes, ExtraProc1, ExtraProc),
|
|
|
|
pred_info_get_proc_table(ExtraPredInfo0, ExtraProcs0),
|
|
map.set(ProcId, ExtraProc, ExtraProcs0, ExtraProcs),
|
|
pred_info_set_proc_table(ExtraProcs, ExtraPredInfo0, ExtraPredInfo),
|
|
|
|
module_info_get_predicate_table(!.ModuleInfo, PredTable0),
|
|
predicate_table_insert(ExtraPredInfo, _, PredTable0, PredTable),
|
|
module_info_set_predicate_table(PredTable, !ModuleInfo).
|
|
|
|
:- pred make_new_pred_info(module_info::in, list(int)::in, pred_status::in,
|
|
pred_proc_id::in, pred_info::in, pred_info::out) is det.
|
|
|
|
make_new_pred_info(_ModuleInfo, UnusedArgs, PredStatus, proc(PredId, ProcId),
|
|
!PredInfo) :-
|
|
PredModuleName = pred_info_module(!.PredInfo),
|
|
Name0 = pred_info_name(!.PredInfo),
|
|
PredOrFunc = pred_info_is_pred_or_func(!.PredInfo),
|
|
pred_info_get_arg_types(!.PredInfo, Tvars, ExistQVars, ArgTypes0),
|
|
pred_info_get_origin(!.PredInfo, OrigOrigin),
|
|
% Create a unique new pred name using the old proc_id.
|
|
( if
|
|
string.prefix(Name0, "__"),
|
|
% XXX The string __LambdaGoal__ is not being generated by lambda.m.
|
|
% It *is* generated by modecheck_unify.m, but lambda.m is supposed
|
|
% to override it.
|
|
not string.prefix(Name0, "__LambdaGoal__")
|
|
then
|
|
( if
|
|
% Fix up special pred names.
|
|
OrigOrigin = origin_compiler(made_for_uci(_SpecialId, TypeCtor))
|
|
then
|
|
type_ctor_module_name_arity(TypeCtor, TypeModule, TypeName,
|
|
TypeArity),
|
|
TypeModuleStr = sym_name_to_string_sep(TypeModule, "__"),
|
|
string.format("%s_%s__%s_%d",
|
|
[s(Name0), s(TypeModuleStr), s(TypeName), i(TypeArity)], Name1)
|
|
else
|
|
% The special predicate has already been specialised.
|
|
Name1 = Name0
|
|
)
|
|
else
|
|
Name1 = Name0
|
|
),
|
|
% The mode number is included because we want to avoid the creation of
|
|
% more than one predicate with the same name if more than one mode of
|
|
% a predicate is specialized. Since the names of e.g. deep profiling
|
|
% proc_static structures are derived from the names of predicates,
|
|
% duplicate predicate names lead to duplicate global variable names
|
|
% and hence to link errors.
|
|
Transform = tn_unused_args(PredOrFunc, proc_id_to_int(ProcId), UnusedArgs),
|
|
make_transformed_pred_name(Name1, Transform, TransformedName),
|
|
PredFormArity = pred_info_pred_form_arity(!.PredInfo),
|
|
pred_info_get_typevarset(!.PredInfo, TypeVars),
|
|
remove_specified_positions(UnusedArgs, ArgTypes0, ArgTypes),
|
|
pred_info_get_context(!.PredInfo, Context),
|
|
pred_info_get_clauses_info(!.PredInfo, ClausesInfo),
|
|
pred_info_get_markers(!.PredInfo, Markers),
|
|
pred_info_get_goal_type(!.PredInfo, GoalType),
|
|
pred_info_get_class_context(!.PredInfo, ClassContext),
|
|
pred_info_get_var_name_remap(!.PredInfo, VarNameRemap),
|
|
|
|
% Since this pred_info isn't built until after the polymorphism
|
|
% transformation is complete, we just use dummy maps for the class
|
|
% constraints.
|
|
map.init(Proofs),
|
|
map.init(ConstraintMap),
|
|
ProcTransform = proc_transform_unused_args(UnusedArgs),
|
|
Origin = origin_proc_transform(ProcTransform, OrigOrigin, PredId, ProcId),
|
|
CurUserDecl = maybe.no,
|
|
pred_info_init(PredOrFunc, PredModuleName, TransformedName, PredFormArity,
|
|
Context, Origin, PredStatus, CurUserDecl, GoalType, Markers, ArgTypes,
|
|
Tvars, ExistQVars, ClassContext, Proofs, ConstraintMap,
|
|
ClausesInfo, VarNameRemap, !:PredInfo),
|
|
pred_info_set_typevarset(TypeVars, !PredInfo).
|
|
|
|
% Replace the goal in the procedure with one to call the given
|
|
% pred_id and proc_id.
|
|
%
|
|
:- pred create_call_goal(list(int)::in,
|
|
pred_id::in, proc_id::in, module_name::in, string::in,
|
|
proc_info::in, proc_info::out) is det.
|
|
|
|
create_call_goal(UnusedArgs, NewPredId, NewProcId,
|
|
PredModuleName, PredName, !OldProc) :-
|
|
proc_info_get_headvars(!.OldProc, HeadVars),
|
|
proc_info_get_goal(!.OldProc, Goal0),
|
|
Goal0 = hlds_goal(_GoalExpr, GoalInfo0),
|
|
|
|
% We must use the interface determinism for determining the determinism
|
|
% of the version of the goal with its arguments removed, not the actual
|
|
% determinism of the body is it may be more lax, which will lead to code
|
|
% generation problems.
|
|
proc_info_interface_determinism(!.OldProc, Determinism),
|
|
goal_info_set_determinism(Determinism, GoalInfo0, GoalInfo1),
|
|
|
|
proc_info_get_var_table(!.OldProc, VarTable0),
|
|
set.list_to_set(HeadVars, NonLocals),
|
|
lookup_var_entries(VarTable0, HeadVars, HeadVarEntries),
|
|
var_table_from_corresponding_lists(HeadVars, HeadVarEntries, VarTable1),
|
|
% The varset should probably be fixed up, but it shouldn't make
|
|
% too much difference.
|
|
proc_info_get_rtti_varmaps(!.OldProc, RttiVarMaps0),
|
|
remove_specified_positions(UnusedArgs, HeadVars, NewHeadVars),
|
|
GoalExpr = plain_call(NewPredId, NewProcId, NewHeadVars,
|
|
not_builtin, no, qualified(PredModuleName, PredName)),
|
|
Goal1 = hlds_goal(GoalExpr, GoalInfo1),
|
|
implicitly_quantify_goal_general(ord_nl_no_lambda,
|
|
set_to_bitset(NonLocals), _, Goal1, Goal,
|
|
VarTable1, VarTable, RttiVarMaps0, RttiVarMaps),
|
|
proc_info_set_goal(Goal, !OldProc),
|
|
proc_info_set_var_table(VarTable, !OldProc),
|
|
proc_info_set_rtti_varmaps(RttiVarMaps, !OldProc).
|
|
|
|
% Create a pred_info for an imported pred with a pragma unused_args
|
|
% in the .opt file.
|
|
%
|
|
:- pred make_imported_unused_args_pred_info(pred_proc_id::in, list(int)::in,
|
|
new_proc_map::in, new_proc_map::out,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
make_imported_unused_args_pred_info(OptProc, UnusedArgs, !NewProcMap,
|
|
!ModuleInfo) :-
|
|
OptProc = proc(PredId, ProcId),
|
|
module_info_pred_proc_info(!.ModuleInfo, PredId, ProcId,
|
|
PredInfo0, ProcInfo0),
|
|
make_new_pred_info(!.ModuleInfo, UnusedArgs,
|
|
pred_status(status_imported(import_locn_interface)), OptProc,
|
|
PredInfo0, NewPredInfo0),
|
|
pred_info_get_proc_table(NewPredInfo0, NewProcs0),
|
|
|
|
% Assign the old procedure to a new predicate.
|
|
proc_info_get_headvars(ProcInfo0, HeadVars0),
|
|
remove_specified_positions(UnusedArgs, HeadVars0, HeadVars),
|
|
proc_info_set_headvars(HeadVars, ProcInfo0, ProcInfo1),
|
|
proc_info_get_argmodes(ProcInfo1, ArgModes0),
|
|
remove_specified_positions(UnusedArgs, ArgModes0, ArgModes),
|
|
proc_info_set_argmodes(ArgModes, ProcInfo1, ProcInfo),
|
|
map.set(ProcId, ProcInfo, NewProcs0, NewProcs),
|
|
pred_info_set_proc_table(NewProcs, NewPredInfo0, NewPredInfo),
|
|
|
|
% Add the new proc to the pred table.
|
|
module_info_get_predicate_table(!.ModuleInfo, PredTable0),
|
|
predicate_table_insert(NewPredInfo, NewPredId, PredTable0, PredTable1),
|
|
module_info_set_predicate_table(PredTable1, !ModuleInfo),
|
|
PredModuleName = pred_info_module(NewPredInfo),
|
|
PredName = pred_info_name(NewPredInfo),
|
|
PredSymName = qualified(PredModuleName, PredName),
|
|
% Add the new proc to the new_proc_map.
|
|
NewProcInfo = new_proc_info(NewPredId, ProcId, PredSymName, UnusedArgs),
|
|
map.det_insert(OptProc, NewProcInfo, !NewProcMap).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred delete_unused_args_in_module(bool::in, global_var_usage_map::in,
|
|
list(pred_proc_id)::in, new_proc_map::in,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
delete_unused_args_in_module(VeryVerbose, GlobalVarUsageMap, PredProcIds,
|
|
NewProcMap, !ModuleInfo) :-
|
|
list.foldl(
|
|
delete_unused_args_in_proc_msg(VeryVerbose, GlobalVarUsageMap,
|
|
NewProcMap),
|
|
PredProcIds, !ModuleInfo).
|
|
|
|
:- pred delete_unused_args_in_proc_msg(bool::in, global_var_usage_map::in,
|
|
new_proc_map::in, pred_proc_id::in,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
delete_unused_args_in_proc_msg(VeryVerbose, GlobalVarUsageMap, NewProcMap,
|
|
PredProcId, !ModuleInfo) :-
|
|
(
|
|
VeryVerbose = yes,
|
|
trace [io(!IO)] (
|
|
get_debug_output_stream(!.ModuleInfo, DebugStream, !IO),
|
|
PredProcId = proc(PredId, ProcId),
|
|
module_info_pred_info(!.ModuleInfo, PredId, PredInfo),
|
|
pred_info_get_name(PredInfo, Name),
|
|
pred_info_get_is_pred_or_func(PredInfo, PredOrFunc),
|
|
pred_info_get_orig_arity(PredInfo, PredFormArity),
|
|
user_arity_pred_form_arity(PredOrFunc,
|
|
user_arity(UserArityInt), PredFormArity),
|
|
proc_id_to_int(ProcId, ProcInt),
|
|
io.format(DebugStream, "%% Fixing up %s `%s/%d in mode %d\n",
|
|
[s(pred_or_func_to_str(PredOrFunc)), s(Name),
|
|
i(UserArityInt), i(ProcInt)], !IO)
|
|
)
|
|
;
|
|
VeryVerbose = no
|
|
),
|
|
delete_unused_args_in_proc(GlobalVarUsageMap, PredProcId, NewProcMap,
|
|
!ModuleInfo).
|
|
|
|
:- pred delete_unused_args_in_proc(global_var_usage_map::in, pred_proc_id::in,
|
|
new_proc_map::in, module_info::in, module_info::out) is det.
|
|
|
|
delete_unused_args_in_proc(GlobalVarUsageMap, OldPredProcId, NewProcMap,
|
|
!ModuleInfo) :-
|
|
% Work out which proc we should be fixing up.
|
|
( if map.search(NewProcMap, OldPredProcId, NewProcInfo) then
|
|
NewProcInfo = new_proc_info(PredId, ProcId, _, UnusedArgs)
|
|
else
|
|
OldPredProcId = proc(PredId, ProcId),
|
|
UnusedArgs = []
|
|
),
|
|
map.lookup(GlobalVarUsageMap, OldPredProcId, OldProcLocalVarUsageMap),
|
|
map.keys(OldProcLocalVarUsageMap, UnusedVars),
|
|
module_info_pred_proc_info(!.ModuleInfo, PredId, ProcId,
|
|
PredInfo0, ProcInfo0),
|
|
proc_info_get_var_table(ProcInfo0, VarTable0),
|
|
proc_info_get_headvars(ProcInfo0, HeadVars0),
|
|
proc_info_get_argmodes(ProcInfo0, ArgModes0),
|
|
proc_info_get_goal(ProcInfo0, Goal0),
|
|
remove_specified_positions(UnusedArgs, HeadVars0, HeadVars),
|
|
remove_specified_positions(UnusedArgs, ArgModes0, ArgModes),
|
|
|
|
some [!ProcInfo, !Goal] (
|
|
!:ProcInfo = ProcInfo0,
|
|
!:Goal = Goal0,
|
|
|
|
proc_info_set_headvars(HeadVars, !ProcInfo),
|
|
proc_info_set_argmodes(ArgModes, !ProcInfo),
|
|
|
|
% Remove unused vars from goal.
|
|
% NOTE We should probably remove unused variables from the type map.
|
|
DeleteInfo0 =
|
|
delete_info(!.ModuleInfo, NewProcMap, UnusedVars, VarTable0),
|
|
delete_unused_args_in_goal(!Goal, DeleteInfo0, DeleteInfo, Changed),
|
|
DeleteInfo = delete_info(_, _, _, VarTable1),
|
|
(
|
|
Changed = changed,
|
|
% If anything has changed, rerun quantification.
|
|
NonLocals = set_of_var.list_to_set(HeadVars),
|
|
proc_info_get_rtti_varmaps(!.ProcInfo, RttiVarMaps0),
|
|
implicitly_quantify_goal_general(ord_nl_no_lambda, NonLocals, _,
|
|
!Goal, VarTable1, VarTable, RttiVarMaps0, RttiVarMaps),
|
|
proc_info_set_goal(!.Goal, !ProcInfo),
|
|
proc_info_set_var_table(VarTable, !ProcInfo),
|
|
proc_info_set_rtti_varmaps(RttiVarMaps, !ProcInfo)
|
|
;
|
|
Changed = unchanged
|
|
),
|
|
ProcInfo = !.ProcInfo
|
|
),
|
|
pred_info_set_proc_info(ProcId, ProcInfo, PredInfo0, PredInfo),
|
|
module_info_set_pred_info(PredId, PredInfo, !ModuleInfo).
|
|
|
|
:- type delete_info
|
|
---> delete_info(
|
|
delete_module_info :: module_info,
|
|
delete_new_proc_map :: new_proc_map,
|
|
delete_unused_vars :: list(prog_var),
|
|
delete_var_table :: var_table
|
|
).
|
|
|
|
% This is the important bit of the transformation.
|
|
%
|
|
:- pred delete_unused_args_in_goal(hlds_goal::in, hlds_goal::out,
|
|
delete_info::in, delete_info::out, maybe_changed::out) is det.
|
|
|
|
delete_unused_args_in_goal(Goal0, Goal, !Info, Changed) :-
|
|
delete_unused_args_in_goal_expr(Goal0, Goal1, !Info, Changed),
|
|
Goal1 = hlds_goal(GoalExpr1, GoalInfo1),
|
|
(
|
|
Changed = changed,
|
|
UnusedVars = !.Info ^ delete_unused_vars,
|
|
delete_unused_args_in_goal_info(UnusedVars, GoalInfo1, GoalInfo),
|
|
Goal = hlds_goal(GoalExpr1, GoalInfo)
|
|
;
|
|
Changed = unchanged,
|
|
Goal = Goal0
|
|
).
|
|
|
|
:- pred delete_unused_args_in_goal_expr(hlds_goal::in, hlds_goal::out,
|
|
delete_info::in, delete_info::out, maybe_changed::out) is det.
|
|
|
|
delete_unused_args_in_goal_expr(Goal0, Goal, !Info, Changed) :-
|
|
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
|
|
(
|
|
GoalExpr0 = unify(_Var, _RHS, _Mode, Unify, _Context),
|
|
ModuleInfo = !.Info ^ delete_module_info,
|
|
UnusedVars = !.Info ^ delete_unused_vars,
|
|
( if need_unify(ModuleInfo, UnusedVars, Unify, ChangedPrime) then
|
|
Goal = Goal0,
|
|
Changed = ChangedPrime
|
|
else
|
|
Goal = hlds_goal(true_goal_expr, GoalInfo0),
|
|
Changed = changed
|
|
)
|
|
;
|
|
GoalExpr0 = plain_call(PredId, ProcId, ArgVars0, Builtin,
|
|
UnifyContext, _SymName),
|
|
NewProcMap = !.Info ^ delete_new_proc_map,
|
|
( if map.search(NewProcMap, proc(PredId, ProcId), NewProcInfo) then
|
|
NewProcInfo = new_proc_info(NewPredId, NewProcId, NewSymName,
|
|
UnusedArgs),
|
|
Changed = changed,
|
|
remove_specified_positions(UnusedArgs, ArgVars0, ArgVars),
|
|
GoalExpr = plain_call(NewPredId, NewProcId, ArgVars, Builtin,
|
|
UnifyContext, NewSymName),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
else
|
|
Changed = unchanged,
|
|
Goal = Goal0
|
|
)
|
|
;
|
|
GoalExpr0 = generic_call(_, _, _, _, _),
|
|
Goal = Goal0,
|
|
Changed = unchanged
|
|
;
|
|
GoalExpr0 = call_foreign_proc(Attributes, PredId, ProcId,
|
|
Args0, ExtraArgs0, MaybeTraceRuntimeCond, Impl),
|
|
% The code in here should be kept in sync with the treatment of
|
|
% foreign_procs in traverse_goal.
|
|
Changed0 = unchanged,
|
|
map.init(Subst0),
|
|
list.map_foldl3(rename_apart_unused_foreign_arg,
|
|
Args0, Args, Subst0, Subst1, !Info, Changed0, ArgsChanged),
|
|
list.map_foldl3(rename_apart_unused_foreign_arg,
|
|
ExtraArgs0, ExtraArgs, Subst1, Subst, !Info, ArgsChanged, Changed),
|
|
GoalExpr = call_foreign_proc(Attributes, PredId, ProcId,
|
|
Args, ExtraArgs, MaybeTraceRuntimeCond, Impl),
|
|
rename_vars_in_goal_info(need_not_rename, Subst, GoalInfo0, GoalInfo),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo)
|
|
;
|
|
GoalExpr0 = conj(ConjType, Goals0),
|
|
delete_unused_args_in_conjuncts(Goals0, Goals, !Info,
|
|
unchanged, Changed),
|
|
GoalExpr = conj(ConjType, Goals),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
;
|
|
GoalExpr0 = disj(Goals0),
|
|
delete_unused_args_in_disjuncts(Goals0, Goals, !Info,
|
|
unchanged, Changed),
|
|
GoalExpr = disj(Goals),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
;
|
|
GoalExpr0 = switch(Var, CanFail, Cases0),
|
|
delete_unused_args_in_cases(Cases0, Cases, !Info, unchanged, Changed),
|
|
GoalExpr = switch(Var, CanFail, Cases),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
;
|
|
GoalExpr0 = negation(NegGoal0),
|
|
delete_unused_args_in_goal(NegGoal0, NegGoal, !Info, Changed),
|
|
GoalExpr = negation(NegGoal),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
;
|
|
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
|
|
delete_unused_args_in_goal(Cond0, Cond, !Info, Changed1),
|
|
delete_unused_args_in_goal(Then0, Then, !Info, Changed2),
|
|
delete_unused_args_in_goal(Else0, Else, !Info, Changed3),
|
|
Changed = maybe_util.or_list([Changed1, Changed2, Changed3]),
|
|
GoalExpr = if_then_else(Vars, Cond, Then, Else),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
;
|
|
GoalExpr0 = scope(Reason, SubGoal0),
|
|
( if
|
|
Reason = from_ground_term(TermVar, from_ground_term_construct)
|
|
then
|
|
UnusedVars = !.Info ^ delete_unused_vars,
|
|
( if list.member(TermVar, UnusedVars) then
|
|
Goal = true_goal,
|
|
% We don't change the set of unneeded variables.
|
|
Changed = unchanged
|
|
else
|
|
Goal = Goal0,
|
|
Changed = unchanged
|
|
)
|
|
else
|
|
delete_unused_args_in_goal(SubGoal0, SubGoal, !Info, Changed),
|
|
GoalExpr = scope(Reason, SubGoal),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
)
|
|
;
|
|
GoalExpr0 = shorthand(_),
|
|
% These should have been expanded out by now.
|
|
unexpected($pred, "shorthand")
|
|
).
|
|
|
|
:- pred rename_apart_unused_foreign_arg(foreign_arg::in, foreign_arg::out,
|
|
map(prog_var, prog_var)::in, map(prog_var, prog_var)::out,
|
|
delete_info::in, delete_info::out,
|
|
maybe_changed::in, maybe_changed::out) is det.
|
|
|
|
rename_apart_unused_foreign_arg(Arg0, Arg, !Subst, !Info, !Changed) :-
|
|
Arg0 = foreign_arg(OldVar, MaybeName, OrigType, BoxPolicy),
|
|
(
|
|
MaybeName = yes(_),
|
|
Arg = Arg0
|
|
;
|
|
MaybeName = no,
|
|
VarTable0 = !.Info ^ delete_var_table,
|
|
lookup_var_entry(VarTable0, OldVar, OldVarEntry),
|
|
add_var_entry(OldVarEntry, NewVar, VarTable0, VarTable),
|
|
!Info ^ delete_var_table := VarTable,
|
|
|
|
% It is possible for an unnamed input argument to occur more than once
|
|
% in the list of foreign_args.
|
|
map.set(OldVar, NewVar, !Subst),
|
|
Arg = foreign_arg(NewVar, MaybeName, OrigType, BoxPolicy),
|
|
!:Changed = changed
|
|
).
|
|
|
|
% Remove unused args in each conjunct, and delete the conjuncts
|
|
% from which nothing is left.
|
|
%
|
|
:- pred delete_unused_args_in_conjuncts(
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
delete_info::in, delete_info::out,
|
|
maybe_changed::in, maybe_changed::out) is det.
|
|
|
|
delete_unused_args_in_conjuncts([], [], !Info, !Changed).
|
|
delete_unused_args_in_conjuncts([Goal0 | Goals0], Goals, !Info, !Changed) :-
|
|
delete_unused_args_in_goal(Goal0, Goal, !Info, LocalChanged),
|
|
(
|
|
LocalChanged = changed,
|
|
!:Changed = changed
|
|
;
|
|
LocalChanged = unchanged
|
|
),
|
|
% Replacing a goal with true signals that it is no longer needed.
|
|
( if Goal = hlds_goal(true_goal_expr, _) then
|
|
Goals = Goals1
|
|
else
|
|
Goals = [Goal | Goals1]
|
|
),
|
|
delete_unused_args_in_conjuncts(Goals0, Goals1, !Info, !Changed).
|
|
|
|
% We can't remove unused goals from the list of disjuncts as we do
|
|
% for conjuncts, since that would change the determinism of the goal.
|
|
%
|
|
:- pred delete_unused_args_in_disjuncts(
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
delete_info::in, delete_info::out,
|
|
maybe_changed::in, maybe_changed::out) is det.
|
|
|
|
delete_unused_args_in_disjuncts([], [], !Info, !Changed).
|
|
delete_unused_args_in_disjuncts([Goal0 | Goals0], [Goal | Goals],
|
|
!Info, !Changed) :-
|
|
delete_unused_args_in_goal(Goal0, Goal, !Info, LocalChanged),
|
|
(
|
|
LocalChanged = changed,
|
|
!:Changed = changed
|
|
;
|
|
LocalChanged = unchanged
|
|
),
|
|
delete_unused_args_in_disjuncts(Goals0, Goals, !Info, !Changed).
|
|
|
|
:- pred delete_unused_args_in_cases(list(case)::in, list(case)::out,
|
|
delete_info::in, delete_info::out,
|
|
maybe_changed::in, maybe_changed::out) is det.
|
|
|
|
delete_unused_args_in_cases([], [], !Info, !Changed).
|
|
delete_unused_args_in_cases([Case0 | Cases0], [Case | Cases],
|
|
!Info, !Changed) :-
|
|
Case0 = case(MainConsId, OtherConsIds, Goal0),
|
|
delete_unused_args_in_goal(Goal0, Goal, !Info, LocalChanged),
|
|
Case = case(MainConsId, OtherConsIds, Goal),
|
|
(
|
|
LocalChanged = changed,
|
|
!:Changed = changed
|
|
;
|
|
LocalChanged = unchanged
|
|
),
|
|
delete_unused_args_in_cases(Cases0, Cases, !Info, !Changed).
|
|
|
|
% Fail if the unification is no longer needed.
|
|
%
|
|
:- pred need_unify(module_info::in, list(prog_var)::in, unification::in,
|
|
maybe_changed::out) is semidet.
|
|
|
|
need_unify(ModuleInfo, UnusedVars, Unify, Changed) :-
|
|
(
|
|
Unify = simple_test(_, _),
|
|
% A simple test doesn't have any unused vars to fixup.
|
|
Changed = unchanged
|
|
;
|
|
% Target unused => we don't need the assignment
|
|
% Source unused => Target unused
|
|
Unify = assign(Target, _Source),
|
|
not list.member(Target, UnusedVars),
|
|
Changed = unchanged
|
|
;
|
|
% LVar unused => we don't need the unification
|
|
Unify = construct(LVar, _, _, _, _, _, _),
|
|
not list.member(LVar, UnusedVars),
|
|
Changed = unchanged
|
|
;
|
|
Unify = deconstruct(LVar, _, ArgVars, ArgModes, CanFail, _CanCGC),
|
|
not list.member(LVar, UnusedVars),
|
|
(
|
|
% Are any of the args unused?
|
|
% If so, we need to fix up the goal_info.
|
|
CanFail = cannot_fail,
|
|
check_deconstruct_args(ModuleInfo, UnusedVars, ArgVars, ArgModes,
|
|
no, Changed)
|
|
;
|
|
CanFail = can_fail,
|
|
Changed = unchanged
|
|
)
|
|
;
|
|
% These should have been transformed into calls by polymorphism.m.
|
|
Unify = complicated_unify(_, _, _),
|
|
unexpected($pred, "complicated unify")
|
|
).
|
|
|
|
% Check if any of the arguments of a deconstruction are unused,
|
|
% if so Changed will be yes and quantification will be rerun. Fails if
|
|
% none of the arguments are used. Arguments which further instantiate
|
|
% the deconstructed variable are ignored in this.
|
|
%
|
|
:- pred check_deconstruct_args(module_info::in, list(prog_var)::in,
|
|
list(prog_var)::in, list(unify_mode)::in, bool::in,
|
|
maybe_changed::out) is semidet.
|
|
|
|
check_deconstruct_args(ModuleInfo, UnusedVars, Vars, ArgModes, !.SomeUsed,
|
|
Changed) :-
|
|
(
|
|
Vars = [],
|
|
ArgModes = [],
|
|
!.SomeUsed = yes,
|
|
Changed = unchanged
|
|
;
|
|
Vars = [],
|
|
ArgModes = [_ | _],
|
|
unexpected($pred, "mismatched lists")
|
|
;
|
|
Vars = [_ | _],
|
|
ArgModes = [],
|
|
unexpected($pred, "mismatched lists")
|
|
;
|
|
Vars = [HeadVar | TailVars],
|
|
ArgModes = [HeadArgMode | TailArgModes],
|
|
( if
|
|
% XXX This test seems wrong to me. Why does it look at a mode
|
|
% that is made up of *two initial* insts?
|
|
HeadArgMode = unify_modes_li_lf_ri_rf(InitX, _, InitY, _),
|
|
mode_is_output(ModuleInfo, from_to_mode(InitX, InitY)),
|
|
list.member(HeadVar, UnusedVars)
|
|
then
|
|
check_deconstruct_args(ModuleInfo, UnusedVars,
|
|
TailVars, TailArgModes, !.SomeUsed, _),
|
|
Changed = changed
|
|
else
|
|
!:SomeUsed = yes,
|
|
check_deconstruct_args(ModuleInfo, UnusedVars,
|
|
TailVars, TailArgModes, !.SomeUsed, Changed)
|
|
)
|
|
).
|
|
|
|
% Remove unused vars from the instmap_delta, quantification fixes up
|
|
% the rest.
|
|
%
|
|
:- pred delete_unused_args_in_goal_info(list(prog_var)::in, hlds_goal_info::in,
|
|
hlds_goal_info::out) is det.
|
|
|
|
delete_unused_args_in_goal_info(UnusedVars, !GoalInfo) :-
|
|
InstMap0 = goal_info_get_instmap_delta(!.GoalInfo),
|
|
instmap_delta_delete_vars(UnusedVars, InstMap0, InstMap),
|
|
goal_info_set_instmap_delta(InstMap, !GoalInfo).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type maybe_warn_unused_args
|
|
---> do_not_warn_unused_args
|
|
; do_warn_unused_args.
|
|
|
|
% Except for type_infos, all args that are unused in one mode of a
|
|
% predicate should be unused in all of the modes of a predicate, so we
|
|
% only need to put out one warning for each predicate.
|
|
%
|
|
:- pred gather_warnings_and_pragmas(module_info::in, unused_arg_info::in,
|
|
maybe_warn_unused_args::in, maybe_gather_pragma_unused_args::in,
|
|
list(pred_proc_id)::in, set(pred_id)::in,
|
|
list(error_spec)::in, list(error_spec)::out,
|
|
set(gen_pragma_unused_args_info)::in,
|
|
set(gen_pragma_unused_args_info)::out) is det.
|
|
|
|
gather_warnings_and_pragmas(_, _, _, _, [], _,
|
|
!Specs, !PragmaUnusedArgInfos).
|
|
gather_warnings_and_pragmas(ModuleInfo, UnusedArgInfo, DoWarn, DoPragma,
|
|
[PredProcId | PredProcIds], !.WarnedPredIds,
|
|
!Specs, !PragmaUnusedArgInfos) :-
|
|
( if map.search(UnusedArgInfo, PredProcId, UnusedArgs) then
|
|
PredProcId = proc(PredId, ProcId) ,
|
|
module_info_pred_info(ModuleInfo, PredId, PredInfo),
|
|
( if
|
|
may_gather_warning_pragma_for_pred(ModuleInfo, PredId, PredInfo)
|
|
then
|
|
(
|
|
DoWarn = do_not_warn_unused_args
|
|
;
|
|
DoWarn = do_warn_unused_args,
|
|
maybe_gather_warning(ModuleInfo, PredInfo, PredId, ProcId,
|
|
UnusedArgs, !WarnedPredIds, !Specs)
|
|
),
|
|
(
|
|
DoPragma = do_not_gather_pragma_unused_args
|
|
;
|
|
DoPragma = do_gather_pragma_unused_args,
|
|
maybe_gather_unused_args_pragma(PredInfo, ProcId, UnusedArgs,
|
|
!PragmaUnusedArgInfos)
|
|
)
|
|
else
|
|
true
|
|
)
|
|
else
|
|
true
|
|
),
|
|
gather_warnings_and_pragmas(ModuleInfo, UnusedArgInfo, DoWarn, DoPragma,
|
|
PredProcIds, !.WarnedPredIds, !Specs, !PragmaUnusedArgInfos).
|
|
|
|
:- pred may_gather_warning_pragma_for_pred(module_info::in,
|
|
pred_id::in, pred_info::in) is semidet.
|
|
|
|
may_gather_warning_pragma_for_pred(ModuleInfo, PredId, PredInfo) :-
|
|
( if
|
|
may_gather_warning_pragma_for_pred_old(ModuleInfo, PredId, PredInfo)
|
|
then
|
|
( if may_gather_warning_pragma_for_pred_new(PredInfo) then
|
|
true
|
|
else
|
|
unexpected($pred, "old succeeds, new fails")
|
|
)
|
|
else
|
|
( if may_gather_warning_pragma_for_pred_new(PredInfo) then
|
|
unexpected($pred, "old fails, new succeeds")
|
|
else
|
|
fail
|
|
)
|
|
).
|
|
|
|
:- pred may_gather_warning_pragma_for_pred_old(module_info::in,
|
|
pred_id::in, pred_info::in) is semidet.
|
|
|
|
may_gather_warning_pragma_for_pred_old(ModuleInfo, PredId, PredInfo) :-
|
|
not pred_info_is_imported(PredInfo),
|
|
pred_info_get_status(PredInfo, PredStatus),
|
|
PredStatus \= pred_status(status_opt_imported),
|
|
|
|
% Don't warn about builtins that have unused arguments.
|
|
not pred_info_is_builtin(PredInfo),
|
|
not is_unify_index_or_compare_pred(PredInfo),
|
|
|
|
% Don't warn about stubs for procedures with no clauses --
|
|
% in that case, we *expect* none of the arguments to be used.
|
|
pred_info_get_markers(PredInfo, Markers),
|
|
not marker_is_present(Markers, marker_stub),
|
|
|
|
% Don't warn about lambda expressions not using arguments.
|
|
% (The warning message for these doesn't contain context,
|
|
% so it's useless).
|
|
Name = pred_info_name(PredInfo),
|
|
not string.sub_string_search(Name, "__LambdaGoal__", _),
|
|
|
|
% Don't warn for a specialized version.
|
|
not (
|
|
string.sub_string_search(Name, "__ho", Position),
|
|
string.length(Name, Length),
|
|
IdLen = Length - Position - 4,
|
|
string.right(Name, IdLen, Id),
|
|
string.to_int(Id, _)
|
|
),
|
|
module_info_get_type_spec_tables(ModuleInfo, TypeSpecTables),
|
|
TypeSpecTables = type_spec_tables(_, TypeSpecForcePreds, _, _),
|
|
not set.member(PredId, TypeSpecForcePreds),
|
|
|
|
% Don't warn for a loop-invariant hoisting-generated procedure.
|
|
pred_info_get_origin(PredInfo, Origin),
|
|
not (
|
|
Origin = origin_proc_transform(proc_transform_loop_inv(_, _), _, _, _)
|
|
),
|
|
|
|
% XXX We don't currently generate pragmas for the automatically
|
|
% generated class instance methods because the compiler aborts
|
|
% when trying to read them back in from the `.opt' files.
|
|
not marker_is_present(Markers, marker_class_instance_method),
|
|
not marker_is_present(Markers, marker_named_class_instance_method).
|
|
|
|
:- pred may_gather_warning_pragma_for_pred_new(pred_info::in) is semidet.
|
|
|
|
may_gather_warning_pragma_for_pred_new(PredInfo) :-
|
|
pred_info_get_status(PredInfo, PredStatus),
|
|
% Previously, this test was effectively:
|
|
% PredStatus \= pred_status(status_imported(_)),
|
|
% PredStatus \= pred_status(status_opt_imported),
|
|
% PredStatus \= pred_status(status_external(_)),
|
|
% However, PredStatus cannot be status_abstract_imported,
|
|
% and the status_pseudo_imported case is caught by
|
|
% the test for made_for_uci below.
|
|
pred_status_defined_in_this_module(PredStatus) = yes,
|
|
|
|
pred_info_get_origin(PredInfo, Origin),
|
|
require_complete_switch [Origin]
|
|
(
|
|
Origin = origin_user(UserMade),
|
|
require_complete_switch [UserMade]
|
|
(
|
|
UserMade = user_made_pred(_, _, _),
|
|
% Don't warn about builtins that have unused arguments.
|
|
not pred_info_is_builtin(PredInfo)
|
|
;
|
|
UserMade = user_made_lambda(_, _, _),
|
|
% Don't warn about lambda expressions not using arguments.
|
|
% (The warning message for these doesn't contain context,
|
|
% so it is useless).
|
|
% NOTE We *could* add any required context. However,
|
|
% in some cases, people use lambdas as a shim between
|
|
% their own code, and library predicates over whose argument
|
|
% lists they have no control. In such cases, ignoring
|
|
% an argument that the user code does not need but the
|
|
% library predicate insists on supplying may be
|
|
% the *whole point* of the lambda expression.
|
|
%
|
|
% XXX The above is nice reasoning, but line in the old version
|
|
% of this test that is relevant here, namely
|
|
%
|
|
% not string.sub_string_search(Name, "__LambdaGoal__", _),
|
|
%
|
|
% has NOT caused the test to fail since *1997*. Specifically,
|
|
% since Tom's commit 2980b5947418ca8b0ed5312aa87d34b4c6ff5514,
|
|
% which replaced __LambdaGoal__ in the names of the predicates
|
|
% we construct for lambda expressions with IntroducedFrom__.
|
|
true
|
|
;
|
|
UserMade = user_made_class_method(_, _)
|
|
;
|
|
UserMade = user_made_instance_method(_, _),
|
|
% XXX We don't currently generate pragmas for the automatically
|
|
% generated class instance methods because the compiler aborts
|
|
% when trying to read them back in from the `.opt' files.
|
|
%
|
|
% I am also not sure whether we would *want* to generated warnings
|
|
% about unused arguments in instance methods. If a class method
|
|
% has an input that is needed by some instances but not others,
|
|
% warning about the instances in the second category would not be
|
|
% helpful, since the class methods needs the argument, and
|
|
% the instance must conform to it.
|
|
( if
|
|
( marker_is_present(Markers, marker_class_instance_method)
|
|
; marker_is_present(Markers,
|
|
marker_named_class_instance_method)
|
|
)
|
|
then
|
|
fail
|
|
else
|
|
unexpected($pred, "user_made_instance_method with marker")
|
|
)
|
|
;
|
|
UserMade = user_made_assertion(_, _, _)
|
|
% XXX By construction, assertions should never have any
|
|
% unused arguments, so trying to find them is a waste of time.
|
|
),
|
|
|
|
% Don't warn about stubs for procedures with no clauses --
|
|
% in that case, we *expect* none of the arguments to be used.
|
|
%
|
|
% XXX I (zs) am not sure whether typecheck.m can ever mark as stub
|
|
% a predicate whose origin is not user_made_pred.
|
|
% (There is a test filtering out predicates with marker_class_method,
|
|
% but nothing similar for the other values of UserMade.)
|
|
pred_info_get_markers(PredInfo, Markers),
|
|
not marker_is_present(Markers, marker_stub)
|
|
;
|
|
Origin = origin_compiler(CompilerMade),
|
|
require_complete_switch [CompilerMade]
|
|
(
|
|
CompilerMade = made_for_uci(_, _),
|
|
fail
|
|
;
|
|
( CompilerMade = made_for_deforestation(_, _)
|
|
; CompilerMade = made_for_solver_repn(_, _)
|
|
; CompilerMade = made_for_tabling(_, _)
|
|
; CompilerMade = made_for_mutable(_, _, _)
|
|
; CompilerMade = made_for_initialise(_, _)
|
|
; CompilerMade = made_for_finalise(_, _)
|
|
)
|
|
% XXX It is likely that some of these kinds of predicates
|
|
% can never contain unused args, which means that
|
|
% processing them is pointless.
|
|
)
|
|
;
|
|
Origin = origin_pred_transform(PredTransform, _, _),
|
|
require_complete_switch [PredTransform]
|
|
(
|
|
PredTransform = pred_transform_pragma_type_spec(_),
|
|
fail
|
|
;
|
|
( PredTransform = pred_transform_distance_granularity(_)
|
|
; PredTransform = pred_transform_table_generator
|
|
; PredTransform = pred_transform_ssdebug(_)
|
|
; PredTransform = pred_transform_structure_reuse
|
|
)
|
|
)
|
|
;
|
|
Origin = origin_proc_transform(ProcTransform, _, _, _),
|
|
require_complete_switch [ProcTransform]
|
|
(
|
|
( ProcTransform = proc_transform_loop_inv(_, _)
|
|
; ProcTransform = proc_transform_higher_order_spec(_)
|
|
),
|
|
fail
|
|
;
|
|
( ProcTransform = proc_transform_user_type_spec(_, _)
|
|
; ProcTransform = proc_transform_accumulator(_, _)
|
|
; ProcTransform = proc_transform_tuple(_, _)
|
|
; ProcTransform = proc_transform_untuple(_, _)
|
|
; ProcTransform = proc_transform_dep_par_conj(_)
|
|
; ProcTransform = proc_transform_par_loop_ctrl
|
|
; ProcTransform = proc_transform_lcmc(_, _)
|
|
; ProcTransform = proc_transform_stm_expansion
|
|
; ProcTransform = proc_transform_io_tabling
|
|
; ProcTransform = proc_transform_direct_arg_in_out
|
|
)
|
|
% XXX It is likely that some of these kinds of predicates
|
|
% can never contain unused args, which means that
|
|
% processing them is pointless.
|
|
;
|
|
ProcTransform = proc_transform_unused_args(_),
|
|
% These shouldn't have been created yet,
|
|
% since we do not ever repeat the unused_args pass.
|
|
unexpected($pred, "proc_transform_unused_args")
|
|
)
|
|
).
|
|
|
|
:- pred maybe_gather_warning(module_info::in, pred_info::in,
|
|
pred_id::in, proc_id::in, list(int)::in,
|
|
set(pred_id)::in, set(pred_id)::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
maybe_gather_warning(ModuleInfo, PredInfo, PredId, ProcId, UnusedArgs0,
|
|
!WarnedPredIds, !Specs) :-
|
|
( if set.member(PredId, !.WarnedPredIds) then
|
|
true
|
|
else
|
|
set.insert(PredId, !WarnedPredIds),
|
|
pred_info_get_proc_table(PredInfo, ProcTable),
|
|
map.lookup(ProcTable, ProcId, Proc),
|
|
pred_info_get_orig_arity(PredInfo, PredFormArity),
|
|
proc_info_get_headvars(Proc, HeadVars),
|
|
NumExtraArgs = num_extra_args(PredFormArity, HeadVars),
|
|
% Strip off the extra type_info/typeclass_info arguments
|
|
% inserted at the front by polymorphism.m.
|
|
drop_poly_inserted_args(NumExtraArgs, UnusedArgs0, UnusedArgs),
|
|
(
|
|
UnusedArgs = [_ | _],
|
|
Spec = report_unused_args(ModuleInfo, PredInfo, UnusedArgs),
|
|
!:Specs = [Spec | !.Specs]
|
|
;
|
|
UnusedArgs = []
|
|
)
|
|
).
|
|
|
|
% Adjust the argument numbers from how they look in an argument list
|
|
% *with* the extra arguments inserted by polymorphism, to how they would
|
|
% look without them. This means dropping the inserted argument
|
|
% if they appear, and subtracting the number of inserted arguments
|
|
% from the argument numbers of all the other arguments.
|
|
%
|
|
:- pred drop_poly_inserted_args(int::in, list(int)::in, list(int)::out) is det.
|
|
|
|
drop_poly_inserted_args(_, [], []).
|
|
drop_poly_inserted_args(NumInserted, [HeadArgWith | TailArgsWith],
|
|
ArgsWithout) :-
|
|
drop_poly_inserted_args(NumInserted, TailArgsWith, TailArgsWithout),
|
|
HeadArgWithout = HeadArgWith - NumInserted,
|
|
( if HeadArgWithout < 1 then
|
|
ArgsWithout = TailArgsWithout
|
|
else
|
|
ArgsWithout = [HeadArgWithout | TailArgsWithout]
|
|
).
|
|
|
|
% Warn about unused arguments in a predicate. We consider an argument
|
|
% unused *only* if it is unused in *every* mode of the predicate.
|
|
% We also never warn about arguments inserted by the polymorphism pass.
|
|
%
|
|
% The latter test is done by maybe_gather_warning with help from
|
|
% drop_poly_inserted_args.
|
|
%
|
|
% XXX I (zs) would like to know where the first test is done,
|
|
% since it is *not* done here. My suspicion is that it is not done at all.
|
|
%
|
|
:- func report_unused_args(module_info, pred_info, list(int)) = error_spec.
|
|
|
|
report_unused_args(_ModuleInfo, PredInfo, UnusedArgs) = Spec :-
|
|
list.length(UnusedArgs, NumArgs),
|
|
pred_info_get_context(PredInfo, Context),
|
|
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
|
|
ModuleName = pred_info_module(PredInfo),
|
|
PredName = pred_info_name(PredInfo),
|
|
pred_info_get_orig_arity(PredInfo, PredFormArity),
|
|
user_arity_pred_form_arity(PredOrFunc,
|
|
user_arity(UserArityInt), PredFormArity),
|
|
SNA = sym_name_arity(qualified(ModuleName, PredName), UserArityInt),
|
|
Pieces1 = [words("In"), fixed(pred_or_func_to_full_str(PredOrFunc)),
|
|
qual_sym_name_arity(SNA), suffix(":"), nl, words("warning:")],
|
|
UnusedArgNs = list.map(func(N) = int_fixed(N), UnusedArgs),
|
|
UnusedArgPieces = piece_list_to_color_pieces(color_subject, "and", [],
|
|
UnusedArgNs),
|
|
( if NumArgs = 1 then
|
|
Pieces2 = [words("argument")] ++ UnusedArgPieces ++
|
|
[words("is")] ++ color_as_incorrect([words("unused.")]) ++ [nl]
|
|
else
|
|
Pieces2 = [words("arguments")] ++ UnusedArgPieces ++
|
|
[words("are")] ++ color_as_incorrect([words("unused.")]) ++ [nl]
|
|
),
|
|
Spec = spec($pred, severity_warning(warn_requested_by_option),
|
|
phase_code_gen, Context, Pieces1 ++ Pieces2).
|
|
|
|
:- pred maybe_gather_unused_args_pragma(pred_info::in, proc_id::in,
|
|
list(int)::in,
|
|
set(gen_pragma_unused_args_info)::in,
|
|
set(gen_pragma_unused_args_info)::out) is det.
|
|
|
|
maybe_gather_unused_args_pragma(PredInfo, ProcId, UnusedArgs,
|
|
!UnusedArgInfos) :-
|
|
( if
|
|
( pred_info_is_exported(PredInfo)
|
|
; pred_info_is_opt_exported(PredInfo)
|
|
; pred_info_is_exported_to_submodules(PredInfo)
|
|
),
|
|
UnusedArgs = [_ | _]
|
|
then
|
|
ModuleName = pred_info_module(PredInfo),
|
|
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
|
|
PredName = pred_info_name(PredInfo),
|
|
PredSymName = qualified(ModuleName, PredName),
|
|
pred_info_get_orig_arity(PredInfo, PredFormArity),
|
|
user_arity_pred_form_arity(PredOrFunc, UserArity, PredFormArity),
|
|
proc_id_to_int(ProcId, ModeNum),
|
|
PredNameArityPFMn = proc_pf_name_arity_mn(PredOrFunc, PredSymName,
|
|
UserArity, ModeNum),
|
|
% We can either collect a set of gen_pragma_unused_args
|
|
% with dummy contexts and item sequence numbers now,
|
|
% or we can collect PredNameArityPFMn/UnusedArgs pairs,
|
|
% and add the dummy contexts and item sequence numbers to them
|
|
% later. Both should work; this is marginally simpler to program.
|
|
UnusedArgInfo = gen_pragma_unused_args_info(PredNameArityPFMn,
|
|
UnusedArgs, dummy_context, item_no_seq_num),
|
|
set.insert(UnusedArgInfo, !UnusedArgInfos)
|
|
else
|
|
true
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred maybe_record_intermod_unused_args(module_info::in, unused_arg_info::in,
|
|
pred_id::in, analysis_info::in, analysis_info::out) is det.
|
|
|
|
maybe_record_intermod_unused_args(ModuleInfo, UnusedArgInfo, PredId,
|
|
!AnalysisInfo) :-
|
|
module_info_pred_info(ModuleInfo, PredId, PredInfo),
|
|
ProcIds = pred_info_all_procids(PredInfo),
|
|
list.foldl(
|
|
maybe_record_intermod_unused_args_2(ModuleInfo, UnusedArgInfo,
|
|
PredId, PredInfo),
|
|
ProcIds, !AnalysisInfo).
|
|
|
|
:- pred maybe_record_intermod_unused_args_2(module_info::in,
|
|
unused_arg_info::in, pred_id::in, pred_info::in, proc_id::in,
|
|
analysis_info::in, analysis_info::out) is det.
|
|
|
|
maybe_record_intermod_unused_args_2(ModuleInfo, UnusedArgInfo,
|
|
PredId, PredInfo, ProcId, !AnalysisInfo) :-
|
|
( if
|
|
procedure_is_exported(ModuleInfo, PredInfo, ProcId),
|
|
not is_unify_index_or_compare_pred(PredInfo)
|
|
then
|
|
PPId = proc(PredId, ProcId),
|
|
( if map.search(UnusedArgInfo, PPId, UnusedArgs) then
|
|
Answer = unused_args_answer(UnusedArgs)
|
|
else
|
|
Answer = unused_args_answer([])
|
|
),
|
|
ppid_to_module_name_func_id(ModuleInfo, PPId, ModuleName, FuncId),
|
|
record_result(ModuleName, FuncId, unused_args_call, Answer, optimal,
|
|
!AnalysisInfo)
|
|
else
|
|
true
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% If a procedure in this module calls a procedure from another module,
|
|
% then we assume that this module depends on the analysis results of that
|
|
% other procedure.
|
|
%
|
|
% This way of constructing the intermodule dependency graph is easier than
|
|
% actually keeping track of which external analysis results we have used
|
|
% in order to reach analysis results for this module.
|
|
% It works because (1) we only have one type of call pattern so we don't
|
|
% need to know which call patterns are used, and (2) we only record the
|
|
% entire module as a dependency, so we don't have to know which exported
|
|
% procedure is calling (directly or indirectly) which imported procedure.
|
|
%
|
|
:- pred record_intermod_dependencies(module_info::in, pred_proc_id::in,
|
|
analysis_info::in, analysis_info::out) is det.
|
|
|
|
record_intermod_dependencies(ModuleInfo, CallerPredProcId, !AnalysisInfo) :-
|
|
module_info_pred_proc_info(ModuleInfo, CallerPredProcId,
|
|
_CallerPredInfo, CallerProcInfo),
|
|
proc_info_get_goal(CallerProcInfo, Goal),
|
|
pred_proc_ids_called_from_goal(Goal, CalleePredProcIds),
|
|
list.foldl(record_intermod_dependencies_2(ModuleInfo),
|
|
CalleePredProcIds, !AnalysisInfo).
|
|
|
|
:- pred record_intermod_dependencies_2(module_info::in, pred_proc_id::in,
|
|
analysis_info::in, analysis_info::out) is det.
|
|
|
|
record_intermod_dependencies_2(ModuleInfo, CalleePredProcId, !AnalysisInfo) :-
|
|
CalleePredProcId = proc(CalleePredId, _),
|
|
module_info_pred_info(ModuleInfo, CalleePredId, CalleePredInfo),
|
|
( if
|
|
pred_info_is_imported_not_external(CalleePredInfo),
|
|
not is_unify_index_or_compare_pred(CalleePredInfo)
|
|
then
|
|
ppid_to_module_name_func_id(ModuleInfo, CalleePredProcId,
|
|
CalleeModule, CalleeFuncId),
|
|
Call = unused_args_call,
|
|
Answer = _ : unused_args_answer,
|
|
get_func_info(ModuleInfo, CalleeModule, CalleeFuncId, Call, Answer,
|
|
FuncInfo),
|
|
record_dependency(CalleeModule, CalleeFuncId, FuncInfo, Call, Answer,
|
|
!AnalysisInfo)
|
|
else
|
|
true
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% The types we use to track which arguments may possibly be unused,
|
|
% and the operations on them.
|
|
%
|
|
|
|
% A collection of the local_var_usage_map structures of each procedure.
|
|
:- type global_var_usage_map == map(pred_proc_id, local_var_usage_map).
|
|
|
|
% Values of this type map variables in a procedure that are
|
|
% not yet known to be used to their aliases. When we find out that
|
|
% either the variable, or one of its aliases, is used, we delete
|
|
% the variable from the map. The absence of the variable from the map
|
|
% implies that the variable is used.
|
|
%
|
|
% XXX Document exactly what set of variables ever get put into this map.
|
|
% Is it just the variables representing the input args of the procedure
|
|
% we are analyzing, or do other variables, such as those aliases,
|
|
% get put in here as well?
|
|
:- type local_var_usage_map == map(prog_var, required_by).
|
|
|
|
% For each variable Var that is not yet definitely known to be used,
|
|
% we record information about the set of variables whose computation
|
|
% requires the value of Var. We do this because if any of those
|
|
% requiring vars is used, then Var is used as well, since it will be used
|
|
% to compute them.
|
|
%
|
|
% We record this set of variables in two parts: a set of local variables,
|
|
% and a set of procedure argument variables. Note that the second set
|
|
% *may* refer to the arguments of the procedure in which Var occurs.
|
|
%
|
|
% The init_global_var_usage_map pass adds entries to each of these sets
|
|
% as required. After that pass is finished, the rest of our algorithm,
|
|
% implemented by record_required_vars_as_used_to_fixpoint, can and will
|
|
% delete a variable's whole entry from its procedure's local_var_usage_map
|
|
% when it decides that the variable is definely USED. On the other hand,
|
|
% our algorithm can never decide that a variable is definitely UNUSED
|
|
% until record_required_vars_as_used_to_fixpoint is finished. And by then,
|
|
% only the absence or presence of a required_by entry for a variable in
|
|
% the local_var_usage_map matters; the contents of any required_by entry
|
|
% are no longer relevant. This is why we never actually delete
|
|
% any elements from these two sets.
|
|
:- type required_by
|
|
---> required_by(
|
|
% The set of requiring local variables.
|
|
set(prog_var),
|
|
|
|
% The set of requiring procedure argument variables.
|
|
set(arg_var_in_proc)
|
|
).
|
|
|
|
% We identify a specific argument of a procedure by storing ...
|
|
:- type arg_var_in_proc
|
|
---> arg_var_in_proc(
|
|
% ... the identity of the procedure, and ...
|
|
pred_proc_id,
|
|
|
|
% ... the identity of the variable that represents that
|
|
% argument in the list of head variables of that procedure
|
|
% (as returned by proc_info_get_headvars). This means that
|
|
% this prog_var is NOT in the varset of the procedure
|
|
% whose whole BODY GOAL we are analyzing, but in the varset
|
|
% of the procedure that is the CALLEE of the call we are
|
|
% processing.
|
|
%
|
|
% Simon chose this representation over an argument number.
|
|
% Both require a translation from the caller to the callee's
|
|
% context: the prog_var representation requires it when an
|
|
% arg_var_in_proc structure is created, while the argument
|
|
% number representation requires it when they are used.
|
|
% The prog_var representation looks simpler, but it is also
|
|
% more error-prone, because the compiler cannot help detect
|
|
% confusing a local variable for a variable in another
|
|
% procedure, or vice versa.
|
|
prog_var
|
|
).
|
|
|
|
%---------------------%
|
|
%
|
|
% Add requirements of local vars.
|
|
%
|
|
|
|
:- pred local_var_is_required_by_local_vars(prog_var::in, list(prog_var)::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
local_var_is_required_by_local_vars(LocalVar, NewRequiringVars,
|
|
!LocalVarUsageMap) :-
|
|
( if map.search(!.LocalVarUsageMap, LocalVar, RequiredBy0) then
|
|
RequiredBy0 = required_by(RequiringLocalVars0, RequiringProcArgs),
|
|
set.insert_list(NewRequiringVars,
|
|
RequiringLocalVars0, RequiringLocalVars),
|
|
RequiredBy = required_by(RequiringLocalVars, RequiringProcArgs),
|
|
map.det_update(LocalVar, RequiredBy, !LocalVarUsageMap)
|
|
else
|
|
true
|
|
).
|
|
|
|
:- pred local_vars_are_required_by_local_var(list(prog_var)::in, prog_var::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
local_vars_are_required_by_local_var([], _, !LocalVarUsageMap).
|
|
local_vars_are_required_by_local_var([LocalVar | LocalVars], RequiringVar,
|
|
!LocalVarUsageMap) :-
|
|
( if map.search(!.LocalVarUsageMap, LocalVar, RequiredBy0) then
|
|
RequiredBy0 = required_by(RequiringLocalVars0, RequiringProcArgs),
|
|
set.insert(RequiringVar, RequiringLocalVars0, RequiringLocalVars),
|
|
RequiredBy = required_by(RequiringLocalVars, RequiringProcArgs),
|
|
map.det_update(LocalVar, RequiredBy, !LocalVarUsageMap)
|
|
else
|
|
true
|
|
),
|
|
local_vars_are_required_by_local_var(LocalVars, RequiringVar,
|
|
!LocalVarUsageMap).
|
|
|
|
%---------------------%
|
|
%
|
|
% Add requirements of procedure arguments.
|
|
%
|
|
|
|
:- pred local_var_is_required_by_proc_arg(prog_var::in, arg_var_in_proc::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
local_var_is_required_by_proc_arg(LocalVar, ArgVarInProc, !LocalVarUsageMap) :-
|
|
( if map.search(!.LocalVarUsageMap, LocalVar, RequiredBy0) then
|
|
RequiredBy0 = required_by(RequiringLocalVars, RequiringProcArgs0),
|
|
set.insert(ArgVarInProc, RequiringProcArgs0, RequiringProcArgs),
|
|
RequiredBy = required_by(RequiringLocalVars, RequiringProcArgs),
|
|
map.det_update(LocalVar, RequiredBy, !LocalVarUsageMap)
|
|
else
|
|
true
|
|
).
|
|
|
|
:- pred local_vars_are_required_by_proc_arg(list(prog_var)::in,
|
|
arg_var_in_proc::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
local_vars_are_required_by_proc_arg([], _ArgVarInProc, !LocalVarUsageMap).
|
|
local_vars_are_required_by_proc_arg([LocalVar | LocalVars], ArgVarInProc,
|
|
!LocalVarUsageMap) :-
|
|
local_var_is_required_by_proc_arg(LocalVar, ArgVarInProc,
|
|
!LocalVarUsageMap),
|
|
local_vars_are_required_by_proc_arg(LocalVars, ArgVarInProc,
|
|
!LocalVarUsageMap).
|
|
|
|
%---------------------%
|
|
%
|
|
% Record that variables are used.
|
|
%
|
|
|
|
:- pred record_vars_as_used(list(prog_var)::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
record_vars_as_used(Vars, !LocalVarUsageMap) :-
|
|
map.delete_list(Vars, !LocalVarUsageMap).
|
|
|
|
:- pred record_var_as_used(prog_var::in,
|
|
local_var_usage_map::in, local_var_usage_map::out) is det.
|
|
|
|
record_var_as_used(Var, !LocalVarUsageMap) :-
|
|
map.delete(Var, !LocalVarUsageMap).
|
|
|
|
%---------------------%
|
|
%
|
|
% Check whether a variable is used.
|
|
%
|
|
|
|
% Succeed if and only if the given argument variable of the
|
|
% given procedure is *definitely* used, and our record of it is not just
|
|
% "it is used *if* some of these *other* variables are used".
|
|
%
|
|
:- pred proc_arg_var_is_used(global_var_usage_map::in, pred_proc_id::in,
|
|
prog_var::in) is semidet.
|
|
|
|
proc_arg_var_is_used(GlobalVarUsageMap, PredProcId, Var) :-
|
|
% Note that GlobalVarUsageMap will have required_by variable entries
|
|
% for local procedures that mention non-local procedures, which
|
|
% do *not* occur in GlobalVarUsageMap as keys. This is why calling
|
|
% map.lookup on GlobalVarUsageMap would not work.
|
|
not (
|
|
map.search(GlobalVarUsageMap, PredProcId, LocalVarUsageMap),
|
|
map.contains(LocalVarUsageMap, Var)
|
|
).
|
|
|
|
% Succeed if and only if the given local variable is *definitely* used,
|
|
% and our record of it not just "it is used *if* these *other* variables
|
|
% are used".
|
|
%
|
|
:- pred local_var_is_used(local_var_usage_map::in, prog_var::in) is semidet.
|
|
|
|
local_var_is_used(LocalVarUsageMap, Var) :-
|
|
not map.contains(LocalVarUsageMap, Var).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred remove_specified_positions(list(int)::in,
|
|
list(T)::in, list(T)::out) is det.
|
|
|
|
remove_specified_positions(ArgNumsToRemove, !List) :-
|
|
remove_specified_positions_loop(ArgNumsToRemove, 1, !List).
|
|
|
|
:- pred remove_specified_positions_loop(list(int)::in, int::in,
|
|
list(T)::in, list(T)::out) is det.
|
|
|
|
remove_specified_positions_loop(_ArgNumsToRemove, _ArgNum,
|
|
List0 @ [], List0).
|
|
remove_specified_positions_loop(ArgNumsToRemove, ArgNum,
|
|
List0 @ [Head0 | Tail0], List) :-
|
|
(
|
|
ArgNumsToRemove = [],
|
|
List = List0
|
|
;
|
|
ArgNumsToRemove = [_ | _],
|
|
remove_specified_positions_loop(ArgNumsToRemove, ArgNum + 1,
|
|
Tail0, Tail),
|
|
( if list.member(ArgNum, ArgNumsToRemove) then
|
|
List = Tail
|
|
else
|
|
List = [Head0 | Tail]
|
|
)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Types and instances used by mmc_analysis.m.
|
|
%
|
|
|
|
:- type unused_args_func_info
|
|
---> unused_args_func_info(pred_form_arity).
|
|
|
|
:- type unused_args_call
|
|
---> unused_args_call.
|
|
|
|
:- type unused_args_answer
|
|
---> unused_args_answer(
|
|
% The list of unused arguments is in sorted order.
|
|
args :: list(int)
|
|
).
|
|
|
|
:- func get_unused_args(unused_args_answer) = list(int).
|
|
|
|
get_unused_args(UnusedArgs) = UnusedArgs ^ args.
|
|
|
|
:- instance analysis(unused_args_func_info, unused_args_call,
|
|
unused_args_answer) where
|
|
[
|
|
analysis_name(_, _) = analysis_name,
|
|
analysis_version_number(_, _) = 3,
|
|
preferred_fixpoint_type(_, _) = least_fixpoint,
|
|
bottom(unused_args_func_info(pred_form_arity(Arity)), _) =
|
|
unused_args_answer(1 .. Arity),
|
|
top(_, _) = unused_args_answer([]),
|
|
(get_func_info(ModuleInfo, ModuleName, FuncId, _, _, FuncInfo) :-
|
|
func_id_to_ppid(ModuleInfo, ModuleName, FuncId, proc(PredId, _)),
|
|
module_info_pred_info(ModuleInfo, PredId, PredInfo),
|
|
pred_info_get_orig_arity(PredInfo, PredFormArity),
|
|
FuncInfo = unused_args_func_info(PredFormArity)
|
|
)
|
|
].
|
|
|
|
:- func analysis_name = string.
|
|
|
|
analysis_name = "unused_args".
|
|
|
|
:- instance call_pattern(unused_args_func_info, unused_args_call) where [].
|
|
:- instance partial_order(unused_args_func_info, unused_args_call) where [
|
|
( more_precise_than(_, _, _) :-
|
|
semidet_fail
|
|
),
|
|
equivalent(_, Call, Call)
|
|
].
|
|
|
|
:- instance to_term(unused_args_call) where [
|
|
( to_term(unused_args_call) = Term :-
|
|
Term = term.functor(atom("any"), [], dummy_context)
|
|
),
|
|
( from_term(Term, unused_args_call) :-
|
|
Term = term.functor(atom("any"), [], _)
|
|
)
|
|
].
|
|
|
|
:- instance answer_pattern(unused_args_func_info, unused_args_answer) where [].
|
|
:- instance partial_order(unused_args_func_info, unused_args_answer) where [
|
|
(more_precise_than(_, Answer1, Answer2) :-
|
|
Answer1 = unused_args_answer(Args1),
|
|
Answer2 = unused_args_answer(Args2),
|
|
set.subset(sorted_list_to_set(Args2), sorted_list_to_set(Args1))
|
|
),
|
|
equivalent(_, Args, Args)
|
|
].
|
|
|
|
:- instance to_term(unused_args_answer) where [
|
|
( to_term(unused_args_answer(Args)) = Term :-
|
|
type_to_term(Args, Term)
|
|
),
|
|
( from_term(Term, unused_args_answer(Args)) :-
|
|
term_to_type(Term, Args)
|
|
)
|
|
].
|
|
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Predicates that can help debug the code of this module.
|
|
%
|
|
|
|
:- pred write_global_var_usage_map(io.text_output_stream::in, module_info::in,
|
|
global_var_usage_map::in, io::di, io::uo) is det.
|
|
|
|
write_global_var_usage_map(Stream, ModuleInfo, GlobalVarUsageMap, !IO) :-
|
|
map.foldl(write_local_var_usage_map(Stream, ModuleInfo),
|
|
GlobalVarUsageMap, !IO).
|
|
|
|
:- pred write_local_var_usage_map(io.text_output_stream::in, module_info::in,
|
|
pred_proc_id::in, local_var_usage_map::in, io::di, io::uo) is det.
|
|
|
|
write_local_var_usage_map(Stream, ModuleInfo, PredProcId,
|
|
LocalVarUsageMap, !IO) :-
|
|
PredProcIdStr = pred_proc_id_to_dev_string(ModuleInfo, PredProcId),
|
|
io.format(Stream, "\n%s:\n", [s(PredProcIdStr)], !IO),
|
|
map.to_assoc_list(LocalVarUsageMap, LocalVarUsages),
|
|
module_info_proc_info(ModuleInfo, PredProcId, ProcInfo),
|
|
proc_info_get_var_table(ProcInfo, VarTable),
|
|
list.foldl2(
|
|
write_var_requiring_vars(Stream, ModuleInfo, VarTable), LocalVarUsages,
|
|
[], RevNoDependVars, !IO),
|
|
list.reverse(RevNoDependVars, NoDependVars),
|
|
(
|
|
NoDependVars = []
|
|
;
|
|
NoDependVars = [_ | _],
|
|
NoDependVarsStr =
|
|
mercury_vars_to_string(VarTable, print_name_and_num, NoDependVars),
|
|
io.format(Stream, "nodepend vars: %s\n", [s(NoDependVarsStr)], !IO)
|
|
).
|
|
|
|
:- pred write_var_requiring_vars(io.text_output_stream::in, module_info::in,
|
|
var_table::in, pair(prog_var, required_by)::in,
|
|
list(prog_var)::in, list(prog_var)::out, io::di, io::uo) is det.
|
|
|
|
write_var_requiring_vars(Stream, ModuleInfo, VarTable, Var - RequiringVars,
|
|
!RevNoDependVars, !IO) :-
|
|
RequiringVars = required_by(LocalVarSet, ArgVarInProcsSet),
|
|
set.to_sorted_list(LocalVarSet, LocalVars),
|
|
set.to_sorted_list(ArgVarInProcsSet, ArgVarsInProcs),
|
|
( if LocalVars = [], ArgVarsInProcs = [] then
|
|
!:RevNoDependVars = [Var | !.RevNoDependVars]
|
|
else
|
|
VarStr = mercury_var_to_string(VarTable, print_name_and_num, Var),
|
|
io.format(Stream, "requiring vars of %s:\n", [s(VarStr)], !IO),
|
|
(
|
|
LocalVars = []
|
|
;
|
|
LocalVars = [_ | _],
|
|
LocalVarsStr = mercury_vars_to_string(VarTable,
|
|
print_name_and_num, LocalVars),
|
|
io.format(Stream, "variables: %s\n", [s(LocalVarsStr)], !IO)
|
|
),
|
|
(
|
|
ArgVarsInProcs = []
|
|
;
|
|
ArgVarsInProcs = [_ | _],
|
|
io.write_string(Stream, "procedure arguments:\n", !IO),
|
|
list.foldl(write_arg_var_in_proc(Stream, ModuleInfo),
|
|
ArgVarsInProcs, !IO)
|
|
)
|
|
).
|
|
|
|
:- pred write_arg_var_in_proc(io.text_output_stream::in, module_info::in,
|
|
arg_var_in_proc::in, io::di, io::uo) is det.
|
|
|
|
write_arg_var_in_proc(Stream, ModuleInfo, ArgVarInProc, !IO) :-
|
|
ArgVarInProc = arg_var_in_proc(PredProcId, Var),
|
|
PredProcIdStr = pred_proc_id_to_dev_string(ModuleInfo, PredProcId),
|
|
module_info_proc_info(ModuleInfo, PredProcId, ProcInfo),
|
|
proc_info_get_var_table(ProcInfo, VarTable),
|
|
VarStr = mercury_var_to_string(VarTable, print_name_and_num, Var),
|
|
io.format(Stream, "%s: %s\n", [s(PredProcIdStr), s(VarStr)], !IO).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
:- end_module transform_hlds.unused_args.
|
|
%---------------------------------------------------------------------------%
|