Files
mercury/compiler/mark_static_terms.m
Zoltan Somogyi 9ddb180757 Handle const_var_maps left by add_trail_ops.m.
This fixes Mantis bug #544.

The code of add_trail_ops.m can transform

    <code that adds an entry to const_var_map>

into

    (
        ...
        <code that adds an entry to const_var_map>
        ...
    ;
        ...,
        fail
    )

where the const_var_map in the MLDS code generator records which variables'
values are available as ground terms.

The MLDS code generator used to reset the const_var_map in its main data
structure, the ml_gen_info, at the end of every disjunction (actually,
at the end of every branched control structure) to the value it had
at the start. This was intended to prevent the code following the branched
structure from relying on const_var_map entries that were added to the
const_var_map on *some* branches, but not others. However, in this case,
it has the effect of forgetting the entry added by the first disjunct,
even though

- the code after the disjunction can be reached *only* via the first disjunct,
  and

- the code after the disjunction (legitimately, until add_trail_ops) depended
  on that entry being available.

The fix is to allow the code after a branched control structure to depend
on any const_var_map entry that is present in the final const_var_map
in every branch of the branched control structure whose end is reachable.

The LLDS code generator was not affected by the bug, because it uses
totally separate systems both for implementing trailing, and for keeping
track of what variables' values are available statically. In particular,
it does not rely on operations inserted and the annotations left on
unifications by the add_trail_ops and mark_static_term passes,
having been written long before either module existed.

compiler/hlds_goal.m:
    Document the update above to what may be marked static.

compiler/ml_gen_info.m:
    Document the updated protocol for handling the const_var_map field.

    Use a named type instead of its expansion.

compiler/ml_code_gen.m:
    Make the predicates that generate code for a branch in a branched
    control structure return the final const_var_maps from the branches
    whose endpoints are reachable.

    Add a predicate that computes the consensus of all the gathered
    const_var_maps.

    Compute consensus const_var_maps for if-then-elses and negations.

    Fix some inconsistencies in variable naming.

    Simplify some code.

compiler/ml_disj_gen.m:
    Compute consensus const_var_maps for disjunctions.

compiler/ml_string_switch.m:
compiler/ml_switch_gen.m:
compiler/ml_tag_switch.m:
    Compute consensus const_var_maps for various kinds of switches.

    In some predicates, put related arguments next to each other.

compiler/ml_unify_gen_construct.m:
    Delete "dynamic" from the names of several predicates that also handled
    non-dynamic construction unifications.

    Fix an out-of-date comment.

compiler/mark_static_terms:
    Fix grammar in a comment.

library/map.m:
    Fix a careless bug: when doing a merge in map.common_subset_loop,
    we threw away an entry from the wrong list in one of three cases.

    Make such bugs harder to overlook by

    - deleting the common parts from variable names, leaving the differences
      easier to see, and

    - replacing numeric suffixes for completely separate data structures
      with A and B suffixes.

tests/valid/bug544.m:
    A new test case for the bug.

tests/valid/Mercury.options:
tests/valid/Mmakefile:
    Enable the bug, and run it with -O5.
2022-02-07 17:30:32 +11:00

