%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 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.
%---------------------------------------------------------------------------%
%
% Authors: pbone, zs.
%
% This file implements the coverage propagation algorithm, which attaches
% coverage information to the component goals of a procedure body.
%
% This module can be compiled with the following trace flag to enable
% debugging: debug_first_var_use. See Mercury.options in this directory.
%
%---------------------------------------------------------------------------%

:- module var_use_analysis.

:- interface.

:- import_module analysis_utils.
:- import_module mdbcomp.
:- import_module mdbcomp.goal_path.
:- import_module mdbcomp.program_representation.
:- import_module coverage.
:- import_module measurements.
:- import_module profile.
:- import_module report.

:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module set.

    % Gives information about the use of a variable measured in average call
    % sequence counts since either the beginning or the end of the procedure.
    %
:- type var_use_info
    --->    var_use_info(
                vui_cost_until_use          :: float,
                vui_proc_cost               :: float,
                vui_use_type                :: var_use_type
            ).

:- type var_use_options
    --->    var_use_options(
                vuo_deep                    :: deep,
                vuo_intermodule_var_use     :: intermodule_var_use,
                vuo_var_use_type            :: var_use_type
            ).

:- type intermodule_var_use
    --->    follow_calls_into_module(string)
    ;       follow_any_call.

:- type var_use_type
    --->    var_use_production
            % The variable is produced: free >> ground
    ;       var_use_consumption
            % The variable is consumed: free >> free
    ;       var_use_other.
            % The variable is used in some other way.

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

:- func average_var_use(list(var_use_info)) = var_use_info.

:- pred var_mode_to_var_use_type(var_mode_rep::in, var_use_type::out) is det.

:- pred pessimistic_var_use_info(var_use_type::in, float::in,
    var_use_info::out) is det.

:- pred pessimistic_var_use_time(var_use_type::in, float::in, float::out)
    is det.

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

    % get_call_site_dynamic_var_use_info(Deep, CSDPtr, ArgNum, RT, VarUseType,
    %   MaybeVarUseInfo):
    %
    % Lookup when the call site CSDPtr will produce ArgNum. RT is the type of
    % recursion in the call site's parent clique and VarUseType is the type of
    % variable use that is expected, VarUseType is used to produce conservative
    % defaults if a callee cannot be analyzed.
    %
    % The first mode avoids a check to ensure that this recursion type provides
    % enough information.
    %
:- pred get_call_site_dynamic_var_use_info(call_site_dynamic_ptr, int,
    recursion_type, float, var_use_options, maybe_error(var_use_info)).
:- mode get_call_site_dynamic_var_use_info(in, in,
    in(recursion_type_known_costs), in, in, out) is det.
:- mode get_call_site_dynamic_var_use_info(in, in,
    in, in, in, out) is det.

    % get_call_site_dynamic_var_use_info_rec_level(Deep, CliquePtr, CSDPtr,
    %   ArgNum, RT, MaybeCurDepth, Cost, CallStack, VarUseType,
    %   MaybeVarUseInfo).
    %
    % As above. This alternative should be used if searching starts at a
    % different recursion level or from within a current variable use analysis.
    %
    % CliquePtr is the current clique, MaybeCurDepth is the current recursion
    % depth within the clique if known, CallStack is the set of proc dynamics
    % that have already been explored.
    %
    % Cost is the cost of the call.
    %
:- pred get_call_site_dynamic_var_use_info_rec_level(clique_ptr,
    call_site_dynamic_ptr, int, recursion_type, maybe(recursion_depth),
    float, set(proc_dynamic_ptr), var_use_options, maybe_error(var_use_info)).
:- mode get_call_site_dynamic_var_use_info_rec_level(in, in, in,
    in(recursion_type_known_costs), in(maybe_yes(ground)), in, in, in, out)
    is det.
:- mode get_call_site_dynamic_var_use_info_rec_level(in, in, in,
    in, in, in, in, in, out) is det.

:- pred clique_var_use_info(clique_ptr::in, int::in,
    var_use_options::in, maybe_error(var_use_info)::out) is det.

:- pred proc_dynamic_var_use_info(clique_ptr::in,
    proc_dynamic_ptr::in, int::in,
    recursion_type::in(recursion_type_known_costs), recursion_depth::in,
    float::in, set(proc_dynamic_ptr)::in, var_use_options::in,
    maybe_error(var_use_info)::out) is det.

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

    % Find the first use of a variable in an arbitrary goal.
    %
:- pred var_first_use(clique_ptr::in,
    map(reverse_goal_path, cost_and_callees)::in,
    map(reverse_goal_path, cs_cost_csq)::in,
    containing_goal_map::in, goal_attr_array(coverage_info)::in,
    recursion_type::in(recursion_type_known_costs), recursion_depth::in,
    goal_rep(goal_id)::in, reverse_goal_path::in, float::in, var_rep::in,
    var_use_options::in, var_use_info::out) is det.

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

:- implementation.

:- import_module measurement_units.
:- import_module program_representation_utils.
:- import_module recursion_patterns.

:- import_module assoc_list.
:- import_module float.
:- import_module int.
:- import_module io.
:- import_module pair.
:- import_module require.
:- import_module string.

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

average_var_use(Uses) = var_use_info(CostUntilUse, AvgProcCost, Type) :-
    (
        Uses = [],
        unexpected($pred, "cannot average zero items")
    ;
        Uses = [var_use_info(_, _, Type) | _],
        foldl2(sum_use_info_costs, Uses, 0.0, SumCost, 0.0, SumProcCost),
        Num = float(length(Uses)),
        CostUntilUse = SumCost / Num,
        AvgProcCost = SumProcCost / Num,
        TestType =
            ( pred(var_use_info(_, _, TypeI)::in) is semidet :-
                Type = TypeI
            ),
        ( if all_true(TestType, Uses) then
            true
        else
            unexpected($pred, "use types do not match")
        )
    ).

:- pred sum_use_info_costs(var_use_info::in, float::in, float::out,
    float::in, float::out) is det.

sum_use_info_costs(var_use_info(Cost, ProcCost, _), !AccCost, !AccProcCost) :-
    !:AccCost = !.AccCost + Cost,
    !:AccProcCost = !.AccProcCost + ProcCost.

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

var_mode_to_var_use_type(var_mode_rep(InitialInst, FinalInst), VarUseType) :-
    ( if
        InitialInst = ir_ground_rep,
        FinalInst = ir_ground_rep
    then
        VarUseType = var_use_consumption
    else if
        InitialInst = ir_free_rep,
        FinalInst = ir_ground_rep
    then
        VarUseType = var_use_production
    else
        VarUseType = var_use_other
    ).

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

pessimistic_var_use_info(VarUseType, ProcCost, VarUseInfo) :-
    pessimistic_var_use_time(VarUseType, ProcCost, CostUntilUse),
    VarUseInfo = var_use_info(CostUntilUse, ProcCost, VarUseType).

pessimistic_var_use_time(VarUseType, ProcCost, CostUntilUse) :-
    (
        VarUseType = var_use_consumption,
        CostUntilUse = 0.0
    ;
        ( VarUseType = var_use_production
        ; VarUseType = var_use_other
        ),
        CostUntilUse = ProcCost
    ).

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

get_call_site_dynamic_var_use_info(CSDPtr, ArgNum, RecursionType, Cost,
        VarUseOptions, MaybeVarUseInfo) :-
    % XXX If the CSD represents a closure then the argument position will be
    % incorrect. This is not currently important, as we assume that the
    % compiler will not push signals and waits into higher order calls.
    % Therefore this should never be called for a higher order call site.
    Deep = VarUseOptions ^ vuo_deep,
    deep_lookup_call_site_dynamics(Deep, CSDPtr, CSD),
    deep_lookup_clique_index(Deep, CSD ^ csd_caller, ParentCliquePtr),
    recursion_type_get_maybe_avg_max_depth(RecursionType, MaybeCurDepth),
    get_call_site_dynamic_var_use_info_rec_level(ParentCliquePtr, CSDPtr,
        ArgNum, RecursionType, MaybeCurDepth, Cost, set.init, VarUseOptions,
        MaybeVarUseInfo).

get_call_site_dynamic_var_use_info_rec_level(ParentCliquePtr, CSDPtr, ArgNum,
        RecursionType, MaybeDepth, Cost, CallStack0, VarUseOptions,
        MaybeVarUseInfo) :-
    Deep = VarUseOptions ^ vuo_deep,
    deep_lookup_clique_maybe_child(Deep, CSDPtr, MaybeCalleeCliquePtr),
    (
        MaybeCalleeCliquePtr = yes(CalleeCliquePtr),
        % This is a non-recursive call site.

        % We don't check if this call crossed a module boundary here,
        % that is done in clique_var_use_info.
        clique_var_use_info(CalleeCliquePtr, ArgNum, VarUseOptions,
            MaybeVarUseInfo)
    ;
        MaybeCalleeCliquePtr = no,
        % This is a recursive call site.

        deep_lookup_call_site_dynamics(Deep, CSDPtr, CSD),
        CalleePDPtr = CSD ^ csd_callee,
        ( if
            not intermodule_var_use_should_follow_csd(VarUseOptions, CSDPtr)
        then
            % Don't follow this call across module boundaries.
            pessimistic_var_use_info(VarUseOptions ^ vuo_var_use_type, Cost,
                VarUseInfo),
            MaybeVarUseInfo = ok(VarUseInfo)
        else if set.member(CalleePDPtr, CallStack0) then
            % Return a first use time of 1.0. This is used by the formula
            % below.
            MaybeVarUseInfo = ok(var_use_info(1.0, Cost,
                VarUseOptions ^ vuo_var_use_type))
        else
            (
                MaybeDepth = yes(Depth0),
                recursion_depth_descend(Depth0, Depth),
                (
                    ( RecursionType = rt_not_recursive
                    ; RecursionType = rt_single(_, _, _, _, _)
                    ),
                    % Don't follow this recursive call as normal, doing so
                    % would make this analysis take too long. We can compute
                    % the cost of variable use time by the following formula:
                    set.insert(CalleePDPtr, CallStack0, CallStack),
                    proc_dynamic_recursive_var_use_info(ParentCliquePtr,
                        CalleePDPtr, ArgNum, RecursionType, Depth, Cost,
                        CallStack, VarUseOptions, MaybeVarUseInfo0),
                    (
                        MaybeVarUseInfo0 = ok(var_use_info(UseTime0, ProcTime0,
                            UseType)),
                        UseTime = UseTime0 + 1.0,
                        ProcTime = ProcTime0 + 1.0,
                        % Add a call sequence count for the cost of this call.
                        MaybeVarUseInfo = ok(var_use_info(UseTime, ProcTime,
                            UseType))
                    ;
                        MaybeVarUseInfo0 = error(_),
                        MaybeVarUseInfo = MaybeVarUseInfo0
                    )
                ;
                    ( RecursionType = rt_divide_and_conquer(_, _)
                    ; RecursionType = rt_mutual_recursion(_)
                    ; RecursionType = rt_other(_)
                    ; RecursionType = rt_errors(_)
                    ),
                    MaybeVarUseInfo = error(
                        "Cannot compute var use on a recursive call site "
                        ++ "for an unknown recursion type")
                )
            ;
                MaybeDepth = no,
                MaybeVarUseInfo = error(
                    "Cannot compute var use on a recursive call site "
                    ++ "for an unknown recursion depth")
            )
        )
    ).

