%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1994-2012 The University of Melbourne.
% Copyright (C) 2013-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: call_gen.m.
% Authors: conway, zs.
%
% This module provides predicates for generating procedure calls,
% including calls to higher-order pred variables.
%
%---------------------------------------------------------------------------%

:- module ll_backend.call_gen.
:- interface.

:- import_module hlds.
:- import_module hlds.code_model.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_pred.
:- import_module libs.
:- import_module libs.globals.
:- import_module ll_backend.code_info.
:- import_module ll_backend.code_loc_dep.
:- import_module ll_backend.llds.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.

:- import_module assoc_list.
:- import_module list.

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

:- pred generate_call(code_model::in, pred_id::in, proc_id::in,
    list(prog_var)::in, hlds_goal_info::in, llds_code::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.

:- pred generate_generic_call(code_model::in, generic_call::in,
    list(prog_var)::in, list(mer_mode)::in, arg_reg_type_info::in,
    determinism::in, hlds_goal_info::in, llds_code::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.

:- type known_call_variant
    --->    ho_call_known_num
    ;       ho_call_unknown.

    % generic_call_info(Globals, GenericCall, NumImmediateInputArgsR,
    %   NumImmediateInputArgsF, CodeAddr, SpecifierArgInfos,
    %   FirstImmediateInputReg, HoCallVariant).
    %
:- pred generic_call_info(globals::in, generic_call::in, int::in, int::in,
    code_addr::out, assoc_list(prog_var, arg_info)::out, int::out,
    known_call_variant::out) is det.

:- pred generate_builtin(code_model::in, pred_id::in, proc_id::in,
    list(prog_var)::in, hlds_goal_info::in, llds_code::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.

:- pred input_arg_locs(assoc_list(prog_var, arg_info)::in,
    assoc_list(prog_var, arg_loc)::out) is det.

:- pred output_arg_locs(assoc_list(prog_var, arg_info)::in,
    assoc_list(prog_var, arg_loc)::out) is det.

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

:- implementation.

:- import_module backend_libs.
:- import_module backend_libs.builtin_ops.
:- import_module hlds.arg_info.
:- import_module hlds.hlds_class.
:- import_module hlds.hlds_llds.
:- import_module hlds.hlds_module.
:- import_module hlds.instmap.
:- import_module libs.options.
:- import_module ll_backend.code_util.
:- import_module ll_backend.continuation_info.
:- import_module ll_backend.trace_gen.
:- import_module mdbcomp.
:- import_module mdbcomp.goal_path.
:- import_module parse_tree.prog_data_event.
:- import_module parse_tree.prog_event.
:- import_module parse_tree.prog_type.
:- import_module parse_tree.set_of_var.
:- import_module parse_tree.var_table.

:- import_module bool.
:- import_module cord.
:- import_module int.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module set.
:- import_module string.

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

generate_call(CodeModel, PredId, ProcId, ArgVars, GoalInfo, Code, !CI, !CLD) :-
    % Find out which arguments are input and which are output.
    ArgInfo = get_pred_proc_arginfo(!.CI, PredId, ProcId),
    assoc_list.from_corresponding_lists(ArgVars, ArgInfo, ArgsInfos),

    % Save the necessary vars on the stack and move the input args
    % to their registers.
    setup_call(GoalInfo, ArgsInfos, LiveVals, SetupCode, !.CI, !CLD),
    kill_dead_input_vars(ArgsInfos, GoalInfo, NonLiveOutputs, !CLD),

    % Figure out what the call model is.
    prepare_for_call(CodeModel, GoalInfo, CallModel, TraceResetCode,
        !CI, !CLD),

    % Make the call. Note that the construction of CallCode will be moved
    % *after* the code that computes ReturnLiveLvalues.
    get_module_info(!.CI, ModuleInfo),
    Address = make_proc_entry_label(!.CI, ModuleInfo, PredId, ProcId,
        for_immediate_call),
    get_next_label(ReturnLabel, !CI),
    call_gen.call_comment(!.CI, PredId, CodeModel, CallComment),
    Context = goal_info_get_context(GoalInfo),
    GoalId = goal_info_get_goal_id(GoalInfo),
    get_maybe_containing_goal_map(!.CI, MaybeContainingGoalMap),
    (
        MaybeContainingGoalMap = yes(ContainingGoalMap),
        GoalPath = goal_id_to_forward_path(ContainingGoalMap, GoalId),
        MaybeGoalPath = yes(GoalPath)
    ;
        MaybeContainingGoalMap = no,
        MaybeGoalPath = no
    ),
    CallCode = from_list([
        llds_instr(livevals(LiveVals), ""),
        llds_instr(llcall(Address, code_label(ReturnLabel), ReturnLiveLvalues,
            Context, MaybeGoalPath, CallModel), CallComment),
        llds_instr(label(ReturnLabel), "continuation label")
    ]),

    % Figure out what variables will be live at the return point, and where,
    % for use in the accurate garbage collector, and in the debugger.
    get_instmap(!.CLD, InstMap),
    InstMapDelta = goal_info_get_instmap_delta(GoalInfo),
    apply_instmap_delta(InstMapDelta, InstMap, ReturnInstMap),

    % Update the code generator state to reflect the situation after the call.
    handle_return(ArgsInfos, GoalInfo, NonLiveOutputs,
        ReturnInstMap, ReturnLiveLvalues, !.CI, !CLD),

    % If the call can fail, generate code to check for and handle the failure.
    remember_position(!.CLD, AfterReturn),
    handle_call_failure(CodeModel, GoalInfo, FailHandlingCode, !CI, !.CLD),
    reset_to_position(AfterReturn, !.CI, !:CLD),

    ( if
        goal_info_has_feature(GoalInfo, feature_debug_self_tail_rec_call),
        get_maybe_trace_info(!.CI, MaybeTraceInfo),
        MaybeTraceInfo = yes(TraceInfo)
    then
        generate_tailrec_event_code(TraceInfo, ArgsInfos, GoalId, Context,
            TraceTailRecResetAndEventCode, TailRecLabel, !CI, !CLD),
        JumpCode = from_list([
            llds_instr(livevals(LiveVals), ""),
            llds_instr(goto(code_label(TailRecLabel)),
                "tail recursive jump")
        ]),
        Code = SetupCode ++ TraceTailRecResetAndEventCode ++ JumpCode
    else
        Code = SetupCode ++ TraceResetCode ++ CallCode ++ FailHandlingCode
    ).

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

generate_generic_call(OuterCodeModel, GenericCall, ArgVars, Modes,
        MaybeRegTypes, Det, GoalInfo, Code, !CI, !CLD) :-
    % For a generic_call, we split the arguments into inputs and outputs,
    % put the inputs in the locations expected by mercury.do_call_closure in
    % runtime/mercury_ho_call.c, generate the call to that code, and pick up
    % the outputs from the locations that we know the runtime system leaves
    % them in.

    % `cast' differs from the other generic call types in that there is no
    % address. Also, live_vars.m assumes that casts do not require live
    % variables to be saved to the stack.
    (
        ( GenericCall = higher_order(_, _, _, _, _)
        ; GenericCall = class_method(_, _, _, _)
        ),
        generate_main_generic_call(OuterCodeModel, GenericCall, ArgVars, Modes,
            MaybeRegTypes, Det, GoalInfo, Code, !CI, !CLD)
    ;
        GenericCall = event_call(EventName),
        generate_event_call(EventName, ArgVars, GoalInfo, Code, !CI, !CLD)
    ;
        GenericCall = cast(_),
        ( if ArgVars = [InputArgVar, OutputArgVar] then
            get_var_table(!.CI, VarTable),
            lookup_var_entry(VarTable, InputArgVar, InputArgEntry),
            InputArgVarIsDummy = InputArgEntry ^ vte_is_dummy,
            (
                InputArgVarIsDummy = is_dummy_type,
                % Dummy types don't actually have values, which is
                % normally harmless. However, using the constant zero means
                % that we don't need to allocate space for an existentially
                % typed version of a dummy type. Using the constant zero
                % also avoids keeping pointers to memory that could be freed.
                Assigned = assign_const(int_const(0))
            ;
                InputArgVarIsDummy = is_not_dummy_type,
                Assigned = assign_copy(InputArgVar)
            ),
            generate_assign_builtin(OutputArgVar, Assigned, Code, !CLD)
        else
            unexpected($pred, "invalid type/inst cast call")
        )
    ).

:- pred generate_main_generic_call(code_model::in, generic_call::in,
    list(prog_var)::in, list(mer_mode)::in, arg_reg_type_info::in,
    determinism::in, hlds_goal_info::in, llds_code::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.

generate_main_generic_call(_OuterCodeModel, GenericCall, ArgVars, Modes,
        MaybeRegTypes, Det, GoalInfo, Code, !CI, !CLD) :-
    get_module_info(!.CI, ModuleInfo),
    arg_info.generic_call_arg_reg_types(ModuleInfo, GenericCall,
        ArgVars, MaybeRegTypes, ArgRegTypes),
    get_var_table(!.CI, VarTable),
    arg_info.compute_in_and_out_vars_sep_regs(ModuleInfo, VarTable,
        ArgVars, Modes, ArgRegTypes, InVarsR, InVarsF, OutVarsR, OutVarsF),
    module_info_get_globals(ModuleInfo, Globals),
    generic_call_info(Globals, GenericCall, length(InVarsR), length(InVarsF),
        CodeAddr, SpecifierArgInfos, FirstImmInputR, HoCallVariant),
    FirstImmInputF = 1,
    determinism_to_code_model(Det, CodeModel),
    (
        CodeModel = model_semi,
        FirstOutputR = 2
    ;
        ( CodeModel = model_det
        ; CodeModel = model_non
        ),
        FirstOutputR = 1
    ),
    FirstOutputF = 1,

    give_vars_consecutive_arg_infos(InVarsR, reg_r, FirstImmInputR, top_in,
        InVarArgInfosR),
    give_vars_consecutive_arg_infos(InVarsF, reg_f, FirstImmInputF, top_in,
        InVarArgInfosF),
    give_vars_consecutive_arg_infos(OutVarsR, reg_r, FirstOutputR, top_out,
        OutArgsInfosR),
    give_vars_consecutive_arg_infos(OutVarsF, reg_f, FirstOutputF, top_out,
        OutArgsInfosF),
    ArgInfos = list.condense([SpecifierArgInfos,
        InVarArgInfosR, InVarArgInfosF, OutArgsInfosR, OutArgsInfosF]),

    % Save the necessary vars on the stack and move the input args defined
    % by variables to their registers.
    setup_call(GoalInfo, ArgInfos, LiveVals0, SetupCode, !.CI, !CLD),
    kill_dead_input_vars(ArgInfos, GoalInfo, NonLiveOutputs, !CLD),

    % Move the input args not defined by variables to their registers.
    % Setting up these arguments last results in slightly more efficient code,
    % since we can use their registers when placing the variables.
    generic_call_nonvar_setup(GenericCall, HoCallVariant, InVarsR, InVarsF,
        NonVarCode, !CLD),

    extra_livevals(FirstImmInputR, ExtraLiveVals),
    set.insert_list(ExtraLiveVals, LiveVals0, LiveVals),

    prepare_for_call(CodeModel, GoalInfo, CallModel, TraceCode, !CI, !CLD),

    % Make the call.
    get_next_label(ReturnLabel, !CI),
    Context = goal_info_get_context(GoalInfo),
    GoalId = goal_info_get_goal_id(GoalInfo),

    % Figure out what variables will be live at the return point, and where,
    % for use in the accurate garbage collector, and in the debugger.
    get_instmap(!.CLD, InstMap),
    InstMapDelta = goal_info_get_instmap_delta(GoalInfo),
    apply_instmap_delta(InstMapDelta, InstMap, ReturnInstMap),

    % Update the code generator state to reflect the situation after the call.
    OutArgsInfos = OutArgsInfosR ++ OutArgsInfosF,
    handle_return(OutArgsInfos, GoalInfo, NonLiveOutputs,
        ReturnInstMap, ReturnLiveLvalues, !.CI, !CLD),

    get_maybe_containing_goal_map(!.CI, MaybeContainingGoalMap),
    (
        MaybeContainingGoalMap = yes(ContainingGoalMap),
        GoalPath = goal_id_to_forward_path(ContainingGoalMap, GoalId),
        MaybeGoalPath = yes(GoalPath)
    ;
        MaybeContainingGoalMap = no,
        MaybeGoalPath = no
    ),
    CallCode = from_list([
        llds_instr(livevals(LiveVals), ""),
        llds_instr(llcall(CodeAddr, code_label(ReturnLabel), ReturnLiveLvalues,
            Context, MaybeGoalPath, CallModel), "Setup and call"),
        llds_instr(label(ReturnLabel), "Continuation label")
    ]),

    % If the call can fail, generate code to check for and handle the failure.
    remember_position(!.CLD, AfterReturn),
    handle_call_failure(CodeModel, GoalInfo, FailHandlingCode, !CI, !.CLD),
    reset_to_position(AfterReturn, !.CI, !:CLD),

    Code = SetupCode ++ NonVarCode ++ TraceCode ++ CallCode ++
        FailHandlingCode.

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

:- pred generate_event_call(string::in, list(prog_var)::in, hlds_goal_info::in,
    llds_code::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.

generate_event_call(EventName, Args, GoalInfo, Code, !CI, !CLD) :-
    get_module_info(!.CI, ModuleInfo),
    module_info_get_event_set(ModuleInfo, EventSet),
    EventSpecMap = EventSet ^ event_set_spec_map,
    ( if
        event_attributes(EventSpecMap, EventName, Attributes),
        event_number(EventSpecMap, EventName, EventNumber)
    then
        generate_event_attributes(Attributes, Args, MaybeUserAttributes,
            AttrCodes, !.CI, !CLD),
        UserEventInfo = user_event_info(EventNumber, MaybeUserAttributes),
        generate_user_event_code(UserEventInfo, GoalInfo, EventCode,
            !CI, !CLD),
        Code = cord_list_to_cord(AttrCodes) ++ EventCode
    else
        unexpected($pred, "bad event name")
    ).

:- pred generate_event_attributes(list(event_attribute)::in,
    list(prog_var)::in, list(maybe(user_attribute))::out, list(llds_code)::out,
    code_info::in, code_loc_dep::in, code_loc_dep::out) is det.

generate_event_attributes([], !.Vars, [], [], _CI, !CLD) :-
    (
        !.Vars = [_ | _],
        unexpected($pred, "var")
    ;
        !.Vars = []
    ).
generate_event_attributes([Attribute | Attributes], !.Vars,
        [MaybeUserAttr | MaybeUserAttrs], [Code | Codes], CI, !CLD) :-
    SynthCall = Attribute ^ attr_maybe_synth_call,
    (
        SynthCall = no,
        (
            !.Vars = [Var | !:Vars],
            produce_variable(Var, Code, Rval, !CLD),
            UserAttr = user_attribute(Rval, Var),
            MaybeUserAttr = yes(UserAttr)
        ;
            !.Vars = [],
            unexpected($pred, "no var")
        )
    ;
        SynthCall = yes(_),
        MaybeUserAttr = no,
        Code = empty
    ),
    generate_event_attributes(Attributes, !.Vars, MaybeUserAttrs, Codes,
        CI, !CLD).

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

    % The registers before the first reg_r input argument are all live.
    %
:- pred extra_livevals(int::in, list(lval)::out) is det.

extra_livevals(FirstInput, ExtraLiveVals) :-
    extra_livevals_from(1, FirstInput, ExtraLiveVals).

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

extra_livevals_from(Reg, FirstInput, ExtraLiveVals) :-
    ( if Reg < FirstInput then
        ExtraLiveVals = [reg(reg_r, Reg) | ExtraLiveVals1],
        NextReg = Reg + 1,
        extra_livevals_from(NextReg, FirstInput, ExtraLiveVals1)
    else
        ExtraLiveVals = []
    ).

generic_call_info(Globals, GenericCall, NumInputArgsR, NumInputArgsF,
        CodeAddr, SpecifierArgInfos, FirstImmediateInputReg, HoCallVariant) :-
    (
        GenericCall = higher_order(PredVar, _, _, _, _),
        Reg = reg(reg_r, 1),
        SpecifierArgInfos = [PredVar - arg_info(Reg, top_in)],
        globals.lookup_int_option(Globals,
            max_specialized_do_call_closure, MaxSpec),
        ( if
            MaxSpec >= 0,
            NumInputArgsR =< MaxSpec,
            NumInputArgsF = 0
        then
            CodeAddr = do_call_closure(specialized_known(NumInputArgsR)),
            HoCallVariant = ho_call_known_num,
            FirstImmediateInputReg = 2
        else
            CodeAddr = do_call_closure(generic),
            HoCallVariant = ho_call_unknown,
            FirstImmediateInputReg = 3
        )
    ;
        GenericCall = class_method(TCVar, _, _, _),
        Reg = reg(reg_r, 1),
        SpecifierArgInfos = [TCVar - arg_info(Reg, top_in)],
        % XXX we do not use float registers for method calls yet
        ( if NumInputArgsF = 0 then
            globals.lookup_int_option(Globals,
                max_specialized_do_call_class_method, MaxSpec),
            ( if
                MaxSpec >= 0,
                NumInputArgsR =< MaxSpec
            then
                CodeAddr = do_call_class_method(
                    specialized_known(NumInputArgsR)),
                HoCallVariant = ho_call_known_num,
                FirstImmediateInputReg = 3
            else
                CodeAddr = do_call_class_method(generic),
                HoCallVariant = ho_call_unknown,
                FirstImmediateInputReg = 4
            )
        else
            sorry($pred, "float reg inputs")
        )
    ;
        % Events and casts are generated inline.
        ( GenericCall = event_call(_)
        ; GenericCall = cast(_)
        ),
        CodeAddr = do_not_reached,
        SpecifierArgInfos = [],
        FirstImmediateInputReg = 1,
        HoCallVariant = ho_call_unknown     % dummy; not used
    ).

    % Some of the values that generic call passes to the dispatch routine
    % to specify what code is being indirectly called come from HLDS
    % variables, while the others come from constants. The ones that come
    % from variables (the closure for a higher order call, the
    % typeclass_info for a method call) are set up together with the
    % arguments being passed the indirectly called code, since with eager
    % code generation this ensures that each target register is reserved
    % for the variable destined for it. This is set up by generic_call_info.
    % generic_call_nonvar_setup generates code to pass to the dispatch routine
    % the parts of the indirectly called code's identifier that come from
    % constants.
    %
:- pred generic_call_nonvar_setup(generic_call::in, known_call_variant::in,
    list(prog_var)::in, list(prog_var)::in,
    llds_code::out, code_loc_dep::in, code_loc_dep::out) is det.

generic_call_nonvar_setup(higher_order(_, _, _, _, _), HoCallVariant,
        InVarsR, InVarsF, Code, !CLD) :-
    (
        HoCallVariant = ho_call_known_num,
        Code = empty
    ;
        HoCallVariant = ho_call_unknown,
        clobber_reg(reg(reg_r, 2), !CLD),
        list.length(InVarsR, NumInVarsR),
        list.length(InVarsF, NumInVarsF),
        NumInVars = encode_num_generic_call_vars(NumInVarsR, NumInVarsF),
        Code = singleton(
            llds_instr(assign(reg(reg_r, 2), const(llconst_int(NumInVars))),
                "Assign number of immediate input arguments")
        )
    ).
generic_call_nonvar_setup(class_method(_, Method, _, _), HoCallVariant,
        InVarsR, InVarsF, Code, !CLD) :-
    (
        InVarsF = []
    ;
        InVarsF = [_ | _],
        sorry($pred, "float input reg")
    ),
    Method = method_proc_num(MethodNum),
    MethodNumConst = const(llconst_int(MethodNum)),
    (
        HoCallVariant = ho_call_known_num,
        clobber_reg(reg(reg_r, 2), !CLD),
        Code = singleton(
            llds_instr(assign(reg(reg_r, 2), MethodNumConst),
                "Index of class method in typeclass info")
        )
    ;
        HoCallVariant = ho_call_unknown,
        clobber_regs([reg(reg_r, 2), reg(reg_r, 3)], !CLD),
        list.length(InVarsR, NumInVarsR),
        % Currently we do not use float registers for method calls.
        NumInVarsF = 0,
        NumInVars = encode_num_generic_call_vars(NumInVarsR, NumInVarsF),
        Code = from_list([
            llds_instr(assign(reg(reg_r, 2), MethodNumConst),
                "Index of class method in typeclass info"),
            llds_instr(assign(reg(reg_r, 3), const(llconst_int(NumInVars))),
                "Assign number of immediate regular input arguments")
        ])
    ).
generic_call_nonvar_setup(event_call(_), _, _, _, _, !CLD) :-
    unexpected($pred, "event_call").
generic_call_nonvar_setup(cast(_), _, _, _, _, !CLD) :-
    unexpected($pred, "cast").

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

:- pred prepare_for_call(code_model::in, hlds_goal_info::in, call_model::out,
    llds_code::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.

prepare_for_call(CodeModel, GoalInfo, CallModel, TraceCode, !CI, !CLD) :-
    succip_is_used(!CI),
    ( if goal_info_has_feature(GoalInfo, feature_do_not_tailcall) then
        AllowLCO = do_not_allow_lco
    else
        AllowLCO = allow_lco
    ),
    (
        CodeModel = model_det,
        CallModel = call_model_det(AllowLCO)
    ;
        CodeModel = model_semi,
        CallModel = call_model_semidet(AllowLCO)
    ;
        CodeModel = model_non,
        may_use_nondet_tailcall(!.CLD, TailCallStatus),
        CallModel = call_model_nondet(TailCallStatus),
        set_resume_point_and_frame_to_unknown(!CLD)
    ),
    trace_prepare_for_call(!.CI, TraceCode).

:- pred handle_call_failure(code_model::in, hlds_goal_info::in, llds_code::out,
    code_info::in, code_info::out, code_loc_dep::in) is det.

handle_call_failure(CodeModel, GoalInfo, FailHandlingCode, !CI, !.CLD) :-
    (
        CodeModel = model_semi,
        Detism = goal_info_get_determinism(GoalInfo),
        ( if Detism = detism_failure then
            generate_failure(FailHandlingCode, !CI, !.CLD)
        else
            get_next_label(ContLab, !CI),
            FailTestCode = singleton(
                llds_instr(if_val(lval(reg(reg_r, 1)), code_label(ContLab)),
                    "test for success")
            ),
            generate_failure(FailCode, !CI, !.CLD),
            ContLabelCode = singleton(
                llds_instr(label(ContLab), "")
            ),
            FailHandlingCode = FailTestCode ++ FailCode ++ ContLabelCode
        )
    ;
        ( CodeModel = model_det
        ; CodeModel = model_non
        ),
        FailHandlingCode = empty
    ).

:- pred call_comment(code_info::in, pred_id::in, code_model::in, string::out)
    is det.

call_comment(CI, PredId, CodeModel, Msg) :-
    (
        CodeModel = model_det,
        BaseMsg = "branch to det procedure"
    ;
        CodeModel = model_semi,
        BaseMsg = "branch to semidet procedure"
    ;
        CodeModel = model_non,
        BaseMsg = "branch to nondet procedure"
    ),
    get_auto_comments(CI, AutoComments),
    (
        AutoComments = yes,
        get_module_info(CI, ModuleInfo),
        module_info_pred_info(ModuleInfo, PredId, PredInfo),
        PredName = pred_info_name(PredInfo),
        Msg = BaseMsg ++ " " ++ PredName
    ;
        AutoComments = no,
        Msg = BaseMsg
    ).

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

    % After we have placed all the input variables in their registers,
    % we will want to clear all the registers so we can start updating
    % the code generator state to reflect their contents after the call.
    % (In the case of higher order calls, we may place some constant
    % input arguments in registers before clearing them.) The register
    % clearing code complains if it is asked to dispose of the last copy
    % of a still live variable, so before we clear the registers, we must
    % make forward-dead all the variables that are in this goal's
    % post-death set. However, a variable may be in this set even if it
    % is not live before the call, if it is bound by the call. (This can
    % happen when the caller ignores some of the output arguments of the
    % called procedure.) We handle such variables not by making them
    % forward-dead but by simply never making them forward-live in the
    % first place.
    %
    % ArgsInfos should list all the output arguments of the call.
    % It may contain the input arguments as well; kill_dead_input_vars
    % and handle_return ignore them.
    %
:- pred kill_dead_input_vars(assoc_list(prog_var, arg_info)::in,
    hlds_goal_info::in, set_of_progvar::out,
    code_loc_dep::in, code_loc_dep::out) is det.

kill_dead_input_vars(ArgsInfos, GoalInfo, NonLiveOutputs, !CLD) :-
    get_forward_live_vars(!.CLD, Liveness),
    find_nonlive_outputs(ArgsInfos, Liveness, set_of_var.init, NonLiveOutputs),
    goal_info_get_post_deaths(GoalInfo, PostDeaths),
    set_of_var.difference(PostDeaths, NonLiveOutputs, ImmediatePostDeaths),
    make_vars_forward_dead(ImmediatePostDeaths, !CLD).

:- pred handle_return(assoc_list(prog_var, arg_info)::in,
    hlds_goal_info::in, set_of_progvar::in, instmap::in, list(liveinfo)::out,
    code_info::in, code_loc_dep::in, code_loc_dep::out) is det.

handle_return(ArgsInfos, GoalInfo, _NonLiveOutputs, ReturnInstMap,
        ReturnLiveLvalues, CI, !CLD) :-
    InstMapDelta = goal_info_get_instmap_delta(GoalInfo),
    ( if instmap_delta_is_reachable(InstMapDelta) then
        OkToDeleteAny = no
    else
        OkToDeleteAny = yes
    ),
    clear_all_registers(OkToDeleteAny, !CLD),
    get_forward_live_vars(!.CLD, Liveness),
    rebuild_registers(ArgsInfos, Liveness, OutputArgLocs, !CLD),
    generate_return_live_lvalues(CI, !.CLD, OutputArgLocs,
        ReturnInstMap, OkToDeleteAny, ReturnLiveLvalues).

:- pred find_nonlive_outputs(assoc_list(prog_var, arg_info)::in,
    set_of_progvar::in, set_of_progvar::in, set_of_progvar::out) is det.

find_nonlive_outputs([], _, !NonLiveOutputs).
find_nonlive_outputs([Var - arg_info(_ArgLoc, Mode) | Args],
        Liveness, !NonLiveOutputs) :-
    (
        Mode = top_out,
        ( if set_of_var.member(Liveness, Var) then
            true
        else
            set_of_var.insert(Var, !NonLiveOutputs)
        )
    ;
        ( Mode = top_in
        ; Mode = top_unused
        )
    ),
    find_nonlive_outputs(Args, Liveness, !NonLiveOutputs).

:- pred rebuild_registers(assoc_list(prog_var, arg_info)::in,
    set_of_progvar::in, assoc_list(prog_var, arg_loc)::out,
    code_loc_dep::in, code_loc_dep::out) is det.

rebuild_registers([], _, [], !CLD).
rebuild_registers([Var - arg_info(ArgLoc, Mode) | Args], Liveness,
        OutputArgLocs, !CLD) :-
    rebuild_registers(Args, Liveness, OutputArgLocs1, !CLD),
    ( if
        Mode = top_out,
        set_of_var.member(Liveness, Var)
    then
        code_util.arg_loc_to_register(ArgLoc, Register),
        set_var_location(Var, Register, !CLD),
        OutputArgLocs = [Var - ArgLoc | OutputArgLocs1]
    else
        OutputArgLocs = OutputArgLocs1
    ).

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

generate_builtin(CodeModel, PredId, ProcId, Args, GoalInfo, Code, !CI, !CLD) :-
    get_module_info(!.CI, ModuleInfo),
    ModuleName = predicate_module(ModuleInfo, PredId),
    PredName = predicate_name(ModuleInfo, PredId),
    builtin_ops.translate_builtin(ModuleName, PredName, ProcId, Args,
        SimpleCode),
    (
        CodeModel = model_det,
        (
            SimpleCode = assign(Var, AssignExpr),
            NonLocals = goal_info_get_nonlocals(GoalInfo),
            ( if set_of_var.contains(NonLocals, Var) then
                generate_assign_builtin(Var, AssignExpr, Code, !CLD)
            else
                % The output of this builtin is unused.
                Code = empty
            )
        ;
            SimpleCode = ref_assign(AddrVar, ValueVar),
            produce_variable(AddrVar, AddrVarCode, AddrRval, !CLD),
            produce_variable(ValueVar, ValueVarCode, ValueRval, !CLD),
            StoreInstr = llds_instr(assign(mem_ref(AddrRval), ValueRval), ""),
            StoreCode = singleton(StoreInstr),
            Code = AddrVarCode ++ ValueVarCode ++ StoreCode
        ;
            SimpleCode = test(_),
            unexpected($pred, "malformed model_det builtin predicate")
        ;
            SimpleCode = noop(DefinedVars),
            NonLocals = goal_info_get_nonlocals(GoalInfo),
            list.filter(set_of_var.contains(NonLocals),
                DefinedVars, UsedDefinedVars),
            list.foldl(magically_put_var_in_unused_reg, UsedDefinedVars, !CLD),
            Code = empty
        )
    ;
        CodeModel = model_semi,
        (
            SimpleCode = test(TestExpr),
            generate_simple_test(TestExpr, Rval, ArgCode, !CLD),
            fail_if_rval_is_false(Rval, TestCode, !CI, !CLD),
            Code = ArgCode ++ TestCode
        ;
            SimpleCode = assign(_, _),
            unexpected($pred, "malformed model_semi builtin predicate")
        ;
            SimpleCode = ref_assign(_, _),
            unexpected($pred, "malformed model_semi builtin predicate")
        ;
            SimpleCode = noop(_),
            unexpected($pred, "malformed model_semi builtin predicate")
        )
    ;
        CodeModel = model_non,
        unexpected($pred, "model_non builtin predicate")
    ).

:- pred generate_assign_builtin(prog_var::in,
    simple_assigned_expr(prog_var)::in, llds_code::out,
    code_loc_dep::in, code_loc_dep::out) is det.

generate_assign_builtin(Var, AssignedExpr, Code, !CLD) :-
    ( if variable_is_forward_live(!.CLD, Var) then
        Rval = convert_simple_assigned_expr(AssignedExpr),
        assign_expr_to_var(Var, Rval, Code, !CLD)
    else
        Code = empty
    ).

:- func convert_simple_assigned_expr(simple_assigned_expr(prog_var)) = rval.

convert_simple_assigned_expr(AssignedExpr) = Rval :-
    (
        AssignedExpr = assign_copy(Var),
        Rval = var(Var)
    ;
        AssignedExpr = assign_const(Const),
        Rval = convert_simple_const(Const)
    ;
        AssignedExpr = assign_binary(BinOp, X, Y),
        Rval = binop(BinOp, var(X), var(Y))
    ;
        AssignedExpr = assign_binary_lc(BinOp, Const, Y),
        Rval = binop(BinOp, convert_simple_const(Const), var(Y))
    ;
        AssignedExpr = assign_unary(UnOp, X),
        Rval = unop(UnOp, var(X))
    ).

    % This function is needed because simple_const is defined in
    % backend_libs.builtin_ops.m, which is not supposed to know about
    % backend-specific types such as rval.
    %
:- func convert_simple_const(simple_const) = rval.

convert_simple_const(int_const(Int)) =          const(llconst_int(Int)).
convert_simple_const(int8_const(Int8)) =        const(llconst_int8(Int8)).
convert_simple_const(int16_const(Int16)) =      const(llconst_int16(Int16)).
convert_simple_const(int32_const(Int32)) =      const(llconst_int32(Int32)).
convert_simple_const(int64_const(Int64)) =      const(llconst_int64(Int64)).
convert_simple_const(uint_const(UInt)) =        const(llconst_uint(UInt)).
convert_simple_const(uint8_const(UInt8)) =      const(llconst_uint8(UInt8)).
convert_simple_const(uint16_const(UInt16)) =    const(llconst_uint16(UInt16)).
convert_simple_const(uint32_const(UInt32)) =    const(llconst_uint32(UInt32)).
convert_simple_const(uint64_const(UInt64)) =    const(llconst_uint64(UInt64)).
convert_simple_const(float_const(Float)) =      const(llconst_float(Float)).

:- pred generate_simple_test(simple_test_expr(prog_var)::in, rval::out,
    llds_code::out, code_loc_dep::in, code_loc_dep::out) is det.

generate_simple_test(TestExpr, Rval, ArgCode, !CLD) :-
    TestExpr = binary_test(BinOp, VarX, VarY),
    produce_variable(VarX, CodeX, RvalX, !CLD),
    produce_variable(VarY, CodeY, RvalY, !CLD),
    Rval = binop(BinOp, RvalX, RvalY),
    ArgCode = CodeX ++ CodeY.

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

input_arg_locs([], []).
input_arg_locs([Var - arg_info(Loc, Mode) | Args], Vs) :-
    input_arg_locs(Args, Vs0),
    (
        Mode = top_in,
        Vs = [Var - Loc | Vs0]
    ;
        ( Mode = top_out
        ; Mode = top_unused
        ),
        Vs = Vs0
    ).

output_arg_locs([], []).
output_arg_locs([Var - arg_info(Loc, Mode) | Args], Vs) :-
    output_arg_locs(Args, Vs0),
    (
        Mode = top_out,
        Vs = [Var - Loc | Vs0]
    ;
        ( Mode = top_in
        ; Mode = top_unused
        ),
        Vs = Vs0
    ).

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

:- pred give_vars_consecutive_arg_infos(list(prog_var)::in,
    reg_type::in, int::in, top_functor_mode::in,
    assoc_list(prog_var, arg_info)::out) is det.

give_vars_consecutive_arg_infos([], _RegType, _N, _TopFunctorMode, []).
give_vars_consecutive_arg_infos([Var | Vars], RegType, N, TopFunctorMode,
        [Var - ArgInfo | ArgInfos]) :-
    ArgInfo = arg_info(reg(RegType, N), TopFunctorMode),
    give_vars_consecutive_arg_infos(Vars, RegType, N + 1, TopFunctorMode,
        ArgInfos).

%---------------------------------------------------------------------------%
:- end_module ll_backend.call_gen.
%---------------------------------------------------------------------------%
