Files
mercury/compiler/goal_form.m
Zoltan Somogyi 0326634b10 Move pred markers and goal features to a new module.
compiler/hlds_markers.m:
    This is that new module.

compiler/hlds.m:
compiler/notes/compiler_design.html:
    Include and document the new module.

compiler/hlds_pred.m:
compiler/hlds_goal.m:
    Delete the moved code.

compiler/*.m:
    Conform to the changes above. Roughly a third of the modules
    that import hlds_pred.m or hlds_goal.m import the new module.
2025-01-12 22:10:28 +11:00

1332 lines
43 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2002-2012 The University of Melbourne.
% Copyright (C) 2014-2018, 2022, 2024-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: goal_form.m.
% Main authors: conway, zs.
%
% A module that provides functions that check whether goals fulfill particular
% criteria.
%
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- module hlds.goal_form.
:- interface.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.set_of_var.
:- import_module bool.
:- import_module list.
%-----------------------------------------------------------------------------%
% Is the input goal a conjunction of unifications that constructs every
% variable in the given set? A from_ground_term_construct scope counts
% as a unification.
%
:- pred goal_is_conj_of_unify(set_of_progvar::in, hlds_goal::in) is semidet.
% Run goal_is_conj_of_unify on each goal in the list.
%
:- pred all_disjuncts_are_conj_of_unify(set_of_progvar::in,
list(hlds_goal)::in) is semidet.
%-----------------------------------------------------------------------------%
% An indication of whether a goal can loop forever.
%
:- type goal_loop_status
---> can_loop
; cannot_loop.
% An indication of whether a goal can throw an exception.
%
:- type goal_throw_status
---> can_throw
; cannot_throw.
% An indication of whether a goal can loop forever or throw an exception.
%
:- type goal_loop_or_throw_status
---> can_loop_or_throw
; cannot_loop_or_throw.
%-----------------------------------------------------------------------------%
%
% These versions use information from the intermodule-analysis framework,
% which is they have the "_imaf" suffix.
%
% XXX Eventually we will only use these versions and the others can be
% deleted.
% Return `goal_can_throw' if the given goal may throw an exception; return
% `goal_cannot_throw' otherwise.
%
% This version differs from the ones below in that it can use results from
% the intermodule-analysis framework (if they are available). The HLDS
% and I/O state need to be threaded through in case analysis files need to
% be read and in case IMDGs need to be updated.
%
:- pred goal_can_throw_imaf(hlds_goal::in, goal_throw_status::out,
module_info::in, module_info::out) is det.
% Return `can_loop_or_throw' if the goal may loop forever or throw an
% exception and return `cannot_loop_or_throw' otherwise.
%
:- pred goal_can_loop_or_throw_imaf(hlds_goal::in,
goal_loop_or_throw_status::out, module_info::in, module_info::out) is det.
%-----------------------------------------------------------------------------%
% The first three versions may be more accurate because they can use
% results of the termination and exception analyses.
% XXX These don't work with the intermodule-analysis framework, so don't
% use them in new code.
% Succeeds if the goal cannot loop forever.
%
:- pred goal_cannot_loop_term_info(module_info::in, hlds_goal::in) is semidet.
% Succeeds if the goal can loop forever.
%
:- pred goal_can_loop_term_info(module_info::in, hlds_goal::in) is semidet.
% Succeeds if the goal cannot throw an exception.
%
:- pred goal_cannot_throw_term_info(module_info::in, hlds_goal::in) is semidet.
% Succeeds if the goal can throw an exception.
%
:- pred goal_can_throw_term_info(module_info::in, hlds_goal::in) is semidet.
% Succeeds if the goal cannot loop forever or throw an exception.
%
:- pred goal_cannot_loop_or_throw_term_info(module_info::in, hlds_goal::in)
is semidet.
% Succeeds if the goal can loop forever or throw an exception.
%
:- pred goal_can_loop_or_throw_term_info(module_info::in, hlds_goal::in)
is semidet.
% These versions do not use the results of the termination or exception
% analyses.
% Succeeds if the goal cannot loop forever or throw an exception.
%
:- pred goal_cannot_loop_or_throw(hlds_goal::in) is semidet.
% Succeed if the goal can loop forever or throw an exception.
%
:- pred goal_can_loop_or_throw(hlds_goal::in) is semidet.
% goal_is_flat(Goal) return `yes' if Goal does not contain any
% branched structures (ie if-then-else or disjunctions or switches.)
%
:- func goal_is_flat(hlds_goal) = bool.
% Determine whether a goal might allocate some heap space, i.e.
% whether it contains any construction unifications or predicate calls.
% BEWARE that this predicate is only an approximation,
% used to decide whether or not to try to reclaim the heap space;
% currently it fails even for some goals which do allocate heap space,
% such as construction of boxed constants.
%
:- pred goal_may_allocate_heap(hlds_goal::in) is semidet.
:- pred goals_may_allocate_heap(list(hlds_goal)::in) is semidet.
% Succeed if execution of the given goal cannot encounter a context
% that causes any variable to be flushed to its stack slot. If such a goal
% needs a resume point, and that resume point cannot be backtracked to
% once control leaves the goal, then the only entry point we need
% for the resume point is the one with the resume variables in their
% original locations.
%
:- pred goal_cannot_stack_flush(hlds_goal::in) is semidet.
% Succeed if the given goal cannot fail before encountering a
% context that forces all variables to be flushed to their stack slots.
% If such a goal needs a resume point, the only entry point we need
% is the stack entry point.
%
:- pred cannot_fail_before_stack_flush(hlds_goal::in) is semidet.
% count_recursive_calls(Goal, PredId, ProcId, Min, Max). Given that
% we are in predicate PredId and procedure ProcId, return the minimum
% and maximum number of recursive calls that an execution of Goal
% may encounter.
%
:- pred count_recursive_calls(hlds_goal::in, pred_id::in, proc_id::in,
int::out, int::out) is det.
%-----------------------------------------------------------------------------%
% Returns `yes' if the goal does not modify the trail.
%
:- func goal_cannot_modify_trail(hlds_goal_info) = bool.
% Returns `yes' if the goal may modify the trail.
%
:- func goal_may_modify_trail(hlds_goal_info) = bool.
%-----------------------------------------------------------------------------%
% Returns yes if the goal, or subgoal contained within, contains
% any foreign code.
%
:- func goal_has_foreign(hlds_goal) = bool.
:- type has_subgoals
---> has_subgoals
; does_not_have_subgoals.
% A goal is primitive iff it doesn't contain any sub-goals
% (except possibly goals inside lambda expressions --
% but lambda expressions will get transformed into separate
% predicates by lambda.m).
%
:- func goal_expr_has_subgoals(hlds_goal_expr) = has_subgoals.
%-----------------------------------------------------------------------------%
% Insist on both given lists having exactly one element,
% and return those elements. The lists are intended to contain
% the arguments and unify modes respectively of a function symbol
% whose representation is no_tag or direct_arg_tag, which means
% that its arity must be exactly one.
%
:- pred get_notag_or_direct_arg_arg_mode(list(Arg)::in, list(UM)::in,
Arg::out, UM::out) is det.
% A version of the above without the modes.
%
:- pred get_notag_or_direct_arg_arg(list(Arg)::in, Arg::out) is det.
%-----------------------------------------------------------------------------%
:- type is_termvar_needed
---> termvar_is_not_needed
; termvar_is_needed.
% Given the TermVar and the subgoal of a from_ground_term_construct scope,
% return an indication of whether the termvar is needed outside the scope,
% the conjuncts inside the scope, and the conjunction's goal_info.
% The conjuncts should all be processable (without an abort) by
% get_from_ground_term_construct_conjunct_info.
%
:- pred get_from_ground_term_construct_info(prog_var::in, hlds_goal::in,
is_termvar_needed::out, list(hlds_goal)::out, hlds_goal_info::out) is det.
% get_from_ground_term_construct_conjunct_info(Goal,
% LHSVar, ConsId, RHSVars, GoalInfo):
%
% A conjunct inside a from_ground_term_construct scope *must* be a
% construction unification; return its details.
%
:- pred get_from_ground_term_construct_conjunct_info(hlds_goal::in,
prog_var::out, cons_id::out, list(prog_var)::out, hlds_goal_info::out)
is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module hlds.code_model.
:- import_module hlds.hlds_markers.
:- import_module parse_tree.prog_data_foreign.
:- import_module parse_tree.prog_data_pragma.
:- import_module transform_hlds.
:- import_module transform_hlds.exception_analysis.
:- import_module transform_hlds.term_constr_main_types.
:- import_module int.
:- import_module maybe.
:- import_module require.
%-----------------------------------------------------------------------------%
goal_is_conj_of_unify(ToAssignVars0, Goal) :-
Goal = hlds_goal(_GoalExpr, GoalInfo),
CodeModel = goal_info_get_code_model(GoalInfo),
CodeModel = model_det,
goal_to_conj_list(Goal, Conj),
only_constant_goals(Conj, ToAssignVars0, ToAssignVars),
set_of_var.is_empty(ToAssignVars).
all_disjuncts_are_conj_of_unify(_ToAssignVars, []).
all_disjuncts_are_conj_of_unify(ToAssignVars, [Disjunct | Disjuncts]) :-
goal_is_conj_of_unify(ToAssignVars, Disjunct),
all_disjuncts_are_conj_of_unify(ToAssignVars, Disjuncts).
:- pred only_constant_goals(list(hlds_goal)::in,
set_of_progvar::in, set_of_progvar::out) is semidet.
only_constant_goals([], !ToAssignVars).
only_constant_goals([Goal | Goals], !ToAssignVars) :-
Goal = hlds_goal(GoalExpr, _),
% We could allow calls as well. Some procedures have an output inst
% that fixes the value of the output variable, which is thus a constant.
% However, calls to such procedures should have been inlined by now.
(
GoalExpr = unify(_, _, _, Unification, _),
Unification = construct(Var, _, _, _, _, _, _)
;
GoalExpr = scope(Reason, _),
Reason = from_ground_term(Var, from_ground_term_construct)
),
set_of_var.delete(Var, !ToAssignVars),
only_constant_goals(Goals, !ToAssignVars).
%-----------------------------------------------------------------------------%
%
% A version of goal_cannot_loop_or_throw that uses results from the
% intermodule-analysis framework.
%
goal_can_throw_imaf(hlds_goal(GoalExpr, GoalInfo), Result, !ModuleInfo) :-
Determinism = goal_info_get_determinism(GoalInfo),
( if Determinism = detism_erroneous then
Result = can_throw
else
do_goal_can_throw_imaf(GoalExpr, GoalInfo, Result, !ModuleInfo)
).
:- pred do_goal_can_throw_imaf(hlds_goal_expr::in, hlds_goal_info::in,
goal_throw_status::out, module_info::in, module_info::out) is det.
do_goal_can_throw_imaf(GoalExpr, _GoalInfo, Result, !ModuleInfo) :-
(
(
GoalExpr = conj(_, Goals)
;
GoalExpr = disj(Goals)
;
GoalExpr = if_then_else(_, CondGoal, ThenGoal, ElseGoal),
Goals = [CondGoal, ThenGoal, ElseGoal]
),
goals_can_throw_imaf(Goals, Result, !ModuleInfo)
;
GoalExpr = plain_call(PredId, ProcId, _, _, _, _),
lookup_exception_analysis_result(proc(PredId, ProcId), Status,
!ModuleInfo),
(
Status = will_not_throw,
Result = cannot_throw
;
( Status = may_throw(_)
; Status = throw_conditional
),
Result = can_throw
)
;
GoalExpr = generic_call(_, _, _, _, _),
% XXX We should use results form closure analysis here.
Result = can_throw
;
GoalExpr = switch(_, _, Cases),
cases_can_throw_imaf(Cases, Result, !ModuleInfo)
;
GoalExpr = unify(_, _, _, Uni, _),
% Complicated unifies are _non_builtin_
(
Uni = complicated_unify(_, _, _),
Result = can_throw
;
( Uni = construct(_, _, _, _, _, _, _)
; Uni = deconstruct(_, _, _, _, _, _)
; Uni = assign(_, _)
; Uni = simple_test(_, _)
),
Result = cannot_throw
)
;
GoalExpr = negation(SubGoal),
goal_can_throw_imaf(SubGoal, Result, !ModuleInfo)
;
GoalExpr = scope(Reason, SubGoal),
( if
Reason = from_ground_term(_, FGT),
( FGT = from_ground_term_construct
; FGT = from_ground_term_deconstruct
)
then
% These scopes contain only construction/deconstruction
% unifications.
Result = cannot_throw
else
goal_can_throw_imaf(SubGoal, Result, !ModuleInfo)
)
;
GoalExpr = call_foreign_proc(Attributes, _, _, _, _, _, _),
ExceptionStatus = get_may_throw_exception(Attributes),
( if
(
ExceptionStatus = proc_will_not_throw_exception
;
ExceptionStatus = default_exception_behaviour,
get_may_call_mercury(Attributes) = proc_will_not_call_mercury
)
then
Result = cannot_throw
else
Result = can_throw
)
;
GoalExpr = shorthand(ShortHand),
(
ShortHand = bi_implication(GoalA, GoalB),
goals_can_throw_imaf([GoalA, GoalB], Result, !ModuleInfo)
;
ShortHand = atomic_goal(_, _, _, _, _, _, _),
% Atomic goals currently throw an exception to signal a rollback so
% it is pretty safe to say that any goal inside an atomic goal
% can throw an exception.
Result = can_throw
;
ShortHand = try_goal(_, _, _),
Result = can_throw
)
).
:- pred goals_can_throw_imaf(list(hlds_goal)::in, goal_throw_status::out,
module_info::in, module_info::out) is det.
goals_can_throw_imaf([], cannot_throw, !ModuleInfo).
goals_can_throw_imaf([Goal | Goals], Result, !ModuleInfo) :-
goal_can_throw_imaf(Goal, Result0, !ModuleInfo),
(
Result0 = cannot_throw,
goals_can_throw_imaf(Goals, Result, !ModuleInfo)
;
Result0 = can_throw,
Result = can_throw
).
:- pred cases_can_throw_imaf(list(case)::in, goal_throw_status::out,
module_info::in, module_info::out) is det.
cases_can_throw_imaf([], cannot_throw, !ModuleInfo).
cases_can_throw_imaf([Case | Cases], Result, !ModuleInfo) :-
Case = case(_, _, Goal),
goal_can_throw_imaf(Goal, Result0, !ModuleInfo),
(
Result0 = cannot_throw,
cases_can_throw_imaf(Cases, Result, !ModuleInfo)
;
Result0 = can_throw,
Result = can_throw
).
goal_can_loop_or_throw_imaf(Goal, Result, !ModuleInfo) :-
% XXX This will need to change after the termination analyses are converted
% to use the intermodule-analysis framework.
( if goal_cannot_loop_term_info(!.ModuleInfo, Goal) then
goal_can_throw_imaf(Goal, ThrowResult, !ModuleInfo),
(
ThrowResult = can_throw,
Result = can_loop_or_throw
;
ThrowResult = cannot_throw,
Result = cannot_loop_or_throw
)
else
Result = can_loop_or_throw
).
%-----------------------------------------------------------------------------%
goal_cannot_loop_term_info(ModuleInfo, Goal) :-
goal_can_loop_func(yes(ModuleInfo), Goal) = no.
goal_can_loop_term_info(ModuleInfo, Goal) :-
goal_can_loop_func(yes(ModuleInfo), Goal) = yes.
goal_cannot_throw_term_info(ModuleInfo, Goal) :-
goal_can_throw_func(yes(ModuleInfo), Goal) = no.
goal_can_throw_term_info(ModuleInfo, Goal) :-
goal_can_throw_func(yes(ModuleInfo), Goal) = yes.
goal_cannot_loop_or_throw_term_info(ModuleInfo, Goal) :-
goal_can_loop_func(yes(ModuleInfo), Goal) = no,
goal_can_throw_func(yes(ModuleInfo), Goal) = no.
goal_can_loop_or_throw_term_info(ModuleInfo, Goal) :-
not goal_cannot_loop_or_throw_term_info(ModuleInfo, Goal).
goal_cannot_loop_or_throw(Goal) :-
goal_can_loop_func(no, Goal) = no,
goal_can_throw_func(no, Goal) = no.
goal_can_loop_or_throw(Goal) :-
not goal_cannot_loop_or_throw(Goal).
:- func goal_can_loop_func(maybe(module_info), hlds_goal) = bool.
goal_can_loop_func(MaybeModuleInfo, Goal) = CanLoop :-
Goal = hlds_goal(GoalExpr, _),
(
GoalExpr = unify(_, _, _, Uni, _),
(
( Uni = assign(_, _)
; Uni = simple_test(_, _)
; Uni = construct(_, _, _, _, _, _, _)
; Uni = deconstruct(_, _, _, _, _, _)
),
CanLoop = no
;
Uni = complicated_unify(_, _, _),
% It can call, possibly indirectly, a user-specified unification
% predicate.
CanLoop = yes
)
;
GoalExpr = plain_call(PredId, ProcId, _, _, _, _),
( if
MaybeModuleInfo = yes(ModuleInfo),
module_info_pred_proc_info(ModuleInfo, PredId, ProcId, _,
ProcInfo),
(
proc_info_get_maybe_termination_info(ProcInfo, MaybeTermInfo),
MaybeTermInfo = yes(cannot_loop(_))
;
proc_info_get_termination2_info(ProcInfo, Term2Info),
term2_info_get_term_status(Term2Info) = yes(cannot_loop(_))
)
then
CanLoop = no
else
CanLoop = yes
)
;
GoalExpr = generic_call(_, _, _, _, _),
% We have no idea whether the called goal can throw exceptions,
% at least without closure analysis.
CanLoop = yes
;
GoalExpr = call_foreign_proc(Attributes, _, _, _, _, _, _),
( if
Terminates = get_terminates(Attributes),
require_complete_switch [Terminates]
(
Terminates = proc_terminates
;
Terminates = depends_on_mercury_calls,
get_may_call_mercury(Attributes) = proc_will_not_call_mercury
;
Terminates = proc_does_not_terminate,
fail
)
then
CanLoop = no
else
CanLoop = yes
)
;
GoalExpr = conj(plain_conj, Goals),
CanLoop = goals_can_loop(MaybeModuleInfo, Goals)
;
GoalExpr = conj(parallel_conj, _Goals),
% In theory, parallel conjunctions can get into deadlocks, which are
% effectively a form of nontermination. We can return `no' here only
% if we are sure this cannot happen for this conjunction.
CanLoop = yes
;
GoalExpr = disj(Goals),
CanLoop = goals_can_loop(MaybeModuleInfo, Goals)
;
GoalExpr = switch(_Var, _CanFail, Cases),
CanLoop = case_list_can_loop(MaybeModuleInfo, Cases)
;
GoalExpr = if_then_else(_Vars, Cond, Then, Else),
( if goal_can_loop_func(MaybeModuleInfo, Cond) = yes then
CanLoop = yes
else if goal_can_loop_func(MaybeModuleInfo, Then) = yes then
CanLoop = yes
else
CanLoop = goal_can_loop_func(MaybeModuleInfo, Else)
)
;
GoalExpr = negation(SubGoal),
CanLoop = goal_can_loop_func(MaybeModuleInfo, SubGoal)
;
GoalExpr = scope(Reason, SubGoal),
( if
Reason = from_ground_term(_, FGT),
( FGT = from_ground_term_construct
; FGT = from_ground_term_deconstruct
)
then
% These scopes contain only construction/deconstruction
% unifications.
CanLoop = no
else
CanLoop = goal_can_loop_func(MaybeModuleInfo, SubGoal)
)
;
GoalExpr = shorthand(ShortHand),
(
ShortHand = atomic_goal(_, _, _, _, MainGoal, OrElseGoals, _),
MainGoalCanLoop = goal_can_loop_func(MaybeModuleInfo, MainGoal),
OrElseCanLoop = goals_can_loop(MaybeModuleInfo, OrElseGoals),
CanLoop = MainGoalCanLoop `or` OrElseCanLoop
;
ShortHand = try_goal(_, _, SubGoal),
CanLoop = goal_can_loop_func(MaybeModuleInfo, SubGoal)
;
ShortHand = bi_implication(_, _),
unexpected($pred, "bi_implication")
)
).
:- func goals_can_loop(maybe(module_info), list(hlds_goal)) = bool.
goals_can_loop(_, []) = no.
goals_can_loop(MaybeModuleInfo, [Goal | Goals]) =
( if goal_can_loop_func(MaybeModuleInfo, Goal) = yes then
yes
else
goals_can_loop(MaybeModuleInfo, Goals)
).
:- func case_list_can_loop(maybe(module_info), list(case)) = bool.
case_list_can_loop(_, []) = no.
case_list_can_loop(MaybeModuleInfo, [case(_, _, Goal) | Cases]) =
( if goal_can_loop_func(MaybeModuleInfo, Goal) = yes then
yes
else
case_list_can_loop(MaybeModuleInfo, Cases)
).
%-----------------------------------------------------------------------------%
:- func goal_can_throw_func(maybe(module_info), hlds_goal) = bool.
goal_can_throw_func(MaybeModuleInfo, hlds_goal(GoalExpr, GoalInfo))
= CanThrow :-
Determinism = goal_info_get_determinism(GoalInfo),
( if Determinism = detism_erroneous then
CanThrow = yes
else
CanThrow = goal_expr_can_throw(MaybeModuleInfo, GoalExpr)
).
:- func goal_expr_can_throw(maybe(module_info), hlds_goal_expr) = bool.
goal_expr_can_throw(MaybeModuleInfo, GoalExpr) = CanThrow :-
(
GoalExpr = unify(_, _, _, Uni, _),
(
( Uni = assign(_, _)
; Uni = simple_test(_, _)
; Uni = construct(_, _, _, _, _, _, _)
; Uni = deconstruct(_, _, _, _, _, _)
),
CanThrow = no
;
Uni = complicated_unify(_, _, _),
% It can call, possibly indirectly, a user-specified unification
% predicate.
CanThrow = yes
)
;
GoalExpr = plain_call(PredId, ProcId, _, _, _, _),
( if
MaybeModuleInfo = yes(ModuleInfo),
module_info_pred_proc_info(ModuleInfo, PredId, ProcId,
_PredInfo, ProcInfo),
proc_info_get_exception_info(ProcInfo, MaybeExceptionInfo),
MaybeExceptionInfo = yes(ExceptionInfo),
ExceptionInfo = proc_exception_info(will_not_throw, _)
then
CanThrow = no
else
CanThrow = yes
)
;
GoalExpr = call_foreign_proc(Attributes, _, _, _, _, _, _),
ExceptionStatus = get_may_throw_exception(Attributes),
( if
(
ExceptionStatus = proc_will_not_throw_exception
;
ExceptionStatus = default_exception_behaviour,
get_may_call_mercury(Attributes) = proc_will_not_call_mercury
)
then
CanThrow = no
else
CanThrow = yes
)
;
GoalExpr = generic_call(_, _, _, _, _),
% We have no idea whether the called goal can throw exceptions,
% at least without closure analysis.
CanThrow = yes
;
( GoalExpr = conj(_ConjType, Goals)
; GoalExpr = disj(Goals)
),
CanThrow = goals_can_throw(MaybeModuleInfo, Goals)
;
GoalExpr = switch(_Var, _CanFail, Cases),
CanThrow = case_list_can_throw(MaybeModuleInfo, Cases)
;
GoalExpr = if_then_else(_, Cond, Then, Else),
( if goal_can_throw_func(MaybeModuleInfo, Cond) = yes then
CanThrow = yes
else if goal_can_throw_func(MaybeModuleInfo, Then) = yes then
CanThrow = yes
else
CanThrow = goal_can_throw_func(MaybeModuleInfo, Else)
)
;
GoalExpr = negation(SubGoal),
CanThrow = goal_can_throw_func(MaybeModuleInfo, SubGoal)
;
GoalExpr = scope(Reason, SubGoal),
( if
Reason = from_ground_term(_, FGT),
( FGT = from_ground_term_construct
; FGT = from_ground_term_deconstruct
)
then
% These scopes contain only construction/deconstruction
% unifications.
CanThrow = no
else
CanThrow = goal_can_throw_func(MaybeModuleInfo, SubGoal)
)
;
GoalExpr = shorthand(ShortHand),
(
ShortHand = atomic_goal(_, _, _, _, _, _, _),
CanThrow = yes
;
ShortHand = try_goal(_, _, _),
CanThrow = yes
;
ShortHand = bi_implication(_, _),
unexpected($pred, "bi_implication")
)
).
:- func goals_can_throw(maybe(module_info), list(hlds_goal)) = bool.
goals_can_throw(_, []) = no.
goals_can_throw(MaybeModuleInfo, [Goal | Goals]) =
( if goal_can_throw_func(MaybeModuleInfo, Goal) = yes then
yes
else
goals_can_throw(MaybeModuleInfo, Goals)
).
:- func case_list_can_throw(maybe(module_info), list(case)) = bool.
case_list_can_throw(_, []) = no.
case_list_can_throw(MaybeModuleInfo, [case(_, _, Goal) | Cases]) =
( if goal_can_throw_func(MaybeModuleInfo, Goal) = yes then
yes
else
case_list_can_throw(MaybeModuleInfo, Cases)
).
%-----------------------------------------------------------------------------%
goal_is_flat(hlds_goal(GoalExpr, _GoalInfo)) = goal_is_flat_expr(GoalExpr).
:- func goal_is_flat_expr(hlds_goal_expr) = bool.
goal_is_flat_expr(GoalExpr) = IsFlat :-
(
( GoalExpr = generic_call(_, _, _, _, _)
; GoalExpr = plain_call(_, _, _, _, _, _)
; GoalExpr = unify(_, _, _, _, _)
; GoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
),
IsFlat = yes
;
GoalExpr = conj(ConjType, Goals),
(
ConjType = parallel_conj,
IsFlat = no
;
ConjType = plain_conj,
IsFlat = goal_is_flat_list(Goals)
)
;
( GoalExpr = disj(_)
; GoalExpr = switch(_, _, _)
; GoalExpr = if_then_else(_, _, _, _)
; GoalExpr = shorthand(_)
),
IsFlat = no
;
GoalExpr = negation(SubGoal),
IsFlat = goal_is_flat(SubGoal)
;
GoalExpr = scope(Reason, SubGoal),
( if
Reason = from_ground_term(_, FGT),
( FGT = from_ground_term_construct
; FGT = from_ground_term_deconstruct
)
then
IsFlat = yes
else
IsFlat = goal_is_flat(SubGoal)
)
).
:- func goal_is_flat_list(list(hlds_goal)) = bool.
goal_is_flat_list([]) = yes.
goal_is_flat_list([Goal | Goals]) = IsFlat :-
( if goal_is_flat(Goal) = yes then
IsFlat = goal_is_flat_list(Goals)
else
IsFlat = no
).
%-----------------------------------------------------------------------------%
goal_may_allocate_heap(Goal) :-
may_goal_allocate_heap(Goal, yes).
goals_may_allocate_heap(Goals) :-
may_goals_allocate_heap(Goals, yes).
:- pred may_goal_allocate_heap(hlds_goal::in, bool::out) is det.
may_goal_allocate_heap(Goal, May) :-
Goal = hlds_goal(GoalExpr, _GoalInfo),
(
GoalExpr = unify(_, _, _, Unification, _),
( if
Unification = construct(_, _, Args, _, _, _, _),
Args = [_ | _]
then
May = yes
else
May = no
)
;
GoalExpr = plain_call(_, _, _, Builtin, _, _),
(
Builtin = inline_builtin,
May = no
;
Builtin = not_builtin,
May = yes
)
;
GoalExpr = generic_call(_, _, _, _, _),
May = yes
;
GoalExpr = call_foreign_proc(_, _, _, _, _, _, _),
% We cannot safely say that a foreign code fragment does not
% allocate memory without knowing all the #defined macros that
% expand to incr_hp and variants thereof.
% XXX You could make it an attribute of the foreign code and
% trust the programmer.
May = yes
;
GoalExpr = conj(ConjType, Goals),
(
ConjType = parallel_conj,
May = yes
;
ConjType = plain_conj,
may_goals_allocate_heap(Goals, May)
)
;
GoalExpr = disj(Goals),
may_goals_allocate_heap(Goals, May)
;
GoalExpr = switch(_Var, _CanFail, Cases),
may_cases_allocate_heap(Cases, May)
;
GoalExpr = if_then_else(_Vars, Cond, Then, Else),
( if may_goal_allocate_heap(Cond, yes) then
May = yes
else if may_goal_allocate_heap(Then, yes) then
May = yes
else
may_goal_allocate_heap(Else, May)
)
;
GoalExpr = negation(SubGoal),
may_goal_allocate_heap(SubGoal, May)
;
GoalExpr = scope(Reason, SubGoal),
( if
Reason = from_ground_term(_, FGT),
( FGT = from_ground_term_construct
; FGT = from_ground_term_deconstruct
)
then
% Construct scopes construct ground terms, but they construct them
% statically, so if we modify the code above to check the
% construct_how field of construction unifications, we could
% return May = no for them.
% Deconstruct scopes do not construct new ground terms.
May = yes
else
may_goal_allocate_heap(SubGoal, May)
)
;
GoalExpr = shorthand(ShortHand),
(
( ShortHand = atomic_goal(_, _, _, _, _, _, _)
; ShortHand = try_goal(_, _, _)
),
May = yes
;
ShortHand = bi_implication(GoalA, GoalB),
( if may_goal_allocate_heap(GoalA, yes) then
May = yes
else
may_goal_allocate_heap(GoalB, May)
)
)
).
:- pred may_goals_allocate_heap(list(hlds_goal)::in, bool::out) is det.
may_goals_allocate_heap([], no).
may_goals_allocate_heap([Goal | Goals], May) :-
( if may_goal_allocate_heap(Goal, yes) then
May = yes
else
may_goals_allocate_heap(Goals, May)
).
:- pred may_cases_allocate_heap(list(case)::in, bool::out) is det.
may_cases_allocate_heap([], no).
may_cases_allocate_heap([case(_, _, Goal) | Cases], May) :-
( if may_goal_allocate_heap(Goal, yes) then
May = yes
else
may_cases_allocate_heap(Cases, May)
).
%-----------------------------------------------------------------------------%
goal_cannot_stack_flush(Goal) :-
Goal = hlds_goal(GoalExpr, _),
(
GoalExpr = unify(_, _, _, Unify, _),
Unify \= complicated_unify(_, _, _)
;
GoalExpr = plain_call(_, _, _, BuiltinState, _, _),
BuiltinState = inline_builtin
;
GoalExpr = conj(ConjType, Goals),
ConjType = plain_conj,
goals_cannot_stack_flush(Goals)
;
GoalExpr = switch(_, _, Cases),
cases_cannot_stack_flush(Cases)
;
GoalExpr = negation(SubGoal),
SubGoal = hlds_goal(SubGoalExpr, _),
SubGoalExpr = unify(_, _, _, Unify, _),
Unify \= complicated_unify(_, _, _)
).
:- pred goals_cannot_stack_flush(list(hlds_goal)::in) is semidet.
goals_cannot_stack_flush([]).
goals_cannot_stack_flush([Goal | Goals]) :-
goal_cannot_stack_flush(Goal),
goals_cannot_stack_flush(Goals).
:- pred cases_cannot_stack_flush(list(case)::in) is semidet.
cases_cannot_stack_flush([]).
cases_cannot_stack_flush([case(_, _, Goal) | Cases]) :-
goal_cannot_stack_flush(Goal),
cases_cannot_stack_flush(Cases).
%-----------------------------------------------------------------------------%
cannot_fail_before_stack_flush(hlds_goal(GoalExpr, GoalInfo)) :-
Detism = goal_info_get_determinism(GoalInfo),
determinism_components(Detism, CanFail, _),
(
CanFail = cannot_fail
;
CanFail = can_fail,
cannot_fail_before_stack_flush_2(GoalExpr)
).
:- pred cannot_fail_before_stack_flush_2(hlds_goal_expr::in) is semidet.
cannot_fail_before_stack_flush_2(conj(ConjType, Goals)) :-
ConjType = plain_conj,
cannot_fail_before_stack_flush_conj(Goals).
:- pred cannot_fail_before_stack_flush_conj(list(hlds_goal)::in) is semidet.
cannot_fail_before_stack_flush_conj([]).
cannot_fail_before_stack_flush_conj([Goal | Goals]) :-
Goal = hlds_goal(GoalExpr, GoalInfo),
( if
(
GoalExpr = plain_call(_, _, _, BuiltinState, _, _),
BuiltinState \= inline_builtin
;
GoalExpr = generic_call(_, _, _, _, _)
)
then
true
else if
Detism = goal_info_get_determinism(GoalInfo),
determinism_components(Detism, cannot_fail, _)
then
cannot_fail_before_stack_flush_conj(Goals)
else
fail
).
%-----------------------------------------------------------------------------%
count_recursive_calls(Goal, PredId, ProcId, Min, Max) :-
Goal = hlds_goal(GoalExpr, _),
(
( GoalExpr = unify(_, _, _, _, _)
; GoalExpr = generic_call(_, _, _, _, _)
; GoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
),
Min = 0,
Max = 0
;
GoalExpr = plain_call(CallPredId, CallProcId, _, _, _, _),
( if
PredId = CallPredId,
ProcId = CallProcId
then
Count = 1
else
Count = 0
),
Min = Count,
Max = Count
;
GoalExpr = conj(_, Goals),
count_recursive_calls_conj(Goals, PredId, ProcId, 0, 0, Min, Max)
;
GoalExpr = disj(Goals),
count_recursive_calls_disj(Goals, PredId, ProcId, Min, Max)
;
GoalExpr = switch(_, _, Cases),
count_recursive_calls_cases(Cases, PredId, ProcId, Min, Max)
;
GoalExpr = if_then_else(_, Cond, Then, Else),
count_recursive_calls(Cond, PredId, ProcId, CMin, CMax),
count_recursive_calls(Then, PredId, ProcId, TMin, TMax),
count_recursive_calls(Else, PredId, ProcId, EMin, EMax),
CTMin = CMin + TMin,
CTMax = CMax + TMax,
int.min(CTMin, EMin, Min),
int.max(CTMax, EMax, Max)
;
GoalExpr = negation(SubGoal),
count_recursive_calls(SubGoal, PredId, ProcId, Min, Max)
;
GoalExpr = scope(Reason, SubGoal),
( if
Reason = from_ground_term(_, FGT),
( FGT = from_ground_term_construct
; FGT = from_ground_term_deconstruct
)
then
% These scopes contain only construction/deconstruction
% unifications.
Min = 0,
Max = 0
else
count_recursive_calls(SubGoal, PredId, ProcId, Min, Max)
)
;
GoalExpr = shorthand(ShortHand),
(
ShortHand = atomic_goal(_, _, _, _, MainGoal, OrElseGoals, _),
count_recursive_calls_disj([MainGoal | OrElseGoals],
PredId, ProcId, Min, Max)
;
ShortHand = try_goal(_, _, SubGoal),
count_recursive_calls(SubGoal, PredId, ProcId, Min, Max)
;
ShortHand = bi_implication(_, _),
% These should have been expanded out by now.
unexpected($pred, "bi_implication")
)
).
:- pred count_recursive_calls_conj(list(hlds_goal)::in,
pred_id::in, proc_id::in, int::in, int::in, int::out, int::out) is det.
count_recursive_calls_conj([], _, _, Min, Max, Min, Max).
count_recursive_calls_conj([Goal | Goals], PredId, ProcId, Min0, Max0,
Min, Max) :-
count_recursive_calls(Goal, PredId, ProcId, Min1, Max1),
Min2 = Min0 + Min1,
Max2 = Max0 + Max1,
count_recursive_calls_conj(Goals, PredId, ProcId,
Min2, Max2, Min, Max).
:- pred count_recursive_calls_disj(list(hlds_goal)::in,
pred_id::in, proc_id::in, int::out, int::out) is det.
count_recursive_calls_disj([], _, _, 0, 0).
count_recursive_calls_disj([Goal | Goals], PredId, ProcId, Min, Max) :-
(
Goals = [],
count_recursive_calls(Goal, PredId, ProcId, Min, Max)
;
Goals = [_ | _],
count_recursive_calls(Goal, PredId, ProcId, Min0, Max0),
count_recursive_calls_disj(Goals, PredId, ProcId, Min1, Max1),
int.min(Min0, Min1, Min),
int.max(Max0, Max1, Max)
).
:- pred count_recursive_calls_cases(list(case)::in, pred_id::in, proc_id::in,
int::out, int::out) is det.
count_recursive_calls_cases([], _, _, _, _) :-
unexpected($pred, "[]").
count_recursive_calls_cases([case(_, _, Goal) | Cases], PredId, ProcId,
Min, Max) :-
(
Cases = [],
count_recursive_calls(Goal, PredId, ProcId, Min, Max)
;
Cases = [_ | _],
count_recursive_calls(Goal, PredId, ProcId, Min0, Max0),
count_recursive_calls_cases(Cases, PredId, ProcId, Min1, Max1),
int.min(Min0, Min1, Min),
int.max(Max0, Max1, Max)
).
%-----------------------------------------------------------------------------%
%
% Trail usage
%
goal_cannot_modify_trail(GoalInfo) =
( if goal_info_has_feature(GoalInfo, feature_will_not_modify_trail) then
yes
else
no
).
goal_may_modify_trail(GoalInfo) = bool.not(goal_cannot_modify_trail(GoalInfo)).
%-----------------------------------------------------------------------------%
goal_has_foreign(Goal) = HasForeign :-
Goal = hlds_goal(GoalExpr, _),
(
( GoalExpr = plain_call(_, _, _, _, _, _)
; GoalExpr = generic_call(_, _, _, _, _)
; GoalExpr = unify(_, _, _, _, _)
),
HasForeign = no
;
GoalExpr = conj(_, Goals),
HasForeign = goals_has_foreign(Goals)
;
GoalExpr = disj(Goals),
HasForeign = goals_has_foreign(Goals)
;
GoalExpr = switch(_, _, Cases),
HasForeign = case_list_has_foreign(Cases)
;
GoalExpr = negation(SubGoal),
HasForeign = goal_has_foreign(SubGoal)
;
GoalExpr = scope(Reason, SubGoal),
( if
Reason = from_ground_term(_, FGT),
( FGT = from_ground_term_construct
; FGT = from_ground_term_deconstruct
)
then
HasForeign = no
else
HasForeign = goal_has_foreign(SubGoal)
)
;
GoalExpr = if_then_else(_, Cond, Then, Else),
( if
( goal_has_foreign(Cond) = yes
; goal_has_foreign(Then) = yes
; goal_has_foreign(Else) = yes
)
then
HasForeign = yes
else
HasForeign = no
)
;
GoalExpr = call_foreign_proc(_, _, _, _, _, _, _),
HasForeign = yes
;
GoalExpr = shorthand(ShortHand),
(
ShortHand = atomic_goal(_, _, _, _, _, _, _),
HasForeign = yes
;
ShortHand = try_goal(_, _, SubGoal),
HasForeign = goal_has_foreign(SubGoal)
;
ShortHand = bi_implication(GoalA, GoalB),
HasForeign = bool.or(goal_has_foreign(GoalA),
goal_has_foreign(GoalB))
)
).
:- func goals_has_foreign(list(hlds_goal)) = bool.
goals_has_foreign([]) = no.
goals_has_foreign([Goal | Goals]) = HasForeign :-
( if goal_has_foreign(Goal) = yes then
HasForeign = yes
else
HasForeign = goals_has_foreign(Goals)
).
:- func case_list_has_foreign(list(case)) = bool.
case_list_has_foreign([]) = no.
case_list_has_foreign([Case | Cases]) = HasForeign :-
Case = case(_, _, Goal),
( if goal_has_foreign(Goal) = yes then
HasForeign = yes
else
HasForeign = case_list_has_foreign(Cases)
).
%-----------------------------------------------------------------------------%
goal_expr_has_subgoals(GoalExpr) = HasSubGoals :-
(
( GoalExpr = unify(_, _, _, _, _)
; GoalExpr = generic_call(_, _, _, _, _)
; GoalExpr = plain_call(_, _, _, _, _, _)
; GoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
),
HasSubGoals = does_not_have_subgoals
;
( GoalExpr = conj(_, SubGoals)
; GoalExpr = disj(SubGoals)
),
(
SubGoals = [],
HasSubGoals = does_not_have_subgoals
;
SubGoals = [_ | _],
HasSubGoals = has_subgoals
)
;
( GoalExpr = if_then_else(_, _, _, _)
; GoalExpr = negation(_)
; GoalExpr = switch(_, _, _)
; GoalExpr = scope(_, _)
),
HasSubGoals = has_subgoals
;
GoalExpr = shorthand(ShortHand),
( ShortHand = atomic_goal(_, _, _, _, _, _, _)
; ShortHand = try_goal(_, _, _)
; ShortHand = bi_implication(_, _)
),
HasSubGoals = has_subgoals
).
%-----------------------------------------------------------------------------%
get_notag_or_direct_arg_arg_mode(RHSVars, ArgModes, RHSVar, ArgMode) :-
( if
RHSVars = [RHSVarPrime],
ArgModes = [ArgModePrime]
then
RHSVar = RHSVarPrime,
ArgMode = ArgModePrime
else
unexpected($pred, "arity != 1)")
).
get_notag_or_direct_arg_arg(RHSVars, RHSVar) :-
( if RHSVars = [RHSVarPrime] then
RHSVar = RHSVarPrime
else
unexpected($pred, "arity != 1)")
).
%-----------------------------------------------------------------------------%
get_from_ground_term_construct_info(TermVar, Goal,
TermVarIsNeeded, Conjuncts, GoalInfo) :-
Goal = hlds_goal(GoalExpr, GoalInfo),
NonLocals = goal_info_get_nonlocals(GoalInfo),
set_of_var.to_sorted_list(NonLocals, NonLocalList),
(
(
NonLocalList = [],
TermVarIsNeeded = termvar_is_not_needed
;
NonLocalList = [NonLocal],
( if NonLocal = TermVar then
TermVarIsNeeded = termvar_is_needed
else
unexpected($pred, "unexpected nonlocal")
)
),
( if GoalExpr = conj(plain_conj, ConjunctsPrime) then
Conjuncts = ConjunctsPrime
else
unexpected($pred, "unexpected nonlocal")
)
;
NonLocalList = [_, _ | _],
unexpected($pred, "unexpected nonlocals")
).
get_from_ground_term_construct_conjunct_info(Goal, LHSVar, ConsId, RHSVars,
GoalInfo) :-
Goal = hlds_goal(GoalExpr, GoalInfo),
( if
GoalExpr = unify(_, _, _, Unify, _),
Unify = construct(LHSVarPrime, ConsIdPrime, RHSVarsPrime,
_, _, _, SubInfo),
SubInfo = no_construct_sub_info
then
LHSVar = LHSVarPrime,
ConsId = ConsIdPrime,
RHSVars = RHSVarsPrime
else
unexpected($pred, "unexpected goal as fgt_construct conjunct")
).
%-----------------------------------------------------------------------------%
:- end_module hlds.goal_form.
%-----------------------------------------------------------------------------%