%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2006-2008, 2010-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: structure_reuse.lbu.m.
% Main authors: nancy.
%
% Implementation of the process of annotating each program point within
% a procedure with local backward use information.
%
% Each program point (goal within a procedure definition) is annotated with a
% set of variables that are in Local Backward Use (LBU). A variable is said to
% be in LBU if it may be accessed upon backtracking.  This information is
% computed based on the backtrack-vars (i.e. the input variables of the
% alternative goals of a disjunction), and forward use information.
%
% The implementation is based on the theory detailed in Nancy Mazur's PhD
% ("Instantiation 2", cf. Section 7.4).
% XXX Note: slight variations as to the treatment of disjunctions,
% switches and if-then-elses.
%
%-----------------------------------------------------------------------------%

:- module transform_hlds.ctgc.structure_reuse.lbu.
:- interface.

:- import_module hlds.
:- import_module hlds.hlds_pred.

:- pred backward_use_information(proc_info::in, proc_info::out) is det.

%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

:- implementation.

:- import_module check_hlds.
:- import_module check_hlds.type_util.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_llds.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.set_of_var.
:- import_module parse_tree.var_table.

:- import_module list.
:- import_module require.

%-----------------------------------------------------------------------------%

backward_use_information(!ProcInfo):-
    proc_info_get_goal(!.ProcInfo, Goal0),
    proc_info_get_var_table(!.ProcInfo, VarTable),

    % Before the first goal, the set of variables in LBU is empty.
    LBU0 = set_of_var.init,
    backward_use_in_goal(VarTable, Goal0, Goal, LBU0, _LBU),

    proc_info_set_goal(Goal, !ProcInfo).

:- pred backward_use_in_goal(var_table::in, hlds_goal::in, hlds_goal::out,
    set_of_progvar::in, set_of_progvar::out) is det.

backward_use_in_goal(VarTable, !TopGoal, !LBU) :-
    !.TopGoal = hlds_goal(Expr0, Info0),

    % Add resume_vars to the LBU-set.
    set_of_var.union(get_backtrack_vars(VarTable, Info0), !LBU),

    backward_use_in_goal_2(VarTable, Info0, Expr0, Expr, !LBU),

    goal_info_set_lbu(!.LBU, Info0, Info),
    !:TopGoal = hlds_goal(Expr, Info).

:- pred backward_use_in_goal_2(var_table::in, hlds_goal_info::in,
    hlds_goal_expr::in, hlds_goal_expr::out,
    set_of_progvar::in, set_of_progvar::out) is det.

backward_use_in_goal_2(VarTable, Info0, !Expr, !LBU) :-
    % Handle each goal type separately:
    (
        !.Expr = unify(_, _, _, _, _)
    ;
        !.Expr = plain_call(_,_, _, _, _, _),
        Det = goal_info_get_determinism(Info0),
        ( if detism_allows_multiple_solns(Det) then
            % Implementation of Instantiation 2 from Nancy's PhD.
            % In this instantation, a non-deterministic procedure
            % call only adds its LFU-variables to the current set
            % of lbu-variables. Cf. PhD Nancy Mazur.

            goal_info_get_pre_births(Info0, PreBirths),
            goal_info_get_post_births(Info0, PostBirths),
            !:LBU = set_of_var.union_list([goal_info_get_lfu(Info0),
                remove_typeinfo_vars_from_set_of_var(VarTable, PreBirths),
                remove_typeinfo_vars_from_set_of_var(VarTable, PostBirths),
                !.LBU])
        else
            true
        )
    ;
        !.Expr = generic_call(_, _, _, _, _)
    ;
        % XXX Can they be nondet? If so, LFU variables need to be added
        % to !LBU.
        !.Expr = call_foreign_proc(_, _, _, _, _, _, _)
    ;
        !.Expr = conj(ConjType, Goals0),
        backward_use_in_conj(VarTable, Goals0, Goals, !LBU),
        !:Expr = conj(ConjType, Goals)
    ;
        !.Expr = disj(Goals0),
        backward_use_in_disj(VarTable, Goals0, Goals, !LBU),
        !:Expr = disj(Goals)
    ;
        !.Expr = switch(A, B, Cases0),
        backward_use_in_cases(VarTable, Cases0, Cases, !LBU),
        !:Expr = switch(A, B, Cases)
    ;
        !.Expr = negation(SubGoal0),
        % handled as: if SubGoal0 then fail else true
        LBU0 = !.LBU,
        backward_use_in_goal(VarTable, SubGoal0, SubGoal, !.LBU, _),
        % A negation does not introduce any choice-points! Hence the
        % negation itself is deterministic, and no new variables in LBU
        % are introduced into the resulting LBU-set.
        !:LBU = LBU0,
        !:Expr = negation(SubGoal)
    ;
        !.Expr = scope(Reason, SubGoal0),
        ( if Reason = from_ground_term(_, from_ground_term_construct) then
            SubGoal = SubGoal0
        else
            % XXX We could treat from_ground_term_deconstruct specially
            % as well.
            backward_use_in_goal(VarTable, SubGoal0, SubGoal, !LBU)
        ),
        !:Expr = scope(Reason, SubGoal)
    ;
        % XXX The implementation for if-then-else is different from the theory
        % in the thesis.  We can obtain more precision when the Condition-goal
        % is deterministic, which means that the Then-goal can be analysed with
        % the initial LBU-set (instead of the set obtained after the analysis
        % of the Condition-goal).
        !.Expr = if_then_else(Vars, Cond0, Then0, Else0),
        LBU0 = !.LBU,

        % Annotate Cond-goal.
        backward_use_in_goal(VarTable, Cond0, Cond, LBU0, _),

        % Annotate Then-goal.
        % When annotating the then-part, the lbu used for it should not
        % contain the resume-vars due to the else part.
        % trick: to calculate inital LBU for the Then-goal, we set the
        % resume-point of the condition to no_resume_point.
        Cond0 = hlds_goal(CondGoal0, CondInfo0),
        goal_info_set_resume_point(no_resume_point, CondInfo0, InfoTmp),
        CondTmp = hlds_goal(CondGoal0, InfoTmp),
        backward_use_in_goal(VarTable, CondTmp, _, LBU0, LBU0T),
        backward_use_in_goal(VarTable, Then0, Then, LBU0T, LBUT),

        % Annotate Else-goal.
        backward_use_in_goal(VarTable, Else0, Else, LBU0, LBUE),
        set_of_var.union(LBUT, LBUE, !:LBU),
        !:Expr = if_then_else(Vars, Cond, Then, Else)
    ;
        !.Expr = shorthand(_),
        % These should have been expanded out by now.
        unexpected($pred, "shorthand")
    ).

