mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-14 13:23:53 +00:00
Estimated hours taken: 100
Branches: main
Make definitions of abstract types available when generating
code for importing modules. This is necessary for the .NET
back-end, and for `:- pragma export' on the C back-end.
compiler/prog_data.m:
compiler/modules.m:
compiler/make.dependencies.m:
compiler/recompilation.version.m:
Handle implementation sections in interface files.
There is a new pseudo-declaration `abstract_imported'
which is applied to items from the implementation
section of an interface file. `abstract_imported'
items may not be used in the error checking passes
for the curent module.
compiler/equiv_type_hlds.m:
compiler/notes/compiler_design.html:
New file.
Go over the HLDS expanding all types fully after
semantic checking has been run.
compiler/mercury_compile.m:
Add the new pass.
Don't write the `.opt' file if there are any errors.
compiler/instmap.m:
Add a predicate instmap_delta_map_foldl to apply
a procedure to all insts in an instmap.
compiler/equiv_type.m:
Export predicates for use by equiv_type_hlds.m
Reorder arguments so state variables and higher-order
programming can be used.
compiler/prog_data.m:
compiler/prog_io_pragma.m:
compiler/make_hlds.m:
compiler/mercury_to_mercury.m:
Handle `:- pragma foreign_type' as a form of type
declaration rather than a pragma.
compiler/hlds_data.m:
compiler/*.m:
Add a field to the type_info_cell_constructor cons_id
to identify the type_ctor, which is needed by
equiv_type_hlds.m.
compiler/module_qual.m:
Donn't allow items from the implementation section of
interface files to match items in the current module.
compiler/*.m:
tests/*/*.m:
Add missing imports which only became apparent with
the bug fixes above.
Remove unnecessary imports which only became apparent with
the bug fixes above.
tests/hard_coded/Mmakefile:
tests/hard_coded/export_test2.{m,exp}:
Test case.
tests/invalid/Mmakefile:
tests/invalid/missing_interface_import2.{m,err_exp}:
Test case.
840 lines
30 KiB
Mathematica
840 lines
30 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 1996-2003 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__state::di, io__state::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__state::di, io__state::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__inst.
|
|
:- import_module parse_tree__mercury_to_mercury.
|
|
:- import_module parse_tree__prog_data.
|
|
:- 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, ModuleInfo0, Inst, 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, ArgNameMap, OrigArgTypes, 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),
|
|
unique_modes__check_call(PredId, ProcId0, Args, ProcId, !ModeInfo),
|
|
Goal = foreign_proc(Attributes, PredId, ProcId, Args, ArgNameMap,
|
|
OrigArgTypes, 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, ProcId0, ArgVars, yes(Determinism),
|
|
ProcId, 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, ModuleInfo0,
|
|
SharedVarInsts, 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).
|
|
|
|
%-----------------------------------------------------------------------------%
|