clique_var_use_info(CliquePtr, ArgNum, VarUseOptions, MaybeVarUseInfo) :-
    Deep = VarUseOptions ^ vuo_deep,
    deep_lookup_clique_parents(Deep, CliquePtr, CSDPtr),
    deep_lookup_csd_desc(Deep, CSDPtr, CSDDesc),
    Cost = float(inherit_callseqs(CSDDesc)),
    find_clique_first_and_other_procs(Deep, CliquePtr, MaybeFirstProc,
        _OtherProcs),
    (
        MaybeFirstProc = yes(FirstPDPtr),
        VarUseType = VarUseOptions ^ vuo_var_use_type,
        ( if intermodule_var_use_should_follow_csd(VarUseOptions, CSDPtr) then
            create_clique_recursion_costs_report(Deep, CliquePtr,
                MaybeRecursionReport),
            (
                MaybeRecursionReport = ok(RecursionReport),
                RecursionType = RecursionReport ^ crr_recursion_type
            ;
                MaybeRecursionReport = error(Error),
                RecursionType = rt_errors([Error])
            ),
            (
                (
                    RecursionType = rt_not_recursive,
                    recursion_type_get_maybe_avg_max_depth(RecursionType,
                        yes(Depth)),
                    % XXX This shouldn't need the recursion information
                    % anymore.
                    proc_dynamic_var_use_info(CliquePtr, FirstPDPtr,
                        ArgNum, RecursionType, Depth, Cost, set.init,
                        VarUseOptions, MaybeVarUseInfo0)
                ;
                    RecursionType = rt_single(_, _, _, _, _),
                    recursion_type_get_maybe_avg_max_depth(RecursionType,
                        yes(Depth)),
                    % Add this PD to the proc dynamic pointer, we don't use a
                    % second recursion in this case.
                    proc_dynamic_recursive_var_use_info(CliquePtr, FirstPDPtr,
                        ArgNum, RecursionType, Depth, Cost,
                        set.make_singleton_set(FirstPDPtr),
                        VarUseOptions, MaybeVarUseInfo0)
                ),
                (
                    MaybeVarUseInfo0 = ok(var_use_info(UseTime0, ProcTime0,
                        UseType)),
                    UseTime = UseTime0 + 1.0,
                    ProcTime = ProcTime0 + 1.0,
                    % Add a call sequence count for the cost of this call.
                    MaybeVarUseInfo = ok(var_use_info(UseTime, ProcTime,
                        UseType))
                ;
                    MaybeVarUseInfo0 = error(_),
                    MaybeVarUseInfo = MaybeVarUseInfo0
                )
            ;
                ( RecursionType = rt_divide_and_conquer(_, _)
                ; RecursionType = rt_mutual_recursion(_)
                ; RecursionType = rt_other(_)
                ; RecursionType = rt_errors(_)
                ),
                pessimistic_var_use_info(VarUseType, Cost, VarUseInfo),
                MaybeVarUseInfo = ok(VarUseInfo)
            )
        else
            pessimistic_var_use_info(VarUseType, Cost, VarUseInfo),
            MaybeVarUseInfo = ok(VarUseInfo)
        )
    ;
        MaybeFirstProc = no,
        unexpected($pred, "clique has no first procedure")
    ).

proc_dynamic_var_use_info(CliquePtr, PDPtr, ArgNum, RecursionType,
        Depth, ProcCost, CallStack, VarUseOptions, MaybeVarUseInfo) :-
    prepare_for_proc_var_first_use(CliquePtr, PDPtr, ArgNum, RecursionType,
        Depth, VarUseOptions, Info),
    (
        Info = proc_var_first_use_prepared_info(Goal, _LastGoalId,
            ContainingGoalMap, CoverageArray, CallSiteCostMap,
            RecursiveCallSiteCostMap, Var),
        goal_var_first_use_wrapper(CliquePtr, CallStack,
            ContainingGoalMap, CoverageArray, CallSiteCostMap,
            RecursiveCallSiteCostMap, RecursionType, Depth, Goal, ProcCost,
            Var, VarUseOptions, VarUseInfo),
        MaybeVarUseInfo = ok(VarUseInfo)
    ;
        Info = error(Error),
        MaybeVarUseInfo = error(Error)
    ).

    % Like proc_dynamic_recursive_var_use_info except that it handles recursive
    % code by induction.
    %
    %   UseTime = BaseUseTime + (RecUseTime + 1.0) * Depth
    %
    % Where BaseUseTime is the use time in the base case execution and
    % RecUseTime is the use time before the recursive call. This works for any
    % self-recursion pattern.
    %
:- pred proc_dynamic_recursive_var_use_info(clique_ptr::in,
    proc_dynamic_ptr::in, int::in,
    recursion_type::in(recursion_type_known_costs), recursion_depth::in,
    float::in, set(proc_dynamic_ptr)::in, var_use_options::in,
    maybe_error(var_use_info)::out) is det.

proc_dynamic_recursive_var_use_info(CliquePtr, PDPtr, ArgNum,
        RecursionType, Depth, _Cost, CallStack, VarUseOptions,
        MaybeVarUseInfo) :-
    prepare_for_proc_var_first_use(CliquePtr, PDPtr, ArgNum, RecursionType,
        Depth, VarUseOptions, Info),
    (
        Info = proc_var_first_use_prepared_info(Goal, LastGoalId,
            ContainingGoalMap, CoverageArray, CallSiteCostMap,
            RecursiveCallSiteCostMap, Var),
        VarUseInfo = var_first_use_static_info(CliquePtr,
            CallSiteCostMap, RecursiveCallSiteCostMap, ContainingGoalMap,
            CoverageArray, Var, VarUseOptions, CallStack, RecursionType, Depth,
            no_recursion_info),

        RecProbsArray0 = create_goal_id_array(LastGoalId, impossible),
        build_recursive_call_sites_list(RecursiveCallSiteCostMap,
            RecursiveCalls),
        goal_rec_prob(Goal, RecursiveCalls, VarUseInfo, RecProb,
            RecProbsArray0, RecProbsArray),

        VarFirstUseInfoRecCase = VarUseInfo ^ fui_rec_info :=
            first_use_rec_info(RecProbsArray, recursive_case),
        rec_goal_var_first_use(Goal, RecursiveCalls, VarFirstUseInfoRecCase,
            RecFoundFirstUse, 0.0, RecTotalTime),
        VarFirstUseInfoBaseCase = VarUseInfo ^ fui_rec_info :=
            first_use_rec_info(RecProbsArray, base_case),
        rec_goal_var_first_use(Goal, RecursiveCalls, VarFirstUseInfoBaseCase,
            BaseFoundFirstUse, 0.0, BaseTotalTime),

        VarUseType = VarUseOptions ^ vuo_var_use_type,
        (
            RecFoundFirstUse = found_first_use(VarUseTimeRec)
        ;
            RecFoundFirstUse = have_not_found_first_use,
            (
                VarUseType = var_use_consumption,
                VarUseTimeRec = 0.0
            ;
                ( VarUseType = var_use_production
                ; VarUseType = var_use_other
                ),
                VarUseTimeRec = RecTotalTime
            )
        ),
        (
            BaseFoundFirstUse = found_first_use(VarUseTimeBase)
        ;
            BaseFoundFirstUse = have_not_found_first_use,
            (
                VarUseType = var_use_consumption,
                VarUseTimeBase = 0.0
            ;
                ( VarUseType = var_use_production
                ; VarUseType = var_use_other
                ),
                VarUseTimeBase = BaseTotalTime
            )
        ),

        DepthF = recursion_depth_to_float(Depth),
        VarUseTime = VarUseTimeBase +
            DepthF * VarUseTimeRec,
        Cost = RecTotalTime * probability_to_float(RecProb) +
            BaseTotalTime * probability_to_float(not_probability(RecProb)),
        MaybeVarUseInfo = ok(var_use_info(VarUseTime, Cost,
            VarUseOptions ^ vuo_var_use_type)),
        trace [compile_time(flag("debug_first_var_use")), io(!IO)] (
            io.output_stream(OutputStream, !IO),
            io.write_string(OutputStream,
                "\nUseTime = BaseUseTime + RecUseTime * Depth\n", !IO),
            io.format(OutputStream, "%f = %f + %f * %f\n",
                [f(VarUseTime), f(VarUseTimeBase),
                f(VarUseTimeRec), f(DepthF)], !IO)
        )
    ;
        Info = error(Error),
        MaybeVarUseInfo = error(Error)
    ).

:- type proc_var_first_use_prepared_info
    --->    error(string)
    ;       proc_var_first_use_prepared_info(
                goal_rep(goal_id),
                goal_id,
                containing_goal_map,
                goal_attr_array(coverage_info),
                map(reverse_goal_path, cost_and_callees),
                map(reverse_goal_path, cs_cost_csq),
                var_rep
            ).

:- pred prepare_for_proc_var_first_use(clique_ptr::in, proc_dynamic_ptr::in,
    int::in, recursion_type::in(recursion_type_known_costs),
    recursion_depth::in, var_use_options::in,
    proc_var_first_use_prepared_info::out) is det.

