Files
mercury/compiler/state_var.m
Julien Fischer 499d193580 Fix a typo.
compiler/state_vars.m:
    s/errrors/errors/
2025-11-05 21:41:27 +11:00

2881 lines
125 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2005-2011 The University of Melbourne.
% Copyright (C) 2014-2016, 2018-2025 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: state_var.m.
% Main author of original version: rafe.
% Main author of the current version, rewritten in 2011: zs.
%
%---------------------------------------------------------------------------%
:- module hlds.make_hlds.state_var.
:- interface.
:- import_module hlds.hlds_clauses.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_module.
:- import_module hlds.make_hlds.goal_expr_to_goal.
:- import_module hlds.make_hlds.qual_info.
:- import_module mdbcomp.
:- import_module mdbcomp.prim_data.
:- import_module parse_tree.
:- import_module parse_tree.error_spec.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_item.
:- import_module list.
:- import_module map.
%---------------------------------------------------------------------------%
% This type describes the state of the code that converts goals
% from their parse tree form to their HLDS form. Almost all the code
% in all the modules of the make_hlds package that handle goals
% pass around values of this type as effectively global state,
% with persistent updates. (The state of the state var transformation
% itself is threaded through that code in a different manner;
% see the definition of the svar_state type below.)
%
% With one exception, all of the fields are writeable.
:- type unravel_info
---> unravel_info(
% The module_info, which we use for several purposes.
% Most uses are readonly, including getting the globals
% for option lookup, and the module name for creating
% debug output streams.
%
% The only situation in which we update the module_info field
% is when processing disable_warning scopes that disable
% the warning for occurs check violations. In that case,
% we set the option controlling that warning to "no"
% while processing the goal in the scope, and reset it
% afterwards. Such scopes are rare enough that storing the
% value of that option as a separate field in this structure
% would not be worthwhile.
ui_module_info :: module_info,
% The value of the from_ground_term_threshold option.
% This field duplicates the value stored in the globals
% structure, but it is needed often enough that a separate
% fast-access copy is worthwhile.
% This field is read-only.
ui_fgt_threshold :: int,
% The store where we record information about what entities
% imported from other modules are used. We use that info
% to generate warnings about unused imports.
ui_qual_info :: qual_info,
% The varset of the clause whose goal we are converting.
% New instances of state variables are allocated from here.
ui_varset :: prog_varset,
% The part of the state of the state var transformation
% that is updated persistently (meaning, that once we create
% a new version, we don't go back to look at previous
% versions.)
ui_state_var_store :: svar_store,
% The errors and warnings that we definitely want to print.
% (The svar_store also contains error_specs, but we print those
% only as hints *if and when* we later find certain other kinds
% of errors.)
ui_error_specs :: list(error_spec)
).
%---------------------------------------------------------------------------%
:- pred create_new_unravel_var(prog_var::out,
unravel_info::in, unravel_info::out) is det.
:- pred create_new_named_unravel_var(string::in, prog_var::out,
unravel_info::in, unravel_info::out) is det.
:- pred record_unravel_found_syntax_error(
unravel_info::in, unravel_info::out) is det.
:- pred add_unravel_spec(error_spec::in,
unravel_info::in, unravel_info::out) is det.
:- pred add_unravel_specs(list(error_spec)::in,
unravel_info::in, unravel_info::out) is det.
%---------------------------------------------------------------------------%
% This synonym improves code legibility. The intention is that we use
% svar instead of prog_var in pred type declarations for any variables X
% that represent state variables !X.
:- type svar == prog_var.
% When collecting the arms of a disjunction, we also need to collect
% the resulting svar_states.
:- type hlds_goal_svar_state
---> hlds_goal_svar_state(hlds_goal, svar_state).
% The state of the currently visible state variables. The state gets
% updated differently along differently execution paths. When execution
% paths rejoin, you need to create the state after the rejoin from the
% states being rejoined (which is what we use hlds_goal_svar_state for)
% using their last common ancestor state as a basis.
:- type svar_state.
% The persistent information needed by the state variable transformation.
% The store should always be threaded straight through all computations
% involved in the translation of the parse tree to the HLDS, with all
% updates being permanent.
:- type svar_store.
%---------------------------------------------------------------------------%
% Given the user-given name of a state variable, return the name
% of the program variable representing its initial version.
%
:- func initial_state_var_name(string) = string.
% is_prog_var_for_some_state_var(VarSet, ProgVar, StateVarName):
%
% Succeed if and only if ProgVar's name indicates that it represents
% a version of a state variable named StateVarName. Succeed for
% any version: initial, middle, or final.
%
:- pred is_prog_var_for_some_state_var(prog_varset::in, prog_var::in,
string::out) is semidet.
%---------------------------------------------------------------------------%
% Replace !X args with two args !.X, !:X in that order.
%
:- pred expand_bang_state_pairs_in_terms(list(prog_term)::in,
list(prog_term)::out) is det.
:- pred expand_bang_state_pairs_in_instance_method(instance_method::in,
instance_method::out) is det.
%---------------------------------------------------------------------------%
:- type new_statevar_map.
% Prepare for processing a clause by processing its head.
% If the head contains any references to !.S or !:S or both,
% make state variable S known in the body of the clause.
% (The head should not contain any references to !S; those should
% have been expanded out by calling expand_bang_state_pairs BEFORE calling
% this predicate.)
%
% Given the original list of args, we return a version in which state
% variable references have been replaced. Since we don't yet know what
% the final values of the state variables will be, we create prog_vars
% to represent these values, and return a mapping from the state vars
% to these designated-final-value prog_vars.
%
:- pred svar_prepare_for_clause_head(module_info::in, qual_info::in,
prog_varset::in, list(prog_term)::in, list(prog_term)::out,
map(svar, prog_var)::out, new_statevar_map::out,
svar_state::out, unravel_info::out) is det.
% Prepare for processing a lambda expression by processing its head.
%
% In most ways, this is very similar to processing the head of a clause,
% but we also need to handle state variables which are visible in the scope
% that encloses the lambda expression. We make those state vars read-only
% within the lambda expression.
%
:- pred svar_prepare_for_lambda_head(prog_context::in,
list(prog_term)::in, list(prog_term)::out,
map(svar, prog_var)::out, new_statevar_map::out,
svar_state::in, svar_state::out,
unravel_info::in, unravel_info::out) is det.
%---------------------%
% Finish processing a clause. Make the final values of the clause's state
% vars match the mapping we decided on when processing the head.
%
:- pred svar_finish_clause_body(prog_context::in, new_statevar_map::in,
map(svar, prog_var)::in, svar_state::in, svar_state::in,
hlds_goal::in, hlds_goal::in, hlds_goal::out,
list(error_spec)::out, unused_statevar_arg_map::out,
unravel_info::in, unravel_info::out) is det.
% Finish processing a lambda expression.
%
:- pred svar_finish_lambda_body(prog_context::in, list(mer_mode)::in,
new_statevar_map::in, map(svar, prog_var)::in, goal::in,
list(hlds_goal)::in, hlds_goal::out, svar_state::in, svar_state::in,
unravel_info::in, unravel_info::out) is det.
%---------------------------------------------------------------------------%
% Finish the execution of an atomic goal. If this goal was not inside
% another atomic goal, then make any updates to state variables performed
% by the atomic goal take effect: make the value assigned to !:S inside
% the goal the new !.S.
%
:- pred svar_finish_atomic_goal(loc_kind::in, svar_state::in, svar_state::out)
is det.
%---------------------------------------------------------------------------%
% Add some local state variables.
%
:- pred svar_prepare_for_local_state_vars(prog_context::in, list(svar)::in,
svar_state::in, svar_state::out,
unravel_info::in, unravel_info::out) is det.
% Remove some local state variables.
%
:- pred svar_finish_local_state_vars(unravel_info::in,
list(svar)::in, svar_state::in, svar_state::in, svar_state::out) is det.
%---------------------------------------------------------------------------%
% Make sure that all arms of a disjunction produce the same state variable
% bindings, by adding unifiers as necessary.
%
:- pred svar_finish_disjunction(list(hlds_goal_svar_state)::in,
list(hlds_goal)::out, svar_state::in, svar_state::out,
unravel_info::in, unravel_info::out) is det.
%---------------------------------------------------------------------------%
% Add unifiers to the Then and Else arms of an if-then-else as needed
% to ensure that all the state variables match up.
%
% We also add unifiers to the Then arm for any new state variable
% mappings produced in the condition.
%
:- pred svar_finish_if_then_else(loc_kind::in, prog_context::in,
list(svar)::in,
hlds_goal::in, hlds_goal::out, hlds_goal::in, hlds_goal::out,
svar_state::in, svar_state::in, svar_state::in, svar_state::in,
svar_state::out,
unravel_info::in, unravel_info::out) is det.
%---------------------------------------------------------------------------%
:- type svar_outer_atomic_scope_info.
:- type svar_inner_atomic_scope_info.
% svar_start_outer_atomic_scope(Context, OuterStateVar, OuterDI, OuterUO,
% OuterScopeInfo, !SVarState, !UrInfo):
%
% This predicate converts a !OuterStateVar specification in an atomic scope
% to a pair of outer state variables, OuterDI and OuterUO. Since
% !OuterStateVar should *not* be accessible inside the atomic scope,
% we delete it, but record it in OuterScopeInfo. The accessibility of
% !OuterStateVar will be restored when you call svar_finish_atomic_scope
% with OuterScopeInfo.
%
:- pred svar_start_outer_atomic_scope(prog_context::in, prog_var::in,
prog_var::out, prog_var::out, svar_outer_atomic_scope_info::out,
svar_state::in, svar_state::out,
unravel_info::in, unravel_info::out) is det.
% svar_finish_outer_atomic_scope(OuterScopeInfo, !SInfo):
%
% Restore the accessibility of !OuterStateVar that was disabled by
% svar_start_atomic_scope.
%
:- pred svar_finish_outer_atomic_scope(svar_outer_atomic_scope_info::in,
svar_state::in, svar_state::out) is det.
% svar_start_inner_atomic_scope(Context, InnerStateVar, InnerScopeInfo,
% !SVarState, !UrInfo):
%
% This predicate prepares for an atomic scope with an !InnerStateVar
% specification by making that state var available.
%
:- pred svar_start_inner_atomic_scope(prog_context::in, prog_var::in,
svar_inner_atomic_scope_info::out,
svar_state::in, svar_state::out,
unravel_info::in, unravel_info::out) is det.
% svar_finish_inner_atomic_scope(Context, InnerScopeInfo, InnerDI, InnerUO,
% !SVarState, !UrInfo):
%
% This predicate ends an atomic scope with an !InnerStateVar
% specification by making that state var unavailable, and returning
% the two variables InnerDI and InnerUO representing the initial and final
% states of this state variable.
%
:- pred svar_finish_inner_atomic_scope(prog_context::in,
svar_inner_atomic_scope_info::in, prog_var::out, prog_var::out,
svar_state::in, svar_state::out) is det.
%---------------------------------------------------------------------------%
% Given a list of argument terms, replace !.X and !:X with the
% ordinary variables corresponding to them, updating the svar_state
% as appropriate. Any occurrence of !X should already have been
% expanded into a <!.X, !:X> pair by a call to expand_bang_state_pairs.
%
:- pred replace_any_dot_colon_state_var_in_terms(
list(prog_term)::in, list(prog_term)::out,
svar_state::in, svar_state::out,
unravel_info::in, unravel_info::out) is det.
% Same as replace_any_dot_colon_state_var_in_terms, but for only one term.
%
:- pred replace_any_dot_colon_state_var_in_term(prog_term::in, prog_term::out,
svar_state::in, svar_state::out,
unravel_info::in, unravel_info::out) is det.
% Look up the prog_var that represents the current state of the given
% state variable.
%
:- pred lookup_dot_state_var(prog_context::in, svar::in, prog_var::out,
svar_state::in, svar_state::out,
unravel_info::in, unravel_info::out) is det.
% Look up the prog_var that represents the next state of the given
% state variable.
%
:- pred lookup_colon_state_var(prog_context::in, svar::in, prog_var::out,
svar_state::in, svar_state::out,
unravel_info::in, unravel_info::out) is det.
%---------------------------------------------------------------------------%
% Flatten a conjunction while preserving the invariants that the state
% variable transformation cares about.
%
:- pred svar_flatten_conj(prog_context::in,
list(hlds_goal)::in, hlds_goal::out,
unravel_info::in, unravel_info::out) is det.
% Flatten a goal into a conjunction while preserving the invariants that
% the state variable transformation cares about.
%
:- pred svar_goal_to_conj_list(hlds_goal::in, list(hlds_goal)::out,
unravel_info::in, unravel_info::out) is det.
%---------------------------------------------------------------------------%
% Does the given argument list have a function result term
% that tries to use state var notation to refer to *two* terms?
%
% If yes, return the state variable involved, and the context of the
% reference.
%
:- pred illegal_state_var_func_result(pred_or_func::in, list(prog_term)::in,
svar::out, prog_context::out) is semidet.
% Does the given term have the form a !X, i.e. does it represent
% *two* arguments? This is not acceptable in some contexts, such as
% function results and lambda expression arguments.
%
% If yes, return the state variable involved, and the context of the
% reference.
%
:- pred is_term_a_bang_state_pair(prog_term::in,
svar::out, prog_context::out) is semidet.
%---------------------------------------------------------------------------%
:- pred report_illegal_state_var_update(prog_context::in,
string::in, prog_context::in, svar::in,
unravel_info::in, unravel_info::out) is det.
:- pred report_illegal_func_svar_result(prog_context::in, svar::in,
unravel_info::in, unravel_info::out) is det.
:- func report_illegal_func_svar_result_raw(prog_context,
prog_varset, svar) = error_spec.
:- pred report_illegal_bang_svar_lambda_arg(prog_context::in, svar::in,
unravel_info::in, unravel_info::out) is det.
:- func report_illegal_bang_svar_lambda_arg_raw(prog_context,
prog_varset, svar) = error_spec.
:- pred report_svar_unify_error(prog_context::in, svar::in,
svar_state::in, svar_state::out,
unravel_info::in, unravel_info::out) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module hlds.goal_vars.
:- import_module hlds.hlds_markers.
:- import_module hlds.make_goal.
:- import_module hlds.mode_util.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.optimization_options.
:- import_module libs.options.
:- import_module mdbcomp.goal_path.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.prog_util.
:- import_module parse_tree.set_of_var.
:- import_module assoc_list.
:- import_module bool.
:- import_module cord.
:- import_module counter.
:- import_module io.
:- import_module maybe.
:- import_module one_or_more.
:- import_module one_or_more_map.
:- import_module pair.
:- import_module require.
:- import_module string.
:- import_module term.
:- import_module term_context.
:- import_module uint.
:- import_module varset.
%---------------------------------------------------------------------------%
create_new_unravel_var(Var, !UrInfo) :-
VarSet0 = !.UrInfo ^ ui_varset,
varset.new_var(Var, VarSet0, VarSet),
!UrInfo ^ ui_varset := VarSet.
create_new_named_unravel_var(Name, Var, !UrInfo) :-
VarSet0 = !.UrInfo ^ ui_varset,
varset.new_named_var(Name, Var, VarSet0, VarSet),
!UrInfo ^ ui_varset := VarSet.
record_unravel_found_syntax_error(!UrInfo) :-
QualInfo0 = !.UrInfo ^ ui_qual_info,
qual_info_set_found_syntax_error(yes, QualInfo0, QualInfo),
!UrInfo ^ ui_qual_info := QualInfo.
add_unravel_spec(NewSpec, !UrInfo) :-
Specs0 = !.UrInfo ^ ui_error_specs,
Specs = [NewSpec | Specs0],
!UrInfo ^ ui_error_specs := Specs.
add_unravel_specs(NewSpecs, !UrInfo) :-
(
NewSpecs = []
;
NewSpecs = [_ | _],
Specs0 = !.UrInfo ^ ui_error_specs,
Specs = NewSpecs ++ Specs0,
!UrInfo ^ ui_error_specs := Specs
).
%---------------------------------------------------------------------------%
%
% Define the main data structures used by the implementation of state vars.
%
% State vars defined outside a lambda goal become readonly when we move
% inside the lambda goal. Inside the lambda goal, it makes sense to access
% the current value of such a state var, but it does not make sense
% to try to update its value.
%
% We should make negations behave similarly: it should not be possible
% to update an outside state var inside a negation. However, for now,
% the language reference manual allows such updates. This type is here
% in case that changes.
:- type readonly_context_kind
---> roc_lambda.
:- type svar_status
% The two updated statuses may legally be present in a status map
% only DURING the processing of an atomic goal. At the end of each
% atomic goal, such statuses are always reset to status_known.
---> status_unknown
% We are in a scope that allows use of this state var,
% but it has not been given a value yet. This could be because
% the scope of the state var was established with !:S, not !S
% or !.S, in a clause head, or because it was established
% in a `some [!S]' scope.
; status_unknown_updated(prog_var)
% Before this atomic goal, this state var was status_unknown,
% but it was initialized by the current atomic goal to the given
% prog_var.
; status_known_ro(prog_var, readonly_context_kind, prog_context)
% The given prog_var is the current version of this state var,
% but the variable is readonly (ro); the program CANNOT create
% new versions of the state var. The second argument says WHY
% new versions cannot be created, and the third says where
% the construct named by the second argument occurs.
; status_known(prog_var)
% The given prog_var is the current version of this state var;
% the program can create new versions of the state var,
% but has not done so yet.
; status_known_updated(prog_var, prog_var).
% The first prog_var is the current version of this state var,
% and the second is the new, updated version, which will become
% the current version when we finish executing the current
% atomic goal.
:- type svar_state
---> svar_state(
state_status_map :: map(svar, svar_status)
).
:- type last_id_map == map(prog_var, uint).
:- type svar_store
---> svar_store(
store_next_goal_id :: ucounter,
store_final_remap :: incremental_rename_map,
% This maps each state variable that has had N name_middle
% versions created to N, *provided* that N > 0. We use this
% for two purposes:
%
% - Giving a name to the variables representing the middle
% (meaning non-initial and non-final) versions of each
% state variable that is affected only by references to
% that state variable. We used to use as the variable
% id assigned by the current varset, but this meant that
% changes in the number and/or handling of non-state
% variables in the clause could affect the variable names
% reported by error messages, requiring annoying updates to
% test cases .err_exp* files.
%
% - We use the absence of an entry for a state variable
% as an indication that this state variable has not been
% updated. This can seem a bit strange, since it is possible
% for a clause body to invoke a predicate that takes the
% state variable from its initial state to its final state
% directly, but even if that ends up being the case,
% it will not have started out that way. Instead, when
% processing the call, we will allocate a new middle version
% for the state var, and only when we finish the processing
% of the clause as a whole will we replace that middle
% version with the final version. And when we do, the
% record of the allocated-but-substitured-out middle version
% will remain in this field.
store_last_id_map :: last_id_map,
% We use this slot to store informational messages that
% we want to print *only* in the presence of mode errors,
% because
%
% - in the presence of mode errors, they can sometimes
% pinpoint the *cause* of some of those errors, but
% - in the absence of mode errors, the messages would be
% only clutter.
%
% As of 2025 feb 20, these messages are all about situations
% in which a disjunction or an if-then-else initialises
% a state variable in some branches but not in others.
store_missing_init_specs :: list(error_spec)
).
% Create a new svar_state/store set up to start processing a clause head.
%
:- func new_svar_state = svar_state.
:- func new_svar_store = svar_store.
new_svar_state = svar_state(map.init).
new_svar_store = svar_store(counter.uinit(1u), map.init, map.init, []).
:- type state_var_name_source
---> name_initial
; name_middle
; name_final.
:- pred new_state_var_instance(svar::in, state_var_name_source::in,
prog_var::out, unravel_info::in, unravel_info::out) is det.
new_state_var_instance(StateVar, NameSource, Var, !UrInfo) :-
VarSet0 = !.UrInfo ^ ui_varset,
SVarName = varset.lookup_name(VarSet0, StateVar),
(
NameSource = name_initial,
ProgVarName = initial_state_var_name(SVarName),
varset.new_named_var(ProgVarName, Var, VarSet0, VarSet)
;
NameSource = name_middle,
SVarState0 = !.UrInfo ^ ui_state_var_store,
LastIdMap0 = SVarState0 ^ store_last_id_map,
( if map.search(LastIdMap0, StateVar, LastId0) then
CurId = LastId0 + 1u,
map.det_update(StateVar, CurId, LastIdMap0, LastIdMap)
else
CurId = 1u,
map.det_insert(StateVar, 1u, LastIdMap0, LastIdMap)
),
SVarState = SVarState0 ^ store_last_id_map := LastIdMap,
!UrInfo ^ ui_state_var_store := SVarState,
ProgVarName = string.format("STATE_VARIABLE_%s_%u",
[s(SVarName), u(CurId)]),
varset.new_named_var(ProgVarName, Var, VarSet0, VarSet)
;
NameSource = name_final,
ProgVarName = string.format("STATE_VARIABLE_%s", [s(SVarName)]),
varset.new_named_var(ProgVarName, Var, VarSet0, VarSet)
),
!UrInfo ^ ui_varset := VarSet.
initial_state_var_name(SVarName) = ProgVarName :-
ProgVarName = string.format("STATE_VARIABLE_%s_0", [s(SVarName)]).
is_prog_var_for_some_state_var(VarSet, Var, SVarName) :-
% All prog_vars representing state vars are named.
varset.search_name(VarSet, Var, VarName),
string.remove_prefix("STATE_VARIABLE_", VarName, AfterStdPrefix),
UnderscoreSeparatedPieces = string.split_at_char('_', AfterStdPrefix),
(
UnderscoreSeparatedPieces = [],
% There is no underscore, so there can be no numerical suffix.
SVarName = AfterStdPrefix
;
UnderscoreSeparatedPieces = [_ | _],
list.det_last(UnderscoreSeparatedPieces, LastPiece),
string.to_int(LastPiece, _N),
% This is either the initial or a middle version of SVarName.
% (Initial if _N is zero, and middle otherwise)
NumericalSuffix = "_" ++ LastPiece,
SVarName = string.det_remove_suffix(AfterStdPrefix, NumericalSuffix)
).
% is_prog_var_for_state_var(VarSet, StateVarName, ProgVar):
%
% Succeed if and only if ProgVar's name indicates that it represents
% a version of the state variable named !StateVarName. Succeed for
% any version: initial, middle, or final.
%
% This was a first attempt at solving the problem that the predicate above
% is also addressing, before I realized that collecting all referenced
% state variables permits a simpler design in our caller than having to
% repeat the test (on *every* variable in a clauses_info's varset) for
% every state var of interest to the code generating unneeded state
% warnings.
%
:- pred is_prog_var_for_state_var(prog_varset::in, string::in, prog_var::in)
is semidet.
:- pragma consider_used(pred(is_prog_var_for_state_var/3)).
is_prog_var_for_state_var(VarSet, SVarName, Var) :-
% All prog_vars representing state vars are named.
varset.search_name(VarSet, Var, VarName),
string.remove_prefix("STATE_VARIABLE_", VarName, AfterStdPrefix),
string.remove_prefix(SVarName, AfterStdPrefix, Suffix),
(
Suffix = ""
% This is the final version of SVarName.
;
string.remove_prefix("_", Suffix, SuffixAfterUnderscore),
string.to_int(SuffixAfterUnderscore, _N)
% This is either the initial or a middle version of SVarName.
% (Initial if _N is zero, and middle otherwise)
).
%---------------------------------------------------------------------------%
%
% Expand !S into !.S, !:S pairs.
%
expand_bang_state_pairs_in_terms([], []).
expand_bang_state_pairs_in_terms([HeadArg0 | TailArgs0], Args) :-
expand_bang_state_pairs_in_terms(TailArgs0, TailArgs),
(
HeadArg0 = variable(_, _),
Args = [HeadArg0 | TailArgs]
;
HeadArg0 = functor(Const, FunctorArgs, Context),
( if
Const = atom("!"),
FunctorArgs = [variable(_StateVar, _)]
then
HeadArg1 = functor(atom("!."), FunctorArgs, Context),
HeadArg2 = functor(atom("!:"), FunctorArgs, Context),
Args = [HeadArg1, HeadArg2 | TailArgs]
else
Args = [HeadArg0 | TailArgs]
)
).
expand_bang_state_pairs_in_instance_method(IM0, IM) :-
IM0 = instance_method(MethodId0, ProcDef0, Context),
MethodId0 = pred_pf_name_arity(PredOrFunc, MethodSymName, _UserArity0),
(
ProcDef0 = instance_proc_def_name(_),
IM = IM0
;
ProcDef0 = instance_proc_def_clauses(ItemClausesCord0),
cord.map_pred(expand_bang_state_pairs_in_clause,
ItemClausesCord0, ItemClausesCord),
% Note that ItemClausesCord0 should never be empty...
( if cord.head(ItemClausesCord, ItemClause) then
Args = ItemClause ^ cl_head_args,
PredFormArity = arg_list_arity(Args),
user_arity_pred_form_arity(PredOrFunc, UserArity, PredFormArity),
MethodId = pred_pf_name_arity(PredOrFunc, MethodSymName, UserArity)
else
MethodId = MethodId0
),
ProcDef = instance_proc_def_clauses(ItemClausesCord),
IM = instance_method(MethodId, ProcDef, Context)
).
:- pred expand_bang_state_pairs_in_clause(item_clause_info::in,
item_clause_info::out) is det.
expand_bang_state_pairs_in_clause(ItemClause0, ItemClause) :-
ItemClause0 = item_clause_info(PredOrFunc, SymName, Args0, VarSet,
MaybeBody, Context, SeqNum),
expand_bang_state_pairs_in_terms(Args0, Args),
ItemClause = item_clause_info(PredOrFunc, SymName, Args, VarSet,
MaybeBody, Context, SeqNum).
%---------------------------------------------------------------------------%
%
% Handle the start of processing a clause.
%
:- type maybe_statevar_arg_pos
---> arg_old(uint)
; arg_new(uint)
; non_arg.
:- type new_statevar_map ==
one_or_more_map(prog_var, maybe_statevar_arg_pos).
:- func init_new_statevar_map = new_statevar_map.
init_new_statevar_map = one_or_more_map.init.
svar_prepare_for_clause_head(ModuleInfo0, QualInfo0, VarSet0,
Args0, Args, FinalMap, NewSVars, !:SVarState, !:UrInfo) :-
module_info_get_globals(ModuleInfo0, Globals),
globals.get_opt_tuple(Globals, OptTuple),
Threshold = OptTuple ^ ot_from_ground_term_threshold,
!:SVarState = new_svar_state,
SVarStore0 = new_svar_store,
Specs0 = [],
!:UrInfo = unravel_info(ModuleInfo0, Threshold, QualInfo0, VarSet0,
SVarStore0, Specs0),
svar_prepare_head_terms(0u, 1u, Args0, Args, map.init, FinalMap,
!SVarState, init_new_statevar_map, NewSVars, !UrInfo).
:- pred svar_prepare_head_terms(uint::in, uint::in,
list(prog_term)::in, list(prog_term)::out,
map(svar, prog_var)::in, map(svar, prog_var)::out,
svar_state::in, svar_state::out,
new_statevar_map::in, new_statevar_map::out,
unravel_info::in, unravel_info::out) is det.
svar_prepare_head_terms(_, _, [], [],
!FinalMap, !SVarState, !NewSVars, !UrInfo).
svar_prepare_head_terms(CurDepth, CurArgNum, [Term0 | Terms0], [Term | Terms],
!FinalMap, !SVarState, !NewSVars, !UrInfo) :-
svar_prepare_head_term(CurDepth, CurArgNum, Term0, Term,
!FinalMap, !SVarState, !NewSVars, !UrInfo),
svar_prepare_head_terms(CurDepth, CurArgNum + 1u, Terms0, Terms,
!FinalMap, !SVarState, !NewSVars, !UrInfo).
:- pred svar_prepare_head_term(uint::in, uint::in,
prog_term::in, prog_term::out,
map(svar, prog_var)::in, map(svar, prog_var)::out,
svar_state::in, svar_state::out,
new_statevar_map::in, new_statevar_map::out,
unravel_info::in, unravel_info::out) is det.
svar_prepare_head_term(CurDepth, CurArgNum, Term0, Term,
!FinalMap, !SVarState, !NewSVars, !UrInfo) :-
(
Term0 = variable(_, _),
Term = Term0
;
Term0 = functor(Functor, SubTerms0, Context),
( if
Functor = atom("!."),
SubTerms0 = [variable(StateVar, _)]
then
!.SVarState = svar_state(StatusMap0),
( if map.search(StatusMap0, StateVar, OldStatus) then
(
OldStatus = status_unknown,
% !:S happened to precede !.S in the head, which is ok.
new_state_var_instance(StateVar, name_initial, Var,
!UrInfo),
Term = variable(Var, Context),
Status = status_known(Var),
map.det_update(StateVar, Status, StatusMap0, StatusMap)
;
OldStatus = status_known(Var),
Term = variable(Var, Context),
StatusMap = StatusMap0
;
OldStatus = status_unknown_updated(_),
unexpected($pred, "status_unknown_updated for !.")
;
OldStatus = status_known_updated(_, _),
unexpected($pred, "status_known_updated for !.")
;
OldStatus = status_known_ro(_, _, _),
% This can happen if the context outside a lambda
% expression has a state variable named StateVar,
% which make_svars_read_only has given this status,
% and the lambda expression itself also has !.StateVar.
new_state_var_instance(StateVar, name_initial, Var,
!UrInfo),
Term = variable(Var, Context),
Status = status_known(Var),
map.det_update(StateVar, Status, StatusMap0, StatusMap)
)
else
new_state_var_instance(StateVar, name_initial, Var, !UrInfo),
Term = variable(Var, Context),
Status = status_known(Var),
map.det_insert(StateVar, Status, StatusMap0, StatusMap)
),
!:SVarState = svar_state(StatusMap),
( if CurDepth = 0u then
MaybeArgPos = arg_old(CurArgNum)
else
MaybeArgPos = non_arg
),
one_or_more_map.add(StateVar, MaybeArgPos, !NewSVars)
else if
Functor = atom("!:"),
SubTerms0 = [variable(StateVar, _)]
then
new_state_var_instance(StateVar, name_final, Var, !UrInfo),
Term = variable(Var, Context),
Status = status_unknown,
!.SVarState = svar_state(StatusMap0),
( if map.search(StatusMap0, StateVar, OldStatus) then
(
OldStatus = status_unknown,
% This is the second occurrence of !:StateVar.
% Since !.FinalMap will contain StateVar, we will generate
% the error message below.
StatusMap = StatusMap0
;
OldStatus = status_known(_),
% The !. part of this state var has already been processed.
% We have nothing more to do.
StatusMap = StatusMap0
;
OldStatus = status_unknown_updated(_),
unexpected($pred, "status_unknown_updated for !:")
;
OldStatus = status_known_updated(_, _),
unexpected($pred, "status_known_updated for !:")
;
OldStatus = status_known_ro(_, _, _),
% This can happen if the context outside a lambda
% expression has a state variable named StateVar,
% which make_svars_read_only has given this status,
% and the lambda expression itself also has !:StateVar.
map.det_update(StateVar, Status, StatusMap0, StatusMap)
)
else
map.det_insert(StateVar, Status, StatusMap0, StatusMap)
),
!:SVarState = svar_state(StatusMap),
map.search_insert(StateVar, Var, MaybeOldVar, !FinalMap),
(
MaybeOldVar = yes(_),
report_repeated_head_state_var(Context, StateVar, !UrInfo)
;
MaybeOldVar = no
),
( if CurDepth = 0u then
MaybeArgPos = arg_new(CurArgNum)
else
MaybeArgPos = non_arg
),
one_or_more_map.add(StateVar, MaybeArgPos, !NewSVars)
else
svar_prepare_head_terms(CurDepth + 1u, 1u, SubTerms0, SubTerms,
!FinalMap, !SVarState, !NewSVars, !UrInfo),
Term = functor(Functor, SubTerms, Context)
)
).
%---------------------------------------------------------------------------%
%
% Handle the start of processing a lambda expression.
%
svar_prepare_for_lambda_head(Context, Args0, Args, FinalMap,
NewSVars, OutsideState, InsideState, !UrInfo) :-
% Make all currently visible state vars readonly, since they cannot
% be updated inside the lambda expression.
%
% Note that some of these state vars may already be readonly, since
% we may already be inside e.g. a lambda expression. We must make sure
% that readonly references work even from code that is inside two or more
% lambda expressions.
OutsideState = svar_state(OutsideStatusMap),
map.to_sorted_assoc_list(OutsideStatusMap, OutsideStatusList),
make_svars_read_only(roc_lambda, Context,
OutsideStatusList, InsideStatusList),
map.from_sorted_assoc_list(InsideStatusList, InsideStatusMap),
InsideState0 = svar_state(InsideStatusMap),
% Handle the arguments of the lambda expression as if they were the head
% of a clause.
svar_prepare_head_terms(0u, 1u, Args0, Args, map.init, FinalMap,
InsideState0, InsideState, init_new_statevar_map, NewSVars, !UrInfo).
:- pred make_svars_read_only(readonly_context_kind::in, prog_context::in,
assoc_list(svar, svar_status)::in, assoc_list(svar, svar_status)::out)
is det.
make_svars_read_only(_ROC, _Context, [], []).
make_svars_read_only(ROC, Context, [SVar - CurStatus | CurTail], LambdaList) :-
make_svars_read_only(ROC, Context, CurTail, LambdaTail),
(
( CurStatus = status_unknown
; CurStatus = status_unknown_updated(_)
),
LambdaList = LambdaTail
;
CurStatus = status_known_ro(_, _, _),
LambdaList = [SVar - CurStatus | LambdaTail]
;
( CurStatus = status_known(Var)
; CurStatus = status_known_updated(Var, _)
),
LambdaStatus = status_known_ro(Var, ROC, Context),
LambdaList = [SVar - LambdaStatus | LambdaTail]
).
%---------------------------------------------------------------------------%
%
% Handle the end of processing a clause or lambda expression.
%
svar_finish_clause_body(Context, NewSVars, FinalMap,
InitialSVarState, FinalSVarState,
HeadUnificationsGoal, BodyGoal0, Goal,
StateVarSpecs, UnusedSVarDescs, !UrInfo) :-
svar_finish_body(Context, FinalMap, [HeadUnificationsGoal, BodyGoal0],
Goal1, InitialSVarState, FinalSVarState, !UrInfo),
SVarStore1 = !.UrInfo ^ ui_state_var_store,
SVarStore1 = svar_store(_, DelayedRenamings, LastIdMap, StateVarSpecs),
( if
map.is_empty(FinalMap),
map.is_empty(DelayedRenamings)
then
Goal = Goal1
else
trace [compiletime(flag("state-var-lambda")), io(!IO)] (
ModuleInfo = !.UrInfo ^ ui_module_info,
module_info_get_globals(ModuleInfo, Globals),
module_info_get_name(ModuleInfo, ModuleName),
get_debug_output_stream(Globals, ModuleName, DebugStream, !IO),
map.to_assoc_list(FinalMap, FinalList),
map.to_assoc_list(DelayedRenamings, DelayedList),
InitialSVarState = svar_state(InitialSVarStateMap),
map.to_assoc_list(InitialSVarStateMap, InitialSVarStateList),
FinalSVarState = svar_state(FinalSVarStateMap),
map.to_assoc_list(FinalSVarStateMap, FinalSVarStateList),
io.write_string(DebugStream, "\nFINISH CLAUSE BODY in ", !IO),
io.write_line(DebugStream, Context, !IO),
io.write_string(DebugStream, "applying subn\n", !IO),
io.write_line(DebugStream, FinalList, !IO),
io.write_string(DebugStream, "with incremental subn\n", !IO),
io.write_line(DebugStream, DelayedList, !IO),
io.write_string(DebugStream, "with initial svar states\n", !IO),
io.write_line(DebugStream, InitialSVarStateList, !IO),
io.write_string(DebugStream, "with final svar states\n", !IO),
io.write_line(DebugStream, FinalSVarStateList, !IO),
io.nl(DebugStream, !IO)
),
incremental_rename_vars_in_goal(map.init, DelayedRenamings,
Goal1, Goal2),
delete_unneeded_copy_goals_in_clause(HeadUnificationsGoal,
Goal2, Goal)
),
VarSet = !.UrInfo ^ ui_varset,
find_unused_statevar_args(VarSet, NewSVars, LastIdMap, UnusedSVarDescs).
svar_finish_lambda_body(Context, Modes, NewSVars, FinalMap, ParseTreeGoal,
Goals0, Goal, InitialSVarState, FinalSVarState, !UrInfo) :-
svar_finish_body(Context, FinalMap, Goals0, Goal,
InitialSVarState, FinalSVarState, !UrInfo),
VarSet = !.UrInfo ^ ui_varset,
LastIdMap = !.UrInfo ^ ui_state_var_store ^ store_last_id_map,
find_unused_statevar_args(VarSet, NewSVars, LastIdMap, UnusedSVarDescs),
report_any_unneeded_svars_in_lambda(Context, Modes, ParseTreeGoal, Goal,
UnusedSVarDescs, !UrInfo).
:- pred svar_finish_body(prog_context::in, map(svar, prog_var)::in,
list(hlds_goal)::in, hlds_goal::out, svar_state::in, svar_state::in,
unravel_info::in, unravel_info::out) is det.
svar_finish_body(Context, FinalMap, Goals0, Goal,
InitialSVarState, FinalSVarState, !UrInfo) :-
map.to_assoc_list(FinalMap, FinalAssocList),
InitialSVarState = svar_state(InitialSVarStatusMap),
FinalSVarState = svar_state(FinalSVarStatusMap),
svar_find_final_renames_and_copy_goals(FinalAssocList,
InitialSVarStatusMap, FinalSVarStatusMap,
[], FinalSVarSubn, [], CopyGoals),
(
CopyGoals = [],
Goals1 = Goals0
;
CopyGoals = [_ | _],
Goals1 = Goals0 ++ CopyGoals
),
svar_flatten_conj(Context, Goals1, Goal1, !UrInfo),
Goal1 = hlds_goal(GoalExpr1, GoalInfo1),
GoalId1 = goal_info_get_goal_id(GoalInfo1),
SVarStore1 = !.UrInfo ^ ui_state_var_store,
SVarStore1 = svar_store(NextGoalId1, DelayedRenamingMap1,
LastIdMap1, StateVarSpecs1),
( if map.search(DelayedRenamingMap1, GoalId1, DelayedRenaming0) then
trace [compiletime(flag("state-var-lambda")), io(!IO)] (
ModuleInfo = !.UrInfo ^ ui_module_info,
module_info_get_globals(ModuleInfo, Globals),
module_info_get_name(ModuleInfo, ModuleName),
get_debug_output_stream(Globals, ModuleName, DebugStream, !IO),
io.write_string(DebugStream, "\nfinishing body, ", !IO),
io.write_string(DebugStream,
"attaching subn to existing goal_id ", !IO),
io.write_line(DebugStream, GoalId1, !IO),
io.write_string(DebugStream, "subn is ", !IO),
io.write_line(DebugStream, FinalSVarSubn, !IO)
),
map.det_update(GoalId1, DelayedRenaming0 ++ FinalSVarSubn,
DelayedRenamingMap1, DelayedRenamingMap),
NextGoalId = NextGoalId1,
Goal = Goal1
else
(
FinalSVarSubn = [],
NextGoalId = NextGoalId1,
DelayedRenamingMap = DelayedRenamingMap1,
Goal = Goal1
;
FinalSVarSubn = [_ | _],
counter.uallocate(GoalIdNum, NextGoalId1, NextGoalId),
GoalId = goal_id(GoalIdNum),
trace [compiletime(flag("state-var-lambda")), io(!IO)] (
ModuleInfo = !.UrInfo ^ ui_module_info,
module_info_get_globals(ModuleInfo, Globals),
module_info_get_name(ModuleInfo, ModuleName),
get_debug_output_stream(Globals, ModuleName, DebugStream, !IO),
io.write_string(DebugStream, "\nfinishing body, ", !IO),
io.write_string(DebugStream,
"attaching subn to new goal_id ", !IO),
io.write_line(DebugStream, GoalId, !IO),
io.write_string(DebugStream, "subn is ", !IO),
io.write_line(DebugStream, FinalSVarSubn, !IO)
),
map.det_insert(GoalId, FinalSVarSubn,
DelayedRenamingMap1, DelayedRenamingMap),
goal_info_set_goal_id(GoalId, GoalInfo1, GoalInfo),
Goal = hlds_goal(GoalExpr1, GoalInfo)
)
),
SVarStore = svar_store(NextGoalId, DelayedRenamingMap,
LastIdMap1, StateVarSpecs1),
!UrInfo ^ ui_state_var_store := SVarStore.
:- pred svar_find_final_renames_and_copy_goals(assoc_list(svar, prog_var)::in,
map(svar, svar_status)::in, map(svar, svar_status)::in,
assoc_list(prog_var, prog_var)::in, assoc_list(prog_var, prog_var)::out,
list(hlds_goal)::in, list(hlds_goal)::out) is det.
svar_find_final_renames_and_copy_goals([], _, _, !FinalSVarSubn, !CopyGoals).
svar_find_final_renames_and_copy_goals([Head | Tail],
InitialStatusMap, FinalStatusMap, !FinalSVarSubn, !CopyGoals) :-
Head = SVar - FinalHeadVar,
map.lookup(InitialStatusMap, SVar, InitialStatus),
map.lookup(FinalStatusMap, SVar, FinalStatus),
(
FinalStatus = status_known(LastVar),
( if FinalStatus = InitialStatus then
% The state variable was not updated by the body.
% Leaving the unification between two headvars representing the
% initial and final states to the done at the start of the clause
% causes problems at the moment for the mode checker in the
% presence of unique modes.
make_copy_goal(LastVar, FinalHeadVar, CopyGoal),
!:CopyGoals = [CopyGoal | !.CopyGoals]
else
!:FinalSVarSubn = [LastVar - FinalHeadVar | !.FinalSVarSubn]
)
;
FinalStatus = status_unknown
% The state variable was never defined.
% The clause head already refers to the final version.
;
FinalStatus = status_known_ro(_, _, _),
unexpected($pred, "readonly status")
;
( FinalStatus = status_known_updated(_, _)
; FinalStatus = status_unknown_updated(_)
),
unexpected($pred, "updated status")
),
svar_find_final_renames_and_copy_goals(Tail,
InitialStatusMap, FinalStatusMap, !FinalSVarSubn, !CopyGoals).
%---------------------------------------------------------------------------%
%
% Handle the completion of an atomic goal. Any variable that was updated in the
% goal gets the updated value as its new current value. The Loc argument is
% needed because sometimes what looks like an atomic goal (such as the
% condition of an if-then-else) is inside another atomic goal (such as an
% if-then-else expression). In such cases, the end of the inside atomic goal
% does NOT mean that we finished the containing atomic goal.
%
svar_finish_atomic_goal(Loc, !SVarState) :-
(
Loc = loc_whole_goal,
!.SVarState = svar_state(StatusMap0),
map.map_values_only(reset_updated_status, StatusMap0, StatusMap),
!:SVarState = svar_state(StatusMap)
;
Loc = loc_inside_atomic_goal
).
:- pred reset_updated_status(svar_status::in, svar_status::out) is det.
reset_updated_status(!Status) :-
(
( !.Status = status_unknown
; !.Status = status_known_ro(_, _, _)
; !.Status = status_known(_)
)
;
!.Status = status_unknown_updated(NewProgVar),
!:Status = status_known(NewProgVar)
;
!.Status = status_known_updated(_OldProgVar, NewProgVar),
!:Status = status_known(NewProgVar)
).
%---------------------------------------------------------------------------%
%
% Handle scopes that introduce state variables.
%
svar_prepare_for_local_state_vars(Context, StateVars,
OutsideState, InsideState, !UrInfo) :-
OutsideState = svar_state(StatusMapOutside),
prepare_svars_for_scope(Context, StateVars,
StatusMapOutside, StatusMapInside, !UrInfo),
InsideState = svar_state(StatusMapInside).
:- pred prepare_svars_for_scope(prog_context::in, list(svar)::in,
map(svar, svar_status)::in, map(svar, svar_status)::out,
unravel_info::in, unravel_info::out) is det.
prepare_svars_for_scope(_Context, [], !StatusMap, !UrInfo).
prepare_svars_for_scope(Context, [SVar | SVars],
!StatusMap, !UrInfo) :-
( if map.search(!.StatusMap, SVar, _OldStatus) then
report_state_var_shadow(Context, SVar, !UrInfo),
map.det_update(SVar, status_unknown, !StatusMap)
else
map.det_insert(SVar, status_unknown, !StatusMap)
),
prepare_svars_for_scope(Context, SVars, !StatusMap, !UrInfo).
svar_finish_local_state_vars(UrInfo, StateVars,
StateBeforeOutside, StateAfterInside, StateAfterOutside) :-
StateBeforeOutside = svar_state(StatusMapBeforeOutside),
StateAfterInside = svar_state(StatusMapAfterInside),
trace [compiletime(flag("state-var-scope")), io(!IO)] (
ModuleInfo = UrInfo ^ ui_module_info,
module_info_get_globals(ModuleInfo, Globals),
module_info_get_name(ModuleInfo, ModuleName),
get_debug_output_stream(Globals, ModuleName, DebugStream, !IO),
map.to_assoc_list(StatusMapBeforeOutside, BeforeOutsideStatuses),
map.to_assoc_list(StatusMapAfterInside, AfterInsideStatuses),
io.write_string(DebugStream, "Finish of scope\n", !IO),
io.write_string(DebugStream, "quantified state vars\n", !IO),
io.write_line(DebugStream, StateVars, !IO),
io.write_string(DebugStream, "status before outside\n", !IO),
list.foldl(io.write_line(DebugStream), BeforeOutsideStatuses, !IO),
io.write_string(DebugStream, "status after inside\n", !IO),
list.foldl(io.write_line(DebugStream), AfterInsideStatuses, !IO)
),
% Remove access to the state vars introduced in the scope.
% Leave the status of all other state vars unaffected.
StatusMapAfterOutside0 = StatusMapAfterInside,
finish_svars_for_scope(StateVars, StatusMapBeforeOutside,
StatusMapAfterOutside0, StatusMapAfterOutside),
StateAfterOutside = svar_state(StatusMapAfterOutside).
:- pred finish_svars_for_scope(list(svar)::in, map(svar, svar_status)::in,
map(svar, svar_status)::in, map(svar, svar_status)::out) is det.
finish_svars_for_scope([], _, !StatusMapAfterOutside).
finish_svars_for_scope([SVar | SVars], StatusMapBeforeOutside,
!StatusMapAfterOutside) :-
( if map.search(StatusMapBeforeOutside, SVar, BeforeOutsideStatus) then
% The state var was visible before the scope. The outside state var
% was shadowed by a state var in the scope. Now that we are leaving
% the scope, restore access to the outside state var. Due to the
% shadowing, its status couldn't have changed inside the scope.
map.det_update(SVar, BeforeOutsideStatus, !StatusMapAfterOutside)
else
% The state var introduced in the scope wasn't visible before it.
map.det_remove(SVar, _, !StatusMapAfterOutside)
),
finish_svars_for_scope(SVars, StatusMapBeforeOutside,
!StatusMapAfterOutside).
%---------------------------------------------------------------------------%
%
% Handle disjunctions. The algorithm we use has two passes over the disjuncts.
%
% - Pass 1 finds out, for each state variable known at the start of the
% disjunction, whether it was updated by any arms, and if yes, it picks
% the final prog_var from one of the updated arms to represent the state var
% after the disjunction.
%
% - Pass 2 processes the arms to ensure that the picked prog_var represents
% the final value of the state variable in all the arms. In arms that do not
% update the state variable, it introduces unifications to copy the initial
% value of the state var to be the final value. In arms that do update the
% state var, it schedules the prog_var representing the final value in
% that arm to be renamed to the picked prog_var.
svar_finish_disjunction(DisjStates, Disjs, StateBefore, StateAfter, !UrInfo) :-
StateBefore = svar_state(StatusMapBefore),
( if map.is_empty(StatusMapBefore) then
% Optimize the common case.
get_disjuncts_with_empty_states(DisjStates, [], RevDisjs),
list.reverse(RevDisjs, Disjs),
StateAfter = StateBefore
else
map.to_sorted_assoc_list(StatusMapBefore, StatusListBefore),
compute_status_after_arms(StatusListBefore, DisjStates,
map.init, ChangedStatusMapAfter, StatusMapBefore, StatusMapAfter),
map.to_sorted_assoc_list(ChangedStatusMapAfter,
ChangedStatusListAfter),
StateAfter = svar_state(StatusMapAfter),
VarSet0 = !.UrInfo ^ ui_varset,
SVarStore0 = !.UrInfo ^ ui_state_var_store,
SVarStore0 = svar_store(NextGoalId0, DelayedRenamings0,
LastIdMap0, SVarSpecs0),
merge_changes_made_by_arms(VarSet0, DisjStates, StatusMapBefore,
ChangedStatusListAfter, [], RevDisjs,
NextGoalId0, NextGoalId, DelayedRenamings0, DelayedRenamings,
SVarSpecs0, SVarSpecs),
list.reverse(RevDisjs, Disjs),
SVarStore = svar_store(NextGoalId, DelayedRenamings,
LastIdMap0, SVarSpecs),
!UrInfo ^ ui_state_var_store := SVarStore
).
:- pred get_disjuncts_with_empty_states(list(hlds_goal_svar_state)::in,
list(hlds_goal)::in, list(hlds_goal)::out) is det.
get_disjuncts_with_empty_states([], !RevDisjuncts).
get_disjuncts_with_empty_states([GoalState | GoalStates], !RevDisjuncts) :-
GoalState = hlds_goal_svar_state(Goal, State),
StatusMapAfterGoal = State ^ state_status_map,
expect(map.is_empty(StatusMapAfterGoal), $pred,
"map after goal not empty"),
!:RevDisjuncts = [Goal | !.RevDisjuncts],
get_disjuncts_with_empty_states(GoalStates, !RevDisjuncts).
% Pass 1. Compute the changes in the status map.
%
:- pred compute_status_after_arms(assoc_list(svar, svar_status)::in,
list(hlds_goal_svar_state)::in,
map(svar, svar_status)::in, map(svar, svar_status)::out,
map(svar, svar_status)::in, map(svar, svar_status)::out) is det.
compute_status_after_arms(_StatusListBefore, [],
!ChangedStatusMapAfter, !StatusMapAfter).
compute_status_after_arms(StatusListBefore, [ArmState | ArmStates],
!ChangedStatusMapAfter, !StatusMapAfter) :-
ArmState = hlds_goal_svar_state(_Armunct, StateAfterArm),
StatusMapAfterArm = StateAfterArm ^ state_status_map,
find_changes_in_arm_and_update_changed_status_map(StatusListBefore,
StatusMapAfterArm, !ChangedStatusMapAfter, !StatusMapAfter),
compute_status_after_arms(StatusListBefore, ArmStates,
!ChangedStatusMapAfter, !StatusMapAfter).
:- pred find_changes_in_arm_and_update_changed_status_map(
assoc_list(svar, svar_status)::in, map(svar, svar_status)::in,
map(svar, svar_status)::in, map(svar, svar_status)::out,
map(svar, svar_status)::in, map(svar, svar_status)::out) is det.
find_changes_in_arm_and_update_changed_status_map([], _,
!ChangedStatusMapAfter, !StatusMapAfter).
find_changes_in_arm_and_update_changed_status_map([Before | Befores],
StatusMapAfterArm, !ChangedStatusMapAfter, !StatusMapAfter) :-
Before = SVar - StatusBefore,
map.lookup(StatusMapAfterArm, SVar, StatusAfter),
( if StatusBefore = StatusAfter then
true
else
( if map.search(!.ChangedStatusMapAfter, SVar, _AlreadyUpdated) then
true
else
map.det_insert(SVar, StatusAfter, !ChangedStatusMapAfter),
map.det_update(SVar, StatusAfter, !StatusMapAfter)
)
),
find_changes_in_arm_and_update_changed_status_map(Befores,
StatusMapAfterArm, !ChangedStatusMapAfter, !StatusMapAfter).
% Pass 2. Effect the computed changes in the status map.
%
:- pred merge_changes_made_by_arms(prog_varset::in,
list(hlds_goal_svar_state)::in,
map(svar, svar_status)::in, assoc_list(svar, svar_status)::in,
list(hlds_goal)::in, list(hlds_goal)::out,
ucounter::in, ucounter::out,
incremental_rename_map::in, incremental_rename_map::out,
list(error_spec)::in, list(error_spec)::out) is det.
merge_changes_made_by_arms(_, [], _, _,
!RevArms, !NextGoalId, !DelayedRenamings, !Specs).
merge_changes_made_by_arms(VarSet, [ArmState | ArmStates],
StatusMapBefore, ChangedStatusListAfter,
!RevArms, !NextGoalId, !DelayedRenamings, !Specs) :-
ArmState = hlds_goal_svar_state(Arm0, StateAfterArm),
StatusMapAfterArm = StateAfterArm ^ state_status_map,
counter.uallocate(ArmIdNum, !NextGoalId),
ArmId = goal_id(ArmIdNum),
handle_arm_updated_state_vars(VarSet,
ChangedStatusListAfter, StatusMapBefore, StatusMapAfterArm,
UninitVarNames, CopyGoals, ArmRenames),
map.det_insert(ArmId, ArmRenames, !DelayedRenamings),
Arm0 = hlds_goal(ArmExpr0, ArmInfo0),
(
CopyGoals = [],
ArmExpr = ArmExpr0
;
CopyGoals = [_ | _],
svar_goal_to_conj_list_internal(Arm0, ArmGoals0,
!NextGoalId, !DelayedRenamings),
ArmExpr = conj(plain_conj, ArmGoals0 ++ CopyGoals)
),
(
UninitVarNames = []
;
UninitVarNames = [_ | _],
% It is ok for an arm that cannot succeed not to initialize
% a variable, but we record an informational message anyway,
% to be printed in case the procedure has a mode error.
ArmContext = goal_info_get_context(ArmInfo0),
report_missing_inits_in_disjunct(ArmContext, UninitVarNames, !Specs)
),
goal_info_set_goal_id(ArmId, ArmInfo0, ArmInfo),
Arm = hlds_goal(ArmExpr, ArmInfo),
!:RevArms = [Arm | !.RevArms],
merge_changes_made_by_arms(VarSet, ArmStates,
StatusMapBefore, ChangedStatusListAfter,
!RevArms, !NextGoalId, !DelayedRenamings, !Specs).
:- pred handle_arm_updated_state_vars(prog_varset::in,
assoc_list(svar, svar_status)::in,
map(svar, svar_status)::in, map(svar, svar_status)::in, list(string)::out,
list(hlds_goal)::out, assoc_list(prog_var, prog_var)::out) is det.
handle_arm_updated_state_vars(_, [], _, _, [], [], []).
handle_arm_updated_state_vars(VarSet, [Change | Changes], StatusMapBefore,
StatusMapAfterArm, UninitVarNames, CopyGoals, Renames) :-
handle_arm_updated_state_vars(VarSet, Changes,
StatusMapBefore, StatusMapAfterArm,
UninitVarNamesTail, CopyGoalsTail, RenamesTail),
Change = StateVar - AfterAllArmsStatus,
map.lookup(StatusMapBefore, StateVar, BeforeStatus),
map.lookup(StatusMapAfterArm, StateVar, AfterArmStatus),
( if AfterArmStatus = BeforeStatus then
expect_not(unify(AfterArmStatus, AfterAllArmsStatus),
$pred, "AfterArmStatus = AfterAllArmsStatus"),
(
% If the state var is readonly in this context, then it shouldn't
% have been updated by any arms. However, if it was, then we have
% (a) already generated an error message for it, and (b) changed
% its status to writeable to suppress duplicate error messages.
% This is why this code treats known_ro the same as known.
( BeforeStatus = status_known(BeforeVar)
; BeforeStatus = status_known_ro(BeforeVar, _, _)
),
(
AfterAllArmsStatus = status_known(AfterAllVar),
make_copy_goal(BeforeVar, AfterAllVar, CopyGoal),
CopyGoals = [CopyGoal | CopyGoalsTail],
UninitVarNames = UninitVarNamesTail,
Renames = RenamesTail
;
( AfterAllArmsStatus = status_known_ro(_, _, _)
; AfterAllArmsStatus = status_known_updated(_, _)
; AfterAllArmsStatus = status_unknown
; AfterAllArmsStatus = status_unknown_updated(_)
),
unexpected($pred,
"AfterAllArmsStatus != status_known (Before == After)")
)
;
BeforeStatus = status_unknown,
varset.lookup_name(VarSet, StateVar, Name),
UninitVarName = "!:" ++ Name,
CopyGoals = CopyGoalsTail,
UninitVarNames = [UninitVarName | UninitVarNamesTail],
Renames = RenamesTail
;
( BeforeStatus = status_known_updated(_, _)
; BeforeStatus = status_unknown_updated(_)
),
% If the state var was updated before this disjunction,
% then any reference to !:StateVar should refer to the already
% known updated prog_var, and thus AfterAllArmsStatus should be
% the same as StatusBefore, which means we shouldn't get here.
unexpected($pred, "BeforeStatus is updated")
)
else
(
AfterArmStatus = status_known(AfterArmVar),
(
AfterAllArmsStatus = status_known(AfterAllVar),
CopyGoals = CopyGoalsTail,
UninitVarNames = UninitVarNamesTail,
( if AfterArmVar = AfterAllVar then
Renames = RenamesTail
else
Renames = [AfterArmVar - AfterAllVar | RenamesTail]
)
;
( AfterAllArmsStatus = status_known_ro(_, _, _)
; AfterAllArmsStatus = status_known_updated(_, _)
; AfterAllArmsStatus = status_unknown
; AfterAllArmsStatus = status_unknown_updated(_)
),
unexpected($pred,
"AfterAllArmsStatus != status_known (Before != After)")
)
;
AfterArmStatus = status_known_ro(_, _, _),
unexpected($pred, "AfterArmStatus = status_known_ro")
;
AfterArmStatus = status_known_updated(_, _),
unexpected($pred, "AfterArmStatus = status_known_updated")
;
AfterArmStatus = status_unknown,
unexpected($pred, "AfterArmStatus = status_unknown")
;
AfterArmStatus = status_unknown_updated(_),
unexpected($pred, "AfterArmStatus = status_unknown")
)
).
:- pred make_copy_goal(prog_var::in, prog_var::in, hlds_goal::out) is det.
make_copy_goal(FromVar, ToVar, CopyGoal) :-
% We can do the copying in one of two ways. Using unifications
% can cause problems because the (plain, non-unique) mode analysis pass
% feels free to schedule them in places where the unique mode analysis pass
% does not like them; specifically, it can cause a di reference to a
% variable to appear before a ui reference.
%
% The alternative is to add a builtin predicate to the standard library
% that just does copying, and to make make_copy_goal construct a call to
% that predicate. That predicate would need to be able to be called in
% three modes: di/uo, mdi/muo and in/out. However, it needs to have inst
% parameters so that whatever shape information we have about the source
% (subtype info, higher order mode info), we copy to the target.
%
% We generate a unification, and try to ensure that we don't generate
% di references to state variables before possible ui references. See the
% comment in svar_find_final_renames_and_copy_goals before the call to
% make_copy_goal.
create_pure_atomic_complicated_unification(ToVar, rhs_var(FromVar),
dummy_context, umc_implicit("state variable"), [], CopyGoal0),
goal_add_features([feature_do_not_warn_singleton, feature_state_var_copy],
CopyGoal0, CopyGoal).
%---------------------------------------------------------------------------%
%
% Handle if-then-else goals. The basic idea is the same as for disjunctions,
% but we also have to handle three complications.
%
% First, the first disjunct consists of two parts: the condition and the then
% part, with data flowing between them.
%
% Second, variables can be quantified over the condition and the then part.
%
% Third, the if-then-else need not be a goal; it can also be an expression.
% This means that it is ok for variables to have status known_updated or
% unknown_updated in any of the status maps we handle.
%
svar_finish_if_then_else(LocKind, Context, QuantStateVars,
ThenGoal0, ThenGoal, ElseGoal0, ElseGoal,
StateBefore, StateAfterCond, StateAfterThen, StateAfterElse,
StateAfterITE, !UrInfo) :-
StateBefore = svar_state(StatusMapBefore),
StatusMapAfterCond = StateAfterCond ^ state_status_map,
StatusMapAfterThen = StateAfterThen ^ state_status_map,
StatusMapAfterElse = StateAfterElse ^ state_status_map,
map.keys(StatusMapBefore, SVarsBefore),
map.keys(StatusMapAfterCond, SVarsAfterCond),
map.keys(StatusMapAfterThen, SVarsAfterThen),
map.keys(StatusMapAfterElse, SVarsAfterElse),
expect(list.sublist(SVarsBefore, SVarsAfterCond), $pred,
"vars Before not sublist of Cond"),
expect(unify(SVarsBefore, SVarsAfterThen), $pred,
"vars Before != AfterThen"),
expect(unify(SVarsBefore, SVarsAfterElse), $pred,
"vars Before != AfterElse"),
handle_state_vars_in_ite(LocKind, QuantStateVars,
SVarsBefore, StatusMapBefore, StatusMapAfterCond,
StatusMapAfterThen, StatusMapAfterElse,
map.init, StatusMapAfterITE,
[], NeckCopyGoals, [], ThenEndCopyGoals, [], ElseEndCopyGoals,
[], ThenRenames, [], ElseRenames,
[], ThenMissingInits, [], ElseMissingInits, !UrInfo),
StateAfterITE = svar_state(StatusMapAfterITE),
% It is ok for an arm that cannot succeed not to initialize a variable,
% but we record warnings for them anyway, to be printed in case the
% procedure has a mode error.
(
ThenMissingInits = []
;
ThenMissingInits = [_ | _],
report_missing_inits_in_ite(Context, ThenMissingInits,
"succeeds", "fails", !UrInfo)
),
(
ElseMissingInits = []
;
ElseMissingInits = [_ | _],
report_missing_inits_in_ite(Context, ElseMissingInits,
"fails", "succeeds", !UrInfo)
),
svar_goal_to_conj_list(ThenGoal0, ThenGoals0, !UrInfo),
svar_goal_to_conj_list(ElseGoal0, ElseGoals0, !UrInfo),
ThenGoals = NeckCopyGoals ++ ThenGoals0 ++ ThenEndCopyGoals,
ElseGoals = ElseGoals0 ++ ElseEndCopyGoals,
ThenGoal0 = hlds_goal(_ThenExpr0, ThenInfo0),
ElseGoal0 = hlds_goal(_ElseExpr0, ElseInfo0),
conj_list_to_goal(ThenGoals, ThenInfo0, ThenGoal1),
conj_list_to_goal(ElseGoals, ElseInfo0, ElseGoal1),
SVarStore0 = !.UrInfo ^ ui_state_var_store,
SVarStore0 = svar_store(NextGoalId0, DelayedRenamings0,
LastIdMap0, SVarSpecs0),
counter.uallocate(ThenGoalIdNum, NextGoalId0, NextGoalId1),
counter.uallocate(ElseGoalIdNum, NextGoalId1, NextGoalId),
ThenGoalId = goal_id(ThenGoalIdNum),
ElseGoalId = goal_id(ElseGoalIdNum),
goal_set_goal_id(ThenGoalId, ThenGoal1, ThenGoal),
goal_set_goal_id(ElseGoalId, ElseGoal1, ElseGoal),
map.det_insert(ThenGoalId, ThenRenames,
DelayedRenamings0, DelayedRenamings1),
map.det_insert(ElseGoalId, ElseRenames,
DelayedRenamings1, DelayedRenamings),
SVarStore = svar_store(NextGoalId, DelayedRenamings,
LastIdMap0, SVarSpecs0),
!UrInfo ^ ui_state_var_store := SVarStore.
:- pred handle_state_vars_in_ite(loc_kind::in, list(svar)::in, list(svar)::in,
map(svar, svar_status)::in, map(svar, svar_status)::in,
map(svar, svar_status)::in, map(svar, svar_status)::in,
map(svar, svar_status)::in, map(svar, svar_status)::out,
list(hlds_goal)::in, list(hlds_goal)::out,
list(hlds_goal)::in, list(hlds_goal)::out,
list(hlds_goal)::in, list(hlds_goal)::out,
assoc_list(prog_var, prog_var)::in, assoc_list(prog_var, prog_var)::out,
assoc_list(prog_var, prog_var)::in, assoc_list(prog_var, prog_var)::out,
list(string)::in, list(string)::out, list(string)::in, list(string)::out,
unravel_info::in, unravel_info::out) is det.
handle_state_vars_in_ite(_, _, [], _, _, _, _, !StatusMapAfterITE,
!NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
!ThenRenames, !ElseRenames, !ThenMissingInits, !ElseMissingInits,
!UrInfo).
handle_state_vars_in_ite(LocKind, QuantStateVars, [SVar | SVars],
StatusMapBefore, StatusMapAfterCond,
StatusMapAfterThen, StatusMapAfterElse, !StatusMapAfterITE,
!NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
!ThenRenames, !ElseRenames, !ThenMissingInits, !ElseMissingInits,
!UrInfo) :-
map.lookup(StatusMapBefore, SVar, StatusBefore),
map.lookup(StatusMapAfterCond, SVar, StatusAfterCond),
map.lookup(StatusMapAfterThen, SVar, StatusAfterThen),
map.lookup(StatusMapAfterElse, SVar, StatusAfterElse),
( if list.member(SVar, QuantStateVars) then
expect(unify(StatusBefore, StatusAfterThen), $pred,
"state var shadowed in if-then-else is nevertheless updated"),
% SVar is quantified in the if-then-else. That means that Cond and Then
% may update a state variable with the same name as SVar, but this
% won't be SVar itself. The status of SVar itself after Cond and after
% Then will thus be unchanged. This is why we pass StatusBefore
% not just for itself, but in place of StatusAfterCond and
% StatusAfterThen as well.
handle_state_var_in_ite(LocKind, SVar,
StatusBefore, StatusBefore, StatusBefore,
StatusAfterElse, StatusAfterITE,
!NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
!ElseRenames, !ThenMissingInits, !ElseMissingInits, !UrInfo)
else
% If StatusBefore = status_known_ro(_, _, _), then we would expect
% StatusBefore = StatusAfterCond
% StatusBefore = StatusAfterThen
% StatusBefore = StatusAfterElse
% However, if the user program actually updates a state variable
% that should be readonly in this scope, then our recovery from that
% error would invalidate these expectations.
handle_state_var_in_ite(LocKind, SVar,
StatusBefore, StatusAfterCond, StatusAfterThen,
StatusAfterElse, StatusAfterITE,
!NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
!ElseRenames, !ThenMissingInits, !ElseMissingInits, !UrInfo)
),
map.det_insert(SVar, StatusAfterITE, !StatusMapAfterITE),
handle_state_vars_in_ite(LocKind, QuantStateVars, SVars,
StatusMapBefore, StatusMapAfterCond,
StatusMapAfterThen, StatusMapAfterElse, !StatusMapAfterITE,
!NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
!ThenRenames, !ElseRenames, !ThenMissingInits, !ElseMissingInits,
!UrInfo).
:- pred handle_state_var_in_ite(loc_kind::in, svar::in,
svar_status::in, svar_status::in, svar_status::in, svar_status::in,
svar_status::out,
list(hlds_goal)::in, list(hlds_goal)::out,
list(hlds_goal)::in, list(hlds_goal)::out,
list(hlds_goal)::in, list(hlds_goal)::out,
assoc_list(prog_var, prog_var)::in, assoc_list(prog_var, prog_var)::out,
list(string)::in, list(string)::out, list(string)::in, list(string)::out,
unravel_info::in, unravel_info::out) is det.
handle_state_var_in_ite(LocKind, SVar, StatusBefore,
StatusAfterCond, StatusAfterThen, StatusAfterElse, StatusAfterITE,
!NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
!ElseRenames, !ThenMissingInits, !ElseMissingInits, !UrInfo) :-
trace [compiletime(flag("state-var-ite")), io(!IO)] (
ModuleInfo = !.UrInfo ^ ui_module_info,
module_info_get_globals(ModuleInfo, Globals),
module_info_get_name(ModuleInfo, ModuleName),
get_debug_output_stream(Globals, ModuleName, DebugStream, !IO),
io.write_string(DebugStream, "state variable ", !IO),
io.write_line(DebugStream, SVar, !IO),
io.write_string(DebugStream, "status before: ", !IO),
io.write_line(DebugStream, StatusBefore, !IO),
io.write_string(DebugStream, "status after cond: ", !IO),
io.write_line(DebugStream, StatusAfterCond, !IO),
io.write_string(DebugStream, "status after then: ", !IO),
io.write_line(DebugStream, StatusAfterThen, !IO),
io.write_string(DebugStream, "status after else: ", !IO),
io.write_line(DebugStream, StatusAfterElse, !IO)
),
% There are eight cases depending on which of Then, Else and Cond
% update the state variable:
%
% # Then Else Cond Action
% A1 no no no do nothing
% A2 no no yes copy from cond at start of then, copy at end of else
% B1 no yes no copy at end of then
% B2 no yes yes copy from cond at start of then
% C1 yes no no copy at end of else
% C2 yes no yes copy at end of else
% D1 yes yes no rename else to match then
% D2 yes yes yes rename else to match then
%
% The nesting structure is:
%
% - Did Then update SVar? (Is StatusAfterThen = StatusAfterCond?)
% - Did Else update SVar? (Is StatusAfterElse = StatusBefore?)
% - did Cond update SVar? (Is StatusAfterCond = StatusBefore?)
%
% Note that if Then updates SVar, as it does in these cases, then
% it does not matter whether Cond also updates SVar, since its final state
% will not depend on that intermediate state. This is why we do not
% differentiate between C1 and C2, and between D1 and D2. And this
% in turn is the reason for why we test for updates by Then (and Else)
% before we test for updates by Cond.
( if StatusAfterThen = StatusAfterCond then
% Cases A-B.
( if StatusAfterElse = StatusBefore then
% Case A.
( if StatusAfterCond = StatusBefore then
% Case A1.
StatusAfterITE = StatusBefore
else
% Case A2.
handle_state_var_in_ite_cond(LocKind, SVar,
StatusBefore, StatusAfterCond, StatusAfterITE,
!NeckCopyGoals, !ElseEndCopyGoals, !ElseMissingInits,
!UrInfo)
)
else
% Case B.
( if StatusAfterCond = StatusBefore then
% Case B1.
handle_state_var_in_ite_else(!.UrInfo, LocKind, SVar,
StatusBefore, StatusAfterElse, StatusAfterITE,
!ThenEndCopyGoals, !ThenMissingInits)
else
% Case B2.
handle_state_var_in_ite_cond_else(LocKind,
StatusAfterCond, StatusAfterElse, StatusAfterITE,
!NeckCopyGoals)
)
)
else
% Cases C-D.
( if StatusAfterElse = StatusBefore then
% Case C. We handle C1 and C2 the same way.
handle_state_var_in_ite_maybe_cond_then(!.UrInfo, LocKind,
SVar, StatusBefore, StatusAfterThen, StatusAfterITE,
!ElseEndCopyGoals, !ElseMissingInits)
else
% Case D. We handle D1 and D2 the same way.
handle_state_var_in_ite_maybe_cond_then_else(LocKind,
StatusAfterThen, StatusAfterElse, StatusAfterITE, !ElseRenames)
)
).
:- pred handle_state_var_in_ite_cond(loc_kind::in, svar::in,
svar_status::in, svar_status::in, svar_status::out,
list(hlds_goal)::in, list(hlds_goal)::out,
list(hlds_goal)::in, list(hlds_goal)::out,
list(string)::in, list(string)::out,
unravel_info::in, unravel_info::out) is det.
:- pragma inline(pred(handle_state_var_in_ite_cond/13)).
handle_state_var_in_ite_cond(LocKind, SVar,
StatusBefore, StatusAfterCond, StatusAfterITE,
!NeckCopyGoals, !ElseEndCopyGoals, !ElseMissingInits, !UrInfo) :-
(
StatusBefore = status_known(VarBefore),
new_state_var_instance(SVar, name_middle, FinalVar, !UrInfo),
VarAfterCond = svar_get_current_progvar(LocKind, StatusAfterCond),
make_copy_goal(VarAfterCond, FinalVar, NeckCopyGoal),
!:NeckCopyGoals = [NeckCopyGoal | !.NeckCopyGoals],
make_copy_goal(VarBefore, FinalVar, ElseCopyGoal),
!:ElseEndCopyGoals = [ElseCopyGoal | !.ElseEndCopyGoals],
StatusAfterITE = status_known(FinalVar)
;
StatusBefore = status_unknown,
VarSet = !.UrInfo ^ ui_varset,
varset.lookup_name(VarSet, SVar, SVarName),
!:ElseMissingInits = ["!:" ++ SVarName | !.ElseMissingInits],
% We pretend the else part defines StateVar, since this is
% the right thing to do when the else part cannot succeed.
% If it can, we will generate an error message during
% mode analysis.
new_state_var_instance(SVar, name_middle, FinalVar, !UrInfo),
VarAfterCond = svar_get_current_progvar(LocKind, StatusAfterCond),
make_copy_goal(VarAfterCond, FinalVar, NeckCopyGoal),
!:NeckCopyGoals = [NeckCopyGoal | !.NeckCopyGoals],
StatusAfterITE = status_known(FinalVar)
;
StatusBefore = status_known_ro(_, _, _),
% The update of !SVar in the condition was an error, for which
% we have already generated an error message. Because of that,
% this dummy value won't be used.
% XXX Returning StatusAfterCond would cause fewer cascading
% error messages, but are those messages useful?
StatusAfterITE = StatusBefore
;
( StatusBefore = status_known_updated(_, _)
; StatusBefore = status_unknown_updated(_)
),
% This can happen if LocKind = loc_inside_atomic_goal, but
% any reference to !:SVar in the condition should have
% just returned the new progvar for SVar.
unexpected($pred, "updated before (case 5)")
).
:- pred handle_state_var_in_ite_else(unravel_info::in, loc_kind::in, svar::in,
svar_status::in, svar_status::in, svar_status::out,
list(hlds_goal)::in, list(hlds_goal)::out,
list(string)::in, list(string)::out) is det.
:- pragma inline(pred(handle_state_var_in_ite_else/10)).
handle_state_var_in_ite_else(UrInfo, LocKind, SVar, StatusBefore,
StatusAfterElse, StatusAfterITE,
!ThenEndCopyGoals, !ThenMissingInits) :-
(
StatusBefore = status_known(VarBefore),
VarAfterElse = svar_get_current_progvar(LocKind, StatusAfterElse),
make_copy_goal(VarBefore, VarAfterElse, CopyGoal),
!:ThenEndCopyGoals = [CopyGoal | !.ThenEndCopyGoals],
StatusAfterITE = StatusAfterElse
;
StatusBefore = status_unknown,
VarSet = UrInfo ^ ui_varset,
varset.lookup_name(VarSet, SVar, SVarName),
!:ThenMissingInits = ["!:" ++ SVarName | !.ThenMissingInits],
% We pretend the then part defines StateVar, since this is the right
% thing to do when the then part cannot succeed. If it can, we will
% generate an error message during mode analysis.
StatusAfterITE = StatusAfterElse
;
StatusBefore = status_known_ro(_, _, _),
% The update of !SVar in the else case was an error, for which
% we have already generated an error message. Because of that,
% this dummy value won't be used.
% XXX Returning StatusAfterElse would cause fewer cascading
% error messages, but are those messages useful?
StatusAfterITE = StatusBefore
;
( StatusBefore = status_known_updated(_, _)
; StatusBefore = status_unknown_updated(_)
),
% This can happen if LocKind = loc_inside_atomic_goal, but
% any reference to !:SVar in the else case should have just returned
% the new progvar for SVar.
unexpected($pred, "updated before (case 2)")
).
:- pred handle_state_var_in_ite_cond_else(loc_kind::in,
svar_status::in, svar_status::in, svar_status::out,
list(hlds_goal)::in, list(hlds_goal)::out) is det.
:- pragma inline(pred(handle_state_var_in_ite_cond_else/6)).
handle_state_var_in_ite_cond_else(LocKind,
StatusAfterCond, StatusAfterElse, StatusAfterITE, !NeckCopyGoals) :-
VarAfterCond = svar_get_current_progvar(LocKind, StatusAfterCond),
VarAfterElse = svar_get_current_progvar(LocKind, StatusAfterElse),
make_copy_goal(VarAfterCond, VarAfterElse, CopyGoal),
!:NeckCopyGoals = [CopyGoal | !.NeckCopyGoals],
StatusAfterITE = StatusAfterElse.
:- pred handle_state_var_in_ite_maybe_cond_then(unravel_info::in,
loc_kind::in, svar::in,
svar_status::in, svar_status::in, svar_status::out,
list(hlds_goal)::in, list(hlds_goal)::out,
list(string)::in, list(string)::out) is det.
:- pragma inline(pred(handle_state_var_in_ite_maybe_cond_then/10)).
handle_state_var_in_ite_maybe_cond_then(UrInfo, LocKind, SVar,
StatusBefore, StatusAfterThen, StatusAfterITE,
!ElseEndCopyGoals, !ElseMissingInits) :-
(
StatusBefore = status_known(VarBefore),
VarAfterThen = svar_get_current_progvar(LocKind, StatusAfterThen),
make_copy_goal(VarBefore, VarAfterThen, CopyGoal),
!:ElseEndCopyGoals = [CopyGoal | !.ElseEndCopyGoals],
StatusAfterITE = StatusAfterThen
;
StatusBefore = status_unknown,
VarSet = UrInfo ^ ui_varset,
varset.lookup_name(VarSet, SVar, SVarName),
!:ElseMissingInits = ["!:" ++ SVarName | !.ElseMissingInits],
% We pretend the else part defines StateVar, since this is the
% right thing to do when the else part cannot succeed. If it can,
% we will generate an error message during mode analysis.
StatusAfterITE = StatusAfterThen
;
StatusBefore = status_known_ro(_, _, _),
% The updates of !SVar in the condition and then cases were errors,
% for which we already generated messages. Because of that,
% this dummy value won't be used.
% XXX Returning StatusAfterThen would cause fewer cascading
% error messages, but are those messages useful?
StatusAfterITE = StatusBefore
;
( StatusBefore = status_known_updated(_, _)
; StatusBefore = status_unknown_updated(_)
),
% This can happen if LocKind = loc_inside_atomic_goal, but
% any reference to !:SVar in the condition and then case
% should have just returned the new progvar for SVar.
unexpected($pred, "updated before (case 7)")
).
:- pred handle_state_var_in_ite_maybe_cond_then_else(loc_kind::in,
svar_status::in, svar_status::in, svar_status::out,
assoc_list(prog_var, prog_var)::in, assoc_list(prog_var, prog_var)::out)
is det.
:- pragma inline(pred(handle_state_var_in_ite_maybe_cond_then_else/6)).
handle_state_var_in_ite_maybe_cond_then_else(LocKind,
StatusAfterThen, StatusAfterElse, StatusAfterITE, !ElseRenames) :-
VarAfterThen = svar_get_current_progvar(LocKind, StatusAfterThen),
VarAfterElse = svar_get_current_progvar(LocKind, StatusAfterElse),
!:ElseRenames = [VarAfterElse - VarAfterThen | !.ElseRenames],
StatusAfterITE = StatusAfterThen.
%---------------------------------------------------------------------------%
%
% Handle atomic goals. Atomic goals are basically a disjunction between
% the main goal and the orelse goals.
%
:- type svar_outer_atomic_scope_info
---> svar_outer_atomic_scope_info(
soasi_state_var :: svar,
soasi_before_status :: svar_status,
soasi_after_status :: svar_status
)
; no_svar_outer_atomic_scope_info.
svar_start_outer_atomic_scope(Context, OuterStateVar, OuterDIVar, OuterUOVar,
OuterScopeInfo, !SVarState, !UrInfo) :-
StatusMap0 = !.SVarState ^ state_status_map,
( if map.remove(OuterStateVar, BeforeStatus, StatusMap0, StatusMap) then
!SVarState ^ state_status_map := StatusMap,
(
BeforeStatus = status_unknown,
report_uninitialized_state_var(warn_dodgy_simple_code, Context,
OuterStateVar, !UrInfo),
new_state_var_instance(OuterStateVar, name_middle, OuterDIVar,
!UrInfo),
new_state_var_instance(OuterStateVar, name_middle, OuterUOVar,
!UrInfo),
OuterScopeInfo = svar_outer_atomic_scope_info(OuterStateVar,
BeforeStatus, BeforeStatus)
;
BeforeStatus = status_known_ro(OuterDIVar, RO_Construct,
RO_Context),
report_illegal_state_var_update(Context,
ro_construct_name(RO_Construct), RO_Context, OuterStateVar,
!UrInfo),
new_state_var_instance(OuterStateVar, name_middle, OuterUOVar,
!UrInfo),
OuterScopeInfo = svar_outer_atomic_scope_info(OuterStateVar,
BeforeStatus, BeforeStatus)
;
BeforeStatus = status_known(OuterDIVar),
new_state_var_instance(OuterStateVar, name_middle, OuterUOVar,
!UrInfo),
AfterStatus = status_known(OuterUOVar),
OuterScopeInfo = svar_outer_atomic_scope_info(OuterStateVar,
BeforeStatus, AfterStatus)
;
( BeforeStatus = status_known_updated(_, _)
; BeforeStatus = status_unknown_updated(_)
),
% This status should exist in a status map only when we are in the
% middle of processing an atomic goal.
unexpected($pred, "status updated")
)
else
report_non_visible_state_var("", Context, OuterStateVar, !UrInfo),
new_state_var_instance(OuterStateVar, name_middle, OuterDIVar,
!UrInfo),
new_state_var_instance(OuterStateVar, name_middle, OuterUOVar,
!UrInfo),
OuterScopeInfo = no_svar_outer_atomic_scope_info
).
svar_finish_outer_atomic_scope(OuterScopeInfo, !SVarState) :-
(
OuterScopeInfo = svar_outer_atomic_scope_info(OuterStateVar,
_BeforeStatus, AfterStatus),
StatusMap0 = !.SVarState ^ state_status_map,
map.det_insert(OuterStateVar, AfterStatus, StatusMap0, StatusMap),
!SVarState ^ state_status_map := StatusMap
;
OuterScopeInfo = no_svar_outer_atomic_scope_info
).
%---------------------------------------------------------------------------%
:- type svar_inner_atomic_scope_info
---> svar_inner_atomic_scope_info(
siasi_state_var :: svar,
siasi_di_var :: prog_var,
siasi_state_before :: svar_state
).
svar_start_inner_atomic_scope(_Context, InnerStateVar, InnerScopeInfo,
!SVarState, !UrInfo) :-
SVarStateBefore = !.SVarState,
new_state_var_instance(InnerStateVar, name_initial, InnerDIVar, !UrInfo),
StatusMap0 = !.SVarState ^ state_status_map,
map.set(InnerStateVar, status_known(InnerDIVar), StatusMap0, StatusMap),
!SVarState ^ state_status_map := StatusMap,
InnerScopeInfo = svar_inner_atomic_scope_info(InnerStateVar, InnerDIVar,
SVarStateBefore).
svar_finish_inner_atomic_scope(_Context, InnerScopeInfo,
InnerDIVar, InnerUOVar, !SVarState) :-
InnerScopeInfo = svar_inner_atomic_scope_info(InnerStateVar, InnerDIVar,
SVarStateBefore),
StatusMap0 = !.SVarState ^ state_status_map,
map.lookup(StatusMap0, InnerStateVar, Status),
(
Status = status_known(InnerUOVar)
;
( Status = status_unknown
; Status = status_unknown_updated(_)
; Status = status_known_ro(_, _, _)
; Status = status_known_updated(_, _)
),
unexpected($pred, "status != known")
),
!:SVarState = SVarStateBefore.
%---------------------------------------------------------------------------%
%
% Look up prog_vars for a state_var.
%
replace_any_dot_colon_state_var_in_terms([], [], !SVarState, !UrInfo).
replace_any_dot_colon_state_var_in_terms([Arg0 | Args0], [Arg | Args],
!SVarState, !UrInfo) :-
replace_any_dot_colon_state_var_in_term(Arg0, Arg, !SVarState, !UrInfo),
replace_any_dot_colon_state_var_in_terms(Args0, Args, !SVarState, !UrInfo).
replace_any_dot_colon_state_var_in_term(Arg0, Arg, !SVarState, !UrInfo) :-
( if Arg0 = functor(atom("!."), [variable(StateVar, _)], Context) then
lookup_dot_state_var(Context, StateVar, Var, !SVarState, !UrInfo),
Arg = variable(Var, Context)
else if Arg0 = functor(atom("!:"), [variable(StateVar, _)], Context) then
lookup_colon_state_var(Context, StateVar, Var, !SVarState, !UrInfo),
Arg = variable(Var, Context)
else
Arg = Arg0
).
lookup_dot_state_var(Context, StateVar, Var, !SVarState, !UrInfo) :-
StatusMap0 = !.SVarState ^ state_status_map,
( if map.search(StatusMap0, StateVar, Status) then
(
Status = status_unknown,
report_uninitialized_state_var(warn_dodgy_simple_code, Context,
StateVar, !UrInfo),
% We make StateVar known to avoid duplicate reports.
new_state_var_instance(StateVar, name_middle, Var, !UrInfo),
map.det_update(StateVar, status_known(Var),
StatusMap0, StatusMap),
!SVarState ^ state_status_map := StatusMap
;
Status = status_unknown_updated(NewVar),
report_uninitialized_state_var(warn_dodgy_simple_code, Context,
StateVar, !UrInfo),
% We make StateVar known to avoid duplicate reports.
new_state_var_instance(StateVar, name_middle, Var, !UrInfo),
map.det_update(StateVar, status_known_updated(Var, NewVar),
StatusMap0, StatusMap),
!SVarState ^ state_status_map := StatusMap
;
( Status = status_known(Var)
; Status = status_known_ro(Var, _, _)
; Status = status_known_updated(Var, _)
)
)
else
report_non_visible_state_var(".", Context, StateVar, !UrInfo),
Var = StateVar
).
lookup_colon_state_var(Context, StateVar, Var, !SVarState, !UrInfo) :-
StatusMap0 = !.SVarState ^ state_status_map,
( if map.search(StatusMap0, StateVar, Status) then
(
Status = status_unknown,
new_state_var_instance(StateVar, name_middle, Var, !UrInfo),
map.det_update(StateVar, status_unknown_updated(Var),
StatusMap0, StatusMap),
!SVarState ^ state_status_map := StatusMap
;
Status = status_known(OldVar),
new_state_var_instance(StateVar, name_middle, Var, !UrInfo),
map.det_update(StateVar, status_known_updated(OldVar, Var),
StatusMap0, StatusMap),
!SVarState ^ state_status_map := StatusMap
;
Status = status_known_ro(OldVar, RO_Construct, RO_Context),
(
RO_Construct = roc_lambda,
RO_ConstructName = "lambda expression"
),
report_illegal_state_var_update(Context, RO_ConstructName,
RO_Context, StateVar, !UrInfo),
% We remove the readonly notation to avoid duplicate reports.
new_state_var_instance(StateVar, name_middle, Var, !UrInfo),
map.det_update(StateVar, status_known_updated(OldVar, Var),
StatusMap0, StatusMap),
!SVarState ^ state_status_map := StatusMap
;
Status = status_known_updated(_OldVar, Var)
;
Status = status_unknown_updated(Var)
)
else
report_non_visible_state_var(":", Context, StateVar, !UrInfo),
% We could make StateVar known to avoid duplicate reports.
% new_state_var_instance(StateVar, name_initial, Var, !UrInfo),
% map.det_insert(StateVar, status_known_updated(Var, Var),
% StatusMap0, StatusMap),
% !SVarState ^ state_status_map := StatusMap
Var = StateVar
).
% Look up the prog_var representing the current state of the state_var
% whose status is given as the second argument.
%
:- func svar_get_current_progvar(loc_kind, svar_status) = prog_var.
svar_get_current_progvar(LocKind, Status) = ProgVar :-
(
LocKind = loc_whole_goal,
(
Status = status_known(ProgVar)
;
( Status = status_known_ro(_, _, _)
; Status = status_known_updated(_, _)
; Status = status_unknown
; Status = status_unknown_updated(_)
),
unexpected($pred, "Status not known")
)
;
LocKind = loc_inside_atomic_goal,
(
Status = status_known(ProgVar)
;
Status = status_known_updated(_, ProgVar)
;
Status = status_unknown_updated(ProgVar)
;
( Status = status_known_ro(_, _, _)
; Status = status_unknown
),
unexpected($pred, "Status not known or updated")
)
).
%---------------------------------------------------------------------------%
%
% Code to handle the flattening of conjunctions. We need to be careful when we
% do so, since the goal we flatten could have a goal id, which would mean that
% the svar_store could have a delayed remapping for that goal_id. Just
% flattening the goal would remove the goal_info containing the goal_id from
% the HLDS, and the delayed renaming would not get done.
%
% Therefore when we flatten such a goal, we ensure that its subgoals
% all have goal_ids (creating new ones if needed), and that the delayed
% renaming that now won't get done on the conjunction as a whole
% *will* get done on each conjunct.
%
svar_flatten_conj(Context, Goals, Goal, !UrInfo) :-
list.map_foldl(svar_goal_to_conj_list, Goals, GoalConjuncts, !UrInfo),
list.condense(GoalConjuncts, Conjuncts),
GoalExpr = conj(plain_conj, Conjuncts),
goal_info_init(Context, GoalInfo),
Goal = hlds_goal(GoalExpr, GoalInfo).
svar_goal_to_conj_list(Goal, Conjuncts, !UrInfo) :-
% The code here is the same as in svar_goal_to_conj_list_internal,
% modulo the differences in the argument list.
Goal = hlds_goal(GoalExpr, GoalInfo),
( if GoalExpr = conj(plain_conj, Conjuncts0) then
SVarStore0 = !.UrInfo ^ ui_state_var_store,
SVarStore0 = svar_store(NextGoalId0, DelayedRenamingMap0,
LastIdMap0, SVarSpecs0),
GoalId = goal_info_get_goal_id(GoalInfo),
( if map.search(DelayedRenamingMap0, GoalId, GoalDelayedRenaming) then
list.map_foldl2(
add_conjunct_delayed_renames(GoalDelayedRenaming),
Conjuncts0, Conjuncts, NextGoalId0, NextGoalId,
DelayedRenamingMap0, DelayedRenamingMap),
SVarStore = svar_store(NextGoalId, DelayedRenamingMap,
LastIdMap0, SVarSpecs0),
!UrInfo ^ ui_state_var_store := SVarStore
else
Conjuncts = Conjuncts0
)
else
Conjuncts = [Goal]
).
:- pred svar_goal_to_conj_list_internal(hlds_goal::in, list(hlds_goal)::out,
ucounter::in, ucounter::out,
incremental_rename_map::in, incremental_rename_map::out) is det.
svar_goal_to_conj_list_internal(Goal, Conjuncts,
!NextGoalId, !DelayedRenamingMap) :-
% The code here is the same as in svar_goal_to_conj_list,
% modulo the differences in the argument list.
Goal = hlds_goal(GoalExpr, GoalInfo),
( if GoalExpr = conj(plain_conj, Conjuncts0) then
GoalId = goal_info_get_goal_id(GoalInfo),
( if map.search(!.DelayedRenamingMap, GoalId, GoalDelayedRenaming) then
list.map_foldl2(
add_conjunct_delayed_renames(GoalDelayedRenaming),
Conjuncts0, Conjuncts, !NextGoalId, !DelayedRenamingMap)
else
Conjuncts = Conjuncts0
)
else
Conjuncts = [Goal]
).
:- pred add_conjunct_delayed_renames(assoc_list(prog_var, prog_var)::in,
hlds_goal::in, hlds_goal::out, ucounter::in, ucounter::out,
incremental_rename_map::in, incremental_rename_map::out) is det.
add_conjunct_delayed_renames(DelayedRenamingToAdd, Goal0, Goal,
!NextGoalId, !DelayedRenamingMap) :-
Goal0 = hlds_goal(GoalExpr, GoalInfo0),
GoalId0 = goal_info_get_goal_id(GoalInfo0),
( if map.search(!.DelayedRenamingMap, GoalId0, DelayedRenaming0) then
% The goal id must be valid.
DelayedRenaming = DelayedRenamingToAdd ++ DelayedRenaming0,
map.det_update(GoalId0, DelayedRenaming, !DelayedRenamingMap),
Goal = Goal0
else
% The goal id must be invalid, since the only thing that attaches goal
% ids to goals at this stage of the compilation process is this module,
% and it attaches goal_ids to goals only if it also puts them the
% delayed renaming map.
counter.uallocate(GoalIdNum, !NextGoalId),
GoalId = goal_id(GoalIdNum),
goal_info_set_goal_id(GoalId, GoalInfo0, GoalInfo),
map.det_insert(GoalId, DelayedRenamingToAdd, !DelayedRenamingMap),
Goal = hlds_goal(GoalExpr, GoalInfo)
).
%---------------------------------------------------------------------------%
%
% A post-pass to delete unneeded copy unifications. Such unifications
% can hide singleton variable problems.
%
% Suppose we have a goal such as this:
%
% ( if p(..., !.S, !:S) then
% <then_part>
% else
% <else_part>
% )
%
% and that !:S is not referred to anywhere in the clause
% (not in then_part, not in else_part, not in the following code).
%
% In this form, warn_singletons can generate a warning about !:S
% being a singleton. However, the state variable transformation
% transforms it to code like this:
%
% ( if p(..., STATE_VARIABLE_S_5, STATE_VARIABLE_S_6) then
% <then_part>, STATE_VARIABLE_S_7 = STATE_VARIABLE_S_6
% else
% <else_part>, STATE_VARIABLE_S_7 = STATE_VARIABLE_S_5
% )
%
% and marks both assignments to STATE_VARIABLE_S_7 as not being subject
% to singleton warnings.
%
% We don't actually *want* to generate warnings about the assignments
% to STATE_VARIABLE_S_7, because the problem is not in those assignments
% or in the if-then-else arms that contain them. Instead, it is
% in the condition. However, with this version of the code, the occurrence
% of STATE_VARIABLE_S_6 in the condition is *not* a singleton.
%
% To allow us to generate a warning about !:S in the condition,
% we delete all copy unifications inserted by the state variable
% transformation that assign to a variable that is not referred to
% either in the code that follows the assignment, or in the head.
% (The head contains the output arguments, which will live
% beyond the lifetime of anything in the clause body.)
%
% To find which variables occur after a copy goal, we have
% delete_unneeded_copy_goals do a backwards traversal of the clause body,
% keeping track of all the variables it has seen.
%
% To find which variables occur in the head, we use the call to goal_vars
% below. The variables in HeadUnificationsGoal contain not just the
% head_vars of the clause, but also the state variable instances any
% of them are unified with. (This includes the input arguments as well as
% the output arguments, but the clause body won't contain any assignments
% to either the input arguments or the state variable instances
% they are unified with, so including them in SeenLater0 is harmless.
% As it happens, we have to include them because we don't know
% which arguments are input and which are output, a distinction
% that in any case may be mode-dependent.)
%
% We cannot count on the definitions of those state var instances being
% before any of the occurrences of the head vars they are unified with.
% For example, if a fact contains an !S argument pair, the call to
% svar_finish_body in our caller will put the unification of the state
% var instances representing !.S and !:S *after* HeadUnificationsGoal.
% If we initialized SeenLater0 to just the head_vars of the clause,
% this unification would assign to a variable that is *not* in SeenLater0,
% and would thus be eliminated, which would be a bug.
%
:- pred delete_unneeded_copy_goals_in_clause(hlds_goal::in,
hlds_goal::in, hlds_goal::out) is det.
delete_unneeded_copy_goals_in_clause(HeadUnificationsGoal, Goal0, Goal) :-
vars_in_goal(HeadUnificationsGoal, HeadUnificationsGoalVars),
SeenLater0 = HeadUnificationsGoalVars,
delete_unneeded_copy_goals(Goal0, Goal, SeenLater0, _SeenLater).
:- pred delete_unneeded_copy_goals(hlds_goal::in, hlds_goal::out,
set_of_progvar::in, set_of_progvar::out) is det.
delete_unneeded_copy_goals(Goal0, Goal, SeenAfter, SeenBefore) :-
Goal0 = hlds_goal(GoalExpr0, GoalInfo),
(
GoalExpr0 = unify(LHSVar, _, _, _, _),
vars_in_goal(Goal0, GoalVars0),
( if
goal_info_has_feature(GoalInfo, feature_state_var_copy),
not set_of_var.member(SeenAfter, LHSVar)
then
Goal = hlds_goal(true_goal_expr, GoalInfo),
SeenBefore = SeenAfter
else
set_of_var.union(GoalVars0, SeenAfter, SeenBefore),
Goal = Goal0
)
;
( GoalExpr0 = plain_call(_, _, _, _, _, _)
; GoalExpr0 = generic_call(_, _, _, _, _)
; GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _)
),
vars_in_goal(Goal0, GoalVars0),
set_of_var.union(GoalVars0, SeenAfter, SeenBefore),
Goal = Goal0
;
GoalExpr0 = conj(ConjKind, Conjuncts0),
% Processing Conjuncts0 without reversing it would lead to recursion
% as deep as Conjuncts0 is long. Since Conjuncts0 can be very long,
% we prefer to pay the price of reversing and unreversing the list
% to achieve tail recursion.
list.reverse(Conjuncts0, RevConjuncts0),
delete_unneeded_copy_goals_rev_conj(RevConjuncts0, RevConjuncts,
SeenAfter, SeenBefore),
list.reverse(RevConjuncts, Conjuncts),
GoalExpr = conj(ConjKind, Conjuncts),
Goal = hlds_goal(GoalExpr, GoalInfo)
;
GoalExpr0 = disj(Disjuncts0),
delete_unneeded_copy_goals_disj(Disjuncts0, Disjuncts,
SeenAfter, SeenBefores),
GoalExpr = disj(Disjuncts),
set_of_var.union_list(SeenBefores, SeenBefore),
Goal = hlds_goal(GoalExpr, GoalInfo)
;
GoalExpr0 = switch(SwitchVar, CanFail, Cases0),
% Switches should not exist at this point in the compilation process,
% but it is simple enough to prepare here for the eventuality that
% this may change in the future.
delete_unneeded_copy_goals_switch(Cases0, Cases,
SeenAfter, SeenBefores),
GoalExpr = switch(SwitchVar, CanFail, Cases),
set_of_var.union_list(SeenBefores, SeenBefore0),
set_of_var.insert(SwitchVar, SeenBefore0, SeenBefore),
Goal = hlds_goal(GoalExpr, GoalInfo)
;
GoalExpr0 = if_then_else(ITEVars, Cond0, Then0, Else0),
delete_unneeded_copy_goals(Else0, Else, SeenAfter, SeenBeforeElse),
delete_unneeded_copy_goals(Then0, Then, SeenAfter, SeenAfterThen),
delete_unneeded_copy_goals(Cond0, Cond, SeenAfterThen, SeenBeforeCond),
GoalExpr = if_then_else(ITEVars, Cond, Then, Else),
set_of_var.union(SeenBeforeCond, SeenBeforeElse, SeenBefore0),
set_of_var.insert_list(ITEVars, SeenBefore0, SeenBefore),
Goal = hlds_goal(GoalExpr, GoalInfo)
;
GoalExpr0 = negation(SubGoal0),
delete_unneeded_copy_goals(SubGoal0, SubGoal, SeenAfter, SeenBefore),
GoalExpr = negation(SubGoal),
Goal = hlds_goal(GoalExpr, GoalInfo)
;
GoalExpr0 = scope(Reason, SubGoal0),
(
Reason = from_ground_term(TermVar, _Kind),
% There won't be any feature_state_var_copy goals inside SubGoal0.
SubGoal = SubGoal0,
% None of the variables in SubGoal can occur in the rest of the
% procedure body, with the exception of TermVar.
set_of_var.insert(TermVar, SeenAfter, SeenBefore)
;
( Reason = require_complete_switch(ScopeVar)
; Reason = require_switch_arms_detism(ScopeVar, _Detism)
),
delete_unneeded_copy_goals(SubGoal0, SubGoal,
SeenAfter, SeenBefore0),
set_of_var.insert(ScopeVar, SeenBefore0, SeenBefore)
;
Reason = loop_control(LCVar, LCSVar, _UseParentStack),
delete_unneeded_copy_goals(SubGoal0, SubGoal,
SeenAfter, SeenBefore0),
set_of_var.insert_list([LCVar, LCSVar], SeenBefore0, SeenBefore)
;
( Reason = exist_quant(ScopeVars, _)
; Reason = promise_solutions(ScopeVars, _PromiseKind)
; Reason = trace_goal(_Comp, _Run, _MaybeIO, _Mutables, ScopeVars)
),
delete_unneeded_copy_goals(SubGoal0, SubGoal,
SeenAfter, SeenBefore0),
set_of_var.insert_list(ScopeVars, SeenBefore0, SeenBefore)
;
( Reason = disable_warnings(_, _)
; Reason = promise_purity(_)
; Reason = require_detism(_)
; Reason = commit(_)
; Reason = barrier(_)
),
delete_unneeded_copy_goals(SubGoal0, SubGoal,
SeenAfter, SeenBefore)
),
GoalExpr = scope(Reason, SubGoal),
Goal = hlds_goal(GoalExpr, GoalInfo)
;
GoalExpr0 = shorthand(ShortHand0),
(
ShortHand0 = atomic_goal(AtomicType,
atomic_interface_vars(OuterInitVar, OuterFinalVar),
atomic_interface_vars(InnerInitVar, InnerFinalVar),
MaybeOutputVars, MainGoal0, OrElseGoals0, OrElseInners),
expect(unify(OrElseInners, []), $pred, "OrElseInners != []"),
Disjuncts0 = [MainGoal0 | OrElseGoals0],
delete_unneeded_copy_goals_disj(Disjuncts0, Disjuncts,
SeenAfter, SeenBefores),
(
Disjuncts = [],
unexpected($pred, "Disjuncts = []")
;
Disjuncts = [MainGoal | OrElseGoals]
),
ShortHand = atomic_goal(AtomicType,
atomic_interface_vars(OuterInitVar, OuterFinalVar),
atomic_interface_vars(InnerInitVar, InnerFinalVar),
MaybeOutputVars, MainGoal, OrElseGoals, OrElseInners),
set_of_var.union_list(SeenBefores, SeenBefore0),
set_of_var.insert_list([OuterInitVar, OuterFinalVar,
InnerInitVar, InnerFinalVar], SeenBefore0, SeenBefore1),
(
MaybeOutputVars = no,
SeenBefore = SeenBefore1
;
MaybeOutputVars = yes(OutputVars),
set_of_var.insert_list(OutputVars, SeenBefore1, SeenBefore)
)
;
ShortHand0 = try_goal(MaybeIOStateVars, ResultVar, SubGoal0),
delete_unneeded_copy_goals(SubGoal0, SubGoal,
SeenAfter, SeenBefore0),
set_of_var.insert(ResultVar, SeenBefore0, SeenBefore1),
(
MaybeIOStateVars = no,
SeenBefore = SeenBefore1
;
MaybeIOStateVars = yes(try_io_state_vars(InitVar, FinalVar)),
set_of_var.insert(InitVar, SeenBefore1, SeenBefore2),
set_of_var.insert(FinalVar, SeenBefore2, SeenBefore)
),
ShortHand = try_goal(MaybeIOStateVars, ResultVar, SubGoal)
;
ShortHand0 = bi_implication(LeftGoal0, RightGoal0),
delete_unneeded_copy_goals(LeftGoal0, LeftGoal,
SeenAfter, SeenBeforeLeft),
delete_unneeded_copy_goals(RightGoal0, RightGoal,
SeenAfter, SeenBeforeRight),
set_of_var.union(SeenBeforeLeft, SeenBeforeRight, SeenBefore),
ShortHand = bi_implication(LeftGoal, RightGoal)
),
GoalExpr = shorthand(ShortHand),
Goal = hlds_goal(GoalExpr, GoalInfo)
).
:- pred delete_unneeded_copy_goals_rev_conj(
list(hlds_goal)::in, list(hlds_goal)::out,
set_of_progvar::in, set_of_progvar::out) is det.
delete_unneeded_copy_goals_rev_conj([], [], SeenAfter, SeenBefore) :-
SeenBefore = SeenAfter.
delete_unneeded_copy_goals_rev_conj(
[RevConjunct0 | RevConjuncts0], [RevConjunct | RevConjuncts],
SeenAfter, SeenBefore) :-
delete_unneeded_copy_goals(RevConjunct0, RevConjunct,
SeenAfter, SeenBetween),
delete_unneeded_copy_goals_rev_conj(RevConjuncts0, RevConjuncts,
SeenBetween, SeenBefore).
:- pred delete_unneeded_copy_goals_disj(
list(hlds_goal)::in, list(hlds_goal)::out,
set_of_progvar::in, list(set_of_progvar)::out) is det.
delete_unneeded_copy_goals_disj([], [], _, []).
delete_unneeded_copy_goals_disj(
[Disjunct0 | Disjuncts0], [Disjunct | Disjuncts],
SeenAfter, [SeenBefore | SeenBefores]) :-
delete_unneeded_copy_goals(Disjunct0, Disjunct, SeenAfter, SeenBefore),
delete_unneeded_copy_goals_disj(Disjuncts0, Disjuncts,
SeenAfter, SeenBefores).
:- pred delete_unneeded_copy_goals_switch(list(case)::in, list(case)::out,
set_of_progvar::in, list(set_of_progvar)::out) is det.
delete_unneeded_copy_goals_switch([], [], _, []).
delete_unneeded_copy_goals_switch([Case0 | Cases0], [Case | Cases],
SeenAfter, [SeenBefore | SeenBefores]) :-
Case0 = case(MainConsId, OtherConsIds, Goal0),
delete_unneeded_copy_goals(Goal0, Goal, SeenAfter, SeenBefore),
Case = case(MainConsId, OtherConsIds, Goal),
delete_unneeded_copy_goals_switch(Cases0, Cases, SeenAfter, SeenBefores).
%---------------------------------------------------------------------------%
%
% Test for various kinds of errors.
%
illegal_state_var_func_result(pf_function, ArgTerms, StateVar, Context) :-
list.last(ArgTerms, LastArgTerm),
is_term_a_bang_state_pair(LastArgTerm, StateVar, Context).
is_term_a_bang_state_pair(ArgTerm, StateVar, Context) :-
ArgTerm = functor(atom("!"), [variable(StateVar, Context)], _).
%---------------------------------------------------------------------------%
%
% Report various kinds of errors.
%
report_illegal_state_var_update(Context, RO_Construct, RO_Context,
StateVar, !UrInfo) :-
VarSet = !.UrInfo ^ ui_varset,
Name = varset.lookup_name(VarSet, StateVar),
Pieces1 = [words("Error: you cannot use")] ++
color_as_incorrect([quote("!:" ++ Name)]) ++
[words("here due to the surrounding"), words(RO_Construct),
suffix(";"),
words("you may only refer to")] ++
color_as_correct([quote("!." ++ Name), suffix(".")]) ++ [nl],
Msg1 = msg(Context, Pieces1),
Pieces2 = [words("Here is the surrounding context that makes"),
words("state variable"), quote(Name), words("readonly."), nl],
Msg2 = msg(RO_Context, Pieces2),
Spec = error_spec($pred, severity_error, phase_pt2h, [Msg1, Msg2]),
add_unravel_spec(Spec, !UrInfo).
:- func ro_construct_name(readonly_context_kind) = string.
ro_construct_name(roc_lambda) = "lambda expression".
%---------------------------------------------------------------------------%
report_illegal_func_svar_result(Context, StateVar, !UrInfo) :-
VarSet = !.UrInfo ^ ui_varset,
Spec = report_illegal_func_svar_result_raw(Context, VarSet, StateVar),
add_unravel_spec(Spec, !UrInfo).
report_illegal_func_svar_result_raw(Context, VarSet, StateVar) = Spec :-
Name = varset.lookup_name(VarSet, StateVar),
% While having !.Var appear as a function argument is quite ordinary,
% having it appear as a function *result* is not. We therefore do not
% suggest it as a likely correction.
Pieces = [words("Error: since it represents two arguments, not one,")] ++
color_as_incorrect([quote("!" ++ Name)]) ++
[words("cannot be a function result. You probably meant")] ++
color_as_correct([fixed("!:" ++ Name), suffix(".")]) ++ [nl],
Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces).
%---------------------------------------------------------------------------%
report_illegal_bang_svar_lambda_arg(Context, StateVar, !UrInfo) :-
VarSet = !.UrInfo ^ ui_varset,
Spec = report_illegal_bang_svar_lambda_arg_raw(Context, VarSet, StateVar),
add_unravel_spec(Spec, !UrInfo).
report_illegal_bang_svar_lambda_arg_raw(Context, VarSet, StateVar) = Spec :-
Name = varset.lookup_name(VarSet, StateVar),
Pieces = [words("Error:")] ++
color_as_incorrect([quote("!" ++ Name)]) ++
[words("cannot be a lambda argument."), nl,
words("Perhaps you meant")] ++
color_as_correct([quote("!." ++ Name)]) ++
[words("or")] ++
color_as_correct([quote("!:" ++ Name), suffix(".")]) ++ [nl],
Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces).
%---------------------------------------------------------------------------%
:- pred report_non_visible_state_var(string::in, prog_context::in, svar::in,
unravel_info::in, unravel_info::out) is det.
report_non_visible_state_var(DorC, Context, StateVar, !UrInfo) :-
VarSet = !.UrInfo ^ ui_varset,
Name = varset.lookup_name(VarSet, StateVar),
Pieces = [words("Error: state variable")] ++
color_as_incorrect([quote("!" ++ DorC ++ Name)]) ++
[words("is not visible in this context."), nl],
Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces),
add_unravel_spec(Spec, !UrInfo).
%---------------------------------------------------------------------------%
:- pred report_uninitialized_state_var(option::in, prog_context::in, svar::in,
unravel_info::in, unravel_info::out) is det.
report_uninitialized_state_var(WarnOption, Context, StateVar, !UrInfo) :-
VarSet = !.UrInfo ^ ui_varset,
Name = varset.lookup_name(VarSet, StateVar),
Pieces = [words("Warning: you cannot refer to")] ++
color_as_subject([quote("!." ++ Name)]) ++
[words("here, because that state variable has")] ++
color_as_incorrect([words("not been initialized")]) ++
[words("yet."), nl],
Spec = spec($pred, severity_warning(WarnOption), phase_pt2h,
Context, Pieces),
add_unravel_spec(Spec, !UrInfo).
%---------------------------------------------------------------------------%
:- pred report_repeated_head_state_var(prog_context::in, svar::in,
unravel_info::in, unravel_info::out) is det.
report_repeated_head_state_var(Context, StateVar, !UrInfo) :-
VarSet = !.UrInfo ^ ui_varset,
Name = varset.lookup_name(VarSet, StateVar),
Pieces = [words("Warning: clause head introduces")] ++
color_as_incorrect([words("state variable"), quote(Name)]) ++
[words("more than once."), nl],
Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces),
add_unravel_spec(Spec, !UrInfo).
%---------------------------------------------------------------------------%
:- pred report_state_var_shadow(prog_context::in, svar::in,
unravel_info::in, unravel_info::out) is det.
report_state_var_shadow(Context, StateVar, !UrInfo) :-
VarSet = !.UrInfo ^ ui_varset,
Name = varset.lookup_name(VarSet, StateVar),
Pieces = [words("Warning: new state variable")] ++
color_as_subject([quote(Name)]) ++
color_as_incorrect([words("shadows old one.")]) ++ [nl],
Spec = spec($pred, severity_warning(warn_state_var_shadowing), phase_pt2h,
Context, Pieces),
add_unravel_spec(Spec, !UrInfo).
%---------------------------------------------------------------------------%
:- pred report_missing_inits_in_ite(prog_context::in, list(string)::in,
string::in, string::in, unravel_info::in, unravel_info::out) is det.
report_missing_inits_in_ite(Context, NextStateVars,
WhenMissing, WhenNotMissing, !UrInfo) :-
NextStateVarsPieces = quote_list_to_color_pieces(color_subject, "and",
[suffix(",")], NextStateVars),
Pieces = [words("When the condition"), words(WhenNotMissing), suffix(","),
words("the if-then-else")] ++
color_as_inconsistent([words("defines")]) ++
NextStateVarsPieces ++
[words("but when the condition"), words(WhenMissing), suffix(",")] ++
color_as_inconsistent([words("it does not.")]) ++ [nl],
Spec = spec($pred, severity_warning(warn_missing_state_var_init),
phase_pt2h, Context, Pieces),
Specs0 = !.UrInfo ^ ui_state_var_store ^ store_missing_init_specs,
Specs = [Spec | Specs0],
!UrInfo ^ ui_state_var_store ^ store_missing_init_specs := Specs.
:- pred report_missing_inits_in_disjunct(prog_context::in, list(string)::in,
list(error_spec)::in, list(error_spec)::out) is det.
report_missing_inits_in_disjunct(Context, NextStateVars, !Specs) :-
Pieces = [words("Other disjuncts define")] ++
quote_list_to_color_pieces(color_subject, "and", [suffix(",")],
NextStateVars) ++
color_as_incorrect([words("but not this one.")]) ++ [nl],
Spec = spec($pred, severity_warning(warn_missing_state_var_init),
phase_pt2h, Context, Pieces),
% The intention is that our caller got !.Specs from the state var store's
% store_missing_init_specs field, and will put the updated list back there.
!:Specs = [Spec | !.Specs].
%---------------------------------------------------------------------------%
report_svar_unify_error(Context, StateVar, !SVarState, !UrInfo) :-
VarSet = !.UrInfo ^ ui_varset,
Name = varset.lookup_name(VarSet, StateVar),
Pieces = [words("Error:")] ++
color_as_incorrect([fixed("!" ++ Name)]) ++
[words("cannot appear as a unification argument."), nl,
words("You probably meant")] ++
color_as_correct([fixed("!." ++ Name)]) ++ [words("or")] ++
color_as_correct([fixed("!:" ++ Name), suffix(".")]) ++ [nl],
Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces),
add_unravel_spec(Spec, !UrInfo),
!.SVarState = svar_state(StatusMap0),
% If StateVar was not known before, then this is the first occurrence
% of this state variable, and the user almost certainly intended it
% to define its initial value. Any messages from later goals complaining
% about the variable not being defined there would only be a distraction.
%
% Adding this dummy entry to the state, means we cannot generate valid
% HLDS goals, but the error reported just above ensures that we will
% throw away the HLDS goals we generate, so this is ok.
( if
map.search(StatusMap0, StateVar, OldStatus),
OldStatus \= status_unknown
then
% The state variable is already known.
true
else
new_state_var_instance(StateVar, name_initial, Var, !UrInfo),
Status = status_known(Var),
map.set(StateVar, Status, StatusMap0, StatusMap),
!:SVarState = svar_state(StatusMap)
).
%---------------------------------------------------------------------------%
:- pred find_unused_statevar_args(prog_varset::in, new_statevar_map::in,
last_id_map::in, unused_statevar_arg_map::out) is det.
find_unused_statevar_args(VarSet, NewSVars, LastIdMap, UnusedSVarArgMap) :-
map.foldl(record_statevar_if_unused(VarSet, LastIdMap), NewSVars,
map.init, UnusedSVarArgMap).
:- pred record_statevar_if_unused(prog_varset::in, last_id_map::in,
prog_var::in, one_or_more(maybe_statevar_arg_pos)::in,
unused_statevar_arg_map::in, unused_statevar_arg_map::out) is det.
record_statevar_if_unused(VarSet, LastIdMap, SVar, OoMArgPos,
!UnusedSVarArgMap) :-
list.sort(one_or_more_to_list(OoMArgPos), SortedArgPoss),
( if
not map.search(LastIdMap, SVar, _),
(
SortedArgPoss = [arg_old(InitPos), arg_new(FinalPos)],
ArgPos = InitPos,
ArgKind = init_and_final_arg(FinalPos)
;
SortedArgPoss = [arg_old(InitPos)],
ArgPos = InitPos,
ArgKind = init_arg_only
;
SortedArgPoss = [arg_new(FinalPos)],
ArgPos = FinalPos,
ArgKind = final_arg_only
)
then
varset.lookup_name(VarSet, SVar, SVarName),
SVarArgDesc = statevar_arg_desc(ArgKind, SVarName),
map.det_insert(ArgPos, SVarArgDesc, !UnusedSVarArgMap)
else
true
).
%---------------------------------------------------------------------------%
:- pred report_any_unneeded_svars_in_lambda(prog_context::in,
list(mer_mode)::in, goal::in, hlds_goal::in, unused_statevar_arg_map::in,
unravel_info::in, unravel_info::out) is det.
report_any_unneeded_svars_in_lambda(Context, Modes, ParseTreeGoal, Goal,
UnusedSVarArgMap, !UrInfo) :-
( if map.is_empty(UnusedSVarArgMap) then
true
else
VarSet = !.UrInfo ^ ui_varset,
non_svar_copy_vars_in_goal(Goal, GoalVarsSet),
set_of_var.to_sorted_list(GoalVarsSet, GoalVars),
list.filter_map(is_prog_var_for_some_state_var(VarSet),
GoalVars, GoalVarSVarNames),
map.foldl(
report_unneeded_svar_in_lambda(Context, Modes,
ParseTreeGoal, GoalVarSVarNames),
UnusedSVarArgMap, !UrInfo)
).
:- pred report_unneeded_svar_in_lambda(prog_context::in, list(mer_mode)::in,
goal::in, list(string)::in, uint::in, statevar_arg_desc::in,
unravel_info::in, unravel_info::out) is det.
report_unneeded_svar_in_lambda(Context, Modes, ParseTreeGoal, GoalVarSVarNames,
ArgNum, SVarArgDesc, !UrInfo) :-
SVarArgDesc = statevar_arg_desc(InitOrFinal, SVarName),
% Please keep the wording of the three warnings generated here
% in sync with the code of the following predicates in pre_typecheck.m:
% - warn_about_any_unneeded_initial_statevars
% - warn_about_unneeded_final_statevar
% - warn_about_unneeded_initial_final_statevar.
(
( InitOrFinal = init_arg_only, Prefix = "!."
; InitOrFinal = final_arg_only, Prefix = "!:"
),
Pieces = [words("Warning: the state variable")] ++
color_as_subject([quote(Prefix ++ SVarName)]) ++ [words("is")] ++
color_as_incorrect([words("never updated")]) ++
[words("in this lambda expressions, so it should be"),
words("replaced with an ordinary variable."), nl],
Severity = severity_warning(warn_unneeded_initial_statevars_lambda),
Spec = spec($pred, Severity, phase_pt2h, Context, Pieces),
add_unravel_spec(Spec, !UrInfo)
;
InitOrFinal = init_and_final_arg(_),
% Please keep this wording in sync with the code of the
% warn_about_unneeded_final_statevar predicate in pre_typecheck.m.
InitOrFinal = init_and_final_arg(FinalArgNum),
( if list.member(SVarName, GoalVarSVarNames) then
% The initial version of SVarName is used by user-written code
% in the lambda goal, so only the final version of SVarName
% is unneeded.
ModuleInfo = !.UrInfo ^ ui_module_info,
FinalArgNumI = uint.cast_to_int(FinalArgNum),
InitArgNumI = uint.cast_to_int(ArgNum),
list.det_index1(Modes, InitArgNumI, InitArgMode),
list.det_index1(Modes, FinalArgNumI, FinalArgMode),
( if
% See the comments in warn_about_any_unneeded_statevars
% for the reasoning behind this test.
%
% Note that we cannot test the HLDS goal from which our caller
% derived GoalVarSVarNames, because that contains the
% unifications implicitly added by the state variable
% transformation itself. We need the goal from *before*
% that transformation.
not ( ParseTreeGoal = true_expr(_) ),
% See the comments in maybe_warn_about_unneeded_final_statevar
% for the reasoning behind this test.
mode_is_free_of_uniqueness(ModuleInfo, InitArgMode),
mode_is_free_of_uniqueness(ModuleInfo, FinalArgMode)
then
Pieces = [words("Warning: the argument")] ++
color_as_subject([quote("!:" ++ SVarName)]) ++
[words("in this lambda expression")] ++
color_as_incorrect([words("could be deleted,")]) ++
[words("because its value"),
words("is always the same as its initial value."), nl],
Severity =
severity_warning(warn_unneeded_final_statevars_lambda),
Spec = spec($pred, Severity, phase_pt2h, Context, Pieces),
add_unravel_spec(Spec, !UrInfo)
else
true
)
else
% The initial version of SVarName is NOT used by user-written code
% in the lambda goal, so both the initial and final versions
% of SVarName are unneeded.
Pieces = [words("Warning: the arguments")] ++
color_as_subject([quote("!." ++ SVarName)]) ++
[words("and")] ++
color_as_subject([quote("!:" ++ SVarName)]) ++
[words("in this lambda expression")] ++
color_as_incorrect([words("could be deleted,")]) ++
[words("because they are not used in the lambda goal,"),
words("and because the final value"),
words("is always the same as the initial value."), nl],
Severity = severity_warning(warn_unneeded_final_statevars_lambda),
Spec = spec($pred, Severity, phase_pt2h, Context, Pieces),
add_unravel_spec(Spec, !UrInfo)
)
).
%---------------------------------------------------------------------------%
:- end_module hlds.make_hlds.state_var.
%---------------------------------------------------------------------------%