mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-15 05:44:58 +00:00
Estimated hours taken: 50
Add support for nested modules.
- module names may themselves be module-qualified
- modules may contain `:- include_module' declarations
which name sub-modules
- a sub-module has access to all the declarations in the
parent module (including its implementation section).
This support is not yet complete; see the BUGS and LIMITATIONS below.
LIMITATIONS
- source file names must match module names
(just as they did previously)
- mmc doesn't allow path names on the command line any more
(e.g. `mmc --make-int ../library/foo.m').
- import_module declarations must use the fully-qualified module name
- module qualifiers must use the fully-qualified module name
- no support for root-qualified module names
(e.g. `:parent:child' instead of `parent:child').
- modules may not be physically nested (only logical nesting, via
`include_module').
BUGS
- doesn't check that the parent module is imported/used before allowing
import/use of its sub-modules.
- doesn't check that there is an include_module declaration in the
parent for each module claiming to be a child of that parent
- privacy of private modules is not enforced
-------------------
NEWS:
Mention that we support nested modules.
library/ops.m:
library/nc_builtin.nl:
library/sp_builtin.nl:
compiler/mercury_to_mercury.m:
Add `include_module' as a new prefix operator.
Change the associativity of `:' from xfy to yfx
(since this made parsing module qualifiers slightly easier).
compiler/prog_data.m:
Add new `include_module' declaration.
Change the `module_name' and `module_specifier' types
from strings to sym_names, so that module names can
themselves be module qualified.
compiler/modules.m:
Add predicates module_name_to_file_name/2 and
file_name_to_module_name/2.
Lots of changes to handle parent module dependencies,
to create parent interface (`.int0') files, to read them in,
to output correct dependencies information for them to the
`.d' and `.dep' files, etc.
Rewrite a lot of the code to improve the readability
(add comments, use subroutines, better variable names).
Also fix a couple of bugs:
- generate_dependencies was using the transitive implementation
dependencies rather than the transitive interface dependencies
to compute the `.int3' dependencies when writing `.d' files
(this bug was introduced during crs's changes to support
`.trans_opt' files)
- when creating the `.int' file, it was reading in the
interfaces for modules imported in the implementation section,
not just those in the interface section.
This meant that the compiler missed a lot of errors.
library/graph.m:
library/lexer.m:
library/term.m:
library/term_io.m:
library/varset.m:
compiler/*.m:
Add `:- import_module' declarations to the interface needed
by declarations in the interface. (The previous version
of the compiler did not detect these missing interface imports,
due to the above-mentioned bug in modules.m.)
compiler/mercury_compile.m:
compiler/intermod.m:
Change mercury_compile__maybe_grab_optfiles and
intermod__grab_optfiles so that they grab the opt files for
parent modules as well as the ones for imported modules.
compiler/mercury_compile.m:
Minor changes to handle parent module dependencies.
(Also improve the wording of the warning about trans-opt
dependencies.)
compiler/make_hlds.m:
compiler/module_qual.m:
Ignore `:- include_module' declarations.
compiler/module_qual.m:
A couple of small changes to handle nested module names.
compiler/prog_out.m:
compiler/prog_util.m:
Add new predicates string_to_sym_name/3 (prog_util.m) and
sym_name_to_string/{2,3} (prog_out.m).
compiler/*.m:
Replace many occurrences of `string' with `module_name'.
Change code that prints out module names or converts
them to strings or filenames to handle the fact that
module names are now sym_names intead of strings.
Also change a few places (e.g. in intermod.m, hlds_module.m)
where the code assumed that any qualified symbol was
fully-qualified.
compiler/prog_io.m:
compiler/prog_io_goal.m:
Move sym_name_and_args/3, parse_qualified_term/4 and
parse_qualified_term/5 preds from prog_io_goal.m to prog_io.m,
since they are very similar to the parse_symbol_name/2 predicate
already in prog_io.m. Rewrite these predicates, both
to improve maintainability, and to handle the newly
allowed syntax (module-qualified module names).
Rename parse_qualified_term/5 as `parse_implicit_qualified_term'.
compiler/prog_io.m:
Rewrite the handling of `:- module' and `:- end_module'
declarations, so that it can handle nested modules.
Add code to parse `include_module' declarations.
compiler/prog_util.m:
compiler/*.m:
Add new predicates mercury_public_builtin_module/1 and
mercury_private_builtin_module/1 in prog_util.m.
Change most of the hard-coded occurrences of "mercury_builtin"
to call mercury_private_builtin_module/1 or
mercury_public_builtin_module/1 or both.
compiler/llds_out.m:
Add llds_out__sym_name_mangle/2, for mangling module names.
compiler/special_pred.m:
compiler/mode_util.m:
compiler/clause_to_proc.m:
compiler/prog_io_goal.m:
compiler/lambda.m:
compiler/polymorphism.m:
Move the predicates in_mode/1, out_mode/1, and uo_mode/1
from special_pred.m to mode_util.m, and change various
hard-coded definitions to instead call these predicates.
compiler/polymorphism.m:
Ensure that the type names `type_info' and `typeclass_info' are
module-qualified in the generated code. This avoids a problem
where the code generated by polymorphism.m was not considered
type-correct, due to the type `type_info' not matching
`mercury_builtin:type_info'.
compiler/check_typeclass.m:
Simplify the code for check_instance_pred and
get_matching_instance_pred_ids.
compiler/mercury_compile.m:
compiler/modules.m:
Disallow directory names in command-line arguments.
compiler/options.m:
compiler/handle_options.m:
compiler/mercury_compile.m:
compiler/modules.m:
Add a `--make-private-interface' option.
The private interface file `<module>.int0' contains
all the declarations in the module; it is used for
compiling sub-modules.
scripts/Mmake.rules:
scripts/Mmake.vars.in:
Add support for creating `.int0' and `.date0' files
by invoking mmc with `--make-private-interface'.
doc/user_guide.texi:
Document `--make-private-interface' and the `.int0'
and `.date0' file extensions.
doc/reference_manual.texi:
Document nested modules.
util/mdemangle.c:
profiler/demangle.m:
Demangle names with multiple module qualifiers.
tests/general/Mmakefile:
tests/general/string_format_test.m:
tests/general/string_format_test.exp:
tests/general/string__format_test.m:
tests/general/string__format_test.exp:
tests/general/.cvsignore:
Change the `:- module string__format_test' declaration in
`string__format_test.m' to `:- module string_format_test',
because with the original declaration the `__' was taken
as a module qualifier, which lead to an error message.
Hence rename the file accordingly, to avoid the warning
about file name not matching module name.
tests/invalid/Mmakefile:
tests/invalid/missing_interface_import.m:
tests/invalid/missing_interface_import.err_exp:
Regression test to check that the compiler reports
errors for missing `import_module' in the interface section.
tests/invalid/*.err_exp:
tests/warnings/unused_args_test.exp:
tests/warnings/unused_import.exp:
Update the expected diagnostics output for the test cases to
reflect a few minor changes to the warning messages.
tests/hard_coded/Mmakefile:
tests/hard_coded/parent.m:
tests/hard_coded/parent.child.m:
tests/hard_coded/parent.exp:
tests/hard_coded/parent2.m:
tests/hard_coded/parent2.child.m:
tests/hard_coded/parent2.exp:
Two simple tests case for the use of nested modules with
separate compilation.
621 lines
22 KiB
Mathematica
621 lines
22 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 1996-1998 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.
|
|
|
|
% 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 report an error message.
|
|
|
|
% XXX what if it would have matched ok with a different mode of the
|
|
% called predicate (e.g. if a predicate is overloaded with both
|
|
% `ui' and `in' modes)?
|
|
|
|
% Variables can become nondet live in several places:
|
|
% in negations, in the conditions of if-then-elses,
|
|
% and in disjunctions, and at nondet calls.
|
|
|
|
% 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 unique_modes.
|
|
:- interface.
|
|
:- import_module hlds_module, hlds_pred, hlds_goal, mode_info.
|
|
:- import_module io, bool.
|
|
|
|
% check every predicate in a module
|
|
:- pred unique_modes__check_module(module_info, module_info,
|
|
io__state, io__state).
|
|
:- mode unique_modes__check_module(in, out, di, uo) is det.
|
|
|
|
% just check a single procedure
|
|
:- pred unique_modes__check_proc(proc_id, pred_id, module_info,
|
|
module_info, bool, io__state, io__state).
|
|
:- mode unique_modes__check_proc(in, in, in, out, out, di, uo) is det.
|
|
|
|
% just check a single goal
|
|
:- pred unique_modes__check_goal(hlds_goal, hlds_goal, mode_info, mode_info).
|
|
:- mode unique_modes__check_goal(in, out, mode_info_di, mode_info_uo) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module hlds_data, mode_debug, modecheck_unify, modecheck_call.
|
|
:- import_module mode_util, prog_out, hlds_out, mercury_to_mercury, passes_aux.
|
|
:- import_module modes, prog_data, mode_errors, llds, unify_proc.
|
|
:- import_module (inst), instmap, inst_match, inst_util.
|
|
:- import_module int, list, map, set, std_util, require, term, varset.
|
|
:- import_module assoc_list.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
unique_modes__check_module(ModuleInfo0, ModuleInfo) -->
|
|
check_pred_modes(check_unique_modes, ModuleInfo0, ModuleInfo,
|
|
_UnsafeToContinue).
|
|
|
|
unique_modes__check_proc(ProcId, PredId, ModuleInfo0, ModuleInfo, Changed) -->
|
|
modecheck_proc(ProcId, PredId, check_unique_modes,
|
|
ModuleInfo0, ModuleInfo, NumErrors, Changed),
|
|
( { NumErrors \= 0 } ->
|
|
io__set_exit_status(1)
|
|
;
|
|
[]
|
|
).
|
|
|
|
% 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, ModeInfo0, ModeInfo) :-
|
|
%
|
|
% store the current context in the mode_info
|
|
%
|
|
Goal0 = GoalExpr0 - GoalInfo0,
|
|
goal_info_get_context(GoalInfo0, Context),
|
|
term__context_init(EmptyContext),
|
|
( Context = EmptyContext ->
|
|
ModeInfo1 = ModeInfo0
|
|
;
|
|
mode_info_set_context(Context, ModeInfo0, ModeInfo1)
|
|
),
|
|
|
|
%
|
|
% Grab the original instmap
|
|
%
|
|
mode_info_get_instmap(ModeInfo1, InstMap0),
|
|
|
|
%
|
|
% Grab the original bag of nondet-live vars
|
|
%
|
|
mode_info_get_nondet_live_vars(ModeInfo1, 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_code_model(GoalInfo0, CodeModel),
|
|
( CodeModel = model_non ->
|
|
ModeInfo2 = ModeInfo1
|
|
;
|
|
mode_info_set_nondet_live_vars([], ModeInfo1, ModeInfo2)
|
|
),
|
|
|
|
%
|
|
% Modecheck the goal
|
|
%
|
|
unique_modes__check_goal_2(GoalExpr0, GoalInfo0, GoalExpr,
|
|
ModeInfo2, ModeInfo3),
|
|
|
|
%
|
|
% Restore the original bag of nondet-live vars
|
|
%
|
|
mode_info_set_nondet_live_vars(NondetLiveVars0, ModeInfo3, ModeInfo),
|
|
|
|
%
|
|
% Grab the final instmap, compute the change in insts
|
|
% over this goal, and save that instmap_delta in the goal_info.
|
|
%
|
|
mode_info_get_instmap(ModeInfo, InstMap),
|
|
goal_info_get_nonlocals(GoalInfo0, NonLocals),
|
|
compute_instmap_delta(InstMap0, InstMap, NonLocals, DeltaInstMap),
|
|
goal_info_set_instmap_delta(GoalInfo0, DeltaInstMap, GoalInfo),
|
|
|
|
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, mode_info).
|
|
:- mode make_all_nondet_live_vars_mostly_uniq(mode_info_di, mode_info_uo)
|
|
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(var), mode_info, list(var)).
|
|
:- mode select_live_vars(in, mode_info_ui, 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(var), mode_info, list(var)).
|
|
:- mode select_nondet_live_vars(in, mode_info_ui, 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(var), instmap_delta, mode_info,
|
|
list(var)).
|
|
:- mode select_changed_inst_vars(in, in, mode_info_ui, 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),
|
|
(
|
|
instmap_delta_is_reachable(DeltaInstMap),
|
|
instmap_delta_search_var(DeltaInstMap, Var, Inst),
|
|
\+ inst_matches_final(Inst, Inst0, 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(var), mode_info, mode_info).
|
|
:- mode make_var_list_mostly_uniq(in, mode_info_di, mode_info_uo) is det.
|
|
|
|
make_var_list_mostly_uniq([], ModeInfo, ModeInfo).
|
|
make_var_list_mostly_uniq([Var | Vars], ModeInfo0, ModeInfo) :-
|
|
make_var_mostly_uniq(Var, ModeInfo0, ModeInfo1),
|
|
make_var_list_mostly_uniq(Vars, ModeInfo1, ModeInfo).
|
|
|
|
:- pred make_var_mostly_uniq(var, mode_info, mode_info).
|
|
:- mode make_var_mostly_uniq(in, mode_info_di, mode_info_uo) is det.
|
|
|
|
make_var_mostly_uniq(Var, ModeInfo0, ModeInfo) :-
|
|
mode_info_get_instmap(ModeInfo0, InstMap0),
|
|
mode_info_get_module_info(ModeInfo0, 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(ModeInfo0, ModuleInfo, ModeInfo1),
|
|
instmap__set(InstMap0, Var, Inst, InstMap),
|
|
mode_info_set_instmap(InstMap, ModeInfo1, ModeInfo)
|
|
;
|
|
ModeInfo = ModeInfo0
|
|
).
|
|
|
|
:- pred unique_modes__check_goal_2(hlds_goal_expr, hlds_goal_info,
|
|
hlds_goal_expr, mode_info, mode_info).
|
|
:- mode unique_modes__check_goal_2(in, in, out, mode_info_di, mode_info_uo)
|
|
is det.
|
|
|
|
unique_modes__check_goal_2(conj(List0), _GoalInfo0, conj(List)) -->
|
|
mode_checkpoint(enter, "conj"),
|
|
mode_info_add_goals_live_vars(List0),
|
|
( { List0 = [] } -> % for efficiency, optimize common case
|
|
{ List = [] }
|
|
;
|
|
unique_modes__check_conj(List0, List)
|
|
),
|
|
mode_checkpoint(exit, "conj").
|
|
|
|
unique_modes__check_goal_2(disj(List0, SM), GoalInfo0, disj(List, SM)) -->
|
|
mode_checkpoint(enter, "disj"),
|
|
( { List0 = [] } ->
|
|
{ List = [] },
|
|
{ instmap__init_unreachable(InstMap) },
|
|
mode_info_set_instmap(InstMap)
|
|
;
|
|
{ goal_info_get_nonlocals(GoalInfo0, NonLocals) },
|
|
{ goal_info_get_code_model(GoalInfo0, CodeModel) },
|
|
% does this disjunction create a choice point?
|
|
( { CodeModel = model_non } ->
|
|
%
|
|
% Mark all the variables which are nondet-live at the
|
|
% start of the disjunction and whose inst is `unique'
|
|
% as instead being only `mostly_unique'.
|
|
%
|
|
mode_info_add_live_vars(NonLocals),
|
|
make_all_nondet_live_vars_mostly_uniq,
|
|
mode_info_remove_live_vars(NonLocals)
|
|
;
|
|
[]
|
|
),
|
|
|
|
%
|
|
% Now just modecheck each disjunct in turn, and then
|
|
% merge the resulting instmaps.
|
|
%
|
|
unique_modes__check_disj(List0, List, InstMapList),
|
|
instmap__merge(NonLocals, InstMapList, disj)
|
|
),
|
|
mode_checkpoint(exit, "disj").
|
|
|
|
unique_modes__check_goal_2(if_then_else(Vs, A0, B0, C0, SM), GoalInfo0, Goal)
|
|
-->
|
|
mode_checkpoint(enter, "if-then-else"),
|
|
{ goal_info_get_nonlocals(GoalInfo0, NonLocals) },
|
|
{ unique_modes__goal_get_nonlocals(A0, A_Vars) },
|
|
{ unique_modes__goal_get_nonlocals(B0, B_Vars) },
|
|
{ unique_modes__goal_get_nonlocals(C0, C_Vars) },
|
|
mode_info_dcg_get_instmap(InstMap0),
|
|
mode_info_lock_vars(if_then_else, NonLocals),
|
|
|
|
%
|
|
% 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.
|
|
%
|
|
mode_info_add_live_vars(C_Vars),
|
|
=(ModeInfo),
|
|
{ set__to_sorted_list(A_Vars, A_Vars_List) },
|
|
{ select_live_vars(A_Vars_List, ModeInfo, A_Live_Vars) },
|
|
{ A0 = _ - A0_GoalInfo },
|
|
{ goal_info_get_instmap_delta(A0_GoalInfo, A0_DeltaInstMap) },
|
|
{ select_changed_inst_vars(A_Live_Vars, A0_DeltaInstMap, ModeInfo,
|
|
ChangedVars) },
|
|
make_var_list_mostly_uniq(ChangedVars),
|
|
mode_info_remove_live_vars(C_Vars),
|
|
|
|
mode_info_add_live_vars(B_Vars),
|
|
unique_modes__check_goal(A0, A),
|
|
mode_info_remove_live_vars(B_Vars),
|
|
mode_info_unlock_vars(if_then_else, NonLocals),
|
|
% mode_info_dcg_get_instmap(InstMapA),
|
|
unique_modes__check_goal(B0, B),
|
|
mode_info_dcg_get_instmap(InstMapB),
|
|
mode_info_set_instmap(InstMap0),
|
|
unique_modes__check_goal(C0, C),
|
|
mode_info_dcg_get_instmap(InstMapC),
|
|
mode_info_set_instmap(InstMap0),
|
|
instmap__merge(NonLocals, [InstMapB, InstMapC], if_then_else),
|
|
{ Goal = if_then_else(Vs, A, B, C, SM) },
|
|
mode_checkpoint(exit, "if-then-else").
|
|
|
|
unique_modes__check_goal_2(not(A0), GoalInfo0, not(A)) -->
|
|
mode_checkpoint(enter, "not"),
|
|
{ goal_info_get_nonlocals(GoalInfo0, NonLocals) },
|
|
mode_info_dcg_get_instmap(InstMap0),
|
|
{ set__to_sorted_list(NonLocals, NonLocalsList) },
|
|
=(ModeInfo),
|
|
{ select_live_vars(NonLocalsList, ModeInfo, LiveNonLocals) },
|
|
make_var_list_mostly_uniq(LiveNonLocals),
|
|
mode_info_lock_vars(negation, NonLocals),
|
|
unique_modes__check_goal(A0, A),
|
|
mode_info_unlock_vars(negation, NonLocals),
|
|
mode_info_set_instmap(InstMap0),
|
|
mode_checkpoint(exit, "not").
|
|
|
|
unique_modes__check_goal_2(some(Vs, G0), _, some(Vs, G)) -->
|
|
mode_checkpoint(enter, "some"),
|
|
unique_modes__check_goal(G0, G),
|
|
mode_checkpoint(exit, "some").
|
|
|
|
unique_modes__check_goal_2(higher_order_call(PredVar, Args, Types, Modes, Det,
|
|
PredOrFunc), _GoalInfo0, Goal) -->
|
|
mode_checkpoint(enter, "higher-order call"),
|
|
mode_info_set_call_context(higher_order_call(PredOrFunc)),
|
|
{ determinism_components(Det, _, at_most_zero) ->
|
|
NeverSucceeds = yes
|
|
;
|
|
NeverSucceeds = no
|
|
},
|
|
{ determinism_to_code_model(Det, CodeModel) },
|
|
unique_modes__check_call_modes(Args, Modes, CodeModel, NeverSucceeds),
|
|
{ Goal = higher_order_call(PredVar, Args, Types, Modes, Det,
|
|
PredOrFunc) },
|
|
mode_info_unset_call_context,
|
|
mode_checkpoint(exit, "higher-order call").
|
|
|
|
unique_modes__check_goal_2(class_method_call(TCVar, Num, Args, Types, Modes,
|
|
Det), _GoalInfo0, Goal) -->
|
|
mode_checkpoint(enter, "class method call"),
|
|
% Setting the context to `higher_order_call(...)' is a little
|
|
% white lie. However, since there can't really be a unique
|
|
% mode error in a class_method_call, this lie will never be
|
|
% used. There can't be an error because the class_method_call
|
|
% is introduced by the compiler as the body of a class method.
|
|
mode_info_set_call_context(higher_order_call(predicate)),
|
|
{ determinism_components(Det, _, at_most_zero) ->
|
|
NeverSucceeds = yes
|
|
;
|
|
NeverSucceeds = no
|
|
},
|
|
{ determinism_to_code_model(Det, CodeModel) },
|
|
unique_modes__check_call_modes(Args, Modes, CodeModel, NeverSucceeds),
|
|
{ Goal = class_method_call(TCVar, Num, Args, Types, Modes, Det) },
|
|
mode_info_unset_call_context,
|
|
mode_checkpoint(exit, "class method call").
|
|
|
|
unique_modes__check_goal_2(call(PredId, ProcId0, Args, Builtin, CallContext,
|
|
PredName), _GoalInfo0, Goal) -->
|
|
mode_checkpoint(enter, "call"),
|
|
mode_info_set_call_context(call(PredId)),
|
|
unique_modes__check_call(PredId, ProcId0, Args, ProcId),
|
|
{ Goal = call(PredId, ProcId, Args, Builtin, CallContext, PredName) },
|
|
mode_info_unset_call_context,
|
|
mode_checkpoint(exit, "call").
|
|
|
|
unique_modes__check_goal_2(unify(A0, B0, _, UnifyInfo0, UnifyContext),
|
|
GoalInfo0, Goal) -->
|
|
mode_checkpoint(enter, "unify"),
|
|
mode_info_set_call_context(unify(UnifyContext)),
|
|
|
|
modecheck_unification(A0, B0, UnifyInfo0, UnifyContext, GoalInfo0,
|
|
check_unique_modes, Goal),
|
|
|
|
mode_info_unset_call_context,
|
|
mode_checkpoint(exit, "unify").
|
|
|
|
unique_modes__check_goal_2(switch(Var, CanFail, Cases0, SM), GoalInfo0,
|
|
switch(Var, CanFail, Cases, SM)) -->
|
|
mode_checkpoint(enter, "switch"),
|
|
( { Cases0 = [] } ->
|
|
{ Cases = [] },
|
|
{ instmap__init_unreachable(InstMap) },
|
|
mode_info_set_instmap(InstMap)
|
|
;
|
|
{ goal_info_get_nonlocals(GoalInfo0, NonLocals) },
|
|
unique_modes__check_case_list(Cases0, Var, Cases, InstMapList),
|
|
instmap__merge(NonLocals, InstMapList, disj)
|
|
),
|
|
mode_checkpoint(exit, "switch").
|
|
|
|
% to modecheck a pragma_c_code, we just modecheck the proc for
|
|
% which it is the goal.
|
|
unique_modes__check_goal_2(pragma_c_code(IsRecursive, PredId, ProcId0,
|
|
Args, ArgNameMap, OrigArgTypes, PragmaCode),
|
|
_GoalInfo, Goal) -->
|
|
mode_checkpoint(enter, "pragma_c_code"),
|
|
mode_info_set_call_context(call(PredId)),
|
|
unique_modes__check_call(PredId, ProcId0, Args, ProcId),
|
|
{ Goal = pragma_c_code(IsRecursive, PredId, ProcId, Args,
|
|
ArgNameMap, OrigArgTypes, PragmaCode) },
|
|
mode_info_unset_call_context,
|
|
mode_checkpoint(exit, "pragma_c_code").
|
|
|
|
:- pred unique_modes__check_call(pred_id, proc_id, list(var), proc_id,
|
|
mode_info, mode_info).
|
|
:- mode unique_modes__check_call(in, in, in, out,
|
|
mode_info_di, mode_info_uo) is det.
|
|
|
|
unique_modes__check_call(PredId, ProcId0, ArgVars, ProcId,
|
|
ModeInfo0, ModeInfo) :-
|
|
%
|
|
% set the error list to empty for use below
|
|
% (saving the old error list and instmap in variables)
|
|
%
|
|
mode_info_get_errors(ModeInfo0, OldErrors),
|
|
mode_info_get_instmap(ModeInfo0, InstMap0),
|
|
mode_info_set_errors([], ModeInfo0, ModeInfo1),
|
|
|
|
%
|
|
% first off, try using the existing mode
|
|
%
|
|
mode_info_get_module_info(ModeInfo0, ModuleInfo),
|
|
module_info_pred_proc_info(ModuleInfo, PredId, ProcId0, _, ProcInfo),
|
|
proc_info_argmodes(ProcInfo, ProcArgModes0),
|
|
proc_info_interface_code_model(ProcInfo, CodeModel),
|
|
proc_info_never_succeeds(ProcInfo, NeverSucceeds),
|
|
unique_modes__check_call_modes(ArgVars, ProcArgModes0, CodeModel,
|
|
NeverSucceeds, ModeInfo1, ModeInfo2),
|
|
|
|
%
|
|
% see whether or not that worked
|
|
% (and restore the old error list)
|
|
%
|
|
mode_info_get_errors(ModeInfo2, Errors),
|
|
mode_info_set_errors(OldErrors, ModeInfo2, ModeInfo3),
|
|
( Errors = [] ->
|
|
ProcId = ProcId0,
|
|
ModeInfo = ModeInfo3
|
|
;
|
|
%
|
|
% 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, ModeInfo3, ModeInfo4),
|
|
proc_info_inferred_determinism(ProcInfo, Determinism),
|
|
modecheck_call_pred(PredId, ArgVars, yes(Determinism),
|
|
ProcId, NewArgVars, ExtraGoals, ModeInfo4, 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(var), list(mode), code_model, bool,
|
|
mode_info, mode_info).
|
|
:- mode unique_modes__check_call_modes(in, in, in, in,
|
|
mode_info_di, mode_info_uo) is det.
|
|
|
|
unique_modes__check_call_modes(ArgVars, ProcArgModes, CodeModel, NeverSucceeds,
|
|
ModeInfo0, ModeInfo) :-
|
|
mode_info_get_module_info(ModeInfo0, ModuleInfo),
|
|
mode_list_get_initial_insts(ProcArgModes, ModuleInfo,
|
|
InitialInsts),
|
|
modecheck_var_has_inst_list(ArgVars, InitialInsts, 0,
|
|
ModeInfo0, ModeInfo1),
|
|
mode_list_get_final_insts(ProcArgModes, ModuleInfo, FinalInsts),
|
|
modecheck_set_var_inst_list(ArgVars, InitialInsts, FinalInsts,
|
|
NewArgVars, ExtraGoals, ModeInfo1, ModeInfo2),
|
|
( 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, ModeInfo2, 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'.
|
|
%
|
|
( CodeModel = model_non ->
|
|
make_all_nondet_live_vars_mostly_uniq(ModeInfo2,
|
|
ModeInfo)
|
|
;
|
|
ModeInfo = ModeInfo2
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred unique_modes__check_conj(list(hlds_goal), list(hlds_goal),
|
|
mode_info, mode_info).
|
|
:- mode unique_modes__check_conj(in, out, mode_info_di, mode_info_uo) is det.
|
|
|
|
% Just process each conjunct in turn.
|
|
% Note that we don't do any reordering of conjuncts here.
|
|
|
|
unique_modes__check_conj([], []) --> [].
|
|
unique_modes__check_conj([Goal0 | Goals0], [Goal | Goals]) -->
|
|
{ unique_modes__goal_get_nonlocals(Goal0, NonLocals) },
|
|
mode_info_remove_live_vars(NonLocals),
|
|
unique_modes__check_goal(Goal0, Goal),
|
|
unique_modes__check_conj(Goals0, Goals).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% 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), list(hlds_goal),
|
|
list(instmap), mode_info, mode_info).
|
|
:- mode unique_modes__check_disj(in, out, out, mode_info_di, mode_info_uo)
|
|
is det.
|
|
|
|
unique_modes__check_disj([], [], []) --> [].
|
|
unique_modes__check_disj([Goal0 | Goals0], [Goal | Goals],
|
|
[InstMap | InstMaps]) -->
|
|
mode_info_dcg_get_instmap(InstMap0),
|
|
unique_modes__check_goal(Goal0, Goal),
|
|
mode_info_dcg_get_instmap(InstMap),
|
|
mode_info_set_instmap(InstMap0),
|
|
unique_modes__check_disj(Goals0, Goals, InstMaps).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred unique_modes__check_case_list(list(case), var, list(case),
|
|
list(instmap), mode_info, mode_info).
|
|
:- mode unique_modes__check_case_list(in, in, out, out,
|
|
mode_info_di, mode_info_uo) is det.
|
|
|
|
unique_modes__check_case_list([], _Var, [], []) --> [].
|
|
unique_modes__check_case_list([Case0 | Cases0], Var,
|
|
[Case | Cases], [InstMap | InstMaps]) -->
|
|
{ Case0 = case(ConsId, Goal0) },
|
|
{ Case = case(ConsId, Goal) },
|
|
mode_info_dcg_get_instmap(InstMap0),
|
|
|
|
% record the fact that Var was bound to ConsId in the
|
|
% instmap before processing this case
|
|
{ cons_id_arity(ConsId, Arity) },
|
|
{ list__duplicate(Arity, free, ArgInsts) },
|
|
modecheck_set_var_inst(Var,
|
|
bound(unique, [functor(ConsId, ArgInsts)])),
|
|
|
|
unique_modes__check_goal(Goal0, Goal1),
|
|
mode_info_dcg_get_instmap(InstMap),
|
|
{ fixup_switch_var(Var, InstMap0, InstMap, Goal1, Goal) },
|
|
mode_info_set_instmap(InstMap0),
|
|
unique_modes__check_case_list(Cases0, Var, Cases, InstMaps).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred unique_modes__goal_get_nonlocals(hlds_goal, set(var)).
|
|
:- mode unique_modes__goal_get_nonlocals(in, out) is det.
|
|
|
|
unique_modes__goal_get_nonlocals(_Goal - GoalInfo, NonLocals) :-
|
|
goal_info_get_nonlocals(GoalInfo, NonLocals).
|
|
|
|
%-----------------------------------------------------------------------------%
|