%-----------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %-----------------------------------------------------------------------------% % Copyright (C) 2009-2012 The University of Melbourne. % Copyright (C) 2015 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_util.m. % %-----------------------------------------------------------------------------% :- module check_hlds.modecheck_util. :- interface. :- import_module check_hlds.mode_info. :- import_module hlds. :- import_module hlds.hlds_goal. :- import_module hlds.hlds_module. :- import_module hlds.hlds_pred. :- import_module hlds.instmap. :- import_module parse_tree. :- import_module parse_tree.prog_data. :- import_module list. :- import_module maybe. %-----------------------------------------------------------------------------% :- type extra_goals ---> no_extra_goals ; extra_goals( % Goals to insert before the main goal. extra_before_main :: list(hlds_goal), % Goals to append after the main goal. extra_after_main :: list(hlds_goal) ). :- type after_goals ---> no_after_goals ; after_goals( % Instmap at end of main goal. after_instmap :: instmap, % Goals to append after the main goal. after_goals :: list(hlds_goal) ). % Append_extra_goals inserts adds some goals to the % list of goals to insert before/after the main goal. % :- pred append_extra_goals(extra_goals::in, extra_goals::in, extra_goals::out) is det. % Handle_extra_goals combines MainGoal and ExtraGoals into a single % hlds_goal_expr, rerunning mode analysis on the entire conjunction % if ExtraGoals is not empty. % :- pred handle_extra_goals(hlds_goal_expr::in, extra_goals::in, hlds_goal_info::in, list(prog_var)::in, list(prog_var)::in, instmap::in, hlds_goal_expr::out, mode_info::in, mode_info::out) is det. %-----------------------------------------------------------------------------% % Calculate the argument number offset that needs to be passed to % modecheck_var_list_is_live, modecheck_var_has_inst_list, and % modecheck_set_var_inst_list. This offset number is calculated so that % real arguments get positive argument numbers and type_info arguments % get argument numbers less than or equal to 0. % :- pred compute_arg_offset(pred_info::in, int::out) is det. % Given a list of variables and a list of expected liveness, ensure % that the inst of each variable satisfies the corresponding expected % liveness. See below for the difference between the two variants. % :- pred modecheck_var_list_is_live_exact_match(list(prog_var)::in, list(is_live)::in, int::in, mode_info::in, mode_info::out) is det. :- pred modecheck_var_list_is_live_no_exact_match(list(prog_var)::in, list(is_live)::in, int::in, mode_info::in, mode_info::out) is det. % Given a list of variables and a list of initial insts, ensure that % the inst of each variable matches the corresponding initial inst. % The first variant requires an exact match (using inst_matches_final), % while the second we allow the var to be more instantiated than the inst % (using inst_matches_initial). % :- pred modecheck_var_has_inst_list_exact_match(list(prog_var)::in, list(mer_inst)::in, int::in, inst_var_sub::out, mode_info::in, mode_info::out) is det. :- pred modecheck_var_has_inst_list_no_exact_match(list(prog_var)::in, list(mer_inst)::in, int::in, inst_var_sub::out, mode_info::in, mode_info::out) is det. % This is a special-cased, cut-down version of % modecheck_var_has_inst_list_no_exact_match for use specifically % on introduced type_info_type variables. % :- pred modecheck_introduced_type_info_var_has_inst_no_exact_match( prog_var::in, mer_type::in, mer_inst::in, mode_info::in, mode_info::out) is det. %-----------------------------------------------------------------------------% :- pred get_var_inst(mode_info::in, prog_var::in, mer_inst::out) is det. % modecheck_set_var_inst(Var, Inst, MaybeUInst, !ModeInfo): % % Assign the given Inst to the given Var, after checking that it is % okay to do so. If the inst to be assigned is the result of an % abstract unification then the MaybeUInst argument should be the % initial inst of the _other_ side of the unification. This allows % more precise (i.e. less conservative) checking in the case that % Inst contains `any' components and Var is locked (i.e. is a % nonlocal variable in a negated context). Where the inst is not % the result of an abstract unification then MaybeUInst should be `no'. % :- pred modecheck_set_var_inst(prog_var::in, mer_inst::in, maybe(mer_inst)::in, mode_info::in, mode_info::out) is det. :- pred modecheck_set_var_inst_list(list(prog_var)::in, list(mer_inst)::in, list(mer_inst)::in, int::in, list(prog_var)::out, extra_goals::out, mode_info::in, mode_info::out) is det. :- pred mode_info_add_goals_live_vars(conj_type::in, list(hlds_goal)::in, mode_info::in, mode_info::out) is det. :- pred mode_info_remove_goals_live_vars(list(hlds_goal)::in, mode_info::in, mode_info::out) is det. %-----------------------------------------------------------------------------% % modecheck_functor_test(Var, ConsId, !ModeInfo): % % Update the instmap to reflect the fact that Var was bound to ConsId. % This is used for the functor tests in `switch' statements. % :- pred modecheck_functor_test(prog_var::in, cons_id::in, mode_info::in, mode_info::out) is det. % modecheck_functors_test(Var, MainConsId, OtherConsIds, !ModeInfo): % % Update the instmap to reflect the fact that Var was bound to either % MainConsId or one of the OtherConsIds. % This is used for the functor tests in `switch' statements. % :- pred modecheck_functors_test(prog_var::in, cons_id::in, list(cons_id)::in, mode_info::in, mode_info::out) is det. %-----------------------------------------------------------------------------% % compute_goal_instmap_delta(InstMap0, GoalExpr, !GoalInfo, !ModeInfo): % % Work out the instmap_delta for a goal from the instmaps before and after % the goal. The instmap before the goal is given by InstMap0; the instmap % after the goal is given by !.ModeInfo. % :- pred compute_goal_instmap_delta(instmap::in, hlds_goal_expr::in, hlds_goal_info::in, hlds_goal_info::out, mode_info::in, mode_info::out) is det. %-----------------------------------------------------------------------------% :- pred mode_context_to_unify_context(mode_info::in, mode_context::in, unify_context::out) is det. % Given a list of variables, and a list of livenesses, % select the live variables. % :- pred get_live_vars(list(prog_var)::in, list(is_live)::in, list(prog_var)::out) is det. % Return a map of all the inst variables in the given modes, and the % sub-insts to which they are constrained. % :- pred get_constrained_inst_vars(module_info::in, list(mer_mode)::in, head_inst_vars::out) is det. %-----------------------------------------------------------------------------% %-----------------------------------------------------------------------------% :- implementation. :- import_module check_hlds.delay_info. :- import_module check_hlds.inst_match. :- import_module check_hlds.inst_test. :- import_module check_hlds.inst_util. :- import_module check_hlds.mode_errors. :- import_module check_hlds.mode_util. :- import_module check_hlds.modecheck_goal. :- import_module check_hlds.modecheck_unify. :- import_module check_hlds.type_util. :- import_module hlds.vartypes. :- import_module parse_tree.prog_mode. :- import_module parse_tree.set_of_var. :- import_module bool. :- import_module int. :- import_module map. :- import_module require. :- import_module set. :- import_module set_tree234. :- import_module term. :- import_module unit. :- import_module varset. %-----------------------------------------------------------------------------% append_extra_goals(no_extra_goals, ExtraGoals, ExtraGoals). append_extra_goals(extra_goals(BeforeGoals, AfterGoals), no_extra_goals, extra_goals(BeforeGoals, AfterGoals)). append_extra_goals(extra_goals(BeforeGoals0, AfterGoals0), extra_goals(BeforeGoals1, AfterGoals1), extra_goals(BeforeGoals, AfterGoals)) :- BeforeGoals = BeforeGoals0 ++ BeforeGoals1, AfterGoals = AfterGoals0 ++ AfterGoals1. handle_extra_goals(MainGoal, no_extra_goals, _GoalInfo0, _Args0, _Args, _InstMap0, MainGoal, !ModeInfo). handle_extra_goals(MainGoal, extra_goals(BeforeGoals0, AfterGoals0), GoalInfo0, Args0, Args, InstMap0, Goal, !ModeInfo) :- mode_info_get_errors(!.ModeInfo, Errors), ( if % There is no point adding extra goals if the code is unreachable % anyway. instmap_is_reachable(InstMap0), % If we recorded errors processing the goal, it will have to be % reprocessed anyway, so don't add the extra goals now. Errors = [] then % We need to be careful to update the delta-instmaps % correctly, using the appropriate instmaps: % % % InstMapAtStart is here % BeforeGoals, % % we don't know the instmap here, % % but as it happens we don't need it % main goal, % % InstMapAfterMain is here % AfterGoals % % InstMapAtEnd (from the ModeInfo) is here % Recompute the new set of non-local variables for the main goal. NonLocals0 = goal_info_get_nonlocals(GoalInfo0), set_of_var.list_to_set(Args0, OldArgVars), set_of_var.list_to_set(Args, NewArgVars), set_of_var.difference(NewArgVars, OldArgVars, IntroducedVars), set_of_var.union(NonLocals0, IntroducedVars, OutsideVars), set_of_var.intersect(OutsideVars, NewArgVars, NonLocals), goal_info_set_nonlocals(NonLocals, GoalInfo0, GoalInfo), % Combine the main goal and the extra goals into a conjunction. Goal0 = hlds_goal(MainGoal, GoalInfo), Context = goal_info_get_context(GoalInfo0), handle_extra_goals_contexts(BeforeGoals0, Context, BeforeGoals), handle_extra_goals_contexts(AfterGoals0, Context, AfterGoals), GoalList0 = BeforeGoals ++ [Goal0 | AfterGoals], mode_info_get_may_change_called_proc(!.ModeInfo, MayChangeCalledProc0), % Make sure we don't go into an infinite loop if % there is a bug in the code to add extra goals. mode_info_set_checking_extra_goals(yes, !ModeInfo), % We have already worked out which procedure should be called, % we don't need to do it again. mode_info_set_may_change_called_proc(may_not_change_called_proc, !ModeInfo), mode_info_set_instmap(InstMap0, !ModeInfo), % Recheck the goals to compute the instmap_deltas. % % This can fail even if the original check on the goal % succeeded in the case of a unification procedure which % binds a partially instantiated variable, because adding % the extra goals can make the partially instantiated % variables `live' after the main goal. % The other thing to beware of in this case is that delaying % must be disabled while processing the extra goals. If it % is not, the main unification will be delayed until after the % argument unifications, which turns them into assignments, % and we end up repeating the process forever. mode_info_add_goals_live_vars(plain_conj, GoalList0, !ModeInfo), modecheck_conj_list_no_delay(GoalList0, GoalList, !ModeInfo), Goal = conj(plain_conj, GoalList), mode_info_set_checking_extra_goals(no, !ModeInfo), mode_info_set_may_change_called_proc(MayChangeCalledProc0, !ModeInfo) else Goal = MainGoal ). % Modecheck a conjunction without doing any reordering. % This is used by handle_extra_goals above. % :- pred modecheck_conj_list_no_delay(list(hlds_goal)::in, list(hlds_goal)::out, mode_info::in, mode_info::out) is det. modecheck_conj_list_no_delay([], [], !ModeInfo). modecheck_conj_list_no_delay([Goal0 | Goals0], [Goal | Goals], !ModeInfo) :- NonLocals = goal_get_nonlocals(Goal0), mode_info_remove_live_vars(NonLocals, !ModeInfo), modecheck_goal(Goal0, Goal, !ModeInfo), mode_info_get_instmap(!.ModeInfo, InstMap), ( if instmap_is_unreachable(InstMap) then % 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 mode information. mode_info_remove_goals_live_vars(Goals0, !ModeInfo), Goals = [] else modecheck_conj_list_no_delay(Goals0, Goals, !ModeInfo) ). :- pred handle_extra_goals_contexts(list(hlds_goal)::in, prog_context::in, list(hlds_goal)::out) is det. handle_extra_goals_contexts([], _Context, []). handle_extra_goals_contexts([Goal0 | Goals0], Context, [Goal | Goals]) :- Goal0 = hlds_goal(GoalExpr, GoalInfo0), goal_info_set_context(Context, GoalInfo0, GoalInfo), Goal = hlds_goal(GoalExpr, GoalInfo), handle_extra_goals_contexts(Goals0, Context, Goals). %-----------------------------------------------------------------------------% compute_arg_offset(PredInfo, ArgOffset) :- OrigArity = pred_info_orig_arity(PredInfo), pred_info_get_arg_types(PredInfo, ArgTypes), list.length(ArgTypes, CurrentArity), ArgOffset = OrigArity - CurrentArity. %-----------------------------------------------------------------------------% modecheck_var_list_is_live_exact_match([_ | _], [], _, !ModeInfo) :- unexpected($module, $pred, "length mismatch"). modecheck_var_list_is_live_exact_match([], [_ | _], _, !ModeInfo) :- unexpected($module, $pred, "length mismatch"). modecheck_var_list_is_live_exact_match([], [], _ArgNum, !ModeInfo). modecheck_var_list_is_live_exact_match([Var | Vars], [IsLive | IsLives], ArgNum0, !ModeInfo) :- ArgNum = ArgNum0 + 1, mode_info_set_call_arg_context(ArgNum, !ModeInfo), modecheck_var_is_live_exact_match(Var, IsLive, !ModeInfo), modecheck_var_list_is_live_exact_match(Vars, IsLives, ArgNum, !ModeInfo). modecheck_var_list_is_live_no_exact_match([_ | _], [], _, !ModeInfo) :- unexpected($module, $pred, "length mismatch"). modecheck_var_list_is_live_no_exact_match([], [_ | _], _, !ModeInfo) :- unexpected($module, $pred, "length mismatch"). modecheck_var_list_is_live_no_exact_match([], [], _ArgNum, !ModeInfo). modecheck_var_list_is_live_no_exact_match([Var | Vars], [IsLive | IsLives], ArgNum0, !ModeInfo) :- ArgNum = ArgNum0 + 1, mode_info_set_call_arg_context(ArgNum, !ModeInfo), modecheck_var_is_live_no_exact_match(Var, IsLive, !ModeInfo), modecheck_var_list_is_live_no_exact_match(Vars, IsLives, ArgNum, !ModeInfo). % `live' means possibly used later on, and `dead' means definitely not used % later on. If you don't need an exact match, then the only time you get % an error is if you pass a variable which is live to a predicate % that expects the variable to be dead; the predicate may use destructive % update to clobber the variable, so we must be sure that it is dead % after the call. % % A version of modecheck_var_is_live specialized for NeedExactMatch = no. % :- pred modecheck_var_is_live_no_exact_match(prog_var::in, is_live::in, mode_info::in, mode_info::out) is det. modecheck_var_is_live_no_exact_match(VarId, ExpectedIsLive, !ModeInfo) :- mode_info_var_is_live(!.ModeInfo, VarId, VarIsLive), ( if ExpectedIsLive = is_dead, VarIsLive = is_live then WaitingVars = set_of_var.make_singleton(VarId), ModeError = mode_error_var_is_live(VarId), mode_info_error(WaitingVars, ModeError, !ModeInfo) else true ). % A version of modecheck_var_is_live specialized for NeedExactMatch = yes. % :- pred modecheck_var_is_live_exact_match(prog_var::in, is_live::in, mode_info::in, mode_info::out) is det. modecheck_var_is_live_exact_match(VarId, ExpectedIsLive, !ModeInfo) :- mode_info_var_is_live(!.ModeInfo, VarId, VarIsLive), ( if VarIsLive = ExpectedIsLive then true else WaitingVars = set_of_var.make_singleton(VarId), ModeError = mode_error_var_is_live(VarId), mode_info_error(WaitingVars, ModeError, !ModeInfo) ). %-----------------------------------------------------------------------------% modecheck_var_has_inst_list_exact_match(Vars, Insts, ArgNum, Subst, !ModeInfo) :- modecheck_var_has_inst_list_exact_match_2(Vars, Insts, ArgNum, map.init, Subst, !ModeInfo), modecheck_head_inst_vars(Vars, Subst, !ModeInfo). modecheck_var_has_inst_list_no_exact_match(Vars, Insts, ArgNum, Subst, !ModeInfo) :- modecheck_var_has_inst_list_no_exact_match_2(Vars, Insts, ArgNum, map.init, Subst, !ModeInfo), modecheck_head_inst_vars(Vars, Subst, !ModeInfo). :- pred modecheck_var_has_inst_list_exact_match_2(list(prog_var)::in, list(mer_inst)::in, int::in, inst_var_sub::in, inst_var_sub::out, mode_info::in, mode_info::out) is det. modecheck_var_has_inst_list_exact_match_2([_ | _], [], _, !Subst, !ModeInfo) :- unexpected($module, $pred, "length mismatch"). modecheck_var_has_inst_list_exact_match_2([], [_ | _], _, !Subst, !ModeInfo) :- unexpected($module, $pred, "length mismatch"). modecheck_var_has_inst_list_exact_match_2([], [], _ArgNum, !Subst, !ModeInfo). modecheck_var_has_inst_list_exact_match_2([Var | Vars], [Inst | Insts], ArgNum0, !Subst, !ModeInfo) :- ArgNum = ArgNum0 + 1, mode_info_set_call_arg_context(ArgNum, !ModeInfo), modecheck_var_has_inst_exact_match(Var, Inst, !Subst, !ModeInfo), modecheck_var_has_inst_list_exact_match_2(Vars, Insts, ArgNum, !Subst, !ModeInfo). :- pred modecheck_var_has_inst_list_no_exact_match_2(list(prog_var)::in, list(mer_inst)::in, int::in, inst_var_sub::in, inst_var_sub::out, mode_info::in, mode_info::out) is det. modecheck_var_has_inst_list_no_exact_match_2([_ | _], [], _, !Subst, !ModeInfo) :- unexpected($module, $pred, "length mismatch"). modecheck_var_has_inst_list_no_exact_match_2([], [_ | _], _, !Subst, !ModeInfo) :- unexpected($module, $pred, "length mismatch"). modecheck_var_has_inst_list_no_exact_match_2([], [], _ArgNum, !Subst, !ModeInfo). modecheck_var_has_inst_list_no_exact_match_2([Var | Vars], [Inst | Insts], ArgNum0, !Subst, !ModeInfo) :- ArgNum = ArgNum0 + 1, mode_info_set_call_arg_context(ArgNum, !ModeInfo), modecheck_var_has_inst_no_exact_match(Var, Inst, !Subst, !ModeInfo), modecheck_var_has_inst_list_no_exact_match_2(Vars, Insts, ArgNum, !Subst, !ModeInfo). :- pred modecheck_var_has_inst_exact_match(prog_var::in, mer_inst::in, inst_var_sub::in, inst_var_sub::out, mode_info::in, mode_info::out) is det. modecheck_var_has_inst_exact_match(Var, Inst0, !Subst, !ModeInfo) :- % Apply the substitution computed while matching earlier arguments. inst_apply_substitution(!.Subst, Inst0, Inst), mode_info_get_instmap(!.ModeInfo, InstMap), instmap_lookup_var(InstMap, Var, VarInst), mode_info_get_var_types(!.ModeInfo, VarTypes), lookup_var_type(VarTypes, Var, Type), mode_info_get_module_info(!.ModeInfo, ModuleInfo0), ( if inst_matches_initial_no_implied_modes_sub(VarInst, Inst, Type, ModuleInfo0, ModuleInfo, !Subst) then mode_info_set_module_info(ModuleInfo, !ModeInfo) else mode_info_get_pred_var_multimode_map(!.ModeInfo, MultiModeMap), ( if map.search(MultiModeMap, Var, MultiModeError) then MaybeMultiModeError = yes(MultiModeError) else MaybeMultiModeError = no ), WaitingVars = set_of_var.make_singleton(Var), ModeError = mode_error_var_has_inst(Var, VarInst, Inst, MaybeMultiModeError), mode_info_error(WaitingVars, ModeError, !ModeInfo) ). :- pred modecheck_var_has_inst_no_exact_match(prog_var::in, mer_inst::in, inst_var_sub::in, inst_var_sub::out, mode_info::in, mode_info::out) is det. modecheck_var_has_inst_no_exact_match(Var, Inst0, !Subst, !ModeInfo) :- % Apply the substitution computed while matching earlier arguments. inst_apply_substitution(!.Subst, Inst0, Inst), mode_info_get_instmap(!.ModeInfo, InstMap), instmap_lookup_var(InstMap, Var, VarInst), mode_info_get_var_types(!.ModeInfo, VarTypes), lookup_var_type(VarTypes, Var, Type), mode_info_get_module_info(!.ModeInfo, ModuleInfo0), ( if inst_matches_initial_sub(VarInst, Inst, Type, ModuleInfo0, ModuleInfo, !Subst) then mode_info_set_module_info(ModuleInfo, !ModeInfo) else mode_info_get_pred_var_multimode_map(!.ModeInfo, MultiModeMap), ( if map.search(MultiModeMap, Var, MultiModeError) then MaybeMultiModeError = yes(MultiModeError) else MaybeMultiModeError = no ), WaitingVars = set_of_var.make_singleton(Var), ModeError = mode_error_var_has_inst(Var, VarInst, Inst, MaybeMultiModeError), mode_info_error(WaitingVars, ModeError, !ModeInfo) ). modecheck_introduced_type_info_var_has_inst_no_exact_match(Var, Type, Inst, !ModeInfo) :- mode_info_get_instmap(!.ModeInfo, InstMap), instmap_lookup_var(InstMap, Var, VarInst), mode_info_get_module_info(!.ModeInfo, ModuleInfo0), ( if inst_matches_initial_sub(VarInst, Inst, Type, ModuleInfo0, ModuleInfo, map.init, _Subst) then mode_info_set_module_info(ModuleInfo, !ModeInfo) else WaitingVars = set_of_var.make_singleton(Var), ModeError = mode_error_var_has_inst(Var, VarInst, Inst, no), mode_info_error(WaitingVars, ModeError, !ModeInfo) ). %-----------------------------------------------------------------------------% :- pred modecheck_head_inst_vars(list(prog_var)::in, inst_var_sub::in, mode_info::in, mode_info::out) is det. modecheck_head_inst_vars(Vars, InstVarSub, !ModeInfo) :- mode_info_get_head_inst_vars(!.ModeInfo, HeadInstVars), ( if map.foldl(modecheck_head_inst_var(HeadInstVars), InstVarSub, unit, _) then true else mode_info_get_instmap(!.ModeInfo, InstMap), instmap_lookup_vars(InstMap, Vars, VarInsts), WaitingVars = set_of_var.list_to_set(Vars), ModeError = mode_error_no_matching_mode(Vars, VarInsts), mode_info_error(WaitingVars, ModeError, !ModeInfo) ). :- pred modecheck_head_inst_var(inst_var_sub::in, inst_var::in, mer_inst::in, unit::in, unit::out) is semidet. modecheck_head_inst_var(HeadInstVars, InstVar, Subst, !Acc) :- ( if map.search(HeadInstVars, InstVar, Inst) then % Subst should not change the constraint. However, the two insts % may have different information about inst test results. Subst = constrained_inst_vars(SubstInstVars, SubstInst), set.member(InstVar, SubstInstVars), ( if Inst = bound(Uniq, _, BoundInsts), SubstInst = bound(SubstUniq, _, SubstBoundInsts) then Uniq = SubstUniq, BoundInsts = SubstBoundInsts else Inst = SubstInst ) else true ). %-----------------------------------------------------------------------------% get_var_inst(ModeInfo, Var, Inst) :- mode_info_get_module_info(ModeInfo, ModuleInfo), mode_info_get_instmap(ModeInfo, InstMap), mode_info_get_var_types(ModeInfo, VarTypes), instmap_lookup_var(InstMap, Var, Inst0), lookup_var_type(VarTypes, Var, Type), normalise_inst(ModuleInfo, Type, Inst0, Inst). %-----------------------------------------------------------------------------% modecheck_set_var_inst(Var0, NewInst, MaybeUInst, !ModeInfo) :- % Note that there are two versions of modecheck_set_var_inst, % one with arity 8 (suffixed with _call) and one with arity 5. % The former is used for predicate calls, where we may need % to introduce unifications to handle calls to implied modes. % mode_info_get_parallel_vars(!.ModeInfo, PVars0), mode_info_get_instmap(!.ModeInfo, InstMap0), ( if instmap_is_reachable(InstMap0) then instmap_lookup_var(InstMap0, Var0, OldInst), mode_info_get_module_info(!.ModeInfo, ModuleInfo0), % The final new inst must be computed by unifying the old inst % and the tentative new inst. However, abstractly unifying % a large inst with itself can be VERY expensive; it can be worse % than quadratic. The OldInst = NewInst test here may increase % execution time slightly in normal cases, but should reduce it % greatly in the worst cases. ( if OldInst = NewInst then ModuleInfo = ModuleInfo0, Inst = OldInst else if abstractly_unify_inst(is_dead, OldInst, NewInst, fake_unify, UnifyInst, _Det, ModuleInfo0, ModuleInfo1) then ModuleInfo = ModuleInfo1, Inst = UnifyInst else unexpected($module, $pred, "unify_inst failed") ), mode_info_set_module_info(ModuleInfo, !ModeInfo), mode_info_get_var_types(!.ModeInfo, VarTypes), lookup_var_type(VarTypes, Var0, Type), ( if % If the top-level inst of the variable is not_reached, % then the instmap as a whole must be unreachable. inst_expand(ModuleInfo, Inst, not_reached) then instmap.init_unreachable(InstMap), mode_info_set_instmap(InstMap, !ModeInfo) else if % If we haven't added any information and % we haven't bound any part of the var, then % the only thing we can have done is lose uniqueness. inst_matches_initial(OldInst, Inst, Type, ModuleInfo) then instmap_set_var(Var0, Inst, InstMap0, InstMap), mode_info_set_instmap(InstMap, !ModeInfo) else if % We must have either added some information, % lost some uniqueness, or bound part of the var. % The call to inst_matches_binding will succeed % only if we haven't bound any part of the var. not inst_matches_binding(Inst, OldInst, Type, ModuleInfo), % We have bound part of the var. If the var was locked, % then we need to report an error ... mode_info_var_is_locked(!.ModeInfo, Var0, Reason0), not ( % ... unless the goal is a unification and the var was unified % with something no more instantiated than itself. This allows % for the case of `any = free', for example. The call to % inst_matches_binding, above will fail for the var with % mode `any >> any' however, it should be allowed because % it has only been unified with a free variable. MaybeUInst = yes(UInst), inst_is_at_least_as_instantiated(Inst, UInst, Type, ModuleInfo), inst_matches_binding_allow_any_any(Inst, OldInst, Type, ModuleInfo) ) then WaitingVars = set_of_var.make_singleton(Var0), ModeError = mode_error_bind_var(Reason0, Var0, OldInst, Inst), mode_info_error(WaitingVars, ModeError, !ModeInfo) else instmap_set_var(Var0, Inst, InstMap0, InstMap), mode_info_set_instmap(InstMap, !ModeInfo), mode_info_get_delay_info(!.ModeInfo, DelayInfo0), delay_info_bind_var(Var0, DelayInfo0, DelayInfo), mode_info_set_delay_info(DelayInfo, !ModeInfo) ) else true ), ( PVars0 = [] ; PVars0 = [par_conj_mode_check(NonLocals, Bound0) | PVars1], ( if set_of_var.member(NonLocals, Var0) then set_of_var.insert(Var0, Bound0, Bound), PVars = [par_conj_mode_check(NonLocals, Bound) | PVars1] else PVars = PVars0 ), mode_info_set_parallel_vars(PVars, !ModeInfo) ). modecheck_set_var_inst_list(Vars0, InitialInsts, FinalInsts, ArgOffset, Vars, Goals, !ModeInfo) :- ( if modecheck_set_var_inst_list_2(Vars0, InitialInsts, FinalInsts, ArgOffset, Vars1, no_extra_goals, Goals1, !ModeInfo) then Vars = Vars1, Goals = Goals1 else unexpected($module, $pred, "length mismatch") ). :- pred modecheck_set_var_inst_list_2(list(prog_var)::in, list(mer_inst)::in, list(mer_inst)::in, int::in, list(prog_var)::out, extra_goals::in, extra_goals::out, mode_info::in, mode_info::out) is semidet. modecheck_set_var_inst_list_2([], [], [], _, [], !ExtraGoals, !ModeInfo). modecheck_set_var_inst_list_2([Var0 | Vars0], [InitialInst | InitialInsts], [FinalInst | FinalInsts], ArgNum0, [Var | Vars], !ExtraGoals, !ModeInfo) :- ArgNum = ArgNum0 + 1, mode_info_set_call_arg_context(ArgNum, !ModeInfo), modecheck_set_var_inst_call(Var0, InitialInst, FinalInst, Var, !ExtraGoals, !ModeInfo), modecheck_set_var_inst_list_2(Vars0, InitialInsts, FinalInsts, ArgNum, Vars, !ExtraGoals, !ModeInfo). :- pred modecheck_set_var_inst_call(prog_var::in, mer_inst::in, mer_inst::in, prog_var::out, extra_goals::in, extra_goals::out, mode_info::in, mode_info::out) is det. modecheck_set_var_inst_call(Var0, InitialInst, FinalInst, Var, !ExtraGoals, !ModeInfo) :- mode_info_get_instmap(!.ModeInfo, InstMap0), ( if instmap_is_reachable(InstMap0) then % The new inst must be computed by unifying the old inst % and the proc's final inst. instmap_lookup_var(InstMap0, Var0, VarInst0), handle_implied_mode(Var0, VarInst0, InitialInst, Var, !ExtraGoals, !ModeInfo), modecheck_set_var_inst(Var0, FinalInst, no, !ModeInfo), ( if Var = Var0 then true else modecheck_set_var_inst(Var, FinalInst, no, !ModeInfo) ) else Var = Var0 ). %-----------------------------------------------------------------------------% % If this was a call to an implied mode for that variable, then we need to % introduce a fresh variable. % :- pred handle_implied_mode(prog_var::in, mer_inst::in, mer_inst::in, prog_var::out, extra_goals::in, extra_goals::out, mode_info::in, mode_info::out) is det. handle_implied_mode(Var0, VarInst0, InitialInst0, Var, !ExtraGoals, !ModeInfo) :- mode_info_get_module_info(!.ModeInfo, ModuleInfo0), inst_expand(ModuleInfo0, InitialInst0, InitialInst), inst_expand(ModuleInfo0, VarInst0, VarInst1), mode_info_get_var_types(!.ModeInfo, VarTypes0), lookup_var_type(VarTypes0, Var0, VarType), ( if % If the initial inst of the variable matches_final the initial inst % specified in the pred's mode declaration, then it's not a call % to an implied mode, it's an exact match with a genuine mode. inst_matches_initial_no_implied_modes_sub(VarInst1, InitialInst, VarType, ModuleInfo0, _ModuleInfo, map.init, _Sub) then Var = Var0 else if % This is the implied mode case. We do not yet handle implied modes % for partially instantiated vars, since that would require doing % a partially instantiated deep copy, and we don't know how to do % that yet. InitialInst = any(_, _), inst_is_free(ModuleInfo0, VarInst1) then % This is the simple case of implied `any' modes, where the declared % mode was `any -> ...' and the argument passed was `free'. Var = Var0, % If the variable's type is not a solver type (in which case inst `any' % means the same as inst `ground') then this is an implied mode that we % don't yet know how to handle. WaitingVars = set_of_var.make_singleton(Var0), ModeError = mode_error_implied_mode(Var0, VarInst0, InitialInst), mode_info_error(WaitingVars, ModeError, !ModeInfo) else if inst_is_bound(ModuleInfo0, InitialInst) then % This is the case we can't handle. Var = Var0, WaitingVars = set_of_var.make_singleton(Var0), ModeError = mode_error_implied_mode(Var0, VarInst0, InitialInst), mode_info_error(WaitingVars, ModeError, !ModeInfo) else % This is the simple case of implied modes, % where the declared mode was free -> ... % Introduce a new variable. mode_info_get_varset(!.ModeInfo, VarSet0), varset.new_var(Var, VarSet0, VarSet), add_var_type(Var, VarType, VarTypes0, VarTypes), mode_info_set_varset(VarSet, !ModeInfo), mode_info_set_var_types(VarTypes, !ModeInfo), % Construct the code to do the unification. create_var_var_unification(Var0, Var, VarType, !.ModeInfo, ExtraGoal), % Append the goals together in the appropriate order: % ExtraGoals0, then NewUnify. NewUnifyExtraGoal = extra_goals([], [ExtraGoal]), append_extra_goals(!.ExtraGoals, NewUnifyExtraGoal, !:ExtraGoals) ). %-----------------------------------------------------------------------------% mode_info_add_goals_live_vars(_ConjType, [], !ModeInfo). mode_info_add_goals_live_vars(ConjType, [Goal | Goals], !ModeInfo) :- % We add the live vars for the goals in the goal list in reverse order, % because this ensures that in the common case (where there is no % delaying), when we come to remove the live vars for the first goal % they will have been added last and will thus be at the start of the list % of live vars sets, which makes them cheaper to remove. mode_info_add_goals_live_vars(ConjType, Goals, !ModeInfo), ( if % Recurse into conjunctions, in case there are any conjunctions % that have not been flattened. Goal = hlds_goal(conj(ConjType, ConjGoals), _) then mode_info_add_goals_live_vars(ConjType, ConjGoals, !ModeInfo) else NonLocals = goal_get_nonlocals(Goal), mode_info_add_live_vars(NonLocals, !ModeInfo) ). mode_info_remove_goals_live_vars([], !ModeInfo). mode_info_remove_goals_live_vars([Goal | Goals], !ModeInfo) :- ( if % Recurse into conjunctions, in case there are any conjunctions % that have not been flattened. Goal = hlds_goal(conj(plain_conj, ConjGoals), _) then mode_info_remove_goals_live_vars(ConjGoals, !ModeInfo) else NonLocals = goal_get_nonlocals(Goal), mode_info_remove_live_vars(NonLocals, !ModeInfo) ), mode_info_remove_goals_live_vars(Goals, !ModeInfo). %-----------------------------------------------------------------------------% modecheck_functor_test(Var, ConsId, !ModeInfo) :- % Figure out the arity of this constructor, _including_ any type-infos % or typeclass-infos inserted for existential data types. mode_info_get_module_info(!.ModeInfo, ModuleInfo), mode_info_get_var_types(!.ModeInfo, VarTypes), lookup_var_type(VarTypes, Var, Type), BoundInst = cons_id_to_bound_inst(ModuleInfo, Type, ConsId), % Record the fact that Var was bound to ConsId. Inst = bound(unique, inst_test_no_results, [BoundInst]), modecheck_set_var_inst(Var, Inst, no, !ModeInfo). modecheck_functors_test(Var, MainConsId, OtherConsIds, !ModeInfo) :- % Figure out the arity of this constructor, _including_ any type-infos % or typeclass-infos inserted for existential data types. mode_info_get_module_info(!.ModeInfo, ModuleInfo), mode_info_get_var_types(!.ModeInfo, VarTypes), lookup_var_type(VarTypes, Var, Type), BoundInsts = list.map(cons_id_to_bound_inst(ModuleInfo, Type), [MainConsId | OtherConsIds]), % Record the fact that Var was bound to MainConsId or one of the % OtherConsIds. Inst = bound(unique, inst_test_no_results, BoundInsts), modecheck_set_var_inst(Var, Inst, no, !ModeInfo). :- func cons_id_to_bound_inst(module_info, mer_type, cons_id) = bound_inst. cons_id_to_bound_inst(ModuleInfo, Type, ConsId) = BoundInst :- ConsIdAdjustedArity = cons_id_adjusted_arity(ModuleInfo, Type, ConsId), list.duplicate(ConsIdAdjustedArity, free, ArgInsts), BoundInst = bound_functor(ConsId, ArgInsts). %-----------------------------------------------------------------------------% compute_goal_instmap_delta(InstMap0, GoalExpr, !GoalInfo, !ModeInfo) :- ( if GoalExpr = conj(_, []) then % When modecheck_unify.m replaces a unification with a dead variable % with `true', make sure the instmap_delta of the goal is empty. % The code generator and mode_util.recompute_instmap_delta can be % confused by references to the dead variable in the instmap_delta, % resulting in calls to error/1. instmap_delta_init_reachable(DeltaInstMap), mode_info_set_instmap(InstMap0, !ModeInfo) else NonLocals = goal_info_get_nonlocals(!.GoalInfo), mode_info_get_instmap(!.ModeInfo, InstMap), compute_instmap_delta(InstMap0, InstMap, NonLocals, DeltaInstMap) ), goal_info_set_instmap_delta(DeltaInstMap, !GoalInfo). %-----------------------------------------------------------------------------% mode_context_to_unify_context(_ModeInfo, ModeContext, UnifyContext) :- ( ModeContext = mode_context_unify(UnifyContext, _) ; ModeContext = mode_context_call(CallId, Arg), UnifyContext = unify_context(umc_call(CallId, Arg), []) ; ModeContext = mode_context_uninitialized, unexpected($module, $pred, "uninitialized context") ). %-----------------------------------------------------------------------------% get_live_vars([], [], []). get_live_vars([_ | _], [], _) :- unexpected($module, $pred, "length mismatch"). get_live_vars([], [_ | _], _) :- unexpected($module, $pred, "length mismatch"). get_live_vars([Var | Vars], [IsLive | IsLives], LiveVars) :- ( IsLive = is_live, LiveVars = [Var | LiveVars0] ; IsLive = is_dead, LiveVars = LiveVars0 ), get_live_vars(Vars, IsLives, LiveVars0). %-----------------------------------------------------------------------------% :- type inst_expansions == set_tree234(inst_name). get_constrained_inst_vars(ModuleInfo, Modes, Map) :- list.foldl2(get_constrained_insts_in_mode(ModuleInfo), Modes, map.init, Map, set_tree234.init, _Expansions). :- pred get_constrained_insts_in_mode(module_info::in, mer_mode::in, head_inst_vars::in, head_inst_vars::out, inst_expansions::in, inst_expansions::out) is det. get_constrained_insts_in_mode(ModuleInfo, Mode, !Map, !Expansions) :- mode_get_insts(ModuleInfo, Mode, InitialInst, FinalInst), get_constrained_insts_in_inst(ModuleInfo, InitialInst, !Map, !Expansions), get_constrained_insts_in_inst(ModuleInfo, FinalInst, !Map, !Expansions). :- pred get_constrained_insts_in_inst(module_info::in, mer_inst::in, head_inst_vars::in, head_inst_vars::out, inst_expansions::in, inst_expansions::out) is det. get_constrained_insts_in_inst(ModuleInfo, Inst, !Map, !Expansions) :- ( ( Inst = free ; Inst = free(_) ; Inst = not_reached ) ; Inst = bound(_, InstResults, BoundInsts), ( InstResults = inst_test_results_fgtc ; InstResults = inst_test_results(_, _, _, InstVarsResult, _, _), ( if InstVarsResult = inst_result_contains_inst_vars_known(InstVars), set.is_empty(InstVars) then true else list.foldl2(get_constrained_insts_in_bound_inst(ModuleInfo), BoundInsts, !Map, !Expansions) ) ; InstResults = inst_test_no_results, list.foldl2(get_constrained_insts_in_bound_inst(ModuleInfo), BoundInsts, !Map, !Expansions) ) ; ( Inst = any(_, HOInstInfo) ; Inst = ground(_, HOInstInfo) ), ( HOInstInfo = none_or_default_func ; HOInstInfo = higher_order(PredInstInfo), get_constrained_insts_in_ho_inst(ModuleInfo, PredInstInfo, !Map, !Expansions) ) ; Inst = constrained_inst_vars(InstVars, _), inst_expand_and_remove_constrained_inst_vars(ModuleInfo, Inst, SubInst), set.fold(add_constrained_inst(SubInst), InstVars, !Map) ; Inst = defined_inst(InstName), ( if insert_new(InstName, !Expansions) then inst_lookup(ModuleInfo, InstName, ExpandedInst), get_constrained_insts_in_inst(ModuleInfo, ExpandedInst, !Map, !Expansions) else true ) ; Inst = inst_var(_), unexpected($module, $pred, "inst_var") ; Inst = abstract_inst(_, _), sorry($module, $pred, "abstract_inst") ). :- pred get_constrained_insts_in_bound_inst(module_info::in, bound_inst::in, head_inst_vars::in, head_inst_vars::out, inst_expansions::in, inst_expansions::out) is det. get_constrained_insts_in_bound_inst(ModuleInfo, BoundInst, !Map, !Expansions) :- BoundInst = bound_functor(_ConsId, Insts), list.foldl2(get_constrained_insts_in_inst(ModuleInfo), Insts, !Map, !Expansions). :- pred get_constrained_insts_in_ho_inst(module_info::in, pred_inst_info::in, head_inst_vars::in, head_inst_vars::out, inst_expansions::in, inst_expansions::out) is det. get_constrained_insts_in_ho_inst(ModuleInfo, PredInstInfo, !Map, !Expansions) :- PredInstInfo = pred_inst_info(_, Modes, _, _), list.foldl2(get_constrained_insts_in_mode(ModuleInfo), Modes, !Map, !Expansions). :- pred add_constrained_inst(mer_inst::in, inst_var::in, head_inst_vars::in, head_inst_vars::out) is det. add_constrained_inst(SubInst, InstVar, !Map) :- ( if map.search(!.Map, InstVar, SubInst0) then ( if SubInst0 = SubInst then true else unexpected($module, $pred, "SubInst differs") ) else map.det_insert(InstVar, SubInst, !Map) ). %-----------------------------------------------------------------------------% :- end_module check_hlds.modecheck_util. %-----------------------------------------------------------------------------%