Files
mercury/compiler/format_call.m
Zoltan Somogyi 168f531867 Add new fields to the goal_info structure for region based memory management.
Estimated hours taken: 4
Branches: main

Add new fields to the goal_info structure for region based memory management.
The fields are currently unused, but (a) Quan will add the code to fill them
in, and then (b) I will modify the code generator to use the filled in fields.

compiler/hlds_goal.m:
	Make the change described above.

	Group all the procedures that access goal_info components together.
	Some of the getters were predicates while some were functions, so
	this diff changes them all to be functions. (The setters remain
	predicates.)

compiler/*.m:
	Trivial changes to conform to the change in hlds_goal.m.

	In simplify.m, break up a huge (800+ line) predicate into smaller
	pieces.
2007-08-07 07:10:09 +00:00

620 lines
25 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2006-2007 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%
%
% File: format_call.m.
% Author: zs.
%
% The job of this module is to generate warnings about calls to
% string.format, io.format and stream.string_writer.format in which the format
% string and the supplied lists of values do not agree. The difficult part of
% this job is actually finding the values of the variables representing the
% format string and the list of values to be printed.
%
% The general approach is a backwards traversal of the procedure body. During
% this traversal, we assign an id to every conjunction (considering a cond and
% then parts of an if-then-else to be a conjunction). When we find a call to
% string.format or io.format, we remember the call site together with the
% identities of the variables holding the format string and the values to
% be printed, and include both variables in the set of variables whose values
% we want to track. As we traverse unifications that bind variables we want to
% track, we record their value in a map specific to the conjunction containing
% the unification. Actually, we keep four such maps. Three record information
% about bindings to function symbols: one for format strings, one for the
% skeletons of the lists of values, and one for the elements of those lists.
% The fourth map is for variable equivalences.
%
% We also record relationships between the conjunctions. Consider the code
% structure below, which contains two relevant conjunctions: the outer one,
% and the one containing the cond and then parts of the inner if-then-else.
% Any attempt to trace the value of V2 requires also knowing the value of V1.
% We therefore record that if you can't find the value of a variable such as V1
% in the inner conjunction, you should continue the search in the outer
% conjunction. We call this relationship "predecessor", since the only relevant
% part of the outer conjunction is the one that appears before the inner one.
% This is enforced by the mode system.
%
% (
% ...,
% V1 = ...,
% ...,
% (
% ...
% ->
% V2 = ... V1 ...,
% string.format(..., V2, ...)
% ;
% V3 = ... V1 ...,
% string.format(..., V3, ...)
% ),
% ...
% )
%
% This design is about as cheap in terms of compilation time as we can make it.
% Its cost has two components. The first component is the traversal, and its
% cost is roughly proportional to the size of the procedure body. The second
% cost is the checking of each call to string.format or io.format. The expected
% complexity of this part is proportional to the number of such calls
% multiplied by the average number of arguments they print. In the worst case,
% this can be multiplied again by the number of conjunctions in the procedure
% body, but I expect that in most cases the variables involved in the relevant
% calls will be found in the same conjunction as the call itself, so the
% typical number of conjunctions that has to be searched will in fact be one.
%
% Note that if the value of e.g. a format string is an input to the procedure
% or is computed by a call rather than a unification, we won't be able to check
% whether the values match the format string. Whether we give a warning in such
% cases is controlled by a separate option, which is consulted in det_report.m.
%
% We could in theory track e.g. format strings through calls to library
% functions such as string.append. However, there is no convenient way to
% evaluate the extent of a need for this capability until this change is
% bootstrapped, so that is left for future work.
%
%-----------------------------------------------------------------------------%
:- module check_hlds.format_call.
:- interface.
:- import_module hlds.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_module.
:- import_module parse_tree.
:- import_module parse_tree.error_util.
:- import_module parse_tree.prog_data.
:- import_module mdbcomp.
:- import_module mdbcomp.prim_data.
:- import_module list.
%-----------------------------------------------------------------------------%
:- pred is_format_call(module_name::in, string::in, list(prog_var)::in,
prog_var::out, prog_var::out) is semidet.
:- pred find_format_call_errors(module_info::in, hlds_goal::in,
list(error_spec)::in, list(error_spec)::out) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module hlds.hlds_pred.
:- import_module libs.
:- import_module libs.compiler_util.
:- import_module libs.options.
:- import_module bool.
:- import_module counter.
:- import_module exception.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module set.
:- import_module string.
:- import_module svmap.
:- import_module svset.
:- import_module univ.
%-----------------------------------------------------------------------------%
:- type format_call_site
---> format_call_site(
format_string_var :: prog_var,
formatted_values_var :: prog_var,
called_pred_module :: module_name,
called_pred_name :: string,
called_pred_arity :: arity,
call_context :: prog_context,
containing_conj :: conj_id
).
:- type list_skeleton_state
---> list_skeleton_nil
; list_skeleton_cons(
head :: prog_var,
tail :: prog_var
).
% Maps each variable representing a format string to the format string
% itself.
:- type string_map == map(prog_var, string).
% Maps each variable participating in the skeleton of the list of values to
% be printed to its value.
:- type list_skeleton_map == map(prog_var, list_skeleton_state).
% Maps each variable representing a value in the list of values to be
% printed to a dummy value of the same kind. We don't include the actual
% value to be printed, since (a) in almost all cases that won't be
% available statically in the program, and (b) we don't actually need it.
:- type list_element_map == map(prog_var, string.poly_type).
% Maps each variable defined in terms of another variable to the variable
% it is assigned from.
:- type eqv_map == map(prog_var, prog_var).
% The knowledge we have recorded from assign and construct unifications in
% a given conjunction.
:- type conj_map
---> conj_map(
string_map :: string_map,
list_skeleton_map :: list_skeleton_map,
list_element_map :: list_element_map,
eqv_map :: eqv_map
).
:- type conj_id
---> conj_id(int).
% Maps the id of each conjunction to the knowledge we have derived from
% unifications in that conjunction.
:- type conj_maps == map(conj_id, conj_map).
% Maps each conjunction to its predecessor (if any) in the sense documented
% above.
:- type conj_pred_map == map(conj_id, conj_id).
%-----------------------------------------------------------------------------%
is_format_call(ModuleName, Name, Args, FormatStringVar, FormattedValuesVar) :-
Name = "format",
(
ModuleName = mercury_std_lib_module_name(unqualified("string"))
->
% We have these arguments regardless of whether we call the
% predicate or function version of string.format.
Args = [FormatStringVar, FormattedValuesVar, _ResultVar]
;
ModuleName = mercury_std_lib_module_name(unqualified("io"))
->
( Args = [FormatStringVar, FormattedValuesVar, _IOIn, _IOOut]
; Args = [_Stream, FormatStringVar, FormattedValuesVar, _IOIn, _IOOut]
)
;
ModuleName = mercury_std_lib_module_name(
qualified(unqualified("stream"), "string_writer"))
->
% Since we do this check after polymorphism there will have been
% a typeclassinfo inserted at the front of the argument list.
Args = [_TC_InfoForStream, _Stream, FormatStringVar,
FormattedValuesVar, _StateIn, _StateOut]
;
fail
).
%-----------------------------------------------------------------------------%
find_format_call_errors(ModuleInfo, Goal, !Msgs) :-
map.init(ConjMaps0),
counter.init(0, Counter0),
traverse_goal(Goal, _, [], FormatCallSites, Counter0, _Counter,
ConjMaps0, ConjMaps, map.init, PredMap, set.init, _, ModuleInfo),
list.foldl(check_format_call_site(ConjMaps, PredMap), FormatCallSites,
!Msgs).
:- pred check_format_call_site(conj_maps::in, conj_pred_map::in,
format_call_site::in, list(error_spec)::in, list(error_spec)::out) is det.
check_format_call_site(ConjMaps, PredMap, FormatCallSite, !Specs) :-
FormatCallSite = format_call_site(StringVar, ValuesVar,
ModuleName, Name, Arity, Context, CurId),
SymName = qualified(ModuleName, Name),
(
follow_format_string(ConjMaps, PredMap, CurId, StringVar,
yes(FormatString0))
->
MaybeFormatString = yes(FormatString0)
;
MaybeFormatString = no,
UnknownFormatPieces = [words("Unknown format string in call to"),
sym_name_and_arity(SymName / Arity), suffix("."), nl],
UnknownFormatSeverity =
severity_conditional(warn_unknown_format_calls, yes,
severity_warning, no),
UnknownFormatMsg = simple_msg(Context,
[option_is_set(warn_unknown_format_calls, yes,
[always(UnknownFormatPieces)])]),
UnknownFormatSpec = error_spec(UnknownFormatSeverity,
phase_detism_check, [UnknownFormatMsg]),
!:Specs = [UnknownFormatSpec | !.Specs]
),
(
follow_list(ConjMaps, PredMap, CurId, ValuesVar, yes(Skeleton)),
list.map(follow_list_value(ConjMaps, PredMap, CurId), Skeleton,
MaybeValueList),
project_all_yes(MaybeValueList, Values0)
->
MaybeValues = yes(Values0)
;
MaybeValues = no,
UnknownFormatValuesPieces =
[words("Unknown format values in call to"),
sym_name_and_arity(SymName / Arity), suffix("."), nl],
UnknownFormatValuesSeverity =
severity_conditional(warn_unknown_format_calls, yes,
severity_warning, no),
UnknownFormatValuesMsg = simple_msg(Context,
[option_is_set(warn_unknown_format_calls, yes,
[always(UnknownFormatValuesPieces)])]),
UnknownFormatValuesSpec = error_spec(UnknownFormatValuesSeverity,
phase_detism_check, [UnknownFormatValuesMsg]),
!:Specs = [UnknownFormatValuesSpec | !.Specs]
),
(
MaybeFormatString = yes(FormatString),
MaybeValues = yes(Values)
->
promise_equivalent_solutions [Result] (
try(string.format(FormatString, Values), Result)
),
(
Result = exception(ExceptionUniv),
( univ_to_type(ExceptionUniv, ExceptionError) ->
ExceptionError = software_error(ExceptionMsg0),
( string.append("string.format: ", Msg, ExceptionMsg0) ->
ExceptionMsg = Msg
;
ExceptionMsg = ExceptionMsg0
),
BadFormatPieces =
[words("Mismatched format and values in call to"),
sym_name_and_arity(SymName / Arity), suffix(":"), nl,
words(ExceptionMsg)],
BadFormatMsg = simple_msg(Context,
[option_is_set(warn_known_bad_format_calls, yes,
[always(BadFormatPieces)])]),
BadFormatSeverity = severity_conditional(
warn_known_bad_format_calls, yes, severity_warning, no),
BadFormatSpec = error_spec(BadFormatSeverity,
phase_simplify(report_in_any_mode), [BadFormatMsg]),
!:Specs = [BadFormatSpec | !.Specs]
;
% We can't decode arbitrary exception values, but string.m
% shouldn't throw anything but software_errors, so ignoring
% the exception should be ok.
true
)
;
% There is no need for any error message; the format works.
Result = succeeded(_)
)
;
% Any error message has already been generated, if asked for.
true
).
:- pred follow_format_string(conj_maps::in, conj_pred_map::in, conj_id::in,
prog_var::in, maybe(string)::out) is det.
follow_format_string(ConjMaps, PredMap, CurId, StringVar, MaybeString) :-
ConjMap = get_conj_map(ConjMaps, CurId),
ConjMap = conj_map(StringMap, _, _, EqvMap),
( map.search(EqvMap, StringVar, EqvVar) ->
follow_format_string(ConjMaps, PredMap, CurId, EqvVar, MaybeString)
; map.search(StringMap, StringVar, String) ->
MaybeString = yes(String)
; map.search(PredMap, CurId, PredId) ->
follow_format_string(ConjMaps, PredMap, PredId, StringVar, MaybeString)
;
MaybeString = no
).
:- pred follow_list(conj_maps::in, conj_pred_map::in, conj_id::in,
prog_var::in, maybe(list(prog_var))::out) is det.
follow_list(ConjMaps, PredMap, CurId, ListVar, MaybeSkeleton) :-
ConjMap = get_conj_map(ConjMaps, CurId),
ConjMap = conj_map(_, ListMap, _, EqvMap),
( map.search(EqvMap, ListVar, EqvVar) ->
follow_list(ConjMaps, PredMap, CurId, EqvVar, MaybeSkeleton)
; map.search(ListMap, ListVar, ListState) ->
(
ListState = list_skeleton_nil,
Skeleton = [],
MaybeSkeleton = yes(Skeleton)
;
ListState = list_skeleton_cons(HeadVar, TailVar),
follow_list(ConjMaps, PredMap, CurId, TailVar, MaybeSkeletonTail),
(
MaybeSkeletonTail = no,
MaybeSkeleton = no
;
MaybeSkeletonTail = yes(SkeletonTail),
Skeleton = [HeadVar | SkeletonTail],
MaybeSkeleton = yes(Skeleton)
)
)
; map.search(PredMap, CurId, PredId) ->
follow_list(ConjMaps, PredMap, PredId, ListVar, MaybeSkeleton)
;
MaybeSkeleton = no
).
:- pred follow_list_value(conj_maps::in, conj_pred_map::in, conj_id::in,
prog_var::in, maybe(string.poly_type)::out) is det.
follow_list_value(ConjMaps, PredMap, CurId, ElementVar, MaybeValue) :-
ConjMap = get_conj_map(ConjMaps, CurId),
ConjMap = conj_map(_, _, ElementMap, EqvMap),
( map.search(EqvMap, ElementVar, EqvVar) ->
follow_list_value(ConjMaps, PredMap, CurId, EqvVar, MaybeValue)
; map.search(ElementMap, ElementVar, Value) ->
MaybeValue = yes(Value)
; map.search(PredMap, CurId, PredId) ->
follow_list_value(ConjMaps, PredMap, PredId, ElementVar, MaybeValue)
;
MaybeValue = no
).
:- pred project_all_yes(list(maybe(T))::in, list(T)::out) is semidet.
project_all_yes([], []).
project_all_yes([yes(Value) | TailMaybes], [Value | Tail]) :-
project_all_yes(TailMaybes, Tail).
%-----------------------------------------------------------------------------%
:- pred traverse_goal(hlds_goal::in, conj_id::out,
list(format_call_site)::in, list(format_call_site)::out,
counter::in, counter::out, conj_maps::in, conj_maps::out,
conj_pred_map::in, conj_pred_map::out,
set(prog_var)::in, set(prog_var)::out, module_info::in) is det.
traverse_goal(Goal, CurId, !FormatCallSites, !Counter, !ConjMaps, !PredMap,
!RelevantVars, ModuleInfo) :-
alloc_id(CurId, !Counter),
goal_to_conj_list(Goal, GoalConj),
traverse_conj(GoalConj, CurId, !FormatCallSites, !Counter,
!ConjMaps, !PredMap, !RelevantVars, ModuleInfo).
:- pred traverse_conj(list(hlds_goal)::in, conj_id::in,
list(format_call_site)::in, list(format_call_site)::out,
counter::in, counter::out, conj_maps::in, conj_maps::out,
conj_pred_map::in, conj_pred_map::out,
set(prog_var)::in, set(prog_var)::out, module_info::in) is det.
traverse_conj([], _CurId, !FormatCallSites, !Counter,
!ConjMaps, !PredMap, !RelevantVars, _ModuleInfo).
traverse_conj([Goal | Goals], CurId, !FormatCallSites, !Counter,
!ConjMaps, !PredMap, !RelevantVars, ModuleInfo) :-
traverse_conj(Goals, CurId, !FormatCallSites, !Counter,
!ConjMaps, !PredMap, !RelevantVars, ModuleInfo),
Goal = hlds_goal(GoalExpr, GoalInfo),
(
GoalExpr = conj(_, Conjuncts),
traverse_conj(Conjuncts, CurId, !FormatCallSites, !Counter,
!ConjMaps, !PredMap, !RelevantVars, ModuleInfo)
;
GoalExpr = disj(Disjuncts),
traverse_disj(Disjuncts, CurId, !FormatCallSites, !Counter,
!ConjMaps, !PredMap, !RelevantVars, ModuleInfo)
;
GoalExpr = switch(_, _, Cases),
Disjuncts = list.map(project_case_goal, Cases),
traverse_disj(Disjuncts, CurId, !FormatCallSites, !Counter,
!ConjMaps, !PredMap, !RelevantVars, ModuleInfo)
;
GoalExpr = if_then_else(_, Cond, Then, Else),
traverse_goal(Else, ElseId, !FormatCallSites, !Counter, !ConjMaps,
!PredMap, !RelevantVars, ModuleInfo),
svmap.det_insert(ElseId, CurId, !PredMap),
alloc_id(CondThenId, !Counter),
goal_to_conj_list(Then, ThenConj),
goal_to_conj_list(Cond, CondConj),
traverse_conj(CondConj ++ ThenConj, CondThenId, !FormatCallSites,
!Counter, !ConjMaps, !PredMap, !RelevantVars, ModuleInfo),
svmap.det_insert(CondThenId, CurId, !PredMap)
;
GoalExpr = negation(SubGoal),
traverse_goal(SubGoal, SubGoalId, !FormatCallSites,
!Counter, !ConjMaps, !PredMap, !RelevantVars, ModuleInfo),
svmap.det_insert(SubGoalId, CurId, !PredMap)
;
GoalExpr = scope(_, SubGoal),
traverse_conj([SubGoal], CurId, !FormatCallSites, !Counter,
!ConjMaps, !PredMap, !RelevantVars, ModuleInfo)
;
GoalExpr = generic_call(_, _, _, _)
;
GoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
;
GoalExpr = plain_call(PredId, _ProcId, Args, _, _, _),
module_info_pred_info(ModuleInfo, PredId, PredInfo),
ModuleName = pred_info_module(PredInfo),
Name = pred_info_name(PredInfo),
( is_format_call(ModuleName, Name, Args, StringVar, ValuesVar) ->
Arity = pred_info_orig_arity(PredInfo),
Context = goal_info_get_context(GoalInfo),
FormatCallSite = format_call_site(StringVar, ValuesVar,
ModuleName, Name, Arity, Context, CurId),
!:FormatCallSites = [FormatCallSite | !.FormatCallSites],
svset.insert_list([StringVar, ValuesVar], !RelevantVars)
;
true
)
;
GoalExpr = unify(_, _, _, Unification, _),
traverse_unify(Unification, CurId, !ConjMaps, !PredMap, !RelevantVars)
;
GoalExpr = shorthand(_),
% These should have been expanded by now.
unexpected(this_file, "traverse_conj: shorthand")
).
:- pred traverse_unify(unification::in, conj_id::in,
conj_maps::in, conj_maps::out, conj_pred_map::in, conj_pred_map::out,
set(prog_var)::in, set(prog_var)::out) is det.
traverse_unify(Unification, CurId, !ConjMaps, !PredMap, !RelevantVars) :-
(
Unification = assign(TargetVar, SourceVar),
( set.member(TargetVar, !.RelevantVars) ->
svset.delete(TargetVar, !RelevantVars),
svset.insert(SourceVar, !RelevantVars),
ConjMap0 = get_conj_map(!.ConjMaps, CurId),
ConjMap0 = conj_map(StringMap, ListMap, ElementMap, EqvMap0),
map.set(EqvMap0, TargetVar, SourceVar, EqvMap),
ConjMap = conj_map(StringMap, ListMap, ElementMap, EqvMap),
svmap.set(CurId, ConjMap, !ConjMaps)
;
true
)
;
Unification = construct(CellVar, ConsId, ArgVars, _, _, _, _),
( set.member(CellVar, !.RelevantVars) ->
ConjMap0 = get_conj_map(!.ConjMaps, CurId),
ConjMap0 = conj_map(StringMap0, ListMap0, ElementMap0, EqvMap0),
(
ConsId = string_const(StringConst)
->
svset.delete(CellVar, !RelevantVars),
map.set(StringMap0, CellVar, StringConst, StringMap),
ConjMap = conj_map(StringMap, ListMap0, ElementMap0, EqvMap0)
;
ConsId = cons(SymName, _Arity),
StringModule =
mercury_std_lib_module_name(unqualified("list")),
SymName = qualified(StringModule, Functor),
(
Functor = "[|]",
ArgVars = [ArgVar1, ArgVar2],
List = list_skeleton_cons(ArgVar1, ArgVar2)
;
Functor = "[]",
ArgVars = [],
List = list_skeleton_nil
)
->
svset.delete(CellVar, !RelevantVars),
svset.insert_list(ArgVars, !RelevantVars),
map.set(ListMap0, CellVar, List, ListMap),
ConjMap = conj_map(StringMap0, ListMap, ElementMap0, EqvMap0)
;
ConsId = cons(SymName, Arity),
Arity = 1,
StringModule =
mercury_std_lib_module_name(unqualified("string")),
SymName = qualified(StringModule, Functor),
(
Functor = "f",
PolyType = f(0.0)
;
Functor = "i",
PolyType = i(0)
;
Functor = "s",
PolyType = s("0")
;
Functor = "c",
PolyType = c('0')
)
->
svset.delete(CellVar, !RelevantVars),
map.set(ElementMap0, CellVar, PolyType, ElementMap),
ConjMap = conj_map(StringMap0, ListMap0, ElementMap, EqvMap0)
;
ConjMap = ConjMap0
),
svmap.set(CurId, ConjMap, !ConjMaps)
;
true
)
;
Unification = deconstruct(_, _, _, _, _, _)
;
Unification = simple_test(_, _)
;
Unification = complicated_unify(_, _, _)
).
:- func project_case_goal(case) = hlds_goal.
project_case_goal(case(_, Goal)) = Goal.
:- pred traverse_disj(list(hlds_goal)::in, conj_id::in,
list(format_call_site)::in, list(format_call_site)::out,
counter::in, counter::out, conj_maps::in, conj_maps::out,
conj_pred_map::in, conj_pred_map::out,
set(prog_var)::in, set(prog_var)::out, module_info::in) is det.
traverse_disj(Disjuncts, CurId, !FormatCallSites, !Counter,
!ConjMaps, !PredMap, !RelevantVars, ModuleInfo) :-
traverse_disj_arms(Disjuncts, CurId, DisjFormatCallSitesLists,
!Counter, !ConjMaps, !PredMap, DisjRelevantVarSets, ModuleInfo),
list.condense(DisjFormatCallSitesLists, DisjFormatCallSites),
!:FormatCallSites = !.FormatCallSites ++ DisjFormatCallSites,
DisjRelevantVars = set.union_list(DisjRelevantVarSets),
set.union(DisjRelevantVars, !RelevantVars).
:- pred traverse_disj_arms(list(hlds_goal)::in, conj_id::in,
list(list(format_call_site))::out,
counter::in, counter::out, conj_maps::in, conj_maps::out,
conj_pred_map::in, conj_pred_map::out, list(set(prog_var))::out,
module_info::in) is det.
traverse_disj_arms([], _, [], !Counter, !ConjMaps, !PredMap, [], _).
traverse_disj_arms([Goal | Goals], ContainingId,
[FormatCallSites | FormatCallSitesTail], !Counter,
!ConjMaps, !PredMap, [RelevantVars | RelevantVarSets], ModuleInfo) :-
traverse_goal(Goal, DisjId, [], FormatCallSites, !Counter,
!ConjMaps, !PredMap, set.init, RelevantVars, ModuleInfo),
svmap.det_insert(DisjId, ContainingId, !PredMap),
traverse_disj_arms(Goals, ContainingId, FormatCallSitesTail, !Counter,
!ConjMaps, !PredMap, RelevantVarSets, ModuleInfo).
:- func get_conj_map(conj_maps, conj_id) = conj_map.
get_conj_map(ConjMaps, ConjId) = ConjMap :-
( map.search(ConjMaps, ConjId, ConjMapPrime) ->
ConjMap = ConjMapPrime
;
ConjMap = conj_map(map.init, map.init, map.init, map.init)
).
:- pred alloc_id(conj_id::out, counter::in, counter::out) is det.
alloc_id(ConjId, !Counter) :-
counter.allocate(N, !Counter),
ConjId = conj_id(N).
%-----------------------------------------------------------------------------%
:- func this_file = string.
this_file = "format_call.m".
%-----------------------------------------------------------------------------%