:- func get_backtrack_vars(var_table, hlds_goal_info) = set_of_progvar.

get_backtrack_vars(VarTable, Info) = Vars :-
    goal_info_get_resume_point(Info, ResPoint),
    (
        ResPoint = resume_point(ResVars, _),
        Vars = remove_typeinfo_vars_from_set_of_var(VarTable, ResVars)
    ;
        ResPoint = no_resume_point,
        Vars = set_of_var.init
    ).

:- pred detism_allows_multiple_solns(prog_data__determinism::in) is semidet.

detism_allows_multiple_solns(detism_non).
detism_allows_multiple_solns(detism_multi).
detism_allows_multiple_solns(detism_cc_non).
detism_allows_multiple_solns(detism_cc_multi).

:- pred backward_use_in_conj(var_table::in, list(hlds_goal)::in,
    list(hlds_goal)::out, set_of_progvar::in, set_of_progvar::out) is det.

backward_use_in_conj(VarTable, !Goals, !LBU) :-
    list.map_foldl(backward_use_in_goal(VarTable), !Goals, !LBU).

:- pred backward_use_in_cases(var_table::in, list(case)::in, list(case)::out,
    set_of_progvar::in, set_of_progvar::out) is det.

backward_use_in_cases(VarTable, !Cases, !LBU) :-
    % Every case is analysed with the same initial set of LBU-vars.
    LBU0 = !.LBU,
    list.map_foldl(backward_use_in_case(LBU0, VarTable), !Cases, !LBU).

:- pred backward_use_in_case(set_of_progvar::in, var_table::in,
    case::in, case::out, set_of_progvar::in, set_of_progvar::out) is det.

backward_use_in_case(LBU0, VarTable, !Case, !LBU):-
    !.Case = case(MainConsId, OtherConsIds, Goal0),
    backward_use_in_goal(VarTable, Goal0, Goal, LBU0, NewLBU),
    !:Case = case(MainConsId, OtherConsIds, Goal),
    set_of_var.union(NewLBU, !LBU).

:- pred backward_use_in_disj(var_table::in,
    list(hlds_goal)::in, list(hlds_goal)::out,
    set_of_progvar::in, set_of_progvar::out) is det.

backward_use_in_disj(VarTable, !Goals, !LBU) :-
    % Every disj-goal is analysed with the same initial set of LBU-vars.
    LBU0 = !.LBU,
    list.map_foldl(backward_use_in_disj_goal(LBU0, VarTable), !Goals, !LBU).

:- pred backward_use_in_disj_goal(set_of_progvar::in, var_table::in,
    hlds_goal::in, hlds_goal::out,
    set_of_progvar::in, set_of_progvar::out) is det.

backward_use_in_disj_goal(LBU0, VarTable, !Goal, !LBU) :-
    backward_use_in_goal(VarTable, !Goal, LBU0, NewLBU),
    set_of_var.union(NewLBU, !LBU).

%-----------------------------------------------------------------------------%
:- end_module transform_hlds.ctgc.structure_reuse.lbu.
%-----------------------------------------------------------------------------%
