%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1994-2012 The University of Melbourne.
% Copyright (C) 2013-2015 2017-2020, 2022, 2024 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: middle_rec.m.
% Main authors: zs, conway.
%
% Code generation - do middle recursion optimization.
%
%---------------------------------------------------------------------------%

:- module ll_backend.middle_rec.
:- interface.

:- import_module hlds.
:- import_module hlds.hlds_goal.
:- import_module ll_backend.code_info.
:- import_module ll_backend.code_loc_dep.
:- import_module ll_backend.llds.

:- pred match_and_generate(hlds_goal::in, llds_code::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out)
    is semidet.

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

:- implementation.

:- import_module backend_libs.
:- import_module backend_libs.builtin_ops.
:- import_module hlds.code_model.
:- import_module hlds.goal_form.
:- import_module hlds.hlds_llds.
:- import_module ll_backend.code_gen.
:- import_module ll_backend.code_util.
:- import_module ll_backend.opt_util.
:- import_module ll_backend.proc_gen.
:- import_module ll_backend.unify_gen_test.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.var_table.

:- import_module assoc_list.
:- import_module bool.
:- import_module cord.
:- import_module int.
:- import_module list.
:- import_module maybe.
:- import_module require.
:- import_module set.

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

match_and_generate(Goal, Instrs, !CI, !CLD) :-
    Goal = hlds_goal(GoalExpr, GoalInfo),
    GoalExpr = switch(Var, cannot_fail, [Case1, Case2]),
    Case1 = case(ConsId1, [], Goal1),
    Case2 = case(ConsId2, [], Goal2),
    ( if
        contains_only_builtins(Goal1) = yes,
        contains_simple_recursive_call(Goal2, !.CI)
    then
        middle_rec_generate_switch(Var, ConsId1, Goal1, Goal2,
            GoalInfo, Instrs, !CI, !CLD)
    else if
        contains_only_builtins(Goal2) = yes,
        contains_simple_recursive_call(Goal1, !.CI)
    then
        middle_rec_generate_switch(Var, ConsId2, Goal2, Goal1,
            GoalInfo, Instrs, !CI, !CLD)
    else
        fail
    ).

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

    % contains_simple_recursive_call(G, CI, Last, ContainsTakeAddr)
    % succeeds if G is a conjunction of goals, exactly one of which is a
    % recursive call (CI says what the current procedure is), there are no
    % other goals that cause control to leave this procedure, and there are
    % no unifications that take the addresses of fields.
    %
:- pred contains_simple_recursive_call(hlds_goal::in, code_info::in)
    is semidet.

contains_simple_recursive_call(hlds_goal(GoalExpr, _), CodeInfo) :-
    GoalExpr = conj(plain_conj, Goals),
    contains_simple_recursive_call_conj(Goals, CodeInfo).

:- pred contains_simple_recursive_call_conj(list(hlds_goal)::in, code_info::in)
    is semidet.

contains_simple_recursive_call_conj([Goal | Goals], CodeInfo) :-
    Goal = hlds_goal(GoalExpr, _),
    OnlyBuiltinsGoalExpr = contains_only_builtins_expr(GoalExpr),
    (
        OnlyBuiltinsGoalExpr = yes,
        contains_simple_recursive_call_conj(Goals, CodeInfo)
    ;
        OnlyBuiltinsGoalExpr = no,
        is_recursive_call(GoalExpr, CodeInfo),
        contains_only_builtins_list(Goals) = yes
    ).

:- pred is_recursive_call(hlds_goal_expr::in, code_info::in) is semidet.