prepare_for_proc_var_first_use(CliquePtr, PDPtr, ArgNum, RecursionType, Depth,
        VarUseOptions, Info) :-
    Deep = VarUseOptions ^ vuo_deep,
    deep_lookup_proc_dynamics(Deep, PDPtr, PD),
    PSPtr = PD ^ pd_proc_static,
    deep_get_maybe_procrep(Deep, PSPtr, MaybeProcrep),
    (
        MaybeProcrep = ok(ProcRep),
        ProcDefn = ProcRep ^ pr_defn,
        HeadVars = ProcDefn ^ pdr_head_vars,
        ( if index0(HeadVars, ArgNum, head_var_rep(Var, Mode)) then
            var_mode_to_var_use_type(Mode, ComputedUse),
            VarUseType = VarUseOptions ^ vuo_var_use_type,
            ( if VarUseType = ComputedUse then
                true
            else
                PDPtr = proc_dynamic_ptr(PDNum),
                string.format(
                    "Var uses do not match, passed: %s calculated from "
                    ++ "procrep: %s, Arg %d in proc dynamic %d",
                    [s(string(VarUseType)), s(string(ComputedUse)),
                     i(ArgNum), i(PDNum)], Msg),
                unexpected($pred, Msg)
            ),

            % Prepare callsite information.
            proc_dynamic_paired_call_site_slots(Deep, PDPtr, Slots),
            list.foldl(build_dynamic_call_site_cost_and_callee_map(Deep),
                Slots, map.init, CallSiteCostMap),

            build_recursive_call_site_cost_map(Deep, CliquePtr, PDPtr,
                RecursionType, yes(Depth), MaybeRecursiveCallSiteCostMap),
            MaybeRecursiveCallSiteCostMap = ok(RecursiveCallSiteCostMap),

            % Build procrep
            Goal0 = ProcDefn ^ pdr_goal,
            label_goals(LastGoalId, ContainingGoalMap, Goal0, Goal),

            % Build coverage annotation.
            deep_lookup_proc_statics(Deep, PSPtr, PS),
            ProcLabel = PS ^ ps_id,
            coverage_point_arrays_to_list(PS ^ ps_coverage_point_infos,
                CoveragePointsArray, CoveragePoints),
            MaybeCoveragePointsArray = PD ^ pd_maybe_coverage_points,
            (
                MaybeCoveragePointsArray = yes(CoveragePointsArray)
            ;
                MaybeCoveragePointsArray = no,
                unexpected($pred, "Couldn't get coverage info")
            ),
            list.foldl2(add_coverage_point_to_map, CoveragePoints,
                map.init, SolnsCoveragePointMap,
                map.init, BranchCoveragePointMap),
            deep_lookup_pd_own(Deep, PDPtr, Own),
            goal_annotate_with_coverage(ProcLabel, Goal, Own, CallSiteCostMap,
                SolnsCoveragePointMap, BranchCoveragePointMap,
                ContainingGoalMap, LastGoalId, CoverageArray),

            Info = proc_var_first_use_prepared_info(Goal, LastGoalId,
                ContainingGoalMap, CoverageArray, CallSiteCostMap,
                RecursiveCallSiteCostMap, Var)
        else
            PDPtr = proc_dynamic_ptr(PDNum),
            string.format(
                "proc_dynamic_var_use_info: ArgNum %d out of range for PD %d",
                [i(ArgNum), i(PDNum)], Msg),
            Info = error(Msg)
        )
    ;
        MaybeProcrep = error(Error),
        Info = error(Error)
    ).

%---------------------------------------------------------------------------%
%
% The actual first use analysis code.
%
% Any changes here should be reflected in the recursion and base case specific
% versions of this code below.
%
% TODO: This code is currently out of date with the code below, corrections
% need to be made to this code, see XXX markers below.
%

    % This type represents whether the first use of a variable has been found
    % or not. If it has then the call sequence counts since it was found is
    % stored in this type also.
    %
:- type found_first_use
    --->    have_not_found_first_use
    ;       found_first_use(
                cost_before_use     :: float
            ).

:- inst found_first_use_found for found_first_use/0
    --->    found_first_use(ground).

:- type var_first_use_static_info
    --->    var_first_use_static_info(
                fui_clique              :: clique_ptr,
                fui_call_site_map       :: map(reverse_goal_path,
                                                cost_and_callees),
                fui_rec_call_site_map   :: map(reverse_goal_path,
                                                cs_cost_csq),
                fui_containing_goal_map :: containing_goal_map,
                fui_coverage_array      :: goal_attr_array(coverage_info),
                fui_var                 :: var_rep,
                fui_var_use_opts        :: var_use_options,

                % A set of call sites whose analysis has started but not yet
                % completed. We keep this set to prevent infinite recursion
                % in the analysis itself.
                fui_call_stack          :: set(proc_dynamic_ptr),

                fui_recursion_type      :: recursion_type,
                fui_cur_depth           :: recursion_depth,
                fui_rec_info            :: first_use_rec_info
            ).

:- inst var_first_use_static_info for var_first_use_static_info/0
    --->    var_first_use_static_info(
                ground, ground, ground, ground, ground, ground, ground, ground,
                recursion_type_known_costs,
                ground, ground
            ).

:- inst var_first_use_static_info_rec for var_first_use_static_info/0
    --->    var_first_use_static_info(
                ground, ground, ground, ground, ground, ground, ground, ground,
                recursion_type_known_costs,
                ground,
                first_use_rec_info
            ).

:- type first_use_rec_info
    --->    first_use_rec_info(
                furi_rec_prob_array     :: goal_attr_array(probability),
                furi_rec_case           :: recursive_case
            )
    ;       no_recursion_info.

:- inst first_use_rec_info for first_use_rec_info/0
    --->    first_use_rec_info(ground, ground).

:- inst no_recursion_info for first_use_rec_info/0
    --->    no_recursion_info.

:- type recursive_case
    --->    recursive_case
    ;       base_case.

    % Find the first use of a variable in a goal.
    % Procedure calls can be resolved via the call site which we'll need to
    % lookup anyway to find cost information, This will callback to the deep
    % profiler as it crosses procedure boundaries.
    %
    % This does not follow higher order or method calls. It may be possible to
    % follow call the calls seen during profiling and aggregate their variable
    % use information based on how often they are called from that call site.
    %
:- pred goal_var_first_use(reverse_goal_path::in, goal_rep(goal_id)::in,
    var_first_use_static_info::in(var_first_use_static_info), float::in,
    float::out, found_first_use::out) is det.

goal_var_first_use(RevGoalPath, Goal, StaticInfo, !CostSoFar, FoundFirstUse) :-
    Goal = goal_rep(GoalExpr, Detism, _),
    CoverageArray = StaticInfo ^ fui_coverage_array,
    Coverage = get_goal_attribute_det(CoverageArray, Goal ^ goal_annotation),
    ( if
        % Do not bother exploring this goal if it is never entered. Or never
        % finishes and we're looking for a production.
        (
            get_coverage_before(Coverage, 0)
        ;
            VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
            VarUseType = var_use_production,
            (
                Detism = erroneous_rep
            ;
                Detism = failure_rep
            ;
                get_coverage_after(Coverage, 0)
            )
        )
    then
        FoundFirstUse = have_not_found_first_use
    else
        (
            GoalExpr = conj_rep(Conjuncts),
            conj_var_first_use(RevGoalPath, 1, Conjuncts, StaticInfo,
                !CostSoFar, FoundFirstUse)
        ;
            GoalExpr = disj_rep(Disjuncts),
            disj_var_first_use(RevGoalPath, Disjuncts, Detism, StaticInfo,
                !CostSoFar, FoundFirstUse)
        ;
            GoalExpr = switch_rep(SwitchedOnVar, _CanFail, Cases),
            switch_var_first_use(RevGoalPath, SwitchedOnVar, Cases,
                StaticInfo, !CostSoFar, FoundFirstUse)
        ;
            GoalExpr = ite_rep(Cond, Then, Else),
            ite_var_first_use(RevGoalPath, Cond, Then, Else, StaticInfo,
                !CostSoFar, FoundFirstUse)
        ;
            (
                GoalExpr = negation_rep(SubGoal),
                GoalPathStep = step_neg
            ;
                GoalExpr = scope_rep(SubGoal, ScopeIsCut),
                GoalPathStep = step_scope(ScopeIsCut)
            ),
            RevSubGoalPath = rgp_cons(RevGoalPath, GoalPathStep),
            goal_var_first_use(RevSubGoalPath, SubGoal, StaticInfo,
                !CostSoFar, FoundFirstUse)
        ;
            GoalExpr = atomic_goal_rep(_, _, BoundVars, AtomicGoal),
            (
                ( AtomicGoal = plain_call_rep(_, _, _)
                ; AtomicGoal = higher_order_call_rep(_, _)
                ; AtomicGoal = method_call_rep(_, _, _)
                ),
                call_var_first_use(AtomicGoal, BoundVars, RevGoalPath,
                    StaticInfo, FoundFirstUse, !CostSoFar)
            ;
                ( AtomicGoal = unify_construct_rep(_, _, _)
                ; AtomicGoal = unify_deconstruct_rep(_, _, _)
                ; AtomicGoal = partial_construct_rep(_, _, _)
                ; AtomicGoal = partial_deconstruct_rep(_, _, _)
                ; AtomicGoal = unify_assign_rep(_, _)
                ; AtomicGoal = cast_rep(_, _)
                ; AtomicGoal = unify_simple_test_rep(_, _)
                ; AtomicGoal = pragma_foreign_code_rep(_)
                ; AtomicGoal = event_call_rep(_, _)
                ; AtomicGoal = builtin_call_rep(_, _, _)
                ),
                % trivial goals have a zero cost, so !CostSoFar is not updated.
                atomic_trivial_var_first_use(AtomicGoal, BoundVars,
                    !.CostSoFar, StaticInfo, FoundFirstUse)
            )
        )
    ),
    trace [compile_time(flag("debug_first_var_use")), io(!IO)] (
        io.output_stream(OutputStream, !IO),
        io.format(OutputStream, "Trace: goal_var_first_use: %s\n",
            [s(rev_goal_path_to_string(RevGoalPath))], !IO)
    ).

