Files
mercury/compiler/post_term_analysis.m
Zoltan Somogyi ee9c7d3a84 Speed up bound vs ground inst checks.
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).
2025-11-19 22:09:04 +11:00

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.
%----------------------------------------------------------------------------%