Files
mercury/compiler/modecheck_goal.m
Zoltan Somogyi 635b2268fe Improve handling of from_ground_term_deconstruct scopes.
compiler/hlds_goal.m:
    Document that all goals inside from_ground_term_deconstruct scopes
    are not just unifications, but unifications whose right hand sides
    are rhs_functors.

compiler/constraint.m:
    Fix a potential problem, which is that pushing a constraint into
    from_ground_term_{deconstruct,other) scopes can invalidate
    their invariants.

compiler/goal_refs.m:
    Fix a comment.

compiler/lco.m:
    Qualify a call.

compiler/modecheck_goal.m:
    Fix a misleading predicate name, and typos.

compiler/try_expand.m:
    Exploit the newly-documented invariant about from_ground_term_deconstruct
    scopes.
2026-01-11 03:57:52 +11:00

1644 lines
71 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2009-2012 The University of Melbourne.
% Copyright (C) 2014-2019, 2021-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: modecheck_goal.m.
% Main author: fjh.
%
% To mode-analyse a goal:
% If goal is
% (a) a disjunction
% Mode-analyse the sub-goals;
% check that the final insts of all the non-local variables are the same
% for all the sub-goals.
% (b) a conjunction
% Attempt to schedule each sub-goal. If a sub-goal can be scheduled,
% then schedule it, otherwise delay it. Continue with the remaining
% subgoals until there are no goals left. Every time a variable gets
% bound, see whether we should wake up a delayed goal, and if so,
% wake it up next time we get back to the conjunction. If there are
% still delayed goals hanging around at the end of the conjunction,
% report a mode error.
% (c) a negation
% Mode-check the sub-goal.
% Check that the sub-goal does not further instantiate any nonlocal
% variables. (Actually, rather than doing this check after we
% mode-analyse the subgoal, we instead "lock" the non-local variables,
% and disallow binding of locked variables.)
% (d) a unification
% Check that the unification doesn't attempt to unify two free variables
% (or in general two free sub-terms) unless one of them is dead.
% Split unifications up if necessary to avoid complicated
% sub-unifications. We also figure out at this point whether or not each
% unification can fail.
% (e) a predicate call
% Check that there is a mode declaration for the predicate which matches
% the current instantiation of the arguments. (Also handle calls
% to implied modes.) If the called predicate is one for which
% we must infer the modes, then create a new mode for the called
% predicate whose initial insts are the result of normalising
% the current inst of the arguments.
% (f) an if-then-else
% Attempt to schedule the condition. If successful, then check that
% it doesn't further instantiate any nonlocal variables, modecheck
% the `then' part and the `else' part, and then check that the final
% insts match. (Perhaps also think about expanding if-then-elses so that
% they can be run backwards, if the condition can't be scheduled?)
%
% To attempt to schedule a goal, first mode-check the goal. If mode-checking
% succeeds, then scheduling succeeds. If mode-checking would report an error
% due to the binding of a local variable, then scheduling fails.
% (If mode-checking would report an error due to the binding of a *local*
% variable, we could report the error right away -- but this idea has
% not yet been implemented.)
%
% Note that the notion of liveness used here is different to that used
% in liveness.m and the code generator. Here, we consider a variable live
% if its value will be used later on in the computation.
%
% XXX We ought to allow unification of free with free even when both
% *variables* are live, if one of the particular *sub-nodes* is dead
% (causes problems handling e.g. `list.same_length').
%
% XXX We ought to break unifications into "micro-unifications", because
% some code can't be scheduled without splitting up unifications.
% For example, `p(X) :- X = f(A, B), B is A + 1.', where p is declared as
% `:- mode p(bound(f(ground,free))->ground).'.
%
% XXX At the moment we don't check for circular modes or insts.
% If they aren't used, the compiler will probably not detect the error;
% if they are, it will probably go into an infinite loop.
%
%---------------------------------------------------------------------------%
:- module check_hlds.modecheck_goal.
:- interface.
:- import_module check_hlds.mode_info.
:- import_module hlds.
:- import_module hlds.hlds_goal.
% Modecheck a goal by abstractly interpreting it, as explained
% at the top of this file.
%
% Input-output:
% InstMap Stored in ModeInfo
% DelayInfo Stored in ModeInfo
% Goal Passed as an argument pair
% Input only:
% ModuleInfo Stored in ModeInfo (constant)
% Context Stored in ModeInfo (changing as we go along the clause)
% Output only:
% Error Messages Stored in ModeInfo (updated as we find errors)
%
:- pred modecheck_goal(hlds_goal::in, hlds_goal::out,
mode_info::in, mode_info::out) is det.
% Mode-check a single goal-expression.
%
:- pred modecheck_goal_expr(hlds_goal_expr::in, hlds_goal_info::in,
hlds_goal_expr::out, mode_info::in, mode_info::out) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.mode_debug.
:- import_module check_hlds.mode_errors.
:- import_module check_hlds.modecheck_call.
:- import_module check_hlds.modecheck_coerce.
:- import_module check_hlds.modecheck_conj.
:- import_module check_hlds.modecheck_unify.
:- import_module check_hlds.modecheck_util.
:- import_module check_hlds.polymorphism_type_info.
:- import_module hlds.goal_transform.
:- import_module hlds.goal_util.
:- import_module hlds.hlds_data.
:- import_module hlds.hlds_markers.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module hlds.inst_test.
:- import_module hlds.instmap.
:- import_module hlds.make_goal.
:- import_module hlds.pred_table.
:- import_module hlds.type_util.
:- import_module mdbcomp.
:- import_module mdbcomp.builtin_modules.
:- import_module mdbcomp.prim_data.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.
:- import_module parse_tree.builtin_lib_types.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_data_event.
:- import_module parse_tree.prog_data_foreign.
:- import_module parse_tree.prog_event.
:- import_module parse_tree.prog_mode.
:- import_module parse_tree.prog_rename.
:- import_module parse_tree.set_of_var.
:- import_module parse_tree.var_table.
:- import_module bag.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module term_context.
:- import_module uint.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
modecheck_goal(Goal0, Goal, !ModeInfo) :-
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
% Note: any changes here may need to be duplicated in unique_modes.m.
% Store the current context in the mode_info.
Context = goal_info_get_context(GoalInfo0),
( if is_dummy_context(Context) then
true
else
mode_info_set_context(Context, !ModeInfo)
),
% Modecheck the goal, and then record the changes in the instantiation
% of the goal's vars in the delta_instmap in the goal_info.
mode_info_get_instmap(!.ModeInfo, InstMap0),
( if goal_info_has_feature(GoalInfo0, feature_duplicated_for_switch) then
mode_info_get_in_dupl_for_switch(!.ModeInfo, InDuplForSwitch),
mode_info_set_in_dupl_for_switch(in_dupl_for_switch, !ModeInfo),
modecheck_goal_expr(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo),
mode_info_set_in_dupl_for_switch(InDuplForSwitch, !ModeInfo)
else
modecheck_goal_expr(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo)
),
compute_goal_instmap_delta(InstMap0, GoalExpr, GoalInfo0, GoalInfo,
!ModeInfo),
Goal = hlds_goal(GoalExpr, GoalInfo).
modecheck_goal_expr(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo) :-
% XXX The predicates we call here should have their definitions
% in the same order as this switch.
(
GoalExpr0 = unify(_, _, _, _, _),
modecheck_goal_unify(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo)
;
GoalExpr0 = plain_call(_, _, _, _, _, _),
modecheck_goal_plain_call(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo)
;
GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _),
modecheck_goal_foreign_call(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo)
;
GoalExpr0 = generic_call(_, _, _, _, _),
modecheck_goal_generic_call(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo)
;
GoalExpr0 = conj(ConjType, Goals),
modecheck_goal_conj(ConjType, Goals, GoalInfo0, GoalExpr, !ModeInfo)
;
GoalExpr0 = disj(Goals),
modecheck_goal_disj(Goals, GoalInfo0, GoalExpr, !ModeInfo)
;
GoalExpr0 = switch(_, _, _),
modecheck_goal_switch(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo)
;
GoalExpr0 = if_then_else(_, _, _, _),
modecheck_goal_if_then_else(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo)
;
GoalExpr0 = negation(SubGoal0),
modecheck_goal_negation(SubGoal0, GoalInfo0, GoalExpr, !ModeInfo)
;
GoalExpr0 = scope(Reason, SubGoal0),
modecheck_goal_scope(Reason, SubGoal0, GoalInfo0, GoalExpr, !ModeInfo)
;
GoalExpr0 = shorthand(ShortHand0),
modecheck_goal_shorthand(ShortHand0, GoalInfo0, GoalExpr, !ModeInfo)
).
%---------------------------------------------------------------------------%
%
% Modecheck unifications. Most of the work is in modecheck_unify.m.
%
:- pred modecheck_goal_unify(hlds_goal_expr::in(goal_expr_unify),
hlds_goal_info::in, hlds_goal_expr::out,
mode_info::in, mode_info::out) is det.
modecheck_goal_unify(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo) :-
GoalExpr0 = unify(LHS0, RHS0, _UniMode, Unification0, UnifyContext),
mode_checkpoint(enter, "unify", GoalInfo0, !ModeInfo),
mode_info_set_call_context(call_context_unify(UnifyContext), !ModeInfo),
modecheck_unify(LHS0, RHS0, Unification0, UnifyContext, GoalInfo0,
GoalExpr, !ModeInfo),
mode_info_unset_call_context(!ModeInfo),
mode_checkpoint(exit, "unify", GoalInfo0, !ModeInfo).
%---------------------------------------------------------------------------%
%
% Modecheck plain calls. Most of the work is in modecheck_call.m.
%
:- pred modecheck_goal_plain_call(hlds_goal_expr::in(goal_expr_plain_call),
hlds_goal_info::in, hlds_goal_expr::out,
mode_info::in, mode_info::out) is det.
modecheck_goal_plain_call(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo) :-
GoalExpr0 = plain_call(PredId, ProcId0, ArgVars0, _Builtin,
MaybeCallUnifyContext, PredSymName),
mode_checkpoint_sn(enter, "call", PredSymName, GoalInfo0, !ModeInfo),
CallId = mode_call_plain(PredId),
mode_info_set_call_context(call_context_call(CallId), !ModeInfo),
mode_info_get_instmap(!.ModeInfo, InstMap0),
MaybeKnownDeterminism = no,
modecheck_plain_or_foreign_call(PredId, MaybeKnownDeterminism,
ProcId0, ProcId, ArgVars0, ArgVars, GoalInfo0, ExtraGoals, !ModeInfo),
mode_info_get_module_info(!.ModeInfo, ModuleInfo),
mode_info_get_pred_id(!.ModeInfo, CallerPredId),
Builtin = builtin_state(ModuleInfo, CallerPredId, PredId, ProcId),
GoalExpr1 = plain_call(PredId, ProcId, ArgVars, Builtin,
MaybeCallUnifyContext, PredSymName),
handle_extra_goals(GoalExpr1, ExtraGoals, GoalInfo0, ArgVars0, ArgVars,
InstMap0, GoalExpr, !ModeInfo),
mode_info_unset_call_context(!ModeInfo),
mode_checkpoint_sn(exit, "call", PredSymName, GoalInfo0, !ModeInfo).
%---------------------------------------------------------------------------%
%
% Modecheck foreign_proc goals.
%
:- pred modecheck_goal_foreign_call(
hlds_goal_expr::in(goal_expr_foreign_proc),
hlds_goal_info::in, hlds_goal_expr::out,
mode_info::in, mode_info::out) is det.
modecheck_goal_foreign_call(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo) :-
% To modecheck a foreign_proc, we just modecheck the proc for
% which it is the goal.
GoalExpr0 = call_foreign_proc(Attributes, PredId, ProcId0,
Args0, ExtraArgs, MaybeTraceRuntimeCond, PragmaCode),
mode_checkpoint(enter, "foreign_proc", GoalInfo0, !ModeInfo),
CallId = mode_call_plain(PredId),
mode_info_set_call_context(call_context_call(CallId), !ModeInfo),
mode_info_get_instmap(!.ModeInfo, InstMap0),
DeterminismKnown = no,
ArgVars0 = list.map(foreign_arg_var, Args0),
modecheck_plain_or_foreign_call(PredId, DeterminismKnown, ProcId0, ProcId,
ArgVars0, ArgVars, GoalInfo0, ExtraGoals, !ModeInfo),
% zs: The assignment to Pragma looks wrong: instead of Args0,
% I think we should use Args after the following call:
% replace_foreign_arg_vars(Args0, ArgVars, Args)
% or is there some reason why Args0 and Args would be the same?
GoalExpr1 = call_foreign_proc(Attributes, PredId, ProcId,
Args0, ExtraArgs, MaybeTraceRuntimeCond, PragmaCode),
handle_extra_goals(GoalExpr1, ExtraGoals, GoalInfo0, ArgVars0, ArgVars,
InstMap0, GoalExpr, !ModeInfo),
mode_info_unset_call_context(!ModeInfo),
mode_checkpoint(exit, "foreign_proc", GoalInfo0, !ModeInfo).
%---------------------------------------------------------------------------%
%
% Modecheck generic calls.
%
:- pred modecheck_goal_generic_call(hlds_goal_expr::in(goal_expr_generic_call),
hlds_goal_info::in, hlds_goal_expr::out,
mode_info::in, mode_info::out) is det.
modecheck_goal_generic_call(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo) :-
GoalExpr0 = generic_call(GenericCall, Args0, Modes0, _MaybeArgRegs,
_Detism),
mode_checkpoint(enter, "generic_call", GoalInfo0, !ModeInfo),
CallId = mode_call_generic(GenericCall),
mode_info_set_call_context(call_context_call(CallId), !ModeInfo),
mode_info_get_instmap(!.ModeInfo, InstMap0),
(
GenericCall = higher_order(PredVar, _, _, _, _),
modecheck_higher_order_call(GenericCall, Args0, Args, Modes,
Det, ExtraGoals, !ModeInfo),
GoalExpr1 =
generic_call(GenericCall, Args, Modes, arg_reg_types_unset, Det),
AllArgs0 = [PredVar | Args0],
AllArgs = [PredVar | Args],
handle_extra_goals(GoalExpr1, ExtraGoals, GoalInfo0, AllArgs0, AllArgs,
InstMap0, GoalExpr, !ModeInfo)
;
% Class method calls are added by polymorphism.m.
% XXX We should probably fill this in so that
% rerunning mode analysis works on code with typeclasses.
GenericCall = class_method(_, _, _, _),
unexpected($pred, "class_method_call")
;
GenericCall = event_call(EventName),
mode_info_get_module_info(!.ModeInfo, ModuleInfo),
module_info_get_event_set(ModuleInfo, EventSet),
EventSpecMap = EventSet ^ event_set_spec_map,
( if event_arg_modes(EventSpecMap, EventName, ModesPrime) then
Modes = ModesPrime
else
% The typechecker should have caught the unknown event,
% and not let compilation of this predicate proceed any further.
unexpected($pred, "unknown event")
),
modecheck_event_call(Modes, Args0, Args, !ModeInfo),
GoalExpr = generic_call(GenericCall, Args, Modes, arg_reg_types_unset,
detism_det)
;
GenericCall = cast(CastType),
( CastType = unsafe_type_cast
; CastType = unsafe_type_inst_cast
; CastType = equiv_type_cast
; CastType = exists_cast
),
( if
goal_info_has_feature(GoalInfo0, feature_keep_constant_binding),
mode_info_get_instmap(!.ModeInfo, InstMap),
( if
Args0 = [Arg1Prime, _Arg2Prime],
Modes0 = [Mode1Prime, Mode2Prime]
then
Arg1 = Arg1Prime,
Mode1 = Mode1Prime,
Mode2 = Mode2Prime
else
unexpected($pred, "bad cast")
),
is_in_mode(Mode1, _Mode1ArgInsts),
is_out_mode(Mode2, _Mode2ArgInsts),
instmap_lookup_var(InstMap, Arg1, Inst1),
Inst1 = bound(Unique, _, [bound_functor(ConsId, [])]),
mode_info_get_module_info(!.ModeInfo, ModuleInfo),
ConsId = du_data_ctor(DuCtor),
get_cons_repn_defn(ModuleInfo, DuCtor, ConsRepn),
ConsRepn ^ cr_tag = shared_local_tag_no_args(_, LocalSectag, _)
then
LocalSectag = local_sectag(_, PrimSec, _),
SectagWholeWord = uint.cast_to_int(PrimSec),
SectagConsId = some_int_const(int_const(SectagWholeWord)),
BoundFunctor = bound_functor(SectagConsId, []),
BoundInst = bound(Unique, inst_test_results_fgtc, [BoundFunctor]),
NewMode2 = from_to_mode(free, BoundInst),
Modes = [Mode1, NewMode2]
else
Modes = Modes0
),
modecheck_builtin_cast(Modes, Args0, Args, Det, ExtraGoals, !ModeInfo),
GoalExpr1 = generic_call(GenericCall, Args, Modes, arg_reg_types_unset,
Det),
handle_extra_goals(GoalExpr1, ExtraGoals, GoalInfo0, Args0, Args,
InstMap0, GoalExpr, !ModeInfo)
;
GenericCall = cast(subtype_coerce),
modecheck_coerce(Args0, Args, Modes0, Modes, Det, ExtraGoals,
!ModeInfo),
GoalExpr1 = generic_call(GenericCall, Args, Modes, arg_reg_types_unset,
Det),
handle_extra_goals(GoalExpr1, ExtraGoals, GoalInfo0, Args0, Args,
InstMap0, GoalExpr, !ModeInfo)
),
mode_info_unset_call_context(!ModeInfo),
mode_checkpoint(exit, "generic_call", GoalInfo0, !ModeInfo).
%---------------------------------------------------------------------------%
%
% Modecheck conjunctions. Most of the work is done by modecheck_conj.m.
%
:- pred modecheck_goal_conj(conj_type::in, list(hlds_goal)::in,
hlds_goal_info::in, hlds_goal_expr::out,
mode_info::in, mode_info::out) is det.
modecheck_goal_conj(ConjType, Goals0, GoalInfo0, GoalExpr, !ModeInfo) :-
(
ConjType = plain_conj,
mode_checkpoint(enter, "conj", GoalInfo0, !ModeInfo),
(
Goals0 = [],
% Optimize the common case for efficiency.
GoalExpr = conj(plain_conj, [])
;
Goals0 = [_ | _],
modecheck_conj_list(ConjType, Goals0, Goals, !ModeInfo),
conj_list_to_goal(Goals, GoalInfo0, hlds_goal(GoalExpr, _GoalInfo))
),
mode_checkpoint(exit, "conj", GoalInfo0, !ModeInfo)
;
ConjType = parallel_conj,
mode_checkpoint(enter, "par_conj", GoalInfo0, !ModeInfo),
% Empty parallel conjunction should not be a common case.
modecheck_conj_list(ConjType, Goals0, Goals, !ModeInfo),
par_conj_list_to_goal(Goals, GoalInfo0, Goal),
Goal = hlds_goal(GoalExpr, _GoalInfo),
mode_checkpoint(exit, "par_conj", GoalInfo0, !ModeInfo)
).
%---------------------------------------------------------------------------%
%
% Modecheck disjunctions.
%
:- pred modecheck_goal_disj(list(hlds_goal)::in, hlds_goal_info::in,
hlds_goal_expr::out, mode_info::in, mode_info::out) is det.
modecheck_goal_disj(Disjuncts0, GoalInfo0, GoalExpr, !ModeInfo) :-
mode_checkpoint(enter, "disj", GoalInfo0, !ModeInfo),
(
Disjuncts0 = [], % for efficiency, optimize common case
GoalExpr = disj(Disjuncts0),
instmap.init_unreachable(InstMap),
mode_info_set_instmap(InstMap, !ModeInfo)
;
% If you modify this code, you may also need to modify
% modecheck_clause_disj or the code that calls it.
Disjuncts0 = [_ | _],
mode_info_get_pred_var_multimode_error_map(!.ModeInfo,
MultiModeErrorMap0),
NonLocals = goal_info_get_nonlocals(GoalInfo0),
LargeFlatConstructs0 = NonLocals,
mode_info_get_instmap(!.ModeInfo, InstMap0),
modecheck_disjuncts(MultiModeErrorMap0, InstMap0,
Disjuncts0, Disjuncts1, ArmInstMaps,
LargeFlatConstructs0, LargeFlatConstructs, !ModeInfo),
mode_info_set_instmap(InstMap0, !ModeInfo),
merge_disj_branches(NonLocals, LargeFlatConstructs,
Disjuncts1, Disjuncts2, ArmInstMaps, !ModeInfo),
% Since merge_disj_branches depends on each disjunct in Disjuncts2
% having a corresponding instmap in InstMaps, we can flatten disjuncts
% only *after* merge_disj_branches has done its job.
flatten_disj(Disjuncts2, Disjuncts),
disj_list_to_goal(Disjuncts, GoalInfo0, hlds_goal(GoalExpr, _GoalInfo))
),
mode_checkpoint(exit, "disj", GoalInfo0, !ModeInfo).
:- pred modecheck_disjuncts(pred_var_multimode_error_map::in, instmap::in,
list(hlds_goal)::in, list(hlds_goal)::out, list(arm_instmap)::out,
set_of_progvar::in, set_of_progvar::out,
mode_info::in, mode_info::out) is det.
modecheck_disjuncts(_, _, [], [], [], !LargeFlatConstructs, !ModeInfo).
modecheck_disjuncts(MultiModeErrorMap0, InstMap0,
[Disjunct0 | Disjuncts0], [Disjunct | Disjuncts],
[ArmInstMap | ArmInstMaps], !LargeFlatConstructs, !ModeInfo) :-
mode_info_set_pred_var_multimode_error_map(MultiModeErrorMap0, !ModeInfo),
mode_info_set_instmap(InstMap0, !ModeInfo),
modecheck_goal(Disjunct0, Disjunct, !ModeInfo),
accumulate_large_flat_constructs(Disjunct, !LargeFlatConstructs),
mode_info_get_instmap(!.ModeInfo, InstMap),
Context = goal_info_get_context(Disjunct ^ hg_info),
ArmInstMap = arm_instmap(Context, InstMap),
modecheck_disjuncts(MultiModeErrorMap0, InstMap0, Disjuncts0, Disjuncts,
ArmInstMaps, !LargeFlatConstructs, !ModeInfo).
:- pred merge_disj_branches(set_of_progvar::in, set_of_progvar::in,
list(hlds_goal)::in, list(hlds_goal)::out, list(arm_instmap)::in,
mode_info::in, mode_info::out) is det.
merge_disj_branches(NonLocals, LargeFlatConstructs, Disjuncts0, Disjuncts,
ArmInstMaps0, !ModeInfo) :-
( if set_of_var.is_empty(LargeFlatConstructs) then
Disjuncts = Disjuncts0,
ArmInstMaps = ArmInstMaps0
else
% The instmaps will each map every var in LargeFlatConstructs
% to a very big inst. This means that instmap_merge will take a long
% time on those variables and add lots of big insts to the merge_inst
% table. That in turn will cause the later equiv_type_hlds pass
% to take a long time processing the merge_inst table. All this
% expense is for nothing, since the chances that the following code
% wants to know the precise set of possible bindings of variables
% constructed in what are effectively fact tables is astronomically
% small.
%
% For the variables in LargeFlatConstructs, we know that their
% final insts do not cause unreachability, do not have uniqueness,
% do not have higher order inst info, and any information they contain
% about specific bindings is something we are better off without.
% We therefore just map all these variables to ground in the instmaps
% of all the arms before merging them.
list.map(
set_large_flat_constructs_to_ground_in_goal(LargeFlatConstructs),
Disjuncts0, Disjuncts),
LargeFlatConstructList =
set_of_var.to_sorted_list(LargeFlatConstructs),
list.map(
arm_instmap_set_vars_same(ground(shared, none_or_default_func),
LargeFlatConstructList),
ArmInstMaps0, ArmInstMaps)
),
instmap_merge(NonLocals, ArmInstMaps, merge_disj, !ModeInfo).
:- pred arm_instmap_set_vars_same(mer_inst::in, list(prog_var)::in,
arm_instmap::in, arm_instmap::out) is det.
arm_instmap_set_vars_same(Inst, Vars, ArmInstMap0, ArmInstMap) :-
ArmInstMap0 = arm_instmap(Context, InstMap0),
instmap_set_vars_same(Inst, Vars, InstMap0, InstMap),
ArmInstMap = arm_instmap(Context, InstMap).
%---------------------------------------------------------------------------%
%
% Modecheck switches.
%
:- pred modecheck_goal_switch(hlds_goal_expr::in(goal_expr_switch),
hlds_goal_info::in, hlds_goal_expr::out,
mode_info::in, mode_info::out) is det.
modecheck_goal_switch(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo) :-
GoalExpr0 = switch(Var, CanFail, Cases0),
mode_checkpoint(enter, "switch", GoalInfo0, !ModeInfo),
(
Cases0 = [],
Cases = [],
instmap.init_unreachable(InstMap),
mode_info_set_instmap(InstMap, !ModeInfo)
;
% If you modify this code, you may also need to modify
% modecheck_clause_switch or the code that calls it.
Cases0 = [_ | _],
mode_info_get_pred_var_multimode_error_map(!.ModeInfo,
MultiModeErrorMap0),
NonLocals = goal_info_get_nonlocals(GoalInfo0),
LargeFlatConstructs0 = NonLocals,
modecheck_case_list(MultiModeErrorMap0, Var, Cases0, Cases1, InstMaps,
LargeFlatConstructs0, LargeFlatConstructs, !ModeInfo),
merge_switch_branches(NonLocals, LargeFlatConstructs,
Cases1, Cases, InstMaps, !ModeInfo)
),
GoalExpr = switch(Var, CanFail, Cases),
mode_checkpoint(exit, "switch", GoalInfo0, !ModeInfo).
:- pred modecheck_case_list(pred_var_multimode_error_map::in, prog_var::in,
list(case)::in, list(case)::out, list(instmap)::out,
set_of_progvar::in, set_of_progvar::out,
mode_info::in, mode_info::out) is det.
modecheck_case_list(_, _, [], [], [], !LargeFlatConstructs, !ModeInfo).
modecheck_case_list(MultiModeErrorMap0, Var, [Case0 | Cases0], [Case | Cases],
[InstMap | InstMaps], !LargeFlatConstructs, !ModeInfo) :-
mode_info_set_pred_var_multimode_error_map(MultiModeErrorMap0, !ModeInfo),
Case0 = case(MainConsId, OtherConsIds, Goal0),
mode_info_get_instmap(!.ModeInfo, InstMap0),
% Record the fact that Var was bound to ConsId in the instmap
% before processing this case.
modecheck_record_functors_test(Var, MainConsId, OtherConsIds, !ModeInfo),
% Modecheck this case (if it is reachable).
mode_info_get_instmap(!.ModeInfo, InstMap1),
( if instmap_is_reachable(InstMap1) then
modecheck_goal(Goal0, Goal1, !ModeInfo),
mode_info_get_instmap(!.ModeInfo, InstMap)
else
% We should not mode-analyse the goal, since it is unreachable.
% Instead we optimize the goal away, so that later passes
% won't complain about it not having mode information.
Goal1 = true_goal,
InstMap = InstMap1
),
% Don't lose the information added by the functor test above.
fixup_instmap_switch_var(Var, InstMap0, InstMap, Goal1, Goal),
Case = case(MainConsId, OtherConsIds, Goal),
accumulate_large_flat_constructs(Goal, !LargeFlatConstructs),
mode_info_set_instmap(InstMap0, !ModeInfo),
modecheck_case_list(MultiModeErrorMap0, Var, Cases0, Cases, InstMaps,
!LargeFlatConstructs, !ModeInfo).
:- pred merge_switch_branches(set_of_progvar::in, set_of_progvar::in,
list(case)::in, list(case)::out, list(instmap)::in,
mode_info::in, mode_info::out) is det.
merge_switch_branches(NonLocals, LargeFlatConstructs, Cases0, Cases,
InstMaps0, !ModeInfo) :-
( if set_of_var.is_empty(LargeFlatConstructs) then
Cases = Cases0,
InstMaps = InstMaps0
else
% The same considerations apply here as in merge_disj_branches.
list.map(
set_large_flat_constructs_to_ground_in_case(LargeFlatConstructs),
Cases0, Cases),
LargeFlatConstructList =
set_of_var.to_sorted_list(LargeFlatConstructs),
list.map(
instmap_set_vars_same(ground(shared, none_or_default_func),
LargeFlatConstructList),
InstMaps0, InstMaps)
),
make_arm_instmaps_for_cases(Cases, InstMaps, ArmInstMaps),
instmap_merge(NonLocals, ArmInstMaps, merge_disj, !ModeInfo).
%---------------------------------------------------------------------------%
%
% Utility predicates used to help optimize the modechecking of disjunctions and
% switches.
%
:- pred accumulate_large_flat_constructs(hlds_goal::in,
set_of_progvar::in, set_of_progvar::out) is det.
accumulate_large_flat_constructs(Goal, !LargeFlatConstructs) :-
( if set_of_var.is_empty(!.LargeFlatConstructs) then
% Calling goal_large_flat_constructs and then set.intersect
% would be waste of time; !:LargeFlatConstructs will still be empty.
true
else
GoalLargeFlatConstructs = goal_large_flat_constructs(Goal),
set_of_var.intersect(GoalLargeFlatConstructs, !LargeFlatConstructs)
).
:- func goal_large_flat_constructs(hlds_goal) = set_of_progvar.
goal_large_flat_constructs(Goal) = LargeFlatConstructs :-
Goal = hlds_goal(GoalExpr, _),
(
GoalExpr = unify(_, _, _, _, _),
% Unifications not wrapped in from_ground_term_construct scopes
% are never marked by the modechecker as being constructed statically.
LargeFlatConstructs = set_of_var.init
;
( GoalExpr = plain_call(_, _, _, _, _, _)
; GoalExpr = generic_call(_, _, _, _, _)
; GoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
),
LargeFlatConstructs = set_of_var.init
;
( GoalExpr = disj(_)
; GoalExpr = switch(_, _, _)
; GoalExpr = if_then_else(_, _, _, _)
; GoalExpr = negation(_)
; GoalExpr = shorthand(_)
; GoalExpr = conj(parallel_conj, _)
),
LargeFlatConstructs = set_of_var.init
;
GoalExpr = scope(Reason, _),
(
Reason = from_ground_term(TermVar, from_ground_term_construct),
LargeFlatConstructs = set_of_var.make_singleton(TermVar)
;
( Reason = from_ground_term(_, from_ground_term_initial)
; Reason = from_ground_term(_, from_ground_term_deconstruct)
; Reason = from_ground_term(_, from_ground_term_other)
; Reason = exist_quant(_, _)
; Reason = disable_warnings(_, _)
; Reason = promise_solutions(_, _)
; Reason = promise_purity(_)
; Reason = require_detism(_)
; Reason = require_complete_switch(_)
; Reason = require_switch_arms_detism(_, _)
; Reason = commit(_)
; Reason = barrier(_)
; Reason = trace_goal(_, _, _, _, _)
; Reason = loop_control(_, _, _)
),
LargeFlatConstructs = set_of_var.init
)
;
GoalExpr = conj(plain_conj, Conjuncts),
goals_large_flat_constructs(Conjuncts,
set_of_var.init, LargeFlatConstructs)
).
:- pred goals_large_flat_constructs(list(hlds_goal)::in,
set_of_progvar::in, set_of_progvar::out) is det.
goals_large_flat_constructs([], !LargeFlatConstructs).
goals_large_flat_constructs([Goal | Goals], !LargeFlatConstructs) :-
GoalLargeFlatConstructs = goal_large_flat_constructs(Goal),
set_of_var.union(GoalLargeFlatConstructs, !LargeFlatConstructs),
goals_large_flat_constructs(Goals, !LargeFlatConstructs).
:- pred set_large_flat_constructs_to_ground_in_goal(set_of_progvar::in,
hlds_goal::in, hlds_goal::out) is det.
set_large_flat_constructs_to_ground_in_goal(LargeFlatConstructs,
Goal0, Goal) :-
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
(
GoalExpr0 = unify(_, _, _, _, _),
Goal = Goal0
;
( GoalExpr0 = plain_call(_, _, _, _, _, _)
; GoalExpr0 = generic_call(_, _, _, _, _)
; GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _)
),
Goal = Goal0
;
( GoalExpr0 = disj(_)
; GoalExpr0 = switch(_, _, _)
; GoalExpr0 = if_then_else(_, _, _, _)
; GoalExpr0 = negation(_)
; GoalExpr0 = shorthand(_)
; GoalExpr0 = conj(parallel_conj, _)
),
Goal = Goal0
;
GoalExpr0 = scope(Reason, SubGoal0),
(
Reason = from_ground_term(TermVar, from_ground_term_construct),
( if set_of_var.member(LargeFlatConstructs, TermVar) then
InstMapDelta0 = goal_info_get_instmap_delta(GoalInfo0),
instmap_delta_set_var(TermVar,
ground(shared, none_or_default_func),
InstMapDelta0, InstMapDelta),
goal_info_set_instmap_delta(InstMapDelta, GoalInfo0, GoalInfo),
SubGoal0 = hlds_goal(SubGoalExpr0, SubGoalInfo0),
goal_info_set_instmap_delta(InstMapDelta,
SubGoalInfo0, SubGoalInfo),
% We could also replace the instmap deltas of the conjuncts
% inside SubGoalExpr0. Doing so would take time but reduce
% the compiler's memory requirements.
SubGoal = hlds_goal(SubGoalExpr0, SubGoalInfo),
GoalExpr = scope(Reason, SubGoal),
Goal = hlds_goal(GoalExpr, GoalInfo)
else
Goal = Goal0
)
;
( Reason = from_ground_term(_, from_ground_term_initial)
; Reason = from_ground_term(_, from_ground_term_deconstruct)
; Reason = from_ground_term(_, from_ground_term_other)
; Reason = exist_quant(_, _)
; Reason = disable_warnings(_, _)
; Reason = promise_solutions(_, _)
; Reason = promise_purity(_)
; Reason = require_detism(_)
; Reason = require_complete_switch(_)
; Reason = require_switch_arms_detism(_, _)
; Reason = commit(_)
; Reason = barrier(_)
; Reason = trace_goal(_, _, _, _, _)
; Reason = loop_control(_, _, _)
),
Goal = Goal0
)
;
GoalExpr0 = conj(plain_conj, Conjuncts0),
set_large_flat_constructs_to_ground_in_goals(LargeFlatConstructs,
Conjuncts0, Conjuncts),
GoalExpr = conj(plain_conj, Conjuncts),
InstMapDelta0 = goal_info_get_instmap_delta(GoalInfo0),
instmap_delta_changed_vars(InstMapDelta0, ChangedVars),
set_of_var.intersect(ChangedVars, LargeFlatConstructs, GroundVars),
instmap_delta_set_vars_same(ground(shared, none_or_default_func),
set_of_var.to_sorted_list(GroundVars),
InstMapDelta0, InstMapDelta),
goal_info_set_instmap_delta(InstMapDelta, GoalInfo0, GoalInfo),
Goal = hlds_goal(GoalExpr, GoalInfo)
).
:- pred set_large_flat_constructs_to_ground_in_goals(set_of_progvar::in,
list(hlds_goal)::in, list(hlds_goal)::out) is det.
set_large_flat_constructs_to_ground_in_goals(_, [], []).
set_large_flat_constructs_to_ground_in_goals(LargeFlatConstructs,
[Goal0 | Goals0], [Goal | Goals]) :-
set_large_flat_constructs_to_ground_in_goal(LargeFlatConstructs,
Goal0, Goal),
set_large_flat_constructs_to_ground_in_goals(LargeFlatConstructs,
Goals0, Goals).
:- pred set_large_flat_constructs_to_ground_in_case(set_of_progvar::in,
case::in, case::out) is det.
set_large_flat_constructs_to_ground_in_case(LargeFlatConstructs,
Case0, Case) :-
Case0 = case(MainConsId, OtherConsIds, Goal0),
set_large_flat_constructs_to_ground_in_goal(LargeFlatConstructs,
Goal0, Goal),
Case = case(MainConsId, OtherConsIds, Goal).
%---------------------------------------------------------------------------%
%
% Modecheck if-then-elses.
%
:- pred modecheck_goal_if_then_else(hlds_goal_expr::in(goal_expr_ite),
hlds_goal_info::in, hlds_goal_expr::out,
mode_info::in, mode_info::out) is det.
modecheck_goal_if_then_else(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo) :-
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
mode_checkpoint(enter, "if-then-else", GoalInfo0, !ModeInfo),
mode_info_get_pred_var_multimode_error_map(!.ModeInfo, MultiModeErrorMap0),
NonLocals = goal_info_get_nonlocals(GoalInfo0),
ThenVars = goal_get_nonlocals(Then0),
mode_info_get_instmap(!.ModeInfo, InstMap0),
% We need to lock the non-local variables, to ensure that the condition
% of the if-then-else does not bind them.
mode_info_lock_vars(var_lock_if_then_else, NonLocals, !ModeInfo),
mode_info_add_live_vars(ThenVars, !ModeInfo),
modecheck_goal(Cond0, Cond, !ModeInfo),
mode_info_get_instmap(!.ModeInfo, InstMapCond),
mode_info_remove_live_vars(ThenVars, !ModeInfo),
mode_info_unlock_vars(var_lock_if_then_else, NonLocals, !ModeInfo),
( if instmap_is_reachable(InstMapCond) then
modecheck_goal(Then0, Then, !ModeInfo),
mode_info_get_instmap(!.ModeInfo, InstMapThen)
else
% We should not mode-analyse the goal, since it is unreachable.
% Instead we optimize the goal away, so that later passes
% won't complain about it not having mode information.
Then = true_goal,
InstMapThen = InstMapCond
),
mode_info_set_pred_var_multimode_error_map(MultiModeErrorMap0, !ModeInfo),
mode_info_set_instmap(InstMap0, !ModeInfo),
modecheck_goal(Else0, Else, !ModeInfo),
mode_info_get_instmap(!.ModeInfo, InstMapElse),
mode_info_set_instmap(InstMap0, !ModeInfo),
make_arm_instmaps_for_goals([Then, Else], [InstMapThen, InstMapElse],
ThenElseArgInfos),
instmap_merge(NonLocals, ThenElseArgInfos, merge_if_then_else, !ModeInfo),
GoalExpr = if_then_else(Vars, Cond, Then, Else),
mode_info_get_instmap(!.ModeInfo, InstMap),
mode_info_get_in_promise_purity_scope(!.ModeInfo, InPromisePurityScope),
(
InPromisePurityScope = not_in_promise_purity_scope,
CondNonLocals0 = goal_get_nonlocals(Cond),
CondNonLocals =
set_of_var.to_sorted_list(
set_of_var.intersect(CondNonLocals0, NonLocals)),
check_no_inst_any_vars(if_then_else, CondNonLocals,
InstMap0, InstMap, !ModeInfo)
;
InPromisePurityScope = in_promise_purity_scope
),
mode_checkpoint(exit, "if-then-else", GoalInfo0, !ModeInfo).
%---------------------------------------------------------------------------%
%
% Modecheck negations.
%
:- pred modecheck_goal_negation(hlds_goal::in, hlds_goal_info::in,
hlds_goal_expr::out, mode_info::in, mode_info::out) is det.
modecheck_goal_negation(SubGoal0, GoalInfo0, GoalExpr, !ModeInfo) :-
mode_checkpoint(enter, "not", GoalInfo0, !ModeInfo),
mode_info_get_pred_var_multimode_error_map(!.ModeInfo, MultiModeErrorMap0),
NonLocals = goal_info_get_nonlocals(GoalInfo0),
mode_info_get_instmap(!.ModeInfo, InstMap0),
% When analyzing a negated goal, nothing is forward-live (live on forward
% execution after that goal), because if the goal succeeds then execution
% will immediately backtrack. So we need to set the live variables set
% to empty here. This allows those variables to be backtrackably
% destructively updated. (If you try to do non-backtrackable destructive
% update on such a variable, it will be caught later on by unique_modes.m.)
mode_info_get_live_vars(!.ModeInfo, LiveVars0),
mode_info_set_live_vars(bag.init, !ModeInfo),
% We need to lock the non-local variables, to ensure that
% the negation does not bind them.
mode_info_lock_vars(var_lock_negation, NonLocals, !ModeInfo),
modecheck_goal(SubGoal0, SubGoal, !ModeInfo),
mode_info_set_live_vars(LiveVars0, !ModeInfo),
mode_info_unlock_vars(var_lock_negation, NonLocals, !ModeInfo),
mode_info_set_instmap(InstMap0, !ModeInfo),
mode_info_get_in_promise_purity_scope(!.ModeInfo, InPromisePurityScope),
(
InPromisePurityScope = not_in_promise_purity_scope,
NegNonLocals = goal_info_get_nonlocals(GoalInfo0),
instmap.init_unreachable(Unreachable),
check_no_inst_any_vars(negation,
set_of_var.to_sorted_list(NegNonLocals),
InstMap0, Unreachable, !ModeInfo)
;
InPromisePurityScope = in_promise_purity_scope
),
GoalExpr = negation(SubGoal),
mode_info_set_pred_var_multimode_error_map(MultiModeErrorMap0, !ModeInfo),
mode_checkpoint(exit, "not", GoalInfo0, !ModeInfo).
%---------------------------------------------------------------------------%
%
% Modecheck scope goals.
%
:- pred modecheck_goal_scope(scope_reason::in, hlds_goal::in,
hlds_goal_info::in, hlds_goal_expr::out,
mode_info::in, mode_info::out) is det.
modecheck_goal_scope(Reason, SubGoal0, GoalInfo0, GoalExpr, !ModeInfo) :-
(
Reason = trace_goal(_, _, _, _, _),
mode_checkpoint(enter, "trace scope", GoalInfo0, !ModeInfo),
mode_info_get_var_table(!.ModeInfo, VarTable),
mode_info_get_instmap(!.ModeInfo, InstMap0),
NonLocals = goal_info_get_nonlocals(GoalInfo0),
set_of_var.to_sorted_list(NonLocals, NonLocalVars),
mode_info_get_module_info(!.ModeInfo, ModuleInfo),
% Testing here whether all the nonlocals of the trace goal are ground,
% and delaying the scope if some are not, is a workaround for a bug.
% The test case for the bug is tests/warnings/moved_trace_goal.m.
%
% The relevant predicate in that test starts with a trace goal
% which prints the values of A, B and C, with A and B being ground,
% but C is not. The compiler used to move this trace goal not just
% after the next if-then-else (which grounds C), but also after
% all the rest of the predicate's code. It did this because the
% compiler's code handling delayed goals recorded, as the set of
% variables that needed to be available for the trace goal to be
% scheduled, not {C}, but {STATE_VARIABLE_IO_10}.
%
% The history behind this is that goal_expr_to_goal.m wraps calls to
% unsafe_{get,set}_io_state around the body of the trace goal.
% So the trace goal is a conjunction whose second conjunct,
% the actual trace goal, is waiting on C, and whose third conjunct,
% the call to unsafe_set_io_state, needs the I/O state at the
% end of the trace goal.
%
% Bug 1 is that delay_info.m constructs the set of variables that
% the conjunction, and therefore the trace goal, is waiting on
% based on the needs of just the last disjunct.
%
% Bug 2 is that it does not notice that this I/O state variable
% is *local* to the trace goal, which means that it can never be
% even mentioned, much less produced, by any goal outside it.
%
% The right way to fix this problem would be to fix both those bugs
% in delay_info.m. Nevertheless, I (zs) have chosen to work around
% them here instead, because
%
% - the code of delay_info.m is not documented anywhere well enough
% for me to be confident in the correctness of any modification
% I could make, while
%
% - that opaque code has nevertheless worked well for over thirty
% years.
%
% A note about the test here: requiring all NonLocalVars to be ground
% is a sufficient condition for the workaround, but not a necessary
% one. The condition we really want is "does SubGoal0 bind any part
% of any of NonLocalVars?". However, we cannot answer that question
% without doing mode analysis on SubGoal0, which is what we want
% to avoid if the condition fails. Requiring groundness cuts the
% Gordian knot of this circularity.
VarIsNonGround =
( pred(Var::in) is semidet :-
lookup_var_type(VarTable, Var, VarType),
instmap_lookup_var(InstMap0, Var, VarInst),
not inst_is_ground(ModuleInfo, VarType, VarInst)
),
list.filter(VarIsNonGround, NonLocalVars, NonGroundNonLocalVars),
(
NonGroundNonLocalVars = [],
% We need to lock the non-local variables, to ensure that
% the trace goal does not bind them. If it did, then the code
% would not be valid with the trace goal disabled.
%
% XXX Now that we know all nonlocals are ground, this locking
% should be unnecessary, but
%
% - this code has worked for a long time;
% - I (zs) am only 99% sure it would work without the locks,
% not 100%, and
% - the negligible slowdown caused by the locks is good insurance
% against possible future bug reports from that 1%.
mode_info_lock_vars(var_lock_trace_goal, NonLocals, !ModeInfo),
modecheck_goal(SubGoal0, SubGoal, !ModeInfo),
mode_info_unlock_vars(var_lock_trace_goal, NonLocals, !ModeInfo),
mode_info_set_instmap(InstMap0, !ModeInfo)
;
NonGroundNonLocalVars = [HeadNGVar | TailNGVars],
Error = mode_error_nonground_trace_goal(HeadNGVar, TailNGVars),
% We add this error NOT because we expect it to be printed;
% it won't be. We add it because it will force modecheck_goal_conj
% to delay this trace goal until we know the values of
% NonGroundNonLocalVars.
%
% When we successfully reschedule this goal, we will take the
% NonGroundNonLocalVars = [] switch arm. This is why the warnings
% about trace goals being moved are generated not here during
% mode analysis (when we do not yet know the final schedule
% of all the goals in the procedure body) but during the
% simplification pass (when we do know that schedule).
mode_info_error(set_of_var.list_to_set(NonGroundNonLocalVars),
Error, !ModeInfo),
SubGoal = SubGoal0
),
GoalExpr = scope(Reason, SubGoal),
mode_checkpoint(exit, "trace scope", GoalInfo0, !ModeInfo)
;
(
( Reason = exist_quant(_, _)
; Reason = disable_warnings(_, _)
; Reason = promise_solutions(_, _)
; Reason = require_detism(_)
; Reason = require_complete_switch(_)
; Reason = require_switch_arms_detism(_, _)
; Reason = commit(_)
; Reason = barrier(_)
)
;
Reason = loop_control(LCVar, LCSVar, _),
% We check that the variables for the loop control are ground.
mode_info_get_instmap(!.ModeInfo, InstMap),
mode_info_get_module_info(!.ModeInfo, ModuleInfo),
mode_info_get_var_table(!.ModeInfo, VarTable),
expect(
var_is_ground_in_instmap(ModuleInfo, VarTable, InstMap, LCVar),
$pred, "Loop control variable is not ground"),
expect(
var_is_ground_in_instmap(ModuleInfo, VarTable, InstMap,
LCSVar),
$pred, "Loop control slot variable is not ground")
),
mode_checkpoint(enter, "scope", GoalInfo0, !ModeInfo),
modecheck_goal(SubGoal0, SubGoal, !ModeInfo),
GoalExpr = scope(Reason, SubGoal),
mode_checkpoint(exit, "scope", GoalInfo0, !ModeInfo)
;
Reason = from_ground_term(TermVar, OldKind),
(
OldKind = from_ground_term_construct,
mode_info_var_is_live(!.ModeInfo, TermVar, IsLive),
(
IsLive = is_live,
% We have already modechecked the subgoal. If we had done
% anything to it that could invalidate its invariants,
% the part of the compiler that did this should have also
% updated the scope reason.
GoalExpr = scope(Reason, SubGoal0),
InstMapDelta0 = goal_info_get_instmap_delta(GoalInfo0),
instmap_delta_lookup_var(InstMapDelta0, TermVar, TermVarInst),
mode_info_get_instmap(!.ModeInfo, InstMap0),
instmap_set_var(TermVar, TermVarInst, InstMap0, InstMap),
mode_info_set_instmap(InstMap, !ModeInfo)
;
IsLive = is_dead,
% We delete construction unifications that construct dead
% variables; do the same with construct scopes.
GoalExpr = conj(plain_conj, [])
)
;
( OldKind = from_ground_term_initial
; OldKind = from_ground_term_deconstruct
; OldKind = from_ground_term_other
),
mode_checkpoint(enter, "from_ground_term scope",
GoalInfo0, !ModeInfo),
modecheck_goal_from_ground_term_scope(TermVar, SubGoal0, GoalInfo0,
MaybeKind1AndSubGoal1, !ModeInfo),
mode_checkpoint(exit, "from_ground_term scope",
GoalInfo0, !ModeInfo),
(
MaybeKind1AndSubGoal1 = yes(Kind1 - SubGoal1),
expect_not(unify(Kind1, from_ground_term_initial), $pred,
"from_ground_term_initial"),
mode_info_set_had_from_ground_term(had_from_ground_term_scope,
!ModeInfo),
mode_info_get_make_ground_terms_unique(!.ModeInfo,
MakeGroundTermsUnique),
(
MakeGroundTermsUnique = do_not_make_ground_terms_unique,
UpdatedReason1 = from_ground_term(TermVar, Kind1),
GoalExpr = scope(UpdatedReason1, SubGoal1)
;
MakeGroundTermsUnique = make_ground_terms_unique,
(
Kind1 = from_ground_term_initial,
unexpected($pred, "from_ground_term_initial")
;
Kind1 = from_ground_term_construct,
modecheck_goal_make_ground_term_unique(TermVar,
SubGoal1, GoalInfo0, GoalExpr, !ModeInfo)
;
( Kind1 = from_ground_term_deconstruct
; Kind1 = from_ground_term_other
),
UpdatedReason1 = from_ground_term(TermVar, Kind1),
GoalExpr = scope(UpdatedReason1, SubGoal1)
)
)
;
MaybeKind1AndSubGoal1 = no,
GoalExpr = conj(plain_conj, [])
)
)
;
Reason = promise_purity(_Purity),
mode_info_get_in_promise_purity_scope(!.ModeInfo, InPPScope),
mode_info_set_in_promise_purity_scope(in_promise_purity_scope,
!ModeInfo),
mode_checkpoint(enter, "promise_purity scope", GoalInfo0, !ModeInfo),
modecheck_goal(SubGoal0, SubGoal, !ModeInfo),
GoalExpr = scope(Reason, SubGoal),
mode_checkpoint(exit, "promise_purity scope", GoalInfo0, !ModeInfo),
mode_info_set_in_promise_purity_scope(InPPScope, !ModeInfo)
).
% This predicate transforms
%
% scope(TermVar,
% conj(plain_conj,
% X1 = ...
% X2 = ...
% ...
% TermVar = ...
% )
% )
%
% into
%
% conj(plain_conj,
% scope(CloneVar,
% conj(plain_conj,
% X1 = ...
% X2 = ...
% ...
% CloneVar = ...
% )
% ),
% builtin.copy(CloneVar, TermVar)
% )
%
% We could transform it instead into a plain conjunction that directly
% builds a unique term, but that could have a significant detrimental
% effect on compile time.
%
% The performance of the generated code is unlikely to be of too much
% importance, since we expect programs will rarely need a unique copy
% of a ground term.
%
:- pred modecheck_goal_make_ground_term_unique(prog_var::in,
hlds_goal::in, hlds_goal_info::in, hlds_goal_expr::out,
mode_info::in, mode_info::out) is det.
modecheck_goal_make_ground_term_unique(TermVar, SubGoal0, GoalInfo0, GoalExpr,
!ModeInfo) :-
mode_info_get_var_table(!.ModeInfo, VarTable0),
lookup_var_entry(VarTable0, TermVar, TermVarEntry),
CloneVarEntry = TermVarEntry ^ vte_name := "",
add_var_entry(CloneVarEntry, CloneVar, VarTable0, VarTable),
mode_info_set_var_table(VarTable, !ModeInfo),
Rename = map.singleton(TermVar, CloneVar),
% By construction, TermVar can appear only in (a) SubGoal0's goal_info,
% and (b) in the last conjunct in SubGoal0's goal_expr; it cannot appear
% in any of the other conjuncts. We could make this code more efficient
% by exploiting this fact, but there is not yet any evidence of any need
% for this.
rename_some_vars_in_goal(Rename, SubGoal0, SubGoal),
rename_vars_in_goal_info(need_not_rename, Rename, GoalInfo0,
ScopeGoalInfo1),
% We must put the instmaps into the goal_infos of all the subgoals of the
% final GoalExpr we return, since modecheck_goal will not get a chance to
% do so.
mode_info_get_instmap(!.ModeInfo, InstMap0),
instmap_lookup_var(InstMap0, TermVar, TermVarOldInst),
ScopeInstMapDelta =
instmap_delta_from_assoc_list([CloneVar - TermVarOldInst]),
goal_info_set_instmap_delta(ScopeInstMapDelta,
ScopeGoalInfo1, ScopeGoalInfo),
Reason = from_ground_term(CloneVar, from_ground_term_construct),
ScopeGoalExpr = scope(Reason, SubGoal),
ScopeGoal = hlds_goal(ScopeGoalExpr, ScopeGoalInfo),
% We could get a more accurate new inst for TermVar by replacing
% all the "shared" functors in TermVarOldInst with "unique".
% However, this should be good enough. XXX wangp, is this right?
TermVarUniqueInst = ground(unique, none_or_default_func),
instmap_set_var(CloneVar, TermVarOldInst, InstMap0, InstMap1),
mode_info_set_instmap(InstMap1, !ModeInfo),
Context = goal_info_get_context(GoalInfo0),
TermVarType = TermVarEntry ^ vte_type,
modecheck_make_type_info_var_for_type(TermVarType, Context, TypeInfoVar,
TypeInfoGoals, !ModeInfo),
InstMapDelta =
instmap_delta_from_assoc_list([TermVar - TermVarUniqueInst]),
mode_info_get_module_info(!.ModeInfo, ModuleInfo),
generate_plain_call(ModuleInfo, pf_predicate,
mercury_public_builtin_module, "copy",
[TypeInfoVar], [CloneVar, TermVar], InstMapDelta,
mode_no(1), detism_det, purity_pure, [], Context, CopyGoal),
mode_info_get_instmap(!.ModeInfo, InstMap2),
instmap_set_var(TermVar, TermVarUniqueInst, InstMap2, InstMap),
mode_info_set_instmap(InstMap, !ModeInfo),
GoalExpr = conj(plain_conj, [ScopeGoal | TypeInfoGoals] ++ [CopyGoal]).
:- pred modecheck_make_type_info_var_for_type(mer_type::in, prog_context::in,
prog_var::out, list(hlds_goal)::out, mode_info::in, mode_info::out) is det.
modecheck_make_type_info_var_for_type(Type, Context, TypeInfoVar,
TypeInfoGoals, !ModeInfo) :-
mode_info_get_module_info(!.ModeInfo, ModuleInfo0),
% Get the relevant information for the current procedure.
mode_info_get_pred_id(!.ModeInfo, PredId),
mode_info_get_proc_id(!.ModeInfo, ProcId),
module_info_pred_proc_info(ModuleInfo0, PredId, ProcId, PredInfo0,
ProcInfo0),
% Create a poly_info for the current procedure. We have to set the
% var_table from the mode_info, not the proc_info, because new vars may
% have been introduced during mode analysis, e.g. when adding
% unifications to handle implied modes.
mode_info_get_var_table(!.ModeInfo, VarTable0),
proc_info_set_var_table(VarTable0, ProcInfo0, ProcInfo1),
polymorphism_make_type_info_var_mi(Type, Context,
TypeInfoVar, TypeInfoGoals, ModuleInfo0, ModuleInfo1,
PredInfo0, PredInfo, ProcInfo1, ProcInfo),
module_info_set_pred_proc_info(PredId, ProcId, PredInfo, ProcInfo,
ModuleInfo1, ModuleInfo),
% Update the information in the mode_info.
proc_info_get_var_table(ProcInfo, VarTable),
mode_info_set_var_table(VarTable, !ModeInfo),
mode_info_set_module_info(ModuleInfo, !ModeInfo).
:- pred modecheck_goal_from_ground_term_scope(prog_var::in,
hlds_goal::in, hlds_goal_info::in,
maybe(pair(from_ground_term_kind, hlds_goal))::out,
mode_info::in, mode_info::out) is det.
modecheck_goal_from_ground_term_scope(TermVar, SubGoal0, GoalInfo0,
MaybeKindAndSubGoal, !ModeInfo) :-
% The original goal does no quantification, so deleting the `scope'
% would be OK. However, deleting it during mode analysis would mean
% we don't have it during unique mode analysis and other later compiler
% passes.
mode_info_get_instmap(!.ModeInfo, InstMap0),
instmap_lookup_var(InstMap0, TermVar, TermVarInst),
modecheck_specializable_ground_term(SubGoal0, TermVar, TermVarInst,
MaybeGroundTermMode),
(
MaybeGroundTermMode = yes(construct_ground_term(RevConj0)),
mode_info_var_is_live(!.ModeInfo, TermVar, LiveTermVar),
(
LiveTermVar = is_live,
SubGoal0 = hlds_goal(_, SubGoalInfo0),
modecheck_ground_term_construct(TermVar, RevConj0,
SubGoalInfo0, SubGoal, !ModeInfo),
Kind = from_ground_term_construct,
MaybeKindAndSubGoal = yes(Kind - SubGoal)
;
LiveTermVar = is_dead,
% The term constructed by the scope is not used anywhere.
MaybeKindAndSubGoal = no
)
;
(
MaybeGroundTermMode = yes(deconstruct_ground_term(_)),
% We should specialize the handling of these scopes as well as
% scopes that construct ground terms, but we don't yet have
% a compelling motivating example.
SubGoal1 = SubGoal0,
Kind = from_ground_term_deconstruct
;
MaybeGroundTermMode = no,
( if
TermVarInst = free,
SubGoal0 = hlds_goal(SubGoalExpr0, SubGoalInfo0),
SubGoalExpr0 = conj(plain_conj, SubGoalConjuncts0)
then
% We reverse the list here for the same reason
% modecheck_specializable_ground_term does in the
% corresponding case.
list.reverse(SubGoalConjuncts0, SubGoalConjuncts1),
SubGoalExpr1 = conj(plain_conj, SubGoalConjuncts1),
SubGoal1 = hlds_goal(SubGoalExpr1, SubGoalInfo0)
else
SubGoal1 = SubGoal0
),
Kind = from_ground_term_other
),
( if goal_info_has_feature(GoalInfo0, feature_from_head) then
attach_features_to_all_goals([feature_from_head],
attach_in_from_ground_term, SubGoal1, SubGoal2)
else
SubGoal2 = SubGoal1
),
mode_checkpoint(enter, "fgt scope", GoalInfo0, !ModeInfo),
modecheck_goal(SubGoal2, SubGoal, !ModeInfo),
mode_checkpoint(exit, "fgt scope", GoalInfo0, !ModeInfo),
MaybeKindAndSubGoal = yes(Kind - SubGoal)
).
:- type ground_term_mode
---> construct_ground_term(list(hlds_goal))
; deconstruct_ground_term(list(hlds_goal)).
:- pred modecheck_specializable_ground_term(hlds_goal::in, prog_var::in,
mer_inst::in, maybe(ground_term_mode)::out) is det.
modecheck_specializable_ground_term(SubGoal, TermVar, TermVarInst,
MaybeGroundTermMode) :-
SubGoal = hlds_goal(SubGoalExpr, SubGoalInfo),
( if
NonLocals = goal_info_get_nonlocals(SubGoalInfo),
set_of_var.is_singleton(NonLocals, TermVar),
goal_info_get_purity(SubGoalInfo) = purity_pure,
SubGoalExpr = conj(plain_conj, [UnifyTermGoal | UnifyArgGoals]),
% If TermVar is created by an impure unification, which is
% possible for solver types, it is possible for UnifyTermGoal
% to contain a unification other than one involving TermVar.
UnifyTermGoal ^ hg_expr = unify(TermVar, _, _, _, _),
all_plain_functor_unifies([UnifyTermGoal | UnifyArgGoals])
then
( if TermVarInst = free then
% UnifyTermGoal unifies TermVar with the arguments created
% by UnifyArgGoals. Since TermVar is now free and the
% argument variables haven't been encountered yet,
% UnifyTermGoal cannot succeed until *after* the argument
% variables become ground.
%
% Putting UnifyTermGoal after UnifyArgGoals here is MUCH faster
% than letting the usual more ordering algorithm delay it
% repeatedly: it is linear instead of quadratic.
list.reverse([UnifyTermGoal | UnifyArgGoals], RevConj),
MaybeGroundTermMode = yes(construct_ground_term(RevConj))
else if TermVarInst = ground(shared, none_or_default_func) then
Conj = [UnifyTermGoal | UnifyArgGoals],
MaybeGroundTermMode = yes(deconstruct_ground_term(Conj))
else
MaybeGroundTermMode = no
)
else
MaybeGroundTermMode = no
).
:- pred all_plain_functor_unifies(list(hlds_goal)::in) is semidet.
all_plain_functor_unifies([]).
all_plain_functor_unifies([Goal | Goals]) :-
Goal = hlds_goal(GoalExpr, _),
GoalExpr = unify(_LHSVar, RHS, _, _, _),
RHS = rhs_functor(_ConsId, is_not_exist_constr, _RHSVars),
all_plain_functor_unifies(Goals).
:- pred modecheck_ground_term_construct(prog_var::in, list(hlds_goal)::in,
hlds_goal_info::in, hlds_goal::out,
mode_info::in, mode_info::out) is det.
modecheck_ground_term_construct(TermVar, ConjGoals0, !.SubGoalInfo,
SubGoal, !ModeInfo) :-
map.init(LocalVarMap0),
modecheck_ground_term_construct_goal_loop(ConjGoals0, ConjGoals,
LocalVarMap0, LocalVarMap),
map.lookup(LocalVarMap, TermVar, TermVarInfo),
TermVarInfo = construct_var_info(TermVarInst),
InstMapDelta = instmap_delta_from_assoc_list([TermVar - TermVarInst]),
goal_info_set_instmap_delta(InstMapDelta, !SubGoalInfo),
% We present the determinism, so that the determinism analysis pass
% does not have to traverse the goals inside the scope.
goal_info_set_determinism(detism_det, !SubGoalInfo),
SubGoalExpr = conj(plain_conj, ConjGoals),
SubGoal = hlds_goal(SubGoalExpr, !.SubGoalInfo),
mode_info_get_instmap(!.ModeInfo, InstMap0),
instmap_set_var(TermVar, TermVarInst, InstMap0, InstMap),
mode_info_set_instmap(InstMap, !ModeInfo).
:- type construct_var_info
---> construct_var_info(mer_inst).
:- type construct_var_info_map == map(prog_var, construct_var_info).
:- pred modecheck_ground_term_construct_goal_loop(
list(hlds_goal)::in, list(hlds_goal)::out,
construct_var_info_map::in, construct_var_info_map::out) is det.
modecheck_ground_term_construct_goal_loop([], [], !LocalVarMap).
modecheck_ground_term_construct_goal_loop([Goal0 | Goals0], [Goal | Goals],
!LocalVarMap) :-
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
( if
GoalExpr0 = unify(LHSVar, RHS, _, _, UnifyContext),
RHS = rhs_functor(ConsId, is_not_exist_constr, RHSVars)
then
% We could set TermInst to simply to ground, as opposed to the inst
% we now use which gives information about LHSVar's shape. This would
% remove the need for the inst information in !LocalVarMap, and
% would make HLDS dumps linear in the size of the term instead of
% quadratic. However, due to structure sharing, the actual memory
% requirements of these bound insts are only linear in the size of the
% term.
modecheck_ground_term_construct_arg_loop(RHSVars, ArgInsts, ArgModes,
!LocalVarMap),
BoundInst = bound_functor(ConsId, ArgInsts),
TermInst = bound(shared, inst_test_results_fgtc, [BoundInst]),
UnifyMode = unify_modes_li_lf_ri_rf(free, TermInst,
TermInst, TermInst),
ConstructHow = construct_statically(born_static),
Uniqueness = cell_is_shared,
Unification = construct(LHSVar, ConsId, RHSVars, ArgModes,
ConstructHow, Uniqueness, no_construct_sub_info),
GoalExpr = unify(LHSVar, RHS, UnifyMode, Unification, UnifyContext),
InstMapDelta = instmap_delta_from_assoc_list([LHSVar - TermInst]),
goal_info_set_instmap_delta(InstMapDelta, GoalInfo0, GoalInfo1),
% We preset the determinism, so that the determinism analysis pass
% does not have to traverse the goals inside the scope.
goal_info_set_determinism(detism_det, GoalInfo1, GoalInfo),
Goal = hlds_goal(GoalExpr, GoalInfo),
LHSVarInfo = construct_var_info(TermInst),
map.det_insert(LHSVar, LHSVarInfo, !LocalVarMap)
else
unexpected($pred, "not rhs_functor unify")
),
modecheck_ground_term_construct_goal_loop(Goals0, Goals, !LocalVarMap).
:- pred modecheck_ground_term_construct_arg_loop(list(prog_var)::in,
list(mer_inst)::out, list(unify_mode)::out,
construct_var_info_map::in, construct_var_info_map::out) is det.
modecheck_ground_term_construct_arg_loop([], [], [], !LocalVarMap).
modecheck_ground_term_construct_arg_loop([Var | Vars], [VarInst | VarInsts],
[ArgMode | ArgModes], !LocalVarMap) :-
% Each variable introduced by the superhomogeneous transformation
% for a ground term appears in the from_ground_term scope exactly twice.
% Once when it is produced (which is handled in the goal loop predicate),
% and once when it is consumed, which is handled here.
%
% Since there will be no more appearances of this variable, we remove it
% from LocalVarMap. This greatly reduces the size of LocalVarMap.
map.det_remove(Var, VarInfo, !LocalVarMap),
VarInfo = construct_var_info(VarInst),
ArgMode = unify_modes_li_lf_ri_rf(free, VarInst, VarInst, VarInst),
modecheck_ground_term_construct_arg_loop(Vars, VarInsts, ArgModes,
!LocalVarMap).
%---------------------------------------------------------------------------%
%
% Modecheck shorthand goals.
%
:- pred modecheck_goal_shorthand(shorthand_goal_expr::in, hlds_goal_info::in,
hlds_goal_expr::out, mode_info::in, mode_info::out) is det.
modecheck_goal_shorthand(ShortHand0, GoalInfo0, GoalExpr, !ModeInfo) :-
(
ShortHand0 = atomic_goal(_, Outer, Inner, MaybeOutputVars,
MainGoal0, OrElseGoals0, OrElseInners),
% The uniqueness of the Outer and Inner variables are handled by the
% addition of calls to the fake predicates "stm_inner_to_outer_io" and
% "stm_outer_to_inner_io" during the construction of the HLDS.
% These calls are removed when atomic goals are expanded.
mode_checkpoint(enter, "atomic", GoalInfo0, !ModeInfo),
mode_info_get_pred_var_multimode_error_map(!.ModeInfo,
MultiModeErrorMap0),
AtomicGoalList0 = [MainGoal0 | OrElseGoals0],
NonLocals = goal_info_get_nonlocals(GoalInfo0),
% XXX STM: Locking the outer variables would generate an error message
% during mode analysis of the sub goal because of the calls to
% "stm_outer_to_inner_io" and "stm_inner_to_outer_io". I (lmika) don't
% think this is a problem as the uniqueness states of the outer and
% inner variables are enforced by these calls anyway.
% mode_info_lock_vars(var_lock_atomic_goal, OuterVars, !ModeInfo),
modecheck_orelse_list(MultiModeErrorMap0, AtomicGoalList0,
AtomicGoalList, InstMapList, !ModeInfo),
mode_info_get_var_table(!.ModeInfo, VarTable),
% mode_info_unlock_vars(var_lock_atomic_goal, OuterVars, !ModeInfo),
MainGoal = list.det_head(AtomicGoalList),
OrElseGoals = list.det_tail(AtomicGoalList),
make_arm_instmaps_for_goals(AtomicGoalList, InstMapList, ArmInstMaps),
instmap_merge(NonLocals, ArmInstMaps, merge_stm_atomic, !ModeInfo),
% Here we determine the type of atomic goal this is. It could be argued
% that this should have been done in the typechecker, but the type of
% the outer variables could be unknown when the typechecker looks
% at the atomic goal.
%
% To prevent the need to traverse the code again, we will put this
% check here (also: types of variables must be known at this point).
Outer = atomic_interface_vars(OuterDI, OuterUO),
lookup_var_type(VarTable, OuterDI, OuterDIType),
lookup_var_type(VarTable, OuterUO, OuterUOType),
( if
( OuterDIType = io_state_type
; OuterDIType = io_io_type
)
then
GoalType = top_level_atomic_goal
else if
OuterDIType = stm_atomic_type
then
GoalType = nested_atomic_goal
else
unexpected($pred, "atomic_goal: invalid outer var type")
),
% The following are sanity checks.
expect(unify(OuterDIType, OuterUOType), $pred,
"atomic_goal: mismatched outer var type"),
Inner = atomic_interface_vars(InnerDI, InnerUO),
lookup_var_type(VarTable, InnerDI, InnerDIType),
lookup_var_type(VarTable, InnerUO, InnerUOType),
expect(unify(InnerDIType, stm_atomic_type), $pred,
"atomic_goal: invalid inner var type"),
expect(unify(InnerUOType, stm_atomic_type), $pred,
"atomic_goal: invalid inner var type"),
ShortHand = atomic_goal(GoalType, Outer, Inner, MaybeOutputVars,
MainGoal, OrElseGoals, OrElseInners),
GoalExpr = shorthand(ShortHand),
mode_checkpoint(exit, "atomic", GoalInfo0, !ModeInfo)
;
ShortHand0 = try_goal(MaybeIO, ResultVar, SubGoal0),
mode_checkpoint(enter, "try", GoalInfo0, !ModeInfo),
mode_info_get_pred_var_multimode_error_map(!.ModeInfo,
MultiModeErrorMap0),
modecheck_goal(SubGoal0, SubGoal, !ModeInfo),
mode_info_set_pred_var_multimode_error_map(MultiModeErrorMap0,
!ModeInfo),
ShortHand = try_goal(MaybeIO, ResultVar, SubGoal),
GoalExpr = shorthand(ShortHand),
mode_checkpoint(exit, "try", GoalInfo0, !ModeInfo)
;
ShortHand0 = bi_implication(_, _),
% These should have been expanded out by now.
unexpected($pred, "bi_implication")
).
:- pred modecheck_orelse_list(pred_var_multimode_error_map::in,
list(hlds_goal)::in, list(hlds_goal)::out, list(instmap)::out,
mode_info::in, mode_info::out) is det.
modecheck_orelse_list(_, [], [], [], !ModeInfo).
modecheck_orelse_list(MultiModeErrorMap0, [Goal0 | Goals0], [Goal | Goals],
[InstMap | InstMaps], !ModeInfo) :-
mode_info_set_pred_var_multimode_error_map(MultiModeErrorMap0, !ModeInfo),
mode_info_get_instmap(!.ModeInfo, InstMap0),
modecheck_goal(Goal0, Goal, !ModeInfo),
mode_info_get_instmap(!.ModeInfo, InstMap),
mode_info_set_instmap(InstMap0, !ModeInfo),
modecheck_orelse_list(MultiModeErrorMap0, Goals0, Goals,
InstMaps, !ModeInfo).
%---------------------------------------------------------------------------%
%
% Service predicates dealing with solver variables.
%
% If the condition of a negation or if-then-else contains any inst any
% non-locals (a potential referential transparency violation), then
% we need to check that the programmer has recognised the possibility
% and placed the if-then-else in a promise_<purity> scope.
%
:- pred check_no_inst_any_vars(negated_context_desc::in, list(prog_var)::in,
instmap::in, instmap::in, mode_info::in, mode_info::out) is det.
check_no_inst_any_vars(_, [], _, _, !ModeInfo).
check_no_inst_any_vars(NegCtxtDesc, [NonLocal | NonLocals], InstMap0, InstMap,
!ModeInfo) :-
( if
( instmap_lookup_var(InstMap0, NonLocal, Inst)
; instmap_lookup_var(InstMap, NonLocal, Inst)
),
mode_info_get_module_info(!.ModeInfo, ModuleInfo),
inst_contains_any(ModuleInfo, Inst)
then
WaitingVars = set_of_var.make_singleton(NonLocal),
ModeError = purity_error_should_be_in_promise_purity_scope(NegCtxtDesc,
NonLocal),
mode_info_error(WaitingVars, ModeError, !ModeInfo)
else
check_no_inst_any_vars(NegCtxtDesc, NonLocals, InstMap0, InstMap,
!ModeInfo)
).
%---------------------------------------------------------------------------%
:- end_module check_hlds.modecheck_goal.
%---------------------------------------------------------------------------%