%-----------------------------------------------------------------------------% % Copyright (C) 1996-2004 The University of Melbourne. % This file may only be copied under the terms of the GNU General % Public License - see the file COPYING in the Mercury distribution. %-----------------------------------------------------------------------------% % file: unique_modes.m % main author: fjh % This module checks that variables with a unique mode (as opposed to % a mostly-unique mode) really are unique, and not nondet live - i.e., % that they cannot be referenced on backtracking. % (Actually the term "nondet live" is a bit of a misnomer, because % really we are just interested in whether something can be referenced % on backtracking, and this can occur after backtracking in semidet % code too, not just in nondet code.) % Basically we just traverse each goal, keeping track of which variables % are nondet live. At each procedure call, we check that any arguments % whose initial insts are required to be unique are not nondet live. % If they are, we first try selecting a different mode of the same % predicate, and if that fails, then we report an error message. % Variables can become nondet live in several places: % in negations, in the conditions of if-then-elses, % in disjunctions, and at nondet calls. % These are the only places at which we can resume execution % after backtracking. % XXX we currently make the conservative assumption that % any non-local variable in a disjunction or nondet call % is nondet-live - and stays nondet-live. %-----------------------------------------------------------------------------% :- module check_hlds__unique_modes. :- interface. :- import_module check_hlds__mode_info. :- import_module hlds__hlds_goal. :- import_module hlds__hlds_module. :- import_module hlds__hlds_pred. :- import_module io, bool. % check every predicate in a module :- pred unique_modes__check_module(module_info::in, module_info::out, io::di, io::uo) is det. % just check a single procedure :- pred unique_modes__check_proc(proc_id::in, pred_id::in, module_info::in, module_info::out, bool::out, io::di, io::uo) is det. % just check a single goal :- pred unique_modes__check_goal(hlds_goal::in, hlds_goal::out, mode_info::in, mode_info::out, io::di, io::uo) is det. %-----------------------------------------------------------------------------% %-----------------------------------------------------------------------------% :- implementation. :- import_module check_hlds__inst_match. :- import_module check_hlds__inst_util. :- import_module check_hlds__mode_debug. :- import_module check_hlds__mode_errors. :- import_module check_hlds__mode_util. :- import_module check_hlds__modecheck_call. :- import_module check_hlds__modecheck_unify. :- import_module check_hlds__modes. :- import_module check_hlds__unify_proc. :- import_module hlds__hlds_data. :- import_module hlds__hlds_out. :- import_module hlds__instmap. :- import_module hlds__passes_aux. :- import_module parse_tree__mercury_to_mercury. :- import_module parse_tree__prog_data. :- import_module parse_tree__prog_mode. :- import_module parse_tree__prog_out. :- import_module term, varset. :- import_module assoc_list, bag, int, list, map. :- import_module require, set, std_util, string. %-----------------------------------------------------------------------------% unique_modes__check_module(!ModuleInfo, !IO) :- check_pred_modes(check_unique_modes, may_change_called_proc, !ModuleInfo, _UnsafeToContinue, !IO). unique_modes__check_proc(ProcId, PredId, !ModuleInfo, Changed, !IO) :- modecheck_proc(ProcId, PredId, check_unique_modes, may_change_called_proc, !ModuleInfo, NumErrors, Changed, !IO), ( NumErrors \= 0 -> io__set_exit_status(1, !IO) ; true ). % XXX we currently make the conservative assumption that % any non-local variable in a disjunction or nondet call % is nondet-live - and stays nondet-live. unique_modes__check_goal(Goal0, Goal, !ModeInfo, !IO) :- % % store the current context in the mode_info % Goal0 = GoalExpr0 - GoalInfo0, goal_info_get_context(GoalInfo0, Context), term__context_init(EmptyContext), ( Context = EmptyContext -> true ; mode_info_set_context(Context, !ModeInfo) ), % % Grab the original instmap % mode_info_get_instmap(!.ModeInfo, InstMap0), % % Grab the original bag of nondet-live vars % mode_info_get_nondet_live_vars(!.ModeInfo, NondetLiveVars0), % % If the goal is not nondet, then nothing is nondet-live, % so reset the bag of nondet-live vars to be empty. % goal_info_get_determinism(GoalInfo0, Detism), ( determinism_components(Detism, _, at_most_many) -> true ; mode_info_set_nondet_live_vars([], !ModeInfo) ), % % Modecheck the goal % unique_modes__check_goal_2(GoalExpr0, GoalInfo0, GoalExpr, !ModeInfo, !IO), % % Restore the original bag of nondet-live vars % mode_info_set_nondet_live_vars(NondetLiveVars0, !ModeInfo), % % Grab the final instmap, compute the change in insts % over this goal, and save that instmap_delta in the goal_info. % compute_goal_instmap_delta(InstMap0, GoalExpr, GoalInfo0, GoalInfo, !ModeInfo), Goal = GoalExpr - GoalInfo. % Make all nondet-live variables whose current inst % is `unique' become `mostly_unique'. % :- pred make_all_nondet_live_vars_mostly_uniq(mode_info::in, mode_info::out) is det. make_all_nondet_live_vars_mostly_uniq(ModeInfo0, ModeInfo) :- mode_info_get_instmap(ModeInfo0, FullInstMap0), ( instmap__is_reachable(FullInstMap0) -> instmap__vars_list(FullInstMap0, AllVars), select_nondet_live_vars(AllVars, ModeInfo0, NondetLiveVars), make_var_list_mostly_uniq(NondetLiveVars, ModeInfo0, ModeInfo) ; ModeInfo = ModeInfo0 ). :- pred select_live_vars(list(prog_var)::in, mode_info::in, list(prog_var)::out) is det. select_live_vars([], _, []). select_live_vars([Var|Vars], ModeInfo, LiveVars) :- ( mode_info_var_is_live(ModeInfo, Var, live) -> LiveVars = [Var | LiveVars1], select_live_vars(Vars, ModeInfo, LiveVars1) ; select_live_vars(Vars, ModeInfo, LiveVars) ). :- pred select_nondet_live_vars(list(prog_var)::in, mode_info::in, list(prog_var)::out) is det. select_nondet_live_vars([], _, []). select_nondet_live_vars([Var|Vars], ModeInfo, NondetLiveVars) :- ( mode_info_var_is_nondet_live(ModeInfo, Var, live) -> NondetLiveVars = [Var | NondetLiveVars1], select_nondet_live_vars(Vars, ModeInfo, NondetLiveVars1) ; select_nondet_live_vars(Vars, ModeInfo, NondetLiveVars) ). % Given a list of variables, a delta instmap, and a mode_info, % select all the variables whose inst changed in the delta instmap % (other than changes which just add information, % e.g. `ground -> bound(42)'.) % :- pred select_changed_inst_vars(list(prog_var)::in, instmap_delta::in, mode_info::in, list(prog_var)::out) is det. select_changed_inst_vars([], _DeltaInstMap, _ModeInfo, []). select_changed_inst_vars([Var | Vars], DeltaInstMap, ModeInfo, ChangedVars) :- mode_info_get_module_info(ModeInfo, ModuleInfo), mode_info_get_instmap(ModeInfo, InstMap0), instmap__lookup_var(InstMap0, Var, Inst0), mode_info_get_var_types(ModeInfo, VarTypes), map__lookup(VarTypes, Var, Type), ( instmap_delta_is_reachable(DeltaInstMap), instmap_delta_search_var(DeltaInstMap, Var, Inst), \+ inst_matches_final(Inst, Inst0, Type, ModuleInfo) -> ChangedVars = [Var | ChangedVars1], select_changed_inst_vars(Vars, DeltaInstMap, ModeInfo, ChangedVars1) ; select_changed_inst_vars(Vars, DeltaInstMap, ModeInfo, ChangedVars) ). :- pred make_var_list_mostly_uniq(list(prog_var)::in, mode_info::in, mode_info::out) is det. make_var_list_mostly_uniq([], !ModeInfo). make_var_list_mostly_uniq([Var | Vars], !ModeInfo) :- make_var_mostly_uniq(Var, !ModeInfo), make_var_list_mostly_uniq(Vars, !ModeInfo). :- pred make_var_mostly_uniq(prog_var::in, mode_info::in, mode_info::out) is det. make_var_mostly_uniq(Var, !ModeInfo) :- mode_info_get_instmap(!.ModeInfo, InstMap0), mode_info_get_module_info(!.ModeInfo, ModuleInfo0), ( % % only variables which are `unique' need to be changed % instmap__is_reachable(InstMap0), instmap__vars_list(InstMap0, Vars), list__member(Var, Vars), instmap__lookup_var(InstMap0, Var, Inst0), inst_expand(ModuleInfo0, Inst0, Inst1), ( Inst1 = ground(unique, _) ; Inst1 = bound(unique, _) ; Inst1 = any(unique) ) -> make_mostly_uniq_inst(Inst0, Inst, ModuleInfo0, ModuleInfo), mode_info_set_module_info(ModuleInfo, !ModeInfo), instmap__set(InstMap0, Var, Inst, InstMap), mode_info_set_instmap(InstMap, !ModeInfo) ; true ). :- pred unique_modes__check_goal_2(hlds_goal_expr::in, hlds_goal_info::in, hlds_goal_expr::out, mode_info::in, mode_info::out, io::di, io::uo) is det. unique_modes__check_goal_2(conj(List0), _GoalInfo0, conj(List), !ModeInfo, !IO) :- mode_checkpoint(enter, "conj", !ModeInfo, !IO), ( List0 = [] -> % for efficiency, optimize common case List = [] ; mode_info_add_goals_live_vars(List0, !ModeInfo), unique_modes__check_conj(List0, List, !ModeInfo, !IO) ), mode_checkpoint(exit, "conj", !ModeInfo, !IO). unique_modes__check_goal_2(par_conj(List0), GoalInfo0, par_conj(List), !ModeInfo, !IO) :- mode_checkpoint(enter, "par_conj", !ModeInfo, !IO), goal_info_get_nonlocals(GoalInfo0, NonLocals), mode_info_add_live_vars(NonLocals, !ModeInfo), % Build a multiset of the nonlocals of the conjuncts % so that we can figure out which variables must be % made shared at the start of the parallel conjunction. make_par_conj_nonlocal_multiset(List0, NonLocalsBag), unique_modes__check_par_conj(List0, NonLocalsBag, List, InstMapList, !ModeInfo, !IO), instmap__unify(NonLocals, InstMapList, !ModeInfo), mode_info_remove_live_vars(NonLocals, !ModeInfo), mode_checkpoint(exit, "par_conj", !ModeInfo, !IO). unique_modes__check_goal_2(disj(List0), GoalInfo0, disj(List), !ModeInfo, !IO) :- mode_checkpoint(enter, "disj", !ModeInfo, !IO), ( List0 = [] -> List = [], instmap__init_unreachable(InstMap), mode_info_set_instmap(InstMap, !ModeInfo) ; % % If the disjunction creates a choice point (i.e. is model_non), % then mark all the variables which are live at the % start of the disjunction and whose inst is `unique' % as instead being only `mostly_unique', since those variables % may be needed again after we backtrack to that choice point % and resume forward execution again. % % Note: for model_det or model_semi disjunctions, % we may do some "shallow" backtracking from semidet % disjuncts. But we handle that seperately for each % disjunct, in unique_modes__check_disj. % goal_info_get_nonlocals(GoalInfo0, NonLocals), goal_info_get_determinism(GoalInfo0, Determinism), % does this disjunction create a choice point? ( determinism_components(Determinism, _, at_most_many) -> mode_info_add_live_vars(NonLocals, !ModeInfo), make_all_nondet_live_vars_mostly_uniq(!ModeInfo), mode_info_remove_live_vars(NonLocals, !ModeInfo) ; true ), % % Now just modecheck each disjunct in turn, and then % merge the resulting instmaps. % unique_modes__check_disj(List0, Determinism, NonLocals, List, InstMapList, !ModeInfo, !IO), instmap__merge(NonLocals, InstMapList, disj, !ModeInfo) ), mode_checkpoint(exit, "disj", !ModeInfo, !IO). unique_modes__check_goal_2(if_then_else(Vars, Cond0, Then0, Else0), GoalInfo0, Goal, !ModeInfo, !IO) :- mode_checkpoint(enter, "if-then-else", !ModeInfo, !IO), goal_info_get_nonlocals(GoalInfo0, NonLocals), goal_get_nonlocals(Cond0, Cond_Vars), goal_get_nonlocals(Then0, Then_Vars), goal_get_nonlocals(Else0, Else_Vars), mode_info_get_instmap(!.ModeInfo, InstMap0), mode_info_lock_vars(if_then_else, NonLocals, !ModeInfo), % % At this point, we should set the inst of any `unique' % variables which occur in the condition and which % are live to `mostly_unique'. However, if a variable's % inst was unchanged over the condition (i.e. it remains % `unique' on exit from the condition), then it is % safe to leave it as `unique' on entry to the condition. % The only case we need to set it to `mostly_unique' is % if the condition would clobber it. % % XXX actually that is not true; the code below does % the wrong thing for examples such as this one: % % :- mode p(di). % p(Var) :- % (if % (if semidet_succeed then % clobber(Var), fail % else % true % ) % then % blah % else % use(Var) % ). % mode_info_add_live_vars(Else_Vars, !ModeInfo), set__to_sorted_list(Cond_Vars, Cond_Vars_List), select_live_vars(Cond_Vars_List, !.ModeInfo, Cond_Live_Vars), Cond0 = _ - Cond0_GoalInfo, goal_info_get_instmap_delta(Cond0_GoalInfo, Cond0_DeltaInstMap), select_changed_inst_vars(Cond_Live_Vars, Cond0_DeltaInstMap, !.ModeInfo, ChangedVars), make_var_list_mostly_uniq(ChangedVars, !ModeInfo), mode_info_remove_live_vars(Else_Vars, !ModeInfo), mode_info_add_live_vars(Then_Vars, !ModeInfo), unique_modes__check_goal(Cond0, Cond, !ModeInfo, !IO), mode_info_remove_live_vars(Then_Vars, !ModeInfo), mode_info_unlock_vars(if_then_else, NonLocals, !ModeInfo), mode_info_get_instmap(!.ModeInfo, InstMapCond), ( instmap__is_reachable(InstMapCond) -> unique_modes__check_goal(Then0, Then, !ModeInfo, !IO), mode_info_get_instmap(!.ModeInfo, InstMapThen) ; % 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 unique mode information. true_goal(Then), InstMapThen = InstMapCond ), mode_info_set_instmap(InstMap0, !ModeInfo), unique_modes__check_goal(Else0, Else, !ModeInfo, !IO), mode_info_get_instmap(!.ModeInfo, InstMapElse), mode_info_set_instmap(InstMap0, !ModeInfo), instmap__merge(NonLocals, [InstMapThen, InstMapElse], if_then_else, !ModeInfo), Goal = if_then_else(Vars, Cond, Then, Else), mode_checkpoint(exit, "if-then-else", !ModeInfo, !IO). unique_modes__check_goal_2(not(SubGoal0), GoalInfo0, not(SubGoal), !ModeInfo, !IO) :- mode_checkpoint(enter, "not", !ModeInfo, !IO), mode_info_get_instmap(!.ModeInfo, InstMap0), % % We need to mark all the variables which are live % after the negation as nondet-live for the negated % goal, since if the negated goal fails, then the % negation will succeed, and so these variables % can be accessed again after backtracking. % goal_info_get_nonlocals(GoalInfo0, NonLocals), set__to_sorted_list(NonLocals, NonLocalsList), select_live_vars(NonLocalsList, !.ModeInfo, LiveNonLocals), make_var_list_mostly_uniq(LiveNonLocals, !ModeInfo), % % But nothing is forward-live for the negated goal, since % if the goal succeeds then execution will immediately backtrack. % So we need to set the live variables set to empty here. % mode_info_get_live_vars(!.ModeInfo, LiveVars0), mode_info_set_live_vars([], !ModeInfo), % % We need to lock the non-local variables, to ensure % that the negation does not bind them. % mode_info_lock_vars(negation, NonLocals, !ModeInfo), unique_modes__check_goal(SubGoal0, SubGoal, !ModeInfo, !IO), mode_info_unlock_vars(negation, NonLocals, !ModeInfo), mode_info_set_live_vars(LiveVars0, !ModeInfo), mode_info_set_instmap(InstMap0, !ModeInfo), mode_checkpoint(exit, "not", !ModeInfo, !IO). unique_modes__check_goal_2(some(Vars, CanRemove, SubGoal0), _, some(Vars, CanRemove, SubGoal), !ModeInfo, !IO) :- mode_checkpoint(enter, "some", !ModeInfo, !IO), unique_modes__check_goal(SubGoal0, SubGoal, !ModeInfo, !IO), mode_checkpoint(exit, "some", !ModeInfo, !IO). unique_modes__check_goal_2(generic_call(GenericCall, Args, Modes, Det), _GoalInfo0, Goal, !ModeInfo, !IO) :- mode_checkpoint(enter, "generic_call", !ModeInfo, !IO), hlds_goal__generic_call_id(GenericCall, CallId), mode_info_set_call_context(call(CallId), !ModeInfo), ( determinism_components(Det, _, at_most_zero) -> NeverSucceeds = yes ; NeverSucceeds = no ), ( GenericCall = higher_order(_, _, _, _), ArgOffset = 1 ; % Class method calls are introduced by the compiler % and should be mode correct. GenericCall = class_method(_, _, _, _), ArgOffset = 0 ; % Casts are introduced by the compiler % and should be mode correct. GenericCall = unsafe_cast, ArgOffset = 0 ; GenericCall = aditi_builtin(_, _), ArgOffset = 0 ), unique_modes__check_call_modes(Args, Modes, ArgOffset, Det, NeverSucceeds, !ModeInfo), Goal = generic_call(GenericCall, Args, Modes, Det), mode_info_unset_call_context(!ModeInfo), mode_checkpoint(exit, "generic_call", !ModeInfo, !IO). unique_modes__check_goal_2(call(PredId, ProcId0, Args, Builtin, CallContext, PredName), _GoalInfo0, Goal, !ModeInfo, !IO) :- prog_out__sym_name_to_string(PredName, PredNameString), string__append("call ", PredNameString, CallString), mode_checkpoint(enter, CallString, !ModeInfo, !IO), mode_info_get_call_id(!.ModeInfo, PredId, CallId), mode_info_set_call_context(call(call(CallId)), !ModeInfo), unique_modes__check_call(PredId, ProcId0, Args, ProcId, !ModeInfo), Goal = call(PredId, ProcId, Args, Builtin, CallContext, PredName), mode_info_unset_call_context(!ModeInfo), mode_checkpoint(exit, "call", !ModeInfo, !IO). unique_modes__check_goal_2(unify(LHS0, RHS0, _, UnifyInfo0, UnifyContext), GoalInfo0, Goal, !ModeInfo, !IO) :- mode_checkpoint(enter, "unify", !ModeInfo, !IO), mode_info_set_call_context(unify(UnifyContext), !ModeInfo), modecheck_unification(LHS0, RHS0, UnifyInfo0, UnifyContext, GoalInfo0, Goal, !ModeInfo, !IO), mode_info_unset_call_context(!ModeInfo), mode_checkpoint(exit, "unify", !ModeInfo, !IO). unique_modes__check_goal_2(switch(Var, CanFail, Cases0), GoalInfo0, switch(Var, CanFail, Cases), !ModeInfo, !IO) :- mode_checkpoint(enter, "switch", !ModeInfo, !IO), ( Cases0 = [] -> Cases = [], instmap__init_unreachable(InstMap), mode_info_set_instmap(InstMap, !ModeInfo) ; goal_info_get_nonlocals(GoalInfo0, NonLocals), unique_modes__check_case_list(Cases0, Var, Cases, InstMapList, !ModeInfo, !IO), instmap__merge(NonLocals, InstMapList, disj, !ModeInfo) ), mode_checkpoint(exit, "switch", !ModeInfo, !IO). % to modecheck a pragma_c_code, we just modecheck the proc for % which it is the goal. unique_modes__check_goal_2(foreign_proc(Attributes, PredId, ProcId0, Args, ExtraArgs, PragmaCode), _GoalInfo, Goal, !ModeInfo, !IO) :- mode_checkpoint(enter, "foreign_proc", !ModeInfo, !IO), mode_info_get_call_id(!.ModeInfo, PredId, CallId), mode_info_set_call_context(call(call(CallId)), !ModeInfo), ArgVars = list__map(foreign_arg_var, Args), unique_modes__check_call(PredId, ProcId0, ArgVars, ProcId, !ModeInfo), Goal = foreign_proc(Attributes, PredId, ProcId, Args, ExtraArgs, PragmaCode), mode_info_unset_call_context(!ModeInfo), mode_checkpoint(exit, "foreign_proc", !ModeInfo, !IO). unique_modes__check_goal_2(shorthand(_), _, _, !ModeInfo, !IO) :- % these should have been expanded out by now error("unique_modes__check_goal_2: unexpected shorthand"). :- pred unique_modes__check_call(pred_id::in, proc_id::in, list(prog_var)::in, proc_id::out, mode_info::in, mode_info::out) is det. unique_modes__check_call(PredId, ProcId0, ArgVars, ProcId, !ModeInfo) :- % % set the error list to empty for use below % (saving the old error list and instmap in variables) % mode_info_get_errors(!.ModeInfo, OldErrors), mode_info_get_instmap(!.ModeInfo, InstMap0), mode_info_set_errors([], !ModeInfo), % % first off, try using the existing mode % mode_info_get_module_info(!.ModeInfo, ModuleInfo), module_info_pred_proc_info(ModuleInfo, PredId, ProcId0, PredInfo, ProcInfo), compute_arg_offset(PredInfo, ArgOffset), proc_info_argmodes(ProcInfo, ProcArgModes0), proc_info_interface_determinism(ProcInfo, InterfaceDeterminism), proc_info_never_succeeds(ProcInfo, NeverSucceeds), unique_modes__check_call_modes(ArgVars, ProcArgModes0, ArgOffset, InterfaceDeterminism, NeverSucceeds, !ModeInfo), ( ProcInfo ^ mode_errors = [_|_] -> % mode error in callee for this mode WaitingVars = set__list_to_set(ArgVars), mode_info_get_instmap(!.ModeInfo, InstMap), instmap__lookup_vars(ArgVars, InstMap, ArgInsts), mode_info_error(WaitingVars, mode_error_in_callee(ArgVars, ArgInsts, PredId, ProcId0, ProcInfo ^ mode_errors), !ModeInfo) ; true ), % % see whether or not that worked % (and restore the old error list) % mode_info_get_errors(!.ModeInfo, Errors), mode_info_set_errors(OldErrors, !ModeInfo), mode_info_get_may_change_called_proc(!.ModeInfo, MayChangeCalledProc), ( Errors = [] -> ProcId = ProcId0 ; MayChangeCalledProc = may_not_change_called_proc -> % We're not allowed to try a different procedure % here, so just return all the errors. ProcId = ProcId0, list__append(OldErrors, Errors, AllErrors), mode_info_set_errors(AllErrors, !ModeInfo) ; % % If it didn't work, restore the original instmap, % and then call modecheck_call_pred. % That will try all the modes, and will infer % new ones if necessary. % % We set the declared determinism for newly inferred % modes to be the same as the determinism inferred for % the existing mode selected by ordinary (non-unique) % mode analysis. This means that determinism analysis % will report an error if the determinism changes % as a result of unique mode analysis. That is OK, % because uniqueness should not affect determinism. % mode_info_set_instmap(InstMap0, !ModeInfo), proc_info_inferred_determinism(ProcInfo, Determinism), modecheck_call_pred(PredId, yes(Determinism), ProcId0, ProcId, ArgVars, NewArgVars, ExtraGoals, !ModeInfo), ( NewArgVars = ArgVars, ExtraGoals = no_extra_goals -> true ; % this shouldn't happen, since modes.m should do % all the handling of implied modes % XXX it might happen, though, if the user % XXX writes strange code; we should report % XXX a proper error here error("unique_modes.m: call to implied mode?") ) ). % to check a call, we just look up the required initial insts % for the arguments of the call, and then check for each % argument if the variable is nondet-live and the required initial % inst was unique. :- pred unique_modes__check_call_modes(list(prog_var)::in, list(mode)::in, int::in, determinism::in, bool::in, mode_info::in, mode_info::out) is det. unique_modes__check_call_modes(ArgVars, ProcArgModes, ArgOffset, Determinism, NeverSucceeds, !ModeInfo) :- mode_info_get_module_info(!.ModeInfo, ModuleInfo), mode_list_get_initial_insts(ProcArgModes, ModuleInfo, InitialInsts), NeedExactMatch = no, modecheck_var_has_inst_list(ArgVars, InitialInsts, NeedExactMatch, ArgOffset, InstVarSub, !ModeInfo), mode_list_get_final_insts(ProcArgModes, ModuleInfo, FinalInsts0), inst_list_apply_substitution(FinalInsts0, InstVarSub, FinalInsts), modecheck_set_var_inst_list(ArgVars, InitialInsts, FinalInsts, ArgOffset, NewArgVars, ExtraGoals, !ModeInfo), ( NewArgVars = ArgVars, ExtraGoals = no_extra_goals -> true ; % this shouldn't happen, since modes.m should do % all the handling of implied modes error("unique_modes.m: call to implied mode?") ), ( NeverSucceeds = yes -> instmap__init_unreachable(InstMap), mode_info_set_instmap(InstMap, !ModeInfo) ; % % Check whether we are at a call to a nondet predicate. % If so, mark all the currently nondet-live variables % whose inst is `unique' as instead being only `mostly_unique'. % ( determinism_components(Determinism, _, at_most_many) -> make_all_nondet_live_vars_mostly_uniq(!ModeInfo) ; true ) ). %-----------------------------------------------------------------------------% :- pred unique_modes__check_conj(list(hlds_goal)::in, list(hlds_goal)::out, mode_info::in, mode_info::out, io::di, io::uo) is det. % Just process each conjunct in turn. % Note that we don't do any reordering of conjuncts here. unique_modes__check_conj([], [], !ModeInfo, !IO). unique_modes__check_conj([Goal0 | Goals0], [Goal | Goals], !ModeInfo, !IO) :- goal_get_nonlocals(Goal0, NonLocals), mode_info_remove_live_vars(NonLocals, !ModeInfo), unique_modes__check_goal(Goal0, Goal, !ModeInfo, !IO), mode_info_get_instmap(!.ModeInfo, InstMap), ( instmap__is_unreachable(InstMap) -> % We should not mode-analyse the remaining goals, since they % are unreachable. Instead we optimize them away, so that % later passes won't complain about them not having % unique mode information. mode_info_remove_goals_live_vars(Goals0, !ModeInfo), Goals = [] ; unique_modes__check_conj(Goals0, Goals, !ModeInfo, !IO) ). %-----------------------------------------------------------------------------% % make_par_conj_nonlocal_multiset builds a multiset (bag) of all % the nonlocals of the conjuncts. :- pred make_par_conj_nonlocal_multiset(list(hlds_goal)::in, bag(prog_var)::out) is det. make_par_conj_nonlocal_multiset([], Empty) :- bag__init(Empty). make_par_conj_nonlocal_multiset([Goal | Goals], NonLocalsMultiSet) :- make_par_conj_nonlocal_multiset(Goals, NonLocalsMultiSet0), goal_get_nonlocals(Goal, NonLocals), set__to_sorted_list(NonLocals, NonLocalsList), bag__from_list(NonLocalsList, NonLocalsMultiSet1), bag__union(NonLocalsMultiSet0, NonLocalsMultiSet1, NonLocalsMultiSet). % To unique-modecheck a parallel conjunction, we find the variables % that are nonlocal to more than one conjunct and make them shared, % then we unique-modecheck the conjuncts. % % The variables that occur in more than one conjunct must be shared % because otherwise it would be possible to make them become clobbered % which would introduce an implicit dependency between the conjuncts % which we do not allow. :- pred unique_modes__check_par_conj(list(hlds_goal)::in, bag(prog_var)::in, list(hlds_goal)::out, list(pair(instmap, set(prog_var)))::out, mode_info::in, mode_info::out, io::di, io::uo) is det. unique_modes__check_par_conj(Goals0, NonLocalVarsBag, Goals, Instmaps, !ModeInfo, !IO) :- unique_modes__check_par_conj_0(NonLocalVarsBag, !ModeInfo), unique_modes__check_par_conj_1(Goals0, Goals, Instmaps, !ModeInfo, !IO). % Figure out which variables occur in more than one % conjunct and make them shared. :- pred unique_modes__check_par_conj_0(bag(prog_var)::in, mode_info::in, mode_info::out) is det. unique_modes__check_par_conj_0(NonLocalVarsBag, !ModeInfo) :- bag__to_assoc_list(NonLocalVarsBag, NonLocalVarsList), list__filter_map((pred(Pair::in, Var::out) is semidet :- Pair = Var - Multiplicity, Multiplicity > 1 ), NonLocalVarsList, SharedList), mode_info_get_instmap(!.ModeInfo, InstMap0), instmap__lookup_vars(SharedList, InstMap0, VarInsts), mode_info_get_module_info(!.ModeInfo, ModuleInfo0), make_shared_inst_list(VarInsts, SharedVarInsts, ModuleInfo0, ModuleInfo1), mode_info_set_module_info(ModuleInfo1, !ModeInfo), instmap__set_vars(InstMap0, SharedList, SharedVarInsts, InstMap1), mode_info_set_instmap(InstMap1, !ModeInfo). % Just process each conjunct in turn. % Because we have already done modechecking, we know that % there are no attempts to bind a variable in multiple % parallel conjuncts, so we don't need to lock/unlock variables. :- pred unique_modes__check_par_conj_1(list(hlds_goal)::in, list(hlds_goal)::out, list(pair(instmap, set(prog_var)))::out, mode_info::in, mode_info::out, io::di, io::uo) is det. unique_modes__check_par_conj_1([], [], [], !ModeInfo, !IO). unique_modes__check_par_conj_1([Goal0 | Goals0], [Goal | Goals], [InstMap - NonLocals|InstMaps], !ModeInfo, !IO) :- goal_get_nonlocals(Goal0, NonLocals), mode_info_get_instmap(!.ModeInfo, InstMap0), unique_modes__check_goal(Goal0, Goal, !ModeInfo, !IO), mode_info_get_instmap(!.ModeInfo, InstMap), mode_info_set_instmap(InstMap0, !ModeInfo), unique_modes__check_par_conj_1(Goals0, Goals, InstMaps, !ModeInfo, !IO). %-----------------------------------------------------------------------------% % Process each of the disjunctions in turn, making sure to restore % the original instmap before processing the next one. % Collect up a list of the resulting instmaps. :- pred unique_modes__check_disj(list(hlds_goal)::in, determinism::in, set(prog_var)::in, list(hlds_goal)::out, list(instmap)::out, mode_info::in, mode_info::out, io::di, io::uo) is det. unique_modes__check_disj([], _, _, [], [], !ModeInfo, !IO). unique_modes__check_disj([Goal0 | Goals0], DisjDetism, DisjNonLocals, [Goal | Goals], [InstMap | InstMaps], !ModeInfo, !IO) :- mode_info_get_instmap(!.ModeInfo, InstMap0), ( % % If the disjunction was model_nondet, then we already marked % all the non-locals as only being mostly-unique, so we % don't need to do anything special here... % \+ determinism_components(DisjDetism, _, at_most_many), % % ... but for model_semi or model_det disjunctions, if the % _disjunct_ can fail, then we still might backtrack to another % disjunct, so again in that case we need to mark all the % non-locals as being only mostly-unique rather than unique. % Goal0 = _ - GoalInfo0, goal_info_get_determinism(GoalInfo0, Determinism), determinism_components(Determinism, CanFail, _), CanFail = can_fail -> mode_info_add_live_vars(DisjNonLocals, !ModeInfo), make_all_nondet_live_vars_mostly_uniq(!ModeInfo), mode_info_remove_live_vars(DisjNonLocals, !ModeInfo) ; true ), unique_modes__check_goal(Goal0, Goal, !ModeInfo, !IO), mode_info_get_instmap(!.ModeInfo, InstMap), mode_info_set_instmap(InstMap0, !ModeInfo), unique_modes__check_disj(Goals0, DisjDetism, DisjNonLocals, Goals, InstMaps, !ModeInfo, !IO). %-----------------------------------------------------------------------------% :- pred unique_modes__check_case_list(list(case)::in, prog_var::in, list(case)::out, list(instmap)::out, mode_info::in, mode_info::out, io::di, io::uo) is det. unique_modes__check_case_list([], _Var, [], [], !ModeInfo, !IO). unique_modes__check_case_list([Case0 | Cases0], Var, [Case | Cases], [InstMap | InstMaps], !ModeInfo, !IO) :- Case0 = case(ConsId, Goal0), Case = case(ConsId, Goal), mode_info_get_instmap(!.ModeInfo, InstMap0), % record the fact that Var was bound to ConsId in the % instmap before processing this case modecheck_functor_test(Var, ConsId, !ModeInfo), mode_info_get_instmap(!.ModeInfo, InstMap1), ( instmap__is_reachable(InstMap1) -> unique_modes__check_goal(Goal0, Goal1, !ModeInfo, !IO) ; % 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 unique mode information. true_goal(Goal1) ), mode_info_get_instmap(!.ModeInfo, InstMap), fixup_switch_var(Var, InstMap0, InstMap, Goal1, Goal), mode_info_set_instmap(InstMap0, !ModeInfo), unique_modes__check_case_list(Cases0, Var, Cases, InstMaps, !ModeInfo, !IO). %-----------------------------------------------------------------------------%