:- inst atomic_goal_rep_call
    for mdbcomp.program_representation.atomic_goal_rep/0
    --->    plain_call_rep(ground, ground, ground)
    ;       higher_order_call_rep(ground, ground)
    ;       method_call_rep(ground, ground, ground).

:- pred call_var_first_use(atomic_goal_rep::in(atomic_goal_rep_call),
    list(var_rep)::in, reverse_goal_path::in,
    var_first_use_static_info::in(var_first_use_static_info),
    found_first_use::out, float::in, float::out) is det.

call_var_first_use(AtomicGoal, BoundVars, RevGoalPath, StaticInfo,
        FoundFirstUse, !CostSoFar) :-
    CostBefore = !.CostSoFar,
    StaticInfo = var_first_use_static_info(CliquePtr, CostMap,
        RecCostMap, _ContainingGoalMap, _CoverageArray, Var, VarUseOptions,
        _CallStack, _RecursionType, _MaybeCurDepth, _RecInfo),
    VarUseType = VarUseOptions ^ vuo_var_use_type,
    map.lookup(CostMap, RevGoalPath, CostAndCallees),

    % Get the cost of the call.
    ( if cost_and_callees_is_recursive(CliquePtr, CostAndCallees) then
        map.lookup(RecCostMap, RevGoalPath, Cost0)
    else
        Cost0 = CostAndCallees ^ cac_cost
    ),
    ( if cs_cost_get_calls(Cost0) = 0.0 then
        Cost = 0.0
    else
        Cost = cs_cost_get_percall(Cost0)
    ),
    !:CostSoFar = !.CostSoFar + Cost,

    % Determine if the variable we are searching for uses of is involved with
    % this call.
    (
        AtomicGoal = plain_call_rep(_, _, Args),
        Vars = Args
    ;
        ( AtomicGoal = higher_order_call_rep(HOVar, Args)
        ; AtomicGoal = method_call_rep(HOVar, _, Args)
        ),
        Vars = [HOVar | Args]
    ),
    ( if list.member(Var, Vars) then
        (
            ( AtomicGoal = higher_order_call_rep(_, _)
            ; AtomicGoal = method_call_rep(_, _, _)
            ),
            % Conservative assumption, but we *could* do better, since we
            % can find out what procedures are actually called at this call
            % site.
            FirstTime = 0.0
        ;
            AtomicGoal = plain_call_rep(_, _, _),
            call_args_first_use(Args, Cost, StaticInfo, CostAndCallees, Times),
            (
                Times = [],
                unexpected($pred, "no solutions for variable first use time")
            ;
                Times = [FirstTime | LaterTimes],
                ( if
                    VarUseType = var_use_production,
                    LaterTimes = [_ | _]
                then
                    unexpected($pred,
                        "multiple solutions for variable production time")
                else
                    true
                )
            )
        ),

        FoundFirstUse = found_first_use(CostBefore + FirstTime),

        % Assertions.
        ( if
            VarUseType = var_use_production,
            not list.member(Var, BoundVars)
        then
            unexpected($pred,
                "a bound var must be produced by a call if it is an argument.")
        else
            true
        ),
        ( if
            VarUseType = var_use_consumption,
            list.member(Var, BoundVars)
        then
            unexpected($pred,
                "a consumed var must not be mentioned in BoundVars")
        else
            true
        ),
        ( if
            VarUseType = var_use_production,
            ( AtomicGoal = higher_order_call_rep(Var, _)
            ; AtomicGoal = method_call_rep(Var, _, _)
            )
        then
            unexpected($pred, "a HO call site cannot produce its own HO value")
        else
            true
        )
    else
        FoundFirstUse = have_not_found_first_use
    ).

:- pred call_args_first_use(list(var_rep)::in, float::in,
    var_first_use_static_info::in(var_first_use_static_info),
    cost_and_callees::in, list(float)::out) is det.

call_args_first_use(Args, Cost, StaticInfo, CostAndCallees, Times) :-
    StaticInfo = var_first_use_static_info(CliquePtr, _CostMap,
        _RecCostMap, _ContainingGoalMap, _CoverageArray, Var, VarUseOptions,
        CallStack, RecursionType, CurDepth, _RecInfo),
    VarUseType = VarUseOptions ^ vuo_var_use_type,
    HigherOrder = CostAndCallees ^ cac_call_site_is_ho,
    (
        HigherOrder = first_order_call,
        Callees = CostAndCallees ^ cac_callees,
        list.member_indexes0(Var, Args, ArgNums),
        ( if set.is_empty(Callees) then
            % There are no callees; this code is never called.
            pessimistic_var_use_time(VarUseType, Cost, Time),
            Times = [Time]
        else if set.is_singleton(Callees, SingletonCallee) then
            CSDPtr = SingletonCallee ^ c_csd,
            list.map(
                get_call_site_dynamic_var_use_time(CliquePtr, CSDPtr,
                    RecursionType, CurDepth, Cost, CallStack, VarUseOptions),
                ArgNums, Times0),
            list.sort_and_remove_dups(Times0, Times)
        else
            unexpected($pred, "wrong number of callees for normal call site")
        )
    ;
        HigherOrder = higher_order_call,
        % The compiler cannot push signals or waits into higher order calls.
        % Therefore we assume a pessimistic default here.
        pessimistic_var_use_time(VarUseType, Cost, Time),
        Times = [Time]
    ).

:- pred get_call_site_dynamic_var_use_time(clique_ptr::in,
    call_site_dynamic_ptr::in, recursion_type::in,
    recursion_depth::in, float::in, set(proc_dynamic_ptr)::in,
    var_use_options::in, int::in, float::out) is det.

get_call_site_dynamic_var_use_time(CliquePtr, CSDPtr,
        RecursionType, CurDepth, Cost, CallStack,
        VarUseOptions, ArgNum, Time) :-
    get_call_site_dynamic_var_use_info_rec_level(CliquePtr, CSDPtr,
        ArgNum, RecursionType, yes(CurDepth), Cost, CallStack,
        VarUseOptions, MaybeVarUseInfo),
    (
        MaybeVarUseInfo = ok(VarUseInfo),
        VarUseInfo = var_use_info(Time, _, _)
    ;
        MaybeVarUseInfo = error(_),
        VarUseType = VarUseOptions ^ vuo_var_use_type,
        pessimistic_var_use_time(VarUseType, Cost, Time)
    ).

:- inst atomic_trivial_goal_rep
    for mdbcomp.program_representation.atomic_goal_rep/0
    --->    unify_construct_rep(ground, ground, ground)
    ;       unify_deconstruct_rep(ground, ground, ground)
    ;       partial_construct_rep(ground, ground, ground)
    ;       partial_deconstruct_rep(ground, ground, ground)
    ;       unify_assign_rep(ground, ground)
    ;       cast_rep(ground, ground)
    ;       unify_simple_test_rep(ground, ground)
    ;       pragma_foreign_code_rep(ground)
    ;       event_call_rep(ground, ground)
    ;       builtin_call_rep(ground, ground, ground).

:- pred atomic_trivial_var_first_use(
    atomic_goal_rep::in(atomic_trivial_goal_rep), list(var_rep)::in, float::in,
    var_first_use_static_info::in, found_first_use::out) is det.

atomic_trivial_var_first_use(AtomicGoal, BoundVars, CostSoFar, StaticInfo,
        FoundFirstUse) :-
    Var = StaticInfo ^ fui_var,
    VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
    atomic_goal_get_vars(AtomicGoal, Vars),
    ( if
        set.member(Var, Vars),
        (
            VarUseType = var_use_consumption
        ;
            VarUseType = var_use_production,
            list.member(Var, BoundVars)
        ;
            VarUseType = var_use_other
        )
    then
        FoundFirstUse = found_first_use(CostSoFar)
    else
        FoundFirstUse = have_not_found_first_use
    ).

    % Find the first use of a variable within a conjunction. Note that when
    % looking for a production of the variable we search backward and add the
    % time from the end of the goal. Similarly with other goal types that have
    % an execution order, namely disjunctions and if-then-elses.
    %
:- pred conj_var_first_use(reverse_goal_path::in, int::in,
    list(goal_rep(goal_id))::in,
    var_first_use_static_info::in(var_first_use_static_info),
    float::in, float::out, found_first_use::out) is det.

conj_var_first_use(_, _, [], _, !Cost, have_not_found_first_use).
conj_var_first_use(RevGoalPath, ConjNum, [Conj | Conjs], StaticInfo,
        !CostSoFar, FoundFirstUse) :-
    goal_var_first_use(rgp_cons(RevGoalPath, step_conj(ConjNum)), Conj,
        StaticInfo, !CostSoFar, HeadFoundFirstUse),
    (
        % If a variable is bound more than once, then we want to use
        % the last time it is bound, since all of the earlier bindings
        % must have only partially instantiated it. Instmaps can be used
        % to track this. This is relevant when searching for the producer
        % of a variable.
        % XXX For now, we assume that no variable is bound more than once.
        HeadFoundFirstUse = found_first_use(_),
        FoundFirstUse = HeadFoundFirstUse
    ;
        HeadFoundFirstUse = have_not_found_first_use,
        conj_var_first_use(RevGoalPath, ConjNum + 1, Conjs, StaticInfo,
            !CostSoFar, TailFoundFirstUse),
        FoundFirstUse = TailFoundFirstUse
    ).

:- pred disj_var_first_use(reverse_goal_path::in,
    list(goal_rep(goal_id))::in, detism_rep::in,
    var_first_use_static_info::in(var_first_use_static_info),
    float::in, float::out, found_first_use::out) is det.

