Files
mercury/compiler/simplify_goal_scope.m
Zoltan Somogyi 0c23ad7c64 Carve five new modules out of goal_util.m.
Each of the new modules has much better cohesion than the old goal_util.m.

There are no algorithmic changes.

compiler/goal_contains.m:
    New module for predicates that compute what subgoals a goal contains.

compiler/goal_refs.m:
    New module for predicates that compute what predicates a goal
    calls or otherwise references.

compiler/goal_reorder.m:
    New module for predicates that test whether two goals can be reordered.

compiler/goal_transform.m:
    New module for predicates that transform goals.

compiler/goal_vars.m:
    New module for predicates that compute what variables occur in a goal.

compiler/goal_util.m:
    Delete the code moved to the new modules. Move some related predicates
    next to each other.

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

compiler/*.m:
    Conform to the changes above. Most import just one of the old
    goal_util.m's six successor modules; some import two; while a very few
    import three. None import more than that.
2025-05-22 22:54:48 +10:00

610 lines
25 KiB
Mathematica

%----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%----------------------------------------------------------------------------%
% Copyright (C) 2014-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: simplify_goal_scope.m.
%
% This module handles simplification of scope goals.
%
%----------------------------------------------------------------------------%
:- module check_hlds.simplify.simplify_goal_scope.
:- interface.
:- import_module check_hlds.simplify.common.
:- import_module check_hlds.simplify.simplify_info.
:- import_module hlds.
:- import_module hlds.hlds_goal.
:- import_module hlds.instmap.
% Handle simplification of scope goals.
%
:- pred simplify_goal_scope(
hlds_goal_expr::in(goal_expr_scope), hlds_goal_expr::out,
hlds_goal_info::in, hlds_goal_info::out,
simplify_nested_context::in, instmap::in,
common_info::in, common_info::out,
simplify_info::in, simplify_info::out) is det.
% If the goal nested inside this scope goal is another scope goal,
% then merge the two scopes, if this is possible. Repeat for as many
% nested scopes as possible.
%
:- pred try_to_merge_nested_scopes(scope_reason::in, hlds_goal::in,
hlds_goal_info::in, hlds_goal::out) is det.
%----------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.simplify.simplify_goal.
:- import_module check_hlds.simplify.simplify_tasks.
:- import_module hlds.const_struct.
:- import_module hlds.goal_refs.
:- import_module hlds.goal_util.
:- import_module hlds.hlds_module.
:- import_module hlds.make_goal.
:- import_module hlds.pred_table.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.optimization_options.
:- import_module libs.options.
:- import_module libs.trace_params.
:- import_module mdbcomp.
:- import_module mdbcomp.builtin_modules.
:- import_module mdbcomp.prim_data.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_data_foreign.
:- import_module parse_tree.var_table.
:- import_module bool.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module set.
simplify_goal_scope(GoalExpr0, GoalExpr, GoalInfo0, GoalInfo,
NestedContext0, InstMap0, Common0, Common, !Info) :-
GoalExpr0 = scope(Reason0, SubGoal0),
( if Reason0 = from_ground_term(TermVar, from_ground_term_construct) then
simplify_info_get_module_info(!.Info, ModuleInfo0),
module_info_get_const_struct_db(ModuleInfo0, ConstStructDb0),
const_struct_db_get_ground_term_enabled(ConstStructDb0,
ConstStructEnabled),
(
ConstStructEnabled = do_not_enable_const_struct_user,
module_info_get_globals(ModuleInfo0, Globals),
globals.get_opt_tuple(Globals, OptTuple),
CommonStruct = OptTuple ^ ot_opt_common_structs,
(
CommonStruct = opt_common_structs,
% Traversing the construction unifications inside the scope
% would allow common.m to
%
% - replace some of those constructions with references to
% other variables that were constructed the same way, and
% - remember those constructions, so that other constructions
% outside the scope could be replaced with references to
% variables built inside the scope.
%
% Since unifying a variable with a statically constructed
% ground term yields code that is at least as fast as unifying
% that variable with another variable that is already bound to
% that term, and probably faster because it does not require
% saving the other variable across calls, neither of these
% actions would be an advantage. On the other hand, both would
% complicate the required treatment of
% from_ground_term_construct scopes in liveness.m, slowing down
% the liveness pass, as well as this pass. Since the code
% inside the scope is already as simple as it can be, we
% leave it alone.
GoalExpr = GoalExpr0,
GoalInfo = GoalInfo0,
Common = Common0
;
CommonStruct = do_not_opt_common_structs,
% Looking inside the scope may allow us to reduce the number of
% memory cells we may need to allocate dynamically. This
% improvement in the generated code trumps the cost in compile
% time. However, we need to update the reason, since leaving it
% as from_ground_term_construct would tell liveness.m that the
% code inside the scope hasn't had either of the actions
% mentioned in the comment above applied to it, and in this
% case, we cannot guarantee that.
simplify_goal(SubGoal0, SubGoal, NestedContext0, InstMap0,
Common0, Common, !Info),
NewReason = from_ground_term(TermVar, from_ground_term_other),
GoalExpr = scope(NewReason, SubGoal),
GoalInfo = GoalInfo0
)
;
ConstStructEnabled = enable_const_struct_user,
( if
SubGoal0 = hlds_goal(SubGoalExpr, _),
SubGoalExpr = conj(plain_conj, Conjuncts),
Conjuncts = [HeadConjunctPrime | TailConjunctsPrime]
then
HeadConjunct = HeadConjunctPrime,
TailConjuncts = TailConjunctsPrime
else
unexpected($pred,
"from_ground_term_construct scope is not conjunction")
),
simplify_info_get_var_table(!.Info, VarTable),
simplify_info_get_defined_where(!.Info, DefinedWhere),
simplify_construct_ground_terms(DefinedWhere, VarTable, TermVar,
HeadConjunct, TailConjuncts, [], ElimVars,
map.init, VarArgMap, ConstStructDb0, ConstStructDb),
module_info_set_const_struct_db(ConstStructDb,
ModuleInfo0, ModuleInfo),
simplify_info_add_elim_vars(ElimVars, !Info),
simplify_info_set_module_info(ModuleInfo, !Info),
map.to_assoc_list(VarArgMap, VarArgs),
( if
VarArgs = [TermVar - TermArg],
TermArg = csa_const_struct(TermConstNumPrime)
then
TermConstNum = TermConstNumPrime
else
unexpected($pred, "unexpected VarArgMap")
),
lookup_const_struct_num(ConstStructDb, TermConstNum,
TermConstStruct),
TermConsId = TermConstStruct ^ cs_cons_id,
ConsId = ground_term_const(TermConstNum, TermConsId),
RHS = rhs_functor(ConsId, is_not_exist_constr, []),
Unification = construct(TermVar, ConsId, [], [],
construct_statically(born_static), cell_is_shared,
no_construct_sub_info),
InstMapDelta = goal_info_get_instmap_delta(GoalInfo0),
instmap_delta_lookup_var(InstMapDelta, TermVar, TermInst),
UnifyMode = unify_modes_li_lf_ri_rf(free, TermInst,
TermInst, TermInst),
UnifyContext = unify_context(umc_explicit, []),
GoalExpr = unify(TermVar, RHS, UnifyMode, Unification,
UnifyContext),
GoalInfo = GoalInfo0,
Common = Common0
)
else
( if Reason0 = disable_warnings(HeadWarning, TailWarnings) then
simplify_info_get_simplify_tasks(!.Info, Tasks0),
list.foldl(disable_simplify_warning,
[HeadWarning | TailWarnings], Tasks0, ScopeTasks),
simplify_info_set_simplify_tasks(ScopeTasks, !Info),
simplify_goal(SubGoal0, SubGoal, NestedContext0, InstMap0,
Common0, Common1, !Info),
simplify_info_set_simplify_tasks(Tasks0, !Info)
else
simplify_goal(SubGoal0, SubGoal, NestedContext0, InstMap0,
Common0, Common1, !Info)
),
try_to_merge_nested_scopes(Reason0, SubGoal, GoalInfo0, Goal1),
Goal1 = hlds_goal(GoalExpr1, _GoalInfo1),
( if GoalExpr1 = scope(FinalReason, FinalSubGoal) then
(
( FinalReason = disable_warnings(_, _)
; FinalReason = promise_purity(_)
; FinalReason = from_ground_term(_, _)
; FinalReason = barrier(removable)
),
Goal = Goal1,
Common = Common1
;
( FinalReason = require_detism(_)
; FinalReason = require_complete_switch(_)
; FinalReason = require_switch_arms_detism(_, _)
),
% The scope has served its purpose, and it is not needed
% anymore.
Goal = FinalSubGoal,
Common = Common1
;
( FinalReason = exist_quant(_, _)
; FinalReason = commit(_)
; FinalReason = promise_solutions(_, _)
; FinalReason = barrier(not_removable)
; FinalReason = loop_control(_, _, _)
),
Goal = Goal1,
% Replacing calls, constructions or deconstructions outside
% a commit with references to variables created inside the
% commit would increase the set of output variables of the goal
% inside the commit. This is not allowed because it could
% change the determinism.
%
% Thus we need to reset the common_info to what it was before
% processing the goal inside the commit, to ensure that we
% don't make any such replacements when processing the rest
% of the goal.
%
% We do the same for several other kinds of scopes from which
% we do not want to "export" common unifications.
Common = Common0
;
FinalReason = trace_goal(MaybeCompiletimeExpr,
MaybeRuntimeExpr, _, _, _),
( if simplify_do_after_front_end(!.Info) then
simplify_goal_trace_goal(MaybeCompiletimeExpr,
MaybeRuntimeExpr, FinalSubGoal, Goal1, Goal, !Info)
else
Goal = Goal1
),
% We throw away the updated Common1 for the same reason
% as in the case above: we don't want to add any outputs
% to the trace_goal scope, since such scopes should not
% have ANY outputs.
Common = Common0
)
else
Goal = Goal1,
Common = Common1
),
Goal = hlds_goal(GoalExpr, GoalInfo)
).
:- pred disable_simplify_warning(goal_warning::in,
simplify_tasks::in, simplify_tasks::out) is det.
disable_simplify_warning(Warning, !Tasks) :-
(
( Warning = goal_warning_singleton_vars
; Warning = goal_warning_repeated_singleton_vars
)
% Warning about singleton vars is done when clauses are added
% to the HLDS, not during simplification.
;
Warning = goal_warning_occurs_check
% Warning about occurs check violations is done during the creation
% of the HLDS, not during simplification.
;
Warning = goal_warning_non_tail_recursive_calls
% Warning about non-tail-recursive calls is done during
% code generation, not during simplification. (What calls
% are tail recursive depends on the backend.)
;
Warning = goal_warning_suspicious_recursion,
!Tasks ^ do_warn_suspicious_recursion := do_not_warn_suspicious_rec
;
Warning = goal_warning_no_solution_disjunct,
!Tasks ^ do_warn_no_solution_disjunct := do_not_warn_no_soln_disjunct
;
Warning = goal_warning_unknown_format_calls
% Unknown format warnings are generated by format_calls.m, which
% does its own traversal of the procedure body. Therefore that warning
% needs to be disabled during *that* traversal, not during the
% traversal that our caller is doing.
).
%-----------------------------------------------------------------------------%
:- type var_to_arg_map == map(prog_var, const_struct_arg).
:- pred simplify_construct_ground_terms(defined_where::in, var_table::in,
prog_var::in, hlds_goal::in, list(hlds_goal)::in,
list(prog_var)::in, list(prog_var)::out,
var_to_arg_map::in, var_to_arg_map::out,
const_struct_db::in, const_struct_db::out) is det.
simplify_construct_ground_terms(DefinedWhere, VarTable, TermVar,
Conjunct, Conjuncts, !ElimVars, !VarArgMap, !ConstStructDb) :-
Conjunct = hlds_goal(GoalExpr, GoalInfo),
( if
GoalExpr = unify(_, _, _, Unify, _),
Unify = construct(LHSVarPrime, ConsIdPrime, RHSVarsPrime, _, _, _, _)
then
LHSVar = LHSVarPrime,
ConsId = ConsIdPrime,
RHSVars = RHSVarsPrime
else
unexpected($pred, "not construction unification")
),
lookup_var_type(VarTable, LHSVar, TermType),
(
RHSVars = [],
Arg = csa_constant(ConsId, TermType)
;
RHSVars = [_ | _],
list.map_foldl(map.det_remove, RHSVars, RHSArgs, !VarArgMap),
InstMapDelta = goal_info_get_instmap_delta(GoalInfo),
instmap_delta_lookup_var(InstMapDelta, LHSVar, TermInst),
ConstStruct = const_struct(ConsId, RHSArgs, TermType, TermInst,
DefinedWhere),
lookup_insert_const_struct(ConstStruct, ConstNum, !ConstStructDb),
Arg = csa_const_struct(ConstNum)
),
map.det_insert(LHSVar, Arg, !VarArgMap),
(
Conjuncts = [],
expect(unify(TermVar, LHSVar), $pred, "last var is not TermVar")
;
Conjuncts = [HeadConjunct | TailConjuncts],
!:ElimVars = [LHSVar | !.ElimVars],
simplify_construct_ground_terms(DefinedWhere, VarTable, TermVar,
HeadConjunct, TailConjuncts,
!ElimVars, !VarArgMap, !ConstStructDb)
).
%---------------------------------------------------------------------------%
:- pred simplify_goal_trace_goal(maybe(trace_expr(trace_compiletime))::in,
maybe(trace_expr(trace_runtime))::in, hlds_goal::in, hlds_goal::in,
hlds_goal::out, simplify_info::in, simplify_info::out) is det.
simplify_goal_trace_goal(MaybeCompiletimeExpr, MaybeRuntimeExpr, SubGoal,
Goal0, Goal, !Info) :-
(
MaybeCompiletimeExpr = yes(CompiletimeExpr),
KeepGoal = evaluate_compile_time_condition(CompiletimeExpr, !.Info)
;
MaybeCompiletimeExpr = no,
% A missing compile time condition means that the
% trace goal is always compiled in.
KeepGoal = yes
),
(
KeepGoal = no,
Goal0 = hlds_goal(_GoalExpr0, GoalInfo0),
Context = goal_info_get_context(GoalInfo0),
Goal = true_goal_with_context(Context),
simplify_info_get_deleted_call_callees(!.Info, DeletedCallCallees0),
SubGoalCalledProcs = goal_proc_refs(SubGoal),
set.union(SubGoalCalledProcs,
DeletedCallCallees0, DeletedCallCallees),
simplify_info_set_deleted_call_callees(DeletedCallCallees, !Info)
;
KeepGoal = yes,
(
MaybeRuntimeExpr = no,
% We keep the scope as a marker of the existence of the
% trace scope.
Goal = Goal0
;
KeepGoal = yes,
MaybeRuntimeExpr = yes(RuntimeExpr),
% We want to execute SubGoal if and only if RuntimeExpr turns out
% to be true. We could have the code generators treat this kind of
% scope as if it were an if-then-else, but that would require
% duplicating most of the code required to handle code generation
% for if-then-elses. Instead, we transform the scope into an
% if-then-else, thus reducing the problem to one that has already
% been solved.
%
% The evaluation of the runtime condition is done as a special kind
% of foreign_proc, i.e. one that has yes(RuntimeExpr) as its
% foreign_trace_cond field. This kind of foreign_proc also acts
% as the marker for the fact that the then-part originated as
% the goal of a trace scope.
simplify_info_get_module_info(!.Info, ModuleInfo),
module_info_get_globals(ModuleInfo, Globals),
globals.get_target(Globals, Target),
PrivateBuiltin = mercury_private_builtin_module,
EvalPredName = "trace_evaluate_runtime_condition",
some [!EvalAttributes] (
(
Target = target_c,
!:EvalAttributes = default_attributes(lang_c)
;
Target = target_java,
!:EvalAttributes = default_attributes(lang_java)
;
Target = target_csharp,
!:EvalAttributes = default_attributes(lang_csharp)
),
set_may_call_mercury(proc_will_not_call_mercury,
!EvalAttributes),
set_thread_safe(proc_thread_safe, !EvalAttributes),
set_purity(purity_semipure, !EvalAttributes),
set_terminates(proc_terminates, !EvalAttributes),
set_may_throw_exception(proc_will_not_throw_exception,
!EvalAttributes),
set_may_modify_trail(proc_will_not_modify_trail,
!EvalAttributes),
set_may_call_mm_tabled(proc_will_not_call_mm_tabled,
!EvalAttributes),
EvalAttributes = !.EvalAttributes
),
EvalFeatures = [],
% The code field of the call_foreign_proc goal is ignored when
% its foreign_trace_cond field is set to `yes', as we do here.
EvalCode = "",
Goal0 = hlds_goal(_GoalExpr0, GoalInfo0),
Context = goal_info_get_context(GoalInfo0),
generate_call_foreign_proc(ModuleInfo, pf_predicate,
PrivateBuiltin, EvalPredName,
[], [], [], instmap_delta_bind_no_var, only_mode,
detism_semi, purity_semipure, EvalFeatures, EvalAttributes,
yes(RuntimeExpr), EvalCode, Context, CondGoal),
GoalExpr = if_then_else([], CondGoal, SubGoal, true_goal),
Goal = hlds_goal(GoalExpr, GoalInfo0)
)
).
:- func evaluate_compile_time_condition(trace_expr(trace_compiletime),
simplify_info) = bool.
evaluate_compile_time_condition(TraceExpr, Info) = Result :-
(
TraceExpr = trace_base(BaseExpr),
Result = evaluate_compile_time_condition_comptime(BaseExpr, Info)
;
TraceExpr = trace_not(ExprA),
ResultA = evaluate_compile_time_condition(ExprA, Info),
Result = bool.not(ResultA)
;
TraceExpr = trace_op(Op, ExprA, ExprB),
ResultA = evaluate_compile_time_condition(ExprA, Info),
ResultB = evaluate_compile_time_condition(ExprB, Info),
(
Op = trace_or,
Result = bool.or(ResultA, ResultB)
;
Op = trace_and,
Result = bool.and(ResultA, ResultB)
)
).
:- func evaluate_compile_time_condition_comptime(trace_compiletime,
simplify_info) = bool.
evaluate_compile_time_condition_comptime(CompTime, Info) = Result :-
simplify_info_get_module_info(Info, ModuleInfo),
module_info_get_globals(ModuleInfo, Globals),
(
CompTime = trace_flag(FlagName),
globals.lookup_accumulating_option(Globals, trace_goal_flags, Flags),
( if list.member(FlagName, Flags) then
Result = yes
else
Result = no
)
;
CompTime = trace_grade(Grade),
(
Grade = trace_grade_debug,
globals.lookup_bool_option(Globals, exec_trace, Result)
;
Grade = trace_grade_ssdebug,
globals.lookup_bool_option(Globals, source_to_source_debug, Result)
% XXX Should we take into account force_disable_ssdebug as well?
;
Grade = trace_grade_prof,
globals.lookup_bool_option(Globals, profile_calls, ProfCalls),
globals.lookup_bool_option(Globals, profile_time, ProfTime),
globals.lookup_bool_option(Globals, profile_memory, ProfMem),
bool.or_list([ProfCalls, ProfTime, ProfMem], Result)
;
Grade = trace_grade_profdeep,
globals.lookup_bool_option(Globals, profile_deep, Result)
;
Grade = trace_grade_par,
globals.lookup_bool_option(Globals, parallel, Result)
;
Grade = trace_grade_trail,
globals.lookup_bool_option(Globals, use_trail, Result)
;
Grade = trace_grade_rbmm,
globals.lookup_bool_option(Globals, use_regions, Result)
;
Grade = trace_grade_llds,
globals.lookup_bool_option(Globals, highlevel_code, NotResult),
bool.not(NotResult, Result)
;
Grade = trace_grade_mlds,
globals.lookup_bool_option(Globals, highlevel_code, Result)
;
Grade = trace_grade_c,
globals.get_target(Globals, Target),
( if Target = target_c then
Result = yes
else
Result = no
)
;
Grade = trace_grade_csharp,
globals.get_target(Globals, Target),
( if Target = target_csharp then
Result = yes
else
Result = no
)
;
Grade = trace_grade_java,
globals.get_target(Globals, Target),
( if Target = target_java then
Result = yes
else
Result = no
)
)
;
CompTime = trace_trace_level(RequiredLevel),
simplify_info_get_eff_trace_level_optimized(Info, EffTraceLevel, _),
(
RequiredLevel = trace_level_shallow,
Result = at_least_at_shallow(EffTraceLevel)
;
RequiredLevel = trace_level_deep,
Result = at_least_at_deep(EffTraceLevel)
)
).
%---------------------------------------------------------------------------%
try_to_merge_nested_scopes(Reason0, InnerGoal0, OuterGoalInfo, Goal) :-
loop_over_any_nested_scopes(Reason0, Reason, InnerGoal0, InnerGoal),
InnerGoal = hlds_goal(_, GoalInfo),
( if
Reason = exist_quant(_, _),
Detism = goal_info_get_determinism(GoalInfo),
OuterDetism = goal_info_get_determinism(OuterGoalInfo),
Detism = OuterDetism
then
% If the inner and outer detisms match, then we do not need
% the `some' scope.
Goal = InnerGoal
else
Goal = hlds_goal(scope(Reason, InnerGoal), OuterGoalInfo)
).
:- pred loop_over_any_nested_scopes(scope_reason::in, scope_reason::out,
hlds_goal::in, hlds_goal::out) is det.
loop_over_any_nested_scopes(Reason0, Reason, Goal0, Goal) :-
( if
Goal0 = hlds_goal(scope(Reason1, Goal1), _),
( if
Reason0 = exist_quant(Vars0, _),
Reason1 = exist_quant(Vars1, _)
then
Reason2 = exist_quant(Vars0 ++ Vars1, compiler_quant)
else if
Reason0 = barrier(Removable0),
Reason1 = barrier(Removable1)
then
( if
Removable0 = removable,
Removable1 = removable
then
Removable2 = removable
else
Removable2 = not_removable
),
Reason2 = barrier(Removable2)
else if
Reason0 = commit(ForcePruning0),
Reason1 = commit(ForcePruning1)
then
( if
ForcePruning0 = do_not_force_pruning,
ForcePruning1 = do_not_force_pruning
then
ForcePruning2 = do_not_force_pruning
else
ForcePruning2 = force_pruning
),
Reason2 = commit(ForcePruning2)
else
fail
)
then
loop_over_any_nested_scopes(Reason2, Reason, Goal1, Goal)
else
Reason = Reason0,
Goal = Goal0
).
%---------------------------------------------------------------------------%
:- end_module check_hlds.simplify.simplify_goal_scope.
%---------------------------------------------------------------------------%