Files
mercury/compiler/mode_comparison.m
Zoltan Somogyi 0ab6993894 Convert mode analysis to use var_tables.
compiler/instmap.m:
compiler/mode_comparison.m:
compiler/mode_debug.m:
compiler/mode_info.m:
compiler/modecheck_call.m:
compiler/modecheck_coerce.m:
compiler/modecheck_goal.m:
compiler/modecheck_unify.m:
compiler/modecheck_util.m:
compiler/modes.m:
compiler/unique_modes.m:
    Convert these modules to use var_tables.

compiler/mode_errors.m:
    Convert this module to use var_tables.

    Fix an ancient error that I think has escaped detection until now
    because it arises only in the presence of a mode error in a procedure
    whose mode is being inferred. The bug is that when we modecheck a call,
    say from p to q, and find no matching modes in the callee because
    its mode inference has generated errors, then we report those errors
    in the callee as part of the explanation of the error in the caller.
    That is fine. What was not fine is that we printed any variables
    in the callee's mode_error using the *caller's* varset. We now
    print them using the callee's var table.

compiler/type_util.m:
    Add a var_table-using variant of an existing predicate,
    for use in new code above.

compiler/pd_util.m:
    Conform to the changes above.

tests/invalid/mode_inf.m:
    Modify this test case to make the caller and callee use disjoint
    sets of variable names, which is probably why the incorrect variables
    in the error message about the callee has not been noticed.

tests/invalid/mode_inf.err_exp:
    Expect the updated, and now correct, version of that error message.
2022-05-02 11:36:42 +10:00