disj_var_first_use(RevGoalPath, Disjuncts, Detism, StaticInfo,
        !CostSoFar, FoundFirstUse) :-
    % We cannot handle nondet/multi disjunctions. So we use pessimistic
    % defaults for FoundFirstUse if this disjunction is nondet or multi.
    % For calculating the cost of the disjunction, assume that it is a semidet
    % disjunction. Doing this will find the incorrect cost for the
    % disjunction. However disjunctions occur rarely, so this is not likely
    % to dramatically affect anything.
    CostBeforeDisjunction = !.CostSoFar,
    disj_var_first_use_2(RevGoalPath, 1, Disjuncts, StaticInfo,
        !CostSoFar, FoundFirstUse0),
    CostAfterDisjunction = !.CostSoFar,
    ( if
        detism_get_solutions(Detism) = at_most_many_rep,
        FoundFirstUse0 = found_first_use(_)
    then
        VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
        (
            VarUseType = var_use_consumption,
            FoundFirstUse = found_first_use(CostBeforeDisjunction)
        ;
            ( VarUseType = var_use_production
            ; VarUseType = var_use_other
            ),
            FoundFirstUse = found_first_use(CostAfterDisjunction)
        )
    else
        FoundFirstUse = FoundFirstUse0
    ).

:- pred disj_var_first_use_2(reverse_goal_path::in, int::in,
    list(goal_rep(goal_id))::in,
    var_first_use_static_info::in(var_first_use_static_info),
    float::in, float::out, found_first_use::out) is det.

disj_var_first_use_2(_, _, [], _, !CostSoFar, have_not_found_first_use).
disj_var_first_use_2(RevGoalPath, DisjNum, [Disj | Disjs], StaticInfo,
        !CostSoFar, FoundFirstUse) :-
    VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
    CoverageArray = StaticInfo ^ fui_coverage_array,
    goal_var_first_use(rgp_cons(RevGoalPath, step_disj(DisjNum)), Disj,
        StaticInfo, !CostSoFar, HeadFoundFirstUse),
    disj_var_first_use_2(RevGoalPath, DisjNum + 1, Disjs, StaticInfo,
        !CostSoFar, TailFoundFirstUse),
    (
        HeadFoundFirstUse = have_not_found_first_use,
        TailFoundFirstUse = have_not_found_first_use,
        FoundFirstUse = HeadFoundFirstUse
    ;
        HeadFoundFirstUse = have_not_found_first_use,
        TailFoundFirstUse = found_first_use(_),
        FoundFirstUse = TailFoundFirstUse
    ;
        HeadFoundFirstUse = found_first_use(_),
        TailFoundFirstUse = have_not_found_first_use,
        FoundFirstUse = HeadFoundFirstUse
    ;
        HeadFoundFirstUse = found_first_use(HeadCost),
        TailFoundFirstUse = found_first_use(TailCost),
        (
            VarUseType = var_use_consumption,
            % The variable is probably consumed in the first disjunct
            % even if it fails. This is also the pessimistic default.
            Cost = HeadCost
        ;
            ( VarUseType = var_use_production
            ; VarUseType = var_use_other
            ),
            % Use a weighted average to reflect the likely success
            % of the first disjunct.
            DisjCoverage =
                get_goal_attribute_det(CoverageArray, Disj ^ goal_annotation),
            ( if get_coverage_before(DisjCoverage, HeadCount) then
                HeadWeight = float(HeadCount)
            else
                unexpected($pred, "unknown coverage before disjunct")
            ),
            (
                Disjs = [],
                TailWeight = 0.0
            ;
                Disjs = [FirstTailDisj | _],
                FirstTailCoverage = get_goal_attribute_det(CoverageArray,
                    FirstTailDisj ^ goal_annotation),
                ( if get_coverage_before(FirstTailCoverage, TailCount) then
                    TailWeight = float(TailCount)
                else
                    unexpected($pred, "unknown coverage before disjunct")
                )
            ),
            weighted_average([HeadWeight, TailWeight], [HeadCost, TailCost],
                Cost)
        ),
        FoundFirstUse = found_first_use(Cost)
    ).

:- pred switch_var_first_use(reverse_goal_path::in, var_rep::in,
    list(case_rep(goal_id))::in,
    var_first_use_static_info::in(var_first_use_static_info),
    float::in, float::out, found_first_use::out) is det.

switch_var_first_use(RevGoalPath, SwitchedOnVar, Cases, StaticInfo,
        CostBeforeSwitch, CostAfterSwitch, FoundFirstUse) :-
    switch_var_first_use_2(RevGoalPath, 1, StaticInfo, Cases, CaseWeights,
        CostBeforeSwitch, CostCases, FoundFirstUseCases),
    weighted_average(CaseWeights, CostCases, CostAfterSwitch),
    Var = StaticInfo ^ fui_var,
    ( if Var = SwitchedOnVar then
        % This can only possibly be a consumption of this variable.
        FoundFirstUse = found_first_use(CostBeforeSwitch)
    else
        ( if
            list.all_true(unify(have_not_found_first_use), FoundFirstUseCases)
        then
            % No case contained a first-use of this variable.
            FoundFirstUse = have_not_found_first_use
        else
            VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
            (
                VarUseType = var_use_consumption,
                DefaultCost = CostAfterSwitch
            ;
                ( VarUseType = var_use_production
                ; VarUseType = var_use_other
                ),
                DefaultCost = CostBeforeSwitch
            ),
            list.map(ffu_to_float(DefaultCost), FoundFirstUseCases,
                FirstUseTimes),
            weighted_average(CaseWeights, FirstUseTimes, AvgFirstUseTime),
            FoundFirstUse = found_first_use(AvgFirstUseTime)
        )
    ).

:- pred switch_var_first_use_2(reverse_goal_path::in, int::in,
    var_first_use_static_info::in(var_first_use_static_info),
    list(case_rep(goal_id))::in, list(float)::out, float::in,
    list(float)::out, list(found_first_use)::out) is det.

switch_var_first_use_2(_, _, _, [], [], _, [], []).
switch_var_first_use_2(RevGoalPath, CaseNum, StaticInfo, [Case | Cases],
        [Weight | Weights], Cost0, [Cost | Costs],
        [FoundFirstUse | FoundFirstUses]) :-
    switch_var_first_use_2(RevGoalPath, CaseNum + 1, StaticInfo,
        Cases, Weights, Cost0, Costs, FoundFirstUses),
    Case = case_rep(_, _, Goal),
    RevArmPath = rgp_cons(RevGoalPath,
        step_switch(CaseNum, unknown_num_functors_in_type)),
    goal_var_first_use(RevArmPath, Goal, StaticInfo, Cost0, Cost,
        FoundFirstUse),
    Coverage = get_goal_attribute_det(StaticInfo ^ fui_coverage_array,
        Goal ^ goal_annotation),
    get_coverage_before_det(Coverage, BeforeCount),
    Weight = float(BeforeCount).

:- pred ite_var_first_use(reverse_goal_path::in,
    goal_rep(goal_id)::in, goal_rep(goal_id)::in, goal_rep(goal_id)::in,
    var_first_use_static_info::in(var_first_use_static_info),
    float::in, float::out, found_first_use::out)
    is det.

ite_var_first_use(RevGoalPath, Cond, Then, Else, StaticInfo,
        !CostSoFar, FoundFirstUse) :-
    CoverageArray = StaticInfo ^ fui_coverage_array,
    ThenCoverage =
        get_goal_attribute_det(CoverageArray, Then ^ goal_annotation),
    get_coverage_before_det(ThenCoverage, CountBeforeThen),
    ElseCoverage =
        get_goal_attribute_det(CoverageArray, Else ^ goal_annotation),
    get_coverage_before_det(ElseCoverage, CountBeforeElse),
    Weights = [float(CountBeforeThen), float(CountBeforeElse)],
    RevCondGoalPath = rgp_cons(RevGoalPath, step_ite_cond),
    RevThenGoalPath = rgp_cons(RevGoalPath, step_ite_then),
    RevElseGoalPath = rgp_cons(RevGoalPath, step_ite_else),
    VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
    CostBeforeITE = !.CostSoFar,
    goal_var_first_use(RevCondGoalPath, Cond, StaticInfo,
        CostBeforeITE, CostAfterCond, CondFoundFirstUse),
    goal_var_first_use(RevThenGoalPath, Then, StaticInfo,
        CostAfterCond, CostAfterThen, ThenFoundFirstUse),
    goal_var_first_use(RevElseGoalPath, Else, StaticInfo,
        CostAfterCond, CostAfterElse, ElseFoundFirstUse),
    weighted_average(Weights, [CostAfterThen, CostAfterElse], CostAfterITE),
    !:CostSoFar = CostAfterITE,
    (
        CondFoundFirstUse = found_first_use(_),
        FoundFirstUse = CondFoundFirstUse
    ;
        CondFoundFirstUse = have_not_found_first_use,
        ( if
            ThenFoundFirstUse = have_not_found_first_use,
            ElseFoundFirstUse = have_not_found_first_use
        then
            FoundFirstUse = have_not_found_first_use
        else
            (
                VarUseType = var_use_consumption,
                DefaultCost = CostAfterCond
            ;
                ( VarUseType = var_use_production
                ; VarUseType = var_use_other
                ),
                DefaultCost = CostAfterITE
            ),
            ffu_to_float(DefaultCost, ThenFoundFirstUse, ThenVarUseTime),
            ffu_to_float(DefaultCost, ElseFoundFirstUse, ElseVarUseTime),
            weighted_average(Weights, [ThenVarUseTime, ElseVarUseTime],
                VarUseTime),
            FoundFirstUse = found_first_use(VarUseTime),
            trace [compile_time(flag("debug_first_var_use")), io(!IO)] (
                io.output_stream(OutputStream, !IO),
                io.format(OutputStream,
                    "Trace: ITE: Weights: %s, Then: %f, Else: %f, " ++
                    "VarUseTime: %f\n",
                    [s(string(Weights)), f(ThenVarUseTime), f(ElseVarUseTime),
                        f(VarUseTime)],
                    !IO)
            )
        )
    ).

:- pred ffu_to_float(float::in, found_first_use::in, float::out) is det.

ffu_to_float(Default, have_not_found_first_use, Default).
ffu_to_float(_, found_first_use(UseTime), UseTime).

%---------------------------------------------------------------------------%
%
% Goal var first use analysis for the base and recursive cases of a recursive
% procedure.
%

:- type recursive_calls_list ==
    assoc_list(forward_goal_path, float).

:- pred build_recursive_call_sites_list(
    map(reverse_goal_path, cs_cost_csq)::in,
    recursive_calls_list::out) is det.