246 lines
9.5 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2000-2012 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%
%
% File: mark_static_terms.m.
% Main author: fjh.
%
% This module traverses the HLDS, updating the `how_to_construct' field of
% construction unifications. For each construction which can be done
% statically, i.e. whose arguments are all static, it replaces this field with
% `construct_statically'. The main use of this information is in the MLDS
% backend, to determine when we can generate static constants instead of
% calling new_object(). However, other parts of the compiler may also use
% this information.
%
%-----------------------------------------------------------------------------%
:- module hlds.mark_static_terms.
:- interface.
:- import_module hlds.hlds_module.
:- import_module bool.
:- import_module io.
%-----------------------------------------------------------------------------%
:- pred maybe_mark_static_terms(bool::in, bool::in,
module_info::in, module_info::out, io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_pred.
:- import_module hlds.passes_aux.
:- import_module libs.
:- import_module libs.file_util.
:- import_module libs.globals.
:- import_module libs.optimization_options.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.
:- import_module list.
:- import_module require.
:- import_module set_tree234.
%-----------------------------------------------------------------------------%
maybe_mark_static_terms(Verbose, Stats, !HLDS, !IO) :-
module_info_get_globals(!.HLDS, Globals),
globals.get_opt_tuple(Globals, OptTuple),
SGCells = OptTuple ^ ot_use_static_ground_cells,
(
SGCells = use_static_ground_cells,
module_info_get_name(!.HLDS, ModuleName),
get_progress_output_stream(Globals, ModuleName, ProgressStream, !IO),
maybe_write_string(ProgressStream, Verbose,
"% Marking static ground terms...\n", !IO),
maybe_flush_output(ProgressStream, Verbose, !IO),
process_valid_nonimported_procs(update_proc(mark_static_terms), !HLDS),
maybe_write_string(ProgressStream, Verbose,
"% done.\n", !IO),
maybe_report_stats(ProgressStream, Stats, !IO)
;
SGCells = do_not_use_static_ground_cells
).
%-----------------------------------------------------------------------------%
% As we traverse the goal, we keep track of which variables are static at
% the current program point.
%
:- type static_info == set_tree234(prog_var).
:- pred mark_static_terms(module_info::in, proc_info::in, proc_info::out)
is det.
mark_static_terms(_ModuleInfo, !Proc) :-
% The ModuleInfo argument is there just for passes_aux.
proc_info_get_goal(!.Proc, Goal0),
StaticInfo0 = set_tree234.init,
goal_mark_static_terms(Goal0, Goal, StaticInfo0, _StaticInfo),
proc_info_set_goal(Goal, !Proc).
:- pred goal_mark_static_terms(hlds_goal::in, hlds_goal::out,
static_info::in, static_info::out) is det.
goal_mark_static_terms(Goal0, Goal, !SI) :-
Goal0 = hlds_goal(GoalExpr0, GoalInfo),
Goal = hlds_goal(GoalExpr, GoalInfo),
(
GoalExpr0 = conj(ConjType, Goals0),
% It's OK to treat parallel conjunctions as if they were sequential
% here, since if we mark any variables as static, the computation
% of those variables will be done at compile time.
conj_mark_static_terms(Goals0, Goals, !SI),
GoalExpr = conj(ConjType, Goals)
;
GoalExpr0 = disj(Goals0),
% We revert to the original static_info at the end of branched goals.
disj_mark_static_terms(Goals0, Goals, !.SI),
GoalExpr = disj(Goals)
;
GoalExpr0 = switch(Var, CanFail, Cases0),
% We revert to the original static_info at the end of branched goals.
cases_mark_static_terms(Cases0, Cases, !.SI),
GoalExpr = switch(Var, CanFail, Cases)
;
GoalExpr0 = negation(SubGoal0),
% We revert to the original static_info at the end of the negation.
goal_mark_static_terms(SubGoal0, SubGoal, !.SI, _SI),
GoalExpr = negation(SubGoal)
;
GoalExpr0 = scope(Reason, SubGoal0),
( if
Reason = from_ground_term(TermVar, from_ground_term_construct)
then
% These scopes already have all their unifications marked
% as construct_statically.
set_tree234.insert(TermVar, !SI),
GoalExpr = GoalExpr0
else
goal_mark_static_terms(SubGoal0, SubGoal, !SI),
GoalExpr = scope(Reason, SubGoal)
)
;
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
SI0 = !.SI,
% We run the Cond and the Then in sequence, and we run the Else
% in parallel with that, and then we throw away the static_infos
% we computed and revert to the original static_info at the end,
% since this was a branched goal.
goal_mark_static_terms(Cond0, Cond, SI0, SI_Cond),
goal_mark_static_terms(Then0, Then, SI_Cond, _SI_Then),
goal_mark_static_terms(Else0, Else, SI0, _SI_Else),
GoalExpr = if_then_else(Vars, Cond, Then, Else)
;
( GoalExpr0 = plain_call(_, _, _, _, _, _)
; GoalExpr = generic_call(_, _, _, _, _)
; GoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
),
GoalExpr = GoalExpr0
;
GoalExpr0 = unify(LHS, RHS, Mode, Unification0, Context),
unification_mark_static_terms(Unification0, Unification, !SI),
GoalExpr = unify(LHS, RHS, Mode, Unification, Context)
;
GoalExpr0 = shorthand(_),
% These should have been expanded out by now.
unexpected($pred, "shorthand")
).
:- pred conj_mark_static_terms(hlds_goals::in, hlds_goals::out,
static_info::in, static_info::out) is det.
conj_mark_static_terms(Goals0, Goals, !SI) :-
list.map_foldl(goal_mark_static_terms, Goals0, Goals, !SI).
:- pred disj_mark_static_terms(hlds_goals::in, hlds_goals::out,
static_info::in) is det.
disj_mark_static_terms([], [], _).
disj_mark_static_terms([Goal0 | Goals0], [Goal | Goals], SI0) :-
% We throw away the static_info obtained after each branch.
goal_mark_static_terms(Goal0, Goal, SI0, _SI),
disj_mark_static_terms(Goals0, Goals, SI0).
:- pred cases_mark_static_terms(list(case)::in, list(case)::out,
static_info::in) is det.
cases_mark_static_terms([], [], _SI0).
cases_mark_static_terms([Case0 | Cases0], [Case | Cases], SI0) :-
Case0 = case(MainConsId, OtherConsIds, Goal0),
% We throw away the static_info obtained after each branch.
goal_mark_static_terms(Goal0, Goal, SI0, _SI),
Case = case(MainConsId, OtherConsIds, Goal),
cases_mark_static_terms(Cases0, Cases, SI0).
:- pred unification_mark_static_terms(unification::in, unification::out,
static_info::in, static_info::out) is det.
unification_mark_static_terms(Unification0, Unification, !StaticVars) :-
(
Unification0 = construct(Var, ConsId, ArgVars, ArgModes,
HowToConstruct0, Unique, SubInfo),
% If all the arguments are static, then the newly constructed variable
% is static too.
( if list.all_true(set_tree234.contains(!.StaticVars), ArgVars) then
HowToConstruct = construct_statically(marked_static),
set_tree234.insert(Var, !StaticVars),
% This is a minor optimization to improve the efficiency of the
% compiler: don't bother allocating memory if we don't need to.
( if HowToConstruct = HowToConstruct0 then
Unification = Unification0
else
Unification = construct(Var, ConsId, ArgVars, ArgModes,
HowToConstruct, Unique, SubInfo)
)
else
Unification = Unification0
)
;
Unification0 = deconstruct(_Var, _ConsId, _ArgVars, _UniModes,
_CanFail, _CanCGC),
Unification = Unification0
% ( if
% % if the variable being deconstructed is static,
% % and the deconstruction cannot fail,
% % then the newly extracted argument variables
% % are static too
% % (XXX is the "cannot fail" bit really necessary?)
% map.search(StaticVars0, Var, Data),
% CanFail = cannot_fail
% then
% XXX insert ArgVars into StaticVars0
% else
% true
% )
;
Unification0 = assign(TargetVar, SourceVar),
Unification = Unification0,
% If the variable being assigned from is static, then the variable
% being assigned to is static too.
( if set_tree234.contains(!.StaticVars, SourceVar) then
set_tree234.insert(TargetVar, !StaticVars)
else
true
)
;
( Unification0 = simple_test(_, _)
; Unification0 = complicated_unify(_, _, _)
),
Unification = Unification0
).
%-----------------------------------------------------------------------------%
:- end_module hlds.mark_static_terms.
%-----------------------------------------------------------------------------%