470 lines
18 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2017 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: mode_comparison.m.
%
% This file contains code compare different modes of a predicate.
%
%---------------------------------------------------------------------------%
:- module check_hlds.mode_comparison.
:- interface.
:- import_module check_hlds.mode_info.
:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.
:- import_module list.
%---------------------------------------------------------------------------%
% Given two modes of a predicate, figure out whether they are
% indistinguishable; that is, whether any valid call to one mode
% would also be a valid call to the other. (If so, it is a mode error.)
% Note that mode declarations which only have different final insts
% do not count as distinguishable.
%
:- pred modes_are_indistinguishable(module_info::in, pred_info::in,
proc_id::in, proc_id::in) is semidet.
% Given two modes of a predicate, figure out whether they are identical,
% except that one is cc_nondet/cc_multi and the other is nondet/multi.
% This is used by determinism analysis to substitute a multi mode
% for a cc_multi one if the call occurs in a non-cc context.
%
:- pred modes_are_identical_bar_cc(module_info::in, pred_info::in,
proc_id::in, proc_id::in) is semidet.
%---------------------------------------------------------------------------%
:- type proc_mode
---> proc_mode(proc_id, inst_var_sub, list(mer_mode)).
:- type match
---> better
; worse
; same
; incomparable.
% The algorithm for choose_best_match is supposed to be equivalent
% to the following specification:
%
% 1. Remove any modes that are strictly less instantiated or
% less informative on input than other valid modes. For example,
%
% - we prefer an (in, in, out) mode over an (out, in, out) mode,
% but not necessarily over an (out, out, in) mode;
% - we prefer a (ground -> ...) mode over a (any -> ...) mode;
% - we prefer a (bound(f) -> ...) mode over a (ground -> ...) mode;
% - we prefer a (... -> dead) mode over a (... -> not dead) mode.
%
% Also, we prefer a (any -> ...) mode over a (free -> ...) mode,
% unless the actual argument is free, in which case we prefer
% the (free -> ...) mode.
%
% 2. If neither is prefered over the other by step 1, then prioritize
% them by determinism, according to the standard partial order
% (best first):
%
% erroneous
% / \
% det failure
% / \ /
% multi semidet
% \ /
% nondet
%
% 3. If there are still multiple possibilities, take them in
% declaration order.
%
:- pred choose_best_match(mode_info::in, list(proc_mode)::in, pred_id::in,
proc_table::in, list(prog_var)::in, proc_id::out, inst_var_sub::out,
list(mer_mode)::out) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.inst_match.
:- import_module check_hlds.mode_util.
:- import_module check_hlds.modecheck_util.
:- import_module parse_tree.prog_detism.
:- import_module parse_tree.var_table.
:- import_module bool.
:- import_module map.
:- import_module maybe.
:- import_module require.
:- import_module term.
%---------------------------------------------------------------------------%
modes_are_indistinguishable(ModuleInfo, PredInfo, ProcId, OtherProcId) :-
% The code of this predicate is similar to the code for
% modes_are_identical/4 and compare_proc/5 below.
pred_info_get_proc_table(PredInfo, Procs),
map.lookup(Procs, ProcId, ProcInfo),
map.lookup(Procs, OtherProcId, OtherProcInfo),
% Compare the initial insts of the arguments.
proc_info_get_argmodes(ProcInfo, ProcArgModes),
proc_info_get_argmodes(OtherProcInfo, OtherProcArgModes),
mode_list_get_initial_insts(ModuleInfo, ProcArgModes, InitialInsts),
mode_list_get_initial_insts(ModuleInfo, OtherProcArgModes,
OtherInitialInsts),
pred_info_get_arg_types(PredInfo, ArgTypes),
compare_inst_list(ModuleInfo, InitialInsts, OtherInitialInsts, no,
ArgTypes, CompareInsts),
CompareInsts = same,
% Compare the expected livenesses of the arguments.
get_arg_lives(ModuleInfo, ProcArgModes, ProcArgLives),
get_arg_lives(ModuleInfo, OtherProcArgModes, OtherProcArgLives),
compare_liveness_list(ProcArgLives, OtherProcArgLives, CompareLives),
CompareLives = same,
% Compare the determinisms; if both are cc_, or if both are not cc_,
% then they are indistinguishable.
proc_info_interface_determinism(ProcInfo, Detism),
proc_info_interface_determinism(OtherProcInfo, OtherDetism),
determinism_components(Detism, _CanFail, Solns),
determinism_components(OtherDetism, _OtherCanFail, OtherSolns),
( Solns = at_most_many_cc, OtherSolns = at_most_many_cc
; Solns \= at_most_many_cc, OtherSolns \= at_most_many_cc
).
%---------------------------------------------------------------------------%
modes_are_identical_bar_cc(ModuleInfo, PredInfo, ProcId, OtherProcId) :-
% The code of this predicate is similar to the code for
% compare_proc/5 below and modes_are_indistinguishable/4 above.
pred_info_get_proc_table(PredInfo, Procs),
map.lookup(Procs, ProcId, ProcInfo),
map.lookup(Procs, OtherProcId, OtherProcInfo),
% Compare the initial insts of the arguments
proc_info_get_argmodes(ProcInfo, ProcArgModes),
proc_info_get_argmodes(OtherProcInfo, OtherProcArgModes),
mode_list_get_initial_insts(ModuleInfo, ProcArgModes, InitialInsts),
mode_list_get_initial_insts(ModuleInfo, OtherProcArgModes,
OtherInitialInsts),
pred_info_get_arg_types(PredInfo, ArgTypes),
compare_inst_list(ModuleInfo, InitialInsts, OtherInitialInsts, no,
ArgTypes, CompareInitialInsts),
CompareInitialInsts = same,
% Compare the final insts of the arguments
mode_list_get_final_insts(ModuleInfo, ProcArgModes, FinalInsts),
mode_list_get_final_insts(ModuleInfo, OtherProcArgModes,
OtherFinalInsts),
compare_inst_list(ModuleInfo, FinalInsts, OtherFinalInsts, no,
ArgTypes, CompareFinalInsts),
CompareFinalInsts = same,
% Compare the expected livenesses of the arguments
get_arg_lives(ModuleInfo, ProcArgModes, ProcArgLives),
get_arg_lives(ModuleInfo, OtherProcArgModes, OtherProcArgLives),
compare_liveness_list(ProcArgLives, OtherProcArgLives, CompareLives),
CompareLives = same,
% Compare the determinisms, ignoring the cc part.
proc_info_interface_determinism(ProcInfo, Detism),
proc_info_interface_determinism(OtherProcInfo, OtherDetism),
determinism_components(Detism, CanFail, Solns),
determinism_components(OtherDetism, OtherCanFail, OtherSolns),
CanFail = OtherCanFail,
( Solns = OtherSolns
; Solns = at_most_many_cc, OtherSolns = at_most_many
; Solns = at_most_many, OtherSolns = at_most_many_cc
).
%---------------------------------------------------------------------------%
choose_best_match(_, [], _, _, _, _, _, _) :-
unexpected($pred, "no best match").
choose_best_match(ModeInfo, [ProcMode | ProcModes], PredId,
Procs, ArgVars, TheProcId, TheInstVarSub, TheArgModes) :-
ProcMode = proc_mode(ProcId, InstVarSub, ArgModes),
% ProcMode is best iff there is no other proc_mode which is better.
( if
some [OtherProcId] (
list.member(proc_mode(OtherProcId, _, _), ProcModes),
compare_proc(ModeInfo, OtherProcId, ProcId, ArgVars, Procs, better)
)
then
choose_best_match(ModeInfo, ProcModes, PredId, Procs, ArgVars,
TheProcId, TheInstVarSub, TheArgModes)
else
TheProcId = ProcId,
TheInstVarSub = InstVarSub,
TheArgModes = ArgModes
).
% Given two modes of a predicate, figure out whether one of them is a
% better match than the other, for calls which could match either mode.
%
:- pred compare_proc(mode_info::in, proc_id::in, proc_id::in,
list(prog_var)::in, proc_table::in, match::out) is det.
compare_proc(ModeInfo, ProcId, OtherProcId, ArgVars, Procs, Compare) :-
% The code of this predicate is similar to the code for
% modes_are_indistinguishable/4 and modes_are_identical_bar_cc/4 above.
map.lookup(Procs, ProcId, ProcInfo),
map.lookup(Procs, OtherProcId, OtherProcInfo),
% Compare the initial insts of the arguments.
proc_info_get_argmodes(ProcInfo, ProcArgModes),
proc_info_get_argmodes(OtherProcInfo, OtherProcArgModes),
mode_info_get_module_info(ModeInfo, ModuleInfo),
mode_info_get_var_table(ModeInfo, VarTable),
lookup_var_types(VarTable, ArgVars, ArgTypes),
mode_list_get_initial_insts(ModuleInfo, ProcArgModes, InitialInsts),
mode_list_get_initial_insts(ModuleInfo, OtherProcArgModes,
OtherInitialInsts),
get_var_insts(ModeInfo, ArgVars, ArgInitialInsts),
compare_inst_list(ModuleInfo, InitialInsts, OtherInitialInsts,
yes(ArgInitialInsts), ArgTypes, CompareInsts),
% Compare the expected livenesses of the arguments.
get_arg_lives(ModuleInfo, ProcArgModes, ProcArgLives),
get_arg_lives(ModuleInfo, OtherProcArgModes, OtherProcArgLives),
compare_liveness_list(ProcArgLives, OtherProcArgLives, CompareLives),
% Compare the determinisms.
proc_info_interface_determinism(ProcInfo, Detism),
proc_info_interface_determinism(OtherProcInfo, OtherDetism),
determinism_components(Detism, CanFail, SolnCount),
determinism_components(OtherDetism, OtherCanFail, OtherSolnCount),
compare_solncounts(SolnCount, OtherSolnCount, CompareSolnCounts),
(
CompareSolnCounts = first_tighter_than,
CompareDet = better
;
CompareSolnCounts = first_same_as,
compare_canfails(CanFail, OtherCanFail, CompareCanFails),
(
CompareCanFails = first_tighter_than,
CompareDet = better
;
CompareCanFails = first_same_as,
CompareDet = same
;
CompareCanFails = first_looser_than,
CompareDet = worse
)
;
CompareSolnCounts = first_looser_than,
CompareDet = worse
),
% Combine the results, with the insts & lives comparisons
% taking priority over the determinism comparison.
combine_results(CompareInsts, CompareLives, Compare0),
prioritized_combine_results(Compare0, CompareDet, Compare).
:- pred get_var_insts(mode_info::in, list(prog_var)::in,
list(mer_inst)::out) is det.
get_var_insts(_, [], []).
get_var_insts(ModeInfo, [Var | Vars], [Inst | Insts]) :-
get_var_inst(ModeInfo, Var, Inst),
get_var_insts(ModeInfo, Vars, Insts).
:- pred compare_inst_list(module_info::in,
list(mer_inst)::in, list(mer_inst)::in,
maybe(list(mer_inst))::in, list(mer_type)::in, match::out) is det.
compare_inst_list(ModuleInfo, InstsA, InstsB, ArgInsts, Types, Result) :-
( if
compare_inst_list_2(ModuleInfo, InstsA, InstsB, ArgInsts,
Types, Result0)
then
Result = Result0
else
unexpected($pred, "length mismatch")
).
:- pred compare_inst_list_2(module_info::in,
list(mer_inst)::in, list(mer_inst)::in,
maybe(list(mer_inst))::in, list(mer_type)::in, match::out) is semidet.
compare_inst_list_2(_, [], [], _, [], same).
compare_inst_list_2(ModuleInfo, [InstA | InstsA], [InstB | InstsB],
no, [Type | Types], Result) :-
compare_inst(ModuleInfo, InstA, InstB, no, Type, Result0),
compare_inst_list_2(ModuleInfo, InstsA, InstsB, no, Types, Result1),
combine_results(Result0, Result1, Result).
compare_inst_list_2(ModuleInfo, [InstA | InstsA], [InstB | InstsB],
yes([ArgInst | ArgInsts]), [Type | Types], Result) :-
compare_inst(ModuleInfo, InstA, InstB, yes(ArgInst), Type, Result0),
compare_inst_list_2(ModuleInfo, InstsA, InstsB, yes(ArgInsts), Types,
Result1),
combine_results(Result0, Result1, Result).
:- pred compare_liveness_list(list(is_live)::in, list(is_live)::in, match::out)
is det.
compare_liveness_list([], [], same).
compare_liveness_list([_ | _], [], _) :-
unexpected($pred, "length mismatch").
compare_liveness_list([], [_ | _], _) :-
unexpected($pred, "length mismatch").
compare_liveness_list([LiveA | LiveAs], [LiveB | LiveBs], Result) :-
compare_liveness(LiveA, LiveB, Result0),
compare_liveness_list(LiveAs, LiveBs, Result1),
combine_results(Result0, Result1, Result).
% Compare_liveness -- prefer dead to live. If either is a valid match,
% then the actual argument must be dead, so prefer the mode which can take
% advantage of that).
%
:- pred compare_liveness(is_live::in, is_live::in, match::out) is det.
compare_liveness(is_dead, is_dead, same).
compare_liveness(is_dead, is_live, better).
compare_liveness(is_live, is_dead, worse).
compare_liveness(is_live, is_live, same).
% Combine two results, giving priority to the first one.
%
:- pred prioritized_combine_results(match::in, match::in, match::out) is det.
prioritized_combine_results(better, _, better).
prioritized_combine_results(worse, _, worse).
prioritized_combine_results(same, Result, Result).
prioritized_combine_results(incomparable, _, incomparable).
% Combine two results, giving them equal priority.
%
:- pred combine_results(match::in, match::in, match::out) is det.
combine_results(better, better, better).
combine_results(better, same, better).
combine_results(better, worse, incomparable).
combine_results(better, incomparable, incomparable).
combine_results(worse, worse, worse).
combine_results(worse, same, worse).
combine_results(worse, better, incomparable).
combine_results(worse, incomparable, incomparable).
combine_results(same, Result, Result).
combine_results(incomparable, _, incomparable).
% Compare two initial insts, to figure out which would be a better match.
%
% More information is better:
% prefer bound(f) to ground
% prefer unique to mostly_unique or ground, and
% prefer mostly_unique to ground
% (unique > mostly_unique > shared > mostly_dead > dead)
% More bound is better:
% (if both can match, the one which is more bound
% is better, because it may be an exact match, whereas
% the other one would be an implied mode)
% prefer ground to free (i.e. prefer in to out)
% prefer ground to any (e.g. prefer in to in(any))
% prefer any to free (e.g. prefer any->ground to out)
%
:- pred compare_inst(module_info::in, mer_inst::in, mer_inst::in,
maybe(mer_inst)::in, mer_type::in, match::out) is det.
compare_inst(ModuleInfo, InstA, InstB, MaybeArgInst, Type, Result) :-
% inst_matches_initial(A,B) succeeds iff
% A specifies at least as much information
% and at least as much binding as B --
% with the exception that `any' matches_initial `free'
% and perhaps vice versa.
( if inst_matches_initial(ModuleInfo, Type, InstA, InstB) then
A_mi_B = yes
else
A_mi_B = no
),
( if inst_matches_initial(ModuleInfo, Type, InstB, InstA) then
B_mi_A = yes
else
B_mi_A = no
),
( A_mi_B = yes, B_mi_A = no, Result = better
; A_mi_B = no, B_mi_A = yes, Result = worse
; A_mi_B = no, B_mi_A = no, Result = incomparable
; A_mi_B = yes, B_mi_A = yes,
% We need to further disambiguate the cases involving `any' and `free',
% since `any' matches_initial `free' and vice versa.
% For these cases, we want to take the actual inst of the argument
% into account: if the argument is `free', we should prefer `free',
% but otherwise, we should prefer `any'.
%
(
MaybeArgInst = no,
Result0 = same
;
MaybeArgInst = yes(ArgInst),
( if
inst_matches_initial_no_implied_modes(ModuleInfo, Type,
ArgInst, InstA)
then
Arg_mf_A = yes
else
Arg_mf_A = no
),
( if
inst_matches_initial_no_implied_modes(ModuleInfo, Type,
ArgInst, InstB)
then
Arg_mf_B = yes
else
Arg_mf_B = no
),
( Arg_mf_A = yes, Arg_mf_B = no, Result0 = better
; Arg_mf_A = no, Arg_mf_B = yes, Result0 = worse
; Arg_mf_A = yes, Arg_mf_B = yes, Result0 = same
; Arg_mf_A = no, Arg_mf_B = no, Result0 = same
)
),
(
Result0 = same,
% If the actual arg inst is not available, or comparing with
% the arg inst doesn't help, then compare the two proc insts.
( if
inst_matches_initial_no_implied_modes(ModuleInfo, Type,
InstA, InstB)
then
A_mf_B = yes
else
A_mf_B = no
),
( if
inst_matches_initial_no_implied_modes(ModuleInfo, Type,
InstB, InstA)
then
B_mf_A = yes
else
B_mf_A = no
),
( A_mf_B = yes, B_mf_A = no, Result = better
; A_mf_B = no, B_mf_A = yes, Result = worse
; A_mf_B = no, B_mf_A = no, Result = incomparable
; A_mf_B = yes, B_mf_A = yes, Result = same
)
;
( Result0 = better
; Result0 = worse
),
Result = Result0
)
).
%---------------------------------------------------------------------------%
:- end_module check_hlds.mode_comparison.
%---------------------------------------------------------------------------%