build_recursive_call_sites_list(Map, List) :-
    List0 = to_assoc_list(Map),
    list.map(
        ( pred((RevGoalPath - Cost)::in, (GoalPath - Calls)::out) is det :-
            Calls = cs_cost_get_calls(Cost),
            rgp_to_fgp(RevGoalPath, GoalPath)
        ), List0, List).

:- pred filter_recursive_call_sites(goal_path_step::in,
    recursive_calls_list::in, recursive_calls_list::out) is det.

filter_recursive_call_sites(GoalPathStep, !RecCallSites) :-
    filter_map((pred((GP0 - Coverage)::in, (GP - Coverage)::out) is semidet :-
            GP0 = fgp_cons(GoalPathStep, GP)
        ), !RecCallSites).

    % rec_goal_var_first_use(Goal, RecCalls, Info, FoundFirstUse, !CostSoFar).
    %
    % Find the first use in both the recursive and base cases.
    %
    % This works under the following assumptions.
    %   + All sub goals succeed at most once.
    %   + The first use is not conjoined with a switch where some switch
    %     branches are recursive and some are not. (or ITE)
    %   + The cost of disjunctions is not computed correctly.
    %
:- pred rec_goal_var_first_use(goal_rep(goal_id)::in,
    recursive_calls_list::in,
    var_first_use_static_info::in(var_first_use_static_info_rec),
    found_first_use::out, float::in, float::out) is det.

rec_goal_var_first_use(Goal, RecCalls, Info, FoundFirstUse,
        !CostSoFar) :-
    Goal = goal_rep(GoalExpr, Detism, GoalId),
    Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, GoalId),
    get_coverage_before_and_after_det(Coverage, Before, After),
    ( if
        % Do not bother exploring this goal if it is never entered.
        % Or never finishes and we are looking for a production.
        (
            Before = 0
        ;
            VarUseType = Info ^ fui_var_use_opts ^ vuo_var_use_type,
            VarUseType = var_use_production,
            ( Detism = erroneous_rep
            ; Detism = failure_rep
            ; After = 0
            )
        )
    then
        FoundFirstUse = have_not_found_first_use
    else
        (
            GoalExpr = conj_rep(Conjs),
            rec_conj_var_first_use(Conjs, 1, RecCalls, Info,
                FoundFirstUse, !CostSoFar)
        ;
            GoalExpr = disj_rep(Disjs),
            rec_disj_var_first_use(Disjs, RecCalls, Info, FoundFirstUse,
                !CostSoFar)
        ;
            GoalExpr = switch_rep(SwitchedOnVar, _CanFail, Cases),
            rec_switch_var_first_use(Cases, SwitchedOnVar, RecCalls,
                Info, FoundFirstUse, !CostSoFar)
        ;
            GoalExpr = ite_rep(Cond, Then, Else),
            rec_ite_var_first_use(Cond, Then, Else,
                RecCalls, Info, FoundFirstUse, !CostSoFar)
        ;
            (
                GoalExpr = negation_rep(SubGoal),
                GoalPathStep = step_neg
            ;
                GoalExpr = scope_rep(SubGoal, ScopeIsCut),
                GoalPathStep = step_scope(ScopeIsCut)
            ),
            filter_recursive_call_sites(GoalPathStep, RecCalls,
                RecCallsSubGoal),
            rec_goal_var_first_use(SubGoal, RecCallsSubGoal,
                Info, FoundFirstUse, !CostSoFar)
        ;
            GoalExpr = atomic_goal_rep(_, _, BoundVars, AtomicGoal),
            (
                ( AtomicGoal = plain_call_rep(_, _, _)
                ; AtomicGoal = higher_order_call_rep(_, _)
                ; AtomicGoal = method_call_rep(_, _, _)
                ),
                ContainingGoalMap = Info ^ fui_containing_goal_map,
                RevGoalPath =
                    goal_id_to_reverse_path(ContainingGoalMap, GoalId),
                call_var_first_use(AtomicGoal, BoundVars, RevGoalPath,
                    Info, FoundFirstUse, !CostSoFar)
            ;
                ( AtomicGoal = unify_construct_rep(_, _, _)
                ; AtomicGoal = unify_deconstruct_rep(_, _, _)
                ; AtomicGoal = partial_construct_rep(_, _, _)
                ; AtomicGoal = partial_deconstruct_rep(_, _, _)
                ; AtomicGoal = unify_assign_rep(_, _)
                ; AtomicGoal = cast_rep(_, _)
                ; AtomicGoal = unify_simple_test_rep(_, _)
                ; AtomicGoal = pragma_foreign_code_rep(_)
                ; AtomicGoal = event_call_rep(_, _)
                ; AtomicGoal = builtin_call_rep(_, _, _)
                ),
                % trivial goals have a zero cost, so !CostSoFar is not updated.
                atomic_trivial_var_first_use(AtomicGoal, BoundVars,
                    !.CostSoFar, Info, FoundFirstUse)
            )
        )
    ),
    trace [compile_time(flag("debug_first_var_use")), io(!IO)] (
        some [ContainingGoalMap, RevGoalPath] (
            ContainingGoalMap = Info ^ fui_containing_goal_map,
            RevGoalPath =
                goal_id_to_reverse_path(ContainingGoalMap, GoalId),
            io.output_stream(OutputStream, !IO),
            io.format(OutputStream, "Trace: goal_var_first_use: %s\n",
                [s(rev_goal_path_to_string(RevGoalPath))], !IO)
        )
    ).

:- pred rec_conj_var_first_use(list(goal_rep(goal_id))::in, int::in,
    recursive_calls_list::in,
    var_first_use_static_info::in(var_first_use_static_info_rec),
    found_first_use::out, float::in, float::out) is det.

rec_conj_var_first_use([], _, _, _, have_not_found_first_use, !CostSoFar).
rec_conj_var_first_use([Conj | Conjs], ConjNum, RecCalls, StaticInfo,
        FoundFirstUse, !CostSoFar) :-
    filter_recursive_call_sites(step_conj(ConjNum), RecCalls, ConjRecCalls),
    rec_goal_var_first_use(Conj, ConjRecCalls, StaticInfo,
        ConjFoundFirstUse, !CostSoFar),
    rec_conj_var_first_use(Conjs, ConjNum + 1, RecCalls, StaticInfo,
        ConjsFoundFirstUse, !CostSoFar),
    (
        % XXX if a variable is bound more than once, because it's used
        % with partial instantiation then we want to use the last time it
        % is bound. Instmaps can be used to track this. This is relevant
        % when searching for the producer of a variable.
        ConjFoundFirstUse = found_first_use(UseTime),
        FoundFirstUse = found_first_use(UseTime)
    ;
        ConjFoundFirstUse = have_not_found_first_use,
        % XXX Use time should be adjusted for the probability of entering
        % Conjs (the success of Conj) But doing so means a weighted average
        % between the success and failure paths, which only makes sense if
        % the consumption (because this is semidet) might be done in the
        % failure case. This also has to be done if the probability of
        % recursion in one of the two cases is different. For now we assume
        % that Conjs will always be entered.
        FoundFirstUse = ConjsFoundFirstUse
    ).

:- pred rec_disj_var_first_use(list(goal_rep(goal_id))::in,
    recursive_calls_list::in,
    var_first_use_static_info::in(var_first_use_static_info_rec),
    found_first_use::out, float::in, float::out) is det.

rec_disj_var_first_use(Disjs, RecCalls, Info, FoundFirstUse, !CostSoFar) :-
    % We do not handle disjunctions, just use a pessimistic default.
    % For calculating the cost of the disjunction, assume that it is a semidet
    % disjunction. Doing this will find the incorrect cost for the
    % disjunction, however disjunctions occur rarely, so this is not likely
    % to dramatically affect anything.
    CostBeforeDisjunction = !.CostSoFar,
    rec_disj_var_first_use_2(Disjs, 1, RecCalls, Info, FoundFirstUse0,
        !CostSoFar),
    CostAfterDisjunction = !.CostSoFar,
    (
        FoundFirstUse0 = found_first_use(_),
        VarUseType = Info ^ fui_var_use_opts ^ vuo_var_use_type,
        (
            VarUseType = var_use_consumption,
            FoundFirstUse = found_first_use(CostBeforeDisjunction)
        ;
            ( VarUseType = var_use_production
            ; VarUseType = var_use_other
            ),
            FoundFirstUse = found_first_use(CostAfterDisjunction)
        )
    ;
        FoundFirstUse0 = have_not_found_first_use,
        FoundFirstUse = FoundFirstUse0
    ).

:- pred rec_disj_var_first_use_2(list(goal_rep(goal_id))::in, int::in,
    recursive_calls_list::in,
    var_first_use_static_info::in(var_first_use_static_info_rec),
    found_first_use::out, float::in, float::out) is det.

rec_disj_var_first_use_2([], _, _, _, have_not_found_first_use, !CostSoFar).
rec_disj_var_first_use_2([Disj | Disjs], DisjNum, RecCalls, Info,
        FoundFirstUse, !CostSoFar) :-
    filter_recursive_call_sites(step_disj(DisjNum), RecCalls, DisjRecCalls),
    rec_goal_var_first_use(Disj, DisjRecCalls, Info, DisjFoundFirstUse,
        !CostSoFar),

    CoverageArray = Info ^ fui_coverage_array,
    Coverage = get_goal_attribute_det(CoverageArray, Disj ^ goal_annotation),
    get_coverage_before_and_after_det(Coverage, Before, After),
    ( if Before = 0 then
        % Avoid a divide by zero.
        CostDisjs = 0.0,
        FoundFirstUse = DisjFoundFirstUse
    else
        rec_disj_var_first_use_2(Disjs, DisjNum + 1, RecCalls, Info,
            DisjsFoundFirstUse, 0.0, CostDisjs0),
        FailureProb = probable(float(Before - After) / float(Before)),
        CostDisjs = CostDisjs0 * probability_to_float(FailureProb),
        (
            DisjFoundFirstUse = have_not_found_first_use,
            FoundFirstUse = DisjsFoundFirstUse
        ;
            DisjFoundFirstUse = found_first_use(UseTime),
            FoundFirstUse = found_first_use(UseTime)
        )
    ),
    !:CostSoFar = !.CostSoFar + CostDisjs.