is_recursive_call(Goal, CodeInfo) :-
    Goal = plain_call(CallPredId, CallProcId, _, BuiltinState, _, _),
    BuiltinState = not_builtin,
    get_pred_id(CodeInfo, PredId),
    PredId = CallPredId,
    get_proc_id(CodeInfo, ProcId),
    ProcId = CallProcId.

    % contains_only_builtins(G) returns `yes' if G is a leaf procedure,
    % i.e. control does not leave G to call another procedure, even
    % if that procedure is a complicated unification. It also does not contain
    % unifications that take the addresses of fields.
    %
:- func contains_only_builtins(hlds_goal) = bool.

contains_only_builtins(hlds_goal(GoalExpr, _)) =
    contains_only_builtins_expr(GoalExpr).

:- func contains_only_builtins_expr(hlds_goal_expr) = bool.

contains_only_builtins_expr(GoalExpr) = OnlyBuiltins :-
    (
        GoalExpr = conj(ConjType, Goals),
        (
            ConjType = plain_conj,
            OnlyBuiltins = contains_only_builtins_list(Goals)
        ;
            ConjType = parallel_conj,
            OnlyBuiltins = no
        )
    ;
        GoalExpr = disj(Goals),
        OnlyBuiltins = contains_only_builtins_list(Goals)
    ;
        GoalExpr = switch(_Var, _CanFail, Cases),
        OnlyBuiltins = contains_only_builtins_cases(Cases)
    ;
        GoalExpr = negation(SubGoal),
        OnlyBuiltins = contains_only_builtins(SubGoal)
    ;
        GoalExpr = scope(Reason, SubGoal),
        ( if
            Reason = from_ground_term(_, FGT),
            ( FGT = from_ground_term_construct
            ; FGT = from_ground_term_deconstruct
            )
        then
            OnlyBuiltins = yes
        else
            OnlyBuiltins = contains_only_builtins(SubGoal)
        )
    ;
        GoalExpr = if_then_else(_Vars, Cond, Then, Else),
        ( if
            contains_only_builtins(Cond) = yes,
            contains_only_builtins(Then) = yes,
            contains_only_builtins(Else) = yes
        then
            OnlyBuiltins = yes
        else
            OnlyBuiltins = no
        )
    ;
        GoalExpr = plain_call(_, _, _, BuiltinState, _, _),
        (
            BuiltinState = inline_builtin,
            OnlyBuiltins = yes
        ;
            BuiltinState = not_builtin,
            OnlyBuiltins = no
        )
    ;
        GoalExpr = unify(_, _, _, Uni, _),
        % Complicated unifies are _non_builtin_
        (
            Uni = assign(_, _),
            OnlyBuiltins = yes
        ;
            Uni = simple_test(_, _),
            OnlyBuiltins = yes
        ;
            Uni = construct(_, _, _, _, _, _, SubInfo),
            (
                SubInfo = no_construct_sub_info,
                OnlyBuiltins = yes
            ;
                SubInfo = construct_sub_info(TakeAddressFields, _),
                (
                    TakeAddressFields = no,
                    OnlyBuiltins = yes
                ;
                    TakeAddressFields = yes(_),
                    OnlyBuiltins = no
                )
            )
        ;
            Uni = deconstruct(_, _, _, _, _, _),
            OnlyBuiltins = yes
        ;
            Uni = complicated_unify(_, _, _),
            OnlyBuiltins = no
        )
    ;
        ( GoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
        ; GoalExpr = generic_call(_, _, _, _, _)
        ),
        OnlyBuiltins = no
    ;
        GoalExpr = shorthand(_),
        unexpected($pred, "shorthand")
    ).

:- func contains_only_builtins_cases(list(case)) = bool.

contains_only_builtins_cases([]) = yes.
contains_only_builtins_cases([case(_, _, Goal) | Cases]) = OnlyBuiltins :-
    OnlyBuiltinsGoal = contains_only_builtins(Goal),
    (
        OnlyBuiltinsGoal = yes,
        OnlyBuiltins = contains_only_builtins_cases(Cases)
    ;
        OnlyBuiltinsGoal = no,
        OnlyBuiltins = no
    ).

:- func contains_only_builtins_list(list(hlds_goal)) = bool.

contains_only_builtins_list([]) = yes.
contains_only_builtins_list([Goal | Goals]) = OnlyBuiltins :-
    OnlyBuiltinsGoal = contains_only_builtins(Goal),
    (
        OnlyBuiltinsGoal = yes,
        OnlyBuiltins = contains_only_builtins_list(Goals)
    ;
        OnlyBuiltinsGoal = no,
        OnlyBuiltins = no
    ).

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

:- pred middle_rec_generate_switch(prog_var::in, cons_id::in,
    hlds_goal::in, hlds_goal::in, hlds_goal_info::in, llds_code::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out)
    is semidet.

middle_rec_generate_switch(Var, BaseConsId, Base, Recursive, SwitchGoalInfo,
        Code, !CI, !CLD) :-
    get_stack_slots(!.CI, StackSlots),
    get_var_table(!.CI, VarTable),
    SlotsComment = explain_stack_slots(VarTable, StackSlots),
    get_module_info(!.CI, ModuleInfo),
    get_pred_id(!.CI, PredId),
    get_proc_id(!.CI, ProcId),
    EntryLabel = make_local_entry_label(ModuleInfo, PredId, ProcId,
        for_from_everywhere),

    pre_goal_update(SwitchGoalInfo, has_subgoals, !CLD),
    produce_variable(Var, VarCode, VarRval, !CLD),
    lookup_var_entry(VarTable, Var, VarEntry),
    VarName = var_entry_name(Var, VarEntry),
    VarType = VarEntry ^ vte_type,
    CheaperTagTest = lookup_cheaper_tag_test(!.CI, VarType),
    generate_test_var_has_cons_id(VarRval, VarName, BaseConsId,
        CheaperTagTest, branch_on_success, BaseLabel, TestCode, !CI),
    EntryTestInstrs = cord.list(VarCode ++ TestCode),

    goal_info_get_store_map(SwitchGoalInfo, StoreMap),
    remember_position(!.CLD, BranchStart),
    generate_goal(model_det, Base, BaseGoalCode, !CI, !CLD),
    generate_branch_end(StoreMap, no, MaybeEnd1, BaseSaveCode, !.CLD),
    reset_to_position(BranchStart, !.CI, !:CLD),
    generate_goal(model_det, Recursive, RecGoalCode, !CI, !CLD),
    generate_branch_end(StoreMap, MaybeEnd1, MaybeEnd, RecSaveCode, !.CLD),

    after_all_branches(StoreMap, MaybeEnd, !.CI, !:CLD),
    post_goal_update(SwitchGoalInfo, !.CI, !CLD),

    ArgModes = get_arginfo(!.CI),
    HeadVars = get_headvars(!.CI),
    assoc_list.from_corresponding_lists(HeadVars, ArgModes, Args),
    setup_return(Args, LiveArgs, EpilogCode, !CLD),

    BaseCode = BaseGoalCode ++ BaseSaveCode ++ EpilogCode,
    RecCode = RecGoalCode ++ RecSaveCode ++ EpilogCode,
    LiveValCode = singleton(
        llds_instr(livevals(LiveArgs), "")
    ),

    BaseInstrs = cord.list(BaseCode),
    RecInstrs = cord.list(RecCode),

    % In the code we generate, the base instruction sequence is executed
    % in situations where this procedure has no stack frame. If this
    % sequence refers to the stack frame, it will be to some other procedure's
    % variables, which is obviously incorrect.
    opt_util.block_refers_to_stack(BaseInstrs) = no,

    AvoidInstrs = BaseInstrs ++ RecInstrs,
    find_unused_register(AvoidInstrs, AuxReg),

    split_rec_code(RecInstrs, BeforeInstrs0, AfterInstrs),
    add_counter_to_livevals(BeforeInstrs0, AuxReg, BeforeInstrs),

    get_next_label(Loop1Label, !CI),
    get_next_label(Loop2Label, !CI),
    get_total_stackslot_count(!.CI, FrameSize0),
    FrameSize = round_det_stack_frame_size(!.CI, FrameSize0),

    generate_downloop_test(EntryTestInstrs, Loop1Label, Loop1Test),

    ( if FrameSize = 0 then
        MaybeIncrSp = empty,
        MaybeDecrSp = empty,
        InitAuxReg = singleton(
            llds_instr(assign(AuxReg, const(llconst_int(0))),
                "initialize counter register")
        ),
        IncrAuxReg = singleton(
            llds_instr(
                assign(AuxReg,
                    binop(int_arith(int_type_int, ao_add), lval(AuxReg),
                        const(llconst_int(1)))),
                "increment loop counter")
        ),
        DecrAuxReg = singleton(
            llds_instr(
                assign(AuxReg,
                    binop(int_arith(int_type_int, ao_sub), lval(AuxReg),
                        const(llconst_int(1)))),
                "decrement loop counter")
        ),
        TestAuxReg = singleton(
            llds_instr(
                if_val(binop(
                    int_cmp(int_type_int, gt),
                        lval(AuxReg), const(llconst_int(0))),
                    code_label(Loop2Label)),
                "test on upward loop")
        )
    else
        PushMsg = proc_gen.push_msg(ModuleInfo, PredId, ProcId),
        MaybeIncrSp = singleton(
            llds_instr(incr_sp(FrameSize, PushMsg, stack_incr_nonleaf), "")
        ),
        MaybeDecrSp = singleton(
            llds_instr(decr_sp(FrameSize), "")
        ),
        InitAuxReg =  singleton(
            llds_instr(assign(AuxReg, lval(sp)), "initialize counter register")
        ),
        IncrAuxReg = empty,
        DecrAuxReg = empty,
        TestAuxReg = singleton(
            llds_instr(if_val(binop(
                int_cmp(int_type_int, gt), lval(sp), lval(AuxReg)),
                code_label(Loop2Label)),
                "test on upward loop")
        )
    ),

    % Even though the recursive call is followed by some goals in the HLDS,
    % these goals may generate no LLDS code, so it is in fact possible for
    % AfterInstrs to be empty. There is no point in testing BeforeInstrs
    % for empty, since if it is, the code is an infinite loop anyway.

    (
        AfterInstrs = [],
        Code =
            from_list([
                llds_instr(label(EntryLabel), "Procedure entry point"),
                llds_instr(comment(SlotsComment), "")
            ]) ++
            from_list(EntryTestInstrs) ++
            singleton(
                llds_instr(label(Loop1Label), "start of the down loop")
            ) ++
            from_list(BeforeInstrs) ++
            from_list(Loop1Test) ++
            singleton(
                llds_instr(label(BaseLabel), "start of base case")
            ) ++
            from_list(BaseInstrs) ++
            LiveValCode ++
            singleton(
                llds_instr(goto(code_succip), "exit from base case")
            )
    ;
        AfterInstrs = [_ | _],
        % The instruction list we are constructing has two copies of BaseList.
        % If this list of instructions defines any labels, we must either not
        % apply this version of the optimization, or we must consistently
        % substitute the labels (which will be referred to only from within the
        % BaseList instructions themselves). We choose the former course.
        find_labels(BaseInstrs, BaseLabels),
        BaseLabels = [],
        Code =
            from_list([
                llds_instr(label(EntryLabel), "Procedure entry point"),
                llds_instr(comment(SlotsComment), "")
            ]) ++
            from_list(EntryTestInstrs) ++
            InitAuxReg ++
            singleton(
                llds_instr(label(Loop1Label), "start of the down loop")
            ) ++
            MaybeIncrSp ++
            IncrAuxReg ++
            from_list(BeforeInstrs) ++
            from_list(Loop1Test) ++
            from_list(BaseInstrs) ++
            singleton(
                llds_instr(label(Loop2Label), "")
            ) ++
            from_list(AfterInstrs) ++
            MaybeDecrSp ++
            DecrAuxReg ++
            TestAuxReg ++
            LiveValCode ++
            from_list([
                llds_instr(goto(code_succip), "exit from recursive case"),
                llds_instr(label(BaseLabel), "start of base case")
            ]) ++
            from_list(BaseInstrs) ++
            LiveValCode ++
            singleton(
                llds_instr(goto(code_succip), "exit from base case")
            )
    ).

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

:- pred generate_downloop_test(list(instruction)::in, label::in,
    list(instruction)::out) is det.

generate_downloop_test([], _, _) :-
    unexpected($pred, "empty list").
generate_downloop_test([Instr0 | Instrs0], Target, Instrs) :-
    ( if Instr0 = llds_instr(if_val(Test, _OldTarget), _Comment) then
        (
            Instrs0 = []
        ;
            Instrs0 = [_ | _],
            unexpected($pred, "if_val followed by other instructions")
        ),
        code_util.negate_rval(Test, NegTest),
        Instrs = [
            llds_instr(if_val(NegTest, code_label(Target)),
                "test on downward loop")
        ]
    else
        generate_downloop_test(Instrs0, Target, Instrs1),
        Instrs = [Instr0 | Instrs1]
    ).

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

:- pred split_rec_code(list(instruction)::in,
    list(instruction)::out, list(instruction)::out) is det.

split_rec_code([], _, _) :-
    unexpected($pred, "did not find call").
split_rec_code([Instr0 | Instrs1], Before, After) :-
    ( if Instr0 = llds_instr(llcall(_, _, _, _, _, _), _) then
        ( if
            opt_util.skip_comments(Instrs1, Instrs2),
            Instrs2 = [Instr2 | Instrs3],
            Instr2 = llds_instr(label(_), _)
        then
            Before = [],
            After = Instrs3
        else
            unexpected($pred, "call not followed by label")
        )
    else
        split_rec_code(Instrs1, Before1, After),
        Before = [Instr0 | Before1]
    ).

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

:- pred add_counter_to_livevals(list(instruction)::in, lval::in,
    list(instruction)::out) is det.

add_counter_to_livevals([], _Lval, []).
add_counter_to_livevals([Instr0 | Instrs0], Lval, [Instr | Instrs]) :-
    ( if Instr0 = llds_instr(livevals(Lives0), Comment) then
        set.insert(Lval, Lives0, Lives),
        Instr = llds_instr(livevals(Lives), Comment)
    else
        Instr = Instr0
    ),
    add_counter_to_livevals(Instrs0, Lval, Instrs).

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

:- pred find_unused_register(list(instruction)::in, lval::out)
    is det.

find_unused_register(Instrs, UnusedReg) :-
    set.init(Used0),
    find_used_registers(Instrs, Used0, Used1),
    set.to_sorted_list(Used1, UsedList),
    find_unused_register_acc(UsedList, 1, UnusedReg).

:- pred find_unused_register_acc(list(int)::in, int::in, lval::out) is det.

find_unused_register_acc([], N, reg(reg_r, N)).
find_unused_register_acc([H | T], N, Reg) :-
    ( if N < H then
        Reg = reg(reg_r, N)
    else
        N1 = N + 1,
        find_unused_register_acc(T, N1, Reg)
    ).

:- pred find_used_registers(list(instruction)::in,
    set(int)::in, set(int)::out) is det.

find_used_registers([], !Used).
find_used_registers([llds_instr(Uinstr, _) | Instrs], !Used) :-
    find_used_registers_instr(Uinstr, !Used),
    find_used_registers(Instrs, !Used).

:- pred find_used_registers_instr(instr::in,
    set(int)::in, set(int)::out) is det.

find_used_registers_instr(Uinstr, !Used) :-
    (
        ( Uinstr = comment(_)
        ; Uinstr = llcall(_, _, _, _, _, _)
        ; Uinstr = mkframe(_, _)
        ; Uinstr = label(_)
        ; Uinstr = goto(_)
        ; Uinstr = arbitrary_c_code(_, _, _)
        ; Uinstr = push_region_frame(_Id, _EmbeddedStackFrame)
        ; Uinstr = use_and_maybe_pop_region_frame(_UseOp, _EmbeddedStackFrame)
        ; Uinstr = discard_ticket
        ; Uinstr = prune_ticket
        ; Uinstr = incr_sp(_, _, _)
        ; Uinstr = decr_sp(_)
        ; Uinstr = decr_sp_and_return(_)
        )
    ;
        Uinstr = livevals(LvalSet),
        set.to_sorted_list(LvalSet, LvalList),
        find_used_registers_lvals(LvalList, !Used)
    ;
        Uinstr = block(_, _, Instrs),
        find_used_registers(Instrs, !Used)
    ;
        ( Uinstr = assign(Lval, Rval)
        ; Uinstr = keep_assign(Lval, Rval)
        ),
        find_used_registers_lval(Lval, !Used),
        find_used_registers_rval(Rval, !Used)
    ;
        Uinstr = incr_hp(Lval, _, _, Rval, _, _, MaybeRegionRval, MaybeReuse),
        find_used_registers_lval(Lval, !Used),
        find_used_registers_rval(Rval, !Used),
        (
            MaybeRegionRval = yes(RegionRval),
            find_used_registers_rval(RegionRval, !Used)
        ;
            MaybeRegionRval = no
        ),
        (
            MaybeReuse = llds_reuse(ReuseRval, MaybeFlagLval),
            find_used_registers_rval(ReuseRval, !Used),
            (
                MaybeFlagLval = yes(FlagLval),
                find_used_registers_lval(FlagLval, !Used)
            ;
                MaybeFlagLval = no
            )
        ;
            MaybeReuse = no_llds_reuse
        )
    ;
        Uinstr = region_fill_frame(_FillOp, _EmbeddedStackFrame,
            IdRval, NumLval, AddrLval),
        find_used_registers_rval(IdRval, !Used),
        find_used_registers_lval(NumLval, !Used),
        find_used_registers_lval(AddrLval, !Used)
    ;
        Uinstr = region_set_fixed_slot(_SetOp, _EmbeddedStackFrame, ValueRval),
        find_used_registers_rval(ValueRval, !Used)
    ;
        Uinstr = foreign_proc_code(_, Components, _, _, _, _, _, _, _, _),
        find_used_registers_components(Components, !Used)
    ;
        ( Uinstr = computed_goto(Rval, _, _)
        ; Uinstr = if_val(Rval, _)
        ; Uinstr = restore_hp(Rval)
        ; Uinstr = free_heap(Rval)
        ; Uinstr = reset_ticket(Rval, _Rsn)
        ; Uinstr = prune_tickets_to(Rval)
        ),
        find_used_registers_rval(Rval, !Used)
    ;
        ( Uinstr = save_maxfr(Lval)
        ; Uinstr = restore_maxfr(Lval)
        ; Uinstr = mark_hp(Lval)
        ; Uinstr = store_ticket(Lval)
        ; Uinstr = mark_ticket_stack(Lval)
        ; Uinstr = init_sync_term(Lval, _, _)
        ; Uinstr = fork_new_child(Lval, _)
        ; Uinstr = join_and_continue(Lval, _)
        ),
        find_used_registers_lval(Lval, !Used)
    ;
        Uinstr = lc_create_loop_control(_, LCLval),
        find_used_registers_lval(LCLval, !Used)
    ;
        Uinstr = lc_wait_free_slot(LCRval, LCSLval, _),
        find_used_registers_rval(LCRval, !Used),
        find_used_registers_lval(LCSLval, !Used)
    ;
        Uinstr = lc_spawn_off(LCRval, LCSRval, _),
        find_used_registers_rval(LCRval, !Used),
        find_used_registers_rval(LCSRval, !Used)
    ;
        Uinstr = lc_join_and_terminate(LCRval, LCSRval),
        find_used_registers_rval(LCRval, !Used),
        find_used_registers_rval(LCSRval, !Used)
    ).

:- pred find_used_registers_components(
    list(foreign_proc_component)::in,
    set(int)::in, set(int)::out) is det.

find_used_registers_components([], !Used).
find_used_registers_components([Comp | Comps], !Used) :-
    find_used_registers_component(Comp, !Used),
    find_used_registers_components(Comps, !Used).

:- pred find_used_registers_component(foreign_proc_component::in,
    set(int)::in, set(int)::out) is det.

find_used_registers_component(foreign_proc_inputs(In), !Used) :-
    insert_foreign_proc_input_registers(In, !Used).
find_used_registers_component(foreign_proc_outputs(Out), !Used) :-
    insert_foreign_proc_output_registers(Out, !Used).
find_used_registers_component(foreign_proc_user_code(_, _, _), !Used).
find_used_registers_component(foreign_proc_raw_code(_, _, _, _), !Used).
find_used_registers_component(foreign_proc_fail_to(_), !Used).
find_used_registers_component(foreign_proc_alloc_id(_), !Used).
find_used_registers_component(foreign_proc_noop, !Used).

:- pred find_used_registers_lvals(list(lval)::in,
    set(int)::in, set(int)::out) is det.

find_used_registers_lvals([], !Used).
find_used_registers_lvals([Lval | Lvals], !Used) :-
    find_used_registers_lval(Lval, !Used),
    find_used_registers_lvals(Lvals, !Used).

:- pred find_used_registers_lval(lval::in,
    set(int)::in, set(int)::out) is det.

find_used_registers_lval(Lval, !Used) :-
    ( if Lval = reg(reg_r, N) then
        copy(N, N1),
        set.insert(N1, !Used)
    else if Lval = field(_, Rval, FieldNum) then
        find_used_registers_rval(Rval, !Used),
        find_used_registers_rval(FieldNum, !Used)
    else if Lval = lvar(_) then
        unexpected($pred, "lvar")
    else
        true
    ).

:- pred find_used_registers_rval(rval::in, set(int)::in, set(int)::out) is det.

find_used_registers_rval(Rval, !Used) :-
    (
        Rval = lval(Lval),
        find_used_registers_lval(Lval, !Used)
    ;
        Rval = var(_),
        unexpected($pred, "var")
    ;
        Rval = mkword_hole(_)
    ;
        Rval = const(_)
    ;
        ( Rval = mkword(_, SubRval)
        ; Rval = cast(_, SubRval)
        ; Rval = unop(_, SubRval)
        ),
        find_used_registers_rval(SubRval, !Used)
    ;
        Rval = binop(_, SubRvalA, SubRvalB),
        find_used_registers_rval(SubRvalA, !Used),
        find_used_registers_rval(SubRvalB, !Used)
    ;
        Rval = mem_addr(MemRef),
        find_used_registers_mem_ref(MemRef, !Used)
    ).

:- pred find_used_registers_mem_ref(mem_ref::in,
    set(int)::in, set(int)::out) is det.

find_used_registers_mem_ref(stackvar_ref(Rval), !Used) :-
    find_used_registers_rval(Rval, !Used).
find_used_registers_mem_ref(framevar_ref(Rval), !Used) :-
    find_used_registers_rval(Rval, !Used).
find_used_registers_mem_ref(heap_ref(Rval1, _, Rval2), !Used) :-
    find_used_registers_rval(Rval1, !Used),
    find_used_registers_rval(Rval2, !Used).

:- pred insert_foreign_proc_input_registers(list(foreign_proc_input)::in,
    set(int)::in, set(int)::out) is det.

insert_foreign_proc_input_registers([], !Used).
insert_foreign_proc_input_registers([Input | Inputs], !Used) :-
    Input = foreign_proc_input(_, _, _, _, Rval, _, _),
    find_used_registers_rval(Rval, !Used),
    insert_foreign_proc_input_registers(Inputs, !Used).

:- pred insert_foreign_proc_output_registers(list(foreign_proc_output)::in,
    set(int)::in, set(int)::out) is det.

insert_foreign_proc_output_registers([], !Used).
insert_foreign_proc_output_registers([Output | Outputs], !Used) :-
    Output = foreign_proc_output(Lval, _, _, _, _, _, _),
    find_used_registers_lval(Lval, !Used),
    insert_foreign_proc_output_registers(Outputs, !Used).

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

    % Find all the labels defined in an instruction sequence.
    %
:- pred find_labels(list(instruction)::in, list(label)::out) is det.

find_labels(Instrs, Labels) :-
    find_labels_acc(Instrs, [], Labels).

:- pred find_labels_acc(list(instruction)::in,
    list(label)::in, list(label)::out) is det.

find_labels_acc([], !Labels).
find_labels_acc([Instr | Instrs], !Labels) :-
    Instr = llds_instr(Uinstr, _),
    ( if Uinstr = label(Label) then
        !:Labels = [Label | !.Labels]
    else if Uinstr = block(_, _, Block) then
        find_labels_acc(Block, !Labels)
    else
        true
    ),
    find_labels_acc(Instrs, !Labels).

%---------------------------------------------------------------------------%
:- end_module ll_backend.middle_rec.
%---------------------------------------------------------------------------%

