mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 01:13:30 +00:00
The code that checks whether a bound inst wrapped around
a list of bound_functors matched the ground inst did several things
in a suboptimal fashion.
- It looked up the definition of the type constructor of the relevant type
(the type of the variable the inst is for) more than once. (This was
not easily visible because the lookups were in different predicates.)
This diff factors these out, not for the immesurably small speedup,
but to make possible the fixes for the next two issues.
- To simplify the "is there a bound_functor for each constructor in the type"
check, it sorted the constructors of the type by name and arity. (Lists of
bound_functors are always sorted by name and arity.) Given that most
modules contain more than one bound inst for any given type constructor,
any sorting after the first was unnecessarily repeated work. This diff
therefore extends the representation of du types, which until now has
include only a list of the data constructors in the type definition
in definition order, with a list of those exact same data constructors
in name/arity order.
- Even if a list of bound_functors lists all the constructors of a type,
the bound inst containing them is not equivalent to ground if the inst
of some argument of some bound_inst is not equivalent to ground.
This means that we need to know the actual argument of each constructor.
The du type definition lists argument types that refer to the type
constructor's type parameters; we need the instances of these argument types
that apply to type of the variable at hand, which usually binds concrete
types to those type parameters.
We used to apply the type-parameter-to-actual-type substitution to
each argument of each data constructor in the type before we compared
the resulting filled-in data constructor descriptions against the list of
bound_functors. However, in cases where the comparison fails, the
substitution applications to arguments beyond the point of failure
are all wasted work. This diff therefore applies the substitution
only when its result is about to be needed.
This diff leads to a speedup of about 3.5% on tools/speedtest,
and about 38% (yes, more than a third) when compiling options.m.
compiler/hlds_data.m:
Add the new field to the representation of du types.
Add a utility predicate that helps construct that field, since it is
now needed by two modules (add_type.m and equiv_type_hlds.m).
Delete two functions that were used only by det_check_switch.m,
which this diff moves to that module (in modified form).
compiler/inst_match.m:
Implement the first and third changes listed above, and take advantage
of the second.
The old call to all_du_ctor_arg_types, which this diff replaces,
effectively lied about the list of constructors it returned,
by simply not returning any constructors containing existentially
quantified types, on the grounds that they "were not handled yet".
We now fail explicitly when we find any such constructors.
Perform the check for one-to-one match between bound_functors and
constructors with less argument passing.
compiler/det_check_switch.m:
Move the code deleted from hlds_data.m here, and simplify it,
taking advantage of the new field in du types.
compiler/Mercury.options:
Specify --optimize-constructor-last-call for det_check_switch.m
to optimize the updated moved code.
compiler/add_foreign_enum.m:
compiler/add_special_pred.m:
compiler/add_type.m:
compiler/check_typeclass.m:
compiler/code_info.m:
compiler/dead_proc_elim.m:
compiler/direct_arg_in_out.m:
compiler/du_type_layout.m:
compiler/equiv_type_hlds.m:
compiler/hlds_out_type_table.m:
compiler/inst_check.m:
compiler/intermod.m:
compiler/intermod_decide.m:
compiler/lookup_switch_util.m:
compiler/ml_type_gen.m:
compiler/ml_unify_gen_test.m:
compiler/ml_unify_gen_util.m:
compiler/mlds.m:
compiler/post_term_analysis.m:
compiler/recompilation.usage.m:
compiler/resolve_unify_functor.m:
compiler/simplify_goal_ite.m:
compiler/table_gen.m:
compiler/tag_switch_util.m:
compiler/term_norm.m:
compiler/type_ctor_info.m:
compiler/type_util.m:
compiler/typecheck_coerce.m:
compiler/unify_proc.m:
compiler/unused_imports.m:
compiler/xml_documentation.m:
Conform to the changes above. This mostly means handling
the new field in du types (usually by ignoring it).
309 lines
12 KiB
Mathematica
309 lines
12 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%----------------------------------------------------------------------------%
|
|
% Copyright (C) 2005-2007, 2009-2012 The University of Melbourne.
|
|
% Copyright (C) 2014-2018, 2020-2025 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: post_term_analysis.m.
|
|
% Main author: juliensf.
|
|
%
|
|
% This module contains various checks that rely on the information produced by
|
|
% termination analysis.
|
|
%
|
|
% Currently, the only thing implemented in this module is a check to see if
|
|
% user-defined special predicates terminate. We emit a warning for all those
|
|
% that do not.
|
|
%
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- module transform_hlds.post_term_analysis.
|
|
:- interface.
|
|
|
|
:- import_module hlds.
|
|
:- import_module hlds.hlds_module.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.error_spec.
|
|
|
|
:- import_module list.
|
|
|
|
:- pred run_post_term_analysis(module_info::in, list(error_spec)::out) is det.
|
|
|
|
%----------------------------------------------------------------------------%
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module backend_libs.
|
|
:- import_module backend_libs.foreign.
|
|
:- import_module hlds.goal_form.
|
|
:- import_module hlds.goal_transform.
|
|
:- import_module hlds.hlds_data.
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module hlds.special_pred.
|
|
:- import_module hlds.status.
|
|
:- import_module hlds.type_util.
|
|
:- import_module libs.
|
|
:- import_module libs.globals.
|
|
:- import_module libs.op_mode.
|
|
:- import_module libs.options.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.prim_data.
|
|
:- import_module mdbcomp.sym_name.
|
|
:- import_module parse_tree.prog_data.
|
|
:- import_module parse_tree.prog_type.
|
|
|
|
:- import_module bool.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module require.
|
|
:- import_module string.
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
run_post_term_analysis(ModuleInfo, Specs) :-
|
|
warn_non_term_user_special_preds(ModuleInfo, Specs).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
%
|
|
% Warn about user-defined special predicates that do not terminate.
|
|
%
|
|
|
|
% We check the termination status of user-defined special predicates
|
|
% by taking the body goal of the compiler generated wrapper predicate
|
|
% and checking if that terminates. We cannot check the termination status
|
|
% of the compiler generated wrappers directly, because termination analysis
|
|
% always assumes that they terminate.
|
|
%
|
|
% Since all of the special predicates of interest here have to be defined
|
|
% in the same module as the type that uses them, we only check locally
|
|
% defined types. The ones for imported types will be checked when
|
|
% the relevant module is compiled and analysed.
|
|
%
|
|
:- pred warn_non_term_user_special_preds(module_info::in,
|
|
list(error_spec)::out) is det.
|
|
|
|
warn_non_term_user_special_preds(ModuleInfo, !:Specs) :-
|
|
module_info_get_globals(ModuleInfo, Globals),
|
|
globals.get_op_mode(Globals, OpMode),
|
|
globals.lookup_bool_option(Globals, termination_enable, Termination),
|
|
globals.lookup_bool_option(Globals, warn_non_term_special_preds,
|
|
WarnSpecialPreds),
|
|
globals.lookup_bool_option(Globals, transitive_optimization,
|
|
TransIntermodOpt),
|
|
!:Specs = [],
|
|
( if
|
|
Termination = yes,
|
|
WarnSpecialPreds = yes,
|
|
|
|
% Don't run this pass if we are only building the optimization
|
|
% interface and we are compiling with
|
|
% `--transitive-intermodule-optimization' enabled, because we will get
|
|
% more accurate results when we build the .trans_opt files.
|
|
% Any warnings this time around may be spurious.
|
|
not (
|
|
OpMode = opm_top_args(opma_augment(opmau_make_plain_opt), _),
|
|
TransIntermodOpt = yes
|
|
)
|
|
then
|
|
module_info_get_type_table(ModuleInfo, TypeTable),
|
|
module_info_get_special_pred_maps(ModuleInfo, SpecialPredMaps),
|
|
% Index predicates cannot be defined by the user and should always
|
|
% terminate by design.
|
|
SpecialPredMaps = special_pred_maps(UnifyMap, _IndexMap, CompareMap),
|
|
map.foldl(
|
|
warn_non_term_user_special_pred_kind(ModuleInfo, TypeTable,
|
|
spec_pred_unify),
|
|
UnifyMap, !Specs),
|
|
map.foldl(
|
|
warn_non_term_user_special_pred_kind(ModuleInfo, TypeTable,
|
|
spec_pred_compare),
|
|
CompareMap, !Specs)
|
|
else
|
|
true
|
|
).
|
|
|
|
:- pred warn_non_term_user_special_pred_kind(module_info::in, type_table::in,
|
|
special_pred_id::in, type_ctor::in, pred_id::in,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
warn_non_term_user_special_pred_kind(ModuleInfo, TypeTable, SpecialPredId,
|
|
TypeCtor, PredId, !Specs) :-
|
|
% Do not perform this check for builtin types that don't have
|
|
% hlds_type_defns.
|
|
BuiltinTypeCtors = builtin_type_ctors_with_no_hlds_type_defn,
|
|
( if list.member(TypeCtor, BuiltinTypeCtors) then
|
|
true
|
|
else
|
|
lookup_type_ctor_defn(TypeTable, TypeCtor, TypeDefn),
|
|
get_type_defn_status(TypeDefn, TypeStatus),
|
|
DefinedThisModule = type_status_defined_in_this_module(TypeStatus),
|
|
(
|
|
DefinedThisModule = yes,
|
|
process_special_pred_for_type(ModuleInfo, SpecialPredId,
|
|
TypeCtor, TypeDefn, PredId, !Specs)
|
|
;
|
|
DefinedThisModule = no
|
|
)
|
|
).
|
|
|
|
% If the specified special predicate for the given type is user-defined
|
|
% then check that it terminates. Emit a warning if it does not.
|
|
%
|
|
:- pred process_special_pred_for_type(module_info::in,
|
|
special_pred_id::in, type_ctor::in, hlds_type_defn::in, pred_id::in,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
process_special_pred_for_type(ModuleInfo, SpecialPredId, TypeCtor, TypeDefn,
|
|
PredId, !Specs) :-
|
|
NeedsTermCheck =
|
|
special_pred_needs_term_check(ModuleInfo, SpecialPredId, TypeDefn),
|
|
(
|
|
NeedsTermCheck = no
|
|
;
|
|
NeedsTermCheck = yes,
|
|
|
|
% Compiler generated special preds are always mode 0.
|
|
proc_id_to_int(ProcId, 0),
|
|
module_info_pred_proc_info(ModuleInfo, PredId, ProcId, _, ProcInfo),
|
|
proc_info_get_goal(ProcInfo, BodyGoal0),
|
|
% The pretest code we add for compiler-generated unification
|
|
% and comparison predicates uses type casts. It uses them in a way
|
|
% that is guaranteed to terminate, but our analysis is not (yet) able
|
|
% to find this out for itself. We therefore analyse only the
|
|
% non-pretest parts of such goals.
|
|
BodyGoal = maybe_strip_equality_pretest(BodyGoal0),
|
|
|
|
% We cannot just look up the termination_info because the
|
|
% termination status of compiler generated wrapper predicates for
|
|
% special preds is always set to terminates. Instead, we check if the
|
|
% body of the generated wrapper predicate terminates.
|
|
|
|
( if goal_cannot_loop_term_info(ModuleInfo, BodyGoal) then
|
|
true
|
|
else
|
|
get_type_defn_context(TypeDefn, Context),
|
|
generate_non_term_user_special_warning(Context, SpecialPredId,
|
|
TypeCtor, !Specs)
|
|
)
|
|
).
|
|
|
|
% Return `yes' iff the specified kind of special_pred for this type
|
|
% needs to have its termination status checked.
|
|
%
|
|
:- func special_pred_needs_term_check(module_info, special_pred_id,
|
|
hlds_type_defn) = bool.
|
|
|
|
special_pred_needs_term_check(ModuleInfo, SpecialPredId, TypeDefn)
|
|
= NeedsTermCheck :-
|
|
get_type_defn_body(TypeDefn, TypeBody),
|
|
get_user_unify_compare(ModuleInfo, TypeBody, MaybeCanonical),
|
|
(
|
|
MaybeCanonical = noncanon(NonCanonical),
|
|
(
|
|
( NonCanonical = noncanon_uni_cmp(_, _)
|
|
; NonCanonical = noncanon_uni_only(_)
|
|
; NonCanonical = noncanon_cmp_only(_)
|
|
),
|
|
(
|
|
SpecialPredId = spec_pred_unify,
|
|
(
|
|
( NonCanonical = noncanon_uni_cmp(_, _)
|
|
; NonCanonical = noncanon_uni_only(_)
|
|
),
|
|
NeedsTermCheck = yes
|
|
;
|
|
NonCanonical = noncanon_cmp_only(_),
|
|
NeedsTermCheck = no
|
|
)
|
|
;
|
|
SpecialPredId = spec_pred_compare,
|
|
(
|
|
( NonCanonical = noncanon_uni_cmp(_, _)
|
|
; NonCanonical = noncanon_cmp_only(_)
|
|
),
|
|
NeedsTermCheck = yes
|
|
;
|
|
NonCanonical = noncanon_uni_only(_),
|
|
NeedsTermCheck = no
|
|
)
|
|
;
|
|
SpecialPredId = spec_pred_index,
|
|
% There is no way for users to specify their own index
|
|
% predicates. All index predicates are compiler generated,
|
|
% and all of them are designed to terminate.
|
|
NeedsTermCheck = no
|
|
)
|
|
;
|
|
NonCanonical = noncanon_abstract(_),
|
|
unexpected($pred, "type is local yet it is noncanon_abstract")
|
|
;
|
|
NonCanonical = noncanon_subtype,
|
|
NeedsTermCheck = no
|
|
)
|
|
;
|
|
MaybeCanonical = canon,
|
|
NeedsTermCheck = no
|
|
).
|
|
|
|
% Succeeds if the given type has user-defined equality and/or comparison
|
|
% and returns the relevant information about which predicates implement it.
|
|
%
|
|
:- pred get_user_unify_compare(module_info::in, hlds_type_body::in,
|
|
maybe_canonical::out) is det.
|
|
|
|
get_user_unify_compare(ModuleInfo, TypeBody, MaybeCanonical) :-
|
|
(
|
|
TypeBody = hlds_du_type(type_body_du(_, _, _, MaybeCanonical, _, _))
|
|
;
|
|
TypeBody = hlds_solver_type(DetailsSolver),
|
|
DetailsSolver = type_details_solver(_, MaybeCanonical)
|
|
;
|
|
TypeBody = hlds_foreign_type(ForeignTypeBody),
|
|
( if
|
|
foreign_type_body_has_user_defined_eq_comp_pred(ModuleInfo,
|
|
ForeignTypeBody, NonCanonical)
|
|
then
|
|
MaybeCanonical = noncanon(NonCanonical)
|
|
else
|
|
MaybeCanonical = canon
|
|
)
|
|
;
|
|
( TypeBody = hlds_abstract_type(_)
|
|
; TypeBody = hlds_eqv_type(_)
|
|
),
|
|
MaybeCanonical = canon
|
|
).
|
|
|
|
:- pred generate_non_term_user_special_warning(prog_context::in,
|
|
special_pred_id::in, type_ctor::in,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
generate_non_term_user_special_warning(Context, SpecialPred, TypeCtor,
|
|
!Specs) :-
|
|
type_ctor_module_name_arity(TypeCtor, TypeModule, TypeName, TypeArity),
|
|
(
|
|
SpecialPred = spec_pred_unify,
|
|
SpecialPredStr = "equality"
|
|
;
|
|
SpecialPred = spec_pred_index,
|
|
unexpected($pred, "index predicate.")
|
|
;
|
|
SpecialPred = spec_pred_compare,
|
|
SpecialPredStr = "comparison"
|
|
),
|
|
SNA = sym_name_arity(qualified(TypeModule, TypeName), TypeArity),
|
|
Pieces = [words("Warning: the user-defined"),
|
|
fixed(SpecialPredStr ++ " predicate"), words("for the type")] ++
|
|
color_as_subject([qual_sym_name_arity(SNA)]) ++
|
|
color_as_incorrect([words("cannot be proven to terminate.")]) ++ [nl],
|
|
Severity = severity_warning(warn_requested_by_option),
|
|
Spec = spec($pred, Severity, phase_termination_analysis, Context, Pieces),
|
|
!:Specs = [Spec | !.Specs].
|
|
|
|
%----------------------------------------------------------------------------%
|
|
:- end_module transform_hlds.post_term_analysis.
|
|
%----------------------------------------------------------------------------%
|