:- pred rec_switch_var_first_use(list(case_rep(goal_id))::in,
    var_rep::in, recursive_calls_list::in,
    var_first_use_static_info::in(var_first_use_static_info_rec),
    found_first_use::out, float::in, float::out) is det.

rec_switch_var_first_use(Cases, SwitchedOnVar, RecCalls, Info,
        FoundFirstUse, CostBeforeSwitch, CostAfterSwitch) :-
    rec_switch_var_first_use_2(Cases, 1, RecCalls, Info, CostBeforeSwitch,
        CaseWeights0, FoundFirstUseCases, CostAfterCases, RecProbs),
    RecCase = Info ^ fui_rec_info ^ furi_rec_case,
    map_corresponding(adjust_weight_for_recursion(RecCase),
        RecProbs, CaseWeights0, CaseWeights),
    weighted_average(CaseWeights, CostAfterCases, CostAfterSwitch),

    Var = Info ^ fui_var,
    ( if Var = SwitchedOnVar then
        FoundFirstUse = found_first_use(CostBeforeSwitch)
    else
        ( if
            list.all_true(unify(have_not_found_first_use), FoundFirstUseCases)
        then
            % No case contained a first-use of this variable.
            FoundFirstUse = have_not_found_first_use
        else
            VarUseType = Info ^ fui_var_use_opts ^ vuo_var_use_type,
            % XXX this is also flawed, the default costs should not be the
            % average costs, they should be the cost for the specific case
            % where that default would be used.
            % XXX Secondly, this needs to also support the 'don't insert waits
            % on all branches' optimisation.
            (
                VarUseType = var_use_consumption,
                DefaultCost = CostAfterSwitch
            ;
                ( VarUseType = var_use_production
                ; VarUseType = var_use_other
                ),
                DefaultCost = CostBeforeSwitch
            ),
            list.map(ffu_to_float(DefaultCost),
                FoundFirstUseCases, FirstUseTimes),
            weighted_average(CaseWeights, FirstUseTimes, UseTime),
            FoundFirstUse = found_first_use(UseTime)
        )
    ).

:- pred rec_switch_var_first_use_2(list(case_rep(goal_id))::in, int::in,
    recursive_calls_list::in,
    var_first_use_static_info::in(var_first_use_static_info_rec),
    float::in, list(float)::out, list(found_first_use)::out,
    list(float)::out, list(probability)::out) is det.

rec_switch_var_first_use_2([], _, _, _, _, [], [], [], []).
rec_switch_var_first_use_2([Case | Cases], CaseNum, RecCalls, Info,
        CostBeforeSwitch, Weights, FoundFirstUses, CostsAfter, RecProbs) :-
    rec_switch_var_first_use_2(Cases, CaseNum + 1, RecCalls, Info,
        CostBeforeSwitch, CasesWeights, CasesFoundFirstUses, CasesCostsAfter,
        CasesRecProbs),
    Goal = Case ^ cr_case_goal,
    GoalId = Goal ^ goal_annotation,
    filter_recursive_call_sites(
        step_switch(CaseNum, unknown_num_functors_in_type),
        RecCalls, CaseRecCalls),
    rec_goal_var_first_use(Goal, CaseRecCalls, Info, FoundFirstUse,
        CostBeforeSwitch, CaseCostAfter),
    Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, GoalId),
    get_coverage_before_det(Coverage, Before),
    RecProb = get_goal_attribute_det(Info ^ fui_rec_info ^ furi_rec_prob_array,
        GoalId),

    Weight = float(Before),
    Weights = [Weight | CasesWeights],
    FoundFirstUses = [FoundFirstUse | CasesFoundFirstUses],
    CostsAfter = [CaseCostAfter | CasesCostsAfter],
    RecProbs = [RecProb | CasesRecProbs].

:- pred rec_ite_var_first_use(goal_rep(goal_id)::in, goal_rep(goal_id)::in,
    goal_rep(goal_id)::in, recursive_calls_list::in,
    var_first_use_static_info::in(var_first_use_static_info_rec),
    found_first_use::out, float::in, float::out) is det.

rec_ite_var_first_use(Cond, Then, Else, RecCalls, Info, FoundFirstUse,
        !CostSoFar) :-
    filter_recursive_call_sites(step_ite_cond, RecCalls, CondRecCalls),
    filter_recursive_call_sites(step_ite_then, RecCalls, ThenRecCalls),
    filter_recursive_call_sites(step_ite_else, RecCalls, ElseRecCalls),
    rec_goal_var_first_use(Cond, CondRecCalls, Info, CondFoundFirstUse,
        !CostSoFar),
    CostAfterCond = !.CostSoFar,
    rec_goal_var_first_use(Then, ThenRecCalls, Info, ThenFoundFirstUse,
        !.CostSoFar, CostAfterThen),
    rec_goal_var_first_use(Else, ElseRecCalls, Info, ElseFoundFirstUse,
        !.CostSoFar, CostAfterElse),

    % Determine goal weights.
    CoverageArray = Info ^ fui_coverage_array,
    Then = goal_rep(_, _, ThenId),
    Else = goal_rep(_, _, ElseId),
    ThenCoverage = get_goal_attribute_det(CoverageArray, ThenId),
    ElseCoverage = get_goal_attribute_det(CoverageArray, ElseId),
    get_coverage_before_det(ThenCoverage, BeforeThen),
    get_coverage_before_det(ElseCoverage, BeforeElse),
    RecProbArray = Info ^ fui_rec_info ^ furi_rec_prob_array,
    ThenRecProb = get_goal_attribute_det(RecProbArray, ThenId),
    ElseRecProb = get_goal_attribute_det(RecProbArray, ElseId),
    Weights0 = map(float, [BeforeThen, BeforeElse]),
    map_corresponding(
        adjust_weight_for_recursion(Info ^ fui_rec_info ^ furi_rec_case),
        [ThenRecProb, ElseRecProb], Weights0, Weights),

    % Get the weighted average of the costs for !:CostSoFar,
    weighted_average(Weights, [CostAfterThen, CostAfterElse], !:CostSoFar),

    % Determine FoundFirstUse
    (
        CondFoundFirstUse = found_first_use(_),
        FoundFirstUse = CondFoundFirstUse
    ;
        CondFoundFirstUse = have_not_found_first_use,

        ( if
            ThenFoundFirstUse = have_not_found_first_use,
            ElseFoundFirstUse = have_not_found_first_use
        then
            FoundFirstUse = have_not_found_first_use
        else
            VarUseType = Info ^ fui_var_use_opts ^ vuo_var_use_type,
            (
                VarUseType = var_use_consumption,
                Default = !.CostSoFar
            ;
                ( VarUseType = var_use_production
                ; VarUseType = var_use_other
                ),
                Default = CostAfterCond
            ),
            list.map(ffu_to_float(Default),
                [ThenFoundFirstUse, ElseFoundFirstUse], UseTimes),
            weighted_average(Weights, UseTimes, UseTime),
            FoundFirstUse = found_first_use(UseTime)
        )
    ).

:- pred adjust_weight_for_recursion(recursive_case::in,
    probability::in, float::in, float::out) is det.

adjust_weight_for_recursion(RecCase, RecProb, !Weight) :-
    (
        RecCase = recursive_case,
        Prob = RecProb
    ;
        RecCase = base_case,
        Prob = not_probability(RecProb)
    ),
    !:Weight = !.Weight * probability_to_float(Prob).

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

    % Give the probability that this goal leads to a recursion.
    %
    % Note that this does not compute whether this goal is on a recursive path,
    % so it is not sufficient on its own. See rec_goal_var_first_use.
    %
:- pred goal_rec_prob(goal_rep(goal_id)::in, recursive_calls_list::in,
    var_first_use_static_info::in, probability::out,
    goal_attr_array(probability)::gaa_di,
    goal_attr_array(probability)::gaa_uo) is det.

goal_rec_prob(Goal, RecCalls, Info, Prob, !ProbArray) :-
    Goal = goal_rep(GoalExpr, _, GoalId),
    Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, GoalId),
    get_coverage_before_det(Coverage, Before),
    ( if Before = 0 then
        % Avoid a divide by zero and provide a short-cut.
        Prob = impossible
        % There is no need to update the array. The default value is already
        % impossible.
    else
        (
            GoalExpr = conj_rep(Conjs),
            conj_rec_prob(Conjs, 1, RecCalls, Info, Prob, !ProbArray)
        ;
            GoalExpr = disj_rep(Disjs),
            disj_rec_prob(Disjs, 1, RecCalls, Info, Prob, !ProbArray)
        ;
            GoalExpr = switch_rep(_, _, Cases),
            switch_rec_prob(Cases, Before, RecCalls, Info, Prob, !ProbArray)
        ;
            GoalExpr = ite_rep(Cond, Then, Else),
            ite_rec_prob(Cond, Then, Else, RecCalls, Info, Prob, !ProbArray)
        ;
            (
                GoalExpr = negation_rep(SubGoal),
                Step = step_neg
            ;
                GoalExpr = scope_rep(SubGoal, MaybeCut),
                Step = step_scope(MaybeCut)
            ),
            filter_recursive_call_sites(Step, RecCalls, SubGoalRecCalls),
            goal_rec_prob(SubGoal, SubGoalRecCalls, Info, Prob, !ProbArray)
        ;
            GoalExpr = atomic_goal_rep(_, _, _, _),
            (
                RecCalls = [],
                Prob = impossible
            ;
                RecCalls = [_ | _],
                Prob = certain
            )
        ),
        update_goal_attribute(GoalId, Prob, !ProbArray)
    ).

:- pred conj_rec_prob(list(goal_rep(goal_id))::in, int::in,
    recursive_calls_list::in, var_first_use_static_info::in, probability::out,
    goal_attr_array(probability)::gaa_di,
    goal_attr_array(probability)::gaa_uo) is det.

conj_rec_prob([], _, _, _, impossible, !ProbArray).
conj_rec_prob([Conj | Conjs], ConjNum, RecCalls, Info, Prob, !ProbArray) :-
    ConjId = Conj ^ goal_annotation,
    Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, ConjId),
    get_coverage_before_and_after_det(Coverage, Before, After),
    ( if Before = 0 then
        % This code is dead. Return the result without a division
        % to prevent a divide by zero.
        Prob = impossible
    else
        conj_rec_prob(Conjs, ConjNum + 1, RecCalls, Info, ConjsProb0,
            !ProbArray),
        SuccessProb = probable(float(After) / float(Before)),
        ConjsProb = and(SuccessProb, ConjsProb0),

        filter_recursive_call_sites(step_conj(ConjNum),
            RecCalls, ConjRecCalls),
        goal_rec_prob(Conj, ConjRecCalls, Info, ConjProb, !ProbArray),
        Prob = or(ConjProb, ConjsProb)
    ).

:- pred disj_rec_prob(list(goal_rep(goal_id))::in, int::in,
    recursive_calls_list::in, var_first_use_static_info::in, probability::out,
    goal_attr_array(probability)::gaa_di,
    goal_attr_array(probability)::gaa_uo) is det.

disj_rec_prob([], _, _, _, impossible, !ProbArray).
disj_rec_prob([Disj | Disjs], DisjNum, RecCalls, Info, Prob, !ProbArray) :-
    DisjId = Disj ^ goal_annotation,
    Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, DisjId),
    get_coverage_before_and_after_det(Coverage, Before, After),
    ( if Before = 0 then
        % As above, this code is dead.
        Prob = impossible
    else
        disj_rec_prob(Disjs, DisjNum + 1, RecCalls, Info, DisjsProb0,
            !ProbArray),
        % Assume that this disjunction is in a single solution context.
        FailureProb = probable(float(Before - After) / float(Before)),
        DisjsProb = and(FailureProb, DisjsProb0),

        filter_recursive_call_sites(step_disj(DisjNum),
            RecCalls, DisjRecCalls),
        goal_rec_prob(Disj, DisjRecCalls, Info, DisjProb, !ProbArray),
        Prob = or(DisjProb, DisjsProb)
    ).

:- pred switch_rec_prob(list(case_rep(goal_id))::in, int::in,
    recursive_calls_list::in, var_first_use_static_info::in, probability::out,
    goal_attr_array(probability)::gaa_di,
    goal_attr_array(probability)::gaa_uo) is det.

switch_rec_prob(Cases, TotalCalls, RecCalls, Info, Prob, !ProbArray) :-
    switch_rec_prob_2(Cases, 1, TotalCalls, RecCalls, Info, Probs, Weights,
        !ProbArray),
    weighted_average(Weights, map(probability_to_float, Probs), ProbFloat),
    Prob = probable(ProbFloat).

:- pred switch_rec_prob_2(list(case_rep(goal_id))::in, int::in, int::in,
    recursive_calls_list::in, var_first_use_static_info::in,
    list(probability)::out, list(float)::out,
    goal_attr_array(probability)::gaa_di,
    goal_attr_array(probability)::gaa_uo) is det.

switch_rec_prob_2([], _, _, _, _, [], [], !ProbArray).
switch_rec_prob_2([Case | Cases], CaseNum, TotalCalls, RecCalls, Info, Probs,
        Weights, !ProbArray) :-
    switch_rec_prob_2(Cases, CaseNum + 1, TotalCalls, RecCalls, Info,
        Probs0, Weights0, !ProbArray),

    Case = case_rep(_, _, Goal),
    filter_recursive_call_sites(
        step_switch(CaseNum, unknown_num_functors_in_type),
        RecCalls, CaseRecCalls),
    goal_rec_prob(Goal, CaseRecCalls, Info, Prob, !ProbArray),
    Goal = goal_rep(_, _, GoalId),
    Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, GoalId),
    get_coverage_before_det(Coverage, Before),
    Weight = float(Before) / float(TotalCalls),

    Probs = [Prob | Probs0],
    Weights = [Weight | Weights0].

:- pred ite_rec_prob(goal_rep(goal_id)::in, goal_rep(goal_id)::in,
    goal_rep(goal_id)::in, recursive_calls_list::in,
    var_first_use_static_info::in, probability::out,
    goal_attr_array(probability)::gaa_di,
    goal_attr_array(probability)::gaa_uo) is det.

ite_rec_prob(Cond, Then, Else, RecCalls, Info, Prob, !ProbArray) :-
    filter_recursive_call_sites(step_ite_cond, RecCalls, CondRecCalls),
    filter_recursive_call_sites(step_ite_then, RecCalls, ThenRecCalls),
    filter_recursive_call_sites(step_ite_else, RecCalls, ElseRecCalls),
    goal_rec_prob(Cond, CondRecCalls, Info, CondProb, !ProbArray),
    goal_rec_prob(Then, ThenRecCalls, Info, ThenProb0, !ProbArray),
    goal_rec_prob(Else, ElseRecCalls, Info, ElseProb0, !ProbArray),
    CondId = Cond ^ goal_annotation,
    Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, CondId),
    get_coverage_before_and_after_det(Coverage, Before, After),
    ThenCallProb = probable(float(After) / float(Before)),
    ElseCallProb = probable(float(Before - After) / float(Before)),
    ThenProb = and(ThenProb0, ThenCallProb),
    ElseProb = and(ElseProb0, ElseCallProb),
    ThenElseProb = or(ThenProb, ElseProb),
    Prob = or(CondProb, ThenElseProb).

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

:- pred goal_var_first_use_wrapper(clique_ptr::in, set(proc_dynamic_ptr)::in,
    containing_goal_map::in, goal_attr_array(coverage_info)::in,
    map(reverse_goal_path, cost_and_callees)::in,
    map(reverse_goal_path, cs_cost_csq)::in,
    recursion_type::in(recursion_type_known_costs), recursion_depth::in,
    goal_rep(goal_id)::in, float::in, var_rep::in,
    var_use_options::in, var_use_info::out) is det.

goal_var_first_use_wrapper(CliquePtr, CallStack, ContainingGoalMap,
        CoverageArray, CallSiteMap, RecursiveCallSiteMap, RT, CurDepth, Goal,
        ProcCost, Var, VarUseOptions, VarUseInfo) :-
    goal_var_first_use(rgp_nil, Goal,
        var_first_use_static_info(CliquePtr, CallSiteMap, RecursiveCallSiteMap,
            ContainingGoalMap, CoverageArray, Var, VarUseOptions, CallStack,
            RT, CurDepth, no_recursion_info),
        0.0, _Cost, FoundFirstUse),
    VarUseType = VarUseOptions ^ vuo_var_use_type,
    found_first_use_to_use_info(FoundFirstUse, ProcCost, VarUseType,
        VarUseInfo).

var_first_use(CliquePtr, CallSiteMap, RecursiveCallSiteMap, ContainingGoalMap,
        CoverageArray, RT, CurDepth, Goal, RevGoalPath, Cost, Var,
        VarUseOptions, VarUseInfo) :-
    goal_var_first_use(RevGoalPath, Goal,
        var_first_use_static_info(CliquePtr, CallSiteMap, RecursiveCallSiteMap,
            ContainingGoalMap, CoverageArray, Var, VarUseOptions, set.init, RT,
            CurDepth, no_recursion_info),
        0.0, _, FoundFirstUse),
    VarUseType = VarUseOptions ^ vuo_var_use_type,
    found_first_use_to_use_info(FoundFirstUse, Cost, VarUseType, VarUseInfo).

:- pred found_first_use_to_use_info(found_first_use::in, float::in,
    var_use_type::in, var_use_info::out) is det.

found_first_use_to_use_info(FoundFirstUse, Cost, VarUseType, VarUseInfo) :-
    (
        FoundFirstUse = found_first_use(CostUntilUse),
        VarUseInfo = var_use_info(CostUntilUse, Cost, VarUseType)
    ;
        FoundFirstUse = have_not_found_first_use,
        % If the first use has not been found then:
        %  a) for productions: they must have been produced, this is an error.
        %  b) For consumptions: the compiler will insert a wait so any calls
        %     following this one can assume that a wait has already been
        %     performed.
        (
            VarUseType = var_use_production,
            unexpected($pred,
                "goal did not produce a variable that it should have")
        ;
            VarUseType = var_use_consumption,
            VarUseInfo = var_use_info(Cost, Cost, VarUseType)
        ;
            VarUseType = var_use_other,
            pessimistic_var_use_info(VarUseType, Cost, VarUseInfo)
        )
    ).

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

:- pred intermodule_var_use_should_follow_csd(var_use_options::in,
    call_site_dynamic_ptr::in) is semidet.

intermodule_var_use_should_follow_csd(VarUseOptions, CSDPtr) :-
    FollowCall = VarUseOptions ^ vuo_intermodule_var_use,
    (
        FollowCall = follow_calls_into_module(Module),
        Deep = VarUseOptions ^ vuo_deep,
        deep_lookup_call_site_dynamics(Deep, CSDPtr, CSD),
        PDPtr = CSD ^ csd_callee,
        intermodule_var_use_should_follow_pd_2(Deep, Module, PDPtr)
    ;
        FollowCall = follow_any_call
    ).

:- pred intermodule_var_use_should_follow_pd(var_use_options::in,
    proc_dynamic_ptr::in) is semidet.
:- pragma consider_used(pred(intermodule_var_use_should_follow_pd/2)).

intermodule_var_use_should_follow_pd(VarUseOptions, PDPtr) :-
    FollowCall = VarUseOptions ^ vuo_intermodule_var_use,
    (
        FollowCall = follow_calls_into_module(Module),
        Deep = VarUseOptions ^ vuo_deep,
        intermodule_var_use_should_follow_pd_2(Deep, Module, PDPtr)
    ;
        FollowCall = follow_any_call
    ).

:- pred intermodule_var_use_should_follow_pd_2(deep::in, string::in,
    proc_dynamic_ptr::in) is semidet.

intermodule_var_use_should_follow_pd_2(Deep, Module, PDPtr) :-
    deep_lookup_proc_dynamics(Deep, PDPtr, PD),
    PSPtr = PD ^ pd_proc_static,
    deep_lookup_proc_statics(Deep, PSPtr, PS),
    Label = PS ^ ps_id,
    ( Label = str_ordinary_proc_label(_, _, Module, _, _, _)
    ; Label = str_special_proc_label(_, _, Module, _, _, _)
    ).

%---------------------------------------------------------------------------%
:- end_module var_use_analysis.
%---------------------------------------------------------------------------%
